From d7ff9d5e217873609d79efe279f2634e3a3dd8b4 Mon Sep 17 00:00:00 2001 From: Yuuta Liang Date: Wed, 25 Oct 2023 03:30:45 +0800 Subject: Refactor: move all logics into CertificationAuthority Signed-off-by: Yuuta Liang --- src/main/model/asn1/ASN1Time.java | 2 +- src/main/model/asn1/GeneralizedTime.java | 10 +- src/main/model/asn1/UtcTime.java | 10 +- .../model/asn1/exceptions/InvalidCAException.java | 10 + src/main/model/ca/CACertificate.java | 281 ------------ src/main/model/ca/CertificationAuthority.java | 480 ++++++++++++++++++++ src/main/model/ca/Template.java | 53 +-- src/main/ui/IssueScreen.java | 22 +- src/main/ui/JCA.java | 73 +-- src/main/ui/MainScreen.java | 4 +- src/main/ui/MgmtScreen.java | 117 ++--- src/main/ui/TemplateSetScreen.java | 13 +- src/main/ui/TemplatesScreen.java | 17 +- src/main/ui/Utils.java | 14 +- src/test/model/TestConstants.java | 113 ++--- src/test/model/asn1/PrintableStringTest.java | 2 +- src/test/model/asn1/UTF8StringTest.java | 4 +- src/test/model/ca/CACertificateTest.java | 178 -------- src/test/model/ca/CertificationAuthorityTest.java | 492 +++++++++++++++++++++ src/test/model/ca/TemplateTest.java | 55 +-- src/test/model/csr/AttributesTest.java | 2 +- src/test/model/pki/cert/ExtensionsTest.java | 30 +- src/test/model/pki/cert/TbsCertificateTest.java | 3 +- src/test/model/pki/cert/ValidityTest.java | 2 +- src/test/ui/UtilsTest.java | 24 +- 25 files changed, 1203 insertions(+), 808 deletions(-) create mode 100644 src/main/model/asn1/exceptions/InvalidCAException.java delete mode 100644 src/main/model/ca/CACertificate.java create mode 100644 src/main/model/ca/CertificationAuthority.java delete mode 100644 src/test/model/ca/CACertificateTest.java create mode 100644 src/test/model/ca/CertificationAuthorityTest.java (limited to 'src') diff --git a/src/main/model/asn1/ASN1Time.java b/src/main/model/asn1/ASN1Time.java index 8f386f5..94e58b3 100644 --- a/src/main/model/asn1/ASN1Time.java +++ b/src/main/model/asn1/ASN1Time.java @@ -15,7 +15,7 @@ public abstract class ASN1Time extends ASN1Object { /** * The time. */ - private ZonedDateTime timestamp; + private final ZonedDateTime timestamp; /** * EFFECTS: Initialize the time with the specific tag, parentTag, and timestamp. For tag and parentTag, consult diff --git a/src/main/model/asn1/GeneralizedTime.java b/src/main/model/asn1/GeneralizedTime.java index 5482906..c97a51b 100644 --- a/src/main/model/asn1/GeneralizedTime.java +++ b/src/main/model/asn1/GeneralizedTime.java @@ -22,7 +22,7 @@ public class GeneralizedTime extends ASN1Time { /** * Rather stupid impl ... */ - private static final DateTimeFormatter formatterNoSecs = new DateTimeFormatterBuilder() + private static final DateTimeFormatter FORMATTER_NO_SECS = new DateTimeFormatterBuilder() .appendValue(ChronoField.YEAR, 4) .appendValue(ChronoField.MONTH_OF_YEAR, 2) .appendValue(ChronoField.DAY_OF_MONTH, 2) @@ -32,7 +32,7 @@ public class GeneralizedTime extends ASN1Time { .toFormatter() .withZone(ZoneId.of("UTC")); - private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder() + private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder() .appendValue(ChronoField.YEAR, 4) .appendValue(ChronoField.MONTH_OF_YEAR, 2) .appendValue(ChronoField.DAY_OF_MONTH, 2) @@ -71,7 +71,7 @@ public class GeneralizedTime extends ASN1Time { @Override public ZonedDateTime toDate(String str) throws ParseException { try { - return ZonedDateTime.parse(str, formatter); + return ZonedDateTime.parse(str, FORMATTER); } catch (DateTimeParseException e) { throw new ParseException(e.getMessage()); } @@ -83,8 +83,8 @@ public class GeneralizedTime extends ASN1Time { @Override public String toString() { if (getTimestamp().getSecond() == 0) { - return getTimestamp().format(formatterNoSecs); + return getTimestamp().format(FORMATTER_NO_SECS); } - return getTimestamp().format(formatter); + return getTimestamp().format(FORMATTER); } } diff --git a/src/main/model/asn1/UtcTime.java b/src/main/model/asn1/UtcTime.java index 7fa93d1..54b7a5a 100644 --- a/src/main/model/asn1/UtcTime.java +++ b/src/main/model/asn1/UtcTime.java @@ -23,7 +23,7 @@ public class UtcTime extends ASN1Time { /** * Rather stupid impl ... */ - private static final DateTimeFormatter formatterNoSecs = new DateTimeFormatterBuilder() + private static final DateTimeFormatter FORMATTER_NO_SECS = new DateTimeFormatterBuilder() .appendPattern("yy") .appendValue(ChronoField.MONTH_OF_YEAR, 2) .appendValue(ChronoField.DAY_OF_MONTH, 2) @@ -33,7 +33,7 @@ public class UtcTime extends ASN1Time { .toFormatter() .withZone(ZoneId.of("UTC")); - private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder() + private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder() .appendPattern("yy") .appendValue(ChronoField.MONTH_OF_YEAR, 2) .appendValue(ChronoField.DAY_OF_MONTH, 2) @@ -72,7 +72,7 @@ public class UtcTime extends ASN1Time { @Override public ZonedDateTime toDate(String str) throws ParseException { try { - return ZonedDateTime.parse(str, formatter); + return ZonedDateTime.parse(str, FORMATTER); } catch (DateTimeParseException e) { throw new ParseException(e.getMessage()); } @@ -84,8 +84,8 @@ public class UtcTime extends ASN1Time { @Override public String toString() { if (getTimestamp().getSecond() == 0) { - return getTimestamp().format(formatterNoSecs); + return getTimestamp().format(FORMATTER_NO_SECS); } - return getTimestamp().format(formatter); + return getTimestamp().format(FORMATTER); } } diff --git a/src/main/model/asn1/exceptions/InvalidCAException.java b/src/main/model/asn1/exceptions/InvalidCAException.java new file mode 100644 index 0000000..e487509 --- /dev/null +++ b/src/main/model/asn1/exceptions/InvalidCAException.java @@ -0,0 +1,10 @@ +package model.asn1.exceptions; + +/** + * Indicate the error that the CA being installed is invalid. + */ +public class InvalidCAException extends Exception { + public InvalidCAException(String message) { + super(message); + } +} diff --git a/src/main/model/ca/CACertificate.java b/src/main/model/ca/CACertificate.java deleted file mode 100644 index 1bd53c9..0000000 --- a/src/main/model/ca/CACertificate.java +++ /dev/null @@ -1,281 +0,0 @@ -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.Certificate; -import model.pki.cert.TbsCertificate; -import model.pki.cert.Validity; -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.RSAPublicKey; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -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/CertificationAuthority.java b/src/main/model/ca/CertificationAuthority.java new file mode 100644 index 0000000..feb557c --- /dev/null +++ b/src/main/model/ca/CertificationAuthority.java @@ -0,0 +1,480 @@ +package model.ca; + +import model.asn1.*; +import model.asn1.exceptions.InvalidCAException; +import model.asn1.exceptions.ParseException; +import model.asn1.parsing.BytesReader; +import model.csr.*; +import model.pki.AlgorithmIdentifier; +import model.pki.SubjectPublicKeyInfo; +import model.pki.cert.Certificate; +import model.pki.cert.Extension; +import model.pki.cert.TbsCertificate; +import model.pki.cert.Validity; +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.InvalidKeySpecException; +import java.security.spec.RSAPrivateKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.*; +import java.util.stream.Stream; + +/** + * Holds a CA private key, its certificate, signed / revoked list, template list, and logs list. + */ +public class CertificationAuthority { + /** + * The RSA2048 private key. + */ + private RSAPrivateKey key; + + /** + * The public key. + */ + private RSAPublicKey publicKey; + + /** + * The signed certificate. + */ + private Certificate certificate; + + /** + * Signed certificates. + */ + private final List signed; + + /** + * The next serial number. + */ + private int serial; + + /** + * Revoked certs. + */ + private final List revoked; + + /** + * Certificate templates. + */ + private final List