/* * This file is part of the OWASP Proxy, a free intercepting proxy library. * Copyright (C) 2008-2010 Rogan Dawes <rogan@dawes.za.net> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to: * The Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ package org.owasp.proxy.ssl; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.net.InetSocketAddress; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.X509KeyManager; import javax.security.auth.x500.X500Principal; import org.owasp.proxy.util.Base64; import org.owasp.proxy.util.BouncyCastleCertificateUtils; public class AutoGeneratingContextSelector implements SSLContextSelector { private static final long DEFAULT_VALIDITY = 10L * 365L * 24L * 60L * 60L * 1000L; private static Logger logger = Logger .getLogger(AutoGeneratingContextSelector.class.getName()); private boolean reuseKeys = false; private Map<String, SSLContext> contextCache = new HashMap<String, SSLContext>(); private PrivateKey caKey; private X509Certificate[] caCerts; private Set<BigInteger> serials = new HashSet<BigInteger>(); /** * creates a {@link AutoGeneratingContextSelector} that will create a RSA {@link KeyPair} and self-signed * {@link X509Certificate} based on the {@link X500Principal} supplied. The user can call * {@link #save(File, String, char[], char[], String)} to save the generated details at a later stage. * * @param ca * @throws GeneralSecurityException * @throws IOException */ public AutoGeneratingContextSelector(X500Principal ca) throws GeneralSecurityException, IOException { create(ca); } /** * creates a {@link AutoGeneratingContextSelector} that will use the supplied {@link PrivateKey} and * {@link X509Certificate} chain * * @param caKey * the CA key * @param caCerts * the certificate chain */ public AutoGeneratingContextSelector(PrivateKey caKey, X509Certificate[] caCerts) { this.caKey = caKey; this.caCerts = new X509Certificate[caCerts.length]; System.arraycopy(caCerts, 0, this.caCerts, 0, caCerts.length); } /** * creates a {@link AutoGeneratingContextSelector} that will load its CA {@link PrivateKey} and * {@link X509Certificate} chain from the indicated keystore * * @param keystore * the location of the keystore * @param type * the keystore type * @param password * the keystore password * @param keyPassword * the key password * @param caAlias * the alias of the key entry * @throws GeneralSecurityException * @throws IOException */ public AutoGeneratingContextSelector(File keystore, String type, char[] password, char[] keyPassword, String caAlias) throws GeneralSecurityException, IOException { initFromKeyStore(keystore, type, password, keyPassword, caAlias); } private void initFromKeyStore(File ks, String type, char[] kspassword, char[] keyPassword, String alias) throws GeneralSecurityException, IOException { InputStream in = new FileInputStream(ks); try { KeyStore keyStore = KeyStore.getInstance(type); keyStore.load(in, kspassword); caKey = (PrivateKey) keyStore.getKey(alias, keyPassword); Certificate[] certChain = keyStore.getCertificateChain(alias); caCerts = new X509Certificate[certChain.length]; System.arraycopy(certChain, 0, caCerts, 0, certChain.length); } catch (IOException ioe) { ioe.printStackTrace(); } finally { in.close(); } } private void create(X500Principal caName) throws GeneralSecurityException, IOException { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(2048, new SecureRandom()); KeyPair caPair = keyGen.generateKeyPair(); caKey = caPair.getPrivate(); PublicKey caPubKey = caPair.getPublic(); Date begin = new Date(); Date ends = new Date(begin.getTime() + DEFAULT_VALIDITY); X509Certificate cert = BouncyCastleCertificateUtils.sign(caName, caPubKey, caName, caPubKey, caKey, begin, ends, BigInteger.ONE); caCerts = new X509Certificate[] { cert }; } public String getCACert() throws CertificateEncodingException { try { return "-----BEGIN CERTIFICATE-----\n" + Base64.encodeBytes(caCerts[0].getEncoded(), Base64.DO_BREAK_LINES) + "\n-----END CERTIFICATE-----\n"; } catch (IOException e) { e.printStackTrace(); return null; } } /** * Saves the CA key and the Certificate chain to the specified keystore * * @param keyStore * the file to save the keystore to * @param type * the keystore type (PKCS12, JKS, etc) * @param password * the keystore password * @param keyPassword * the key password * @param caAlias * the alias to save the key and cert under * @throws GeneralSecurityException * @throws IOException */ public void save(File keyStore, String type, char[] password, char[] keyPassword, String caAlias) throws GeneralSecurityException, IOException { KeyStore store = KeyStore.getInstance(type); store.load(null, password); store.setKeyEntry(caAlias, caKey, keyPassword, caCerts); OutputStream out = new FileOutputStream(keyStore); try { store.store(out, password); } catch (IOException ioe) { ioe.printStackTrace(); } finally { out.close(); } } /** * Determines whether the public and private key used for the CA will be reused for other hosts as well. * * This is mostly just a performance optimisation, to save time generating a key pair for each host. Paranoid * clients may have an issue with this, in theory. * * @param reuse * true to reuse the CA key pair, false to generate a new key pair for each host */ public synchronized void setReuseKeys(boolean reuse) { reuseKeys = reuse; } /* * (non-Javadoc) * * @see org.owasp.proxy.daemon.CertificateProvider#getSocketFactory(java.lang .String, int) */ public synchronized SSLContext select(InetSocketAddress target) { String host = target.getHostName(); SSLContext sslContext = contextCache.get(host); if (sslContext == null) { try { X509KeyManager km = createKeyMaterial(host); sslContext = SSLContext.getInstance("SSLv3"); sslContext.init(new KeyManager[] { km }, null, null); contextCache.put(host, sslContext); } catch (GeneralSecurityException gse) { logger.warning("Error obtaining the SSLContext: " + gse.getLocalizedMessage()); } } return sslContext; } protected X500Principal getSubjectPrincipal(String host) { return new X500Principal("cn=" + host + ",ou=UNTRUSTED,o=UNTRUSTED"); } private X509KeyManager createKeyMaterial(String host) throws GeneralSecurityException { KeyPair keyPair; if (reuseKeys) { keyPair = new KeyPair(caCerts[0].getPublicKey(), caKey); } else { KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA"); keygen.initialize(2048, new SecureRandom()); keyPair = keygen.generateKeyPair(); } X500Principal subject = getSubjectPrincipal(host); Date begin = new Date(); Date ends = new Date(begin.getTime() + DEFAULT_VALIDITY); X509Certificate cert = BouncyCastleCertificateUtils.sign(subject, keyPair .getPublic(), caCerts[0].getSubjectX500Principal(), caCerts[0] .getPublicKey(), caKey, begin, ends, getNextSerialNo()); X509Certificate[] chain = new X509Certificate[caCerts.length + 1]; System.arraycopy(caCerts, 0, chain, 1, caCerts.length); chain[0] = cert; return new SingleX509KeyManager(host, keyPair.getPrivate(), chain); } protected BigInteger getNextSerialNo() { BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); while (serials.contains(serial)) serial = serial.add(BigInteger.ONE); serials.add(serial); return serial; } }