package org.carlspring.strongbox.security.certificates; import org.carlspring.commons.io.resource.ResourceCloser; import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; 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.net.Authenticator; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; import java.net.Proxy; import java.net.Socket; import java.net.URL; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Component public class KeyStoreManager { private static final Logger logger = LoggerFactory.getLogger(KeyStoreManager.class); @Inject private ProxyAuthenticator proxyAuthenticator; public KeyStoreManager() { } @PostConstruct public void init() { Authenticator.setDefault(proxyAuthenticator); } private KeyStore load(File fileName, char[] password) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); if (fileName == null) { keyStore.load(null, password); return keyStore; } InputStream is = new FileInputStream(fileName); try { keyStore.load(is, password); return keyStore; } finally { ResourceCloser.close(is, logger); } } private KeyStore store(File fileName, char[] password, KeyStore keyStore) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException { OutputStream os = new FileOutputStream(fileName); try { keyStore.store(os, password); return keyStore; } finally { ResourceCloser.close(os, logger); } } public KeyStore createNew(File fileName, char[] password) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { return store(fileName, password, load(null, password)); } public KeyStore changePassword(File fileName, char[] oldPassword, char[] newPassword) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { return store(fileName, newPassword, load(fileName, oldPassword)); } public Map<String, Certificate> listCertificates(File fileName, char[] password) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { KeyStore keyStore = load(fileName, password); Map<String, Certificate> certificates = new HashMap<>(); Enumeration<String> aliases = keyStore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); certificates.put(alias, keyStore.getCertificate(alias)); } return certificates; } public KeyStore removeCertificates(File fileName, char[] password, InetAddress host, int port) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { KeyStore keyStore = load(fileName, password); String prefix = host.getCanonicalHostName() + ":" + Integer.toString(port); Enumeration<String> aliases = keyStore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); if (StringUtils.startsWithIgnoreCase(alias, prefix)) { keyStore.deleteEntry(alias); } } return store(fileName, password, keyStore); } public KeyStore addCertificates(File fileName, char[] password, InetAddress host, int port) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, KeyManagementException { KeyStore keyStore = load(fileName, password); String prefix = host.getCanonicalHostName() + ":" + Integer.toString(port); X509Certificate[] chain = remoteCertificateChain(host, port); for (X509Certificate cert : chain) { keyStore.setCertificateEntry(prefix + "_" + cert.getSubjectDN().getName(), cert); } return store(fileName, password, keyStore); } public KeyStore addHttpsCertificates(File fileName, char[] password, Proxy httpProxy, PasswordAuthentication credentials, String host, int port) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException { KeyStore keyStore = load(fileName, password); String prefix = host + ":" + Integer.toString(port); ChainCaptureTrustManager tm = new ChainCaptureTrustManager(); SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(null, new TrustManager[]{ tm }, null); URL url = new URL("https", host, port, "/"); HttpsURLConnection conn = (HttpsURLConnection) (httpProxy != null ? url.openConnection(httpProxy) : url.openConnection()); conn.setSSLSocketFactory(ctx.getSocketFactory()); if (credentials != null) { proxyAuthenticator.getCredentials().set(credentials); } try { conn.connect(); for (X509Certificate cert : tm.chain) { keyStore.setCertificateEntry(prefix + "_" + cert.getSubjectDN().getName(), cert); } return store(fileName, password, keyStore); } finally { proxyAuthenticator.getCredentials().remove(); conn.disconnect(); } } public KeyStore addSslCertificates(File fileName, char[] password, Proxy socksProxy, PasswordAuthentication credentials, String host, int port) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, KeyManagementException { KeyStore keyStore = load(fileName, password); String prefix = host + ":" + Integer.toString(port); X509Certificate[] chain = remoteCertificateChain(socksProxy, credentials, host, port); for (X509Certificate cert : chain) { keyStore.setCertificateEntry(prefix + "_" + cert.getSubjectDN().getName(), cert); } return store(fileName, password, keyStore); } private X509Certificate[] remoteCertificateChain(InetAddress address, int port) throws NoSuchAlgorithmException, IOException, KeyStoreException, KeyManagementException { ChainCaptureTrustManager tm = new ChainCaptureTrustManager(); SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(null, new TrustManager[]{ tm }, null); SSLSocket socket = (SSLSocket) ctx.getSocketFactory().createSocket(address, port); try { socket.startHandshake(); socket.close(); } catch (SSLException ignore) // non trusted certificates should be returned as well { } return tm.chain; } private X509Certificate[] remoteCertificateChain(Proxy socksProxy, PasswordAuthentication credentials, String host, int port) throws NoSuchAlgorithmException, IOException, KeyStoreException, KeyManagementException { ChainCaptureTrustManager tm = new ChainCaptureTrustManager(); SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(null, new TrustManager[]{ tm }, null); if (credentials != null) { proxyAuthenticator.getCredentials().set(credentials); } Socket proxySocket = socksProxy != null ? new Socket(socksProxy) : null; if (proxySocket != null) { proxySocket.connect(new InetSocketAddress(host, port)); } try { handshake(ctx, proxySocket, host, port); return tm.chain; } finally { proxyAuthenticator.getCredentials().remove(); ResourceCloser.close(proxySocket, logger); } } private void handshake(SSLContext ctx, Socket proxySocket, String host, int port) throws IOException { SSLSocket socket = (SSLSocket) (proxySocket == null ? ctx.getSocketFactory().createSocket(host, port) : ctx.getSocketFactory().createSocket(proxySocket, host, port, true)); try { socket.startHandshake(); } catch (SSLException ignore) // non trusted certificates should be returned as well { } finally { ResourceCloser.close(socket, logger); } } private class ChainCaptureTrustManager implements X509TrustManager { private X509Certificate[] chain; @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { throw new UnsupportedOperationException(); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { this.chain = chain; } } }