package model.pki.cert; import annotations.Assoc; import model.asn1.ASN1Object; import model.asn1.Int; import model.asn1.Tag; import model.asn1.TagClass; import model.asn1.exceptions.ParseException; import model.asn1.parsing.BytesReader; import model.pki.AlgorithmIdentifier; import model.pki.SubjectPublicKeyInfo; import model.x501.Name; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.stream.Stream; /** * Represents a X.509 certificate * *
 *  Certificate ::= SIGNED{TBSCertificate}
 *  TBSCertificate ::= SEQUENCE {
 *      version                 [0] Version DEFAULT v1,
 *      serialNumber            CertificateSerialNumber,
 *      signature               AlgorithmIdentifier{{SupportedAlgorithms}},
 *      issuer                  Name,
 *      validity                Validity,
 *      subject                 Name,
 *      subjectPublicKeyInfo    SubjectPublicKeyInfo,
 *      issuerUniqueIdentifier  [1] IMPLICIT UniqueIdentifier OPTIONAL,
 *      ...,
 *      [[2: -- if present, version shall be v2 or v3
 *      subjectUniqueIdentifier [2] IMPLICIT UniqueIdentifier OPTIONAL]],
 *      [[3: -- if present, version shall be v2 or v3
 *      extensions              [3] Extensions OPTIONAL]]
 *      -- If present, version shall be v3]]
 *  }
 *
 *  Version ::= INTEGER {v1(0), v2(1), v3(2)}
 *  CertificateSerialNumber ::= INTEGER
 *
 *  uniqueIdentifier ATTRIBUTE ::= {
 *      WITH SYNTAX UniqueIdentifier
 *      EQUALITY MATCHING RULE bitStringMatch
 *      LDAP-SYNTAX bitString.&id
 *      LDAP-NAME {"x500UniqueIdentifier"}
 *      ID id-at-uniqueIdentifier }
 *  UniqueIdentifier ::= BIT STRING
 * 
*

* NOTE that subjectUniqueIdentifier and issuerUniqueIdentifier are not supported. */ public class TbsCertificate extends ASN1Object { // Version ::= INTEGER {v1(0), v2(1), v3(2)} public static final int VERSION_V1 = 0; public static final int VERSION_V2 = 1; public static final int VERSION_V3 = 2; /** * The X.509 cert version. subjectUniqueIdentifier is v2 only, and extensions is v3 only. *

     *   [0] Version DEFAULT v1
     * 
*/ @Assoc(partOf = true, lowerBond = 0) private final Int version; /** * The serial number of that certificate that is unique across the CA. *
     *     serialNumber CertificateSerialNumber
     *     CertificateSerialNumber ::= INTEGER
     * 
*/ @Assoc(partOf = true) private final Int serialNumber; @Assoc(partOf = true) private final AlgorithmIdentifier signature; /** * The subject and issuer distinguished names. *
     *     issuer Name,
     *     subject Name
     * 
*/ @Assoc(partOf = true) private final Name issuer; /** * The validity period of that certificate. * Validity ::= SEQUENCE { notBefore Time, notAfter Time, ... } */ @Assoc(partOf = true) private final Validity validity; /** * See the comments on issuer. */ @Assoc(partOf = true) private final Name subject; /** * The public key of the certificate's holder. */ @Assoc(partOf = true) private final SubjectPublicKeyInfo subjectPublicKeyInfo; /** * [3] Optional. */ @Assoc(partOf = true, lowerBond = 0) private final Extensions extensions; /** * EFFECTS: Init with the given parameters. For tag and parentTag, see {@link ASN1Object}. * REQUIRES: * - Version must be V1, V2, or V3. * - {issuer,subject}UniqueIdentifier could be null. * - If {issuer,subject}UniqueIdentifier presents, version must be V2 or V3. * - Extensions could be null. * - If extensions presents, version must be V3. * - The signature should be valid. * - Field and Desired Tags: * version CONTEXT SPECIFIC 0 (EXPLICIT), INTEGER, OPTIONAL DEFAULT v1 * serialNumber INTEGER * signature SEQUENCE * issuer SEQUENCE * validity SEQUENCE * subject SEQUENCE * subjectPublicKeyInfo SEQUENCE * extensions CONTEXT SPECIFIC 3 (EXPLICIT), SEQUENCE, OPTIONAL */ public TbsCertificate(Tag tag, Tag parentTag, final Int version, final Int serialNumber, final AlgorithmIdentifier signature, final Name issuer, final Validity validity, final Name subject, final SubjectPublicKeyInfo subjectPublicKeyInfo, final Extensions extensions) { super(tag, parentTag); this.version = version; this.serialNumber = serialNumber; this.signature = signature; this.issuer = issuer; this.validity = validity; this.subject = subject; this.subjectPublicKeyInfo = subjectPublicKeyInfo; this.extensions = extensions; } /** * EFFECTS: Parse input DER. * Throws {@link ASN1Object} if invalid: * - Any fields missing * - Any fields having an incorrect parent / inner tag (as seen in the ASN.1 definition) * - Any fields with encoding instructions that violate implicit / explicit encoding rules * - extensions are specified, but the version is v1 or v2 * - Other issues found during parsing the object, like early EOF (see {@link ASN1Object}) * MODIFIES: this, encoded */ public TbsCertificate(BytesReader encoded, boolean hasParentTag) throws ParseException { super(encoded, hasParentTag); int i = encoded.getIndex(); if (encoded.detectTag(new Tag(TagClass.CONTEXT_SPECIFIC, true, 0))) { this.version = new Int(encoded, true); } else { this.version = null; } this.serialNumber = new Int(encoded, false); this.signature = new AlgorithmIdentifier(encoded, false); this.issuer = new Name(encoded, false); this.validity = new Validity(encoded, false); this.subject = new Name(encoded, false); this.subjectPublicKeyInfo = new SubjectPublicKeyInfo(encoded, false); if (encoded.detectTag(new Tag(TagClass.CONTEXT_SPECIFIC, true, 3))) { this.extensions = new Extensions(encoded, true); } else { // Enforce the extensions tag - nothing else should be here. if (Integer.compareUnsigned(getLength(), (encoded.getIndex() - i)) != 0) { throw new ParseException("Unexpected objects after."); } this.extensions = null; } enforceInput(); enforceVersion(); } /** * EFFECTS: Throw {@link ParseException} if any field have illegal tags. */ private void enforceInput() throws ParseException { if (this.version != null) { this.version.getTag().enforce(Int.TAG); this.version.getParentTag().enforce(new Tag(TagClass.CONTEXT_SPECIFIC, true, 0)); } this.serialNumber.getTag().enforce(Int.TAG); this.signature.getTag().enforce(TAG_SEQUENCE); this.issuer.getTag().enforce(TAG_SEQUENCE); this.validity.getTag().enforce(TAG_SEQUENCE); this.subject.getTag().enforce(TAG_SEQUENCE); this.subjectPublicKeyInfo.getTag().enforce(TAG_SEQUENCE); if (extensions != null) { this.extensions.getTag().enforce(TAG_SEQUENCE); this.extensions.getParentTag().enforce(new Tag(TagClass.CONTEXT_SPECIFIC, true, 3)); } } /** * EFFECTS: Throw {@link ParseException} if the version is incorrect. */ private void enforceVersion() throws ParseException { if (version != null && (version.getLong() < VERSION_V1 || version.getLong() > VERSION_V3)) { throw new ParseException("Illegal certificate version: " + version.getLong()); } if (extensions != null && (version == null || version.getLong() != VERSION_V3)) { throw new ParseException("Extensions present. The version must be v3 or above."); } } /** * EFFECTS: Encode into ordered DER. */ @Override public Byte[] encodeValueDER() { return Stream.of(version == null ? Collections.emptyList() : Arrays.asList(version.encodeDER()), Arrays.asList(serialNumber.encodeDER()), Arrays.asList(signature.encodeDER()), Arrays.asList(issuer.encodeDER()), Arrays.asList(validity.encodeDER()), Arrays.asList(subject.encodeDER()), Arrays.asList(subjectPublicKeyInfo.encodeDER()), extensions == null ? Collections.emptyList() : Arrays.asList(extensions.encodeDER())) .flatMap(Collection::stream) .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; } public Int getSerialNumber() { return serialNumber; } public AlgorithmIdentifier getSignature() { return signature; } public Name getIssuer() { return issuer; } public Validity getValidity() { return validity; } public Name getSubject() { return subject; } public SubjectPublicKeyInfo getSubjectPublicKeyInfo() { return subjectPublicKeyInfo; } public Extensions getExtensions() { return extensions; } }