From 0bcc057e741af3fbc108f42b75f9d42f48f6a51e Mon Sep 17 00:00:00 2001 From: Yuuta Liang Date: Sat, 14 Oct 2023 05:12:06 +0800 Subject: Implement the CA Signed-off-by: Yuuta Liang --- README.md | 4 +- src/main/model/asn1/ASN1Object.java | 2 +- src/main/model/asn1/Int.java | 13 +- src/main/model/ca/AuditLogEntry.java | 39 +++ src/main/model/ca/CACertificate.java | 279 +++++++++++++++++++++ src/main/model/ca/Template.java | 104 ++++++++ src/main/model/pki/cert/TbsCertificate.java | 15 ++ src/main/model/pki/crl/CertificateListContent.java | 2 +- src/main/ui/IssueScreen.java | 138 ++++++++++ src/main/ui/JCA.java | 203 +++++++++++++++ src/main/ui/Main.java | 4 +- src/main/ui/MainScreen.java | 200 +++++++++++++++ src/main/ui/MgmtScreen.java | 170 +++++++++++++ src/main/ui/Screen.java | 31 +++ src/main/ui/TemplateSetScreen.java | 131 ++++++++++ src/main/ui/TemplatesScreen.java | 108 ++++++++ src/main/ui/UIHandler.java | 45 ++++ src/main/ui/Utils.java | 44 ++-- src/test/model/TestConstants.java | 4 +- src/test/model/asn1/IntTest.java | 3 + src/test/model/ca/AuditLogEntryTest.java | 26 ++ src/test/model/ca/CACertificateTest.java | 178 +++++++++++++ src/test/model/ca/TemplateTest.java | 27 ++ src/test/model/pki/cert/TbsCertificateTest.java | 12 + src/test/ui/UtilsTest.java | 67 +++++ tests/.gitignore | 1 + tests/Makefile | 40 +++ tests/ca.cnf | 108 ++++++++ tests/leaf.csr.cnf | 9 + 29 files changed, 1974 insertions(+), 33 deletions(-) create mode 100644 src/main/model/ca/AuditLogEntry.java create mode 100644 src/main/model/ca/CACertificate.java create mode 100644 src/main/model/ca/Template.java create mode 100644 src/main/ui/IssueScreen.java create mode 100644 src/main/ui/JCA.java create mode 100644 src/main/ui/MainScreen.java create mode 100644 src/main/ui/MgmtScreen.java create mode 100644 src/main/ui/Screen.java create mode 100644 src/main/ui/TemplateSetScreen.java create mode 100644 src/main/ui/TemplatesScreen.java create mode 100644 src/main/ui/UIHandler.java create mode 100644 src/test/model/ca/AuditLogEntryTest.java create mode 100644 src/test/model/ca/CACertificateTest.java create mode 100644 src/test/model/ca/TemplateTest.java create mode 100644 src/test/ui/UtilsTest.java create mode 100644 tests/.gitignore create mode 100644 tests/Makefile create mode 100644 tests/ca.cnf create mode 100644 tests/leaf.csr.cnf diff --git a/README.md b/README.md index 3ab9a43..907ba30 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ proprietary). As a user, I want to be able to: -1. Import a CA cryptography key-pair and its corresponding X.509 certificate +1. Generate a CA cryptography key-pair and its corresponding X.509 certificate into the program and view it. The private key must not be displayed or exported in any format under any circumstances for security purposes. 2. Input CSRs, edit the certificate properties (e.g., subject, not before, @@ -41,7 +41,7 @@ optionally revoke any of them with a corresponding PKCS#10 reason. The certificates must not be deleted from the list under any circumstances but only revoked because some future administrators or the legal team may need to audit it. -4. Publish base and optionally delta CRLs. +4. Publish base CRLs. 5. Add, enable, disable, or remove custom certificate templates (also called policies) that constraints what each type of certificates can and cannot have and their properties (e.g., TLS server certificates vs user logon certificates diff --git a/src/main/model/asn1/ASN1Object.java b/src/main/model/asn1/ASN1Object.java index d1bce06..9b4a98c 100644 --- a/src/main/model/asn1/ASN1Object.java +++ b/src/main/model/asn1/ASN1Object.java @@ -159,7 +159,7 @@ public class ASN1Object implements Encodable { list.addAll(Arrays.asList(tag.encodeDER())); list.addAll(Arrays.asList(new ASN1Length(val.length).encodeDER())); - list.addAll(Arrays.asList(encodeValueDER())); + list.addAll(Arrays.asList(val)); if (parentTag != null) { // Explicit final List newList = new ArrayList<>(list.size() + 3); diff --git a/src/main/model/asn1/Int.java b/src/main/model/asn1/Int.java index 5b75a73..4eeeedf 100644 --- a/src/main/model/asn1/Int.java +++ b/src/main/model/asn1/Int.java @@ -25,9 +25,18 @@ public class Int extends ASN1Object { * encoding. For more information, consult {@link ASN1Object}. * REQUIRES: Consult {@link ASN1Object}. */ - public Int(Tag tag, Tag parentTag, long value) { + public Int(Tag tag, Tag parentTag, BigInteger value) { super(tag, parentTag); - this.value = BigInteger.valueOf(value); + this.value = value; + } + + /** + * EFFECTS: Initiate the INTEGER object with the given tag and an optional context-specific tag number for explicit + * encoding. For more information, consult {@link ASN1Object}. + * REQUIRES: Consult {@link ASN1Object}. + */ + public Int(Tag tag, Tag parentTag, long value) { + this(tag, parentTag, BigInteger.valueOf(value)); } /** diff --git a/src/main/model/ca/AuditLogEntry.java b/src/main/model/ca/AuditLogEntry.java new file mode 100644 index 0000000..a8d1929 --- /dev/null +++ b/src/main/model/ca/AuditLogEntry.java @@ -0,0 +1,39 @@ +package model.ca; + +import java.time.ZonedDateTime; + +/** + * An audit log entry. Audit logs record who did what at when, used for future auditing purposes. + * These logs will never be deleted. + */ +public class AuditLogEntry { + private final String user; + private final ZonedDateTime time; + private final String action; + + /** + * EFFECTS: Init the entry with the given user, time, and action. + */ + public AuditLogEntry(String user, ZonedDateTime time, String action) { + this.user = user; + this.time = time; + this.action = action; + } + + @Override + public String toString() { + return String.format("%s\t%s\t%s", time, user, action); + } + + public String getUser() { + return user; + } + + public ZonedDateTime getTime() { + return time; + } + + public String getAction() { + return action; + } +} diff --git a/src/main/model/ca/CACertificate.java b/src/main/model/ca/CACertificate.java new file mode 100644 index 0000000..36a9ac5 --- /dev/null +++ b/src/main/model/ca/CACertificate.java @@ -0,0 +1,279 @@ +package model.ca; + +import model.asn1.*; +import model.asn1.exceptions.ParseException; +import model.csr.*; +import model.pki.AlgorithmIdentifier; +import model.pki.SubjectPublicKeyInfo; +import model.pki.cert.*; +import model.pki.cert.Certificate; +import model.pki.crl.CertificateList; +import model.pki.crl.CertificateListContent; +import model.pki.crl.RevokedCertificate; +import model.x501.AttributeTypeAndValue; +import model.x501.Name; +import model.x501.RelativeDistinguishedName; +import ui.Utils; + +import java.math.BigInteger; +import java.security.*; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.RSAPrivateKeySpec; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.*; +import java.util.stream.Stream; + +/** + * Holds a CA private key, its certificate, and signed / revoked list. + */ +public class CACertificate { + /** + * The key pair. + */ + private KeyPair key; + + /** + * The signed certificate. + */ + private Certificate certificate; + + /** + * Signed certificates. + */ + private List signed; + + /** + * The next serial number. + */ + private int serial; + + /** + * Revoked certs. + */ + private List revoked; + + /** + * EFFECT: Init with a null key and null certificate, empty signed and revoked list, and serial at 1. + */ + public CACertificate() { + this.key = null; + this.certificate = null; + this.serial = 1; + this.signed = new ArrayList<>(); + this.revoked = new ArrayList<>(); + } + + /** + * EFFECTS: Generate a new RSA2048 private key. + * REQUIRES: getPublicKey() is null (i.e., no private key had been installed) + */ + public void generateKey() throws NoSuchAlgorithmException { + final KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); + gen.initialize(2048); + this.key = gen.generateKeyPair(); + } + + /** + * EFFECT: Install the CA certificate. + * MODIFIES: this + * REQUIRES: + * - The new certificate must have the same algorithm and public key as getPublicKey(), except for testing purpose + * - It must be a v3 certificate + * - It must have basicConstraints { cA = TRUE } + * - It must contain key usage Digital Signature, Certificate Sign, CRL Sign + * - getCertificate() must be null (i.e., no certificate is installed yet). + */ + public void installCertificate(Certificate certificate) { + this.certificate = certificate; + } + + /** + * EFFECTS: Generate a CSR based on public key. It will have subject = CN=JCA. + */ + private CertificationRequestInfo generateCSR() throws ParseException { + return new CertificationRequestInfo(ASN1Object.TAG_SEQUENCE, null, + new Int(Int.TAG, null, CertificationRequestInfo.VERSION_V1), + new Name(ASN1Object.TAG_SEQUENCE, null, new RelativeDistinguishedName[]{ + new RelativeDistinguishedName(ASN1Object.TAG_SET, null, new AttributeTypeAndValue[]{ + new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null, + new ObjectIdentifier(ObjectIdentifier.TAG, null, + ObjectIdentifier.OID_CN), + new PrintableString(PrintableString.TAG, null, "JCA")) + }) + }), + getCAPublicKeyInfo(), + new Attributes(new Tag(TagClass.CONTEXT_SPECIFIC, true, 0), // IMPLICIT + null, + new Attribute[]{ + new Attribute(ASN1Object.TAG_SEQUENCE, null, + new ObjectIdentifier(ObjectIdentifier.TAG, null, + new Integer[]{ 1, 3, 6, 1, 4, 1, 311, 13, 2, 3 }), + new Values(ASN1Object.TAG_SET, null, + new ASN1Object[]{ + new IA5String(IA5String.TAG, null, + "10.0.20348.2") + }))})); + } + + private Byte[] getPubKeyBitStream() { + final RSAPublicKey pub = (RSAPublicKey) key.getPublic(); + final BigInteger exponent = pub.getPublicExponent(); + byte[] modules = pub.getModulus().toByteArray(); + final Int asn1Exponent = new Int(Int.TAG, null, exponent); + // Use OctetString to avoid leading zero issues. + final ASN1Object asn1Modules = new OctetString(Int.TAG, null, Utils.byteToByte(modules)); + final Byte[] asn1ExponentDER = asn1Exponent.encodeDER(); + final Byte[] asn1ModulesDER = asn1Modules.encodeDER(); + return Stream.of(Arrays.asList(ASN1Object.TAG_SEQUENCE.encodeDER()), + Arrays.asList(new ASN1Length(asn1ModulesDER.length + asn1ExponentDER.length).encodeDER()), + Arrays.asList(asn1ModulesDER), + Arrays.asList(asn1ExponentDER)) + .flatMap(Collection::stream) + .toArray(Byte[]::new); + } + + /** + * EFFECTS: Encode the RSA public key into SubjectPubicKeyInfo format (BIT STRING -> SEQUENCE -> { INT INT }). + */ + public SubjectPublicKeyInfo getCAPublicKeyInfo() { + return new SubjectPublicKeyInfo(ASN1Object.TAG_SEQUENCE, null, + new AlgorithmIdentifier(ASN1Object.TAG_SEQUENCE, null, + new ObjectIdentifier(ObjectIdentifier.TAG, null, + ObjectIdentifier.OID_RSA_ENCRYPTION), + new Null(Null.TAG, null)), + new BitString(BitString.TAG, null, 0, getPubKeyBitStream())); + } + + /** + * EFFECT: Generate CSR and sign it, so the CA can request itself a certificate. + * REQUIRES: The CA cert must not be installed. + */ + public CertificationRequest signCSR() + throws ParseException, NoSuchAlgorithmException, SignatureException, InvalidKeyException { + final CertificationRequestInfo info = generateCSR(); + return new CertificationRequest(ASN1Object.TAG_SEQUENCE, null, + info, + getSigningAlgorithm(), + new BitString(BitString.TAG, null, 0, signBytes(info.encodeDER()))); + } + + /** + * EFFECT: Return SHA256withRSA. + */ + private AlgorithmIdentifier getSigningAlgorithm() { + return new AlgorithmIdentifier(ASN1Object.TAG_SEQUENCE, null, + new ObjectIdentifier(ObjectIdentifier.TAG, null, + ObjectIdentifier.OID_SHA256_WITH_RSA_ENCRYPTION), + new Null(Null.TAG, null)); + } + + /** + * EFFECTS: Sign the CSR based on the template. + * REQUIRES: The CA cert must be installed first, req must have a subject, template must be enabled. + * MODIFIES: this + */ + public Certificate signCert(CertificationRequestInfo req, Template template) + throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + final TbsCertificate newCert = generateCert(req, template); + final Certificate cert = new Certificate(ASN1Object.TAG_SEQUENCE, null, + newCert, + getSigningAlgorithm(), + new BitString(BitString.TAG, null, 0, + signBytes(newCert.encodeValueDER()))); + this.signed.add(cert); + return cert; + } + + /** + * EFFECTS: Hash the input message with SHA256 and sign it with RSA and get the signature. + */ + private Byte[] signBytes(Byte[] message) + throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + final Signature signature = Signature.getInstance("SHA256withRSA"); + signature.initSign(key.getPrivate()); + signature.update(Utils.byteToByte(message)); + return Utils.byteToByte(signature.sign()); + } + + /** + * EFFECTS: Apply the template. + * For the new certificate: + * - Issuer will be set to CA#getCertificate()#getSubject() + * - The template will be applied (subject, validity, cdp) + * - A serial number will be generated + */ + private TbsCertificate generateCert(CertificationRequestInfo req, Template template) { + final ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")); + return new TbsCertificate(ASN1Object.TAG_SEQUENCE, null, + new Int(Int.TAG, new Tag(TagClass.CONTEXT_SPECIFIC, true, 0), + TbsCertificate.VERSION_V3), + new Int(Int.TAG, null, serial++), + getSigningAlgorithm(), + certificate.getCertificate().getSubject(), + new Validity(ASN1Object.TAG_SEQUENCE, null, + new GeneralizedTime(GeneralizedTime.TAG, null, now), + new UtcTime(UtcTime.TAG, null, + now.plusDays(template.getValidity()))), + template.getSubject() == null ? req.getSubject() : + template.getSubject(), + req.getSubjectPKInfo(), + null); + } + + /** + * EFFECTS: Add the revocation info to revoked list. + * REQUIRES: revoked should have the serial of an issued certificate; its date should be current. + * MODIFIES: this + */ + public void revoke(RevokedCertificate rev) { + revoked.add(rev); + } + + /** + * EFFECTS: Generate and sign the CRL, based on getRevokedCerts(). The CSR will have current time as thisUpdate with + * no nextUptime, and it will have issuer same as the CA's subject. + * REQUIRES: The CA cert must be installed first. + */ + public CertificateList signCRL() + throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + CertificateListContent content = new CertificateListContent(ASN1Object.TAG_SEQUENCE, null, + certificate.getCertificate().getSubject(), + getSigningAlgorithm(), + new GeneralizedTime(GeneralizedTime.TAG, null, ZonedDateTime.now(ZoneId.of("UTC"))), + null, + revoked.toArray(new RevokedCertificate[0])); + return new CertificateList(ASN1Object.TAG_SEQUENCE, null, + content, + getSigningAlgorithm(), + new BitString(BitString.TAG, null, 0, + signBytes(content.encodeValueDER()))); + } + + public Certificate getCertificate() { + return certificate; + } + + public List getSigned() { + return signed; + } + + /** + * EFFECTS: Get the public key, or null if no private key is installed. + */ + public PublicKey getPublicKey() { + if (key == null) { + return null; + } + return key.getPublic(); + } + + public List getRevoked() { + return revoked; + } + + public int getSerial() { + return serial; + } +} diff --git a/src/main/model/ca/Template.java b/src/main/model/ca/Template.java new file mode 100644 index 0000000..ff2510e --- /dev/null +++ b/src/main/model/ca/Template.java @@ -0,0 +1,104 @@ +package model.ca; + +import model.asn1.*; +import model.asn1.exceptions.ParseException; +import model.pki.cert.TbsCertificate; +import model.x501.AttributeTypeAndValue; +import model.x501.Name; +import model.x501.RelativeDistinguishedName; + +import java.util.List; + +/** + * Represents a certificate template. Certificate templates are like policies the define part of the issued certificates + * of what to have in common. + */ +public class Template { + /** + * Name of the template. + */ + private String name; + + /** + * Whether the template is usable or not. + */ + private boolean enabled; + + /** + * Subject of the issued certs. Null -> unspecified + */ + private Name subject; + + /** + * Length of validity in days since the point of issue. + */ + private long validity; + + /** + * EFFECTS: Init with all given parameters. + * REQUIRES: name should be non-null; subject should be a valid X.509 subject name; validity should be > 0 + */ + public Template(String name, + boolean enabled, + Name subject, + long validity) { + this.name = name; + this.enabled = enabled; + this.subject = subject; + this.validity = validity; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Name getSubject() { + return subject; + } + + public void setSubject(Name subject) { + this.subject = subject; + } + + /** + * EFFECTS: Set the subject to CN=commonName,C=CA + * Throws {@link ParseException} if commonName is not a valid PrintableString + */ + public void setSubject(String commonName) throws ParseException { + if (commonName == null) { + this.subject = null; + return; + } + setSubject(new Name(ASN1Object.TAG_SEQUENCE, null, new RelativeDistinguishedName[]{ + new RelativeDistinguishedName(ASN1Object.TAG_SET, null, new AttributeTypeAndValue[]{ + new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null, + new ObjectIdentifier(ObjectIdentifier.TAG, null, + ObjectIdentifier.OID_CN), + new PrintableString(PrintableString.TAG, null, commonName))}), + new RelativeDistinguishedName(ASN1Object.TAG_SET, null, new AttributeTypeAndValue[]{ + new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null, + new ObjectIdentifier(ObjectIdentifier.TAG, null, + ObjectIdentifier.OID_C), + new PrintableString(PrintableString.TAG, null, "CA"))})})); + } + + public long getValidity() { + return validity; + } + + public void setValidity(long validity) { + this.validity = validity; + } +} diff --git a/src/main/model/pki/cert/TbsCertificate.java b/src/main/model/pki/cert/TbsCertificate.java index 1175456..ce228af 100644 --- a/src/main/model/pki/cert/TbsCertificate.java +++ b/src/main/model/pki/cert/TbsCertificate.java @@ -229,6 +229,21 @@ public class TbsCertificate extends ASN1Object { .toArray(Byte[]::new); } + /** + * EFFECT: Get the extension by ID. If the certificate is V1 or does not have any extensions or does not have the + * specified extension, null is returned. + * REQUIRES: extnId should be a valid X.509 certificate extension ID. + */ + public Extension getExtension(Integer[] extnId) { + if (extensions == null) { + return null; + } + return Arrays.stream(extensions.getExtensions()) + .filter(extn -> Arrays.equals(extnId, extn.getExtnId().getInts())) + .findFirst() + .orElse(null); + } + public Int getVersion() { return version; } diff --git a/src/main/model/pki/crl/CertificateListContent.java b/src/main/model/pki/crl/CertificateListContent.java index 6f75d71..c7e901d 100644 --- a/src/main/model/pki/crl/CertificateListContent.java +++ b/src/main/model/pki/crl/CertificateListContent.java @@ -70,8 +70,8 @@ public class CertificateListContent extends ASN1Object { .flatMap(Arrays::stream) .collect(Collectors.toList()); return Stream.of(Arrays.asList(version.encodeDER()), - Arrays.asList(issuer.encodeDER()), Arrays.asList(signature.encodeDER()), + Arrays.asList(issuer.encodeDER()), Arrays.asList(thisUpdate.encodeDER()), nextUpdate == null ? Collections.emptyList() : Arrays.asList(nextUpdate.encodeDER()), Arrays.asList(new Tag(TagClass.UNIVERSAL, true, 0x30).encodeDER()), diff --git a/src/main/ui/IssueScreen.java b/src/main/ui/IssueScreen.java new file mode 100644 index 0000000..e152b0d --- /dev/null +++ b/src/main/ui/IssueScreen.java @@ -0,0 +1,138 @@ +package ui; + +import model.asn1.exceptions.ParseException; +import model.asn1.parsing.BytesReader; +import model.ca.Template; +import model.csr.CertificationRequest; +import model.pki.cert.Certificate; + +public class IssueScreen implements UIHandler { + private final JCA session; + + private Template template; + private CertificationRequest incomingCSR; + + /** + * EFFECTS: Init with the session. + */ + public IssueScreen(JCA session) { + this.session = session; + } + + /** + * EFFECTS: Set current template and CSR in use by args. + * REQUIRES: args.length = 2, args[0] instanceof CertificateRequest, args[1] instanceof Template + * MODIFIES: args[1] + */ + @Override + public void enter(Object... args) { + this.incomingCSR = (CertificationRequest) args[0]; + this.template = (Template) args[1]; + } + + @Override + public void help() { + System.out.print("show\tView the current certificate\n" + + "set\tSet properties or template\n" + + "commit\tIssue the certificate\n" + + "exit\tDiscard and go to main menu\n" + + "help\tPrint this message\n"); + } + + @Override + public void show() { + System.out.println("Requested Subject:\t" + incomingCSR.getCertificationRequestInfo().getSubject()); + System.out.println("Subject:\t" + (template.getSubject() == null + ? incomingCSR.getCertificationRequestInfo().getSubject() + : template.getSubject())); + System.out.println("Template:\t" + template.getName()); + System.out.println("Validity:\t" + template.getValidity() + " days"); + } + + @Override + public void commit() { + try { + Certificate certificate = session.getCa().signCert(incomingCSR.getCertificationRequestInfo(), template); + System.out.println(Utils.toPEM(certificate.encodeDER(), "CERTIFICATE")); + session.log("A certificate was issued."); + session.setScreen(Screen.MAIN); + } catch (Throwable e) { + System.out.println(e.getMessage()); + } + } + + private void handleIssueSetSubject(String val) { + try { + template.setSubject(val); + } catch (ParseException e) { + System.out.println(e.getMessage()); + } + } + + private void handleIssueSetValidity(String val) { + if (val == null) { + System.out.println("Cannot unset validity"); + return; + } + try { + long i = Long.parseLong(val); + if (i <= 0) { + System.out.println("Invalid validity days"); + return; + } + template.setValidity(i); + } catch (NumberFormatException ignored) { + System.out.println("Invalid validity days"); + } + } + + private void handleIssueSet(String... args) { + if (args.length != 2 && args.length != 3) { + System.out.println("Usage: set "); + System.out.println("Supported keys: subject validity"); + return; + } + String val = args.length == 3 ? args[2] : null; + switch (args[1]) { + case "subject": + handleIssueSetSubject(val); + break; + case "validity": + handleIssueSetValidity(val); + break; + default: + System.out.println("Unknown key"); + break; + } + } + + @Override + public void command(String... args) { + switch (args[0]) { + case "set": + handleIssueSet(args); + break; + default: + help(); + break; + } + } + + /** + * EFFECTS: Clear the certificates and return main. + * MODIFIES: this + */ + @Override + public Screen exit() { + incomingCSR = null; + template = null; + return Screen.MAIN; + } + + @Override + public String getPS1() { + return String.format("/%s/ %%", template.getSubject() == null + ? incomingCSR.getCertificationRequestInfo().getSubject() + : template.getSubject()); + } +} diff --git a/src/main/ui/JCA.java b/src/main/ui/JCA.java new file mode 100644 index 0000000..f9467ea --- /dev/null +++ b/src/main/ui/JCA.java @@ -0,0 +1,203 @@ +package ui; + +import model.asn1.exceptions.ParseException; +import model.ca.AuditLogEntry; +import model.ca.CACertificate; +import model.ca.Template; + +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.time.ZonedDateTime; +import java.util.*; + +/** + * Main program + */ +public class JCA { + /** + * The current screen. + */ + private UIHandler screen; + + /** + * Instances of the five screens; + */ + private final UIHandler mainScreen; + private final UIHandler mgmtScreen; + private final UIHandler issueScreen; + private final UIHandler templatesScreen; + private final UIHandler templateSetScreen; + + /** + * Templates + */ + private final List