If you try to implement with Java the client side for a HTTPS communication with client authenification and google for it, you will find many examples. But with most of the examples you find, have the one or other problem, if you really try it. That’s why I want to wrap it up and bring it together to one class.
Important are:
- Working with Java 1.7 or newer
- Support for the latest used TLS version 1.2
- Support for the JSSE implementation
- Support for the latest cipher suites like TLS_RSA_WITH_AES_128_GCM_SHA256 or TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- Support for different password for keystore (JKS) and private key
- Manual selection of which client certificate to use
I have created a class SSLUtil handling this all and creating an SSLContext.
First we create a inner class inside SSLUtil helding the necessary information form keystores, passwords and keys.
static class SSLConfig { private final String keyStoreFile; private final String keyStorePassword; private final String keyName; private final String keyPassword; private final String trustStoreFile; private final String trustStorePassword; public SSLConfig(final String keyStoreFile, final String keyStorePassword, final String keyName, final String keyPassword, final String trustStoreFile, final String trustStorePassword) { super(); this.keyStoreFile = keyStoreFile; this.keyStorePassword = keyStorePassword; this.keyName = keyName; this.keyPassword = keyPassword; this.trustStoreFile = trustStoreFile; this.trustStorePassword = trustStorePassword; } public String getKeyStoreFile() { return this.keyStoreFile; } public String getKeyStorePassword() { return this.keyStorePassword; } public String getKeyName() { return this.keyName; } public String getKeyPassword() { return this.keyPassword; } public String getTrustStoreFile() { return this.trustStoreFile; } public String getTrustStorePassword() { return this.trustStorePassword; } };
Creating the SSLContext looks like this.
public static SSLConfig createSSLConfig(final String keyStoreFile, final String keyStorePassword, final String keyName, final String keyPassword, final String trustStoreFile, final String trustStorePassword) { return new SSLConfig(keyStoreFile, keyStorePassword, keyName, keyPassword, trustStoreFile, trustStorePassword); } public static SSLContext getSSLContext(final SSLConfig sslConfig) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, IOException, CertificateException, KeyManagementException { SSLContext sslContext = null; if (sslConfig != null) { sslContext = SSLContext.getInstance("TLS"); final KeyManager[] keyManagers = SSLUtil.getKeyManagers(sslConfig.getKeyStoreFile(), sslConfig.getKeyStorePassword(), sslConfig.getKeyName(), sslConfig.getKeyPassword()); final TrustManager[] trustManagers = SSLUtil.getTrustManagers(sslConfig.getTrustStoreFile(), sslConfig.getTrustStorePassword()); sslContext.init(keyManagers, trustManagers, new SecureRandom()); } else { final SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); sslContextBuilder.loadTrustMaterial(null, new TrustStrategy() { @Override public boolean isTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { return true; } }); sslContext = sslContextBuilder.build(); } return sslContext; }
public static SSLConfig createSSLConfig(final String keyStoreFile, final String keyStorePassword, final String keyName, final String keyPassword, final String trustStoreFile, final String trustStorePassword) { return new SSLConfig(keyStoreFile, keyStorePassword, keyName, keyPassword, trustStoreFile, trustStorePassword); } public static SSLContext getSSLContext(final SSLConfig sslConfig) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, IOException, CertificateException, KeyManagementException { SSLContext sslContext = null; if (sslConfig != null) { sslContext = SSLContext.getInstance("TLS"); final KeyManager[] keyManagers = SSLUtil.getKeyManagers(sslConfig.getKeyStoreFile(), sslConfig.getKeyStorePassword(), sslConfig.getKeyName(), sslConfig.getKeyPassword()); final TrustManager[] trustManagers = SSLUtil.getTrustManagers(sslConfig.getTrustStoreFile(), sslConfig.getTrustStorePassword()); sslContext.init(keyManagers, trustManagers, new SecureRandom()); } else { final SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); sslContextBuilder.loadTrustMaterial(null, new TrustStrategy() { @Override public boolean isTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { return true; } }); sslContext = sslContextBuilder.build(); } return sslContext; }
public static SSLConfig createSSLConfig(final String keyStoreFile, final String keyStorePassword, final String keyName, final String keyPassword, final String trustStoreFile, final String trustStorePassword) { return new SSLConfig(keyStoreFile, keyStorePassword, keyName, keyPassword, trustStoreFile, trustStorePassword); } public static SSLContext getSSLContext(final SSLConfig sslConfig) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, IOException, CertificateException, KeyManagementException { SSLContext sslContext = null; if (sslConfig != null) { sslContext = SSLContext.getInstance("TLS"); final KeyManager[] keyManagers = SSLUtil.getKeyManagers(sslConfig.getKeyStoreFile(), sslConfig.getKeyStorePassword(), sslConfig.getKeyName(), sslConfig.getKeyPassword()); final TrustManager[] trustManagers = SSLUtil.getTrustManagers(sslConfig.getTrustStoreFile(), sslConfig.getTrustStorePassword()); sslContext.init(keyManagers, trustManagers, new SecureRandom()); } else { final SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); sslContextBuilder.loadTrustMaterial(null, new TrustStrategy() { @Override public boolean isTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { return true; } }); sslContext = sslContextBuilder.build(); } return sslContext; }
public static SSLConfig createSSLConfig(final String keyStoreFile, final String keyStorePassword, final String keyName, final String keyPassword, final String trustStoreFile, final String trustStorePassword) { return new SSLConfig(keyStoreFile, keyStorePassword, keyName, keyPassword, trustStoreFile, trustStorePassword); } public static SSLContext getSSLContext(final SSLConfig sslConfig) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, IOException, CertificateException, KeyManagementException { SSLContext sslContext = null; if (sslConfig != null) { sslContext = SSLContext.getInstance("TLS"); final KeyManager[] keyManagers = SSLUtil.getKeyManagers(sslConfig.getKeyStoreFile(), sslConfig.getKeyStorePassword(), sslConfig.getKeyName(), sslConfig.getKeyPassword()); final TrustManager[] trustManagers = SSLUtil.getTrustManagers(sslConfig.getTrustStoreFile(), sslConfig.getTrustStorePassword()); sslContext.init(keyManagers, trustManagers, new SecureRandom()); } else { final SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); sslContextBuilder.loadTrustMaterial(null, new TrustStrategy() { @Override public boolean isTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { return true; } }); sslContext = sslContextBuilder.build(); } return sslContext; }
If the parameter sslConfig is null we create an SSLContext accepting every HTTPS connection and not using client certificate. Important is the parameter “TLS” instead of “SSL” to support TLS 1.2.
Loading the keystore is straight forward.
private static KeyStore loadKeystore(final String keystorePathAndFilename, final String keyStorePassword) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { final KeyStore clientKeyStore = KeyStore.getInstance("JKS"); final InputStream keystoreAsStream; final File ksFile = new File(keystorePathAndFilename); if (ksFile.exists()) { try { keystoreAsStream = new FileInputStream(ksFile); } catch (final FileNotFoundException e) { AppTestUtil.LOGGER.error("Could not read keystore file '" + keystorePathAndFilename + "'!", e); throw new KeyStoreException("Could not read keystore file '" + keystorePathAndFilename + "'!", e); } AppTestUtil.LOGGER.info("Keystore in file '" + keystorePathAndFilename + "' found, trying to load..."); } else { AppTestUtil.LOGGER.error("Could not find keystore file '" + keystorePathAndFilename + "'!"); throw new KeyStoreException("Could not find keystore file '" + keystorePathAndFilename + "'!"); } clientKeyStore.load(keystoreAsStream, keyStorePassword.toCharArray()); return clientKeyStore; }
And also creating the trust manager is nothing special.
private static TrustManager[] getTrustManagers(final String keystorePathAndFilename, final String keyStorePassword) throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException { final TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); final KeyStore trustKeyStore = SSLUtil.loadKeystore(keystorePathAndFilename, keyStorePassword); trustFactory.init(trustKeyStore); final TrustManager[] trustManagers = trustFactory.getTrustManagers(); return trustManagers; }
Additionally we need to create the key manager.
private static KeyManager[] getKeyManagers(final String keystorePathAndFilename, final String keyStorePassword, final String privateKeyAlias, final String privateKeyPassword) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, IOException, CertificateException { final KeyManagerFactory keyFactory = KeyManagerFactory.getInstance("NewSunX509"); final KeyStore keyStore = SSLUtil.loadKeystore(keystorePathAndFilename, keyStorePassword); keyFactory.init(keyStore, privateKeyPassword.toCharArray()); final KeyManager[] keyManagers = keyFactory.getKeyManagers(); if ((privateKeyAlias != null) && (privateKeyAlias.trim().length() > 0)) { for (int i = 0; i < keyManagers.length; i++) { final KeyManager keyManager = keyManagers[i]; if (keyManager instanceof X509KeyManager) { keyManagers[i] = new AliasPreferingX509KeyManager((X509KeyManager) keyManager, privateKeyAlias); } } } return keyManagers; }
Here are two important things: The parameter “NewSunX509” allows to use different password for the key store and the private key. Additional it uses the JSSE implementation supporting the new cipher suites.
Second important thing is the AliasPreferingX509KeyManager I found as an example in the blog post http://denistek.blogspot.co.at/2010/05/mutual-authentication-with-client.html.
private static class AliasPreferingX509KeyManager implements X509KeyManager { private final X509KeyManager x509KeyManager; private final String alias; private AliasPreferingX509KeyManager(final X509KeyManager x509KeyManager, final String alias) { this.x509KeyManager = x509KeyManager; this.alias = alias; } @Override public String[] getClientAliases(final String keyType, final Principal[] issuers) { return this.x509KeyManager.getClientAliases(keyType, issuers); } @Override public String chooseClientAlias(final String[] keyType, final Principal[] issuers, final Socket socket) { for (int i = 0; i < keyType.length; i++) { final String[] clientAliases = getClientAliases(keyType[i], issuers); if (clientAliases != null) { for (int j = 0; j < clientAliases.length; j++) { if (clientAliases[j].equals(this.alias) || clientAliases[j].endsWith("." + this.alias)) { return clientAliases[j]; } } } } return this.x509KeyManager.chooseClientAlias(keyType, issuers, socket); } @Override public String[] getServerAliases(final String keyType, final Principal[] issuers) { return this.x509KeyManager.getServerAliases(keyType, issuers); } @Override public String chooseServerAlias(final String keyType, final Principal[] issuers, final Socket socket) { return this.x509KeyManager.chooseServerAlias(keyType, issuers, socket); } @Override public X509Certificate[] getCertificateChain(final String alias) { return this.x509KeyManager.getCertificateChain(alias); } @Override public PrivateKey getPrivateKey(final String alias) { return this.x509KeyManager.getPrivateKey(alias); } };
That’s now a complete solution for creating a SSLContext for HTTPS connections.
Bernhard Mähr @ OPITZ-CONSULTING published at http://thecattlecrew.wordpress.com/