package model.pki.cert; 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 **/ private final Int version; /** * The serial number of that certificate that is unique across the CA. *
* serialNumber CertificateSerialNumber * CertificateSerialNumber ::= INTEGER **/ private final Int serialNumber; private final AlgorithmIdentifier signature; /** * The subject and issuer distinguished names. *
* issuer Name, * subject Name **/ private final Name issuer; /** * The validity period of that certificate. * Validity ::= SEQUENCE { notBefore Time, notAfter Time, ... } */ private final Validity validity; /** * See the comments on issuer. */ private final Name subject; private final SubjectPublicKeyInfo subjectPublicKeyInfo; /** * [3] Optional. */ 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.