All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.util.ssl.cert.X509Certificate Maven / Gradle / Ivy

/*
 * Copyright 2017-2024 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2017-2024 Ping Identity Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Copyright (C) 2017-2024 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.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.CryptoHelper;
import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;
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. @Nullable private final ASN1BitString issuerUniqueID; // The signature value for the certificate. @NotNull private final ASN1BitString signatureValue; // The encoded certificate public key. @NotNull private final ASN1BitString encodedPublicKey; // The subject unique identifier for the certificate. @Nullable private final ASN1BitString subjectUniqueID; // The ASN.1 element with the encoded public key algorithm parameters. @Nullable private final ASN1Element publicKeyAlgorithmParameters; // The ASN.1 element with the encoded signature algorithm parameters. @Nullable private final ASN1Element signatureAlgorithmParameters; // The certificate serial number. @NotNull private final BigInteger serialNumber; // The bytes that comprise the encoded representation of the X.509 // certificate. @NotNull private final byte[] x509CertificateBytes; // The decoded public key for this certificate, if available. @Nullable private final DecodedPublicKey decodedPublicKey; // The issuer DN for the certificate. @NotNull private final DN issuerDN; // The subject DN for the certificate. @NotNull private final DN subjectDN; // The list of extensions for the certificate. @NotNull 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. @NotNull private final OID publicKeyAlgorithmOID; // The OID for the signature algorithm. @NotNull private final OID signatureAlgorithmOID; // The public key algorithm name that corresponds with the public key // algorithm OID, if available. @Nullable private final String publicKeyAlgorithmName; // The signature algorithm name that corresponds with the signature algorithm // OID, if available. @Nullable private final String signatureAlgorithmName; // The X.509 certificate version. @NotNull 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(@NotNull final X509CertificateVersion version, @NotNull final BigInteger serialNumber, @NotNull final OID signatureAlgorithmOID, @Nullable final ASN1Element signatureAlgorithmParameters, @NotNull final ASN1BitString signatureValue, @NotNull final DN issuerDN, final long notBefore, final long notAfter, @NotNull final DN subjectDN, @NotNull final OID publicKeyAlgorithmOID, @Nullable final ASN1Element publicKeyAlgorithmParameters, @NotNull final ASN1BitString encodedPublicKey, @Nullable final DecodedPublicKey decodedPublicKey, @Nullable final ASN1BitString issuerUniqueID, @Nullable final ASN1BitString subjectUniqueID, @NotNull 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(@NotNull 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. */ @NotNull() static DN decodeName(@NotNull 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(@NotNull 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. */ @NotNull() 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. */ @NotNull() static ASN1Element encodeName(@NotNull 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. */ @NotNull() 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. */ @NotNull() public static ObjectPair generateSelfSignedCertificate( @NotNull final SignatureAlgorithmIdentifier signatureAlgorithm, @NotNull final PublicKeyAlgorithmIdentifier publicKeyAlgorithm, final int keySizeBits, @NotNull final DN subjectDN, final long notBefore, final long notAfter, @Nullable final X509CertificateExtension... extensions) throws CertException { // Generate the key pair for the certificate. final KeyPairGenerator keyPairGenerator; try { keyPairGenerator = CryptoHelper.getKeyPairGenerator(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. */ @NotNull() public static X509Certificate generateSelfSignedCertificate( @NotNull final SignatureAlgorithmIdentifier signatureAlgorithm, @NotNull final KeyPair keyPair, @NotNull final DN subjectDN, final long notBefore, final long notAfter, @Nullable 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 = CryptoHelper.getMessageDigest( SubjectKeyIdentifierExtension. SUBJECT_KEY_IDENTIFIER_DIGEST_ALGORITHM); 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. */ @NotNull() public static X509Certificate generateIssuerSignedCertificate( @NotNull final SignatureAlgorithmIdentifier signatureAlgorithm, @NotNull final X509Certificate issuerCertificate, @NotNull final PrivateKey issuerPrivateKey, @NotNull final OID publicKeyAlgorithmOID, @Nullable final ASN1Element publicKeyAlgorithmParameters, @NotNull final ASN1BitString encodedPublicKey, @Nullable final DecodedPublicKey decodedPublicKey, @NotNull final DN subjectDN, final long notBefore, final long notAfter, @NotNull final X509CertificateExtension... extensions) throws CertException { // Generate a subject key identifier from the encoded public key. final byte[] subjectKeyIdentifier; try { final MessageDigest sha256 = CryptoHelper.getMessageDigest( SubjectKeyIdentifierExtension. SUBJECT_KEY_IDENTIFIER_DIGEST_ALGORITHM); 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. */ @NotNull() private static BigInteger generateSerialNumber() { final UUID uuid = CryptoHelper.getRandomUUID(); 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. */ @NotNull() private static ASN1BitString generateSignature( @NotNull final SignatureAlgorithmIdentifier signatureAlgorithm, @NotNull final PrivateKey privateKey, @NotNull final BigInteger serialNumber, @NotNull final DN issuerDN, final long notBefore, final long notAfter, @NotNull final DN subjectDN, @NotNull final OID publicKeyAlgorithmOID, @Nullable final ASN1Element publicKeyAlgorithmParameters, @NotNull final ASN1BitString encodedPublicKey, @NotNull final X509CertificateExtension... extensions) throws CertException { // Get and initialize the signature generator. final Signature signature; try { signature = CryptoHelper.getSignature(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. */ @NotNull() public byte[] getX509CertificateBytes() { return x509CertificateBytes; } /** * Retrieves the certificate version. * * @return The certificate version. */ @NotNull() public X509CertificateVersion getVersion() { return version; } /** * Retrieves the certificate serial number. * * @return The certificate serial number. */ @NotNull() public BigInteger getSerialNumber() { return serialNumber; } /** * Retrieves the certificate signature algorithm OID. * * @return The certificate signature algorithm OID. */ @NotNull() 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. */ @Nullable() 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. */ @NotNull() 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. */ @Nullable() public ASN1Element getSignatureAlgorithmParameters() { return signatureAlgorithmParameters; } /** * Retrieves the certificate issuer DN. * * @return The certificate issuer DN. */ @NotNull() 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}. */ @NotNull() 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}. */ @NotNull() 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(@NotNull 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. */ @NotNull() public DN getSubjectDN() { return subjectDN; } /** * Retrieves the certificate public key algorithm OID. * * @return The certificate public key algorithm OID. */ @NotNull() 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. */ @Nullable() 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. */ @NotNull() 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. */ @Nullable() public ASN1Element getPublicKeyAlgorithmParameters() { return publicKeyAlgorithmParameters; } /** * Retrieves the encoded public key as a bit string. * * @return The encoded public key as a bit string. */ @NotNull() 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. */ @Nullable() 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. */ @Nullable() 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. */ @Nullable() public ASN1BitString getSubjectUniqueID() { return subjectUniqueID; } /** * Retrieves the list of certificate extensions. * * @return The list of certificate extensions. */ @NotNull() public List getExtensions() { return extensions; } /** * Retrieves the signature value for the certificate. * * @return The signature value for the certificate. */ @NotNull() 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(@Nullable 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 = CryptoHelper.getSignature(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. */ @NotNull() 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. */ @NotNull() 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. */ @NotNull() private byte[] getFingerprint(@NotNull final String digestAlgorithm) throws CertException { try { final MessageDigest digest = CryptoHelper.getMessageDigest(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: *
    *
  1. * 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. *
  2. *
  3. * 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. *
  4. *
* * @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: *
    *
  1. * The subject DN of this certificate must match the issuer DN for the * provided certificate. *
  2. *
  3. * 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. *
  4. *
* * @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(@NotNull 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: *
    *
  1. * The subject DN of this certificate must match the issuer DN for the * provided certificate. *
  2. *
  3. * 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. *
  4. *
* * @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(@NotNull final X509Certificate c, @Nullable 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. */ @NotNull() public Certificate toCertificate() throws CertificateException { return CryptoHelper.getCertificateFactory("X.509").generateCertificate( new ByteArrayInputStream(x509CertificateBytes)); } /** * Retrieves a hash code for this certificate. * * @return A hash code for this certificate. */ @Override() public int hashCode() { return Arrays.hashCode(x509CertificateBytes); } /** * Indicates whether the provided object is considered equal to this X.509 * certificate. * * @param o The object for which to make the determination. * * @return {@code true} if the provided object is considered equal to this * X.509 certificate, or {@code false} if not. */ @Override() public boolean equals(@Nullable final Object o) { if (o == null) { return false; } if (o == this) { return true; } if (! (o instanceof X509Certificate)) { return false; } final X509Certificate c = (X509Certificate) o; return Arrays.equals(x509CertificateBytes, c.x509CertificateBytes); } /** * Retrieves a string representation of the decoded X.509 certificate. * * @return A string representation of the decoded X.509 certificate. */ @Override() @NotNull() 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(@NotNull 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. */ @NotNull() 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. */ @NotNull() 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(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy