Path: blob/master/test/jdk/java/security/testlibrary/CertificateBuilder.java
66644 views
/*1* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.testlibrary;2627import java.io.*;28import java.util.*;29import java.security.*;30import java.security.cert.X509Certificate;31import java.security.cert.CertificateException;32import java.security.cert.CertificateFactory;33import java.security.cert.Extension;34import javax.security.auth.x500.X500Principal;35import java.math.BigInteger;3637import sun.security.util.DerOutputStream;38import sun.security.util.DerValue;39import sun.security.util.ObjectIdentifier;40import sun.security.util.SignatureUtil;41import sun.security.x509.AccessDescription;42import sun.security.x509.AlgorithmId;43import sun.security.x509.AuthorityInfoAccessExtension;44import sun.security.x509.AuthorityKeyIdentifierExtension;45import sun.security.x509.SubjectKeyIdentifierExtension;46import sun.security.x509.BasicConstraintsExtension;47import sun.security.x509.ExtendedKeyUsageExtension;48import sun.security.x509.DNSName;49import sun.security.x509.GeneralName;50import sun.security.x509.GeneralNames;51import sun.security.x509.KeyUsageExtension;52import sun.security.x509.SerialNumber;53import sun.security.x509.SubjectAlternativeNameExtension;54import sun.security.x509.URIName;55import sun.security.x509.KeyIdentifier;5657/**58* Helper class that builds and signs X.509 certificates.59*60* A CertificateBuilder is created with a default constructor, and then61* uses additional public methods to set the public key, desired validity62* dates, serial number and extensions. It is expected that the caller will63* have generated the necessary key pairs prior to using a CertificateBuilder64* to generate certificates.65*66* The following methods are mandatory before calling build():67* <UL>68* <LI>{@link #setSubjectName(java.lang.String)}69* <LI>{@link #setPublicKey(java.security.PublicKey)}70* <LI>{@link #setNotBefore(java.util.Date)} and71* {@link #setNotAfter(java.util.Date)}, or72* {@link #setValidity(java.util.Date, java.util.Date)}73* <LI>{@link #setSerialNumber(java.math.BigInteger)}74* </UL><BR>75*76* Additionally, the caller can either provide a {@link List} of77* {@link Extension} objects, or use the helper classes to add specific78* extension types.79*80* When all required and desired parameters are set, the81* {@link #build(java.security.cert.X509Certificate, java.security.PrivateKey,82* java.lang.String)} method can be used to create the {@link X509Certificate}83* object.84*85* Multiple certificates may be cut from the same settings using subsequent86* calls to the build method. Settings may be cleared using the87* {@link #reset()} method.88*/89public class CertificateBuilder {90private final CertificateFactory factory;9192private X500Principal subjectName = null;93private BigInteger serialNumber = null;94private PublicKey publicKey = null;95private Date notBefore = null;96private Date notAfter = null;97private final Map<String, Extension> extensions = new HashMap<>();98private byte[] tbsCertBytes;99private byte[] signatureBytes;100101/**102* Default constructor for a {@code CertificateBuilder} object.103*104* @throws CertificateException if the underlying {@link CertificateFactory}105* cannot be instantiated.106*/107public CertificateBuilder() throws CertificateException {108factory = CertificateFactory.getInstance("X.509");109}110111/**112* Set the subject name for the certificate.113*114* @param name An {@link X500Principal} to be used as the subject name115* on this certificate.116*/117public void setSubjectName(X500Principal name) {118subjectName = name;119}120121/**122* Set the subject name for the certificate.123*124* @param name The subject name in RFC 2253 format125*/126public void setSubjectName(String name) {127subjectName = new X500Principal(name);128}129130/**131* Set the public key for this certificate.132*133* @param pubKey The {@link PublicKey} to be used on this certificate.134*/135public void setPublicKey(PublicKey pubKey) {136publicKey = Objects.requireNonNull(pubKey, "Caught null public key");137}138139/**140* Set the NotBefore date on the certificate.141*142* @param nbDate A {@link Date} object specifying the start of the143* certificate validity period.144*/145public void setNotBefore(Date nbDate) {146Objects.requireNonNull(nbDate, "Caught null notBefore date");147notBefore = (Date)nbDate.clone();148}149150/**151* Set the NotAfter date on the certificate.152*153* @param naDate A {@link Date} object specifying the end of the154* certificate validity period.155*/156public void setNotAfter(Date naDate) {157Objects.requireNonNull(naDate, "Caught null notAfter date");158notAfter = (Date)naDate.clone();159}160161/**162* Set the validity period for the certificate163*164* @param nbDate A {@link Date} object specifying the start of the165* certificate validity period.166* @param naDate A {@link Date} object specifying the end of the167* certificate validity period.168*/169public void setValidity(Date nbDate, Date naDate) {170setNotBefore(nbDate);171setNotAfter(naDate);172}173174/**175* Set the serial number on the certificate.176*177* @param serial A serial number in {@link BigInteger} form.178*/179public void setSerialNumber(BigInteger serial) {180Objects.requireNonNull(serial, "Caught null serial number");181serialNumber = serial;182}183184185/**186* Add a single extension to the certificate.187*188* @param ext The extension to be added.189*/190public void addExtension(Extension ext) {191Objects.requireNonNull(ext, "Caught null extension");192extensions.put(ext.getId(), ext);193}194195/**196* Add multiple extensions contained in a {@code List}.197*198* @param extList The {@link List} of extensions to be added to199* the certificate.200*/201public void addExtensions(List<Extension> extList) {202Objects.requireNonNull(extList, "Caught null extension list");203for (Extension ext : extList) {204extensions.put(ext.getId(), ext);205}206}207208/**209* Helper method to add DNSName types for the SAN extension210*211* @param dnsNames A {@code List} of names to add as DNSName types212*213* @throws IOException if an encoding error occurs.214*/215public void addSubjectAltNameDNSExt(List<String> dnsNames) throws IOException {216if (!dnsNames.isEmpty()) {217GeneralNames gNames = new GeneralNames();218for (String name : dnsNames) {219gNames.add(new GeneralName(new DNSName(name)));220}221addExtension(new SubjectAlternativeNameExtension(false,222gNames));223}224}225226/**227* Helper method to add one or more OCSP URIs to the Authority Info Access228* certificate extension.229*230* @param locations A list of one or more OCSP responder URIs as strings231*232* @throws IOException if an encoding error occurs.233*/234public void addAIAExt(List<String> locations)235throws IOException {236if (!locations.isEmpty()) {237List<AccessDescription> acDescList = new ArrayList<>();238for (String ocspUri : locations) {239acDescList.add(new AccessDescription(240AccessDescription.Ad_OCSP_Id,241new GeneralName(new URIName(ocspUri))));242}243addExtension(new AuthorityInfoAccessExtension(acDescList));244}245}246247/**248* Set a Key Usage extension for the certificate. The extension will249* be marked critical.250*251* @param bitSettings Boolean array for all nine bit settings in the order252* documented in RFC 5280 section 4.2.1.3.253*254* @throws IOException if an encoding error occurs.255*/256public void addKeyUsageExt(boolean[] bitSettings) throws IOException {257addExtension(new KeyUsageExtension(bitSettings));258}259260/**261* Set the Basic Constraints Extension for a certificate.262*263* @param crit {@code true} if critical, {@code false} otherwise264* @param isCA {@code true} if the extension will be on a CA certificate,265* {@code false} otherwise266* @param maxPathLen The maximum path length issued by this CA. Values267* less than zero will omit this field from the resulting extension and268* no path length constraint will be asserted.269*270* @throws IOException if an encoding error occurs.271*/272public void addBasicConstraintsExt(boolean crit, boolean isCA,273int maxPathLen) throws IOException {274addExtension(new BasicConstraintsExtension(crit, isCA, maxPathLen));275}276277/**278* Add the Authority Key Identifier extension.279*280* @param authorityCert The certificate of the issuing authority.281*282* @throws IOException if an encoding error occurs.283*/284public void addAuthorityKeyIdExt(X509Certificate authorityCert)285throws IOException {286addAuthorityKeyIdExt(authorityCert.getPublicKey());287}288289/**290* Add the Authority Key Identifier extension.291*292* @param authorityKey The public key of the issuing authority.293*294* @throws IOException if an encoding error occurs.295*/296public void addAuthorityKeyIdExt(PublicKey authorityKey) throws IOException {297KeyIdentifier kid = new KeyIdentifier(authorityKey);298addExtension(new AuthorityKeyIdentifierExtension(kid, null, null));299}300301/**302* Add the Subject Key Identifier extension.303*304* @param subjectKey The public key to be used in the resulting certificate305*306* @throws IOException if an encoding error occurs.307*/308public void addSubjectKeyIdExt(PublicKey subjectKey) throws IOException {309byte[] keyIdBytes = new KeyIdentifier(subjectKey).getIdentifier();310addExtension(new SubjectKeyIdentifierExtension(keyIdBytes));311}312313/**314* Add the Extended Key Usage extension.315*316* @param ekuOids A {@link List} of object identifiers in string form.317*318* @throws IOException if an encoding error occurs.319*/320public void addExtendedKeyUsageExt(List<String> ekuOids)321throws IOException {322if (!ekuOids.isEmpty()) {323Vector<ObjectIdentifier> oidVector = new Vector<>();324for (String oid : ekuOids) {325oidVector.add(ObjectIdentifier.of(oid));326}327addExtension(new ExtendedKeyUsageExtension(oidVector));328}329}330331/**332* Clear all settings and return the {@code CertificateBuilder} to333* its default state.334*/335public void reset() {336extensions.clear();337subjectName = null;338notBefore = null;339notAfter = null;340serialNumber = null;341publicKey = null;342signatureBytes = null;343tbsCertBytes = null;344}345346/**347* Build the certificate.348*349* @param issuerCert The certificate of the issuing authority, or350* {@code null} if the resulting certificate is self-signed.351* @param issuerKey The private key of the issuing authority352* @param algName The signature algorithm name353*354* @return The resulting {@link X509Certificate}355*356* @throws IOException if an encoding error occurs.357* @throws CertificateException If the certificate cannot be generated358* by the underlying {@link CertificateFactory}359* @throws NoSuchAlgorithmException If an invalid signature algorithm360* is provided.361*/362public X509Certificate build(X509Certificate issuerCert,363PrivateKey issuerKey, String algName)364throws IOException, CertificateException, NoSuchAlgorithmException {365// TODO: add some basic checks (key usage, basic constraints maybe)366367byte[] encodedCert = encodeTopLevel(issuerCert, issuerKey, algName);368ByteArrayInputStream bais = new ByteArrayInputStream(encodedCert);369return (X509Certificate)factory.generateCertificate(bais);370}371372/**373* Encode the contents of the outer-most ASN.1 SEQUENCE:374*375* <PRE>376* Certificate ::= SEQUENCE {377* tbsCertificate TBSCertificate,378* signatureAlgorithm AlgorithmIdentifier,379* signatureValue BIT STRING }380* </PRE>381*382* @param issuerCert The certificate of the issuing authority, or383* {@code null} if the resulting certificate is self-signed.384* @param issuerKey The private key of the issuing authority385* @param signAlg The signature algorithm object386*387* @return The DER-encoded X.509 certificate388*389* @throws CertificateException If an error occurs during the390* signing process.391* @throws IOException if an encoding error occurs.392*/393private byte[] encodeTopLevel(X509Certificate issuerCert,394PrivateKey issuerKey, String algName)395throws CertificateException, IOException, NoSuchAlgorithmException {396397AlgorithmId signAlg = AlgorithmId.get(algName);398DerOutputStream outerSeq = new DerOutputStream();399DerOutputStream topLevelItems = new DerOutputStream();400401try {402Signature sig = SignatureUtil.fromKey(signAlg.getName(), issuerKey, (Provider)null);403// Rewrite signAlg, RSASSA-PSS needs some parameters.404signAlg = SignatureUtil.fromSignature(sig, issuerKey);405tbsCertBytes = encodeTbsCert(issuerCert, signAlg);406sig.update(tbsCertBytes);407signatureBytes = sig.sign();408} catch (GeneralSecurityException ge) {409throw new CertificateException(ge);410}411topLevelItems.write(tbsCertBytes);412signAlg.derEncode(topLevelItems);413topLevelItems.putBitString(signatureBytes);414outerSeq.write(DerValue.tag_Sequence, topLevelItems);415416return outerSeq.toByteArray();417}418419/**420* Encode the bytes for the TBSCertificate structure:421* <PRE>422* TBSCertificate ::= SEQUENCE {423* version [0] EXPLICIT Version DEFAULT v1,424* serialNumber CertificateSerialNumber,425* signature AlgorithmIdentifier,426* issuer Name,427* validity Validity,428* subject Name,429* subjectPublicKeyInfo SubjectPublicKeyInfo,430* issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,431* -- If present, version MUST be v2 or v3432* subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,433* -- If present, version MUST be v2 or v3434* extensions [3] EXPLICIT Extensions OPTIONAL435* -- If present, version MUST be v3436* }437*438* @param issuerCert The certificate of the issuing authority, or439* {@code null} if the resulting certificate is self-signed.440* @param signAlg The signature algorithm object441*442* @return The DER-encoded bytes for the TBSCertificate structure443*444* @throws IOException if an encoding error occurs.445*/446private byte[] encodeTbsCert(X509Certificate issuerCert,447AlgorithmId signAlg) throws IOException {448DerOutputStream tbsCertSeq = new DerOutputStream();449DerOutputStream tbsCertItems = new DerOutputStream();450451// Hardcode to V3452byte[] v3int = {0x02, 0x01, 0x02};453tbsCertItems.write(DerValue.createTag(DerValue.TAG_CONTEXT, true,454(byte)0), v3int);455456// Serial Number457SerialNumber sn = new SerialNumber(serialNumber);458sn.encode(tbsCertItems);459460// Algorithm ID461signAlg.derEncode(tbsCertItems);462463// Issuer Name464if (issuerCert != null) {465tbsCertItems.write(466issuerCert.getSubjectX500Principal().getEncoded());467} else {468// Self-signed469tbsCertItems.write(subjectName.getEncoded());470}471472// Validity period (set as UTCTime)473DerOutputStream valSeq = new DerOutputStream();474valSeq.putUTCTime(notBefore);475valSeq.putUTCTime(notAfter);476tbsCertItems.write(DerValue.tag_Sequence, valSeq);477478// Subject Name479tbsCertItems.write(subjectName.getEncoded());480481// SubjectPublicKeyInfo482tbsCertItems.write(publicKey.getEncoded());483484// TODO: Extensions!485encodeExtensions(tbsCertItems);486487// Wrap it all up in a SEQUENCE and return the bytes488tbsCertSeq.write(DerValue.tag_Sequence, tbsCertItems);489return tbsCertSeq.toByteArray();490}491492/**493* Encode the extensions segment for an X.509 Certificate:494*495* <PRE>496* Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension497*498* Extension ::= SEQUENCE {499* extnID OBJECT IDENTIFIER,500* critical BOOLEAN DEFAULT FALSE,501* extnValue OCTET STRING502* -- contains the DER encoding of an ASN.1 value503* -- corresponding to the extension type identified504* -- by extnID505* }506* </PRE>507*508* @param tbsStream The {@code DerOutputStream} that holds the509* TBSCertificate contents.510*511* @throws IOException if an encoding error occurs.512*/513private void encodeExtensions(DerOutputStream tbsStream)514throws IOException {515DerOutputStream extSequence = new DerOutputStream();516DerOutputStream extItems = new DerOutputStream();517518for (Extension ext : extensions.values()) {519ext.encode(extItems);520}521extSequence.write(DerValue.tag_Sequence, extItems);522tbsStream.write(DerValue.createTag(DerValue.TAG_CONTEXT, true,523(byte)3), extSequence);524}525526}527528529