org.eclipse.jetty.util.ssl.SslContextFactory Maven / Gradle / Ivy
Show all versions of jetty-util Show documentation
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
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.Socket;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.CRL;
import java.security.cert.CertStore;
import java.security.cert.CertStoreParameters;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.PKIXCertPathChecker;
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.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.net.ssl.CertPathTrustManagerParameters;
import javax.net.ssl.HostnameVerifier;
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.X509ExtendedTrustManager;
import javax.net.ssl.X509TrustManager;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.Resources;
import org.eclipse.jetty.util.security.CertificateUtils;
import org.eclipse.jetty.util.security.CertificateValidator;
import org.eclipse.jetty.util.security.Password;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SslContextFactory is used to configure SSL parameters
* to be used by server and client connectors.
* Use {@link Server} to configure server-side connectors,
* and {@link Client} to configure HTTP or WebSocket clients.
*/
@ManagedObject
public abstract class SslContextFactory extends ContainerLifeCycle implements Dumpable
{
public static final TrustManager[] TRUST_ALL_CERTS = new X509TrustManager[]{new X509ExtendedTrustManagerWrapper(null)};
public static final String DEFAULT_KEYMANAGERFACTORY_ALGORITHM = KeyManagerFactory.getDefaultAlgorithm();
public static final String DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM = TrustManagerFactory.getDefaultAlgorithm();
/**
* 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 static final Logger LOG = LoggerFactory.getLogger(SslContextFactory.class);
private static final Logger LOG_CONFIG = LoggerFactory.getLogger(LOG.getName() + ".config");
private static final Pattern KEY_SIZE_PATTERN = Pattern.compile("_(\\d+)_");
/**
* Default Excluded Protocols List
*/
private static final String[] DEFAULT_EXCLUDED_PROTOCOLS = {"SSL", "SSLv2", "SSLv2Hello", "SSLv3"};
/**
* Default Excluded Cipher Suite List
*/
private static final String[] DEFAULT_EXCLUDED_CIPHER_SUITES = {
// Exclude weak / insecure ciphers
"^.*_(MD5|SHA|SHA1)$",
// Exclude ciphers that don't support forward secrecy
"^TLS_RSA_.*$",
// The following exclusions are present to cleanup known bad cipher
// suites that may be accidentally included via include patterns.
// The default enabled cipher list in Java will not include these
// (but they are available in the supported list).
"^SSL_.*$",
"^.*_NULL_.*$",
"^.*_anon_.*$"
};
private static final String X_509 = "X.509";
private final AutoLock _lock = new AutoLock();
private final Set _excludeProtocols = new LinkedHashSet<>();
private final Set _includeProtocols = new LinkedHashSet<>();
private final Set _excludeCipherSuites = new LinkedHashSet<>();
private final Set _includeCipherSuites = new LinkedHashSet<>();
private final Map _aliasX509 = new HashMap<>();
private final Map _certHosts = new HashMap<>();
private final Map _certWilds = new HashMap<>();
private String[] _selectedProtocols;
private boolean _useCipherSuitesOrder = true;
private Comparator _cipherComparator;
private String[] _selectedCipherSuites;
private Resource _keyStoreResource;
private String _keyStoreProvider;
private String _keyStoreType = "PKCS12";
private String _certAlias;
private Resource _trustStoreResource;
private String _trustStoreProvider;
private String _trustStoreType;
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 = "HTTPS";
private boolean _trustAll;
private boolean _renegotiationAllowed = true;
private int _renegotiationLimit = 5;
private Factory _factory;
private PKIXCertPathChecker _pkixCertPathChecker;
private HostnameVerifier _hostnameVerifier;
private CertificateFactory _x509CertificateFactory;
/**
* Construct an instance of SslContextFactory with the default configuration.
*/
protected SslContextFactory()
{
this(false);
}
/**
* Construct an instance of SslContextFactory that trusts all certificates
*
* @param trustAll whether to blindly trust all certificates
* @see #setTrustAll(boolean)
*/
public SslContextFactory(boolean trustAll)
{
setTrustAll(trustAll);
setExcludeProtocols(DEFAULT_EXCLUDED_PROTOCOLS);
setExcludeCipherSuites(DEFAULT_EXCLUDED_CIPHER_SUITES);
}
/**
* Creates the SSLContext object and starts the lifecycle
*/
@Override
protected void doStart() throws Exception
{
super.doStart();
try (AutoLock l = _lock.lock())
{
load();
}
_x509CertificateFactory = getCertificateFactoryInstance(X_509);
checkConfiguration();
}
protected void checkConfiguration()
{
SSLEngine engine = _factory._context.createSSLEngine();
customize(engine);
SSLParameters supported = engine.getSSLParameters();
checkProtocols(supported);
checkCiphers(supported);
}
protected void checkTrustAll()
{
if (isTrustAll())
LOG_CONFIG.warn("Trusting all certificates configured for {}", this);
}
protected void checkEndPointIdentificationAlgorithm()
{
if (getEndpointIdentificationAlgorithm() == null)
LOG_CONFIG.warn("No Client EndPointIdentificationAlgorithm configured for {}", this);
}
protected void checkProtocols(SSLParameters supported)
{
for (String protocol : supported.getProtocols())
{
for (String excluded : DEFAULT_EXCLUDED_PROTOCOLS)
{
if (excluded.equals(protocol))
LOG_CONFIG.warn("Protocol {} not excluded for {}", protocol, this);
}
}
}
protected void checkCiphers(SSLParameters supported)
{
for (String suite : supported.getCipherSuites())
{
for (String excludedSuiteRegex : DEFAULT_EXCLUDED_CIPHER_SUITES)
{
if (suite.matches(excludedSuiteRegex))
LOG_CONFIG.warn("Weak cipher suite {} enabled for {}", suite, this);
}
}
}
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[] trustManagers = 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
trustManagers = TRUST_ALL_CERTS;
}
context = getSSLContextInstance();
context.init(null, trustManagers, getSecureRandomInstance());
}
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
context = getSSLContextInstance();
context.init(keyManagers, trustManagers, getSecureRandomInstance());
}
}
// 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 Dumpable.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
try
{
SSLEngine sslEngine = SSLContext.getDefault().createSSLEngine();
Dumpable.dumpObjects(out, indent, this, "trustAll=" + _trustAll,
new SslSelectionDump("Protocol",
sslEngine.getSupportedProtocols(),
sslEngine.getEnabledProtocols(),
getExcludeProtocols(),
getIncludeProtocols()),
new SslSelectionDump("Cipher Suite",
sslEngine.getSupportedCipherSuites(),
sslEngine.getEnabledCipherSuites(),
getExcludeCipherSuites(),
getIncludeCipherSuites()));
}
catch (NoSuchAlgorithmException x)
{
LOG.trace("IGNORED", x);
}
}
List selectionDump() throws NoSuchAlgorithmException
{
/* 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 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()));
return selections;
}
@Override
protected void doStop() throws Exception
{
try (AutoLock l = _lock.lock())
{
unload();
}
super.doStop();
}
private void unload()
{
_factory = null;
_selectedProtocols = null;
_selectedCipherSuites = null;
_aliasX509.clear();
_certHosts.clear();
_certWilds.clear();
}
Map aliasCerts()
{
return _aliasX509;
}
Map hostCerts()
{
return _certHosts;
}
Map wildCerts()
{
return _certWilds;
}
@ManagedAttribute(value = "The selected TLS protocol versions", readonly = true)
public String[] getSelectedProtocols()
{
return Arrays.copyOf(_selectedProtocols, _selectedProtocols.length);
}
@ManagedAttribute(value = "The selected cipher suites", readonly = true)
public String[] getSelectedCipherSuites()
{
return Arrays.copyOf(_selectedCipherSuites, _selectedCipherSuites.length);
}
public Comparator getCipherComparator()
{
return _cipherComparator;
}
public void setCipherComparator(Comparator cipherComparator)
{
if (cipherComparator != null)
setUseCipherSuitesOrder(true);
_cipherComparator = cipherComparator;
}
public Set 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[])}
*/
@ManagedAttribute("The excluded TLS protocols")
public String[] getExcludeProtocols()
{
return _excludeProtocols.toArray(new String[0]);
}
/**
* You can either use the exact Protocol name or a a regular expression.
*
* @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));
}
/**
* You can either use the exact Protocol name or a a regular expression.
*
* @param protocol Protocol name patterns to add to {@link SSLEngine#setEnabledProtocols(String[])}
*/
public void addExcludeProtocols(String... protocol)
{
_excludeProtocols.addAll(Arrays.asList(protocol));
}
/**
* @return The array of protocol name patterns to include in
* {@link SSLEngine#setEnabledProtocols(String[])}
*/
@ManagedAttribute("The included TLS protocols")
public String[] getIncludeProtocols()
{
return _includeProtocols.toArray(new String[0]);
}
/**
* You can either use the exact Protocol name or a a regular expression.
*
* @param protocols The array of protocol name patterns 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 name patterns to exclude from
* {@link SSLEngine#setEnabledCipherSuites(String[])}
*/
@ManagedAttribute("The excluded cipher suites")
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));
}
/**
* You can either use the exact Cipher suite name or a a regular expression.
*
* @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[])}
*/
@ManagedAttribute("The included cipher suites")
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));
}
@ManagedAttribute("Whether to respect the cipher suites order")
public boolean isUseCipherSuitesOrder()
{
return _useCipherSuitesOrder;
}
public void setUseCipherSuitesOrder(boolean useCipherSuitesOrder)
{
_useCipherSuitesOrder = useCipherSuitesOrder;
}
/**
* @return The file or URL of the SSL Key store.
*/
@ManagedAttribute("The keyStore path")
public String getKeyStorePath()
{
return Objects.toString(_keyStoreResource, null);
}
/**
* @param keyStorePath The file or URL of the SSL Key store.
*/
public void setKeyStorePath(String keyStorePath)
{
if (StringUtil.isBlank(keyStorePath))
{
// allow user to unset variable
_keyStoreResource = null;
return;
}
Resource res = ResourceFactory.of(this).newResource(keyStorePath);
if (!Resources.isReadableFile(res))
{
_keyStoreResource = null;
throw new IllegalArgumentException("KeyStore Path not accessible: " + keyStorePath);
}
_keyStoreResource = res;
}
/**
* @return The provider of the key store
*/
@ManagedAttribute("The keyStore provider name")
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 "PKCS12")
*/
@ManagedAttribute("The keyStore type")
public String getKeyStoreType()
{
return (_keyStoreType);
}
/**
* @param keyStoreType The type of the key store
*/
public void setKeyStoreType(String keyStoreType)
{
_keyStoreType = keyStoreType;
}
/**
* @return Alias of SSL certificate for the connector
*/
@ManagedAttribute("The certificate alias")
public String getCertAlias()
{
return _certAlias;
}
/**
* Set the default certificate Alias.
* 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
*
*
* @param certAlias Alias of SSL certificate for the connector
*/
public void setCertAlias(String certAlias)
{
_certAlias = certAlias;
}
@ManagedAttribute("The trustStore path")
public String getTrustStorePath()
{
return Objects.toString(_trustStoreResource, null);
}
/**
* @param trustStorePath The file name or URL of the trust store location
*/
public void setTrustStorePath(String trustStorePath)
{
if (StringUtil.isBlank(trustStorePath))
{
// allow user to unset variable
_trustStoreResource = null;
return;
}
Resource res = ResourceFactory.of(this).newResource(trustStorePath);
if (!Resources.isReadableFile(res))
{
_trustStoreResource = null;
throw new IllegalArgumentException("TrustStore Path not accessible: " + trustStorePath);
}
_trustStoreResource = res;
}
/**
* @return The provider of the trust store
*/
@ManagedAttribute("The trustStore provider name")
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
*/
@ManagedAttribute("The trustStore type")
public String getTrustStoreType()
{
return _trustStoreType;
}
/**
* @param trustStoreType The type of the trust store
*/
public void setTrustStoreType(String trustStoreType)
{
_trustStoreType = trustStoreType;
}
/**
* @return true if SSL certificate has to be validated
*/
@ManagedAttribute("Whether certificates are validated")
public boolean isValidateCerts()
{
return _validateCerts;
}
/**
* Set true if SSL certificates have to be validated.
* @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
*/
@ManagedAttribute("Whether peer certificates are validated")
public boolean isValidatePeerCerts()
{
return _validatePeerCerts;
}
/**
* Set true if SSL certificates of the peer have to be validated.
* @param validatePeerCerts true if SSL certificates of the peer have to be validated
*/
public void setValidatePeerCerts(boolean validatePeerCerts)
{
_validatePeerCerts = validatePeerCerts;
}
public String getKeyStorePassword()
{
return _keyStorePassword == null ? null : _keyStorePassword.toString();
}
/**
* @param password The password for the key store. If null is passed and
* a keystore is set, then
* the {@link #getPassword(String)} is used to
* obtain a password either from the {@value #PASSWORD_PROPERTY}
* system property.
*/
public void setKeyStorePassword(String password)
{
_keyStorePassword = password == null ? getPassword(PASSWORD_PROPERTY) : newPassword(password);
}
public String getKeyManagerPassword()
{
return _keyManagerPassword == null ? null : _keyManagerPassword.toString();
}
/**
* @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 #getPassword(String)} is used to
* obtain a password from the {@value #KEYPASSWORD_PROPERTY} system property.
*/
public void setKeyManagerPassword(String password)
{
_keyManagerPassword = password == null ? getPassword(KEYPASSWORD_PROPERTY) : newPassword(password);
}
/**
* @param password The password for the truststore. If null is passed then
* the {@link #getPassword(String)} is used to
* obtain a password from the {@value #PASSWORD_PROPERTY}
* system property.
*/
public void setTrustStorePassword(String password)
{
_trustStorePassword = password == null ? getPassword(PASSWORD_PROPERTY) : newPassword(password);
}
/**
*
* Get the optional Security Provider name.
*
*
* Security Provider name used with:
*
*
* - {@link SecureRandom#getInstance(String, String)}
* - {@link SSLContext#getInstance(String, String)}
* - {@link TrustManagerFactory#getInstance(String, String)}
* - {@link KeyManagerFactory#getInstance(String, String)}
* - {@link CertStore#getInstance(String, CertStoreParameters, String)}
* - {@link java.security.cert.CertificateFactory#getInstance(String, String)}
*
*
* @return The optional Security Provider name.
*/
@ManagedAttribute("The provider name")
public String getProvider()
{
return _sslProvider;
}
/**
*
* Set the optional Security Provider name.
*
*
* Security Provider name used with:
*
*
* - {@link SecureRandom#getInstance(String, String)}
* - {@link SSLContext#getInstance(String, String)}
* - {@link TrustManagerFactory#getInstance(String, String)}
* - {@link KeyManagerFactory#getInstance(String, String)}
* - {@link CertStore#getInstance(String, CertStoreParameters, String)}
* - {@link java.security.cert.CertificateFactory#getInstance(String, String)}
*
*
* @param provider The optional Security Provider name.
*/
public void setProvider(String provider)
{
_sslProvider = provider;
}
/**
* @return The SSL protocol (default "TLS") passed to
* {@link SSLContext#getInstance(String, String)}
*/
@ManagedAttribute("The TLS protocol")
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)}
*/
@ManagedAttribute("The SecureRandom algorithm")
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;
}
/**
* @return The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
*/
@ManagedAttribute("The KeyManagerFactory algorithm")
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}
*/
@ManagedAttribute("The TrustManagerFactory algorithm")
public String getTrustManagerFactoryAlgorithm()
{
return _trustManagerFactoryAlgorithm;
}
/**
* @return True if all certificates should be trusted if there is no KeyStore or TrustStore
*/
@ManagedAttribute("Whether certificates should be trusted even if they are invalid")
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)
*/
@ManagedAttribute("Whether renegotiation is allowed")
public boolean isRenegotiationAllowed()
{
return _renegotiationAllowed;
}
/**
* Set whether TLS renegotiation is allowed.
* @param renegotiationAllowed whether TLS renegotiation is allowed
*/
public void setRenegotiationAllowed(boolean renegotiationAllowed)
{
_renegotiationAllowed = renegotiationAllowed;
}
/**
* @return The number of renegotiations 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.
*/
@ManagedAttribute("The max number of renegotiations allowed")
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
*/
@ManagedAttribute("The path to the certificate revocation list file")
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)
*/
@ManagedAttribute("The maximum number of intermediate certificates")
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;
try (AutoLock l = _lock.lock())
{
if (_factory == null)
throw new IllegalStateException("SslContextFactory reload failed");
return _factory._context;
}
}
/**
* @param sslContext Set a preconfigured SSLContext
*/
public void setSslContext(SSLContext sslContext)
{
_setContext = sslContext;
}
/**
* @return the endpoint identification algorithm
*/
@ManagedAttribute("The endpoint identification algorithm")
public String getEndpointIdentificationAlgorithm()
{
return _endpointIdentificationAlgorithm;
}
/**
* When set to "HTTPS" hostname verification will be enabled.
* Deployments can be vulnerable to a man-in-the-middle attack if a EndpointIdentificationAlgorithm
* is not set.
*
* @param endpointIdentificationAlgorithm Set the endpointIdentificationAlgorithm
* @see #setHostnameVerifier(HostnameVerifier)
*/
public void setEndpointIdentificationAlgorithm(String endpointIdentificationAlgorithm)
{
_endpointIdentificationAlgorithm = endpointIdentificationAlgorithm;
}
public PKIXCertPathChecker getPkixCertPathChecker()
{
return _pkixCertPathChecker;
}
public void setPkixCertPathChecker(PKIXCertPathChecker pkixCertPatchChecker)
{
_pkixCertPathChecker = pkixCertPatchChecker;
}
/**
* 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 = Objects.toString(_keyStorePassword, null);
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 = Objects.toString(getTrustStoreType(), getKeyStoreType());
String provider = Objects.toString(getTrustStoreProvider(), getKeyStoreProvider());
Password passwd = _trustStorePassword;
if (resource == null || resource.equals(_keyStoreResource))
{
resource = _keyStoreResource;
if (passwd == null)
passwd = _keyStorePassword;
}
return CertificateUtils.getKeyStore(resource, type, provider, Objects.toString(passwd, null));
}
/**
* Loads certificate revocation list (CRL) from a file.
*
* 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 = getKeyManagerFactoryInstance();
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 (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 = newPKIXBuilderParameters(trustStore, crls);
TrustManagerFactory trustManagerFactory = getTrustManagerFactoryInstance();
trustManagerFactory.init(new CertPathTrustManagerParameters(pbParams));
managers = trustManagerFactory.getTrustManagers();
}
else
{
TrustManagerFactory trustManagerFactory = getTrustManagerFactoryInstance();
trustManagerFactory.init(trustStore);
managers = trustManagerFactory.getTrustManagers();
}
}
return managers;
}
// @checkstyle-disable-check : AbbreviationAsWordInNameCheck
protected PKIXBuilderParameters newPKIXBuilderParameters(KeyStore trustStore, Collection extends CRL> crls) throws Exception
// @checkstyle-enable-check : AbbreviationAsWordInNameCheck
{
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 (_pkixCertPathChecker != null)
pbParams.addCertPathChecker(_pkixCertPathChecker);
if (crls != null && !crls.isEmpty())
{
pbParams.addCertStore(getCertStoreInstance(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);
}
}
return pbParams;
}
/**
* 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)
{
List selectedProtocols = processIncludeExcludePatterns("Protocols", enabledProtocols, supportedProtocols, _includeProtocols, _excludeProtocols);
if (selectedProtocols.isEmpty())
LOG.warn("No selected Protocols from {}", Arrays.asList(supportedProtocols));
_selectedProtocols = selectedProtocols.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 selectedCiphers = processIncludeExcludePatterns("Cipher Suite", enabledCipherSuites, supportedCipherSuites, _includeCipherSuites, _excludeCipherSuites);
if (selectedCiphers.isEmpty())
LOG.warn("No supported Cipher Suite from {}", Arrays.asList(supportedCipherSuites));
Comparator comparator = getCipherComparator();
if (comparator != null)
{
if (LOG.isDebugEnabled())
LOG.debug("Sorting selected ciphers with {}", comparator);
selectedCiphers.sort(comparator);
}
_selectedCipherSuites = selectedCiphers.toArray(new String[0]);
}
private List processIncludeExcludePatterns(String type, String[] enabled, String[] supported, Set included, Set excluded)
{
List selected = new ArrayList<>();
// Set the starting list - either from the included or enabled list
if (included.isEmpty())
{
selected.addAll(Arrays.asList(enabled));
}
else
{
// process include patterns
for (String includedItem : included)
{
Pattern pattern = Pattern.compile(includedItem);
boolean added = false;
for (String supportedItem : supported)
{
if (pattern.matcher(supportedItem).matches())
{
added = true;
selected.add(supportedItem);
}
}
if (!added)
LOG.info("No {} matching '{}' is supported", type, includedItem);
}
}
// process exclude patterns
for (String excludedItem : excluded)
{
Pattern pattern = Pattern.compile(excludedItem);
selected.removeIf(selectedItem -> pattern.matcher(selectedItem).matches());
}
return selected;
}
/**
* @deprecated no replacement
*/
@Deprecated
protected void processIncludeCipherSuites(String[] supportedCipherSuites, List selectedCiphers)
{
}
/**
* @deprecated no replacement
*/
@Deprecated
protected void removeExcludedCipherSuites(List selectedCiphers)
{
}
/**
* 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
*/
@ManagedAttribute("Whether certificate revocation list distribution points 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
*/
@ManagedAttribute("Whether online 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
*/
@ManagedAttribute("The online certificate status protocol URL")
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;
try (AutoLock l = _lock.lock())
{
if (_factory == null)
throw new IllegalStateException("SslContextFactory reload failed");
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;
try (AutoLock l = _lock.lock())
{
if (_factory == null)
throw new IllegalStateException("SslContextFactory reload failed");
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
*/
@ManagedAttribute("Whether TLS 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
*/
@ManagedAttribute("The maximum TLS session cache size")
public int getSslSessionCacheSize()
{
return _sslSessionCacheSize;
}
/**
* Set SSL session cache size.
* Set the max cache size to be set on {@link SSLSessionContext#setSessionCacheSize(int)}
* when this factory is started.
*
* @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
*/
@ManagedAttribute("The TLS session cache timeout, in seconds")
public int getSslSessionTimeout()
{
return _sslSessionTimeout;
}
/**
* Set SSL session timeout.
* Set the timeout in seconds to be set on {@link SSLSessionContext#setSessionTimeout(int)}
* when this factory is started.
*
* @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;
}
/**
* Get the HostnameVerifier used by a client to verify host names in the server certificate.
* @return the HostnameVerifier used by a client to verify host names in the server certificate
*/
public HostnameVerifier getHostnameVerifier()
{
return _hostnameVerifier;
}
/**
* Sets a {@code HostnameVerifier} used by a client to verify host names in the server certificate.
* The {@code HostnameVerifier} works in conjunction with {@link #setEndpointIdentificationAlgorithm(String)}.
* When {@code endpointIdentificationAlgorithm=="HTTPS"} (the default) the JDK TLS implementation
* checks that the host name indication set by the client matches the host names in the server certificate.
* If this check passes successfully, the {@code HostnameVerifier} is invoked and the application
* can perform additional checks and allow/deny the connection to the server.
* When {@code endpointIdentificationAlgorithm==null} the JDK TLS implementation will not check
* the host names, and any check is therefore performed only by the {@code HostnameVerifier.}
*
* @param hostnameVerifier the HostnameVerifier used by a client to verify host names in the server certificate
*/
public void setHostnameVerifier(HostnameVerifier hostnameVerifier)
{
_hostnameVerifier = hostnameVerifier;
}
/**
* Returns the password object for the given realm.
*
* @param realm the realm
* @return the Password object
*/
protected Password getPassword(String realm)
{
String password = System.getProperty(realm);
return password == null ? null : newPassword(password);
}
/**
* Creates a new Password object.
*
* @param password the password string
* @return the new Password object
*/
public Password newPassword(String password)
{
return new Password(password);
}
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;
}
protected CertificateFactory getCertificateFactoryInstance(String type) throws CertificateException
{
String provider = getProvider();
try
{
if (provider != null)
{
return CertificateFactory.getInstance(type, provider);
}
}
catch (Throwable cause)
{
String msg = String.format("Unable to get CertificateFactory instance for type [%s] on provider [%s], using default", type, provider);
if (LOG.isDebugEnabled())
LOG.debug(msg, cause);
else
LOG.info(msg);
}
return CertificateFactory.getInstance(type);
}
protected CertStore getCertStoreInstance(Collection extends CRL> crls) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException
{
String type = "Collection";
String provider = getProvider();
try
{
if (provider != null)
{
return CertStore.getInstance(type, new CollectionCertStoreParameters(crls), provider);
}
}
catch (Throwable cause)
{
String msg = String.format("Unable to get CertStore instance for type [%s] on provider [%s], using default", type, provider);
if (LOG.isDebugEnabled())
LOG.debug(msg, cause);
else
LOG.info(msg);
}
return CertStore.getInstance(type, new CollectionCertStoreParameters(crls));
}
protected KeyManagerFactory getKeyManagerFactoryInstance() throws NoSuchAlgorithmException
{
String algorithm = getKeyManagerFactoryAlgorithm();
String provider = getProvider();
try
{
if (provider != null)
{
return KeyManagerFactory.getInstance(algorithm, provider);
}
}
catch (Throwable cause)
{
// fall back to non-provider option
String msg = String.format("Unable to get KeyManagerFactory instance for algorithm [%s] on provider [%s], using default", algorithm, provider);
if (LOG.isDebugEnabled())
LOG.debug(msg, cause);
else
LOG.info(msg);
}
return KeyManagerFactory.getInstance(algorithm);
}
protected SecureRandom getSecureRandomInstance() throws NoSuchAlgorithmException
{
String algorithm = getSecureRandomAlgorithm();
if (algorithm != null)
{
String provider = getProvider();
try
{
if (provider != null)
{
return SecureRandom.getInstance(algorithm, provider);
}
}
catch (Throwable cause)
{
String msg = String.format("Unable to get SecureRandom instance for algorithm [%s] on provider [%s], using default", algorithm, provider);
if (LOG.isDebugEnabled())
LOG.debug(msg, cause);
else
LOG.info(msg);
}
return SecureRandom.getInstance(algorithm);
}
return null;
}
protected SSLContext getSSLContextInstance() throws NoSuchAlgorithmException
{
String protocol = getProtocol();
String provider = getProvider();
try
{
if (provider != null)
{
return SSLContext.getInstance(protocol, provider);
}
}
catch (Throwable cause)
{
String msg = String.format("Unable to get SSLContext instance for protocol [%s] on provider [%s], using default", protocol, provider);
if (LOG.isDebugEnabled())
LOG.debug(msg, cause);
else
LOG.info(msg);
}
return SSLContext.getInstance(protocol);
}
protected TrustManagerFactory getTrustManagerFactoryInstance() throws NoSuchAlgorithmException
{
String algorithm = getTrustManagerFactoryAlgorithm();
String provider = getProvider();
try
{
if (provider != null)
{
return TrustManagerFactory.getInstance(algorithm, provider);
}
}
catch (Throwable cause)
{
String msg = String.format("Unable to get TrustManagerFactory instance for algorithm [%s] on provider [%s], using default", algorithm, provider);
if (LOG.isDebugEnabled())
LOG.debug(msg, cause);
else
LOG.info(msg);
}
return TrustManagerFactory.getInstance(algorithm);
}
/**
* 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.
*
* 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.
*
* If the given {@code address} is null, it is equivalent to {@link #newSSLEngine()}, otherwise
* {@link #newSSLEngine(String, int)} is called.
*
* 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();
return newSSLEngine(address.getHostString(), 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(List.of(new AliasSNIMatcher()));
if (_selectedCipherSuites != null)
sslParams.setCipherSuites(_selectedCipherSuites);
if (_selectedProtocols != null)
sslParams.setProtocols(_selectedProtocols);
if (this instanceof Server)
{
Server server = (Server)this;
if (server.getWantClientAuth())
sslParams.setWantClientAuth(true);
if (server.getNeedClientAuth())
sslParams.setNeedClientAuth(true);
}
return sslParams;
}
public void reload(Consumer consumer) throws Exception
{
try (AutoLock l = _lock.lock())
{
consumer.accept(this);
unload();
load();
}
}
/**
* Obtain the X509 Certificate Chain from the provided SSLSession using this
* SslContextFactory's optional Provider specific {@link CertificateFactory}.
*
* @param sslSession the session to use for active peer certificates
* @return the certificate chain
*/
public X509Certificate[] getX509CertChain(SSLSession sslSession)
{
return getX509CertChain(_x509CertificateFactory, sslSession);
}
/**
* Obtain the X509 Certificate Chain from the provided SSLSession using this
* SslContextFactory's optional Provider specific {@link CertificateFactory}.
*
* @param sslSession the session to use for active peer certificates
* @return the certificate chain
*/
public static X509Certificate[] getCertChain(SSLSession sslSession)
{
return getX509CertChain(null, sslSession);
}
private static X509Certificate[] getX509CertChain(CertificateFactory certificateFactory, SSLSession sslSession)
{
try
{
if (certificateFactory == null)
certificateFactory = CertificateFactory.getInstance(X_509);
Certificate[] javaxCerts = sslSession.getPeerCertificates();
if (javaxCerts == null || javaxCerts.length == 0)
return null;
int length = javaxCerts.length;
X509Certificate[] javaCerts = new X509Certificate[length];
for (int i = 0; i < length; i++)
{
byte[] bytes = javaxCerts[i].getEncoded();
ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
javaCerts[i] = (X509Certificate)certificateFactory.generateCertificate(stream);
}
return javaCerts;
}
catch (SSLPeerUnverifiedException pue)
{
return null;
}
catch (Exception e)
{
LOG.warn("Unable to get X509CertChain", 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.
*
* This is based on the information on effective key lengths in RFC 2246 - The TLS Protocol
* Version 1.0, Appendix C. CipherSuite definitions:
*
* 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
*
*
* For unknown ciphers, any substring of digits bounded by '_' is taken as the key length.
*
* @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;
if (cipherSuite.contains("WITH_3DES_EDE_CBC_"))
return 168;
if (cipherSuite.contains("WITH_IDEA_CBC_"))
return 128;
else if (cipherSuite.contains("WITH_DES40_CBC_"))
return 40;
else if (cipherSuite.contains("WITH_DES_CBC_"))
return 56;
Matcher matcher = KEY_SIZE_PATTERN.matcher(cipherSuite);
if (matcher.find())
{
String keyLengthString = matcher.group(1);
try
{
return Integer.parseInt(keyLengthString);
}
catch (NumberFormatException e)
{
if (LOG.isTraceEnabled())
LOG.trace("unknown key length", e);
}
}
return 0;
}
public void validateCerts(X509Certificate[] certs) throws Exception
{
KeyStore trustStore = loadTrustStore(_trustStoreResource);
Collection extends CRL> crls = loadCRL(_crlPath);
CertificateValidator validator = new CertificateValidator(trustStore, crls);
validator.validate(certs);
}
@Override
public String toString()
{
return String.format("%s@%x[provider=%s,keyStore=%s,trustStore=%s]",
getClass().getSimpleName(),
hashCode(),
_sslProvider,
_keyStoreResource,
_trustStoreResource);
}
private static class Factory
{
private final KeyStore _keyStore;
private final KeyStore _trustStore;
private final SSLContext _context;
private Factory(KeyStore keyStore, KeyStore trustStore, SSLContext context)
{
_keyStore = keyStore;
_trustStore = trustStore;
_context = context;
}
}
static class AliasSNIMatcher extends SNIMatcher
{
private String _host;
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)
{
_host = StringUtil.asciiToLowerCase(((SNIHostName)serverName).getAsciiName());
if (LOG.isDebugEnabled())
LOG.debug("SNI host name {}", _host);
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("No SNI host name 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 static class Client extends SslContextFactory
{
private SniProvider sniProvider = (sslEngine, serverNames) -> serverNames;
public Client()
{
this(false);
}
public Client(boolean trustAll)
{
super(trustAll);
}
@Override
protected void checkConfiguration()
{
checkTrustAll();
checkEndPointIdentificationAlgorithm();
super.checkConfiguration();
}
@Override
public void customize(SSLEngine sslEngine)
{
SSLParameters sslParameters = sslEngine.getSSLParameters();
List serverNames = sslParameters.getServerNames();
if (serverNames == null)
serverNames = Collections.emptyList();
List newServerNames = getSNIProvider().apply(sslEngine, serverNames);
if (newServerNames != null && newServerNames != serverNames)
{
sslParameters.setServerNames(newServerNames);
sslEngine.setSSLParameters(sslParameters);
}
super.customize(sslEngine);
}
/**
* @return the SNI provider used to customize the SNI
*/
public SniProvider getSNIProvider()
{
return sniProvider;
}
/**
* @param sniProvider the SNI provider used to customize the SNI
*/
public void setSNIProvider(SniProvider sniProvider)
{
this.sniProvider = Objects.requireNonNull(sniProvider);
}
/**
* A provider for SNI names to send to the server during the TLS handshake.
* By default, the OpenJDK TLS implementation does not send SNI names when
* they are IP addresses, following what currently specified in
* TLS 1.3,
* or when they are non-domain strings such as {@code "localhost"}.
* If you need to send custom SNI, such as a non-domain SNI or an IP address SNI,
* you can set your own SNI provider or use {@link #NON_DOMAIN_SNI_PROVIDER}.
*/
@FunctionalInterface
public interface SniProvider
{
/**
* An SNI provider that, if the given {@code serverNames} list is empty,
* retrieves the host via {@link SSLEngine#getPeerHost()}, converts it to
* ASCII bytes, and sends it as SNI.
* This allows to send non-domain SNI such as {@code "localhost"} or
* IP addresses.
*/
public static final SniProvider NON_DOMAIN_SNI_PROVIDER = Client::getSniServerNames;
/**
* Provides the SNI names to send to the server.
* Currently, RFC 6066 allows for different types of server names,
* but defines only one of type "host_name".
* As such, the input {@code serverNames} list and the list to be returned
* contain at most one element.
*
* @param sslEngine the SSLEngine that processes the TLS handshake
* @param serverNames the non-null immutable list of server names computed by implementation
* @return either the same {@code serverNames} list passed as parameter, or a new list
* containing the server names to send to the server
*/
public List apply(SSLEngine sslEngine, List serverNames);
}
private static List getSniServerNames(SSLEngine sslEngine, List serverNames)
{
if (serverNames.isEmpty())
{
String host = sslEngine.getPeerHost();
if (host != null)
{
// Must use the byte[] constructor, because the character ':' is forbidden when
// using the String constructor (but typically present in IPv6 addresses).
// Since Java 17, only letter|digit|hyphen characters are allowed, even by the byte[] constructor.
return List.of(new SNIHostName(host.getBytes(StandardCharsets.US_ASCII)));
}
}
return serverNames;
}
}
@ManagedObject
public static class Server extends SslContextFactory implements SniX509ExtendedKeyManager.SniSelector
{
public static final String SNI_HOST = "org.eclipse.jetty.util.ssl.sniHost";
private boolean _needClientAuth;
private boolean _wantClientAuth;
private boolean _sniRequired;
private SniX509ExtendedKeyManager.SniSelector _sniSelector;
public Server()
{
setEndpointIdentificationAlgorithm(null);
}
/**
* @return True if SSL needs client authentication.
* @see SSLEngine#getNeedClientAuth()
*/
@ManagedAttribute("Whether client authentication is needed")
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()
*/
@ManagedAttribute("Whether client authentication is wanted")
public boolean getWantClientAuth()
{
return _wantClientAuth;
}
/**
* @param wantClientAuth True if SSL wants client authentication.
* @see SSLEngine#getWantClientAuth()
*/
public void setWantClientAuth(boolean wantClientAuth)
{
_wantClientAuth = wantClientAuth;
}
/**
* Returns whether an SNI match is required when choosing the alias that
* identifies the certificate to send to the client.
* The exact logic to choose an alias given the SNI is configurable via
* {@link #setSNISelector(SniX509ExtendedKeyManager.SniSelector)}.
* The default implementation is {@link #sniSelect(String, Principal[], SSLSession, String, Collection)}
* and if SNI is not required it will delegate the TLS implementation to
* choose an alias (typically the first alias in the KeyStore).
* Note that if a non SNI handshake is accepted, requests may still be rejected
* at the HTTP level for incorrect SNI (see SecureRequestCustomizer).
*
* @return whether an SNI match is required when choosing the alias that identifies the certificate
*/
@ManagedAttribute("Whether the TLS handshake is rejected if there is no SNI host match")
public boolean isSniRequired()
{
return _sniRequired;
}
/**
* Sets whether an SNI match is required when choosing the alias that
* identifies the certificate to send to the client.
* This setting may have no effect if {@link #sniSelect(String, Principal[], SSLSession, String, Collection)} is
* overridden or a custom function is passed to {@link #setSNISelector(SniX509ExtendedKeyManager.SniSelector)}.
*
* @param sniRequired whether an SNI match is required when choosing the alias that identifies the certificate
*/
public void setSniRequired(boolean sniRequired)
{
_sniRequired = sniRequired;
}
@Override
protected KeyManager[] getKeyManagers(KeyStore keyStore) throws Exception
{
KeyManager[] managers = super.getKeyManagers(keyStore);
boolean hasSniX509ExtendedKeyManager = false;
// Is SNI needed to select a certificate?
if (isSniRequired() || !wildCerts().isEmpty() || hostCerts().size() > 1 || (hostCerts().size() == 1 && aliasCerts().size() > 1))
{
for (int idx = 0; idx < managers.length; idx++)
{
if (managers[idx] instanceof X509ExtendedKeyManager)
{
managers[idx] = newSniX509ExtendedKeyManager((X509ExtendedKeyManager)managers[idx]);
hasSniX509ExtendedKeyManager = true;
}
}
}
if (isSniRequired())
{
if (managers == null || !hasSniX509ExtendedKeyManager)
throw new IllegalStateException("No SNI Key managers when SNI is required");
}
return managers;
}
/**
* @return the custom function to select certificates based on SNI information
*/
public SniX509ExtendedKeyManager.SniSelector getSNISelector()
{
return _sniSelector;
}
/**
* Sets a custom function to select certificates based on SNI information.
*
* @param sniSelector the selection function
*/
public void setSNISelector(SniX509ExtendedKeyManager.SniSelector sniSelector)
{
_sniSelector = sniSelector;
}
@Override
public String sniSelect(String keyType, Principal[] issuers, SSLSession session, String sniHost, Collection certificates)
{
boolean sniRequired = isSniRequired();
if (LOG.isDebugEnabled())
LOG.debug("Selecting alias: keyType={}, sni={}, sniRequired={}, certs={}", keyType, String.valueOf(sniHost), sniRequired, certificates);
String alias;
if (sniHost == null)
{
// No SNI, so reject or delegate.
alias = sniRequired ? null : SniX509ExtendedKeyManager.SniSelector.DELEGATE;
}
else
{
// Match the SNI host.
List matching = certificates.stream()
.filter(x509 -> x509.matches(sniHost))
.collect(Collectors.toList());
if (matching.isEmpty())
{
// There is no match for this SNI among the certificates valid for
// this keyType; check if there is any certificate that matches this
// SNI, as we will likely be called again with a different keyType.
boolean anyMatching = aliasCerts().values().stream()
.anyMatch(x509 -> x509.matches(sniHost));
alias = sniRequired || anyMatching ? null : SniX509ExtendedKeyManager.SniSelector.DELEGATE;
}
else
{
alias = matching.get(0).getAlias();
if (matching.size() > 1)
{
// Prefer strict matches over wildcard matches.
alias = matching.stream()
.min(Comparator.comparingInt(cert -> cert.getWilds().size()))
.map(X509::getAlias)
.orElse(alias);
}
}
}
if (LOG.isDebugEnabled())
LOG.debug("Selected alias={}", String.valueOf(alias));
return alias;
}
protected X509ExtendedKeyManager newSniX509ExtendedKeyManager(X509ExtendedKeyManager keyManager)
{
return new SniX509ExtendedKeyManager(keyManager, this);
}
}
/**
* A wrapper that delegates to another (if not {@code null}) X509ExtendedKeyManager.
*/
public static class X509ExtendedKeyManagerWrapper extends X509ExtendedKeyManager
{
private final X509ExtendedKeyManager keyManager;
public X509ExtendedKeyManagerWrapper(X509ExtendedKeyManager keyManager)
{
this.keyManager = keyManager;
}
@Override
public String[] getClientAliases(String keyType, Principal[] issuers)
{
return keyManager == null ? null : keyManager.getClientAliases(keyType, issuers);
}
@Override
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket)
{
return keyManager == null ? null : keyManager.chooseClientAlias(keyType, issuers, socket);
}
@Override
public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine)
{
return keyManager == null ? null : keyManager.chooseEngineClientAlias(keyType, issuers, engine);
}
@Override
public String[] getServerAliases(String keyType, Principal[] issuers)
{
return keyManager == null ? null : keyManager.getServerAliases(keyType, issuers);
}
@Override
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
{
return keyManager == null ? null : keyManager.chooseServerAlias(keyType, issuers, socket);
}
@Override
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine)
{
return keyManager == null ? null : keyManager.chooseEngineServerAlias(keyType, issuers, engine);
}
@Override
public X509Certificate[] getCertificateChain(String alias)
{
return keyManager == null ? null : keyManager.getCertificateChain(alias);
}
@Override
public PrivateKey getPrivateKey(String alias)
{
return keyManager == null ? null : keyManager.getPrivateKey(alias);
}
}
/**
* A wrapper that delegates to another (if not {@code null}) X509ExtendedTrustManager.
*/
public static class X509ExtendedTrustManagerWrapper extends X509ExtendedTrustManager
{
private final X509ExtendedTrustManager trustManager;
public X509ExtendedTrustManagerWrapper(X509ExtendedTrustManager trustManager)
{
this.trustManager = trustManager;
}
@Override
public X509Certificate[] getAcceptedIssuers()
{
return trustManager == null ? new X509Certificate[0] : trustManager.getAcceptedIssuers();
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException
{
if (trustManager != null)
trustManager.checkClientTrusted(certs, authType);
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException
{
if (trustManager != null)
trustManager.checkClientTrusted(chain, authType, socket);
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException
{
if (trustManager != null)
trustManager.checkClientTrusted(chain, authType, engine);
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException
{
if (trustManager != null)
trustManager.checkServerTrusted(certs, authType);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException
{
if (trustManager != null)
trustManager.checkServerTrusted(chain, authType, socket);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException
{
if (trustManager != null)
trustManager.checkServerTrusted(chain, authType, engine);
}
}
}