Java tutorial
package cybervillains.ca; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jcajce.provider.config.ConfigurableProvider; import org.openqa.selenium.security.CertificateGenerator; import org.openqa.selenium.security.KeyAndCert; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; import java.security.SignatureException; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.security.spec.DSAParameterSpec; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.crypto.spec.DHParameterSpec; /** * This is the main entry point into the Cybervillains CA. * * This class handles generation, storage and the persistent mapping of input to duplicated * certificates and mapped public keys. * * Default setting is to immediately persist changes to the store by writing out the keystore and * mapping file every time a new certificate is added. This behavior can be disabled if desired, to * enhance performance or allow temporary testing without modifying the certificate store. * *************************************************************************************** * 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 KeyStoreManager { static Logger log = Logger.getLogger(KeyStoreManager.class.getName()); private final String CERTMAP_SER_FILE = "certmap.ser"; private final String SUBJMAP_SER_FILE = "subjmap.ser"; @SuppressWarnings("FieldCanBeLocal") private final String EXPORTED_CERT_NAME = "cybervillainsCA.cer"; private final char[] _keypassword = "password".toCharArray(); private final char[] _keystorepass = "password".toCharArray(); private final String _caPrivateKeystore = "cybervillainsCA.jks"; private final String _caCertAlias = "signingCert"; public static final String _caPrivKeyAlias = "signingCertPrivKey"; X509Certificate _caCert; PrivateKey _caPrivKey; KeyStore _ks; private HashMap<PublicKey, PrivateKey> _rememberedPrivateKeys; private HashMap<PublicKey, PublicKey> _mappedPublicKeys; private HashMap<String, String> _certMap; private HashMap<String, String> _subjectMap; private final String KEYMAP_SER_FILE = "keymap.ser"; private final String PUB_KEYMAP_SER_FILE = "pubkeymap.ser"; public final String RSA_KEYGEN_ALGO = "RSA"; public final String DSA_KEYGEN_ALGO = "DSA"; public final KeyPairGenerator _rsaKpg; public final KeyPairGenerator _dsaKpg; private boolean persistImmediately = true; private File root; private final String certificateRevocationList; @SuppressWarnings("unchecked") public KeyStoreManager(File root, String certificateRevocationList) { this.root = root; this.certificateRevocationList = certificateRevocationList; ConfigurableProvider bcProv = new BouncyCastleProvider(); DHParameterSpec dhSpec = new DHParameterSpec( new BigInteger("f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f95" + "74c0b3d0782675159578ebad4594fe67107108180b449167123e84c28161" + "3b7cf09328cc8a6e13c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bf" + "a213562f1fb627a01243bcca4f1bea8519089a883dfe15ae59f06928b665" + "e807b552564014c3bfecf492a", 16), new BigInteger("fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31" + "e3f80b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813" + "b801d346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf" + "83b57e7c6a8a6150f04fb83f6d3c51ec3023554135a169132f675f3ae2b6" + "1d72aeff22203199dd14801c7", 16), 512); bcProv.setParameter(ConfigurableProvider.DH_DEFAULT_PARAMS, dhSpec); Security.insertProviderAt((Provider) bcProv, 2); SecureRandom _sr = new SecureRandom(); try { _rsaKpg = KeyPairGenerator.getInstance(RSA_KEYGEN_ALGO); _dsaKpg = KeyPairGenerator.getInstance(DSA_KEYGEN_ALGO); } catch (Throwable t) { throw new Error(t); } try { File privKeys = new File(root, KEYMAP_SER_FILE); if (!privKeys.exists()) { _rememberedPrivateKeys = new HashMap<PublicKey, PrivateKey>(); } else { ObjectInputStream in = new ObjectInputStream(new FileInputStream(privKeys)); // Deserialize the object _rememberedPrivateKeys = (HashMap<PublicKey, PrivateKey>) in.readObject(); in.close(); } File pubKeys = new File(root, PUB_KEYMAP_SER_FILE); if (!pubKeys.exists()) { _mappedPublicKeys = new HashMap<PublicKey, PublicKey>(); } else { ObjectInputStream in = new ObjectInputStream(new FileInputStream(pubKeys)); // Deserialize the object _mappedPublicKeys = (HashMap<PublicKey, PublicKey>) in.readObject(); in.close(); } } catch (FileNotFoundException e) { // check for file exists, won't happen. e.printStackTrace(); } catch (IOException e) { // we could correct, but this probably indicates a corruption // of the serialized file that we want to know about; likely // synchronization problems during serialization. e.printStackTrace(); throw new Error(e); } catch (ClassNotFoundException e) { // serious problem. e.printStackTrace(); throw new Error(e); } BigInteger p = new BigInteger("fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669" + "455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b7" + "6b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb" + "83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7", 16); BigInteger q = new BigInteger("9760508f15230bccb292b982a2eb840bf0581cf5", 16); BigInteger g = new BigInteger("f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b3d078267" + "5159578ebad4594fe67107108180b449167123e84c281613b7cf09328cc8a6e1" + "3c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a01243b" + "cca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a", 16); DSAParameterSpec dsaParameterSpec = new DSAParameterSpec(p, q, g); _rsaKpg.initialize(1024, _sr); try { _dsaKpg.initialize(dsaParameterSpec, _sr); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); _dsaKpg.initialize(1024, _sr); } try { _ks = KeyStore.getInstance("JKS"); reloadKeystore(); } catch (FileNotFoundException fnfe) { try { createKeystore(); } catch (Exception e) { throw new Error(e); } } catch (Exception e) { throw new Error(e); } try { File file = new File(root, CERTMAP_SER_FILE); if (!file.exists()) { _certMap = new HashMap<String, String>(); } else { ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); // Deserialize the object _certMap = (HashMap<String, String>) in.readObject(); in.close(); } } catch (FileNotFoundException e) { // won't happen, check file.exists() e.printStackTrace(); } catch (IOException e) { // corrupted file, we want to know. e.printStackTrace(); throw new Error(e); } catch (ClassNotFoundException e) { // something very wrong, exit e.printStackTrace(); throw new Error(e); } try { File file = new File(root, SUBJMAP_SER_FILE); if (!file.exists()) { _subjectMap = new HashMap<String, String>(); } else { ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); // Deserialize the object _subjectMap = (HashMap<String, String>) in.readObject(); in.close(); } } catch (FileNotFoundException e) { // won't happen, check file.exists() e.printStackTrace(); } catch (IOException e) { // corrupted file, we want to know. e.printStackTrace(); throw new Error(e); } catch (ClassNotFoundException e) { // something very wrong, exit e.printStackTrace(); throw new Error(e); } } private void reloadKeystore() throws IOException, NoSuchAlgorithmException, CertificateException, KeyStoreException, UnrecoverableKeyException { InputStream is = new FileInputStream(new File(root, _caPrivateKeystore)); _ks.load(is, _keystorepass); _caCert = (X509Certificate) _ks.getCertificate(_caCertAlias); _caPrivKey = (PrivateKey) _ks.getKey(_caPrivKeyAlias, _keypassword); } /** * Creates, writes and loads a new keystore and CA root certificate. */ protected void createKeystore() { Certificate signingCert; PrivateKey caPrivKey; if (_caCert == null || _caPrivKey == null) { try { log.fine("Keystore or signing cert & keypair not found. Generating..."); KeyPair caKeypair = getRSAKeyPair(); caPrivKey = caKeypair.getPrivate(); signingCert = CertificateCreator.createTypicalMasterCert(caKeypair); log.fine("Done generating signing cert"); log.fine(String.valueOf(signingCert)); _ks.load(null, _keystorepass); _ks.setCertificateEntry(_caCertAlias, signingCert); _ks.setKeyEntry(_caPrivKeyAlias, caPrivKey, _keypassword, new Certificate[] { signingCert }); File caKsFile = new File(root, _caPrivateKeystore); OutputStream os = new FileOutputStream(caKsFile); _ks.store(os, _keystorepass); log.fine("Wrote JKS keystore to: " + caKsFile.getAbsolutePath()); // also export a .cer that can be imported as a trusted root // to disable all warning dialogs for interception File signingCertFile = new File(root, EXPORTED_CERT_NAME); FileOutputStream cerOut = new FileOutputStream(signingCertFile); byte[] buf = signingCert.getEncoded(); log.fine("Wrote signing cert to: " + signingCertFile.getAbsolutePath()); cerOut.write(buf); cerOut.flush(); cerOut.close(); _caCert = (X509Certificate) signingCert; _caPrivKey = caPrivKey; } catch (Exception e) { log.log(Level.SEVERE, "Fatal error creating/storing keystore or signing cert.", e); throw new Error(e); } } else { log.fine("Successfully loaded keystore."); log.fine(String.valueOf(_caCert)); } } /** * Stores a new certificate and its associated private key in the keystore. * * @throws KeyStoreException * @throws CertificateException * @throws NoSuchAlgorithmException */ public synchronized void addCertAndPrivateKey(String hostname, final X509Certificate cert, final PrivateKey privKey) throws KeyStoreException, CertificateException, NoSuchAlgorithmException { // String alias = ThumbprintUtil.getThumbprint(cert); _ks.deleteEntry(hostname); _ks.setCertificateEntry(hostname, cert); _ks.setKeyEntry(hostname, privKey, _keypassword, new Certificate[] { cert }); if (persistImmediately) { persist(); } } /** * Writes the keystore and certificate/keypair mappings to disk. * * @throws KeyStoreException * @throws NoSuchAlgorithmException * @throws CertificateException */ public synchronized void persist() throws KeyStoreException, NoSuchAlgorithmException, CertificateException { try { FileOutputStream kso = new FileOutputStream(new File(root, _caPrivateKeystore)); _ks.store(kso, _keystorepass); kso.flush(); kso.close(); persistCertMap(); persistSubjectMap(); persistKeyPairMap(); persistPublicKeyMap(); } catch (IOException ioe) { ioe.printStackTrace(); } } /** * Returns the aliased certificate. Certificates are aliased by their SHA1 digest. * * @see ThumbprintUtil * @throws KeyStoreException */ public synchronized X509Certificate getCertificateByAlias(final String alias) throws KeyStoreException { return (X509Certificate) _ks.getCertificate(alias); } /** * Returns the aliased certificate. Certificates are aliased by their hostname. * * @see ThumbprintUtil * @throws KeyStoreException * @throws UnrecoverableKeyException * @throws NoSuchProviderException * @throws NoSuchAlgorithmException * @throws CertificateException * @throws SignatureException * @throws CertificateNotYetValidException * @throws CertificateExpiredException * @throws InvalidKeyException * @throws CertificateParsingException */ public synchronized X509Certificate getCertificateByHostname(final String hostname) throws KeyStoreException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, UnrecoverableKeyException { String alias = _subjectMap.get(getSubjectForHostname(hostname)); if (alias != null) { return (X509Certificate) _ks.getCertificate(alias); } return getMappedCertificateForHostname(hostname); } /** * Gets the authority root signing cert. * * @throws KeyStoreException */ public synchronized X509Certificate getSigningCert() throws KeyStoreException { return _caCert; } /** * Gets the authority private signing key. * * @throws KeyStoreException * @throws NoSuchAlgorithmException * @throws UnrecoverableKeyException */ public synchronized PrivateKey getSigningPrivateKey() throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { return _caPrivKey; } /** * This method returns the mapped certificate for a hostname, or generates a "standard" SSL server * certificate issued by the CA to the supplied subject if no mapping has been created. This is * not a true duplication, just a shortcut method that is adequate for web browsers. * * @throws CertificateParsingException * @throws InvalidKeyException * @throws CertificateExpiredException * @throws CertificateNotYetValidException * @throws SignatureException * @throws CertificateException * @throws NoSuchAlgorithmException * @throws NoSuchProviderException * @throws KeyStoreException * @throws UnrecoverableKeyException */ public X509Certificate getMappedCertificateForHostname(String hostname) throws InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, KeyStoreException, UnrecoverableKeyException { String subject = getSubjectForHostname(hostname); String thumbprint = _subjectMap.get(subject); if (thumbprint == null) { KeyAndCert keyAndCert = new CertificateGenerator(root).generateCertificate(hostname, certificateRevocationList); X509Certificate newCert = keyAndCert.getCertificate(); addCertAndPrivateKey(hostname, newCert, keyAndCert.getPrivateKey()); thumbprint = ThumbprintUtil.getThumbprint(newCert); _subjectMap.put(subject, thumbprint); if (persistImmediately) { persist(); } return newCert; } return getCertificateByAlias(thumbprint); } private String getSubjectForHostname(String hostname) { return "CN=" + hostname + ", OU=Test, O=CyberVillainsCA, L=Seattle, S=Washington, C=US"; } private synchronized void persistCertMap() { try { ObjectOutput out = new ObjectOutputStream(new FileOutputStream(new File(root, CERTMAP_SER_FILE))); out.writeObject(_certMap); out.flush(); out.close(); } catch (FileNotFoundException e) { // writing, this shouldn't happen... e.printStackTrace(); } catch (IOException e) { // big problem! e.printStackTrace(); throw new Error(e); } } private synchronized void persistSubjectMap() { try { ObjectOutput out = new ObjectOutputStream(new FileOutputStream(new File(root, SUBJMAP_SER_FILE))); out.writeObject(_subjectMap); out.flush(); out.close(); } catch (FileNotFoundException e) { // writing, this shouldn't happen... e.printStackTrace(); } catch (IOException e) { // big problem! e.printStackTrace(); throw new Error(e); } } /** * Generate an RSA Key Pair */ public KeyPair getRSAKeyPair() { KeyPair kp = _rsaKpg.generateKeyPair(); rememberKeyPair(kp); return kp; } private synchronized void persistPublicKeyMap() { try { ObjectOutput out = new ObjectOutputStream(new FileOutputStream(new File(root, PUB_KEYMAP_SER_FILE))); out.writeObject(_mappedPublicKeys); out.flush(); out.close(); } catch (FileNotFoundException e) { // writing, won't happen e.printStackTrace(); } catch (IOException e) { // very bad e.printStackTrace(); throw new Error(e); } } private synchronized void persistKeyPairMap() { try { ObjectOutput out = new ObjectOutputStream(new FileOutputStream(new File(root, KEYMAP_SER_FILE))); out.writeObject(_rememberedPrivateKeys); out.flush(); out.close(); } catch (FileNotFoundException e) { // writing, won't happen. e.printStackTrace(); } catch (IOException e) { // very bad e.printStackTrace(); throw new Error(e); } } private synchronized void rememberKeyPair(final KeyPair kp) { _rememberedPrivateKeys.put(kp.getPublic(), kp.getPrivate()); if (persistImmediately) { persistKeyPairMap(); } } public KeyStore getKeyStore() { return _ks; } }