1 /* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.security.testlibrary; 27 28 import java.io.*; 29 import java.util.*; 30 import java.security.*; 31 import java.security.cert.X509Certificate; 32 import java.security.cert.CertificateException; 33 import java.security.cert.CertificateFactory; 34 import java.security.cert.Extension; 35 import javax.security.auth.x500.X500Principal; 36 import java.math.BigInteger; 37 38 import sun.security.util.DerOutputStream; 39 import sun.security.util.DerValue; 40 import sun.security.util.ObjectIdentifier; 41 import sun.security.x509.AccessDescription; 42 import sun.security.x509.AlgorithmId; 43 import sun.security.x509.AuthorityInfoAccessExtension; 44 import sun.security.x509.AuthorityKeyIdentifierExtension; 45 import sun.security.x509.SubjectKeyIdentifierExtension; 46 import sun.security.x509.BasicConstraintsExtension; 47 import sun.security.x509.ExtendedKeyUsageExtension; 48 import sun.security.x509.DNSName; 49 import sun.security.x509.GeneralName; 50 import sun.security.x509.GeneralNames; 51 import sun.security.x509.KeyUsageExtension; 52 import sun.security.x509.SerialNumber; 53 import sun.security.x509.SubjectAlternativeNameExtension; 54 import sun.security.x509.URIName; 55 import sun.security.x509.KeyIdentifier; 56 57 /** 58 * Helper class that builds and signs X.509 certificates. 59 * 60 * A CertificateBuilder is created with a default constructor, and then 61 * uses additional public methods to set the public key, desired validity 62 * dates, serial number and extensions. It is expected that the caller will 63 * have generated the necessary key pairs prior to using a CertificateBuilder 64 * 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)} and 71 * {@link #setNotAfter(java.util.Date)}, or 72 * {@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} of 77 * {@link Extension} objects, or use the helper classes to add specific 78 * extension types. 79 * 80 * When all required and desired parameters are set, the 81 * {@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 subsequent 86 * calls to the build method. Settings may be cleared using the 87 * {@link #reset()} method. 88 */ 89 public class CertificateBuilder { 90 private final CertificateFactory factory; 91 92 private X500Principal subjectName = null; 93 private BigInteger serialNumber = null; 94 private PublicKey publicKey = null; 95 private Date notBefore = null; 96 private Date notAfter = null; 97 private final Map<String, Extension> extensions = new HashMap<>(); 98 private byte[] tbsCertBytes; 99 private byte[] signatureBytes; 100 101 /** 102 * Default constructor for a {@code CertificateBuilder} object. 103 * 104 * @throws CertificateException if the underlying {@link CertificateFactory} 105 * cannot be instantiated. 106 */ CertificateBuilder()107 public CertificateBuilder() throws CertificateException { 108 factory = CertificateFactory.getInstance("X.509"); 109 } 110 111 /** 112 * Set the subject name for the certificate. 113 * 114 * @param name An {@link X500Principal} to be used as the subject name 115 * on this certificate. 116 */ setSubjectName(X500Principal name)117 public void setSubjectName(X500Principal name) { 118 subjectName = name; 119 } 120 121 /** 122 * Set the subject name for the certificate. 123 * 124 * @param name The subject name in RFC 2253 format 125 */ setSubjectName(String name)126 public void setSubjectName(String name) { 127 subjectName = new X500Principal(name); 128 } 129 130 /** 131 * Set the public key for this certificate. 132 * 133 * @param pubKey The {@link PublicKey} to be used on this certificate. 134 */ setPublicKey(PublicKey pubKey)135 public void setPublicKey(PublicKey pubKey) { 136 publicKey = Objects.requireNonNull(pubKey, "Caught null public key"); 137 } 138 139 /** 140 * Set the NotBefore date on the certificate. 141 * 142 * @param nbDate A {@link Date} object specifying the start of the 143 * certificate validity period. 144 */ setNotBefore(Date nbDate)145 public void setNotBefore(Date nbDate) { 146 Objects.requireNonNull(nbDate, "Caught null notBefore date"); 147 notBefore = (Date)nbDate.clone(); 148 } 149 150 /** 151 * Set the NotAfter date on the certificate. 152 * 153 * @param naDate A {@link Date} object specifying the end of the 154 * certificate validity period. 155 */ setNotAfter(Date naDate)156 public void setNotAfter(Date naDate) { 157 Objects.requireNonNull(naDate, "Caught null notAfter date"); 158 notAfter = (Date)naDate.clone(); 159 } 160 161 /** 162 * Set the validity period for the certificate 163 * 164 * @param nbDate A {@link Date} object specifying the start of the 165 * certificate validity period. 166 * @param naDate A {@link Date} object specifying the end of the 167 * certificate validity period. 168 */ setValidity(Date nbDate, Date naDate)169 public void setValidity(Date nbDate, Date naDate) { 170 setNotBefore(nbDate); 171 setNotAfter(naDate); 172 } 173 174 /** 175 * Set the serial number on the certificate. 176 * 177 * @param serial A serial number in {@link BigInteger} form. 178 */ setSerialNumber(BigInteger serial)179 public void setSerialNumber(BigInteger serial) { 180 Objects.requireNonNull(serial, "Caught null serial number"); 181 serialNumber = serial; 182 } 183 184 185 /** 186 * Add a single extension to the certificate. 187 * 188 * @param ext The extension to be added. 189 */ addExtension(Extension ext)190 public void addExtension(Extension ext) { 191 Objects.requireNonNull(ext, "Caught null extension"); 192 extensions.put(ext.getId(), ext); 193 } 194 195 /** 196 * Add multiple extensions contained in a {@code List}. 197 * 198 * @param extList The {@link List} of extensions to be added to 199 * the certificate. 200 */ addExtensions(List<Extension> extList)201 public void addExtensions(List<Extension> extList) { 202 Objects.requireNonNull(extList, "Caught null extension list"); 203 for (Extension ext : extList) { 204 extensions.put(ext.getId(), ext); 205 } 206 } 207 208 /** 209 * Helper method to add DNSName types for the SAN extension 210 * 211 * @param dnsNames A {@code List} of names to add as DNSName types 212 * 213 * @throws IOException if an encoding error occurs. 214 */ addSubjectAltNameDNSExt(List<String> dnsNames)215 public void addSubjectAltNameDNSExt(List<String> dnsNames) throws IOException { 216 if (!dnsNames.isEmpty()) { 217 GeneralNames gNames = new GeneralNames(); 218 for (String name : dnsNames) { 219 gNames.add(new GeneralName(new DNSName(name))); 220 } 221 addExtension(new SubjectAlternativeNameExtension(false, 222 gNames)); 223 } 224 } 225 226 /** 227 * Helper method to add one or more OCSP URIs to the Authority Info Access 228 * certificate extension. 229 * 230 * @param locations A list of one or more OCSP responder URIs as strings 231 * 232 * @throws IOException if an encoding error occurs. 233 */ addAIAExt(List<String> locations)234 public void addAIAExt(List<String> locations) 235 throws IOException { 236 if (!locations.isEmpty()) { 237 List<AccessDescription> acDescList = new ArrayList<>(); 238 for (String ocspUri : locations) { 239 acDescList.add(new AccessDescription( 240 AccessDescription.Ad_OCSP_Id, 241 new GeneralName(new URIName(ocspUri)))); 242 } 243 addExtension(new AuthorityInfoAccessExtension(acDescList)); 244 } 245 } 246 247 /** 248 * Set a Key Usage extension for the certificate. The extension will 249 * be marked critical. 250 * 251 * @param bitSettings Boolean array for all nine bit settings in the order 252 * documented in RFC 5280 section 4.2.1.3. 253 * 254 * @throws IOException if an encoding error occurs. 255 */ addKeyUsageExt(boolean[] bitSettings)256 public void addKeyUsageExt(boolean[] bitSettings) throws IOException { 257 addExtension(new KeyUsageExtension(bitSettings)); 258 } 259 260 /** 261 * Set the Basic Constraints Extension for a certificate. 262 * 263 * @param crit {@code true} if critical, {@code false} otherwise 264 * @param isCA {@code true} if the extension will be on a CA certificate, 265 * {@code false} otherwise 266 * @param maxPathLen The maximum path length issued by this CA. Values 267 * less than zero will omit this field from the resulting extension and 268 * no path length constraint will be asserted. 269 * 270 * @throws IOException if an encoding error occurs. 271 */ addBasicConstraintsExt(boolean crit, boolean isCA, int maxPathLen)272 public void addBasicConstraintsExt(boolean crit, boolean isCA, 273 int maxPathLen) throws IOException { 274 addExtension(new BasicConstraintsExtension(crit, isCA, maxPathLen)); 275 } 276 277 /** 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 */ addAuthorityKeyIdExt(X509Certificate authorityCert)284 public void addAuthorityKeyIdExt(X509Certificate authorityCert) 285 throws IOException { 286 addAuthorityKeyIdExt(authorityCert.getPublicKey()); 287 } 288 289 /** 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 */ addAuthorityKeyIdExt(PublicKey authorityKey)296 public void addAuthorityKeyIdExt(PublicKey authorityKey) throws IOException { 297 KeyIdentifier kid = new KeyIdentifier(authorityKey); 298 addExtension(new AuthorityKeyIdentifierExtension(kid, null, null)); 299 } 300 301 /** 302 * Add the Subject Key Identifier extension. 303 * 304 * @param subjectKey The public key to be used in the resulting certificate 305 * 306 * @throws IOException if an encoding error occurs. 307 */ addSubjectKeyIdExt(PublicKey subjectKey)308 public void addSubjectKeyIdExt(PublicKey subjectKey) throws IOException { 309 byte[] keyIdBytes = new KeyIdentifier(subjectKey).getIdentifier(); 310 addExtension(new SubjectKeyIdentifierExtension(keyIdBytes)); 311 } 312 313 /** 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 */ addExtendedKeyUsageExt(List<String> ekuOids)320 public void addExtendedKeyUsageExt(List<String> ekuOids) 321 throws IOException { 322 if (!ekuOids.isEmpty()) { 323 Vector<ObjectIdentifier> oidVector = new Vector<>(); 324 for (String oid : ekuOids) { 325 oidVector.add(new ObjectIdentifier(oid)); 326 } 327 addExtension(new ExtendedKeyUsageExtension(oidVector)); 328 } 329 } 330 331 /** 332 * Clear all settings and return the {@code CertificateBuilder} to 333 * its default state. 334 */ reset()335 public void reset() { 336 extensions.clear(); 337 subjectName = null; 338 notBefore = null; 339 notAfter = null; 340 serialNumber = null; 341 publicKey = null; 342 signatureBytes = null; 343 tbsCertBytes = null; 344 } 345 346 /** 347 * Build the certificate. 348 * 349 * @param issuerCert The certificate of the issuing authority, or 350 * {@code null} if the resulting certificate is self-signed. 351 * @param issuerKey The private key of the issuing authority 352 * @param algName The signature algorithm name 353 * 354 * @return The resulting {@link X509Certificate} 355 * 356 * @throws IOException if an encoding error occurs. 357 * @throws CertificateException If the certificate cannot be generated 358 * by the underlying {@link CertificateFactory} 359 * @throws NoSuchAlgorithmException If an invalid signature algorithm 360 * is provided. 361 */ build(X509Certificate issuerCert, PrivateKey issuerKey, String algName)362 public X509Certificate build(X509Certificate issuerCert, 363 PrivateKey issuerKey, String algName) 364 throws IOException, CertificateException, NoSuchAlgorithmException { 365 // TODO: add some basic checks (key usage, basic constraints maybe) 366 367 AlgorithmId signAlg = AlgorithmId.get(algName); 368 byte[] encodedCert = encodeTopLevel(issuerCert, issuerKey, signAlg); 369 ByteArrayInputStream bais = new ByteArrayInputStream(encodedCert); 370 return (X509Certificate)factory.generateCertificate(bais); 371 } 372 373 /** 374 * Encode the contents of the outer-most ASN.1 SEQUENCE: 375 * 376 * <PRE> 377 * Certificate ::= SEQUENCE { 378 * tbsCertificate TBSCertificate, 379 * signatureAlgorithm AlgorithmIdentifier, 380 * signatureValue BIT STRING } 381 * </PRE> 382 * 383 * @param issuerCert The certificate of the issuing authority, or 384 * {@code null} if the resulting certificate is self-signed. 385 * @param issuerKey The private key of the issuing authority 386 * @param signAlg The signature algorithm object 387 * 388 * @return The DER-encoded X.509 certificate 389 * 390 * @throws CertificateException If an error occurs during the 391 * signing process. 392 * @throws IOException if an encoding error occurs. 393 */ encodeTopLevel(X509Certificate issuerCert, PrivateKey issuerKey, AlgorithmId signAlg)394 private byte[] encodeTopLevel(X509Certificate issuerCert, 395 PrivateKey issuerKey, AlgorithmId signAlg) 396 throws CertificateException, IOException { 397 DerOutputStream outerSeq = new DerOutputStream(); 398 DerOutputStream topLevelItems = new DerOutputStream(); 399 400 tbsCertBytes = encodeTbsCert(issuerCert, signAlg); 401 topLevelItems.write(tbsCertBytes); 402 try { 403 signatureBytes = signCert(issuerKey, signAlg); 404 } catch (GeneralSecurityException ge) { 405 throw new CertificateException(ge); 406 } 407 signAlg.derEncode(topLevelItems); 408 topLevelItems.putBitString(signatureBytes); 409 outerSeq.write(DerValue.tag_Sequence, topLevelItems); 410 411 return outerSeq.toByteArray(); 412 } 413 414 /** 415 * Encode the bytes for the TBSCertificate structure: 416 * <PRE> 417 * TBSCertificate ::= SEQUENCE { 418 * version [0] EXPLICIT Version DEFAULT v1, 419 * serialNumber CertificateSerialNumber, 420 * signature AlgorithmIdentifier, 421 * issuer Name, 422 * validity Validity, 423 * subject Name, 424 * subjectPublicKeyInfo SubjectPublicKeyInfo, 425 * issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, 426 * -- If present, version MUST be v2 or v3 427 * subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, 428 * -- If present, version MUST be v2 or v3 429 * extensions [3] EXPLICIT Extensions OPTIONAL 430 * -- If present, version MUST be v3 431 * } 432 * 433 * @param issuerCert The certificate of the issuing authority, or 434 * {@code null} if the resulting certificate is self-signed. 435 * @param signAlg The signature algorithm object 436 * 437 * @return The DER-encoded bytes for the TBSCertificate structure 438 * 439 * @throws IOException if an encoding error occurs. 440 */ encodeTbsCert(X509Certificate issuerCert, AlgorithmId signAlg)441 private byte[] encodeTbsCert(X509Certificate issuerCert, 442 AlgorithmId signAlg) throws IOException { 443 DerOutputStream tbsCertSeq = new DerOutputStream(); 444 DerOutputStream tbsCertItems = new DerOutputStream(); 445 446 // Hardcode to V3 447 byte[] v3int = {0x02, 0x01, 0x02}; 448 tbsCertItems.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, 449 (byte)0), v3int); 450 451 // Serial Number 452 SerialNumber sn = new SerialNumber(serialNumber); 453 sn.encode(tbsCertItems); 454 455 // Algorithm ID 456 signAlg.derEncode(tbsCertItems); 457 458 // Issuer Name 459 if (issuerCert != null) { 460 tbsCertItems.write( 461 issuerCert.getSubjectX500Principal().getEncoded()); 462 } else { 463 // Self-signed 464 tbsCertItems.write(subjectName.getEncoded()); 465 } 466 467 // Validity period (set as UTCTime) 468 DerOutputStream valSeq = new DerOutputStream(); 469 valSeq.putUTCTime(notBefore); 470 valSeq.putUTCTime(notAfter); 471 tbsCertItems.write(DerValue.tag_Sequence, valSeq); 472 473 // Subject Name 474 tbsCertItems.write(subjectName.getEncoded()); 475 476 // SubjectPublicKeyInfo 477 tbsCertItems.write(publicKey.getEncoded()); 478 479 // TODO: Extensions! 480 encodeExtensions(tbsCertItems); 481 482 // Wrap it all up in a SEQUENCE and return the bytes 483 tbsCertSeq.write(DerValue.tag_Sequence, tbsCertItems); 484 return tbsCertSeq.toByteArray(); 485 } 486 487 /** 488 * Encode the extensions segment for an X.509 Certificate: 489 * 490 * <PRE> 491 * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension 492 * 493 * Extension ::= SEQUENCE { 494 * extnID OBJECT IDENTIFIER, 495 * critical BOOLEAN DEFAULT FALSE, 496 * extnValue OCTET STRING 497 * -- contains the DER encoding of an ASN.1 value 498 * -- corresponding to the extension type identified 499 * -- by extnID 500 * } 501 * </PRE> 502 * 503 * @param tbsStream The {@code DerOutputStream} that holds the 504 * TBSCertificate contents. 505 * 506 * @throws IOException if an encoding error occurs. 507 */ encodeExtensions(DerOutputStream tbsStream)508 private void encodeExtensions(DerOutputStream tbsStream) 509 throws IOException { 510 DerOutputStream extSequence = new DerOutputStream(); 511 DerOutputStream extItems = new DerOutputStream(); 512 513 for (Extension ext : extensions.values()) { 514 ext.encode(extItems); 515 } 516 extSequence.write(DerValue.tag_Sequence, extItems); 517 tbsStream.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, 518 (byte)3), extSequence); 519 } 520 521 /** 522 * Digitally sign the X.509 certificate. 523 * 524 * @param issuerKey The private key of the issuing authority 525 * @param signAlg The signature algorithm object 526 * 527 * @return The digital signature bytes. 528 * 529 * @throws GeneralSecurityException If any errors occur during the 530 * digital signature process. 531 */ signCert(PrivateKey issuerKey, AlgorithmId signAlg)532 private byte[] signCert(PrivateKey issuerKey, AlgorithmId signAlg) 533 throws GeneralSecurityException { 534 Signature sig = Signature.getInstance(signAlg.getName()); 535 sig.initSign(issuerKey); 536 sig.update(tbsCertBytes); 537 return sig.sign(); 538 } 539 } 540