Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2017-2018 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright (C) 2017-2018 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.util.ssl.cert;
import java.io.ByteArrayInputStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import com.unboundid.asn1.ASN1BigInteger;
import com.unboundid.asn1.ASN1BitString;
import com.unboundid.asn1.ASN1Constants;
import com.unboundid.asn1.ASN1Element;
import com.unboundid.asn1.ASN1Exception;
import com.unboundid.asn1.ASN1GeneralizedTime;
import com.unboundid.asn1.ASN1Integer;
import com.unboundid.asn1.ASN1ObjectIdentifier;
import com.unboundid.asn1.ASN1OctetString;
import com.unboundid.asn1.ASN1Sequence;
import com.unboundid.asn1.ASN1Set;
import com.unboundid.asn1.ASN1UTCTime;
import com.unboundid.asn1.ASN1UTF8String;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.RDN;
import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
import com.unboundid.ldap.sdk.schema.Schema;
import com.unboundid.util.Base64;
import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.OID;
import com.unboundid.util.ObjectPair;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import static com.unboundid.util.ssl.cert.CertMessages.*;
/**
* This class provides support for decoding an X.509 certificate as defined in
* RFC 5280. The certificate
* is encoded using the ASN.1 Distinguished Encoding Rules (DER), which is a
* subset of BER, and is supported by the code in the
* {@code com.unboundid.asn1} package. The ASN.1 specification is as follows:
*
* Certificate ::= SEQUENCE {
* tbsCertificate TBSCertificate,
* signatureAlgorithm AlgorithmIdentifier,
* signatureValue BIT STRING }
*
* TBSCertificate ::= SEQUENCE {
* version [0] EXPLICIT Version DEFAULT v1,
* serialNumber CertificateSerialNumber,
* signature AlgorithmIdentifier,
* issuer Name,
* validity Validity,
* subject Name,
* subjectPublicKeyInfo SubjectPublicKeyInfo,
* issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
* -- If present, version MUST be v2 or v3
* subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
* -- If present, version MUST be v2 or v3
* extensions [3] EXPLICIT Extensions OPTIONAL
* -- If present, version MUST be v3
* }
*
* Version ::= INTEGER { v1(0), v2(1), v3(2) }
*
* CertificateSerialNumber ::= INTEGER
*
* Validity ::= SEQUENCE {
* notBefore Time,
* notAfter Time }
*
* Time ::= CHOICE {
* utcTime UTCTime,
* generalTime GeneralizedTime }
*
* UniqueIdentifier ::= BIT STRING
*
* SubjectPublicKeyInfo ::= SEQUENCE {
* algorithm AlgorithmIdentifier,
* subjectPublicKey BIT STRING }
*
* Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
*
* Extension ::= SEQUENCE {
* extnID OBJECT IDENTIFIER,
* critical BOOLEAN DEFAULT FALSE,
* extnValue OCTET STRING
* -- contains the DER encoding of an ASN.1 value
* -- corresponding to the extension type identified
* -- by extnID
* }
*
* AlgorithmIdentifier ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER,
* parameters ANY DEFINED BY algorithm OPTIONAL }
*
* Name ::= CHOICE { -- only one possibility for now --
* rdnSequence RDNSequence }
*
* RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
*
* RelativeDistinguishedName ::=
* SET SIZE (1..MAX) OF AttributeTypeAndValue
*
* AttributeTypeAndValue ::= SEQUENCE {
* type AttributeType,
* value AttributeValue }
*
* AttributeType ::= OBJECT IDENTIFIER
*
* AttributeValue ::= ANY -- DEFINED BY AttributeType
*
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class X509Certificate
implements Serializable
{
/**
* The DER type for the version number element, which is explicitly typed.
*/
private static final byte TYPE_EXPLICIT_VERSION = (byte) 0xA0;
/**
* The DER type for the issuer unique ID element, which is implicitly typed.
*/
private static final byte TYPE_IMPLICIT_ISSUER_UNIQUE_ID = (byte) 0x81;
/**
* The DER type for the subject unique ID element, which is implicitly typed.
*/
private static final byte TYPE_IMPLICIT_SUBJECT_UNIQUE_ID = (byte) 0x82;
/**
* The DER type for the extensions element, which is explicitly typed.
*/
private static final byte TYPE_EXPLICIT_EXTENSIONS = (byte) 0xA3;
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = -4680448103099282243L;
// The issuer unique identifier for the certificate.
private final ASN1BitString issuerUniqueID;
// The signature value for the certificate.
private final ASN1BitString signatureValue;
// The encoded certificate public key.
private final ASN1BitString encodedPublicKey;
// The subject unique identifier for the certificate.
private final ASN1BitString subjectUniqueID;
// The ASN.1 element with the encoded public key algorithm parameters.
private final ASN1Element publicKeyAlgorithmParameters;
// The ASN.1 element with the encoded signature algorithm parameters.
private final ASN1Element signatureAlgorithmParameters;
// The certificate serial number.
private final BigInteger serialNumber;
// The bytes that comprise the encoded representation of the X.509
// certificate.
private final byte[] x509CertificateBytes;
// The decoded public key for this certificate, if available.
private final DecodedPublicKey decodedPublicKey;
// The issuer DN for the certificate.
private final DN issuerDN;
// The subject DN for the certificate.
private final DN subjectDN;
// The list of extensions for the certificate.
private final List extensions;
// The time that indicates the end of the certificate validity window.
private final long notAfter;
// The time that indicates the beginning of the certificate validity window.
private final long notBefore;
// The OID for the public key algorithm.
private final OID publicKeyAlgorithmOID;
// The OID for the signature algorithm.
private final OID signatureAlgorithmOID;
// The public key algorithm name that corresponds with the public key
// algorithm OID, if available.
private final String publicKeyAlgorithmName;
// The signature algorithm name that corresponds with the signature algorithm
// OID, if available.
private final String signatureAlgorithmName;
// The X.509 certificate version.
private final X509CertificateVersion version;
/**
* Creates a new X.509 certificate with the provided information. This is
* primarily intended for unit testing and other internal use.
*
* @param version The version number for the
* certificate.
* @param serialNumber The serial number for the
* certificate. This must not be
* {@code null}.
* @param signatureAlgorithmOID The signature algorithm OID for the
* certificate. This must not be
* {@code null}.
* @param signatureAlgorithmParameters The encoded signature algorithm
* parameters for the certificate. This
* may be {@code null} if there are no
* parameters.
* @param signatureValue The encoded signature for the
* certificate. This must not be
* {@code null}.
* @param issuerDN The issuer DN for the certificate.
* This must not be {@code null}.
* @param notBefore The validity start time for the
* certificate.
* @param notAfter The validity end time for the
* certificate.
* @param subjectDN The subject DN for the certificate.
* This must not be {@code null}.
* @param publicKeyAlgorithmOID The OID of the public key algorithm
* for the certificate. This must not
* be {@code null}.
* @param publicKeyAlgorithmParameters The encoded public key algorithm
* parameters for the certificate. This
* may be {@code null} if there are no
* parameters.
* @param encodedPublicKey The encoded public key for the
* certificate. This must not be
* {@code null}.
* @param decodedPublicKey The decoded public key for the
* certificate. This may be
* {@code null} if it is not available.
* @param issuerUniqueID The issuer unique ID for the
* certificate. This may be
* {@code null} if the certificate does
* not have an issuer unique ID.
* @param subjectUniqueID The subject unique ID for the
* certificate. This may be
* {@code null} if the certificate does
* not have a subject unique ID.
* @param extensions The set of extensions to include in
* the certificate. This must not be
* {@code null} but may be empty.
*
* @throws CertException If a problem is encountered while creating the
* certificate.
*/
X509Certificate(final X509CertificateVersion version,
final BigInteger serialNumber,
final OID signatureAlgorithmOID,
final ASN1Element signatureAlgorithmParameters,
final ASN1BitString signatureValue,
final DN issuerDN, final long notBefore, final long notAfter,
final DN subjectDN, final OID publicKeyAlgorithmOID,
final ASN1Element publicKeyAlgorithmParameters,
final ASN1BitString encodedPublicKey,
final DecodedPublicKey decodedPublicKey,
final ASN1BitString issuerUniqueID,
final ASN1BitString subjectUniqueID,
final X509CertificateExtension... extensions)
throws CertException
{
this.version = version;
this.serialNumber = serialNumber;
this.signatureAlgorithmOID = signatureAlgorithmOID;
this.signatureAlgorithmParameters = signatureAlgorithmParameters;
this.signatureValue = signatureValue;
this.issuerDN = issuerDN;
this.notBefore = notBefore;
this.notAfter = notAfter;
this.subjectDN = subjectDN;
this.publicKeyAlgorithmOID = publicKeyAlgorithmOID;
this.publicKeyAlgorithmParameters = publicKeyAlgorithmParameters;
this.encodedPublicKey = encodedPublicKey;
this.decodedPublicKey = decodedPublicKey;
this.issuerUniqueID = issuerUniqueID;
this.subjectUniqueID = subjectUniqueID;
this.extensions = StaticUtils.toList(extensions);
final SignatureAlgorithmIdentifier signatureAlgorithmIdentifier =
SignatureAlgorithmIdentifier.forOID(signatureAlgorithmOID);
if (signatureAlgorithmIdentifier == null)
{
signatureAlgorithmName = null;
}
else
{
signatureAlgorithmName =
signatureAlgorithmIdentifier.getUserFriendlyName();
}
final PublicKeyAlgorithmIdentifier publicKeyAlgorithmIdentifier =
PublicKeyAlgorithmIdentifier.forOID(publicKeyAlgorithmOID);
if (publicKeyAlgorithmIdentifier == null)
{
publicKeyAlgorithmName = null;
}
else
{
publicKeyAlgorithmName = publicKeyAlgorithmIdentifier.getName();
}
x509CertificateBytes = encode().encode();
}
/**
* Decodes the contents of the provided byte array as an X.509 certificate.
*
* @param encodedCertificate The byte array containing the encoded X.509
* certificate. This must not be {@code null}.
*
* @throws CertException If the contents of the provided byte array could
* not be decoded as a valid X.509 certificate.
*/
public X509Certificate(final byte[] encodedCertificate)
throws CertException
{
x509CertificateBytes = encodedCertificate;
final ASN1Element[] certificateElements;
try
{
certificateElements =
ASN1Sequence.decodeAsSequence(encodedCertificate).elements();
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_NOT_SEQUENCE.get(StaticUtils.getExceptionMessage(e)),
e);
}
if (certificateElements.length != 3)
{
throw new CertException(
ERR_CERT_DECODE_UNEXPECTED_SEQUENCE_ELEMENT_COUNT.get(
certificateElements.length));
}
final ASN1Element[] tbsCertificateElements;
try
{
tbsCertificateElements =
ASN1Sequence.decodeAsSequence(certificateElements[0]).elements();
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_FIRST_ELEMENT_NOT_SEQUENCE.get(
StaticUtils.getExceptionMessage(e)),
e);
}
int tbsCertificateElementIndex;
try
{
// The version element may or may not be present in a certificate. If it
// is present, then it will be explicitly tagged, which means that it's a
// constructed element with the DER-encoded integer inside it. If it is
// absent, then a default version of v1 will be used.
if ((tbsCertificateElements[0].getType() & 0xFF) == 0xA0)
{
final int versionIntValue = ASN1Integer.decodeAsInteger(
tbsCertificateElements[0].getValue()).intValue();
version = X509CertificateVersion.valueOf(versionIntValue);
if (version == null)
{
throw new CertException(
ERR_CERT_DECODE_UNSUPPORTED_VERSION.get(version));
}
tbsCertificateElementIndex = 1;
}
else
{
version = X509CertificateVersion.V1;
tbsCertificateElementIndex = 0;
}
}
catch (final CertException e)
{
Debug.debugException(e);
throw e;
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_CANNOT_PARSE_VERSION.get(
StaticUtils.getExceptionMessage(e)),
e);
}
try
{
serialNumber = tbsCertificateElements[tbsCertificateElementIndex++].
decodeAsBigInteger().getBigIntegerValue();
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_CANNOT_PARSE_SERIAL_NUMBER.get(
StaticUtils.getExceptionMessage(e)),
e);
}
try
{
final ASN1Element[] signatureAlgorithmElements =
tbsCertificateElements[tbsCertificateElementIndex++].
decodeAsSequence().elements();
signatureAlgorithmOID =
signatureAlgorithmElements[0].decodeAsObjectIdentifier().getOID();
if (signatureAlgorithmElements.length > 1)
{
signatureAlgorithmParameters = signatureAlgorithmElements[1];
}
else
{
signatureAlgorithmParameters = null;
}
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_CANNOT_PARSE_SIG_ALG.get(
StaticUtils.getExceptionMessage(e)),
e);
}
final SignatureAlgorithmIdentifier signatureAlgorithmIdentifier =
SignatureAlgorithmIdentifier.forOID(signatureAlgorithmOID);
if (signatureAlgorithmIdentifier == null)
{
signatureAlgorithmName = null;
}
else
{
signatureAlgorithmName =
signatureAlgorithmIdentifier.getUserFriendlyName();
}
try
{
issuerDN =
decodeName(tbsCertificateElements[tbsCertificateElementIndex++]);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_CANNOT_PARSE_ISSUER_DN.get(
StaticUtils.getExceptionMessage(e)),
e);
}
try
{
final ASN1Element[] validityElements =
tbsCertificateElements[tbsCertificateElementIndex++].
decodeAsSequence().elements();
switch (validityElements[0].getType())
{
case ASN1Constants.UNIVERSAL_UTC_TIME_TYPE:
notBefore = decodeUTCTime(validityElements[0]);
break;
case ASN1Constants.UNIVERSAL_GENERALIZED_TIME_TYPE:
notBefore = validityElements[0].decodeAsGeneralizedTime().getTime();
break;
default:
throw new CertException(
ERR_CERT_DECODE_NOT_BEFORE_UNEXPECTED_TYPE.get(
StaticUtils.toHex(validityElements[0].getType()),
StaticUtils.toHex(ASN1Constants.UNIVERSAL_UTC_TIME_TYPE),
StaticUtils.toHex(ASN1Constants.
UNIVERSAL_GENERALIZED_TIME_TYPE)));
}
switch (validityElements[1].getType())
{
case ASN1Constants.UNIVERSAL_UTC_TIME_TYPE:
notAfter = decodeUTCTime(validityElements[1]);
break;
case ASN1Constants.UNIVERSAL_GENERALIZED_TIME_TYPE:
notAfter = validityElements[1].decodeAsGeneralizedTime().getTime();
break;
default:
throw new CertException(
ERR_CERT_DECODE_NOT_AFTER_UNEXPECTED_TYPE.get(
StaticUtils.toHex(validityElements[0].getType()),
StaticUtils.toHex(ASN1Constants.UNIVERSAL_UTC_TIME_TYPE),
StaticUtils.toHex(ASN1Constants.
UNIVERSAL_GENERALIZED_TIME_TYPE)));
}
}
catch (final CertException e)
{
Debug.debugException(e);
throw e;
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_COULD_NOT_PARSE_VALIDITY.get(
StaticUtils.getExceptionMessage(e)),
e);
}
try
{
subjectDN =
decodeName(tbsCertificateElements[tbsCertificateElementIndex++]);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_CANNOT_PARSE_SUBJECT_DN.get(
StaticUtils.getExceptionMessage(e)),
e);
}
try
{
final ASN1Element[] subjectPublicKeyInfoElements =
tbsCertificateElements[tbsCertificateElementIndex++].
decodeAsSequence().elements();
final ASN1Element[] publicKeyAlgorithmElements =
subjectPublicKeyInfoElements[0].decodeAsSequence().elements();
publicKeyAlgorithmOID =
publicKeyAlgorithmElements[0].decodeAsObjectIdentifier().getOID();
if (publicKeyAlgorithmElements.length > 1)
{
publicKeyAlgorithmParameters = publicKeyAlgorithmElements[1];
}
else
{
publicKeyAlgorithmParameters = null;
}
encodedPublicKey = subjectPublicKeyInfoElements[1].decodeAsBitString();
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_CANNOT_PARSE_PUBLIC_KEY_INFO.get(
StaticUtils.getExceptionMessage(e)),
e);
}
final PublicKeyAlgorithmIdentifier publicKeyAlgorithmIdentifier =
PublicKeyAlgorithmIdentifier.forOID(publicKeyAlgorithmOID);
if (publicKeyAlgorithmIdentifier == null)
{
publicKeyAlgorithmName = null;
decodedPublicKey = null;
}
else
{
publicKeyAlgorithmName = publicKeyAlgorithmIdentifier.getName();
DecodedPublicKey pk = null;
switch (publicKeyAlgorithmIdentifier)
{
case RSA:
try
{
pk = new RSAPublicKey(encodedPublicKey);
}
catch (final Exception e)
{
Debug.debugException(e);
}
break;
case EC:
try
{
pk = new EllipticCurvePublicKey(encodedPublicKey);
}
catch (final Exception e)
{
Debug.debugException(e);
}
break;
}
decodedPublicKey = pk;
}
ASN1BitString issuerID = null;
ASN1BitString subjectID = null;
final ArrayList extList = new ArrayList<>(10);
for (;
tbsCertificateElementIndex < tbsCertificateElements.length;
tbsCertificateElementIndex++)
{
switch (tbsCertificateElements[tbsCertificateElementIndex].getType())
{
case (byte) 0x81:
try
{
issuerID = tbsCertificateElements[tbsCertificateElementIndex].
decodeAsBitString();
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_CANNOT_PARSE_ISSUER_UNIQUE_ID.get(
StaticUtils.getExceptionMessage(e)),
e);
}
break;
case (byte) 0x82:
try
{
subjectID = tbsCertificateElements[tbsCertificateElementIndex].
decodeAsBitString();
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_CANNOT_PARSE_SUBJECT_UNIQUE_ID.get(
StaticUtils.getExceptionMessage(e)),
e);
}
break;
case (byte) 0xA3:
try
{
// This element is explicitly tagged.
final ASN1Element[] extensionElements = ASN1Sequence.
decodeAsSequence(tbsCertificateElements[
tbsCertificateElementIndex].getValue()).elements();
for (final ASN1Element extensionElement : extensionElements)
{
extList.add(X509CertificateExtension.decode(extensionElement));
}
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_CANNOT_PARSE_EXTENSION.get(
StaticUtils.getExceptionMessage(e)),
e);
}
break;
}
}
issuerUniqueID = issuerID;
subjectUniqueID = subjectID;
extensions = Collections.unmodifiableList(extList);
try
{
final ASN1Element[] signatureAlgorithmElements =
certificateElements[1].decodeAsSequence().elements();
final OID oid =
signatureAlgorithmElements[0].decodeAsObjectIdentifier().getOID();
if (! oid.equals(signatureAlgorithmOID))
{
throw new CertException(
ERR_CERT_DECODE_SIG_ALG_MISMATCH.get(
signatureAlgorithmOID.toString(), oid.toString()));
}
}
catch (final CertException e)
{
Debug.debugException(e);
throw e;
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_CANNOT_PARSE_SIG_ALG.get(
StaticUtils.getExceptionMessage(e)),
e);
}
try
{
signatureValue = certificateElements[2].decodeAsBitString();
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_CANNOT_PARSE_SIG_VALUE.get(
StaticUtils.getExceptionMessage(e)),
e);
}
}
/**
* Decodes the provided ASN.1 element as an X.509 name.
*
* @param element The ASN.1 element to decode.
*
* @return The DN created from the decoded X.509 name.
*
* @throws CertException If a problem is encountered while trying to decode
* the X.509 name.
*/
static DN decodeName(final ASN1Element element)
throws CertException
{
Schema schema;
try
{
schema = Schema.getDefaultStandardSchema();
}
catch (final Exception e)
{
Debug.debugException(e);
schema = null;
}
final ASN1Element[] rdnElements;
try
{
rdnElements = ASN1Sequence.decodeAsSequence(element).elements();
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_NAME_NOT_SEQUENCE.get(
StaticUtils.getExceptionMessage(e)),
e);
}
final ArrayList rdns = new ArrayList<>(rdnElements.length);
for (int i=0; i < rdnElements.length; i++)
{
try
{
final ASN1Element[] attributeSetElements =
rdnElements[i].decodeAsSet().elements();
final String[] attributeNames = new String[attributeSetElements.length];
final byte[][] attributeValues =
new byte[attributeSetElements.length][];
for (int j=0; j < attributeSetElements.length; j++)
{
final ASN1Element[] attributeTypeAndValueElements =
ASN1Sequence.decodeAsSequence(attributeSetElements[j]).
elements();
final OID attributeTypeOID = attributeTypeAndValueElements[0].
decodeAsObjectIdentifier().getOID();
final AttributeTypeDefinition attributeType =
schema.getAttributeType(attributeTypeOID.toString());
if (attributeType == null)
{
attributeNames[j] = attributeTypeOID.toString();
}
else
{
attributeNames[j] = attributeType.getNameOrOID().toUpperCase();
}
attributeValues[j] = attributeTypeAndValueElements[1].
decodeAsOctetString().getValue();
}
rdns.add(new RDN(attributeNames, attributeValues, schema));
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_DECODE_CANNOT_PARSE_NAME_SEQUENCE_ELEMENT.get(i,
StaticUtils.getExceptionMessage(e)),
e);
}
}
Collections.reverse(rdns);
return new DN(rdns);
}
/**
* Decodes the provided ASN.1 element as a UTC time element and retrieves the
* corresponding time. As per the X.509 specification, the resulting value
* will be guaranteed to fall between the years 1950 and 2049.
*
* @param element The ASN.1 element to decode as a UTC time value.
*
* @return The decoded time value.
*
* @throws ASN1Exception If the provided element cannot be decoded as a UTC
* time element.
*/
private static long decodeUTCTime(final ASN1Element element)
throws ASN1Exception
{
final long timeValue = ASN1UTCTime.decodeAsUTCTime(element).getTime();
final GregorianCalendar calendar = new GregorianCalendar();
calendar.setTimeInMillis(timeValue);
final int year = calendar.get(Calendar.YEAR);
if (year < 1949)
{
calendar.set(Calendar.YEAR, (year + 100));
}
else if (year > 2050)
{
calendar.set(Calendar.YEAR, (year - 100));
}
return calendar.getTimeInMillis();
}
/**
* Encodes this X.509 certificate to an ASN.1 element.
*
* @return The encoded X.509 certificate.
*
* @throws CertException If a problem is encountered while trying to encode
* the X.509 certificate.
*/
ASN1Element encode()
throws CertException
{
try
{
final ArrayList tbsCertificateElements = new ArrayList<>(10);
if (version != X509CertificateVersion.V1)
{
tbsCertificateElements.add(new ASN1Element(TYPE_EXPLICIT_VERSION,
new ASN1Integer(version.getIntValue()).encode()));
}
tbsCertificateElements.add(new ASN1BigInteger(serialNumber));
if (signatureAlgorithmParameters == null)
{
tbsCertificateElements.add(new ASN1Sequence(
new ASN1ObjectIdentifier(signatureAlgorithmOID)));
}
else
{
tbsCertificateElements.add(new ASN1Sequence(
new ASN1ObjectIdentifier(signatureAlgorithmOID),
signatureAlgorithmParameters));
}
tbsCertificateElements.add(encodeName(issuerDN));
tbsCertificateElements.add(encodeValiditySequence(notBefore, notAfter));
tbsCertificateElements.add(encodeName(subjectDN));
if (publicKeyAlgorithmParameters == null)
{
tbsCertificateElements.add(new ASN1Sequence(
new ASN1Sequence(
new ASN1ObjectIdentifier(publicKeyAlgorithmOID)),
encodedPublicKey));
}
else
{
tbsCertificateElements.add(new ASN1Sequence(
new ASN1Sequence(
new ASN1ObjectIdentifier(publicKeyAlgorithmOID),
publicKeyAlgorithmParameters),
encodedPublicKey));
}
if (issuerUniqueID != null)
{
tbsCertificateElements.add(new ASN1BitString(
TYPE_IMPLICIT_ISSUER_UNIQUE_ID, issuerUniqueID.getBits()));
}
if (subjectUniqueID != null)
{
tbsCertificateElements.add(new ASN1BitString(
TYPE_IMPLICIT_SUBJECT_UNIQUE_ID, subjectUniqueID.getBits()));
}
if (! extensions.isEmpty())
{
final ArrayList extensionElements =
new ArrayList<>(extensions.size());
for (final X509CertificateExtension e : extensions)
{
extensionElements.add(e.encode());
}
tbsCertificateElements.add(new ASN1Element(TYPE_EXPLICIT_EXTENSIONS,
new ASN1Sequence(extensionElements).encode()));
}
final ArrayList certificateElements = new ArrayList<>(3);
certificateElements.add(new ASN1Sequence(tbsCertificateElements));
if (signatureAlgorithmParameters == null)
{
certificateElements.add(new ASN1Sequence(
new ASN1ObjectIdentifier(signatureAlgorithmOID)));
}
else
{
certificateElements.add(new ASN1Sequence(
new ASN1ObjectIdentifier(signatureAlgorithmOID),
signatureAlgorithmParameters));
}
certificateElements.add(signatureValue);
return new ASN1Sequence(certificateElements);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_ENCODE_ERROR.get(toString(),
StaticUtils.getExceptionMessage(e)),
e);
}
}
/**
* Encodes the provided DN as an X.509 name for inclusion in an encoded
* certificate.
*
* @param dn The DN to encode.
*
* @return The encoded X.509 name.
*
* @throws CertException If a problem is encountered while encoding the
* provided DN as an X.509 name.
*/
static ASN1Element encodeName(final DN dn)
throws CertException
{
final Schema schema;
try
{
schema = Schema.getDefaultStandardSchema();
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_ENCODE_NAME_CANNOT_GET_SCHEMA.get(String.valueOf(dn),
StaticUtils.getExceptionMessage(e)),
e);
}
final RDN[] rdns = dn.getRDNs();
final ArrayList rdnSequenceElements =
new ArrayList<>(rdns.length);
for (int i=rdns.length - 1; i >= 0; i--)
{
final RDN rdn =rdns[i];
final String[] names = rdn.getAttributeNames();
final String[] values = rdn.getAttributeValues();
final ArrayList rdnElements = new ArrayList<>(names.length);
for (int j=0; j < names.length; j++)
{
final AttributeTypeDefinition at = schema.getAttributeType(names[j]);
if (at == null)
{
throw new CertException(ERR_CERT_ENCODE_NAME_UNKNOWN_ATTR_TYPE.get(
String.valueOf(dn), names[j]));
}
try
{
rdnElements.add(new ASN1Sequence(
new ASN1ObjectIdentifier(at.getOID()),
new ASN1UTF8String(values[j])));
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_ENCODE_NAME_ERROR.get(String.valueOf(dn),
StaticUtils.getExceptionMessage(e)),
e);
}
}
rdnSequenceElements.add(new ASN1Set(rdnElements));
}
return new ASN1Sequence(rdnSequenceElements);
}
/**
* Encodes the certificate validity sequence, using a UTC time encoding if
* both notBefore and notAfter values fall within the range 1950-2049, and
* using generalized time if either value falls outside that range.
*
* @param notBefore The notBefore value to include in the sequence.
* @param notAfter The notAfter value to include in the sequence.
*
* @return The encoded validity sequence.
*/
static ASN1Sequence encodeValiditySequence(final long notBefore,
final long notAfter)
{
final GregorianCalendar notBeforeCalendar = new GregorianCalendar();
notBeforeCalendar.setTimeInMillis(notBefore);
final int notBeforeYear = notBeforeCalendar.get(Calendar.YEAR);
final GregorianCalendar notAfterCalendar = new GregorianCalendar();
notAfterCalendar.setTimeInMillis(notAfter);
final int notAfterYear = notAfterCalendar.get(Calendar.YEAR);
if ((notBeforeYear >= 1950) && (notBeforeYear <= 2049) &&
(notAfterYear >= 1950) && (notAfterYear <= 2049))
{
return new ASN1Sequence(
new ASN1UTCTime(notBefore),
new ASN1UTCTime(notAfter));
}
else
{
return new ASN1Sequence(
new ASN1GeneralizedTime(notBefore),
new ASN1GeneralizedTime(notAfter));
}
}
/**
* Generates a self-signed X.509 certificate with the provided information.
*
* @param signatureAlgorithm The algorithm to use to generate the signature.
* This must not be {@code null}.
* @param publicKeyAlgorithm The algorithm to use to generate the key pair.
* This must not be {@code null}.
* @param keySizeBits The size of the key to generate, in bits.
* @param subjectDN The subject DN for the certificate. This must
* not be {@code null}.
* @param notBefore The validity start time for the certificate.
* @param notAfter The validity end time for the certificate.
* @param extensions The set of extensions to include in the
* certificate. This may be {@code null} or empty
* if the certificate should not include any
* custom extensions. Note that the generated
* certificate will automatically include a
* {@link SubjectKeyIdentifierExtension}, so that
* should not be provided.
*
* @return An {@code ObjectPair} that contains both the self-signed
* certificate and its corresponding key pair.
*
* @throws CertException If a problem is encountered while creating the
* certificate.
*/
public static ObjectPair
generateSelfSignedCertificate(
final SignatureAlgorithmIdentifier signatureAlgorithm,
final PublicKeyAlgorithmIdentifier publicKeyAlgorithm,
final int keySizeBits, final DN subjectDN,
final long notBefore, final long notAfter,
final X509CertificateExtension... extensions)
throws CertException
{
// Generate the key pair for the certificate.
final KeyPairGenerator keyPairGenerator;
try
{
keyPairGenerator =
KeyPairGenerator.getInstance(publicKeyAlgorithm.getName());
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_GEN_SELF_SIGNED_CANNOT_GET_KEY_GENERATOR.get(
publicKeyAlgorithm.getName(),
StaticUtils.getExceptionMessage(e)),
e);
}
try
{
keyPairGenerator.initialize(keySizeBits);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_GEN_SELF_SIGNED_INVALID_KEY_SIZE.get(keySizeBits,
publicKeyAlgorithm.getName(),
StaticUtils.getExceptionMessage(e)),
e);
}
final KeyPair keyPair;
try
{
keyPair = keyPairGenerator.generateKeyPair();
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_GEN_SELF_SIGNED_CANNOT_GENERATE_KEY_PAIR.get(
keySizeBits, publicKeyAlgorithm.getName(),
StaticUtils.getExceptionMessage(e)),
e);
}
// Generate the certificate and return it along with the key pair.
final X509Certificate certificate = generateSelfSignedCertificate(
signatureAlgorithm, keyPair, subjectDN, notBefore, notAfter,
extensions);
return new ObjectPair<>(certificate, keyPair);
}
/**
* Generates a self-signed X.509 certificate with the provided information.
*
* @param signatureAlgorithm The algorithm to use to generate the signature.
* This must not be {@code null}.
* @param keyPair The key pair for the certificate. This must
* not be {@code null}.
* @param subjectDN The subject DN for the certificate. This must
* not be {@code null}.
* @param notBefore The validity start time for the certificate.
* @param notAfter The validity end time for the certificate.
* @param extensions The set of extensions to include in the
* certificate. This may be {@code null} or empty
* if the certificate should not include any
* custom extensions. Note that the generated
* certificate will automatically include a
* {@link SubjectKeyIdentifierExtension}, so that
* should not be provided.
*
* @return An {@code ObjectPair} that contains both the self-signed
* certificate and its corresponding key pair.
*
* @throws CertException If a problem is encountered while creating the
* certificate.
*/
public static X509Certificate generateSelfSignedCertificate(
final SignatureAlgorithmIdentifier signatureAlgorithm,
final KeyPair keyPair, final DN subjectDN,
final long notBefore, final long notAfter,
final X509CertificateExtension... extensions)
throws CertException
{
// Extract the parameters and encoded public key from the generated key
// pair. And while we're at it, generate a subject key identifier from
// the encoded public key.
DecodedPublicKey decodedPublicKey = null;
final ASN1BitString encodedPublicKey;
final ASN1Element publicKeyAlgorithmParameters;
final byte[] subjectKeyIdentifier;
final OID publicKeyAlgorithmOID;
try
{
final ASN1Element[] pkElements = ASN1Sequence.decodeAsSequence(
keyPair.getPublic().getEncoded()).elements();
final ASN1Element[] pkAlgIDElements = ASN1Sequence.decodeAsSequence(
pkElements[0]).elements();
publicKeyAlgorithmOID =
pkAlgIDElements[0].decodeAsObjectIdentifier().getOID();
if (pkAlgIDElements.length == 1)
{
publicKeyAlgorithmParameters = null;
}
else
{
publicKeyAlgorithmParameters = pkAlgIDElements[1];
}
encodedPublicKey = pkElements[1].decodeAsBitString();
try
{
if (publicKeyAlgorithmOID.equals(
PublicKeyAlgorithmIdentifier.RSA.getOID()))
{
decodedPublicKey = new RSAPublicKey(encodedPublicKey);
}
else if (publicKeyAlgorithmOID.equals(
PublicKeyAlgorithmIdentifier.EC.getOID()))
{
decodedPublicKey = new EllipticCurvePublicKey(encodedPublicKey);
}
}
catch (final Exception e)
{
Debug.debugException(e);
}
final MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
subjectKeyIdentifier = sha256.digest(encodedPublicKey.getBytes());
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_GEN_SELF_SIGNED_CANNOT_PARSE_KEY_PAIR.get(
StaticUtils.getExceptionMessage(e)),
e);
}
// Construct the set of all extensions for the certificate.
final ArrayList extensionList =
new ArrayList<>(10);
extensionList.add(new SubjectKeyIdentifierExtension(false,
new ASN1OctetString(subjectKeyIdentifier)));
if (extensions != null)
{
for (final X509CertificateExtension e : extensions)
{
if (! e.getOID().equals(SubjectKeyIdentifierExtension.
SUBJECT_KEY_IDENTIFIER_OID))
{
extensionList.add(e);
}
}
}
final X509CertificateExtension[] allExtensions =
new X509CertificateExtension[extensionList.size()];
extensionList.toArray(allExtensions);
// Encode the tbsCertificate sequence for the certificate and use it to
// generate the certificate's signature.
final BigInteger serialNumber = generateSerialNumber();
final ASN1BitString encodedSignature = generateSignature(signatureAlgorithm,
keyPair.getPrivate(), serialNumber, subjectDN, notBefore, notAfter,
subjectDN, publicKeyAlgorithmOID, publicKeyAlgorithmParameters,
encodedPublicKey, allExtensions);
// Construct and return the signed certificate and the private key.
return new X509Certificate(X509CertificateVersion.V3, serialNumber,
signatureAlgorithm.getOID(), null, encodedSignature, subjectDN,
notBefore, notAfter, subjectDN, publicKeyAlgorithmOID,
publicKeyAlgorithmParameters, encodedPublicKey, decodedPublicKey, null,
null, allExtensions);
}
/**
* Generates an issuer-signed X.509 certificate with the provided information.
*
* @param signatureAlgorithm
* The algorithm to use to generate the signature. This must not
* be {@code null}.
* @param issuerCertificate
* The certificate for the issuer. This must not be
* {@code null}.
* @param issuerPrivateKey
* The private key for the issuer. This must not be
* {@code null}.
* @param publicKeyAlgorithmOID
* The OID for the certificate's public key algorithm. This must
* not be {@code null}.
* @param publicKeyAlgorithmParameters
* The encoded public key algorithm parameters for the
* certificate. This may be {@code null} if there are no
* parameters.
* @param encodedPublicKey
* The encoded public key for the certificate. This must not be
* {@code null}.
* @param decodedPublicKey
* The decoded public key for the certificate. This may be
* {@code null} if it is not available.
* @param subjectDN
* The subject DN for the certificate. This must not be
* {@code null}.
* @param notBefore
* The validity start time for the certificate.
* @param notAfter
* The validity end time for the certificate.
* @param extensions
* The set of extensions to include in the certificate. This
* may be {@code null} or empty if the certificate should not
* include any custom extensions. Note that the generated
* certificate will automatically include a
* {@link SubjectKeyIdentifierExtension}, so that should not be
* provided. In addition, if the issuer certificate includes its
* own {@code SubjectKeyIdentifierExtension}, then its value will
* be used to generate an
* {@link AuthorityKeyIdentifierExtension}.
*
* @return The issuer-signed certificate.
*
* @throws CertException If a problem is encountered while creating the
* certificate.
*/
public static X509Certificate generateIssuerSignedCertificate(
final SignatureAlgorithmIdentifier signatureAlgorithm,
final X509Certificate issuerCertificate,
final PrivateKey issuerPrivateKey,
final OID publicKeyAlgorithmOID,
final ASN1Element publicKeyAlgorithmParameters,
final ASN1BitString encodedPublicKey,
final DecodedPublicKey decodedPublicKey, final DN subjectDN,
final long notBefore, final long notAfter,
final X509CertificateExtension... extensions)
throws CertException
{
// Generate a subject key identifier from the encoded public key.
final byte[] subjectKeyIdentifier;
try
{
final MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
subjectKeyIdentifier = sha256.digest(encodedPublicKey.getBytes());
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_GEN_ISSUER_SIGNED_CANNOT_GENERATE_KEY_ID.get(
StaticUtils.getExceptionMessage(e)),
e);
}
// If the issuer certificate contains a subject key identifier, then
// extract it to use as the authority key identifier.
ASN1OctetString authorityKeyIdentifier = null;
for (final X509CertificateExtension e : issuerCertificate.extensions)
{
if (e instanceof SubjectKeyIdentifierExtension)
{
authorityKeyIdentifier =
((SubjectKeyIdentifierExtension) e).getKeyIdentifier();
}
}
// Construct the set of all extensions for the certificate.
final ArrayList extensionList =
new ArrayList<>(10);
extensionList.add(new SubjectKeyIdentifierExtension(false,
new ASN1OctetString(subjectKeyIdentifier)));
if (authorityKeyIdentifier == null)
{
extensionList.add(new AuthorityKeyIdentifierExtension(false, null,
new GeneralNamesBuilder().addDirectoryName(
issuerCertificate.subjectDN).build(),
issuerCertificate.serialNumber));
}
else
{
extensionList.add(new AuthorityKeyIdentifierExtension(false,
authorityKeyIdentifier, null, null));
}
if (extensions != null)
{
for (final X509CertificateExtension e : extensions)
{
if (e.getOID().equals(
SubjectKeyIdentifierExtension.SUBJECT_KEY_IDENTIFIER_OID) ||
e.getOID().equals(
AuthorityKeyIdentifierExtension.AUTHORITY_KEY_IDENTIFIER_OID))
{
continue;
}
extensionList.add(e);
}
}
final X509CertificateExtension[] allExtensions =
new X509CertificateExtension[extensionList.size()];
extensionList.toArray(allExtensions);
// Encode the tbsCertificate sequence for the certificate and use it to
// generate the certificate's signature.
final BigInteger serialNumber = generateSerialNumber();
final ASN1BitString encodedSignature = generateSignature(signatureAlgorithm,
issuerPrivateKey, serialNumber, issuerCertificate.subjectDN, notBefore,
notAfter, subjectDN, publicKeyAlgorithmOID,
publicKeyAlgorithmParameters, encodedPublicKey, allExtensions);
// Construct and return the signed certificate.
return new X509Certificate(X509CertificateVersion.V3, serialNumber,
signatureAlgorithm.getOID(), null, encodedSignature,
issuerCertificate.subjectDN, notBefore, notAfter, subjectDN,
publicKeyAlgorithmOID, publicKeyAlgorithmParameters, encodedPublicKey,
decodedPublicKey, null, null, allExtensions);
}
/**
* Generates a serial number for the certificate.
*
* @return The generated serial number.
*/
private static BigInteger generateSerialNumber()
{
final UUID uuid = UUID.randomUUID();
final long msb = uuid.getMostSignificantBits() & 0x7FFF_FFFF_FFFF_FFFFL;
final long lsb = uuid.getLeastSignificantBits() & 0x7FFF_FFFF_FFFF_FFFFL;
return BigInteger.valueOf(msb).shiftLeft(64).add(BigInteger.valueOf(lsb));
}
/**
* Generates a signature for the certificate with the provided information.
*
* @param signatureAlgorithm The signature algorithm to use to
* generate the signature. This must
* not be {@code null}.
* @param privateKey The private key to use to sign the
* certificate. This must not be
* {@code null}.
* @param serialNumber The serial number for the
* certificate. This must not be
* {@code null}.
* @param issuerDN The issuer DN for the certificate.
* This must not be {@code null}.
* @param notBefore The validity start time for the
* certificate.
* @param notAfter The validity end time for the
* certificate.
* @param subjectDN The subject DN for the certificate.
* This must not be {@code null}.
* @param publicKeyAlgorithmOID The OID for the public key algorithm.
* This must not be {@code null}.
* @param publicKeyAlgorithmParameters The encoded public key algorithm
* parameters. This may be
* {@code null} if no parameters are
* needed.
* @param encodedPublicKey The encoded representation of the
* public key. This must not be
* {@code null}.
* @param extensions The set of extensions to include in
* the certificate. This must not be
* {@code null} but may be empty.
*
* @return An encoded representation of the generated signature.
*
* @throws CertException If a problem is encountered while generating the
* certificate.
*/
private static ASN1BitString generateSignature(
final SignatureAlgorithmIdentifier signatureAlgorithm,
final PrivateKey privateKey,
final BigInteger serialNumber,
final DN issuerDN, final long notBefore,
final long notAfter, final DN subjectDN,
final OID publicKeyAlgorithmOID,
final ASN1Element publicKeyAlgorithmParameters,
final ASN1BitString encodedPublicKey,
final X509CertificateExtension... extensions)
throws CertException
{
// Get and initialize the signature generator.
final Signature signature;
try
{
signature = Signature.getInstance(signatureAlgorithm.getJavaName());
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_GEN_SIGNATURE_CANNOT_GET_SIGNATURE_GENERATOR.get(
signatureAlgorithm.getJavaName(),
StaticUtils.getExceptionMessage(e)),
e);
}
try
{
signature.initSign(privateKey);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_GEN_SIGNATURE_CANNOT_INIT_SIGNATURE_GENERATOR.get(
signatureAlgorithm.getJavaName(),
StaticUtils.getExceptionMessage(e)),
e);
}
// Construct the tbsCertificate element of the certificate and compute its
// signature.
try
{
final ArrayList tbsCertificateElements = new ArrayList<>(8);
tbsCertificateElements.add(new ASN1Element(TYPE_EXPLICIT_VERSION,
new ASN1Integer(X509CertificateVersion.V3.getIntValue()).encode()));
tbsCertificateElements.add(new ASN1BigInteger(serialNumber));
tbsCertificateElements.add(new ASN1Sequence(
new ASN1ObjectIdentifier(signatureAlgorithm.getOID())));
tbsCertificateElements.add(encodeName(issuerDN));
tbsCertificateElements.add(encodeValiditySequence(notBefore, notAfter));
tbsCertificateElements.add(encodeName(subjectDN));
if (publicKeyAlgorithmParameters == null)
{
tbsCertificateElements.add(new ASN1Sequence(
new ASN1Sequence(
new ASN1ObjectIdentifier(publicKeyAlgorithmOID)),
encodedPublicKey));
}
else
{
tbsCertificateElements.add(new ASN1Sequence(
new ASN1Sequence(
new ASN1ObjectIdentifier(publicKeyAlgorithmOID),
publicKeyAlgorithmParameters),
encodedPublicKey));
}
final ArrayList extensionElements =
new ArrayList<>(extensions.length);
for (final X509CertificateExtension e : extensions)
{
extensionElements.add(e.encode());
}
tbsCertificateElements.add(new ASN1Element(TYPE_EXPLICIT_EXTENSIONS,
new ASN1Sequence(extensionElements).encode()));
final byte[] tbsCertificateBytes =
new ASN1Sequence(tbsCertificateElements).encode();
signature.update(tbsCertificateBytes);
final byte[] signatureBytes = signature.sign();
return new ASN1BitString(ASN1BitString.getBitsForBytes(signatureBytes));
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_GEN_SIGNATURE_CANNOT_COMPUTE.get(
signatureAlgorithm.getJavaName(),
StaticUtils.getExceptionMessage(e)),
e);
}
}
/**
* Retrieves the bytes that comprise the encoded representation of this X.509
* certificate.
*
* @return The bytes that comprise the encoded representation of this X.509
* certificate.
*/
public byte[] getX509CertificateBytes()
{
return x509CertificateBytes;
}
/**
* Retrieves the certificate version.
*
* @return The certificate version.
*/
public X509CertificateVersion getVersion()
{
return version;
}
/**
* Retrieves the certificate serial number.
*
* @return The certificate serial number.
*/
public BigInteger getSerialNumber()
{
return serialNumber;
}
/**
* Retrieves the certificate signature algorithm OID.
*
* @return The certificate signature algorithm OID.
*/
public OID getSignatureAlgorithmOID()
{
return signatureAlgorithmOID;
}
/**
* Retrieves the certificate signature algorithm name, if available.
*
* @return The certificate signature algorithm name, or {@code null} if the
* signature algorithm OID does not correspond to any known algorithm
* name.
*/
public String getSignatureAlgorithmName()
{
return signatureAlgorithmName;
}
/**
* Retrieves the signature algorithm name if it is available, or the string
* representation of the signature algorithm OID if not.
*
* @return The signature algorithm name or OID.
*/
public String getSignatureAlgorithmNameOrOID()
{
if (signatureAlgorithmName != null)
{
return signatureAlgorithmName;
}
else
{
return signatureAlgorithmOID.toString();
}
}
/**
* Retrieves the encoded signature algorithm parameters, if present.
*
* @return The encoded signature algorithm parameters, or {@code null} if
* there are no signature algorithm parameters.
*/
public ASN1Element getSignatureAlgorithmParameters()
{
return signatureAlgorithmParameters;
}
/**
* Retrieves the certificate issuer DN.
*
* @return The certificate issuer DN.
*/
public DN getIssuerDN()
{
return issuerDN;
}
/**
* Retrieves the certificate validity start time as the number of milliseconds
* since the epoch (January 1, 1970 UTC).
*
* @return The certificate validity start time as the number of milliseconds
* since the epoch.
*/
public long getNotBeforeTime()
{
return notBefore;
}
/**
* Retrieves the certificate validity start time as a {@code Date}.
*
* @return The certificate validity start time as a {@code Date}.
*/
public Date getNotBeforeDate()
{
return new Date(notBefore);
}
/**
* Retrieves the certificate validity end time as the number of milliseconds
* since the epoch (January 1, 1970 UTC).
*
* @return The certificate validity end time as the number of milliseconds
* since the epoch.
*/
public long getNotAfterTime()
{
return notAfter;
}
/**
* Retrieves the certificate validity end time as a {@code Date}.
*
* @return The certificate validity end time as a {@code Date}.
*/
public Date getNotAfterDate()
{
return new Date(notAfter);
}
/**
* Indicates whether the current time is within the certificate's validity
* window.
*
* @return {@code true} if the current time is within the certificate's
* validity window, or {@code false} if not.
*/
public boolean isWithinValidityWindow()
{
return isWithinValidityWindow(System.currentTimeMillis());
}
/**
* Indicates whether the provided {@code Date} represents a time within the
* certificate's validity window.
*
* @param date The {@code Date} for which to make the determination. It
* must not be {@code null}.
*
* @return {@code true} if the provided {@code Date} is within the
* certificate's validity window, or {@code false} if not.
*/
public boolean isWithinValidityWindow(final Date date)
{
return isWithinValidityWindow(date.getTime());
}
/**
* Indicates whether the specified time is within the certificate's validity
* window.
*
* @param time The time to for which to make the determination.
*
* @return {@code true} if the specified time is within the certificate's
* validity window, or {@code false} if not.
*/
public boolean isWithinValidityWindow(final long time)
{
return ((time >= notBefore) && (time <= notAfter));
}
/**
* Retrieves the certificate subject DN.
*
* @return The certificate subject DN.
*/
public DN getSubjectDN()
{
return subjectDN;
}
/**
* Retrieves the certificate public key algorithm OID.
*
* @return The certificate public key algorithm OID.
*/
public OID getPublicKeyAlgorithmOID()
{
return publicKeyAlgorithmOID;
}
/**
* Retrieves the certificate public key algorithm name, if available.
*
* @return The certificate public key algorithm name, or {@code null} if the
* public key algorithm OID does not correspond to any known
* algorithm name.
*/
public String getPublicKeyAlgorithmName()
{
return publicKeyAlgorithmName;
}
/**
* Retrieves the public key algorithm name if it is available, or the string
* representation of the public key algorithm OID if not.
*
* @return The signature algorithm name or OID.
*/
public String getPublicKeyAlgorithmNameOrOID()
{
if (publicKeyAlgorithmName != null)
{
return publicKeyAlgorithmName;
}
else
{
return publicKeyAlgorithmOID.toString();
}
}
/**
* Retrieves the encoded public key algorithm parameters, if present.
*
* @return The encoded public key algorithm parameters, or {@code null} if
* there are no public key algorithm parameters.
*/
public ASN1Element getPublicKeyAlgorithmParameters()
{
return publicKeyAlgorithmParameters;
}
/**
* Retrieves the encoded public key as a bit string.
*
* @return The encoded public key as a bit string.
*/
public ASN1BitString getEncodedPublicKey()
{
return encodedPublicKey;
}
/**
* Retrieves a decoded representation of the public key, if available.
*
* @return A decoded representation of the public key, or {@code null} if the
* public key could not be decoded.
*/
public DecodedPublicKey getDecodedPublicKey()
{
return decodedPublicKey;
}
/**
* Retrieves the issuer unique identifier for the certificate, if any.
*
* @return The issuer unique identifier for the certificate, or {@code null}
* if there is none.
*/
public ASN1BitString getIssuerUniqueID()
{
return issuerUniqueID;
}
/**
* Retrieves the subject unique identifier for the certificate, if any.
*
* @return The subject unique identifier for the certificate, or {@code null}
* if there is none.
*/
public ASN1BitString getSubjectUniqueID()
{
return subjectUniqueID;
}
/**
* Retrieves the list of certificate extensions.
*
* @return The list of certificate extensions.
*/
public List getExtensions()
{
return extensions;
}
/**
* Retrieves the signature value for the certificate.
*
* @return The signature value for the certificate.
*/
public ASN1BitString getSignatureValue()
{
return signatureValue;
}
/**
* Verifies the signature for this certificate.
*
* @param issuerCertificate The issuer certificate for this certificate. It
* may be {@code null} if this is a self-signed
* certificate. It must not be {@code null} if it
* is not a self-signed certificate.
*
* @throws CertException If the certificate signature could not be verified.
*/
public void verifySignature(final X509Certificate issuerCertificate)
throws CertException
{
// Get the issuer certificate. If the certificate is self-signed, then it
// might be the current certificate.
final X509Certificate issuer;
if (issuerCertificate == null)
{
if (isSelfSigned())
{
issuer = this;
}
else
{
throw new CertException(
ERR_CERT_VERIFY_SIGNATURE_ISSUER_CERT_NOT_PROVIDED.get());
}
}
else
{
issuer = issuerCertificate;
}
// Get the public key from the issuer certificate.
final PublicKey publicKey;
try
{
publicKey = issuer.toCertificate().getPublicKey();
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_VERIFY_SIGNATURE_CANNOT_GET_PUBLIC_KEY.get(
StaticUtils.getExceptionMessage(e)),
e);
}
// Get and initialize the signature generator.
final Signature signature;
final SignatureAlgorithmIdentifier signatureAlgorithm;
try
{
signatureAlgorithm =
SignatureAlgorithmIdentifier.forOID(signatureAlgorithmOID);
signature = Signature.getInstance(signatureAlgorithm.getJavaName());
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_VERIFY_SIGNATURE_CANNOT_GET_SIGNATURE_VERIFIER.get(
getSignatureAlgorithmNameOrOID(),
StaticUtils.getExceptionMessage(e)),
e);
}
try
{
signature.initVerify(publicKey);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_VERIFY_SIGNATURE_CANNOT_INIT_SIGNATURE_VERIFIER.get(
signatureAlgorithm.getJavaName(),
StaticUtils.getExceptionMessage(e)),
e);
}
// Construct the tbsCertificate element of the certificate and compute its
// signature.
try
{
final ASN1Element[] x509CertificateElements =
ASN1Sequence.decodeAsSequence(x509CertificateBytes).elements();
final byte[] tbsCertificateBytes = x509CertificateElements[0].encode();
signature.update(tbsCertificateBytes);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_GEN_SIGNATURE_CANNOT_COMPUTE.get(
signatureAlgorithm.getJavaName(),
StaticUtils.getExceptionMessage(e)),
e);
}
try
{
if (! signature.verify(signatureValue.getBytes()))
{
throw new CertException(
ERR_CERT_VERIFY_SIGNATURE_NOT_VALID.get(subjectDN));
}
}
catch (final CertException ce)
{
Debug.debugException(ce);
throw ce;
}
catch (final Exception e)
{
Debug.debugException(e);
throw new CertException(
ERR_CERT_VERIFY_SIGNATURE_ERROR.get(subjectDN,
StaticUtils.getExceptionMessage(e)),
e);
}
}
/**
* Retrieves the bytes that comprise a SHA-1 fingerprint of this certificate.
*
* @return The bytes that comprise a SHA-1 fingerprint of this certificate.
*
* @throws CertException If a problem is encountered while computing the
* fingerprint.
*/
public byte[] getSHA1Fingerprint()
throws CertException
{
return getFingerprint("SHA-1");
}
/**
* Retrieves the bytes that comprise a 256-bit SHA-2 fingerprint of this
* certificate.
*
* @return The bytes that comprise a 256-bit SHA-2 fingerprint of this
* certificate.
*
* @throws CertException If a problem is encountered while computing the
* fingerprint.
*/
public byte[] getSHA256Fingerprint()
throws CertException
{
return getFingerprint("SHA-256");
}
/**
* Retrieves the bytes that comprise a fingerprint of this certificate.
*
* @param digestAlgorithm The digest algorithm to use to generate the
* fingerprint.
*
* @return The bytes that comprise a fingerprint of this certificate.
*
* @throws CertException If a problem is encountered while computing the
* fingerprint.
*/
private byte[] getFingerprint(final String digestAlgorithm)
throws CertException
{
try
{
final MessageDigest digest = MessageDigest.getInstance(digestAlgorithm);
return digest.digest(x509CertificateBytes);
}
catch (final Exception e)
{
// This should never happen.
Debug.debugException(e);
throw new CertException(
ERR_CERT_CANNOT_COMPUTE_FINGERPRINT.get(digestAlgorithm,
StaticUtils.getExceptionMessage(e)),
e);
}
}
/**
* Indicates whether this certificate is self-signed. The following criteria
* will be used to make the determination:
*
*
* If the certificate has both subject key identifier and authority
* key identifier extensions, then it will be considered self-signed if
* and only if the subject key identifier matches the authority key
* identifier.
*
*
* If the certificate does not have both a subject key identifier and an
* authority key identifier, then it will be considered self-signed if and
* only if its subject DN matches its issuer DN.
*
*
*
* @return {@code true} if this certificate is self-signed, or {@code false}
* if it is not.
*/
public boolean isSelfSigned()
{
AuthorityKeyIdentifierExtension akie = null;
SubjectKeyIdentifierExtension skie = null;
for (final X509CertificateExtension e : extensions)
{
if (e instanceof AuthorityKeyIdentifierExtension)
{
akie = (AuthorityKeyIdentifierExtension) e;
}
else if (e instanceof SubjectKeyIdentifierExtension)
{
skie = (SubjectKeyIdentifierExtension) e;
}
}
if ((akie != null) && (skie != null))
{
return ((akie.getKeyIdentifier() != null) &&
Arrays.equals(akie.getKeyIdentifier().getValue(),
skie.getKeyIdentifier().getValue()));
}
else
{
return subjectDN.equals(issuerDN);
}
}
/**
* Indicates whether this certificate is the issuer for the provided
* certificate. In order for this to be true, the following conditions must
* be met:
*
*
* The subject DN of this certificate must match the issuer DN for the
* provided certificate.
*
*
* If the provided certificate has an authority key identifier extension,
* then this certificate must have a subject key identifier extension with
* a matching identifier value.
*
*
*
* @param c The certificate for which to make the determination. This must
* not be {@code null}.
*
* @return {@code true} if this certificate is considered the issuer for the
* provided certificate, or {@code } false if not.
*/
public boolean isIssuerFor(final X509Certificate c)
{
return isIssuerFor(c, null);
}
/**
* Indicates whether this certificate is the issuer for the provided
* certificate. In order for this to be true, the following conditions must
* be met:
*
*
* The subject DN of this certificate must match the issuer DN for the
* provided certificate.
*
*
* If the provided certificate has an authority key identifier extension,
* then this certificate must have a subject key identifier extension with
* a matching identifier value.
*
*
*
* @param c The certificate for which to make the
* determination. This must not be {@code null}.
* @param nonMatchReason An optional buffer that may be updated with the
* reason that this certificate is not considered the
* issuer for the provided certificate. This may be
* {@code null} if the caller does not require a
* reason.
*
* @return {@code true} if this certificate is considered the issuer for the
* provided certificate, or {@code } false if not.
*/
public boolean isIssuerFor(final X509Certificate c,
final StringBuilder nonMatchReason)
{
if (! c.issuerDN.equals(subjectDN))
{
if (nonMatchReason != null)
{
nonMatchReason.append(INFO_CERT_IS_ISSUER_FOR_DN_MISMATCH.get(
subjectDN, c.subjectDN, issuerDN));
}
return false;
}
byte[] authorityKeyIdentifier = null;
for (final X509CertificateExtension extension : c.extensions)
{
if (extension instanceof AuthorityKeyIdentifierExtension)
{
final AuthorityKeyIdentifierExtension akie =
(AuthorityKeyIdentifierExtension) extension;
if (akie.getKeyIdentifier() != null)
{
authorityKeyIdentifier = akie.getKeyIdentifier().getValue();
break;
}
}
}
if (authorityKeyIdentifier != null)
{
boolean matchFound = false;
for (final X509CertificateExtension extension : extensions)
{
if (extension instanceof SubjectKeyIdentifierExtension)
{
final SubjectKeyIdentifierExtension skie =
(SubjectKeyIdentifierExtension) extension;
matchFound = Arrays.equals(authorityKeyIdentifier,
skie.getKeyIdentifier().getValue());
break;
}
}
if (! matchFound)
{
if (nonMatchReason != null)
{
nonMatchReason.append(INFO_CERT_IS_ISSUER_FOR_KEY_ID_MISMATCH.get(
subjectDN, c.subjectDN));
}
return false;
}
}
return true;
}
/**
* Converts this X.509 certificate object to a Java {@code Certificate}
* object.
*
* @return The Java {@code Certificate} object that corresponds to this
* X.509 certificate.
*
* @throws CertificateException If a problem is encountered while performing
* the conversion.
*/
public Certificate toCertificate()
throws CertificateException
{
return CertificateFactory.getInstance("X.509").generateCertificate(
new ByteArrayInputStream(x509CertificateBytes));
}
/**
* Retrieves a string representation of the decoded X.509 certificate.
*
* @return A string representation of the decoded X.509 certificate.
*/
@Override()
public String toString()
{
final StringBuilder buffer = new StringBuilder();
toString(buffer);
return buffer.toString();
}
/**
* Appends a string representation of the decoded X.509 certificate to the
* provided buffer.
*
* @param buffer The buffer to which the information should be appended.
*/
public void toString(final StringBuilder buffer)
{
buffer.append("X509Certificate(version='");
buffer.append(version.getName());
buffer.append("', serialNumber='");
StaticUtils.toHex(serialNumber.toByteArray(), ":", buffer);
buffer.append("', signatureAlgorithmOID='");
buffer.append(signatureAlgorithmOID.toString());
buffer.append('\'');
if (signatureAlgorithmName != null)
{
buffer.append(", signatureAlgorithmName='");
buffer.append(signatureAlgorithmName);
buffer.append('\'');
}
buffer.append(", issuerDN='");
buffer.append(issuerDN.toString());
buffer.append("', notBefore='");
buffer.append(StaticUtils.encodeGeneralizedTime(notBefore));
buffer.append("', notAfter='");
buffer.append(StaticUtils.encodeGeneralizedTime(notAfter));
buffer.append("', subjectDN='");
buffer.append(subjectDN.toString());
buffer.append("', publicKeyAlgorithmOID='");
buffer.append(publicKeyAlgorithmOID.toString());
buffer.append('\'');
if (publicKeyAlgorithmName != null)
{
buffer.append(", publicKeyAlgorithmName='");
buffer.append(publicKeyAlgorithmName);
buffer.append('\'');
}
buffer.append(", subjectPublicKey=");
if (decodedPublicKey == null)
{
buffer.append('\'');
try
{
StaticUtils.toHex(encodedPublicKey.getBytes(), ":", buffer);
}
catch (final Exception e)
{
Debug.debugException(e);
encodedPublicKey.toString(buffer);
}
buffer.append('\'');
}
else
{
decodedPublicKey.toString(buffer);
if (decodedPublicKey instanceof EllipticCurvePublicKey)
{
try
{
final OID namedCurveOID =
publicKeyAlgorithmParameters.decodeAsObjectIdentifier().getOID();
buffer.append(", ellipticCurvePublicKeyParameters=namedCurve='");
buffer.append(NamedCurve.getNameOrOID(namedCurveOID));
buffer.append('\'');
}
catch (final Exception e)
{
Debug.debugException(e);
}
}
}
if (issuerUniqueID != null)
{
buffer.append(", issuerUniqueID='");
buffer.append(issuerUniqueID.toString());
buffer.append('\'');
}
if (subjectUniqueID != null)
{
buffer.append(", subjectUniqueID='");
buffer.append(subjectUniqueID.toString());
buffer.append('\'');
}
if (! extensions.isEmpty())
{
buffer.append(", extensions={");
final Iterator iterator = extensions.iterator();
while (iterator.hasNext())
{
iterator.next().toString(buffer);
if (iterator.hasNext())
{
buffer.append(", ");
}
}
buffer.append('}');
}
buffer.append(", signatureValue='");
try
{
StaticUtils.toHex(signatureValue.getBytes(), ":", buffer);
}
catch (final Exception e)
{
Debug.debugException(e);
buffer.append(signatureValue.toString());
}
buffer.append("')");
}
/**
* Retrieves a list of the lines that comprise a PEM representation of this
* X.509 certificate.
*
* @return A list of the lines that comprise a PEM representation of this
* X.509 certificate.
*/
public List toPEM()
{
final ArrayList lines = new ArrayList<>(10);
lines.add("-----BEGIN CERTIFICATE-----");
final String certBase64 = Base64.encode(x509CertificateBytes);
lines.addAll(StaticUtils.wrapLine(certBase64, 64));
lines.add("-----END CERTIFICATE-----");
return Collections.unmodifiableList(lines);
}
/**
* Retrieves a multi-line string containing a PEM representation of this X.509
* certificate.
*
* @return A multi-line string containing a PEM representation of this X.509
* certificate.
*/
public String toPEMString()
{
final StringBuilder buffer = new StringBuilder();
buffer.append("-----BEGIN CERTIFICATE-----");
buffer.append(StaticUtils.EOL);
final String certBase64 = Base64.encode(x509CertificateBytes);
for (final String line : StaticUtils.wrapLine(certBase64, 64))
{
buffer.append(line);
buffer.append(StaticUtils.EOL);
}
buffer.append("-----END CERTIFICATE-----");
buffer.append(StaticUtils.EOL);
return buffer.toString();
}
}