diff options
author | Yuuta Liang <yuutaw@student.cs.ubc.ca> | 2023-10-26 05:00:12 +0800 |
---|---|---|
committer | Yuuta Liang <yuutaw@student.cs.ubc.ca> | 2023-10-26 05:00:12 +0800 |
commit | 578b7d1db256d9a582cef45ae5d13d858a977416 (patch) | |
tree | b856cc5af32a0d649321f501f2966d013cade6c0 /src/main | |
parent | f73bca3372a31f360d894dcbe8580cef779af739 (diff) | |
download | jca-578b7d1db256d9a582cef45ae5d13d858a977416.tar jca-578b7d1db256d9a582cef45ae5d13d858a977416.tar.gz jca-578b7d1db256d9a582cef45ae5d13d858a977416.tar.bz2 jca-578b7d1db256d9a582cef45ae5d13d858a977416.zip |
Add persistence
Signed-off-by: Yuuta Liang <yuutaw@student.cs.ubc.ca>
Diffstat (limited to 'src/main')
-rw-r--r-- | src/main/model/asn1/exceptions/InvalidDBException.java | 10 | ||||
-rw-r--r-- | src/main/model/ca/CertificationAuthority.java | 80 | ||||
-rw-r--r-- | src/main/persistence/Decoder.java | 284 | ||||
-rw-r--r-- | src/main/persistence/FS.java | 46 | ||||
-rw-r--r-- | src/main/ui/IssueScreen.java | 8 | ||||
-rw-r--r-- | src/main/ui/JCA.java | 95 | ||||
-rw-r--r-- | src/main/ui/MainScreen.java | 6 | ||||
-rw-r--r-- | src/main/ui/MgmtScreen.java | 3 | ||||
-rw-r--r-- | src/main/ui/TemplateSetScreen.java | 8 | ||||
-rw-r--r-- | src/main/ui/TemplatesScreen.java | 2 | ||||
-rw-r--r-- | src/main/ui/Utils.java | 6 |
11 files changed, 516 insertions, 32 deletions
diff --git a/src/main/model/asn1/exceptions/InvalidDBException.java b/src/main/model/asn1/exceptions/InvalidDBException.java new file mode 100644 index 0000000..4068a4b --- /dev/null +++ b/src/main/model/asn1/exceptions/InvalidDBException.java @@ -0,0 +1,10 @@ +package model.asn1.exceptions; + +/** + * The database is invalid. + */ +public class InvalidDBException extends RuntimeException { + public InvalidDBException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/model/ca/CertificationAuthority.java b/src/main/model/ca/CertificationAuthority.java index feb557c..038d209 100644 --- a/src/main/model/ca/CertificationAuthority.java +++ b/src/main/model/ca/CertificationAuthority.java @@ -35,6 +35,8 @@ import java.util.stream.Stream; * Holds a CA private key, its certificate, signed / revoked list, template list, and logs list. */ public class CertificationAuthority { + public static final int SERIAL_DEFAULT = 1; + /** * The RSA2048 private key. */ @@ -81,14 +83,45 @@ public class CertificationAuthority { private final String user; /** - * EFFECT: Init with a null key and null certificate, empty signed, revoked template, and log list, serial at 1, and - * user "yuuta". + * EFFECT: Init with the given parameters and user "yuuta". + * Throws {@link NoSuchAlgorithmException} if the key is specified but RSA is not supported. + * Throws {@link InvalidKeySpecException} if the key specified is invalid. + * Throws {@link InvalidCAException} or {@link ParseException} if the CA specified is invalid. + * REQUIRES: n / p / e must be either all null or all non-null containing RSA2048 module and exponents. + * If certificate is non-null, n / p / e must be non-null. + */ + public CertificationAuthority(BigInteger n, BigInteger p, BigInteger e, + Certificate certificate, + List<Certificate> signed, + int serial, + List<RevokedCertificate> revoked, + List<Template> templates, + List<AuditLogEntry> logs) + throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidCAException, ParseException { + if (n != null) { + setKey(n, p, e); + } + if (certificate != null) { + validateCertificate(certificate); + } + this.certificate = certificate; + this.signed = new ArrayList<>(signed); + this.serial = serial; + this.revoked = new ArrayList<>(revoked); + this.templates = new ArrayList<>(templates); + this.logs = new ArrayList<>(logs); + this.user = "yuuta"; + } + + /** + * EFFECT: Init with a null key and null certificate, empty signed, revoked template, and log list, + * serial at SERIAL_DEFAULT, and user "yuuta". */ public CertificationAuthority() { this.key = null; this.publicKey = null; this.certificate = null; - this.serial = 1; + this.serial = SERIAL_DEFAULT; this.signed = new ArrayList<>(); this.revoked = new ArrayList<>(); this.templates = new ArrayList<>(); @@ -111,17 +144,29 @@ public class CertificationAuthority { } /** - * EFFECTS: Load the RSA private and public exponents. This action will be logged. + * EFFECTS: Load the RSA private and public exponents. * Throws {@link NoSuchAlgorithmException} if RSA is not available on the platform. * Throws {@link InvalidKeySpecException} if the input is invalid. * REQUIRES: getPublicKey() is null (i.e., no private key had been installed) * MODIFIES: this */ - public void loadKey(BigInteger n, BigInteger p, BigInteger e) + private void setKey(BigInteger n, BigInteger p, BigInteger e) throws NoSuchAlgorithmException, InvalidKeySpecException { this.key = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new RSAPrivateKeySpec(n, p)); this.publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(n, e)); + } + + /** + * EFFECTS: Load the RSA private and public exponents. This action will be logged. + * Throws {@link NoSuchAlgorithmException} if RSA is not available on the platform. + * Throws {@link InvalidKeySpecException} if the input is invalid. + * REQUIRES: getPublicKey() is null (i.e., no private key had been installed) + * MODIFIES: this + */ + public void loadKey(BigInteger n, BigInteger p, BigInteger e) + throws NoSuchAlgorithmException, InvalidKeySpecException { + setKey(n, p, e); log("Installed CA private key."); } @@ -185,6 +230,22 @@ public class CertificationAuthority { } /** + * EFFECT: Validate the CA certificate. Throws {@link InvalidCAException} if any of the + * following are violated: + * - It must be a v3 certificate + * - The new certificate must have the same algorithm and public key as getPublicKey() + * - It must have basicConstraints { cA = TRUE } + * - It must contain key usage Digital Signature, Certificate Sign, CRL Sign + * Throws {@link ParseException} if the cert has invalid extension values. + */ + private void validateCertificate(Certificate certificate) throws InvalidCAException, ParseException { + validateCACertificateVersion(certificate); + validateCACertificatePublicKey(certificate); + validateCACertificateBasicConstraints(certificate); + validateCACertificateKeyUsage(certificate); + } + + /** * EFFECT: Install the CA certificate. Throws {@link InvalidCAException} if any of the * following are violated: * - It must be a v3 certificate @@ -198,10 +259,7 @@ public class CertificationAuthority { * MODIFIES: this */ public void installCertificate(Certificate certificate) throws InvalidCAException, ParseException { - validateCACertificateVersion(certificate); - validateCACertificatePublicKey(certificate); - validateCACertificateBasicConstraints(certificate); - validateCACertificateKeyUsage(certificate); + validateCertificate(certificate); this.certificate = certificate; log("CA certificate is installed."); } @@ -477,4 +535,8 @@ public class CertificationAuthority { public RSAPublicKey getPublicKey() { return publicKey; } + + public RSAPrivateKey getKey() { + return key; + } } diff --git a/src/main/persistence/Decoder.java b/src/main/persistence/Decoder.java new file mode 100644 index 0000000..799a4ad --- /dev/null +++ b/src/main/persistence/Decoder.java @@ -0,0 +1,284 @@ +package persistence; + +import model.asn1.ASN1Object; +import model.asn1.ASN1Time; +import model.asn1.Int; +import model.asn1.exceptions.InvalidCAException; +import model.asn1.exceptions.InvalidDBException; +import model.asn1.exceptions.ParseException; +import model.asn1.parsing.BytesReader; +import model.ca.AuditLogEntry; +import model.ca.CertificationAuthority; +import model.ca.Template; +import model.pki.cert.Certificate; +import model.pki.crl.Reason; +import model.pki.crl.RevokedCertificate; +import model.x501.Name; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import ui.Utils; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +/** + * Util class that encodes / decodes the CA into JSON and vice-versa. + */ +public class Decoder { + /** + * EFFECTS: Convert the list into a JSONArray using the supplied mapper and add to the JSONObject. Nothing is added + * if the given list is empty. + */ + private static <T> void encodeList(JSONObject o, String key, List<T> list, Function<? super T, ?> mapper) { + if (list.isEmpty()) { + return; + } + o.put(key, new JSONArray(list.stream().map(mapper).collect(Collectors.toList()))); + } + + /** + * EFFECTS: Decode the JSONArray into a list of specific objects, using the given mapper. Return empty list if the + * o does not contain key. + * REQUIRES: If o contains key, it must be a JSONArray. + */ + private static <T> List<T> decodeListFromString(final JSONObject o, String key, + Function<? super String, ? extends T> mapper) { + return o.keySet().contains(key) + ? StreamSupport.stream(o.getJSONArray(key).spliterator(), false) + .map(obj -> mapper.apply((String) obj)) + .collect(Collectors.toList()) + : Collections.emptyList(); + } + + /** + * EFFECTS: Decode the JSONArray into a list of specific objects, using the given mapper. Return empty list if the + * o does not contain key. + * REQUIRES: If o contains key, it must be a JSONArray. + */ + private static <T> List<T> decodeListFromObject(final JSONObject o, String key, + Function<? super JSONObject, ? extends T> mapper) { + return o.keySet().contains(key) + ? StreamSupport.stream(o.getJSONArray(key).spliterator(), false) + .map(obj -> mapper.apply((JSONObject) obj)) + .collect(Collectors.toList()) + : Collections.emptyList(); + } + + /** + * EFFECTS: Decode the JSON object into CertificationAuthority + * Throws {@link InvalidDBException} if the JSON is invalid. + * Throws {@link NoSuchAlgorithmException} if RSA is not supported. + * JSON should be <pre> + * { + * "key": { + * "n": "octet string", + * "p": "octet string", + * "e": "octet string" + * }, + * "cert": "-----BEGIN CERTIFICATE-----\n...", + * "templates": [ + * { + * "name": "123", + * "enabled": true, + * "subject": "-----BEGIN DISTINGUISHED NAME-----\n...", + * "validity": 100 + * } + * ], + * "signed": [ + * "-----BEGIN CERTIFICATE-----\n...", + * ], + * "revoked": [ + * { + * "serial": "octet string", + * "date": "-----BEGIN DATE-----\n", + * "reason": "KEY_COMPROMISED" + * } + * ], + * "logs": [ + * { + * "user": "", + * "time": "ISO 8601", + * "action": "" + * } + * ], + * "serial": 1 + * } + * </pre> + * Missing fields will be default. + */ + public static CertificationAuthority decodeCA(JSONObject o) throws InvalidDBException, NoSuchAlgorithmException { + try { + Certificate caCert = o.keySet().contains("cert") ? decodeCertificate(o.getString("cert")) : null; + final List<Template> templates = decodeListFromObject(o, "templates", Decoder::decodeTemplate); + final List<Certificate> signed = decodeListFromString(o, "signed", Decoder::decodeCertificate); + final List<RevokedCertificate> revoked = + decodeListFromObject(o, "revoked", Decoder::decodeRevokedCertificate); + final List<AuditLogEntry> logs = decodeListFromObject(o, "logs", Decoder::decodeLog); + final int serial = o.keySet().contains("serial") ? o.getInt("serial") : + CertificationAuthority.SERIAL_DEFAULT; + final BigInteger n = o.keySet().contains("key") + ? new BigInteger(o.getJSONObject("key").getString("n"), 16) : null; + final BigInteger p = o.keySet().contains("key") + ? new BigInteger(o.getJSONObject("key").getString("p"), 16) : null; + final BigInteger e = o.keySet().contains("key") + ? new BigInteger(o.getJSONObject("key").getString("e"), 16) : null; + return new CertificationAuthority(n, p, e, caCert, signed, serial, revoked, templates, logs); + } catch (InvalidKeySpecException | NullPointerException | ParseException + | InvalidCAException | NumberFormatException | JSONException ex) { + throw new InvalidDBException("Invalid JSON format", ex); + } + } + + /** + * EFFECTS: Encode the CertificationAuthority to JSON. + */ + public static JSONObject encodeCA(CertificationAuthority ca) { + JSONObject obj = new JSONObject(); + if (ca.getKey() != null) { + obj.put("key", encodeKey(ca.getKey(), ca.getPublicKey())); + } + if (ca.getCertificate() != null) { + obj.put("cert", encodeCertificate(ca.getCertificate())); + } + if (ca.getSerial() != CertificationAuthority.SERIAL_DEFAULT) { + obj.put("serial", ca.getSerial()); + } + encodeList(obj, "templates", ca.getTemplates(), Decoder::encodeTemplate); + encodeList(obj, "signed", ca.getSigned(), Decoder::encodeCertificate); + encodeList(obj, "revoked", ca.getRevoked(), Decoder::encodeRevokedCertificate); + encodeList(obj, "logs", ca.getLogs(), Decoder::encodeLog); + return obj; + } + + /** + * EFFECTS: Encode the n / p / e modulus and exponents into JSON. + */ + private static JSONObject encodeKey(RSAPrivateKey privateKey, RSAPublicKey publicKey) { + JSONObject obj = new JSONObject(); + obj.put("n", privateKey.getModulus().toString(16)); + obj.put("p", privateKey.getPrivateExponent().toString(16)); + obj.put("e", publicKey.getPublicExponent().toString(16)); + return obj; + } + + /** + * EFFECTS: Encode the log entry into a JSON object. + */ + private static JSONObject encodeLog(AuditLogEntry entry) { + JSONObject o = new JSONObject(); + o.put("user", entry.getUser()); + o.put("time", entry.getTime().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); + o.put("action", entry.getAction()); + return o; + } + + /** + * EFFECTS: Decode the log entry from JSON object. + * Throws {@link InvalidDBException} if the input is invalid. + * REQUIRES: { "user": "", "time": "ISO_LOCAL_DATE_TIME", "action": "" } + */ + private static AuditLogEntry decodeLog(JSONObject o) throws InvalidDBException { + try { + return new AuditLogEntry(o.getString("user"), + ZonedDateTime.parse(o.getString("time"), DateTimeFormatter.ISO_ZONED_DATE_TIME), + o.getString("action")); + } catch (DateTimeParseException e) { + throw new InvalidDBException("Invalid date", e); + } + } + + /** + * EFFECTS: Encode the templates into a JSON array. + */ + private static JSONObject encodeTemplate(Template template) { + JSONObject o = new JSONObject(); + o.put("name", template.getName()); + o.put("enabled", template.isEnabled()); + if (template.getSubject() != null) { + o.put("subject", Utils.toPEM(template.getSubject().encodeDER(), "DISTINGUISHED NAME")); + } + o.put("validity", template.getValidity()); + return o; + } + + /** + * EFFECTS: Decode the JSON object into a Template. + * Throws {@link InvalidDBException} if the subject is invalid. + * REQUIRES: { "name": "string", "enabled": boolean, "name": "-----BEGIN DISTINGUISHED NAME-----", "validity": long} + */ + private static Template decodeTemplate(JSONObject o) throws InvalidDBException { + try { + return new Template(o.getString("name"), + o.getBoolean("enabled"), + o.keySet().contains("subject") ? new Name(new BytesReader(Utils.parsePEM( + Utils.byteToByte(o.getString("subject").getBytes()), "DISTINGUISHED NAME")), + false) : null, + o.getLong("validity")); + } catch (ParseException e) { + throw new InvalidDBException("Invalid template", e); + } + } + + /** + * EFFECTS: Encode the certificate to PEM. + */ + private static String encodeCertificate(Certificate cert) { + return Utils.toPEM(cert.encodeDER(), "CERTIFICATE"); + } + + /** + * EFFECTS: Decode the JSON pem into Certificate. + * Throws {@link InvalidDBException} if the value cannot be parsed. + * REQUIRES: -----BEGIN CERTIFICATE----- ... + */ + private static Certificate decodeCertificate(String pem) { + try { + return new Certificate(new BytesReader( + Utils.parsePEM(Utils.byteToByte(pem.getBytes(StandardCharsets.UTF_8)), "CERTIFICATE")), + false); + } catch (ParseException e) { + throw new InvalidDBException("Invalid certificate", e); + } + } + + /** + * EFFECTS: Encode the RevokedCertificate into a JSON object. + */ + private static JSONObject encodeRevokedCertificate(RevokedCertificate rev) { + JSONObject o = new JSONObject(); + o.put("serial", rev.getSerialNumber().getValue().toString(16)); + o.put("date", Utils.toPEM(rev.getRevocationDate().encodeDER(), "DATE")); + o.put("reason", rev.getReason().name()); + return o; + } + + /** + * EFFECTS: Decode the RevokedCertificate from JSON. + * Throws {@link InvalidDBException} if the value cannot be parsed. + * REQUIRES: { "serial": "12ab", "date": "-----BEGIN DATE-----", "reason": "KEY_COMPROMISED" } + */ + private static RevokedCertificate decodeRevokedCertificate(JSONObject o) { + try { + return new RevokedCertificate(ASN1Object.TAG_SEQUENCE, null, + new Int(Int.TAG, null, new BigInteger(o.getString("serial"), 16)), + (ASN1Time) ASN1Object.parse(new BytesReader(Utils.parsePEM(Utils.byteToByte(o.getString("date") + .getBytes(StandardCharsets.UTF_8)), "DATE")), false), + Reason.valueOf(o.getString("reason"))); + } catch (ParseException | IllegalArgumentException e) { + throw new InvalidDBException("Invalid revoked certificate", e); + } + } +} diff --git a/src/main/persistence/FS.java b/src/main/persistence/FS.java new file mode 100644 index 0000000..3b5222d --- /dev/null +++ b/src/main/persistence/FS.java @@ -0,0 +1,46 @@ +package persistence; + +import model.asn1.exceptions.InvalidDBException; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +/** + * Util class for file-system IO. + */ +public class FS { + /** + * EFFECTS: Read and decode the content of given path as UTF-8 into JSON. + * Throws {@link InvalidDBException} if IO error occurs or the input cannot be parsed. + */ + public static JSONObject read(Path path) throws InvalidDBException { + try { + final InputStream fd = Files.newInputStream(path, StandardOpenOption.READ); + final JSONObject obj = new JSONObject(new JSONTokener(fd)); + fd.close(); + return obj; + } catch (IOException | JSONException e) { + throw new InvalidDBException("Cannot read or parse the file", e); + } + } + + /** + * EFFECTS: Write the UTF-8 encoded JSON to the given path. + * open(2) parameters: <pre>O_WRONLY | O_TRUNC | O_CREAT</pre> + * Throws {@link IOException} if the file cannot be opened or written. + */ + public static void write(Path path, JSONObject obj) throws IOException { + final OutputStream fd = Files.newOutputStream(path, + StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); + fd.write(obj.toString().getBytes(StandardCharsets.UTF_8)); + fd.close(); + } +} diff --git a/src/main/ui/IssueScreen.java b/src/main/ui/IssueScreen.java index 5e3ad50..3e70a0a 100644 --- a/src/main/ui/IssueScreen.java +++ b/src/main/ui/IssueScreen.java @@ -4,6 +4,7 @@ import model.asn1.exceptions.ParseException; import model.ca.Template; import model.csr.CertificationRequest; import model.pki.cert.Certificate; +import model.x501.Name; /** * The screen that accepts a CSR and template and allows user to change its properties and issue. @@ -65,6 +66,7 @@ public class IssueScreen implements UIHandler { public void commit() { try { Certificate certificate = session.getCa().signCert(incomingCSR.getCertificationRequestInfo(), template); + session.save(); System.out.println(Utils.toPEM(certificate.encodeDER(), "CERTIFICATE")); session.setScreen(Screen.MAIN); } catch (Throwable e) { @@ -78,7 +80,11 @@ public class IssueScreen implements UIHandler { */ private void handleIssueSetSubject(String val) { try { - template = new Template(template.getName(), template.isEnabled(), val, template.getValidity()); + if (val == null) { + template = new Template(template.getName(), template.isEnabled(), (Name) null, template.getValidity()); + } else { + template = new Template(template.getName(), template.isEnabled(), val, template.getValidity()); + } } catch (ParseException e) { System.out.println(e.getMessage()); } diff --git a/src/main/ui/JCA.java b/src/main/ui/JCA.java index 882c546..420ec10 100644 --- a/src/main/ui/JCA.java +++ b/src/main/ui/JCA.java @@ -1,9 +1,14 @@ package ui; +import model.asn1.exceptions.InvalidDBException; import model.asn1.exceptions.ParseException; import model.ca.CertificationAuthority; +import persistence.Decoder; +import persistence.FS; +import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.util.Scanner; @@ -13,6 +18,11 @@ import java.util.Scanner; */ public class JCA { /** + * Default db file (./data/ca.json) + */ + private static final Path PATH_DEFAULT = Path.of("data", "ca.json"); + + /** * Instances of the five screens; */ private final UIHandler mainScreen; @@ -23,13 +33,18 @@ public class JCA { /** * The CA */ - private final CertificationAuthority ca; + private CertificationAuthority ca; /** * The current screen. */ private UIHandler screen; /** + * There are unsaved changes. + */ + private boolean unsaved = false; + + /** * EFFECTS: Init with main screen and empty CA. No private key and no CA cert. * Throws {@link NoSuchAlgorithmException} when crypto issue happens. */ @@ -106,34 +121,72 @@ public class JCA { screen.enter(args); } + /** + * EFFECTS: Read the database file and replace all local states. + * MODIFIES: this + */ + private void load() { + if (unsaved) { + System.out.println("Current database is not saved yet."); + return; + } + try { + this.ca = Decoder.decodeCA(FS.read(PATH_DEFAULT)); + } catch (InvalidDBException | NoSuchAlgorithmException e) { + System.out.println(e.getMessage()); + if (e.getCause() != null) { + System.out.println(e.getCause().getMessage()); + e.getCause().printStackTrace(); + } + } + } + private void handleLine(String... args) { if (args[0].equals("log")) { ca.getLogs().forEach(System.out::println); return; } - switch (args[0]) { - case "help": - screen.help(); - System.out.println("log\tView audit logs"); - break; - case "show": - screen.show(); - break; - case "commit": - screen.commit(); - break; - case "exit": - setScreen(screen.exit()); - break; - default: - screen.command(args); - break; + if ("help".equals(args[0])) { + screen.help(); + System.out.println("log\tView audit logs"); + System.out.println("load\tLoad database"); + System.out.println("save\tSave database"); + } else if ("commit".equals(args[0])) { + screen.commit(); + } else if ("show".equals(args[0])) { + screen.show(); + } else if ("exit".equals(args[0])) { + setScreen(screen.exit()); + } else if ("save".equals(args[0])) { + save(); + } else if ("load".equals(args[0])) { + load(); + } else { + screen.command(args); } printPS1(); } + /** + * EFFECTS: Print the '*user@JCA PS1' line + */ private void printPS1() { - System.out.printf("%s@JCA %s ", ca.getUser(), screen.getPS1()); + System.out.printf("%s%s@JCA %s ", + unsaved ? "*" : "", + ca.getUser(), + screen.getPS1()); + } + + /** + * EFFECTS: Save the DB + */ + public void save() { + try { + FS.write(PATH_DEFAULT, Decoder.encodeCA(ca)); + unsaved = false; + } catch (IOException e) { + System.out.println(e.getMessage()); + } } /** @@ -152,6 +205,10 @@ public class JCA { } } + public void setUnsaved(boolean unsaved) { + this.unsaved = unsaved; + } + public CertificationAuthority getCa() { return ca; } diff --git a/src/main/ui/MainScreen.java b/src/main/ui/MainScreen.java index 2eaf882..8a85881 100644 --- a/src/main/ui/MainScreen.java +++ b/src/main/ui/MainScreen.java @@ -7,6 +7,7 @@ import model.asn1.parsing.BytesReader; import model.ca.Template; import model.csr.CertificationRequest; import model.pki.cert.Certificate; +import model.pki.crl.CertificateList; import model.pki.crl.Reason; import model.pki.crl.RevokedCertificate; @@ -138,6 +139,7 @@ public class MainScreen implements UIHandler { session.getCa().revoke(new RevokedCertificate(ASN1Object.TAG_SEQUENCE, null, c.getCertificate().getSerialNumber(), new UtcTime(UtcTime.TAG, null, ZonedDateTime.now(ZoneId.of("UTC"))), reason)); + session.save(); } catch (IllegalArgumentException ignored) { System.out.println("Illegal serial number or reason"); } @@ -177,7 +179,9 @@ public class MainScreen implements UIHandler { return; } try { - System.out.println(Utils.toPEM(session.getCa().signCRL().encodeDER(), "X509 CRL")); + CertificateList crl = session.getCa().signCRL(); + session.save(); + System.out.println(Utils.toPEM(crl.encodeDER(), "X509 CRL")); } catch (Throwable e) { System.out.println(e.getMessage()); } diff --git a/src/main/ui/MgmtScreen.java b/src/main/ui/MgmtScreen.java index 0a25bfe..c630a34 100644 --- a/src/main/ui/MgmtScreen.java +++ b/src/main/ui/MgmtScreen.java @@ -73,6 +73,7 @@ public class MgmtScreen implements UIHandler { try { CertificationRequest req = session.getCa().signCSR(); System.out.println(Utils.toPEM(req.encodeDER(), "CERTIFICATE REQUEST")); + session.setUnsaved(true); } catch (Throwable e) { System.out.println(e.getMessage()); } @@ -90,6 +91,7 @@ public class MgmtScreen implements UIHandler { final Byte[] in = session.handleInputPEM("CERTIFICATE"); final Certificate cert = new Certificate(new BytesReader(in), false); session.getCa().installCertificate(cert); + session.setUnsaved(true); } catch (InvalidCAException | ParseException e) { System.out.println(e.getMessage()); } @@ -105,6 +107,7 @@ public class MgmtScreen implements UIHandler { } try { session.getCa().generateKey(); + session.setUnsaved(true); } catch (NoSuchAlgorithmException e) { System.out.println(e.getMessage()); } diff --git a/src/main/ui/TemplateSetScreen.java b/src/main/ui/TemplateSetScreen.java index a0b39c1..30d25b9 100644 --- a/src/main/ui/TemplateSetScreen.java +++ b/src/main/ui/TemplateSetScreen.java @@ -2,6 +2,7 @@ package ui; import model.asn1.exceptions.ParseException; import model.ca.Template; +import model.x501.Name; /** * The screen that modifies the properties of a single template and add it to the store. @@ -36,7 +37,11 @@ public class TemplateSetScreen implements UIHandler { */ private void handleSetSubject(String val) { try { - template = new Template(template.getName(), template.isEnabled(), val, template.getValidity()); + if (val == null) { + template = new Template(template.getName(), template.isEnabled(), (Name) null, template.getValidity()); + } else { + template = new Template(template.getName(), template.isEnabled(), val, template.getValidity()); + } } catch (ParseException e) { System.out.println(e.getMessage()); } @@ -94,6 +99,7 @@ public class TemplateSetScreen implements UIHandler { @Override public void commit() { session.getCa().addTemplate(template); + session.setUnsaved(true); session.setScreen(Screen.TEMPLATES); } diff --git a/src/main/ui/TemplatesScreen.java b/src/main/ui/TemplatesScreen.java index e08df50..e622709 100644 --- a/src/main/ui/TemplatesScreen.java +++ b/src/main/ui/TemplatesScreen.java @@ -75,6 +75,7 @@ public class TemplatesScreen implements UIHandler { return; } session.getCa().setTemplateEnable(tmp, enable); + session.setUnsaved(true); } /** @@ -92,6 +93,7 @@ public class TemplatesScreen implements UIHandler { return; } session.getCa().removeTemplate(tmp); + session.setUnsaved(true); } /** diff --git a/src/main/ui/Utils.java b/src/main/ui/Utils.java index f653ffa..4a9beeb 100644 --- a/src/main/ui/Utils.java +++ b/src/main/ui/Utils.java @@ -80,7 +80,11 @@ public final class Utils { throw new ParseException("Not a valid PEM"); } final String b64 = matcher.group(1).replace("\n", ""); - return byteToByte(Base64.getDecoder().decode(b64)); + try { + return byteToByte(Base64.getDecoder().decode(b64)); + } catch (IllegalArgumentException e) { + throw new ParseException(e.getMessage()); + } } /** |