Path: blob/master/test/jdk/java/security/testlibrary/CertificateBuilder.java
41149 views
/*1* Copyright (c) 2015, 2020, 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.x509.AccessDescription;41import sun.security.x509.AlgorithmId;42import sun.security.x509.AuthorityInfoAccessExtension;43import sun.security.x509.AuthorityKeyIdentifierExtension;44import sun.security.x509.SubjectKeyIdentifierExtension;45import sun.security.x509.BasicConstraintsExtension;46import sun.security.x509.ExtendedKeyUsageExtension;47import sun.security.x509.DNSName;48import sun.security.x509.GeneralName;49import sun.security.x509.GeneralNames;50import sun.security.x509.KeyUsageExtension;51import sun.security.x509.SerialNumber;52import sun.security.x509.SubjectAlternativeNameExtension;53import sun.security.x509.URIName;54import sun.security.x509.KeyIdentifier;5556/**57* Helper class that builds and signs X.509 certificates.58*59* A CertificateBuilder is created with a default constructor, and then60* uses additional public methods to set the public key, desired validity61* dates, serial number and extensions. It is expected that the caller will62* have generated the necessary key pairs prior to using a CertificateBuilder63* to generate certificates.64*65* The following methods are mandatory before calling build():66* <UL>67* <LI>{@link #setSubjectName(java.lang.String)}68* <LI>{@link #setPublicKey(java.security.PublicKey)}69* <LI>{@link #setNotBefore(java.util.Date)} and70* {@link #setNotAfter(java.util.Date)}, or71* {@link #setValidity(java.util.Date, java.util.Date)}72* <LI>{@link #setSerialNumber(java.math.BigInteger)}73* </UL><BR>74*75* Additionally, the caller can either provide a {@link List} of76* {@link Extension} objects, or use the helper classes to add specific77* extension types.78*79* When all required and desired parameters are set, the80* {@link #build(java.security.cert.X509Certificate, java.security.PrivateKey,81* java.lang.String)} method can be used to create the {@link X509Certificate}82* object.83*84* Multiple certificates may be cut from the same settings using subsequent85* calls to the build method. Settings may be cleared using the86* {@link #reset()} method.87*/88public class CertificateBuilder {89private final CertificateFactory factory;9091private X500Principal subjectName = null;92private BigInteger serialNumber = null;93private PublicKey publicKey = null;94private Date notBefore = null;95private Date notAfter = null;96private final Map<String, Extension> extensions = new HashMap<>();97private byte[] tbsCertBytes;98private byte[] signatureBytes;99100/**101* Default constructor for a {@code CertificateBuilder} object.102*103* @throws CertificateException if the underlying {@link CertificateFactory}104* cannot be instantiated.105*/106public CertificateBuilder() throws CertificateException {107factory = CertificateFactory.getInstance("X.509");108}109110/**111* Set the subject name for the certificate.112*113* @param name An {@link X500Principal} to be used as the subject name114* on this certificate.115*/116public void setSubjectName(X500Principal name) {117subjectName = name;118}119120/**121* Set the subject name for the certificate.122*123* @param name The subject name in RFC 2253 format124*/125public void setSubjectName(String name) {126subjectName = new X500Principal(name);127}128129/**130* Set the public key for this certificate.131*132* @param pubKey The {@link PublicKey} to be used on this certificate.133*/134public void setPublicKey(PublicKey pubKey) {135publicKey = Objects.requireNonNull(pubKey, "Caught null public key");136}137138/**139* Set the NotBefore date on the certificate.140*141* @param nbDate A {@link Date} object specifying the start of the142* certificate validity period.143*/144public void setNotBefore(Date nbDate) {145Objects.requireNonNull(nbDate, "Caught null notBefore date");146notBefore = (Date)nbDate.clone();147}148149/**150* Set the NotAfter date on the certificate.151*152* @param naDate A {@link Date} object specifying the end of the153* certificate validity period.154*/155public void setNotAfter(Date naDate) {156Objects.requireNonNull(naDate, "Caught null notAfter date");157notAfter = (Date)naDate.clone();158}159160/**161* Set the validity period for the certificate162*163* @param nbDate A {@link Date} object specifying the start of the164* certificate validity period.165* @param naDate A {@link Date} object specifying the end of the166* certificate validity period.167*/168public void setValidity(Date nbDate, Date naDate) {169setNotBefore(nbDate);170setNotAfter(naDate);171}172173/**174* Set the serial number on the certificate.175*176* @param serial A serial number in {@link BigInteger} form.177*/178public void setSerialNumber(BigInteger serial) {179Objects.requireNonNull(serial, "Caught null serial number");180serialNumber = serial;181}182183184/**185* Add a single extension to the certificate.186*187* @param ext The extension to be added.188*/189public void addExtension(Extension ext) {190Objects.requireNonNull(ext, "Caught null extension");191extensions.put(ext.getId(), ext);192}193194/**195* Add multiple extensions contained in a {@code List}.196*197* @param extList The {@link List} of extensions to be added to198* the certificate.199*/200public void addExtensions(List<Extension> extList) {201Objects.requireNonNull(extList, "Caught null extension list");202for (Extension ext : extList) {203extensions.put(ext.getId(), ext);204}205}206207/**208* Helper method to add DNSName types for the SAN extension209*210* @param dnsNames A {@code List} of names to add as DNSName types211*212* @throws IOException if an encoding error occurs.213*/214public void addSubjectAltNameDNSExt(List<String> dnsNames) throws IOException {215if (!dnsNames.isEmpty()) {216GeneralNames gNames = new GeneralNames();217for (String name : dnsNames) {218gNames.add(new GeneralName(new DNSName(name)));219}220addExtension(new SubjectAlternativeNameExtension(false,221gNames));222}223}224225/**226* Helper method to add one or more OCSP URIs to the Authority Info Access227* certificate extension.228*229* @param locations A list of one or more OCSP responder URIs as strings230*231* @throws IOException if an encoding error occurs.232*/233public void addAIAExt(List<String> locations)234throws IOException {235if (!locations.isEmpty()) {236List<AccessDescription> acDescList = new ArrayList<>();237for (String ocspUri : locations) {238acDescList.add(new AccessDescription(239AccessDescription.Ad_OCSP_Id,240new GeneralName(new URIName(ocspUri))));241}242addExtension(new AuthorityInfoAccessExtension(acDescList));243}244}245246/**247* Set a Key Usage extension for the certificate. The extension will248* be marked critical.249*250* @param bitSettings Boolean array for all nine bit settings in the order251* documented in RFC 5280 section 4.2.1.3.252*253* @throws IOException if an encoding error occurs.254*/255public void addKeyUsageExt(boolean[] bitSettings) throws IOException {256addExtension(new KeyUsageExtension(bitSettings));257}258259/**260* Set the Basic Constraints Extension for a certificate.261*262* @param crit {@code true} if critical, {@code false} otherwise263* @param isCA {@code true} if the extension will be on a CA certificate,264* {@code false} otherwise265* @param maxPathLen The maximum path length issued by this CA. Values266* less than zero will omit this field from the resulting extension and267* no path length constraint will be asserted.268*269* @throws IOException if an encoding error occurs.270*/271public void addBasicConstraintsExt(boolean crit, boolean isCA,272int maxPathLen) throws IOException {273addExtension(new BasicConstraintsExtension(crit, isCA, maxPathLen));274}275276/**277* Add the Authority Key Identifier extension.278*279* @param authorityCert The certificate of the issuing authority.280*281* @throws IOException if an encoding error occurs.282*/283public void addAuthorityKeyIdExt(X509Certificate authorityCert)284throws IOException {285addAuthorityKeyIdExt(authorityCert.getPublicKey());286}287288/**289* Add the Authority Key Identifier extension.290*291* @param authorityKey The public key of the issuing authority.292*293* @throws IOException if an encoding error occurs.294*/295public void addAuthorityKeyIdExt(PublicKey authorityKey) throws IOException {296KeyIdentifier kid = new KeyIdentifier(authorityKey);297addExtension(new AuthorityKeyIdentifierExtension(kid, null, null));298}299300/**301* Add the Subject Key Identifier extension.302*303* @param subjectKey The public key to be used in the resulting certificate304*305* @throws IOException if an encoding error occurs.306*/307public void addSubjectKeyIdExt(PublicKey subjectKey) throws IOException {308byte[] keyIdBytes = new KeyIdentifier(subjectKey).getIdentifier();309addExtension(new SubjectKeyIdentifierExtension(keyIdBytes));310}311312/**313* Add the Extended Key Usage extension.314*315* @param ekuOids A {@link List} of object identifiers in string form.316*317* @throws IOException if an encoding error occurs.318*/319public void addExtendedKeyUsageExt(List<String> ekuOids)320throws IOException {321if (!ekuOids.isEmpty()) {322Vector<ObjectIdentifier> oidVector = new Vector<>();323for (String oid : ekuOids) {324oidVector.add(ObjectIdentifier.of(oid));325}326addExtension(new ExtendedKeyUsageExtension(oidVector));327}328}329330/**331* Clear all settings and return the {@code CertificateBuilder} to332* its default state.333*/334public void reset() {335extensions.clear();336subjectName = null;337notBefore = null;338notAfter = null;339serialNumber = null;340publicKey = null;341signatureBytes = null;342tbsCertBytes = null;343}344345/**346* Build the certificate.347*348* @param issuerCert The certificate of the issuing authority, or349* {@code null} if the resulting certificate is self-signed.350* @param issuerKey The private key of the issuing authority351* @param algName The signature algorithm name352*353* @return The resulting {@link X509Certificate}354*355* @throws IOException if an encoding error occurs.356* @throws CertificateException If the certificate cannot be generated357* by the underlying {@link CertificateFactory}358* @throws NoSuchAlgorithmException If an invalid signature algorithm359* is provided.360*/361public X509Certificate build(X509Certificate issuerCert,362PrivateKey issuerKey, String algName)363throws IOException, CertificateException, NoSuchAlgorithmException {364// TODO: add some basic checks (key usage, basic constraints maybe)365366AlgorithmId signAlg = AlgorithmId.get(algName);367byte[] encodedCert = encodeTopLevel(issuerCert, issuerKey, signAlg);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, AlgorithmId signAlg)395throws CertificateException, IOException {396DerOutputStream outerSeq = new DerOutputStream();397DerOutputStream topLevelItems = new DerOutputStream();398399tbsCertBytes = encodeTbsCert(issuerCert, signAlg);400topLevelItems.write(tbsCertBytes);401try {402signatureBytes = signCert(issuerKey, signAlg);403} catch (GeneralSecurityException ge) {404throw new CertificateException(ge);405}406signAlg.derEncode(topLevelItems);407topLevelItems.putBitString(signatureBytes);408outerSeq.write(DerValue.tag_Sequence, topLevelItems);409410return outerSeq.toByteArray();411}412413/**414* Encode the bytes for the TBSCertificate structure:415* <PRE>416* TBSCertificate ::= SEQUENCE {417* version [0] EXPLICIT Version DEFAULT v1,418* serialNumber CertificateSerialNumber,419* signature AlgorithmIdentifier,420* issuer Name,421* validity Validity,422* subject Name,423* subjectPublicKeyInfo SubjectPublicKeyInfo,424* issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,425* -- If present, version MUST be v2 or v3426* subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,427* -- If present, version MUST be v2 or v3428* extensions [3] EXPLICIT Extensions OPTIONAL429* -- If present, version MUST be v3430* }431*432* @param issuerCert The certificate of the issuing authority, or433* {@code null} if the resulting certificate is self-signed.434* @param signAlg The signature algorithm object435*436* @return The DER-encoded bytes for the TBSCertificate structure437*438* @throws IOException if an encoding error occurs.439*/440private byte[] encodeTbsCert(X509Certificate issuerCert,441AlgorithmId signAlg) throws IOException {442DerOutputStream tbsCertSeq = new DerOutputStream();443DerOutputStream tbsCertItems = new DerOutputStream();444445// Hardcode to V3446byte[] v3int = {0x02, 0x01, 0x02};447tbsCertItems.write(DerValue.createTag(DerValue.TAG_CONTEXT, true,448(byte)0), v3int);449450// Serial Number451SerialNumber sn = new SerialNumber(serialNumber);452sn.encode(tbsCertItems);453454// Algorithm ID455signAlg.derEncode(tbsCertItems);456457// Issuer Name458if (issuerCert != null) {459tbsCertItems.write(460issuerCert.getSubjectX500Principal().getEncoded());461} else {462// Self-signed463tbsCertItems.write(subjectName.getEncoded());464}465466// Validity period (set as UTCTime)467DerOutputStream valSeq = new DerOutputStream();468valSeq.putUTCTime(notBefore);469valSeq.putUTCTime(notAfter);470tbsCertItems.write(DerValue.tag_Sequence, valSeq);471472// Subject Name473tbsCertItems.write(subjectName.getEncoded());474475// SubjectPublicKeyInfo476tbsCertItems.write(publicKey.getEncoded());477478// TODO: Extensions!479encodeExtensions(tbsCertItems);480481// Wrap it all up in a SEQUENCE and return the bytes482tbsCertSeq.write(DerValue.tag_Sequence, tbsCertItems);483return tbsCertSeq.toByteArray();484}485486/**487* Encode the extensions segment for an X.509 Certificate:488*489* <PRE>490* Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension491*492* Extension ::= SEQUENCE {493* extnID OBJECT IDENTIFIER,494* critical BOOLEAN DEFAULT FALSE,495* extnValue OCTET STRING496* -- contains the DER encoding of an ASN.1 value497* -- corresponding to the extension type identified498* -- by extnID499* }500* </PRE>501*502* @param tbsStream The {@code DerOutputStream} that holds the503* TBSCertificate contents.504*505* @throws IOException if an encoding error occurs.506*/507private void encodeExtensions(DerOutputStream tbsStream)508throws IOException {509DerOutputStream extSequence = new DerOutputStream();510DerOutputStream extItems = new DerOutputStream();511512for (Extension ext : extensions.values()) {513ext.encode(extItems);514}515extSequence.write(DerValue.tag_Sequence, extItems);516tbsStream.write(DerValue.createTag(DerValue.TAG_CONTEXT, true,517(byte)3), extSequence);518}519520/**521* Digitally sign the X.509 certificate.522*523* @param issuerKey The private key of the issuing authority524* @param signAlg The signature algorithm object525*526* @return The digital signature bytes.527*528* @throws GeneralSecurityException If any errors occur during the529* digital signature process.530*/531private byte[] signCert(PrivateKey issuerKey, AlgorithmId signAlg)532throws GeneralSecurityException {533Signature sig = Signature.getInstance(signAlg.getName());534sig.initSign(issuerKey);535sig.update(tbsCertBytes);536return sig.sign();537}538}539540541