package org.browsermob.proxy.selenium; import org.bouncycastle.asn1.DEREncodableVector; import org.bouncycastle.asn1.DERObjectIdentifier; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.jce.X509Principal; import org.bouncycastle.x509.X509V3CertificateGenerator; import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure; import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure; import javax.security.auth.x500.X500Principal; import java.math.BigInteger; import java.security.*; import java.security.cert.*; import java.util.*; /** * Methods for creating certificates. * * *************************************************************************************** * Copyright (c) 2007, Information Security Partners, LLC * All rights reserved. * * In a special exception, Selenium/OpenQA is allowed to use this code under the Apache License 2.0. * * @author Brad Hill * */ public class CertificateCreator { private static final HashSet<String> clientCertOidsNeverToCopy = new HashSet<String>(); private static final HashSet<String> clientCertDefaultOidsNotToCopy = new HashSet<String>(); /** * The default key generation algorithm for this package is RSA. */ public static final String KEYGEN_ALGO = "RSA"; /** * The default sign algorithm for this package is SHA1 with RSA. */ public static final String SIGN_ALGO = "SHA1withRSA"; /** * X.509 OID for Subject Key Identifier Extension - Replaced when duplicating a cert. */ public static final String OID_SUBJECT_KEY_IDENTIFIER = "2.5.29.14"; /** * X.509 OID for Subject Authority Key Identifier - Replaced when duplicating a cert. */ public static final String OID_AUTHORITY_KEY_IDENTIFIER = "2.5.29.35"; /** * X.509 OID for Issuer Alternative Name - Omitted when duplicating a cert by default. */ public static final String OID_ISSUER_ALTERNATIVE_NAME = "2.5.29.8"; /** * X.509 OID for Issuer Alternative Name 2 - Omitted when duplicating a cert by default. */ public static final String OID_ISSUER_ALTERNATIVE_NAME_2 = "2.5.29.18"; /** * X.509 OID for Certificate Revocation List Distribution Point - Omitted when duplicating a cert by default. */ public static final String OID_CRL_DISTRIBUTION_POINT = "2.5.28.31"; /** * X.509 OID for Authority Information Access - Omitted when duplicating a cert by default. */ public static final String OID_AUTHORITY_INFO_ACCESS = "1.3.6.1.5.5.7.1.1"; /** * X.509 OID for Additional CA Issuers for AIA - Omitted when duplicating a cert by default. */ public static final String OID_ID_AD_CAISSUERS = "1.3.6.1.5.5.7.48.2"; static { clientCertOidsNeverToCopy.add(OID_SUBJECT_KEY_IDENTIFIER); clientCertOidsNeverToCopy.add(OID_AUTHORITY_KEY_IDENTIFIER); clientCertDefaultOidsNotToCopy.add(OID_ISSUER_ALTERNATIVE_NAME); clientCertDefaultOidsNotToCopy.add(OID_ISSUER_ALTERNATIVE_NAME_2); clientCertDefaultOidsNotToCopy.add(OID_CRL_DISTRIBUTION_POINT); clientCertDefaultOidsNotToCopy.add(OID_AUTHORITY_INFO_ACCESS); } /** * Utility method for generating a "standard" server certificate. Recognized by most * browsers as valid for SSL/TLS. These certificates are generated de novo, not from * a template, so they will not retain the structure of the original certificate and may * not be suitable for applications that require Extended Validation/High Assurance SSL * or other distinct extensions or EKU. * * @param newPubKey * @param caCert * @param caPrivateKey * @param hostname * @return * @throws CertificateParsingException * @throws SignatureException * @throws InvalidKeyException * @throws CertificateExpiredException * @throws CertificateNotYetValidException * @throws CertificateException * @throws NoSuchAlgorithmException * @throws NoSuchProviderException */ @SuppressWarnings({ "deprecation", "unused" }) public static X509Certificate generateStdSSLServerCertificate( final PublicKey newPubKey, final X509Certificate caCert, final PrivateKey caPrivateKey, final String subject) throws CertificateParsingException, SignatureException, InvalidKeyException, CertificateExpiredException, CertificateNotYetValidException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException { X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator(); v3CertGen.setSubjectDN(new X500Principal(subject)); v3CertGen.setSignatureAlgorithm(CertificateCreator.SIGN_ALGO); v3CertGen.setPublicKey(newPubKey); v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + 30L * 60 * 60 * 24 * 30 * 12)); v3CertGen.setNotBefore(new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30 *12)); v3CertGen.setIssuerDN(caCert.getSubjectX500Principal()); // Firefox actually tracks serial numbers within a CA and refuses to validate if it sees duplicates // This is not a secure serial number generator, (duh!) but it's good enough for our purposes. v3CertGen.setSerialNumber(new BigInteger(Long.toString(System.currentTimeMillis()))); v3CertGen.addExtension( X509Extensions.BasicConstraints, true, new BasicConstraints(false) ); v3CertGen.addExtension( X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(newPubKey)); v3CertGen.addExtension( X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(caCert.getPublicKey())); // Firefox 2 disallows these extensions in an SSL server cert. IE7 doesn't care. // v3CertGen.addExtension( // X509Extensions.KeyUsage, // false, // new KeyUsage(KeyUsage.dataEncipherment | KeyUsage.digitalSignature ) ); DEREncodableVector typicalSSLServerExtendedKeyUsages = new DEREncodableVector(); typicalSSLServerExtendedKeyUsages.add(new DERObjectIdentifier(ExtendedKeyUsageConstants.serverAuth)); typicalSSLServerExtendedKeyUsages.add(new DERObjectIdentifier(ExtendedKeyUsageConstants.clientAuth)); typicalSSLServerExtendedKeyUsages.add(new DERObjectIdentifier(ExtendedKeyUsageConstants.netscapeServerGatedCrypto)); typicalSSLServerExtendedKeyUsages.add(new DERObjectIdentifier(ExtendedKeyUsageConstants.msServerGatedCrypto)); v3CertGen.addExtension( X509Extensions.ExtendedKeyUsage, false, new DERSequence(typicalSSLServerExtendedKeyUsages)); // Disabled by default. Left in comments in case this is desired. // // v3CertGen.addExtension( // X509Extensions.AuthorityInfoAccess, // false, // new AuthorityInformationAccess(new DERObjectIdentifier(OID_ID_AD_CAISSUERS), // new GeneralName(GeneralName.uniformResourceIdentifier, "http://" + subject + "/aia"))); // v3CertGen.addExtension( // X509Extensions.CRLDistributionPoints, // false, // new CRLDistPoint(new DistributionPoint[] {})); X509Certificate cert = v3CertGen.generate(caPrivateKey, "BC"); return cert; } /** * This method creates an X509v3 certificate based on an an existing certificate. * It attempts to create as faithful a copy of the existing certificate as possible * by duplicating all certificate extensions. * * If you are testing an application that makes use of additional certificate * extensions (e.g. logotype, S/MIME capabilities) this method will preserve those * fields. * * You may optionally include a set of OIDs not to copy from the original certificate. * The most common reason to do this would be to remove fields that would cause inconsistency, * such as Authority Info Access or Issuer Alternative Name where these are not defined for * the MITM authority certificate. * * OIDs 2.5.29.14 : Subject Key Identifier and 2.5.29.35 : Authority Key Identifier, * are never copied, but generated directly based on the input keys and certificates. * * You may also optionally include maps of custom extensions which will be added to or replace * extensions with the same OID on the original certificate for the the MITM certificate. * * FUTURE WORK: JDK 1.5 is very strict in parsing extensions. In particular, known extensions * that include URIs must parse to valid URIs (including URL encoding all non-valid URI characters) * or the extension will be rejected and not available to copy to the MITM certificate. Will need * to directly extract these as ASN.1 fields and re-insert (hopefully BouncyCastle will handle them) * * * @param originalCert The original certificate to duplicate. * @param newPubKey The new public key for the MITM certificate. * @param caCert The certificate of the signing authority fot the MITM certificate. * @param caPrivateKey The private key of the signing authority. * @param extensionOidsNotToCopy An optional list of certificate extension OIDs not to copy to the MITM certificate. * @return The new MITM certificate. * @throws CertificateParsingException * @throws SignatureException * @throws InvalidKeyException * @throws CertificateExpiredException * @throws CertificateNotYetValidException * @throws CertificateException * @throws NoSuchAlgorithmException * @throws NoSuchProviderException */ public static X509Certificate mitmDuplicateCertificate(final X509Certificate originalCert, final PublicKey newPubKey, final X509Certificate caCert, final PrivateKey caPrivateKey, Set<String> extensionOidsNotToCopy) throws CertificateParsingException, SignatureException, InvalidKeyException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException { if(extensionOidsNotToCopy == null) { extensionOidsNotToCopy = new HashSet<String>(); } X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator(); v3CertGen.setSubjectDN(originalCert.getSubjectX500Principal()); v3CertGen.setSignatureAlgorithm(CertificateCreator.SIGN_ALGO); // needs to be the same as the signing cert, not the copied cert v3CertGen.setPublicKey(newPubKey); v3CertGen.setNotAfter(originalCert.getNotAfter()); v3CertGen.setNotBefore(originalCert.getNotBefore()); v3CertGen.setIssuerDN(caCert.getSubjectX500Principal()); v3CertGen.setSerialNumber(originalCert.getSerialNumber()); // copy other extensions: Set<String> critExts = originalCert.getCriticalExtensionOIDs(); // get extensions returns null, not an empty set! if(critExts != null) { for (String oid : critExts) { if(!clientCertOidsNeverToCopy.contains(oid) && !extensionOidsNotToCopy.contains(oid)) { v3CertGen.copyAndAddExtension(new DERObjectIdentifier(oid), true, originalCert); } } } Set<String> nonCritExs = originalCert.getNonCriticalExtensionOIDs(); if(nonCritExs != null) { for(String oid: nonCritExs) { if(!clientCertOidsNeverToCopy.contains(oid) && !extensionOidsNotToCopy.contains(oid)){ v3CertGen.copyAndAddExtension(new DERObjectIdentifier(oid), false, originalCert); } } } v3CertGen.addExtension( X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(newPubKey)); v3CertGen.addExtension( X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(caCert.getPublicKey())); X509Certificate cert = v3CertGen.generate(caPrivateKey, "BC"); // For debugging purposes. //cert.checkValidity(new Date()); //cert.verify(caCert.getPublicKey()); return cert; } /** * Convenience method for the most common case of certificate duplication. * * This method will not add any custom extensions and won't copy the extensions 2.5.29.8 : Issuer Alternative Name, * 2.5.29.18 : Issuer Alternative Name 2, 2.5.29.31 : CRL Distribution Point or 1.3.6.1.5.5.7.1.1 : Authority Info Access, if they are present. * * @param originalCert * @param newPubKey * @param caCert * @param caPrivateKey * @return * @throws CertificateParsingException * @throws SignatureException * @throws InvalidKeyException * @throws CertificateExpiredException * @throws CertificateNotYetValidException * @throws CertificateException * @throws NoSuchAlgorithmException * @throws NoSuchProviderException */ public static X509Certificate mitmDuplicateCertificate(final X509Certificate originalCert, final PublicKey newPubKey, final X509Certificate caCert, final PrivateKey caPrivateKey) throws CertificateParsingException, SignatureException, InvalidKeyException, CertificateExpiredException, CertificateNotYetValidException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException { return mitmDuplicateCertificate(originalCert, newPubKey, caCert, caPrivateKey, clientCertDefaultOidsNotToCopy); } /** * Creates a typical Certification Authority (CA) certificate. * @param keyPair * @throws SecurityException * @throws InvalidKeyException * @throws NoSuchProviderException * @throws NoSuchAlgorithmException * @throws CertificateException */ @SuppressWarnings("deprecation") public static X509Certificate createTypicalMasterCert(final KeyPair keyPair) throws SignatureException, InvalidKeyException, SecurityException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException { X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator(); X509Principal issuer=new X509Principal("O=CyberVillians.com,OU=CyberVillians Certification Authority,C=US"); // Create v3CertGen.setSerialNumber(BigInteger.valueOf(1)); v3CertGen.setIssuerDN(issuer); v3CertGen.setSubjectDN(issuer); //Set validity period v3CertGen.setNotBefore(new Date(System.currentTimeMillis() - 12 /* months */ *(1000L * 60 * 60 * 24 * 30))); v3CertGen.setNotAfter (new Date(System.currentTimeMillis() + 240 /* months */ *(1000L * 60 * 60 * 24 * 30))); //Set signature algorithm & public key v3CertGen.setPublicKey(keyPair.getPublic()); v3CertGen.setSignatureAlgorithm(CertificateCreator.SIGN_ALGO); // Add typical extensions for signing cert v3CertGen.addExtension( X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(keyPair.getPublic())); v3CertGen.addExtension( X509Extensions.BasicConstraints, true, new BasicConstraints(0)); v3CertGen.addExtension( X509Extensions.KeyUsage, false, new KeyUsage(KeyUsage.cRLSign | KeyUsage.keyCertSign) ); DEREncodableVector typicalCAExtendedKeyUsages = new DEREncodableVector(); typicalCAExtendedKeyUsages.add(new DERObjectIdentifier(ExtendedKeyUsageConstants.serverAuth)); typicalCAExtendedKeyUsages.add(new DERObjectIdentifier(ExtendedKeyUsageConstants.OCSPSigning)); typicalCAExtendedKeyUsages.add(new DERObjectIdentifier(ExtendedKeyUsageConstants.verisignUnknown)); v3CertGen.addExtension( X509Extensions.ExtendedKeyUsage, false, new DERSequence(typicalCAExtendedKeyUsages)); X509Certificate cert = v3CertGen.generate(keyPair.getPrivate(), "BC"); cert.checkValidity(new Date()); cert.verify(keyPair.getPublic()); return cert; } }