org.parosproxy.paros.network.SSLConnector Maven / Gradle / Ivy
Show all versions of zap Show documentation
/*
* Created on May 31, 2004
*
* Paros and its related class files.
*
* Paros is an HTTP/HTTPS proxy for assessing web application security.
* Copyright (C) 2003-2004 Chinotec Technologies Company
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Clarified Artistic License
* as published by the Free Software Foundation.
*
* This program 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
* Clarified Artistic License for more details.
*
* You should have received a copy of the Clarified Artistic License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// ZAP: 2012/04/23 Added @Override annotation to all appropriate methods.
// ZAP: 2013/01/23 Clean up of exception handling/logging.
// ZAP: 2013/01/25 Issue 462: SSLSocketFactory with TLS enabled and default Cipher options
// ZAP: 2013/06/01 Issue 669: Certificate algorithm constraints in Java 1.7
// ZAP: 2014/03/23 Tidy up, removed and deprecated unused methods other minor changes
// ZAP: 2014/03/23 Issue 951: TLS' versions 1.1 and 1.2 not enabled by default
// ZAP: 2014/07/17 Issue 704: ZAP Error: handshake alert: unrecognized_name
// ZAP: 2014/08/14 Issue 1184: Improve support for IBM JDK
// ZAP: 2014/08/14 Issue 1274: ZAP Error [javax.net.ssl.SSLException]: Unsupported record version
// SSLv2Hello
// ZAP: 2014/10/28 Issue 1390: Force https on cfu call
// ZAP: 2015/10/13 Issue 1975: Allow use of default disabled cipher suites (such as RC4-SHA)
// ZAP: 2017/04/14 Validate that SSLv2Hello is set in conjunction with at least one SSL/TLS version.
// ZAP: 2017/09/22 Rely on SNI if the domain is no known when creating the SSL/TLS tunnel.
// ZAP: 2017/11/07 Pass the listeningIpAddress when creating the SSL/TLS tunnel. Check if hostname
// is an ipAddress when creating the SSL/TLS tunnel.
// ZAP: 2018/06/08 Don't enable client cert if none set (Issue 4745).
// ZAP: 2019/02/26 Disable TLS 1.3 by default as it currently fails with Java 11
// ZAP: 2019/06/01 Normalise line endings.
// ZAP: 2019/06/05 Normalise format/style.
// ZAP: 2020/04/20 Let SOCKS proxy resolve hosts if set (Issue 29).
// ZAP: 2020/10/30 Add SNI hostname when using SOCKS with unresolved addresses.
// ZAP: 2020/11/26 Use Log4j 2 classes for logging.
package org.parosproxy.paros.network;
import ch.csnc.extension.httpclient.SSLContextManager;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.ExtendedSSLSession;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.StandardConstants;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509KeyManager;
import org.apache.commons.collections.MapIterator;
import org.apache.commons.collections.map.LRUMap;
import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.HttpMethodDirector;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
import org.apache.commons.validator.routines.InetAddressValidator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.parosproxy.paros.security.CachedSslCertifificateServiceImpl;
import org.parosproxy.paros.security.CertData;
import org.parosproxy.paros.security.SslCertificateService;
public class SSLConnector implements SecureProtocolSocketFactory {
private static final String SSL = "SSL";
private static final String CONTENTS_UNRECOGNIZED_NAME_EXCEPTION = "unrecognized_name";
public static final String SECURITY_PROTOCOL_SSL_V2_HELLO = "SSLv2Hello";
public static final String SECURITY_PROTOCOL_SSL_V3 = "SSLv3";
public static final String SECURITY_PROTOCOL_TLS_V1 = "TLSv1";
public static final String SECURITY_PROTOCOL_TLS_V1_1 = "TLSv1.1";
public static final String SECURITY_PROTOCOL_TLS_V1_2 = "TLSv1.2";
public static final String SECURITY_PROTOCOL_TLS_V1_3 = "TLSv1.3";
private static final String[] DEFAULT_ENABLED_PROTOCOLS = {
SECURITY_PROTOCOL_SSL_V3,
SECURITY_PROTOCOL_TLS_V1,
SECURITY_PROTOCOL_TLS_V1_1,
SECURITY_PROTOCOL_TLS_V1_2,
SECURITY_PROTOCOL_TLS_V1_3
};
private static final String[] DEFAULT_SERVER_ENABLED_PROTOCOLS = {
SECURITY_PROTOCOL_SSL_V3,
SECURITY_PROTOCOL_TLS_V1,
SECURITY_PROTOCOL_TLS_V1_1,
SECURITY_PROTOCOL_TLS_V1_2,
// Disable TLS 1.3 by default as it currently fails with Java 11
// TODO re-enable (or just use DEFAULT_ENABLED_PROTOCOLS) when Java 11 works with TLS 1.3
// SECURITY_PROTOCOL_TLS_V1_3
};
private static final String[] FAIL_SAFE_DEFAULT_ENABLED_PROTOCOLS = {SECURITY_PROTOCOL_TLS_V1};
// client socket factories
private SSLSocketFactory clientSSLSockFactory = null;
private SSLSocketFactory clientSSLSockCertFactory = null;
private static String[] supportedProtocols;
private static String[] clientEnabledProtocols;
private static String[] serverEnabledProtocols;
private static ServerSslSocketsDecorator serverSslSocketsDecorator;
private ClientSslSocketsDecorator clientSslSocketsDecorator;
/**
* The maximum time, in minutes, that the cached misconfigured hosts are considered valid.
*
* @see #cacheMisconfiguredHost(String, int, InetAddress)
*/
private static long MAX_AGE_MISCONFIGURED_HOST_IN_MIN = 5;
/**
* The maximum time, in milliseconds, that the cached misconfigured hosts are considered valid.
*
* @see #MAX_AGE_MISCONFIGURED_HOST_IN_MIN
* @see #cacheMisconfiguredHost(String, int, InetAddress)
*/
private static long MAX_AGE_MISCONFIGURED_HOST_IN_MS =
TimeUnit.MINUTES.toMillis(MAX_AGE_MISCONFIGURED_HOST_IN_MIN);
/**
* A cache of misconfigured hosts (i.e. secure connection cannot be established because of
* "unrecognized_name" exception). The hosts are cached for a (short) period of time to avoid
* (most likely) failed connections.
*
* The {@code key} is the hostname+port and the {@code value} the address of the host.
*
* @see #MAX_AGE_MISCONFIGURED_HOST_IN_MIN
* @see #timeStampLastStaleCheck
* @see #cacheMisconfiguredHost(String, int, InetAddress)
* @see #getCachedMisconfiguredHost(String, int)
* @see #removeStaleCachedMisconfiguredHosts()
*/
private static LRUMap misconfiguredHosts;
/**
* Time stamp of last time the cache of misconfigured hosts was checked for stale entries.
*
* @see #misconfiguredHosts
* @see #removeStaleCachedMisconfiguredHosts()
*/
private static long timeStampLastStaleCheck;
// server related socket factories
// ZAP: removed ServerSocketFaktory
// ZAP: Added logger
private static final Logger logger = LogManager.getLogger(SSLConnector.class);
private static SSLContextManager sslContextManager = null;
/*
* If relaxedTrust then we ignore all of the 'usual' https checks.
* This is needed in order to test sites with custom certs
* However we dont want to override these checks for things like the 'check-for-updates' call
*/
private boolean relaxedTrust = true;
public SSLConnector() {
this(true);
}
public SSLConnector(boolean relaxedTrust) {
this.relaxedTrust = relaxedTrust;
if (clientSSLSockFactory == null) {
serverSslSocketsDecorator = new ServerSslSocketsDecorator();
clientSslSocketsDecorator = new ClientSslSocketsDecorator();
clientSSLSockFactory = getClientSocketFactory(SSL);
misconfiguredHosts = new LRUMap(10);
}
// ZAP: removed ServerSocketFaktory
if (sslContextManager == null) {
sslContextManager = new SSLContextManager();
}
}
public SSLContextManager getSSLContextManager() {
return sslContextManager;
}
public void setEnableClientCert(boolean enabled) {
if (enabled) {
if (clientSSLSockCertFactory == null) {
return;
}
clientSSLSockFactory = clientSSLSockCertFactory;
logger.info("ClientCert enabled using: " + sslContextManager.getDefaultKey());
} else {
clientSSLSockFactory = getClientSocketFactory(SSL);
logger.info("ClientCert disabled");
}
}
public void setActiveCertificate() {
SSLContext sslcont = sslContextManager.getSSLContext(sslContextManager.getDefaultKey());
clientSSLSockCertFactory =
createDecoratedClientSslSocketFactory(sslcont.getSocketFactory());
logger.info("ActiveCertificate set to: " + sslContextManager.getDefaultKey());
}
// ZAP: removed server socket methods
// FIXME: really needed?
public ServerSocket listen(int paramPortNum, int maxConnection, InetAddress ip)
throws IOException {
// ZAP: removed ServerSocketFaktory
// ServerSocket sslServerPort = serverSSLSockFactory.createServerSocket(
// paramPortNum, maxConnection, ip);
// return sslServerPort;
throw new UnsupportedOperationException(
"this code is probably not needed any more, SSL server sockets are not \"static\", they're created on the fly");
}
public SSLSocketFactory getClientSocketFactory(String type) {
// Trust all invalid server certificate
TrustManager[] trustMgr = new TrustManager[] {new RelaxedX509TrustManager()};
try {
SSLContext sslContext = SSLContext.getInstance(type);
java.security.SecureRandom x = new java.security.SecureRandom();
x.setSeed(System.currentTimeMillis());
if (relaxedTrust) {
sslContext.init(null, trustMgr, x);
} else {
sslContext.init(null, null, x);
}
clientSSLSockFactory =
createDecoratedClientSslSocketFactory(sslContext.getSocketFactory());
HttpsURLConnection.setDefaultSSLSocketFactory(clientSSLSockFactory);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return clientSSLSockFactory;
}
// ZAP: removed ServerSocketFaktory
/**
* @deprecated (2.3.0) No longer supported since it's no longer required/called by Commons
* HttpClient library (version ≥ 3.0). Throws {@code UnsupportedOperationException}.
*/
@Override
@Deprecated
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort)
throws IOException, UnknownHostException {
throw new UnsupportedOperationException(
"Method no longer supported since it's no longer required/called by Commons HttpClient library (version >= 3.0).");
}
/**
* Gets the SSL/TLS versions that can be safely used (known to be supported by the JRE).
*
* @return the SSL/TLS versions that can be safely used.
* @since 2.7.0
*/
public static String[] getFailSafeProtocols() {
return Arrays.copyOf(
FAIL_SAFE_DEFAULT_ENABLED_PROTOCOLS, FAIL_SAFE_DEFAULT_ENABLED_PROTOCOLS.length);
}
public static String[] getSupportedProtocols() {
if (supportedProtocols == null) {
readSupportedProtocols(null);
}
return Arrays.copyOf(supportedProtocols, supportedProtocols.length);
}
private static synchronized void readSupportedProtocols(SSLSocket sslSocket) {
if (supportedProtocols == null) {
logger.info("Reading supported SSL/TLS protocols...");
String[] tempSupportedProtocols;
if (sslSocket != null) {
logger.info("Using an existing SSLSocket...");
tempSupportedProtocols = sslSocket.getSupportedProtocols();
} else {
logger.info("Using a SSLEngine...");
try {
SSLContext ctx = SSLContext.getInstance(SSL);
ctx.init(null, null, null);
try {
tempSupportedProtocols = ctx.createSSLEngine().getSupportedProtocols();
} catch (UnsupportedOperationException e) {
logger.warn(
"Failed to use SSLEngine. Trying with unconnected socket...", e);
try (SSLSocket socket = (SSLSocket) ctx.getSocketFactory().createSocket()) {
tempSupportedProtocols = socket.getSupportedProtocols();
}
}
} catch (NoSuchAlgorithmException | KeyManagementException | IOException e) {
logger.error(
"Failed to read the SSL/TLS supported protocols."
+ " Using default protocol versions: "
+ Arrays.toString(FAIL_SAFE_DEFAULT_ENABLED_PROTOCOLS),
e);
tempSupportedProtocols = FAIL_SAFE_DEFAULT_ENABLED_PROTOCOLS;
}
}
Arrays.sort(tempSupportedProtocols);
supportedProtocols = tempSupportedProtocols;
logger.info(
"Done reading supported SSL/TLS protocols: "
+ Arrays.toString(supportedProtocols));
}
}
public static String[] getClientEnabledProtocols() {
if (clientEnabledProtocols == null) {
setClientEnabledProtocols(DEFAULT_ENABLED_PROTOCOLS);
}
return Arrays.copyOf(clientEnabledProtocols, clientEnabledProtocols.length);
}
public static void setClientEnabledProtocols(String[] protocols) {
clientEnabledProtocols = extractSupportedProtocols(protocols);
}
public static String[] getServerEnabledProtocols() {
if (serverEnabledProtocols == null) {
setServerEnabledProtocols(DEFAULT_SERVER_ENABLED_PROTOCOLS);
}
return Arrays.copyOf(serverEnabledProtocols, serverEnabledProtocols.length);
}
public static void setServerEnabledProtocols(String[] protocols) {
serverEnabledProtocols = extractSupportedProtocols(protocols);
}
private static String[] extractSupportedProtocols(String[] enabledProtocols) {
if (enabledProtocols == null || enabledProtocols.length == 0) {
throw new IllegalArgumentException("Protocol(s) required but no protocol set.");
}
String[] supportedProtocols = getSupportedProtocols();
ArrayList enabledSupportedProtocols = new ArrayList<>(supportedProtocols.length);
for (String protocol : enabledProtocols) {
if (protocol != null && Arrays.binarySearch(supportedProtocols, protocol) >= 0) {
enabledSupportedProtocols.add(protocol);
}
}
enabledSupportedProtocols.trimToSize();
if (enabledSupportedProtocols.isEmpty()) {
throw new IllegalArgumentException("No supported protocol(s) set.");
}
if (enabledSupportedProtocols.size() == 1
&& enabledSupportedProtocols.contains(SECURITY_PROTOCOL_SSL_V2_HELLO)) {
throw new IllegalArgumentException(
"Only SSLv2Hello set, must have at least one SSL/TLS version enabled.");
}
String[] extractedSupportedProtocols = new String[enabledSupportedProtocols.size()];
enabledSupportedProtocols.toArray(extractedSupportedProtocols);
return extractedSupportedProtocols;
}
/**
* Attempts to get a new socket connection to the given host within the given time limit.
*
* @param host the host name/IP
* @param port the port on the host
* @param localAddress the local host name/IP to bind the socket to
* @param localPort the port on the local machine
* @param params {@link HttpConnectionParams Http connection parameters}
* @return Socket a new socket
* @throws IOException if an I/O error occurs while creating the socket
* @throws UnknownHostException if the IP address of the host cannot be determined
* @throws ConnectTimeoutException
*/
@Override
public Socket createSocket(
final String host,
final int port,
final InetAddress localAddress,
final int localPort,
final HttpConnectionParams params)
throws IOException, UnknownHostException, ConnectTimeoutException {
if (params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
if (timeout == 0) {
InetAddress hostAddress = getCachedMisconfiguredHost(host, port);
if (hostAddress != null) {
return clientSSLSockFactory.createSocket(
hostAddress, port, localAddress, localPort);
}
try {
SSLSocket sslSocket =
(SSLSocket)
clientSSLSockFactory.createSocket(
host, port, localAddress, localPort);
sslSocket.startHandshake();
return sslSocket;
} catch (SSLException e) {
if (!e.getMessage().contains(CONTENTS_UNRECOGNIZED_NAME_EXCEPTION)
|| !params.getBooleanParameter(
HttpMethodDirector.PARAM_RESOLVE_HOSTNAME, true)) {
throw e;
}
hostAddress = InetAddress.getByName(host);
cacheMisconfiguredHost(host, port, hostAddress);
return clientSSLSockFactory.createSocket(
hostAddress, port, localAddress, localPort);
}
}
Socket socket = clientSSLSockFactory.createSocket();
SocketAddress localAddr = new InetSocketAddress(localAddress, localPort);
socket.bind(localAddr);
InetSocketAddress remoteAddr = createRemoteAddr(params, host, port);
addSniHostName((SSLSocket) socket, remoteAddr);
socket.connect(remoteAddr, timeout);
return socket;
}
private static InetSocketAddress createRemoteAddr(
HttpConnectionParams params, String host, int port) {
if (params == null
|| params.getBooleanParameter(HttpMethodDirector.PARAM_RESOLVE_HOSTNAME, true)) {
return new InetSocketAddress(host, port);
}
return InetSocketAddress.createUnresolved(host, port);
}
/**
* Adds the SNI hostname to the given {@code SSLSocket}, if needed.
*
* The SNI hostname is added if the given address is unresolved and is a hostname. The
* default {@code SSLSocket} implementation does not automatically add the SNI hostname if the
* address is unresolved.
*
* @param sslSocket the socket to add the SNI hostname.
* @param remoteAddr the remote address, to where the socket is going to be connected.
*/
private static void addSniHostName(SSLSocket sslSocket, InetSocketAddress remoteAddr) {
if (!remoteAddr.isUnresolved()) {
return;
}
SNIHostName sniHostName = createSniHostName(remoteAddr.getHostString());
if (sniHostName == null) {
return;
}
SSLParameters parameters = sslSocket.getSSLParameters();
List serverNames = copy(parameters.getServerNames());
serverNames.add(sniHostName);
parameters.setServerNames(serverNames);
sslSocket.setSSLParameters(parameters);
}
private static SNIHostName createSniHostName(String hostname) {
if (isIpAddress(hostname)) {
return null;
}
try {
return new SNIHostName(hostname);
} catch (IllegalArgumentException e) {
logger.warn("Failed to create the SNI hostname for: " + hostname, e);
}
return null;
}
private static List copy(List list) {
if (list == null || list.isEmpty()) {
return new ArrayList<>(1);
}
List newList = new ArrayList<>(list.size() + 1);
newList.addAll(list);
return newList;
}
private static void cacheMisconfiguredHost(String host, int port, InetAddress address) {
synchronized (misconfiguredHosts) {
if (!misconfiguredHosts.isEmpty()) {
removeStaleCachedMisconfiguredHosts();
}
logger.info(
"Caching address of misconfigured (\"unrecognized_name\") host [host="
+ host
+ ", port="
+ port
+ "] for the next "
+ MAX_AGE_MISCONFIGURED_HOST_IN_MIN
+ " minutes, following connections will not use the hostname.");
misconfiguredHosts.put(
host + port, new MisconfiguredHostCacheEntry(host, port, address));
}
}
/**
* Removes all stale cached misconfigured hosts.
*
* Note: This method should be called in a {@code synchronized} block with
* the object {@code misconfiguredHosts}.
*
* @see #misconfiguredHosts
* @see #cacheMisconfiguredHost(String, int, InetAddress)
* @see #getCachedMisconfiguredHost(String, int)
*/
private static void removeStaleCachedMisconfiguredHosts() {
long currentTime = System.currentTimeMillis();
if (!((currentTime - timeStampLastStaleCheck) >= MAX_AGE_MISCONFIGURED_HOST_IN_MS)) {
return;
}
timeStampLastStaleCheck = currentTime;
for (MapIterator it = misconfiguredHosts.mapIterator(); it.hasNext(); ) {
it.next();
MisconfiguredHostCacheEntry entry = (MisconfiguredHostCacheEntry) it.getValue();
if (entry.isStale(currentTime)) {
logger.info(
"Removing stale cached address of misconfigured (\"unrecognized_name\") host [host="
+ entry.getHost()
+ ", port="
+ entry.getPort()
+ "], following connections will be attempted with the hostname.");
it.remove();
}
}
}
private static InetAddress getCachedMisconfiguredHost(String host, int port) {
synchronized (misconfiguredHosts) {
if (misconfiguredHosts.isEmpty()) {
return null;
}
removeStaleCachedMisconfiguredHosts();
MisconfiguredHostCacheEntry entry =
(MisconfiguredHostCacheEntry) misconfiguredHosts.get(host + port);
if (entry != null) {
return entry.getAddress();
}
return null;
}
}
/**
* @deprecated (2.3.0) No longer supported since it's no longer required/called by Commons
* HttpClient library (version ≥ 3.0). Throws {@code UnsupportedOperationException}.
*/
@Override
@Deprecated
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
throw new UnsupportedOperationException(
"Method no longer supported since it's no longer required/called by Commons HttpClient library (version >= 3.0).");
}
/**
* @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean)
*/
@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose)
throws IOException, UnknownHostException {
return createSocket(socket, host, port, autoClose, null);
}
@Override
public Socket createSocket(
Socket socket, String host, int port, boolean autoClose, HttpConnectionParams params)
throws IOException {
InetAddress inetAddress = getCachedMisconfiguredHost(host, port);
if (inetAddress != null) {
return clientSSLSockFactory.createSocket(
socket, inetAddress.getHostAddress(), port, autoClose);
}
try {
SSLSocket socketSSL =
(SSLSocket) clientSSLSockFactory.createSocket(socket, host, port, autoClose);
socketSSL.startHandshake();
return socketSSL;
} catch (SSLException e) {
if (e.getMessage().contains(CONTENTS_UNRECOGNIZED_NAME_EXCEPTION)
&& params.getBooleanParameter(
HttpMethodDirector.PARAM_RESOLVE_HOSTNAME, true)) {
cacheMisconfiguredHost(host, port, InetAddress.getByName(host));
}
// Throw the exception anyway because the socket might no longer be usable (e.g.
// closed). The connection will be
// retried (see HttpMethodDirector#executeWithRetry(HttpMethod) for more information on
// the retry policy).
throw e;
}
}
/**
* Create a SSLsocket using an existing connected socket. It can be used such as a tunneled SSL
* proxy socket (e.g. when a CONNECT request is received). This SSLSocket will start server side
* handshake immediately.
*
* @param targethost the host where you want to connect to
* @param socket
* @return
* @throws IOException
*/
public Socket createTunnelServerSocket(String targethost, Socket socket) throws IOException {
InetAddress listeningAddress = socket.getLocalAddress();
// ZAP: added host name parameter
SSLSocket s =
(SSLSocket)
getTunnelSSLSocketFactory(targethost, listeningAddress)
.createSocket(
socket,
socket.getInetAddress().getHostAddress(),
socket.getPort(),
true);
s.setUseClientMode(false);
s.startHandshake();
return s;
}
/** @deprecated (2.8.0) No longer used/needed. */
@Deprecated
public SSLSocketFactory getTunnelSSLSocketFactory(String hostname) {
return getTunnelSSLSocketFactory(hostname, null);
}
public SSLSocketFactory getTunnelSSLSocketFactory(
String hostname, InetAddress listeningAddress) {
// SSLServerSocketFactory ssf = null;
// set up key manager to do server authentication
// KeyStore ks;
try {
SSLContext ctx = SSLContext.getInstance(SSL);
// Normally "SunX509", "IbmX509"...
KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyManager[] keyManagers;
if (hostname != null && !hostname.isEmpty()) {
initKeyManagerFactoryWithCertForHostname(kmf, hostname, listeningAddress);
keyManagers = kmf.getKeyManagers();
} else {
keyManagers = new KeyManager[] {new SniX509KeyManager(kmf, listeningAddress)};
}
java.security.SecureRandom x = new java.security.SecureRandom();
x.setSeed(System.currentTimeMillis());
ctx.init(keyManagers, null, x);
SSLSocketFactory tunnelSSLFactory =
createDecoratedServerSslSocketFactory(ctx.getSocketFactory());
return tunnelSSLFactory;
} catch (NoSuchAlgorithmException
| KeyStoreException
| CertificateException
| UnrecoverableKeyException
| KeyManagementException
| InvalidKeyException
| NoSuchProviderException
| SignatureException
| IOException e) {
// Turn into RuntimeException. How to handle this error in a user
// friendly way?
throw new RuntimeException(e);
}
}
static void initKeyManagerFactoryWithCertForHostname(
KeyManagerFactory keyManagerFactory, String hostname, InetAddress listeningAddress)
throws InvalidKeyException, UnrecoverableKeyException, NoSuchAlgorithmException,
CertificateException, NoSuchProviderException, SignatureException,
KeyStoreException, IOException {
boolean hostnameIsIpAddress = isIpAddress(hostname);
CertData certData = hostnameIsIpAddress ? new CertData() : new CertData(hostname);
if (hostname == null && listeningAddress != null) {
certData.addSubjectAlternativeName(
new CertData.Name(CertData.Name.IP_ADDRESS, listeningAddress.getHostAddress()));
}
if (hostnameIsIpAddress) {
certData.addSubjectAlternativeName(
new CertData.Name(CertData.Name.IP_ADDRESS, hostname));
}
KeyStore ks = CachedSslCertifificateServiceImpl.getService().createCertForHost(certData);
keyManagerFactory.init(ks, SslCertificateService.PASSPHRASE);
}
private static boolean isIpAddress(String value) {
return value != null
&& !value.isEmpty()
&& InetAddressValidator.getInstance().isValid(value);
}
private static SSLSocketFactory createDecoratedServerSslSocketFactory(
final SSLSocketFactory delegate) {
return new DecoratedSocketsSslSocketFactory(delegate, serverSslSocketsDecorator);
}
private SSLSocketFactory createDecoratedClientSslSocketFactory(
final SSLSocketFactory delegate) {
return new DecoratedSocketsSslSocketFactory(delegate, clientSslSocketsDecorator);
}
private static class ServerSslSocketsDecorator
implements DecoratedSocketsSslSocketFactory.SslSocketDecorator {
@Override
public void decorate(SSLSocket sslSocket) {
if (supportedProtocols == null) {
readSupportedProtocols(sslSocket);
}
sslSocket.setEnabledProtocols(getServerEnabledProtocols());
}
}
private class ClientSslSocketsDecorator
implements DecoratedSocketsSslSocketFactory.SslSocketDecorator {
@Override
public void decorate(SSLSocket sslSocket) {
if (supportedProtocols == null) {
readSupportedProtocols(sslSocket);
}
sslSocket.setEnabledProtocols(getClientEnabledProtocols());
if (relaxedTrust) {
sslSocket.setEnabledCipherSuites(sslSocket.getSupportedCipherSuites());
}
}
}
private static class MisconfiguredHostCacheEntry {
private final String host;
private final int port;
private final InetAddress address;
private final long timeStampCreation;
public MisconfiguredHostCacheEntry(String host, int port, InetAddress address) {
this.host = host;
this.port = port;
this.address = address;
this.timeStampCreation = System.currentTimeMillis();
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public InetAddress getAddress() {
return address;
}
public boolean isStale(long currentTime) {
return (currentTime - timeStampCreation) >= MAX_AGE_MISCONFIGURED_HOST_IN_MS;
}
}
private static class SniX509KeyManager implements X509KeyManager {
private final KeyManagerFactory keyManagerFactory;
private InetAddress listeningAddress;
private X509KeyManager x509KeyManager;
public SniX509KeyManager(
KeyManagerFactory keyManagerFactory, InetAddress listeningAddress) {
this.keyManagerFactory = keyManagerFactory;
this.listeningAddress = listeningAddress;
}
@Override
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
if (x509KeyManager == null) {
createX509KeyManager(socket);
}
return x509KeyManager.chooseServerAlias(keyType, issuers, socket);
}
private void createX509KeyManager(Socket socket) {
if (!(socket instanceof SSLSocket)) {
logAndThrow("Expected a SSLSocket to extract the domain from SNI extension.");
}
SSLSocket sslSocket = (SSLSocket) socket;
String hostname = extractHostname(sslSocket.getHandshakeSession());
if (hostname == null) {
logger.debug("No domain extracted from SSL/TLS handshake session.");
}
try {
initKeyManagerFactoryWithCertForHostname(
keyManagerFactory, hostname, listeningAddress);
} catch (InvalidKeyException
| UnrecoverableKeyException
| NoSuchAlgorithmException
| CertificateException
| NoSuchProviderException
| SignatureException
| KeyStoreException
| IOException e) {
logAndThrow(
"Failed to generate the certificate for '"
+ hostname
+ "' caused by: "
+ e.getMessage(),
e);
}
x509KeyManager = getX509KeyManager(keyManagerFactory.getKeyManagers());
if (x509KeyManager == null) {
logAndThrow(
"No X509KeyManager found in: "
+ Arrays.toString(keyManagerFactory.getKeyManagers()));
}
}
private static void logAndThrow(String message) {
logAndThrow(message, null);
}
private static void logAndThrow(String message, Throwable cause) {
logger.warn(message, cause);
throw new RuntimeException(message, cause);
}
private static X509KeyManager getX509KeyManager(KeyManager[] keyManagers) {
for (int i = 0; i < keyManagers.length; i++) {
KeyManager keyManager = keyManagers[i];
if (keyManager instanceof X509KeyManager) {
return (X509KeyManager) keyManager;
}
}
return null;
}
private static String extractHostname(SSLSession sslSession) {
if (sslSession instanceof ExtendedSSLSession) {
for (SNIServerName serverName :
((ExtendedSSLSession) sslSession).getRequestedServerNames()) {
if (serverName.getType() == StandardConstants.SNI_HOST_NAME) {
return ((SNIHostName) serverName).getAsciiName();
}
}
}
return null;
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
return x509KeyManager.getCertificateChain(alias);
}
@Override
public PrivateKey getPrivateKey(String alias) {
return x509KeyManager.getPrivateKey(alias);
}
@Override
public String[] getServerAliases(String keyType, Principal[] issuers) {
return null;
}
@Override
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
return null;
}
@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
return null;
}
}
}
class RelaxedX509TrustManager extends X509ExtendedTrustManager {
public boolean checkClientTrusted(java.security.cert.X509Certificate[] chain) {
return true;
}
public boolean isServerTrusted(java.security.cert.X509Certificate[] chain) {
return true;
}
public boolean isClientTrusted(java.security.cert.X509Certificate[] chain) {
return true;
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {}
@Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] chain, String authType, Socket socket)
throws CertificateException {}
@Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] chain, String authType, Socket socket)
throws CertificateException {}
@Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] chain, String authType, SSLEngine engine)
throws CertificateException {}
@Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] chain, String authType, SSLEngine engine)
throws CertificateException {}
}