Java tutorial
/** * Sahi - Web Automation and Test Tool * * Copyright 2006 V Narayan Raman * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.sf.sahi.ssl; import net.sf.sahi.config.Configuration; import net.sf.sahi.util.Utils; import org.apache.log4j.Logger; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.X500NameBuilder; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.PEMWriter; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import javax.net.ssl.*; import java.io.*; import java.math.BigInteger; import java.net.Socket; import java.security.*; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Date; import java.util.HashMap; public class SSLHelper { private static final SSLHelper instance = new SSLHelper(); private static final String KEYSTORE_PASSWORD = "DUMMYPASSWORD"; private static final Logger logger = Logger.getLogger(SSLHelper.class); static HashMap<String, SSLSocketFactory> sslSocketFactories = new HashMap<>(); public final String rootCAPath = Configuration.getRootCaPath(); private final Provider bouncyCastleProvider = new BouncyCastleProvider(); private KeyStore keystore; private X509Certificate rootCA; private KeyStore clientCertKeystore; private SecureRandom secureRandom = new SecureRandom(); public static SSLHelper getInstance() { return instance; } private synchronized static void executeCommand(String command) throws Exception { Utils.executeCommand(Utils.getCommandTokens(command)); } public static TrustManager[] getAllTrustingManager() { TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] certs, String authType) { } public void checkServerTrusted(X509Certificate[] certs, String authType) { } } }; return trustAllCerts; } private SSLSocketFactory getSSLClientSocketFactory(String domain) throws Exception { if (!sslSocketFactories.containsKey(domain)) { generateKeyAndPutIntoKeyStore(domain); final SSLSocketFactory socketFactory = createSocketFactory(domain); sslSocketFactories.put(domain, socketFactory); return socketFactory; } return sslSocketFactories.get(domain); } public void checkRootCA() throws Exception { createKeystore(); createClientKeyStore(); if (!new File(rootCAPath).exists() || !new File(Configuration.getRootKeyPath()).exists()) { createRootCA(); } else { readRootCA(); } } /** * Read certificate and adds it to the keystore. * * @throws IOException * @throws CertificateException * @throws KeyStoreException */ private void readRootCA() throws IOException, CertificateException, KeyStoreException { Key _privateKey = readPrivateKey(Configuration.getRootKeyPath()); X509CertificateHolder holder = (X509CertificateHolder) readWithPemParser(Configuration.getRootCaPath()); rootCA = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME) .getCertificate(holder); keystore.setKeyEntry(Configuration.getRootCaName(), _privateKey, KEYSTORE_PASSWORD.toCharArray(), new X509Certificate[] { rootCA }); } private PrivateKey readPrivateKey(String privateKeyPath) throws IOException { PEMKeyPair keyPair = (PEMKeyPair) new PEMParser(new FileReader(privateKeyPath)).readObject(); return new JcaPEMKeyConverter().getKeyPair(keyPair).getPrivate(); } private void createClientKeyStore() throws Exception { clientCertKeystore = KeyStore.getInstance(Configuration.getSSLClientKeyStoreType()); String clientCertPath = Configuration.getSSLClientCertPath(); FileInputStream fileInputStream = null; if (clientCertPath != null) { try { fileInputStream = new FileInputStream(clientCertPath); } catch (IOException ioe) { logger.error("\n----\nCertificate not found: " + clientCertPath + "\n----"); } } String password = Configuration.getSSLClientCertPassword(); char[] passphrase = password == null ? null : password.toCharArray(); clientCertKeystore.load(fileInputStream, passphrase); } private Object readWithPemParser(String source) throws IOException { PEMParser parser = new PEMParser(new FileReader(source)); return parser.readObject(); } protected KeyStore getKeyStore() { return keystore; } private SSLSocketFactory createSocketFactory(String domain) throws Exception { SSLSocketFactory factory; KeyManagerFactory keyManagerFactory = getKeyManagerFactory(keystore, KEYSTORE_PASSWORD); SSLContext sslContext = SSLContext.getInstance("SSLv3"); KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); keyManagers = wrapKeyManagers(keyManagers, domain); sslContext.init(keyManagers, getAllTrustingManager(), secureRandom); factory = sslContext.getSocketFactory(); return factory; } private KeyManager[] wrapKeyManagers(KeyManager[] kma, String domain) { KeyManager[] kma2 = new KeyManager[kma.length]; for (int i = 0; i < kma2.length; i++) { kma2[i] = new X509KeyMangerWrapper((X509KeyManager) kma[i], domain); } return kma2; } public KeyManagerFactory getKeyManagerFactoryForRemoteFetch() throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { String fileWithPath = Configuration.getSSLClientCertPath(); if (fileWithPath != null) logger.info("Using SSL Cient Cert path at: " + fileWithPath); return getKeyManagerFactory(clientCertKeystore, Configuration.getSSLClientCertPassword()); } public KeyManagerFactory getKeyManagerFactory(KeyStore keystore, String password) throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException { char[] passphrase = password == null ? null : password.toCharArray(); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(Configuration.getSSLAlgorithm()); keyManagerFactory.init(keystore, passphrase); return keyManagerFactory; } protected void generateKeyAndPutIntoKeyStore(final String domain) throws Exception { KeyPair keyPair = newKeyPair(); X509Certificate cert = buildSignedCert(domain, keyPair); addCertToKeystore(cert, keyPair.getPrivate(), domain); } /** * Creates an empty keystore * @throws Exception */ private void createKeystore() throws Exception { if (keystore == null) { Security.addProvider(bouncyCastleProvider); keystore = KeyStore.getInstance(Configuration.getSSLClientKeyStoreType(), bouncyCastleProvider); keystore.load(null, KEYSTORE_PASSWORD.toCharArray()); } } /** * Private/Public Keys for a certificate * @return * @throws NoSuchProviderException * @throws NoSuchAlgorithmException */ private KeyPair newKeyPair() throws NoSuchProviderException, NoSuchAlgorithmException { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", bouncyCastleProvider.getName()); keyPairGenerator.initialize(2048, secureRandom); return keyPairGenerator.generateKeyPair(); } private X500Name buildRootIssuer() { return buildName(Configuration.getRootCaName()); } /** * used to write certificates and keys to a file * @param target * @param o * @throws IOException * @throws CertificateEncodingException */ public void writePEMObject(String target, Object o) throws IOException, CertificateEncodingException { new File(target).delete(); PEMWriter writer = new PEMWriter(new FileWriter(target)); writer.writeObject(o); writer.close(); } /** * We need a root CA as a file to add to the browser under which all certificates will be trusted. * * @throws Exception */ private void createRootCA() throws Exception { KeyPair _keyPair = newKeyPair(); rootCA = buildRootCert(Configuration.getRootCaName(), _keyPair); writePEMObject(rootCAPath, rootCA); writePEMObject(Configuration.getRootKeyPath(), _keyPair.getPrivate()); keystore.setKeyEntry(Configuration.getRootCaName(), _keyPair.getPrivate(), KEYSTORE_PASSWORD.toCharArray(), new X509Certificate[] { rootCA }); } private X509Certificate buildRootCert(String domain, KeyPair _keyPair) throws Exception { X509v3CertificateBuilder certificateBuilder = createX509v3CertificateBuilder(domain, _keyPair); certificateBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true)); return createX509Certificate(certificateBuilder, _keyPair.getPrivate()); } private X509Certificate buildSignedCert(String domain, KeyPair _keyPair) throws Exception { PrivateKey privateRootKey = (PrivateKey) keystore.getKey(Configuration.getRootCaName(), KEYSTORE_PASSWORD.toCharArray()); X509v3CertificateBuilder certificateBuilder = createX509v3CertificateBuilder(domain, _keyPair); return createX509Certificate(certificateBuilder, privateRootKey); } private X509Certificate createX509Certificate(X509v3CertificateBuilder certificateBuilder, PrivateKey privateRootKey) throws OperatorCreationException, CertificateException { ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSAEncryption") .setProvider(bouncyCastleProvider).build(privateRootKey); return new JcaX509CertificateConverter().setProvider(bouncyCastleProvider) .getCertificate(certificateBuilder.build(signer)); } private X509v3CertificateBuilder createX509v3CertificateBuilder(String domain, KeyPair _keyPair) { X500Name issuer = buildRootIssuer(); X500Name subject = buildName(domain); Date startDate = new Date(); long aYear = (long) 1000 * 60 * 60 * 24 * 365; Date expiryDate = new Date(startDate.getTime() + aYear); SubjectPublicKeyInfo subjectPublicKeyInfo = getSubjectPublicKeyInfo(_keyPair); return new X509v3CertificateBuilder(issuer, BigInteger.valueOf(new Date().getTime()), new Date(), expiryDate, subject, subjectPublicKeyInfo); } private X500Name buildName(String domain) { X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE); builder.addRDN(BCStyle.O, "*** Sahi Browser Testing Dummy Certificate ***"); builder.addRDN(BCStyle.CN, domain); return builder.build(); } private void addCertToKeystore(X509Certificate _cert, Key _privateKey, String alias) throws KeyStoreException { X509Certificate[] chain = new X509Certificate[2]; chain[0] = _cert; chain[1] = rootCA; keystore.setKeyEntry(alias, _privateKey, KEYSTORE_PASSWORD.toCharArray(), chain); } private SubjectPublicKeyInfo getSubjectPublicKeyInfo(KeyPair _keyPair) { byte[] encodedPublicKey = _keyPair.getPublic().getEncoded(); return new SubjectPublicKeyInfo(ASN1Sequence.getInstance(encodedPublicKey)); } public SSLSocket convertToSecureSocket(final Socket plainSocket, final String domain) throws Exception { try { return (SSLSocket) getSSLClientSocketFactory(domain).createSocket(plainSocket, plainSocket.getInetAddress().getHostName(), plainSocket.getPort(), true); } catch (IOException e) { e.printStackTrace(); } return null; } public SSLSocket convertToSecureServerSocket(final Socket socket, final String domain) throws Exception { SSLSocket sslSocket = convertToSecureSocket(socket, domain); sslSocket.setUseClientMode(false); return sslSocket; } /** * Wrapper for the key manager to select the correct server key within a single * key store. * * @author Jens Wilke */ public static class X509KeyMangerWrapper implements X509KeyManager { X509KeyManager wrapped; String domain; public X509KeyMangerWrapper(X509KeyManager wrapped, String domain) { this.wrapped = wrapped; this.domain = domain; } public String[] getClientAliases(String s, Principal[] principals) { return wrapped.getClientAliases(s, principals); } public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { return wrapped.chooseClientAlias(strings, principals, socket); } public String[] getServerAliases(String s, Principal[] principals) { return wrapped.getServerAliases(s, principals); } /** * the socket factory calls this through the ssl context * first with EC_EC and then with RSA. The normal key store implementation * returns the last certificate. Return the CN for the domain we want to * connect. This certificate and key is also existing in the keystore. */ public String chooseServerAlias(String s, Principal[] principals, Socket socket) { return domain; } public X509Certificate[] getCertificateChain(String s) { return wrapped.getCertificateChain(s); } public PrivateKey getPrivateKey(String s) { return wrapped.getPrivateKey(s); } } }