aboutsummaryrefslogtreecommitdiff
path: root/src/main/model/pki/cert
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/model/pki/cert')
-rw-r--r--src/main/model/pki/cert/Certificate.java127
-rw-r--r--src/main/model/pki/cert/Extension.java113
-rw-r--r--src/main/model/pki/cert/Extensions.java67
-rw-r--r--src/main/model/pki/cert/TbsCertificate.java263
-rw-r--r--src/main/model/pki/cert/Validity.java95
5 files changed, 665 insertions, 0 deletions
diff --git a/src/main/model/pki/cert/Certificate.java b/src/main/model/pki/cert/Certificate.java
new file mode 100644
index 0000000..4e6c291
--- /dev/null
+++ b/src/main/model/pki/cert/Certificate.java
@@ -0,0 +1,127 @@
+package model.pki.cert;
+
+import model.asn1.ASN1Object;
+import model.asn1.BitString;
+import model.asn1.Tag;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import model.pki.AlgorithmIdentifier;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Stream;
+
+/**
+ * Represents an X.509 signed certificate.
+ * <pre>
+ * Certificate ::= SIGNED{TBSCertificate}
+ *
+ * ENCRYPTED{ToBeEnciphered} ::= BIT STRING (CONSTRAINED BY {
+ * -- shall be the result of applying an encipherment procedure
+ * -- to the BER-encoded octets of a value of -- ToBeEnciphered } )
+ *
+ * HASH{ToBeHashed} ::= SEQUENCE {
+ * algorithmIdentifier AlgorithmIdentifier{{SupportedAlgorithms}},
+ * hashValue BIT STRING (CONSTRAINED BY {
+ * -- shall be the result of applying a hashing procedure to the DER-encoded
+ * -- octets of a value of -- ToBeHashed } ),
+ * ... }
+ *
+ * ENCRYPTED-HASH{ToBeSigned} ::= BIT STRING (CONSTRAINED BY {
+ * -- shall be the result of applying a hashing procedure to the DER-encoded (see 6.2)
+ * -- octets of a value of -- ToBeSigned -- and then applying an encipherment procedure
+ * -- to those octets -- } )
+ *
+ * SIGNATURE{ToBeSigned} ::= SEQUENCE {
+ * algorithmIdentifier AlgorithmIdentifier{{SupportedAlgorithms}},
+ * encrypted ENCRYPTED-HASH{ToBeSigned},
+ * ... }
+ *
+ * SIGNED{ToBeSigned} ::= SEQUENCE {
+ * toBeSigned ToBeSigned,
+ * COMPONENTS OF SIGNATURE{ToBeSigned},
+ * ... }
+ * </pre>
+ *
+ * A certificate creates a binding between the proposed subject name and the public key. It is only valid once a trusted
+ * CA signs it. Relying parties only need to trust a single trust anchor (the Root CA), and all of its issued certs are
+ * trusted. This is done through the cert tree: each certificate contains the Issued By field, indicating the DN of the
+ * upper level, all the way until the root CA, which is hard-coded in relying parties.
+ */
+public class Certificate extends ASN1Object {
+ /**
+ * All info of that cert, excluding the signature.
+ * It will be signed, and the signature is in <pre>signature</pre>.
+ */
+ private final TbsCertificate certificate;
+
+ /**
+ * The algorithm used for <pre>signature</pre>.
+ */
+ private final AlgorithmIdentifier signatureAlgorithm;
+
+ /**
+ * The signature.
+ */
+ private final BitString signature;
+
+ /**
+ * EFFECTS: Initialize the object with the given tag and parentTag, and info, signatureAlgorithm, and signature.
+ * REQUIRES: The algorithm must match the signature. The fields must have correct tags as described in the class
+ * specification (SEQUENCE, SEQUENCE, BIT STRING).
+ */
+ public Certificate(Tag tag, Tag parentTag,
+ final TbsCertificate certificate,
+ final AlgorithmIdentifier signatureAlgorithm,
+ final BitString signature) {
+ super(tag, parentTag);
+ this.certificate = certificate;
+ this.signatureAlgorithm = signatureAlgorithm;
+ this.signature = signature;
+ }
+
+ /**
+ * EFFECTS: Parse input DER, without verifying the signature.
+ * Throws {@link ParseException} if the input is invalid:
+ * - Any fields missing
+ * - Any fields having an incorrect tag (as seen in the ASN.1 definition)
+ * - Any fields with encoding instructions that violate implicit / explicit encoding rules
+ * - Other issues found during parsing the object, like early EOF (see {@link ASN1Object})
+ * MODIFIES: this, encoded
+ */
+ public Certificate(BytesReader encoded, boolean hasParentTag) throws ParseException {
+ super(encoded, hasParentTag);
+ this.certificate = new TbsCertificate(encoded, false);
+ this.certificate.getTag().enforce(TAG_SEQUENCE);
+
+ this.signatureAlgorithm = new AlgorithmIdentifier(encoded, false);
+ this.signatureAlgorithm.getTag().enforce(TAG_SEQUENCE);
+
+ this.signature = new BitString(encoded, false);
+ this.signature.getTag().enforce(BitString.TAG);
+ }
+
+ /**
+ * EFFECT: Encode that sequence into an ordered array of bytes, following the class specification.
+ */
+ @Override
+ public Byte[] encodeValueDER() {
+ return Stream.of(Arrays.asList(certificate.encodeDER()),
+ Arrays.asList(signatureAlgorithm.encodeDER()),
+ Arrays.asList(signature.encodeDER()))
+ .flatMap(Collection::stream)
+ .toArray(Byte[]::new);
+ }
+
+ public TbsCertificate getCertificate() {
+ return certificate;
+ }
+
+ public AlgorithmIdentifier getSignatureAlgorithm() {
+ return signatureAlgorithm;
+ }
+
+ public BitString getSignature() {
+ return signature;
+ }
+}
diff --git a/src/main/model/pki/cert/Extension.java b/src/main/model/pki/cert/Extension.java
new file mode 100644
index 0000000..0c104a4
--- /dev/null
+++ b/src/main/model/pki/cert/Extension.java
@@ -0,0 +1,113 @@
+package model.pki.cert;
+
+import model.asn1.*;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.stream.Stream;
+
+/**
+ * A X.509v3 certificate extension entry.
+ * <pre>
+ * Extension ::= SEQUENCE {
+ * extnId EXTENSION.&id({ExtensionSet}),
+ * critical BOOLEAN DEFAULT FALSE,
+ * extnValue OCTET STRING
+ * (CONTAINING EXTENSION.&ExtnType({ExtensionSet}{@extnId})
+ * ENCODED BY der),
+ * ... }
+ * </pre>
+ * Extensions only exist in v3 certificates. They allow the CA and the relying party to add additional verification
+ * stages to the certificate to constraint its use or to supply additional information. For example, the CA may put a
+ * CDP (CRL Distribution Point) into the extensions.
+ */
+public class Extension extends ASN1Object {
+ /**
+ * The ID of the type of that extension.
+ */
+ private final ObjectIdentifier extnId;
+
+ /**
+ * Marking an extension critical means that the relying-party
+ * must reject that certificate if the type is unrecognized.
+ * If the type is recognized but cannot be fully parsed, the
+ * behaviour is undefined.
+ * Marking an extension critical reduces compatibility.
+ */
+ private final Bool critical;
+
+ /**
+ * The DER-encoded ASN.1 content of that extension.
+ */
+ private final OctetString extnValue;
+
+ /**
+ * EFFECTS: Init with tags, extnId, critical, and extnValue. For tags, see {@link ASN1Object}.
+ * extnValue is not checked against extnId.
+ * REQUIRES: Tags of extnId, critical, extnValue should be OID, BOOLEAN, OCTET STRING. The value should be a DER
+ * bytes octet string. If critical is unspecified (which defaults to false), put null.
+ */
+ public Extension(Tag tag, Tag parentTag,
+ final ObjectIdentifier extnId,
+ final Bool critical,
+ final OctetString extnValue) {
+ super(tag, parentTag);
+ this.extnId = extnId;
+ this.critical = critical;
+ this.extnValue = extnValue;
+ }
+
+ /**
+ * EFFECTS: Parse input DER.
+ * Throws {@link ParseException} if the input is invalid:
+ * - Any fields missing
+ * - Any fields having an incorrect tag (as seen in the ASN.1 definition)
+ * - Any fields with encoding instructions that violate implicit / explicit encoding rules
+ * - Other issues found during parsing the object, like early EOF (see {@link ASN1Object})
+ * Note that critical is optional, and if it does not exist, it will be left as null, and it should be treated as
+ * false.
+ * MODIFIES: this, encoded
+ */
+ public Extension(BytesReader encoded, boolean hasParentType) throws ParseException {
+ super(encoded, hasParentType);
+ this.extnId = new ObjectIdentifier(encoded, false);
+ this.extnId.getTag().enforce(ObjectIdentifier.TAG);
+
+ if (encoded.detectTag(Bool.TAG)) {
+ critical = new Bool(encoded, false);
+ } else {
+ critical = null;
+ }
+
+ this.extnValue = new OctetString(encoded, false);
+ this.extnValue.getTag().enforce(OctetString.TAG);
+ }
+
+ /**
+ * EFFECTS: Encode the DER.
+ */
+ @Override
+ public Byte[] encodeValueDER() {
+ return Stream.of(Arrays.asList(extnId.encodeDER()),
+ critical == null ? Collections.<Byte>emptyList() :
+ Arrays.asList(critical.encodeDER()),
+ Arrays.asList(extnValue.encodeDER()))
+ .flatMap(Collection::stream)
+ .toArray(Byte[]::new);
+ }
+
+ public ObjectIdentifier getExtnId() {
+ return extnId;
+ }
+
+ public Bool getCritical() {
+ return critical;
+ }
+
+ public OctetString getExtnValue() {
+ return extnValue;
+ }
+}
diff --git a/src/main/model/pki/cert/Extensions.java b/src/main/model/pki/cert/Extensions.java
new file mode 100644
index 0000000..780fa2c
--- /dev/null
+++ b/src/main/model/pki/cert/Extensions.java
@@ -0,0 +1,67 @@
+package model.pki.cert;
+
+import model.asn1.ASN1Object;
+import model.asn1.Encodable;
+import model.asn1.Tag;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import model.x501.RelativeDistinguishedName;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+
+/**
+ * Represents an X.509 certificate extensions list:
+ * <pre>
+ * Extensions ::= SEQUENCE OF Extension
+ * </pre>
+ */
+public class Extensions extends ASN1Object {
+ private final Extension[] extensions;
+
+ /**
+ * EFFECT: Initialize with the given tags and extensions. For tag and parentTag, consult
+ * {@link ASN1Object}.
+ * REQUIRES: Extensions should have SEQUENCE tag.
+ */
+ public Extensions(Tag tag, Tag parentTag, Extension[] extensions) {
+ super(tag, parentTag);
+ this.extensions = extensions;
+ }
+
+ /**
+ * EFFECT: Parse the Name from input DER bytes. For details on parsing, refer to {@link ASN1Object}.
+ * Throws {@link ParseException} for invalid input.
+ * MODIFIES: this, encoded
+ */
+ public Extensions(BytesReader encoded, boolean hasParentTag) throws ParseException {
+ super(encoded, hasParentTag);
+ final List<Extension> list = new ArrayList<>();
+ for (int i = 0; i < getLength();) {
+ int index = encoded.getIndex();
+ final Extension ext = new Extension(encoded, false);
+ ext.getTag().enforce(TAG_SEQUENCE);
+ list.add(ext);
+ index = encoded.getIndex() - index;
+ i += index;
+ }
+ this.extensions = list.toArray(new Extension[0]);
+ }
+
+ /**
+ * EFFECTS: Encode the SEQUENCE OF into DER, keep order. RDNs will be encoded one-by-one.
+ */
+ @Override
+ public Byte[] encodeValueDER() {
+ return Stream.of(extensions)
+ .map(Encodable::encodeDER)
+ .flatMap(Arrays::stream)
+ .toArray(Byte[]::new);
+ }
+
+ public Extension[] getExtensions() {
+ return extensions;
+ }
+}
diff --git a/src/main/model/pki/cert/TbsCertificate.java b/src/main/model/pki/cert/TbsCertificate.java
new file mode 100644
index 0000000..1175456
--- /dev/null
+++ b/src/main/model/pki/cert/TbsCertificate.java
@@ -0,0 +1,263 @@
+package model.pki.cert;
+
+import model.asn1.*;
+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
+ *
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * 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.
+ * <pre>
+ * [0] Version DEFAULT v1
+ * </pre>
+ */
+ private final Int version;
+
+ /**
+ * The serial number of that certificate that is unique across the CA.
+ * <pre>
+ * serialNumber CertificateSerialNumber
+ * CertificateSerialNumber ::= INTEGER
+ * </pre>
+ */
+ private final Int serialNumber;
+
+ private final AlgorithmIdentifier signature;
+
+ /**
+ * The subject and issuer distinguished names.
+ * <pre>
+ * issuer Name,
+ * subject Name
+ * </pre>
+ */
+ 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) {
+ new Tag(encoded).enforce(new Tag(TagClass.CONTEXT_SPECIFIC, true, 3));
+ }
+ 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_V2
+ && 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.<Byte>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.<Byte>emptyList()
+ : Arrays.asList(extensions.encodeDER()))
+ .flatMap(Collection::stream)
+ .toArray(Byte[]::new);
+ }
+
+ 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;
+ }
+}
diff --git a/src/main/model/pki/cert/Validity.java b/src/main/model/pki/cert/Validity.java
new file mode 100644
index 0000000..76279ed
--- /dev/null
+++ b/src/main/model/pki/cert/Validity.java
@@ -0,0 +1,95 @@
+package model.pki.cert;
+
+import model.asn1.*;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Stream;
+
+/**
+ * Represents the following ASN.1 structure:
+ * <pre>
+ * Validity ::= SEQUENCE {
+ * notBefore Time,
+ * notAfter Time,
+ * ...
+ * }
+ *
+ * Time ::= CHOICE {
+ * utcTime UTCTime,
+ * generalizedTime GeneralizedTime
+ * }
+ * </pre>
+ * It describes the validity period of the certificate.
+ */
+public class Validity extends ASN1Object {
+ /**
+ * The certificate is not valid before that time.
+ */
+ private final ASN1Time notBefore;
+
+ /**
+ * The certificate is not valid after that time.
+ */
+ private final ASN1Time notAfter;
+
+ /**
+ * EFFECTS: Init with the given tag, parentTag, notBefore, and notAfter. For more info on tag and parentTag, see
+ * {@link ASN1Object}.
+ * REQUIRES: notBefore and notAfter are either UTCTime or GeneralizedTime.
+ */
+ public Validity(Tag tag, Tag parentTag,
+ ASN1Time notBefore, ASN1Time notAfter) {
+ super(tag, parentTag);
+ this.notBefore = notBefore;
+ this.notAfter = notAfter;
+ }
+
+ /**
+ * EFFECTS: Parse input DER.
+ * Throws {@link ASN1Object} if invalid:
+ * - Any fields missing (info, algorithm, signature)
+ * - Any fields having an incorrect tag (as seen in the ASN.1 definition)
+ * - Any fields with encoding instructions that violate implicit / explicit encoding rules
+ * - Other issues found during parsing the object, like early EOF (see {@link ASN1Object})
+ * MODIFIES: this, encoded
+ */
+ public Validity(BytesReader encoded, boolean hasParentTag) throws ParseException {
+ super(encoded, hasParentTag);
+ if (encoded.detectTag(GeneralizedTime.TAG)) {
+ this.notBefore = new GeneralizedTime(encoded, false);
+ this.notBefore.getTag().enforce(GeneralizedTime.TAG);
+ } else {
+ this.notBefore = new UtcTime(encoded, false);
+ this.notBefore.getTag().enforce(UtcTime.TAG);
+ }
+ if (encoded.detectTag(GeneralizedTime.TAG)) {
+ this.notAfter = new GeneralizedTime(encoded, false);
+ this.notAfter.getTag().enforce(GeneralizedTime.TAG);
+ } else {
+ this.notAfter = new UtcTime(encoded, false);
+ this.notAfter.getTag().enforce(UtcTime.TAG);
+ }
+ }
+
+ /**
+ * EFFECTS: Encode into ordered DER.
+ */
+ @Override
+ public Byte[] encodeValueDER() {
+ return Stream.of(Arrays.asList(notBefore.encodeDER()),
+ Arrays.asList(notAfter.encodeDER()))
+ .flatMap(Collection::stream)
+ .toArray(Byte[]::new);
+ }
+
+ public ASN1Time getNotBefore() {
+ return notBefore;
+ }
+
+ public ASN1Time getNotAfter() {
+ return notAfter;
+ }
+}