blob: 0731a944258bc3c4338d64d4509a80a8776ff351 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.util.ssl;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.CRL;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.CertPathTrustManagerParameters;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIMatcher;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.StandardConstants;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509TrustManager;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.security.CertificateUtils;
import org.eclipse.jetty.util.security.CertificateValidator;
import org.eclipse.jetty.util.security.Password;
/**
* SslContextFactory is used to configure SSL connectors
* as well as HttpClient. It holds all SSL parameters and
* creates SSL context based on these parameters to be
* used by the SSL connectors.
*/
public class SslContextFactory extends AbstractLifeCycle implements Dumpable
{
public final static TrustManager[] TRUST_ALL_CERTS = new X509TrustManager[]{new X509TrustManager()
{
public java.security.cert.X509Certificate[] getAcceptedIssuers()
{
return new java.security.cert.X509Certificate[]{};
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
{
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
{
}
}};
private static final Logger LOG = Log.getLogger(SslContextFactory.class);
public static final String DEFAULT_KEYMANAGERFACTORY_ALGORITHM =
(Security.getProperty("ssl.KeyManagerFactory.algorithm") == null ?
KeyManagerFactory.getDefaultAlgorithm() : Security.getProperty("ssl.KeyManagerFactory.algorithm"));
public static final String DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM =
(Security.getProperty("ssl.TrustManagerFactory.algorithm") == null ?
TrustManagerFactory.getDefaultAlgorithm() : Security.getProperty("ssl.TrustManagerFactory.algorithm"));
/** String name of key password property. */
public static final String KEYPASSWORD_PROPERTY = "org.eclipse.jetty.ssl.keypassword";
/** String name of keystore password property. */
public static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";
private final Set<String> _excludeProtocols = new LinkedHashSet<>();
private final Set<String> _includeProtocols = new LinkedHashSet<>();
private final Set<String> _excludeCipherSuites = new LinkedHashSet<>();
private final List<String> _includeCipherSuites = new ArrayList<>();
private final Map<String, X509> _aliasX509 = new HashMap<>();
private final Map<String, X509> _certHosts = new HashMap<>();
private final Map<String, X509> _certWilds = new HashMap<>();
private String[] _selectedProtocols;
private boolean _useCipherSuitesOrder = true;
private Comparator<String> _cipherComparator;
private String[] _selectedCipherSuites;
private Resource _keyStoreResource;
private String _keyStoreProvider;
private String _keyStoreType = "JKS";
private String _certAlias;
private Resource _trustStoreResource;
private String _trustStoreProvider;
private String _trustStoreType = "JKS";
private boolean _needClientAuth = false;
private boolean _wantClientAuth = false;
private Password _keyStorePassword;
private Password _keyManagerPassword;
private Password _trustStorePassword;
private String _sslProvider;
private String _sslProtocol = "TLS";
private String _secureRandomAlgorithm;
private String _keyManagerFactoryAlgorithm = DEFAULT_KEYMANAGERFACTORY_ALGORITHM;
private String _trustManagerFactoryAlgorithm = DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM;
private boolean _validateCerts;
private boolean _validatePeerCerts;
private int _maxCertPathLength = -1;
private String _crlPath;
private boolean _enableCRLDP = false;
private boolean _enableOCSP = false;
private String _ocspResponderURL;
private KeyStore _setKeyStore;
private KeyStore _setTrustStore;
private boolean _sessionCachingEnabled = true;
private int _sslSessionCacheSize = -1;
private int _sslSessionTimeout = -1;
private SSLContext _setContext;
private String _endpointIdentificationAlgorithm = null;
private boolean _trustAll;
private boolean _renegotiationAllowed = true;
private int _renegotiationLimit = 5;
private Factory _factory;
/**
* Construct an instance of SslContextFactory
* Default constructor for use in XmlConfiguration files
*/
public SslContextFactory()
{
this(false);
}
/**
* Construct an instance of SslContextFactory
* Default constructor for use in XmlConfiguration files
*
* @param trustAll whether to blindly trust all certificates
* @see #setTrustAll(boolean)
*/
public SslContextFactory(boolean trustAll)
{
this(trustAll, null);
}
/**
* Construct an instance of SslContextFactory
*
* @param keyStorePath default keystore location
*/
public SslContextFactory(String keyStorePath)
{
this(false, keyStorePath);
}
private SslContextFactory(boolean trustAll, String keyStorePath)
{
setTrustAll(trustAll);
addExcludeProtocols("SSL", "SSLv2", "SSLv2Hello", "SSLv3");
setExcludeCipherSuites("^.*_(MD5|SHA|SHA1)$");
if (keyStorePath != null)
setKeyStorePath(keyStorePath);
}
/**
* Create the SSLContext object and starts the lifecycle
*/
@Override
protected void doStart() throws Exception
{
super.doStart();
synchronized (this)
{
load();
}
}
private void load() throws Exception
{
SSLContext context = _setContext;
KeyStore keyStore = _setKeyStore;
KeyStore trustStore = _setTrustStore;
if (context == null)
{
// Is this an empty factory?
if (keyStore == null && _keyStoreResource == null && trustStore == null && _trustStoreResource == null)
{
TrustManager[] trust_managers = null;
if (isTrustAll())
{
if (LOG.isDebugEnabled())
LOG.debug("No keystore or trust store configured. ACCEPTING UNTRUSTED CERTIFICATES!!!!!");
// Create a trust manager that does not validate certificate chains
trust_managers = TRUST_ALL_CERTS;
}
String algorithm = getSecureRandomAlgorithm();
SecureRandom secureRandom = algorithm == null ? null : SecureRandom.getInstance(algorithm);
context = _sslProvider == null ? SSLContext.getInstance(_sslProtocol) : SSLContext.getInstance(_sslProtocol, _sslProvider);
context.init(null, trust_managers, secureRandom);
}
else
{
if (keyStore == null)
keyStore = loadKeyStore(_keyStoreResource);
if (trustStore == null)
trustStore = loadTrustStore(_trustStoreResource);
Collection<? extends CRL> crls = loadCRL(getCrlPath());
// Look for X.509 certificates to create alias map
if (keyStore != null)
{
for (String alias : Collections.list(keyStore.aliases()))
{
Certificate certificate = keyStore.getCertificate(alias);
if (certificate != null && "X.509".equals(certificate.getType()))
{
X509Certificate x509C = (X509Certificate)certificate;
// Exclude certificates with special uses
if (X509.isCertSign(x509C))
{
if (LOG.isDebugEnabled())
LOG.debug("Skipping " + x509C);
continue;
}
X509 x509 = new X509(alias, x509C);
_aliasX509.put(alias, x509);
if (isValidateCerts())
{
CertificateValidator validator = new CertificateValidator(trustStore, crls);
validator.setMaxCertPathLength(getMaxCertPathLength());
validator.setEnableCRLDP(isEnableCRLDP());
validator.setEnableOCSP(isEnableOCSP());
validator.setOcspResponderURL(getOcspResponderURL());
validator.validate(keyStore, x509C); // TODO what about truststore?
}
LOG.info("x509={} for {}", x509, this);
for (String h : x509.getHosts())
_certHosts.put(h, x509);
for (String w : x509.getWilds())
_certWilds.put(w, x509);
}
}
}
// Instantiate key and trust managers
KeyManager[] keyManagers = getKeyManagers(keyStore);
TrustManager[] trustManagers = getTrustManagers(trustStore, crls);
// Initialize context
SecureRandom secureRandom = (_secureRandomAlgorithm == null) ? null : SecureRandom.getInstance(_secureRandomAlgorithm);
context = _sslProvider == null ? SSLContext.getInstance(_sslProtocol) : SSLContext.getInstance(_sslProtocol, _sslProvider);
context.init(keyManagers, trustManagers, secureRandom);
}
}
// Initialize cache
SSLSessionContext serverContext = context.getServerSessionContext();
if (serverContext != null)
{
if (getSslSessionCacheSize() > -1)
serverContext.setSessionCacheSize(getSslSessionCacheSize());
if (getSslSessionTimeout() > -1)
serverContext.setSessionTimeout(getSslSessionTimeout());
}
// select the protocols and ciphers
SSLParameters enabled = context.getDefaultSSLParameters();
SSLParameters supported = context.getSupportedSSLParameters();
selectCipherSuites(enabled.getCipherSuites(), supported.getCipherSuites());
selectProtocols(enabled.getProtocols(), supported.getProtocols());
_factory = new Factory(keyStore, trustStore, context);
if (LOG.isDebugEnabled())
{
LOG.debug("Selected Protocols {} of {}", Arrays.asList(_selectedProtocols), Arrays.asList(supported.getProtocols()));
LOG.debug("Selected Ciphers {} of {}", Arrays.asList(_selectedCipherSuites), Arrays.asList(supported.getCipherSuites()));
}
}
@Override
public String dump()
{
return ContainerLifeCycle.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
out.append(String.valueOf(this)).append(" trustAll=").append(Boolean.toString(_trustAll)).append(System.lineSeparator());
try
{
/* Use a pristine SSLEngine (not one from this SslContextFactory).
* This will allow for proper detection and identification
* of JRE/lib/security/java.security level disabled features
*/
SSLEngine sslEngine = SSLContext.getDefault().createSSLEngine();
List<Object> selections = new ArrayList<>();
// protocols
selections.add(new SslSelectionDump("Protocol",
sslEngine.getSupportedProtocols(),
sslEngine.getEnabledProtocols(),
getExcludeProtocols(),
getIncludeProtocols()));
// ciphers
selections.add(new SslSelectionDump("Cipher Suite",
sslEngine.getSupportedCipherSuites(),
sslEngine.getEnabledCipherSuites(),
getExcludeCipherSuites(),
getIncludeCipherSuites()));
ContainerLifeCycle.dump(out, indent, selections);
}
catch (NoSuchAlgorithmException ignore)
{
LOG.ignore(ignore);
}
}
@Override
protected void doStop() throws Exception
{
synchronized (this)
{
unload();
}
super.doStop();
}
private void unload()
{
_factory = null;
_selectedProtocols = null;
_selectedCipherSuites = null;
_aliasX509.clear();
_certHosts.clear();
_certWilds.clear();
}
public String[] getSelectedProtocols()
{
return Arrays.copyOf(_selectedProtocols, _selectedProtocols.length);
}
public String[] getSelectedCipherSuites()
{
return Arrays.copyOf(_selectedCipherSuites, _selectedCipherSuites.length);
}
public Comparator<String> getCipherComparator()
{
return _cipherComparator;
}
public void setCipherComparator(Comparator<String> cipherComparator)
{
if (cipherComparator != null)
setUseCipherSuitesOrder(true);
_cipherComparator = cipherComparator;
}
public Set<String> getAliases()
{
return Collections.unmodifiableSet(_aliasX509.keySet());
}
public X509 getX509(String alias)
{
return _aliasX509.get(alias);
}
/**
* @return The array of protocol names to exclude from
* {@link SSLEngine#setEnabledProtocols(String[])}
*/
public String[] getExcludeProtocols()
{
return _excludeProtocols.toArray(new String[0]);
}
/**
* @param protocols The array of protocol names to exclude from
* {@link SSLEngine#setEnabledProtocols(String[])}
*/
public void setExcludeProtocols(String... protocols)
{
_excludeProtocols.clear();
_excludeProtocols.addAll(Arrays.asList(protocols));
}
/**
* @param protocol Protocol names to add to {@link SSLEngine#setEnabledProtocols(String[])}
*/
public void addExcludeProtocols(String... protocol)
{
_excludeProtocols.addAll(Arrays.asList(protocol));
}
/**
* @return The array of protocol names to include in
* {@link SSLEngine#setEnabledProtocols(String[])}
*/
public String[] getIncludeProtocols()
{
return _includeProtocols.toArray(new String[0]);
}
/**
* @param protocols The array of protocol names to include in
* {@link SSLEngine#setEnabledProtocols(String[])}
*/
public void setIncludeProtocols(String... protocols)
{
_includeProtocols.clear();
_includeProtocols.addAll(Arrays.asList(protocols));
}
/**
* @return The array of cipher suite names to exclude from
* {@link SSLEngine#setEnabledCipherSuites(String[])}
*/
public String[] getExcludeCipherSuites()
{
return _excludeCipherSuites.toArray(new String[0]);
}
/**
* You can either use the exact cipher suite name or a a regular expression.
*
* @param cipherSuites The array of cipher suite names to exclude from
* {@link SSLEngine#setEnabledCipherSuites(String[])}
*/
public void setExcludeCipherSuites(String... cipherSuites)
{
_excludeCipherSuites.clear();
_excludeCipherSuites.addAll(Arrays.asList(cipherSuites));
}
/**
* @param cipher Cipher names to add to {@link SSLEngine#setEnabledCipherSuites(String[])}
*/
public void addExcludeCipherSuites(String... cipher)
{
_excludeCipherSuites.addAll(Arrays.asList(cipher));
}
/**
* @return The array of cipher suite names to include in
* {@link SSLEngine#setEnabledCipherSuites(String[])}
*/
public String[] getIncludeCipherSuites()
{
return _includeCipherSuites.toArray(new String[0]);
}
/**
* You can either use the exact cipher suite name or a a regular expression.
*
* @param cipherSuites The array of cipher suite names to include in
* {@link SSLEngine#setEnabledCipherSuites(String[])}
*/
public void setIncludeCipherSuites(String... cipherSuites)
{
_includeCipherSuites.clear();
_includeCipherSuites.addAll(Arrays.asList(cipherSuites));
}
public boolean isUseCipherSuitesOrder()
{
return _useCipherSuitesOrder;
}
public void setUseCipherSuitesOrder(boolean useCipherSuitesOrder)
{
_useCipherSuitesOrder = useCipherSuitesOrder;
}
/**
* @return The file or URL of the SSL Key store.
*/
public String getKeyStorePath()
{
return _keyStoreResource.toString();
}
/**
* @param keyStorePath The file or URL of the SSL Key store.
*/
public void setKeyStorePath(String keyStorePath)
{
try
{
_keyStoreResource = Resource.newResource(keyStorePath);
}
catch (MalformedURLException e)
{
throw new IllegalArgumentException(e);
}
}
/**
* @return The provider of the key store
*/
public String getKeyStoreProvider()
{
return _keyStoreProvider;
}
/**
* @param keyStoreProvider The provider of the key store
*/
public void setKeyStoreProvider(String keyStoreProvider)
{
_keyStoreProvider = keyStoreProvider;
}
/**
* @return The type of the key store (default "JKS")
*/
public String getKeyStoreType()
{
return (_keyStoreType);
}
/**
* @param keyStoreType The type of the key store (default "JKS")
*/
public void setKeyStoreType(String keyStoreType)
{
_keyStoreType = keyStoreType;
}
/**
* @return Alias of SSL certificate for the connector
*/
public String getCertAlias()
{
return _certAlias;
}
/**
* Set the default certificate Alias.
* <p>This can be used if there are multiple non-SNI certificates
* to specify the certificate that should be used, or with SNI
* certificates to set a certificate to try if no others match
* </p>
*
* @param certAlias Alias of SSL certificate for the connector
*/
public void setCertAlias(String certAlias)
{
_certAlias = certAlias;
}
/**
* @param trustStorePath The file name or URL of the trust store location
*/
public void setTrustStorePath(String trustStorePath)
{
try
{
_trustStoreResource = Resource.newResource(trustStorePath);
}
catch (MalformedURLException e)
{
throw new IllegalArgumentException(e);
}
}
/**
* @return The provider of the trust store
*/
public String getTrustStoreProvider()
{
return _trustStoreProvider;
}
/**
* @param trustStoreProvider The provider of the trust store
*/
public void setTrustStoreProvider(String trustStoreProvider)
{
_trustStoreProvider = trustStoreProvider;
}
/**
* @return The type of the trust store (default "JKS")
*/
public String getTrustStoreType()
{
return _trustStoreType;
}
/**
* @param trustStoreType The type of the trust store (default "JKS")
*/
public void setTrustStoreType(String trustStoreType)
{
_trustStoreType = trustStoreType;
}
/**
* @return True if SSL needs client authentication.
* @see SSLEngine#getNeedClientAuth()
*/
public boolean getNeedClientAuth()
{
return _needClientAuth;
}
/**
* @param needClientAuth True if SSL needs client authentication.
* @see SSLEngine#getNeedClientAuth()
*/
public void setNeedClientAuth(boolean needClientAuth)
{
_needClientAuth = needClientAuth;
}
/**
* @return True if SSL wants client authentication.
* @see SSLEngine#getWantClientAuth()
*/
public boolean getWantClientAuth()
{
return _wantClientAuth;
}
/**
* @param wantClientAuth True if SSL wants client authentication.
* @see SSLEngine#getWantClientAuth()
*/
public void setWantClientAuth(boolean wantClientAuth)
{
_wantClientAuth = wantClientAuth;
}
/**
* @return true if SSL certificate has to be validated
*/
public boolean isValidateCerts()
{
return _validateCerts;
}
/**
* @param validateCerts true if SSL certificates have to be validated
*/
public void setValidateCerts(boolean validateCerts)
{
_validateCerts = validateCerts;
}
/**
* @return true if SSL certificates of the peer have to be validated
*/
public boolean isValidatePeerCerts()
{
return _validatePeerCerts;
}
/**
* @param validatePeerCerts true if SSL certificates of the peer have to be validated
*/
public void setValidatePeerCerts(boolean validatePeerCerts)
{
_validatePeerCerts = validatePeerCerts;
}
/**
* @param password The password for the key store. If null is passed and
* a keystore is set, then
* the {@link Password#getPassword(String, String, String)} is used to
* obtain a password either from the {@value #PASSWORD_PROPERTY}
* System property or by prompting for manual entry.
*/
public void setKeyStorePassword(String password)
{
if (password == null)
{
if (_keyStoreResource != null)
_keyStorePassword = Password.getPassword(PASSWORD_PROPERTY, null, null);
else
_keyStorePassword = null;
}
else
{
_keyStorePassword = new Password(password);
}
}
/**
* @param password The password (if any) for the specific key within the key store.
* If null is passed and the {@value #KEYPASSWORD_PROPERTY} system property is set,
* then the {@link Password#getPassword(String, String, String)} is used to
* obtain a password from the {@value #KEYPASSWORD_PROPERTY} system property.
*/
public void setKeyManagerPassword(String password)
{
if (password == null)
{
if (System.getProperty(KEYPASSWORD_PROPERTY) != null)
_keyManagerPassword = Password.getPassword(KEYPASSWORD_PROPERTY, null, null);
else
_keyManagerPassword = null;
}
else
{
_keyManagerPassword = new Password(password);
}
}
/**
* @param password The password for the trust store. If null is passed and a truststore is set
* that is different from the keystore, then
* the {@link Password#getPassword(String, String, String)} is used to
* obtain a password either from the {@value #PASSWORD_PROPERTY}
* System property or by prompting for manual entry.
*/
public void setTrustStorePassword(String password)
{
if (password == null)
{
if (_trustStoreResource != null && !_trustStoreResource.equals(_keyStoreResource))
_trustStorePassword = Password.getPassword(PASSWORD_PROPERTY, null, null);
else
_trustStorePassword = null;
}
else
{
_trustStorePassword = new Password(password);
}
}
/**
* @return The SSL provider name, which if set is passed to
* {@link SSLContext#getInstance(String, String)}
*/
public String getProvider()
{
return _sslProvider;
}
/**
* @param provider The SSL provider name, which if set is passed to
* {@link SSLContext#getInstance(String, String)}
*/
public void setProvider(String provider)
{
_sslProvider = provider;
}
/**
* @return The SSL protocol (default "TLS") passed to
* {@link SSLContext#getInstance(String, String)}
*/
public String getProtocol()
{
return _sslProtocol;
}
/**
* @param protocol The SSL protocol (default "TLS") passed to
* {@link SSLContext#getInstance(String, String)}
*/
public void setProtocol(String protocol)
{
_sslProtocol = protocol;
}
/**
* @return The algorithm name, which if set is passed to
* {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to
* {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
*/
public String getSecureRandomAlgorithm()
{
return _secureRandomAlgorithm;
}
/**
* @param algorithm The algorithm name, which if set is passed to
* {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to
* {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
*/
public void setSecureRandomAlgorithm(String algorithm)
{
_secureRandomAlgorithm = algorithm;
}
/**
* @deprecated use {@link #getKeyManagerFactoryAlgorithm()} instead
*/
@Deprecated
public String getSslKeyManagerFactoryAlgorithm()
{
return getKeyManagerFactoryAlgorithm();
}
/**
* @deprecated use {@link #setKeyManagerFactoryAlgorithm(String)} instead
*/
@Deprecated
public void setSslKeyManagerFactoryAlgorithm(String algorithm)
{
setKeyManagerFactoryAlgorithm(algorithm);
}
/**
* @return The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
*/
public String getKeyManagerFactoryAlgorithm()
{
return _keyManagerFactoryAlgorithm;
}
/**
* @param algorithm The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
*/
public void setKeyManagerFactoryAlgorithm(String algorithm)
{
_keyManagerFactoryAlgorithm = algorithm;
}
/**
* @return The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
*/
public String getTrustManagerFactoryAlgorithm()
{
return _trustManagerFactoryAlgorithm;
}
/**
* @return True if all certificates should be trusted if there is no KeyStore or TrustStore
*/
public boolean isTrustAll()
{
return _trustAll;
}
/**
* @param trustAll True if all certificates should be trusted if there is no KeyStore or TrustStore
*/
public void setTrustAll(boolean trustAll)
{
_trustAll = trustAll;
if (trustAll)
setEndpointIdentificationAlgorithm(null);
}
/**
* @param algorithm The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
* Use the string "TrustAll" to install a trust manager that trusts all.
*/
public void setTrustManagerFactoryAlgorithm(String algorithm)
{
_trustManagerFactoryAlgorithm = algorithm;
}
/**
* @return whether TLS renegotiation is allowed (true by default)
*/
public boolean isRenegotiationAllowed()
{
return _renegotiationAllowed;
}
/**
* @param renegotiationAllowed whether TLS renegotiation is allowed
*/
public void setRenegotiationAllowed(boolean renegotiationAllowed)
{
_renegotiationAllowed = renegotiationAllowed;
}
/**
* @return The number of renegotions allowed for this connection. When the limit
* is 0 renegotiation will be denied. If the limit is less than 0 then no limit is applied.
*/
public int getRenegotiationLimit()
{
return _renegotiationLimit;
}
/**
* @param renegotiationLimit The number of renegotions allowed for this connection.
* When the limit is 0 renegotiation will be denied. If the limit is less than 0 then no limit is applied.
* Default 5.
*/
public void setRenegotiationLimit(int renegotiationLimit)
{
_renegotiationLimit = renegotiationLimit;
}
/**
* @return Path to file that contains Certificate Revocation List
*/
public String getCrlPath()
{
return _crlPath;
}
/**
* @param crlPath Path to file that contains Certificate Revocation List
*/
public void setCrlPath(String crlPath)
{
_crlPath = crlPath;
}
/**
* @return Maximum number of intermediate certificates in
* the certification path (-1 for unlimited)
*/
public int getMaxCertPathLength()
{
return _maxCertPathLength;
}
/**
* @param maxCertPathLength maximum number of intermediate certificates in
* the certification path (-1 for unlimited)
*/
public void setMaxCertPathLength(int maxCertPathLength)
{
_maxCertPathLength = maxCertPathLength;
}
/**
* @return The SSLContext
*/
public SSLContext getSslContext()
{
if (!isStarted())
return _setContext;
synchronized (this)
{
return _factory._context;
}
}
/**
* @param sslContext Set a preconfigured SSLContext
*/
public void setSslContext(SSLContext sslContext)
{
_setContext = sslContext;
}
/**
* @return the endpoint identification algorithm
*/
public String getEndpointIdentificationAlgorithm()
{
return _endpointIdentificationAlgorithm;
}
/**
* When set to "HTTPS" hostname verification will be enabled
*
* @param endpointIdentificationAlgorithm Set the endpointIdentificationAlgorithm
*/
public void setEndpointIdentificationAlgorithm(String endpointIdentificationAlgorithm)
{
_endpointIdentificationAlgorithm = endpointIdentificationAlgorithm;
}
/**
* Override this method to provide alternate way to load a keystore.
*
* @param resource the resource to load the keystore from
* @return the key store instance
* @throws Exception if the keystore cannot be loaded
*/
protected KeyStore loadKeyStore(Resource resource) throws Exception
{
String storePassword = _keyStorePassword == null ? null : _keyStorePassword.toString();
return CertificateUtils.getKeyStore(resource, getKeyStoreType(), getKeyStoreProvider(), storePassword);
}
/**
* Override this method to provide alternate way to load a truststore.
*
* @param resource the resource to load the truststore from
* @return the key store instance
* @throws Exception if the truststore cannot be loaded
*/
protected KeyStore loadTrustStore(Resource resource) throws Exception
{
String type = getTrustStoreType();
String provider = getTrustStoreProvider();
String passwd = _trustStorePassword == null ? null : _trustStorePassword.toString();
if (resource == null || resource.equals(_keyStoreResource))
{
resource = _keyStoreResource;
if (type == null)
type = _keyStoreType;
if (provider == null)
provider = _keyStoreProvider;
if (passwd == null)
passwd = _keyStorePassword == null ? null : _keyStorePassword.toString();
}
return CertificateUtils.getKeyStore(resource, type, provider, passwd);
}
/**
* Loads certificate revocation list (CRL) from a file.
* <p>
* Required for integrations to be able to override the mechanism used to
* load CRL in order to provide their own implementation.
*
* @param crlPath path of certificate revocation list file
* @return Collection of CRL's
* @throws Exception if the certificate revocation list cannot be loaded
*/
protected Collection<? extends CRL> loadCRL(String crlPath) throws Exception
{
return CertificateUtils.loadCRL(crlPath);
}
protected KeyManager[] getKeyManagers(KeyStore keyStore) throws Exception
{
KeyManager[] managers = null;
if (keyStore != null)
{
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(getKeyManagerFactoryAlgorithm());
keyManagerFactory.init(keyStore, _keyManagerPassword == null ? (_keyStorePassword == null ? null : _keyStorePassword.toString().toCharArray()) : _keyManagerPassword.toString().toCharArray());
managers = keyManagerFactory.getKeyManagers();
if (managers != null)
{
String alias = getCertAlias();
if (alias != null)
{
for (int idx = 0; idx < managers.length; idx++)
{
if (managers[idx] instanceof X509ExtendedKeyManager)
managers[idx] = new AliasedX509ExtendedKeyManager((X509ExtendedKeyManager)managers[idx], alias);
}
}
if (!_certHosts.isEmpty() || !_certWilds.isEmpty())
{
for (int idx = 0; idx < managers.length; idx++)
{
if (managers[idx] instanceof X509ExtendedKeyManager)
managers[idx] = new SniX509ExtendedKeyManager((X509ExtendedKeyManager)managers[idx]);
}
}
}
}
if (LOG.isDebugEnabled())
LOG.debug("managers={} for {}", managers, this);
return managers;
}
protected TrustManager[] getTrustManagers(KeyStore trustStore, Collection<? extends CRL> crls) throws Exception
{
TrustManager[] managers = null;
if (trustStore != null)
{
// Revocation checking is only supported for PKIX algorithm
if (isValidatePeerCerts() && "PKIX".equalsIgnoreCase(getTrustManagerFactoryAlgorithm()))
{
PKIXBuilderParameters pbParams = new PKIXBuilderParameters(trustStore, new X509CertSelector());
// Set maximum certification path length
pbParams.setMaxPathLength(_maxCertPathLength);
// Make sure revocation checking is enabled
pbParams.setRevocationEnabled(true);
if (crls != null && !crls.isEmpty())
{
pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(crls)));
}
if (_enableCRLDP)
{
// Enable Certificate Revocation List Distribution Points (CRLDP) support
System.setProperty("com.sun.security.enableCRLDP", "true");
}
if (_enableOCSP)
{
// Enable On-Line Certificate Status Protocol (OCSP) support
Security.setProperty("ocsp.enable", "true");
if (_ocspResponderURL != null)
{
// Override location of OCSP Responder
Security.setProperty("ocsp.responderURL", _ocspResponderURL);
}
}
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm);
trustManagerFactory.init(new CertPathTrustManagerParameters(pbParams));
managers = trustManagerFactory.getTrustManagers();
}
else
{
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm);
trustManagerFactory.init(trustStore);
managers = trustManagerFactory.getTrustManagers();
}
}
return managers;
}
/**
* Select protocols to be used by the connector
* based on configured inclusion and exclusion lists
* as well as enabled and supported protocols.
*
* @param enabledProtocols Array of enabled protocols
* @param supportedProtocols Array of supported protocols
*/
public void selectProtocols(String[] enabledProtocols, String[] supportedProtocols)
{
Set<String> selected_protocols = new LinkedHashSet<>();
// Set the starting protocols - either from the included or enabled list
if (!_includeProtocols.isEmpty())
{
// Use only the supported included protocols
for (String protocol : _includeProtocols)
{
if (Arrays.asList(supportedProtocols).contains(protocol))
selected_protocols.add(protocol);
else
LOG.info("Protocol {} not supported in {}", protocol, Arrays.asList(supportedProtocols));
}
}
else
selected_protocols.addAll(Arrays.asList(enabledProtocols));
// Remove any excluded protocols
selected_protocols.removeAll(_excludeProtocols);
if (selected_protocols.isEmpty())
LOG.warn("No selected protocols from {}", Arrays.asList(supportedProtocols));
_selectedProtocols = selected_protocols.toArray(new String[0]);
}
/**
* Select cipher suites to be used by the connector
* based on configured inclusion and exclusion lists
* as well as enabled and supported cipher suite lists.
*
* @param enabledCipherSuites Array of enabled cipher suites
* @param supportedCipherSuites Array of supported cipher suites
*/
protected void selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites)
{
List<String> selected_ciphers = new ArrayList<>();
// Set the starting ciphers - either from the included or enabled list
if (_includeCipherSuites.isEmpty())
selected_ciphers.addAll(Arrays.asList(enabledCipherSuites));
else
processIncludeCipherSuites(supportedCipherSuites, selected_ciphers);
removeExcludedCipherSuites(selected_ciphers);
if (selected_ciphers.isEmpty())
LOG.warn("No supported ciphers from {}", Arrays.asList(supportedCipherSuites));
Comparator<String> comparator = getCipherComparator();
if (comparator != null)
{
if (LOG.isDebugEnabled())
LOG.debug("Sorting selected ciphers with {}", comparator);
Collections.sort(selected_ciphers, comparator);
}
_selectedCipherSuites = selected_ciphers.toArray(new String[0]);
}
protected void processIncludeCipherSuites(String[] supportedCipherSuites, List<String> selected_ciphers)
{
for (String cipherSuite : _includeCipherSuites)
{
Pattern p = Pattern.compile(cipherSuite);
boolean added = false;
for (String supportedCipherSuite : supportedCipherSuites)
{
Matcher m = p.matcher(supportedCipherSuite);
if (m.matches())
{
added = true;
selected_ciphers.add(supportedCipherSuite);
}
}
if (!added)
LOG.info("No Cipher matching '{}' is supported", cipherSuite);
}
}
protected void removeExcludedCipherSuites(List<String> selected_ciphers)
{
for (String excludeCipherSuite : _excludeCipherSuites)
{
Pattern excludeCipherPattern = Pattern.compile(excludeCipherSuite);
for (Iterator<String> i = selected_ciphers.iterator(); i.hasNext(); )
{
String selectedCipherSuite = i.next();
Matcher m = excludeCipherPattern.matcher(selectedCipherSuite);
if (m.matches())
i.remove();
}
}
}
/**
* Check if the lifecycle has been started and throw runtime exception
*/
private void checkIsStarted()
{
if (!isStarted())
throw new IllegalStateException("!STARTED: " + this);
}
/**
* @return true if CRL Distribution Points support is enabled
*/
public boolean isEnableCRLDP()
{
return _enableCRLDP;
}
/**
* Enables CRL Distribution Points Support
*
* @param enableCRLDP true - turn on, false - turns off
*/
public void setEnableCRLDP(boolean enableCRLDP)
{
_enableCRLDP = enableCRLDP;
}
/**
* @return true if On-Line Certificate Status Protocol support is enabled
*/
public boolean isEnableOCSP()
{
return _enableOCSP;
}
/**
* Enables On-Line Certificate Status Protocol support
*
* @param enableOCSP true - turn on, false - turn off
*/
public void setEnableOCSP(boolean enableOCSP)
{
_enableOCSP = enableOCSP;
}
/**
* @return Location of the OCSP Responder
*/
public String getOcspResponderURL()
{
return _ocspResponderURL;
}
/**
* Set the location of the OCSP Responder.
*
* @param ocspResponderURL location of the OCSP Responder
*/
public void setOcspResponderURL(String ocspResponderURL)
{
_ocspResponderURL = ocspResponderURL;
}
/**
* Set the key store.
*
* @param keyStore the key store to set
*/
public void setKeyStore(KeyStore keyStore)
{
_setKeyStore = keyStore;
}
public KeyStore getKeyStore()
{
if (!isStarted())
return _setKeyStore;
synchronized (this)
{
return _factory._keyStore;
}
}
/**
* Set the trust store.
*
* @param trustStore the trust store to set
*/
public void setTrustStore(KeyStore trustStore)
{
_setTrustStore = trustStore;
}
public KeyStore getTrustStore()
{
if (!isStarted())
return _setTrustStore;
synchronized (this)
{
return _factory._trustStore;
}
}
/**
* Set the key store resource.
*
* @param resource the key store resource to set
*/
public void setKeyStoreResource(Resource resource)
{
_keyStoreResource = resource;
}
public Resource getKeyStoreResource()
{
return _keyStoreResource;
}
/**
* Set the trust store resource.
*
* @param resource the trust store resource to set
*/
public void setTrustStoreResource(Resource resource)
{
_trustStoreResource = resource;
}
public Resource getTrustStoreResource()
{
return _trustStoreResource;
}
/**
* @return true if SSL Session caching is enabled
*/
public boolean isSessionCachingEnabled()
{
return _sessionCachingEnabled;
}
/**
* Set the flag to enable SSL Session caching.
* If set to true, then the {@link SSLContext#createSSLEngine(String, int)} method is
* used to pass host and port information as a hint for session reuse. Note that
* this is only a hint and session may not be reused. Moreover, the hint is typically
* only used on client side implementations and setting this to false does not
* stop a server from accepting an offered session ID to reuse.
*
* @param enableSessionCaching the value of the flag
*/
public void setSessionCachingEnabled(boolean enableSessionCaching)
{
_sessionCachingEnabled = enableSessionCaching;
}
/**
* Get SSL session cache size.
* Passed directly to {@link SSLSessionContext#setSessionCacheSize(int)}
*
* @return SSL session cache size
*/
public int getSslSessionCacheSize()
{
return _sslSessionCacheSize;
}
/**
* Set SSL session cache size.
* <p>Set the max cache size to be set on {@link SSLSessionContext#setSessionCacheSize(int)}
* when this factory is started.</p>
*
* @param sslSessionCacheSize SSL session cache size to set. A value of -1 (default) uses
* the JVM default, 0 means unlimited and positive number is a max size.
*/
public void setSslSessionCacheSize(int sslSessionCacheSize)
{
_sslSessionCacheSize = sslSessionCacheSize;
}
/**
* Get SSL session timeout.
*
* @return SSL session timeout
*/
public int getSslSessionTimeout()
{
return _sslSessionTimeout;
}
/**
* Set SSL session timeout.
* <p>Set the timeout in seconds to be set on {@link SSLSessionContext#setSessionTimeout(int)}
* when this factory is started.</p>
*
* @param sslSessionTimeout SSL session timeout to set in seconds. A value of -1 (default) uses
* the JVM default, 0 means unlimited and positive number is a timeout in seconds.
*/
public void setSslSessionTimeout(int sslSessionTimeout)
{
_sslSessionTimeout = sslSessionTimeout;
}
public SSLServerSocket newSslServerSocket(String host, int port, int backlog) throws IOException
{
checkIsStarted();
SSLContext context = getSslContext();
SSLServerSocketFactory factory = context.getServerSocketFactory();
SSLServerSocket socket =
(SSLServerSocket)(host == null ?
factory.createServerSocket(port, backlog) :
factory.createServerSocket(port, backlog, InetAddress.getByName(host)));
socket.setSSLParameters(customize(socket.getSSLParameters()));
return socket;
}
public SSLSocket newSslSocket() throws IOException
{
checkIsStarted();
SSLContext context = getSslContext();
SSLSocketFactory factory = context.getSocketFactory();
SSLSocket socket = (SSLSocket)factory.createSocket();
socket.setSSLParameters(customize(socket.getSSLParameters()));
return socket;
}
/**
* Factory method for "scratch" {@link SSLEngine}s, usually only used for retrieving configuration
* information such as the application buffer size or the list of protocols/ciphers.
* <p>
* This method should not be used for creating {@link SSLEngine}s that are used in actual socket
* communication.
*
* @return a new, "scratch" {@link SSLEngine}
*/
public SSLEngine newSSLEngine()
{
checkIsStarted();
SSLContext context = getSslContext();
SSLEngine sslEngine = context.createSSLEngine();
customize(sslEngine);
return sslEngine;
}
/**
* General purpose factory method for creating {@link SSLEngine}s, although creation of
* {@link SSLEngine}s on the server-side should prefer {@link #newSSLEngine(InetSocketAddress)}.
*
* @param host the remote host
* @param port the remote port
* @return a new {@link SSLEngine}
*/
public SSLEngine newSSLEngine(String host, int port)
{
checkIsStarted();
SSLContext context = getSslContext();
SSLEngine sslEngine = isSessionCachingEnabled() ?
context.createSSLEngine(host, port) :
context.createSSLEngine();
customize(sslEngine);
return sslEngine;
}
/**
* Server-side only factory method for creating {@link SSLEngine}s.
* <p>
* If the given {@code address} is null, it is equivalent to {@link #newSSLEngine()}, otherwise
* {@link #newSSLEngine(String, int)} is called.
* <p>
* If {@link #getNeedClientAuth()} is {@code true}, then the host name is passed to
* {@link #newSSLEngine(String, int)}, possibly incurring in a reverse DNS lookup, which takes time
* and may hang the selector (since this method is usually called by the selector thread).
* <p>
* Otherwise, the host address is passed to {@link #newSSLEngine(String, int)} without DNS lookup
* penalties.
* <p>
* Clients that wish to create {@link SSLEngine} instances must use {@link #newSSLEngine(String, int)}.
*
* @param address the remote peer address
* @return a new {@link SSLEngine}
*/
public SSLEngine newSSLEngine(InetSocketAddress address)
{
if (address == null)
return newSSLEngine();
boolean useHostName = getNeedClientAuth();
String hostName = useHostName ? address.getHostName() : address.getAddress().getHostAddress();
return newSSLEngine(hostName, address.getPort());
}
/**
* Customize an SslEngine instance with the configuration of this factory,
* by calling {@link #customize(SSLParameters)}
*
* @param sslEngine the SSLEngine to customize
*/
public void customize(SSLEngine sslEngine)
{
if (LOG.isDebugEnabled())
LOG.debug("Customize {}", sslEngine);
sslEngine.setSSLParameters(customize(sslEngine.getSSLParameters()));
}
/**
* Customize an SslParameters instance with the configuration of this factory.
*
* @param sslParams The parameters to customize
* @return The passed instance of sslParams (returned as a convenience)
*/
public SSLParameters customize(SSLParameters sslParams)
{
sslParams.setEndpointIdentificationAlgorithm(getEndpointIdentificationAlgorithm());
sslParams.setUseCipherSuitesOrder(isUseCipherSuitesOrder());
if (!_certHosts.isEmpty() || !_certWilds.isEmpty())
sslParams.setSNIMatchers(Collections.singletonList(new AliasSNIMatcher()));
if (_selectedCipherSuites != null)
sslParams.setCipherSuites(_selectedCipherSuites);
if (_selectedProtocols != null)
sslParams.setProtocols(_selectedProtocols);
if (getWantClientAuth())
sslParams.setWantClientAuth(true);
if (getNeedClientAuth())
sslParams.setNeedClientAuth(true);
return sslParams;
}
public void reload(Consumer<SslContextFactory> consumer) throws Exception
{
synchronized (this)
{
consumer.accept(this);
unload();
load();
}
}
public static X509Certificate[] getCertChain(SSLSession sslSession)
{
try
{
Certificate[] javaxCerts = sslSession.getPeerCertificates();
if (javaxCerts == null || javaxCerts.length == 0)
return null;
int length = javaxCerts.length;
X509Certificate[] javaCerts = new X509Certificate[length];
java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");
for (int i = 0; i < length; i++)
{
byte bytes[] = javaxCerts[i].getEncoded();
ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
javaCerts[i] = (X509Certificate)cf.generateCertificate(stream);
}
return javaCerts;
}
catch (SSLPeerUnverifiedException pue)
{
return null;
}
catch (Exception e)
{
LOG.warn(Log.EXCEPTION, e);
return null;
}
}
/**
* Given the name of a TLS/SSL cipher suite, return an int representing it effective stream
* cipher key strength. i.e. How much entropy material is in the key material being fed into the
* encryption routines.
* <p>
* This is based on the information on effective key lengths in RFC 2246 - The TLS Protocol
* Version 1.0, Appendix C. CipherSuite definitions:
* <p>
* <pre>
* Effective
* Cipher Type Key Bits
*
* NULL * Stream 0
* IDEA_CBC Block 128
* RC2_CBC_40 * Block 40
* RC4_40 * Stream 40
* RC4_128 Stream 128
* DES40_CBC * Block 40
* DES_CBC Block 56
* 3DES_EDE_CBC Block 168
* </pre>
*
* @param cipherSuite String name of the TLS cipher suite.
* @return int indicating the effective key entropy bit-length.
*/
public static int deduceKeyLength(String cipherSuite)
{
// Roughly ordered from most common to least common.
if (cipherSuite == null)
return 0;
else if (cipherSuite.contains("WITH_AES_256_"))
return 256;
else if (cipherSuite.contains("WITH_RC4_128_"))
return 128;
else if (cipherSuite.contains("WITH_AES_128_"))
return 128;
else if (cipherSuite.contains("WITH_RC4_40_"))
return 40;
else if (cipherSuite.contains("WITH_3DES_EDE_CBC_"))
return 168;
else if (cipherSuite.contains("WITH_IDEA_CBC_"))
return 128;
else if (cipherSuite.contains("WITH_RC2_CBC_40_"))
return 40;
else if (cipherSuite.contains("WITH_DES40_CBC_"))
return 40;
else if (cipherSuite.contains("WITH_DES_CBC_"))
return 56;
else
return 0;
}
@Override
public String toString()
{
return String.format("%s@%x(%s,%s)",
getClass().getSimpleName(),
hashCode(),
_keyStoreResource,
_trustStoreResource);
}
class Factory
{
private final KeyStore _keyStore;
private final KeyStore _trustStore;
private final SSLContext _context;
Factory(KeyStore keyStore, KeyStore trustStore, SSLContext context)
{
super();
_keyStore = keyStore;
_trustStore = trustStore;
_context = context;
}
}
class AliasSNIMatcher extends SNIMatcher
{
private String _host;
private X509 _x509;
AliasSNIMatcher()
{
super(StandardConstants.SNI_HOST_NAME);
}
@Override
public boolean matches(SNIServerName serverName)
{
if (LOG.isDebugEnabled())
LOG.debug("SNI matching for {}", serverName);
if (serverName instanceof SNIHostName)
{
String host = _host = ((SNIHostName)serverName).getAsciiName();
host = StringUtil.asciiToLowerCase(host);
// Try an exact match
_x509 = _certHosts.get(host);
// Else try an exact wild match
if (_x509 == null)
{
_x509 = _certWilds.get(host);
// Else try an 1 deep wild match
if (_x509 == null)
{
int dot = host.indexOf('.');
if (dot >= 0)
{
String domain = host.substring(dot + 1);
_x509 = _certWilds.get(domain);
}
}
}
if (LOG.isDebugEnabled())
LOG.debug("SNI matched {}->{}", host, _x509);
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("SNI no match for {}", serverName);
}
// Return true and allow the KeyManager to accept or reject when choosing a certificate.
// If we don't have a SNI host, or didn't see any certificate aliases,
// just say true as it will either somehow work or fail elsewhere.
return true;
}
public String getHost()
{
return _host;
}
public X509 getX509()
{
return _x509;
}
}
}