Replace custom reloadable Key/TrustManager (#30509)
Make SSLContext reloadable This commit replaces all customKeyManagers and TrustManagers (ReloadableKeyManager,ReloadableTrustManager, EmptyKeyManager, EmptyTrustManager) with instances of X509ExtendedKeyManager and X509ExtendedTrustManager. This change was triggered by the effort to allow Elasticsearch to run in a FIPS-140 environment. In JVMs running in FIPS approved mode, only SunJSSE TrustManagers and KeyManagers can be used. Reloadability is now ensured by a volatile instance of SSLContext in SSLContectHolder. SSLConfigurationReloaderTests use the reloadable SSLContext to initialize HTTP Clients and Servers and use these for testing the key material and trust relations.
This commit is contained in:
parent
fe3e0257ae
commit
2b09e90237
|
@ -8,7 +8,6 @@ package org.elasticsearch.xpack.core.ssl;
|
||||||
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
||||||
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
|
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
|
||||||
import org.apache.lucene.util.SetOnce;
|
import org.apache.lucene.util.SetOnce;
|
||||||
import org.bouncycastle.operator.OperatorCreationException;
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
import org.elasticsearch.ElasticsearchException;
|
||||||
import org.elasticsearch.common.CheckedSupplier;
|
import org.elasticsearch.common.CheckedSupplier;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
|
@ -21,28 +20,24 @@ import org.elasticsearch.xpack.core.security.SecurityField;
|
||||||
import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
|
import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
|
||||||
|
|
||||||
import javax.net.ssl.HostnameVerifier;
|
import javax.net.ssl.HostnameVerifier;
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
import javax.net.ssl.SSLParameters;
|
import javax.net.ssl.SSLParameters;
|
||||||
import javax.net.ssl.SSLSessionContext;
|
import javax.net.ssl.SSLSessionContext;
|
||||||
import javax.net.ssl.SSLSocket;
|
import javax.net.ssl.SSLSocket;
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
import javax.net.ssl.X509ExtendedKeyManager;
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
import javax.net.ssl.X509ExtendedTrustManager;
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
import javax.security.auth.DestroyFailedException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.KeyStoreException;
|
import java.security.KeyStore;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.Principal;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.UnrecoverableKeyException;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -54,6 +49,7 @@ import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,8 +67,7 @@ public class SSLService extends AbstractComponent {
|
||||||
* Create a new SSLService that parses the settings for the ssl contexts that need to be created, creates them, and then caches them
|
* Create a new SSLService that parses the settings for the ssl contexts that need to be created, creates them, and then caches them
|
||||||
* for use later
|
* for use later
|
||||||
*/
|
*/
|
||||||
public SSLService(Settings settings, Environment environment) throws CertificateException, UnrecoverableKeyException,
|
public SSLService(Settings settings, Environment environment) {
|
||||||
NoSuchAlgorithmException, IOException, DestroyFailedException, KeyStoreException, OperatorCreationException {
|
|
||||||
super(settings);
|
super(settings);
|
||||||
this.env = environment;
|
this.env = environment;
|
||||||
this.globalSSLConfiguration = new SSLConfiguration(settings.getByPrefix(XPackSettings.GLOBAL_SSL_PREFIX));
|
this.globalSSLConfiguration = new SSLConfiguration(settings.getByPrefix(XPackSettings.GLOBAL_SSL_PREFIX));
|
||||||
|
@ -403,10 +398,8 @@ public class SSLService extends AbstractComponent {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("using ssl settings [{}]", sslConfiguration);
|
logger.debug("using ssl settings [{}]", sslConfiguration);
|
||||||
}
|
}
|
||||||
ReloadableTrustManager trustManager =
|
X509ExtendedTrustManager trustManager = sslConfiguration.trustConfig().createTrustManager(env);
|
||||||
new ReloadableTrustManager(sslConfiguration.trustConfig().createTrustManager(env), sslConfiguration.trustConfig());
|
X509ExtendedKeyManager keyManager = sslConfiguration.keyConfig().createKeyManager(env);
|
||||||
ReloadableX509KeyManager keyManager =
|
|
||||||
new ReloadableX509KeyManager(sslConfiguration.keyConfig().createKeyManager(env), sslConfiguration.keyConfig());
|
|
||||||
return createSslContext(keyManager, trustManager, sslConfiguration);
|
return createSslContext(keyManager, trustManager, sslConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,7 +410,7 @@ public class SSLService extends AbstractComponent {
|
||||||
* @param trustManager the trust manager to use
|
* @param trustManager the trust manager to use
|
||||||
* @return the created SSLContext
|
* @return the created SSLContext
|
||||||
*/
|
*/
|
||||||
private SSLContextHolder createSslContext(ReloadableX509KeyManager keyManager, ReloadableTrustManager trustManager,
|
private SSLContextHolder createSslContext(X509ExtendedKeyManager keyManager, X509ExtendedTrustManager trustManager,
|
||||||
SSLConfiguration sslConfiguration) {
|
SSLConfiguration sslConfiguration) {
|
||||||
// Initialize sslContext
|
// Initialize sslContext
|
||||||
try {
|
try {
|
||||||
|
@ -427,7 +420,7 @@ public class SSLService extends AbstractComponent {
|
||||||
// check the supported ciphers and log them here to prevent spamming logs on every call
|
// check the supported ciphers and log them here to prevent spamming logs on every call
|
||||||
supportedCiphers(sslContext.getSupportedSSLParameters().getCipherSuites(), sslConfiguration.cipherSuites(), true);
|
supportedCiphers(sslContext.getSupportedSSLParameters().getCipherSuites(), sslConfiguration.cipherSuites(), true);
|
||||||
|
|
||||||
return new SSLContextHolder(sslContext, trustManager, keyManager);
|
return new SSLContextHolder(sslContext, sslConfiguration);
|
||||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||||
throw new ElasticsearchException("failed to initialize the SSLContext", e);
|
throw new ElasticsearchException("failed to initialize the SSLContext", e);
|
||||||
}
|
}
|
||||||
|
@ -436,9 +429,7 @@ public class SSLService extends AbstractComponent {
|
||||||
/**
|
/**
|
||||||
* Parses the settings to load all SSLConfiguration objects that will be used.
|
* Parses the settings to load all SSLConfiguration objects that will be used.
|
||||||
*/
|
*/
|
||||||
Map<SSLConfiguration, SSLContextHolder> loadSSLConfigurations() throws CertificateException,
|
Map<SSLConfiguration, SSLContextHolder> loadSSLConfigurations() {
|
||||||
UnrecoverableKeyException, NoSuchAlgorithmException, IOException, DestroyFailedException, KeyStoreException,
|
|
||||||
OperatorCreationException {
|
|
||||||
Map<SSLConfiguration, SSLContextHolder> sslConfigurations = new HashMap<>();
|
Map<SSLConfiguration, SSLContextHolder> sslConfigurations = new HashMap<>();
|
||||||
sslConfigurations.put(globalSSLConfiguration, createSslContext(globalSSLConfiguration));
|
sslConfigurations.put(globalSSLConfiguration, createSslContext(globalSSLConfiguration));
|
||||||
|
|
||||||
|
@ -560,258 +551,70 @@ public class SSLService extends AbstractComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps a trust manager to delegate to. If the trust material needs to be reloaded, then the delegate will be switched after
|
|
||||||
* reloading
|
|
||||||
*/
|
|
||||||
final class ReloadableTrustManager extends X509ExtendedTrustManager {
|
|
||||||
|
|
||||||
private volatile X509ExtendedTrustManager trustManager;
|
final class SSLContextHolder {
|
||||||
private final TrustConfig trustConfig;
|
private volatile SSLContext context;
|
||||||
|
|
||||||
ReloadableTrustManager(X509ExtendedTrustManager trustManager, TrustConfig trustConfig) {
|
|
||||||
this.trustManager = trustManager == null ? new EmptyX509TrustManager() : trustManager;
|
|
||||||
this.trustConfig = trustConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {
|
|
||||||
trustManager.checkClientTrusted(x509Certificates, s, socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {
|
|
||||||
trustManager.checkServerTrusted(x509Certificates, s, socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {
|
|
||||||
trustManager.checkClientTrusted(x509Certificates, s, sslEngine);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {
|
|
||||||
trustManager.checkServerTrusted(x509Certificates, s, sslEngine);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
|
|
||||||
trustManager.checkClientTrusted(x509Certificates, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
|
|
||||||
trustManager.checkServerTrusted(x509Certificates, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public X509Certificate[] getAcceptedIssuers() {
|
|
||||||
return trustManager.getAcceptedIssuers();
|
|
||||||
}
|
|
||||||
|
|
||||||
void reload() {
|
|
||||||
X509ExtendedTrustManager loadedTrustManager = trustConfig.createTrustManager(env);
|
|
||||||
if (loadedTrustManager == null) {
|
|
||||||
this.trustManager = new EmptyX509TrustManager();
|
|
||||||
} else {
|
|
||||||
this.trustManager = loadedTrustManager;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
X509ExtendedTrustManager getTrustManager() {
|
|
||||||
return trustManager;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps a key manager and delegates all calls to it. When the key material needs to be reloaded, then the delegate is swapped after
|
|
||||||
* a new one has been loaded
|
|
||||||
*/
|
|
||||||
final class ReloadableX509KeyManager extends X509ExtendedKeyManager {
|
|
||||||
|
|
||||||
private volatile X509ExtendedKeyManager keyManager;
|
|
||||||
private final KeyConfig keyConfig;
|
private final KeyConfig keyConfig;
|
||||||
|
private final TrustConfig trustConfig;
|
||||||
|
private final SSLConfiguration sslConfiguration;
|
||||||
|
|
||||||
ReloadableX509KeyManager(X509ExtendedKeyManager keyManager, KeyConfig keyConfig) {
|
SSLContextHolder(SSLContext context, SSLConfiguration sslConfiguration) {
|
||||||
this.keyManager = keyManager == null ? new EmptyKeyManager() : keyManager;
|
|
||||||
this.keyConfig = keyConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getClientAliases(String s, Principal[] principals) {
|
|
||||||
return keyManager.getClientAliases(s, principals);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
|
|
||||||
return keyManager.chooseClientAlias(strings, principals, socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getServerAliases(String s, Principal[] principals) {
|
|
||||||
return keyManager.getServerAliases(s, principals);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
|
|
||||||
return keyManager.chooseServerAlias(s, principals, socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public X509Certificate[] getCertificateChain(String s) {
|
|
||||||
return keyManager.getCertificateChain(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PrivateKey getPrivateKey(String s) {
|
|
||||||
return keyManager.getPrivateKey(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String chooseEngineClientAlias(String[] strings, Principal[] principals, SSLEngine engine) {
|
|
||||||
return keyManager.chooseEngineClientAlias(strings, principals, engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String chooseEngineServerAlias(String s, Principal[] principals, SSLEngine engine) {
|
|
||||||
return keyManager.chooseEngineServerAlias(s, principals, engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
void reload() {
|
|
||||||
X509ExtendedKeyManager loadedKeyManager = keyConfig.createKeyManager(env);
|
|
||||||
if (loadedKeyManager == null) {
|
|
||||||
this.keyManager = new EmptyKeyManager();
|
|
||||||
} else {
|
|
||||||
this.keyManager = loadedKeyManager;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pkg-private accessor for testing
|
|
||||||
X509ExtendedKeyManager getKeyManager() {
|
|
||||||
return keyManager;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A struct for holding the SSLContext and the backing key manager and trust manager
|
|
||||||
*/
|
|
||||||
static final class SSLContextHolder {
|
|
||||||
|
|
||||||
private final SSLContext context;
|
|
||||||
private final ReloadableTrustManager trustManager;
|
|
||||||
private final ReloadableX509KeyManager keyManager;
|
|
||||||
|
|
||||||
SSLContextHolder(SSLContext context, ReloadableTrustManager trustManager, ReloadableX509KeyManager keyManager) {
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.trustManager = trustManager;
|
this.sslConfiguration = sslConfiguration;
|
||||||
this.keyManager = keyManager;
|
this.keyConfig = sslConfiguration.keyConfig();
|
||||||
|
this.trustConfig = sslConfiguration.trustConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
SSLContext sslContext() {
|
SSLContext sslContext() {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReloadableX509KeyManager keyManager() {
|
|
||||||
return keyManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReloadableTrustManager trustManager() {
|
|
||||||
return trustManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void reload() {
|
|
||||||
trustManager.reload();
|
|
||||||
keyManager.reload();
|
|
||||||
invalidateSessions(context.getClientSessionContext());
|
|
||||||
invalidateSessions(context.getServerSessionContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidates the sessions in the provided {@link SSLSessionContext}
|
* Invalidates the sessions in the provided {@link SSLSessionContext}
|
||||||
*/
|
*/
|
||||||
private static void invalidateSessions(SSLSessionContext sslSessionContext) {
|
private void invalidateSessions(SSLSessionContext sslSessionContext) {
|
||||||
Enumeration<byte[]> sessionIds = sslSessionContext.getIds();
|
Enumeration<byte[]> sessionIds = sslSessionContext.getIds();
|
||||||
while (sessionIds.hasMoreElements()) {
|
while (sessionIds.hasMoreElements()) {
|
||||||
byte[] sessionId = sessionIds.nextElement();
|
byte[] sessionId = sessionIds.nextElement();
|
||||||
sslSessionContext.getSession(sessionId).invalidate();
|
sslSessionContext.getSession(sessionId).invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized void reload() {
|
||||||
|
invalidateSessions(context.getClientSessionContext());
|
||||||
|
invalidateSessions(context.getServerSessionContext());
|
||||||
|
reloadSslContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void reloadSslContext() {
|
||||||
* This is an empty key manager that is used in case a loaded key manager is null
|
try {
|
||||||
*/
|
X509ExtendedKeyManager loadedKeyManager = Optional.ofNullable(keyConfig.createKeyManager(env)).
|
||||||
private static final class EmptyKeyManager extends X509ExtendedKeyManager {
|
orElse(getEmptyKeyManager());
|
||||||
|
X509ExtendedTrustManager loadedTrustManager = Optional.ofNullable(trustConfig.createTrustManager(env)).
|
||||||
@Override
|
orElse(getEmptyTrustManager());
|
||||||
public String[] getClientAliases(String s, Principal[] principals) {
|
SSLContext loadedSslContext = SSLContext.getInstance(sslContextAlgorithm(sslConfiguration.supportedProtocols()));
|
||||||
return new String[0];
|
loadedSslContext.init(new X509ExtendedKeyManager[]{loadedKeyManager},
|
||||||
}
|
new X509ExtendedTrustManager[]{loadedTrustManager}, null);
|
||||||
|
supportedCiphers(loadedSslContext.getSupportedSSLParameters().getCipherSuites(), sslConfiguration.cipherSuites(), false);
|
||||||
@Override
|
this.context = loadedSslContext;
|
||||||
public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
|
} catch (GeneralSecurityException | IOException e) {
|
||||||
return null;
|
throw new ElasticsearchException("failed to initialize the SSLContext", e);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getServerAliases(String s, Principal[] principals) {
|
|
||||||
return new String[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public X509Certificate[] getCertificateChain(String s) {
|
|
||||||
return new X509Certificate[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PrivateKey getPrivateKey(String s) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
X509ExtendedKeyManager getEmptyKeyManager() throws GeneralSecurityException, IOException {
|
||||||
/**
|
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
* This is an empty trust manager that is used in case a loaded trust manager is null
|
keyStore.load(null, null);
|
||||||
*/
|
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||||
static final class EmptyX509TrustManager extends X509ExtendedTrustManager {
|
keyManagerFactory.init(keyStore, null);
|
||||||
|
return (X509ExtendedKeyManager) keyManagerFactory.getKeyManagers()[0];
|
||||||
@Override
|
|
||||||
public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {
|
|
||||||
throw new CertificateException("no certificates are trusted");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
X509ExtendedTrustManager getEmptyTrustManager() throws GeneralSecurityException, IOException {
|
||||||
public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {
|
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
throw new CertificateException("no certificates are trusted");
|
keyStore.load(null, null);
|
||||||
}
|
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
|
||||||
|
trustManagerFactory.init(keyStore);
|
||||||
@Override
|
return (X509ExtendedTrustManager) trustManagerFactory.getTrustManagers()[0];
|
||||||
public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {
|
|
||||||
throw new CertificateException("no certificates are trusted");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {
|
|
||||||
throw new CertificateException("no certificates are trusted");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
|
|
||||||
throw new CertificateException("no certificates are trusted");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
|
|
||||||
throw new CertificateException("no certificates are trusted");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public X509Certificate[] getAcceptedIssuers() {
|
|
||||||
return new X509Certificate[0];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,25 +5,33 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.core.ssl;
|
package org.elasticsearch.xpack.core.ssl;
|
||||||
|
|
||||||
import org.apache.lucene.util.SetOnce;
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClients;
|
||||||
|
import org.apache.http.ssl.SSLContextBuilder;
|
||||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||||
import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
|
import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
|
||||||
|
import org.elasticsearch.common.CheckedRunnable;
|
||||||
import org.elasticsearch.common.settings.MockSecureSettings;
|
import org.elasticsearch.common.settings.MockSecureSettings;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.env.TestEnvironment;
|
import org.elasticsearch.env.TestEnvironment;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.test.http.MockResponse;
|
||||||
|
import org.elasticsearch.test.http.MockWebServer;
|
||||||
import org.elasticsearch.threadpool.TestThreadPool;
|
import org.elasticsearch.threadpool.TestThreadPool;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
import javax.net.ssl.X509ExtendedKeyManager;
|
import javax.net.ssl.SSLContext;
|
||||||
import javax.net.ssl.X509ExtendedTrustManager;
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
import javax.security.auth.x500.X500Principal;
|
import javax.security.auth.x500.X500Principal;
|
||||||
|
|
||||||
|
import java.io.BufferedWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -32,20 +40,23 @@ import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.security.AccessController;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.PrivateKey;
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivilegedActionException;
|
||||||
|
import java.security.PrivilegedExceptionAction;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
import static org.hamcrest.Matchers.not;
|
|
||||||
import static org.hamcrest.Matchers.sameInstance;
|
import static org.hamcrest.Matchers.sameInstance;
|
||||||
import static org.hamcrest.core.Is.is;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for the reloading of SSL configuration
|
* Unit tests for the reloading of SSL configuration
|
||||||
|
@ -71,8 +82,7 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests reloading a keystore. The contents of the keystore is used for both keystore and truststore material, so both key
|
* Tests reloading a keystore that is used in the KeyManager of SSLContext
|
||||||
* config and trust config is checked.
|
|
||||||
*/
|
*/
|
||||||
public void testReloadingKeyStore() throws Exception {
|
public void testReloadingKeyStore() throws Exception {
|
||||||
final Path tempDir = createTempDir();
|
final Path tempDir = createTempDir();
|
||||||
|
@ -86,29 +96,25 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||||
.setSecureSettings(secureSettings)
|
.setSecureSettings(secureSettings)
|
||||||
.build();
|
.build();
|
||||||
final Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings);
|
final Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings);
|
||||||
|
//Load HTTPClient only once. Client uses the same store as a truststore
|
||||||
final BiConsumer<X509ExtendedKeyManager, SSLConfiguration> keyManagerPreChecks = (keyManager, config) -> {
|
try (CloseableHttpClient client = getSSLClient(keystorePath, "testnode")) {
|
||||||
// key manager checks
|
final Consumer<SSLContext> keyMaterialPreChecks = (context) -> {
|
||||||
String[] aliases = keyManager.getServerAliases("RSA", null);
|
try (MockWebServer server = new MockWebServer(context, true)) {
|
||||||
assertNotNull(aliases);
|
server.enqueue(new MockResponse().setResponseCode(200).setBody("body"));
|
||||||
assertThat(aliases.length, is(1));
|
server.start();
|
||||||
assertThat(aliases[0], is("testnode"));
|
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close());
|
||||||
};
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Exception starting or connecting to the mock server", e);
|
||||||
final SetOnce<Integer> trustedCount = new SetOnce<>();
|
}
|
||||||
final BiConsumer<X509ExtendedTrustManager, SSLConfiguration> trustManagerPreChecks = (trustManager, config) -> {
|
|
||||||
// trust manager checks
|
|
||||||
Certificate[] certificates = trustManager.getAcceptedIssuers();
|
|
||||||
trustedCount.set(certificates.length);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
final Runnable modifier = () -> {
|
final Runnable modifier = () -> {
|
||||||
try {
|
try {
|
||||||
// modify it
|
// modify the keystore that the KeyManager uses
|
||||||
KeyStore keyStore = KeyStore.getInstance("jks");
|
KeyStore keyStore = KeyStore.getInstance("jks");
|
||||||
keyStore.load(null, null);
|
keyStore.load(null, null);
|
||||||
final KeyPair keyPair = CertUtils.generateKeyPair(512);
|
final KeyPair keyPair = CertUtils.generateKeyPair(512);
|
||||||
X509Certificate cert = CertUtils.generateSignedCertificate(new X500Principal("CN=testReloadingKeyStore"), null, keyPair,
|
X509Certificate cert = CertUtils.generateSignedCertificate(new X500Principal("CN=localhost"), null, keyPair,
|
||||||
null, null, 365);
|
null, null, 365);
|
||||||
keyStore.setKeyEntry("key", keyPair.getPrivate(), "testnode".toCharArray(), new X509Certificate[]{cert});
|
keyStore.setKeyEntry("key", keyPair.getPrivate(), "testnode".toCharArray(), new X509Certificate[]{cert});
|
||||||
Path updated = tempDir.resolve("updated.jks");
|
Path updated = tempDir.resolve("updated.jks");
|
||||||
|
@ -120,93 +126,97 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||||
throw new RuntimeException("modification failed", e);
|
throw new RuntimeException("modification failed", e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// The new server certificate is not in the client's truststore so SSLHandshake should fail
|
||||||
final BiConsumer<X509ExtendedKeyManager, SSLConfiguration> keyManagerPostChecks = (updatedKeyManager, config) -> {
|
final Consumer<SSLContext> keyMaterialPostChecks = (updatedContext) -> {
|
||||||
String[] aliases = updatedKeyManager.getServerAliases("RSA", null);
|
try (MockWebServer server = new MockWebServer(updatedContext, true)) {
|
||||||
assertNotNull(aliases);
|
server.enqueue(new MockResponse().setResponseCode(200).setBody("body"));
|
||||||
assertThat(aliases.length, is(1));
|
server.start();
|
||||||
assertThat(aliases[0], is("key"));
|
SSLHandshakeException sslException = expectThrows(SSLHandshakeException.class, () ->
|
||||||
|
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close()));
|
||||||
|
assertThat(sslException.getCause().getMessage(), containsString("PKIX path building failed"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Exception starting or connecting to the mock server", e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
final BiConsumer<X509ExtendedTrustManager, SSLConfiguration> trustManagerPostChecks = (updatedTrustManager, config) -> {
|
validateSSLConfigurationIsReloaded(settings, env, keyMaterialPreChecks, modifier, keyMaterialPostChecks);
|
||||||
assertThat(trustedCount.get() - updatedTrustManager.getAcceptedIssuers().length, is(5));
|
}
|
||||||
};
|
|
||||||
validateSSLConfigurationIsReloaded(settings, env, keyManagerPreChecks, trustManagerPreChecks, modifier, keyManagerPostChecks,
|
|
||||||
trustManagerPostChecks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the reloading of a PEM key config when the key is overwritten. The trust portion is not tested as it is not modified by this
|
* Tests the reloading of SSLContext when a PEM key and certificate are used.
|
||||||
* test.
|
|
||||||
*/
|
*/
|
||||||
public void testPEMKeyConfigReloading() throws Exception {
|
public void testPEMKeyCertConfigReloading() throws Exception {
|
||||||
Path tempDir = createTempDir();
|
final Path tempDir = createTempDir();
|
||||||
Path keyPath = tempDir.resolve("testnode.pem");
|
final Path keyPath = tempDir.resolve("testnode.pem");
|
||||||
Path certPath = tempDir.resolve("testnode.crt");
|
final Path certPath = tempDir.resolve("testnode.crt");
|
||||||
Path clientCertPath = tempDir.resolve("testclient.crt");
|
final Path clientTruststorePath = tempDir.resolve("testnode.jks");
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), clientTruststorePath);
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem"), keyPath);
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem"), keyPath);
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), certPath);
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), certPath);
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
|
|
||||||
MockSecureSettings secureSettings = new MockSecureSettings();
|
MockSecureSettings secureSettings = new MockSecureSettings();
|
||||||
secureSettings.setString("xpack.ssl.secure_key_passphrase", "testnode");
|
secureSettings.setString("xpack.ssl.secure_key_passphrase", "testnode");
|
||||||
final Settings settings = Settings.builder()
|
final Settings settings = Settings.builder()
|
||||||
.put("path.home", createTempDir())
|
.put("path.home", createTempDir())
|
||||||
.put("xpack.ssl.key", keyPath)
|
.put("xpack.ssl.key", keyPath)
|
||||||
.put("xpack.ssl.certificate", certPath)
|
.put("xpack.ssl.certificate", certPath)
|
||||||
.putList("xpack.ssl.certificate_authorities", certPath.toString(), clientCertPath.toString())
|
|
||||||
.setSecureSettings(secureSettings)
|
.setSecureSettings(secureSettings)
|
||||||
.build();
|
.build();
|
||||||
final Environment env = randomBoolean() ? null :
|
final Environment env = randomBoolean() ? null :
|
||||||
TestEnvironment.newEnvironment(Settings.builder().put("path.home", createTempDir()).build());
|
TestEnvironment.newEnvironment(Settings.builder().put("path.home", createTempDir()).build());
|
||||||
|
// Load HTTPClient once. Client uses a keystore containing testnode key/cert as a truststore
|
||||||
final SetOnce<PrivateKey> privateKey = new SetOnce<>();
|
try (CloseableHttpClient client = getSSLClient(clientTruststorePath, "testnode")) {
|
||||||
final BiConsumer<X509ExtendedKeyManager, SSLConfiguration> keyManagerPreChecks = (keyManager, config) -> {
|
final Consumer<SSLContext> keyMaterialPreChecks = (context) -> {
|
||||||
String[] aliases = keyManager.getServerAliases("RSA", null);
|
try (MockWebServer server = new MockWebServer(context, false)) {
|
||||||
assertNotNull(aliases);
|
server.enqueue(new MockResponse().setResponseCode(200).setBody("body"));
|
||||||
assertThat(aliases.length, is(1));
|
server.start();
|
||||||
assertThat(aliases[0], is("key"));
|
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close());
|
||||||
privateKey.set(keyManager.getPrivateKey("key"));
|
} catch (Exception e) {
|
||||||
assertNotNull(privateKey.get());
|
throw new RuntimeException("Exception starting or connecting to the mock server", e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final KeyPair keyPair = CertUtils.generateKeyPair(randomFrom(1024, 2048));
|
|
||||||
final Runnable modifier = () -> {
|
final Runnable modifier = () -> {
|
||||||
try {
|
try {
|
||||||
// make sure we wait long enough to see a change. if time is within a second the file may not be seen as modified since the
|
final KeyPair keyPair = CertUtils.generateKeyPair(512);
|
||||||
// size is the same!
|
X509Certificate cert = CertUtils.generateSignedCertificate(new X500Principal("CN=localhost"), null, keyPair,
|
||||||
assertTrue(awaitBusy(() -> {
|
null, null, 365);
|
||||||
try {
|
|
||||||
BasicFileAttributes attributes = Files.readAttributes(keyPath, BasicFileAttributes.class);
|
|
||||||
return System.currentTimeMillis() - attributes.lastModifiedTime().toMillis() >= 1000L;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("io exception while checking time", e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
Path updatedKeyPath = tempDir.resolve("updated.pem");
|
Path updatedKeyPath = tempDir.resolve("updated.pem");
|
||||||
|
Path updatedCertPath = tempDir.resolve("updated.crt");
|
||||||
try (OutputStream os = Files.newOutputStream(updatedKeyPath);
|
try (OutputStream os = Files.newOutputStream(updatedKeyPath);
|
||||||
OutputStreamWriter osWriter = new OutputStreamWriter(os, StandardCharsets.UTF_8);
|
OutputStreamWriter osWriter = new OutputStreamWriter(os, StandardCharsets.UTF_8);
|
||||||
JcaPEMWriter writer = new JcaPEMWriter(osWriter)) {
|
JcaPEMWriter writer = new JcaPEMWriter(osWriter)) {
|
||||||
writer.writeObject(keyPair,
|
writer.writeObject(keyPair,
|
||||||
new JcePEMEncryptorBuilder("DES-EDE3-CBC").setProvider(CertUtils.BC_PROV).build("testnode".toCharArray()));
|
new JcePEMEncryptorBuilder("DES-EDE3-CBC").setProvider(CertUtils.BC_PROV).build("testnode".toCharArray()));
|
||||||
}
|
}
|
||||||
|
try (BufferedWriter out = Files.newBufferedWriter(updatedCertPath);
|
||||||
|
JcaPEMWriter pemWriter = new JcaPEMWriter(out)) {
|
||||||
|
pemWriter.writeObject(cert);
|
||||||
|
}
|
||||||
atomicMoveIfPossible(updatedKeyPath, keyPath);
|
atomicMoveIfPossible(updatedKeyPath, keyPath);
|
||||||
|
atomicMoveIfPossible(updatedCertPath, certPath);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("failed to modify file", e);
|
throw new RuntimeException("failed to modify file", e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// The new server certificate is not in the client's truststore so SSLHandshake should fail
|
||||||
final BiConsumer<X509ExtendedKeyManager, SSLConfiguration> keyManagerPostChecks = (keyManager, config) -> {
|
final Consumer<SSLContext> keyMaterialPostChecks = (updatedContext) -> {
|
||||||
String[] aliases = keyManager.getServerAliases("RSA", null);
|
try (MockWebServer server = new MockWebServer(updatedContext, false)) {
|
||||||
assertNotNull(aliases);
|
server.enqueue(new MockResponse().setResponseCode(200).setBody("body"));
|
||||||
assertThat(aliases.length, is(1));
|
server.start();
|
||||||
assertThat(aliases[0], is("key"));
|
SSLHandshakeException sslException = expectThrows(SSLHandshakeException.class, () ->
|
||||||
assertThat(keyManager.getPrivateKey(aliases[0]), not(equalTo(privateKey)));
|
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close()));
|
||||||
assertThat(keyManager.getPrivateKey(aliases[0]), is(equalTo(keyPair.getPrivate())));
|
assertThat(sslException.getCause().getMessage(), containsString("PKIX path building failed"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Exception starting or connecting to the mock server", e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
validateKeyConfigurationIsReloaded(settings, env, keyManagerPreChecks, modifier, keyManagerPostChecks);
|
|
||||||
|
validateSSLConfigurationIsReloaded(settings, env, keyMaterialPreChecks, modifier, keyMaterialPostChecks);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the reloading of the trust config when the trust store is modified. The key config is not tested as part of this test.
|
* Tests the reloading of SSLContext when the trust store is modified. The same store is used as a TrustStore (for the
|
||||||
|
* reloadable SSLContext used in the HTTPClient) and as a KeyStore for the MockWebServer
|
||||||
*/
|
*/
|
||||||
public void testReloadingTrustStore() throws Exception {
|
public void testReloadingTrustStore() throws Exception {
|
||||||
Path tempDir = createTempDir();
|
Path tempDir = createTempDir();
|
||||||
|
@ -220,75 +230,101 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||||
.setSecureSettings(secureSettings)
|
.setSecureSettings(secureSettings)
|
||||||
.build();
|
.build();
|
||||||
Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings);
|
Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings);
|
||||||
|
// Create the MockWebServer once for both pre and post checks
|
||||||
final SetOnce<Integer> trustedCount = new SetOnce<>();
|
try(MockWebServer server = getSslServer(trustStorePath, "testnode")){
|
||||||
final BiConsumer<X509ExtendedTrustManager, SSLConfiguration> trustManagerPreChecks = (trustManager, config) -> {
|
final Consumer<SSLContext> trustMaterialPreChecks = (context) -> {
|
||||||
// trust manager checks
|
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(context).build()){
|
||||||
Certificate[] certificates = trustManager.getAcceptedIssuers();
|
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close());
|
||||||
trustedCount.set(certificates.length);
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error connecting to the mock server", e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
final Runnable modifier = () -> {
|
final Runnable modifier = () -> {
|
||||||
try {
|
try {
|
||||||
Path updatedTruststore = tempDir.resolve("updated.jks");
|
Path updatedTrustStore = tempDir.resolve("updated.jks");
|
||||||
KeyStore keyStore = KeyStore.getInstance("jks");
|
KeyStore keyStore = KeyStore.getInstance("jks");
|
||||||
keyStore.load(null, null);
|
keyStore.load(null, null);
|
||||||
try (OutputStream out = Files.newOutputStream(updatedTruststore)) {
|
final KeyPair keyPair = CertUtils.generateKeyPair(512);
|
||||||
|
X509Certificate cert = CertUtils.generateSignedCertificate(new X500Principal("CN=localhost"), null, keyPair,
|
||||||
|
null, null, 365);
|
||||||
|
keyStore.setKeyEntry("newKey", keyPair.getPrivate(), "testnode".toCharArray(), new Certificate[]{cert});
|
||||||
|
try (OutputStream out = Files.newOutputStream(updatedTrustStore)) {
|
||||||
keyStore.store(out, "testnode".toCharArray());
|
keyStore.store(out, "testnode".toCharArray());
|
||||||
}
|
}
|
||||||
atomicMoveIfPossible(updatedTruststore, trustStorePath);
|
atomicMoveIfPossible(updatedTrustStore, trustStorePath);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("failed to modify file", e);
|
throw new RuntimeException("failed to modify file", e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final BiConsumer<X509ExtendedTrustManager, SSLConfiguration> trustManagerPostChecks = (updatedTrustManager, config) -> {
|
// Client's truststore doesn't contain the server's certificate anymore so SSLHandshake should fail
|
||||||
assertThat(trustedCount.get() - updatedTrustManager.getAcceptedIssuers().length, is(6));
|
final Consumer<SSLContext> trustMaterialPostChecks = (updatedContext) -> {
|
||||||
|
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(updatedContext).build()){
|
||||||
|
SSLHandshakeException sslException = expectThrows(SSLHandshakeException.class, () ->
|
||||||
|
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close()));
|
||||||
|
assertThat(sslException.getCause().getMessage(), containsString("PKIX path building failed"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error closing CloseableHttpClient", e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
validateTrustConfigurationIsReloaded(settings, env, trustManagerPreChecks, modifier, trustManagerPostChecks);
|
validateSSLConfigurationIsReloaded(settings, env, trustMaterialPreChecks, modifier, trustMaterialPostChecks);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the reloading of a trust config that is backed by PEM certificate files. The key config is not tested as we only care about the
|
* Test the reloading of SSLContext whose trust config is backed by PEM certificate files.
|
||||||
* trust config in this test.
|
|
||||||
*/
|
*/
|
||||||
public void testReloadingPEMTrustConfig() throws Exception {
|
public void testReloadingPEMTrustConfig() throws Exception {
|
||||||
Path tempDir = createTempDir();
|
Path tempDir = createTempDir();
|
||||||
Path clientCertPath = tempDir.resolve("testclient.crt");
|
Path clientCertPath = tempDir.resolve("testnode.crt");
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
|
Path keyStorePath = tempDir.resolve("testnode.jks");
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), keyStorePath);
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), clientCertPath);
|
||||||
Settings settings = Settings.builder()
|
Settings settings = Settings.builder()
|
||||||
.putList("xpack.ssl.certificate_authorities", clientCertPath.toString())
|
.putList("xpack.ssl.certificate_authorities", clientCertPath.toString())
|
||||||
.put("path.home", createTempDir())
|
.put("path.home", createTempDir())
|
||||||
.build();
|
.build();
|
||||||
Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings);
|
Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings);
|
||||||
|
// Create the MockWebServer once for both pre and post checks
|
||||||
final BiConsumer<X509ExtendedTrustManager, SSLConfiguration> trustManagerPreChecks = (trustManager, config) -> {
|
try(MockWebServer server = getSslServer(keyStorePath, "testnode")){
|
||||||
// trust manager checks
|
final Consumer<SSLContext> trustMaterialPreChecks = (context) -> {
|
||||||
Certificate[] certificates = trustManager.getAcceptedIssuers();
|
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(context).build()){
|
||||||
assertThat(certificates.length, is(1));
|
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close());
|
||||||
assertThat(((X509Certificate)certificates[0]).getSubjectX500Principal().getName(), containsString("Test Client"));
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Exception connecting to the mock server", e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final Runnable modifier = () -> {
|
final Runnable modifier = () -> {
|
||||||
try {
|
try {
|
||||||
Path updatedCert = tempDir.resolve("updated.crt");
|
final KeyPair keyPair = CertUtils.generateKeyPair(512);
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), updatedCert,
|
X509Certificate cert = CertUtils.generateSignedCertificate(new X500Principal("CN=localhost"), null, keyPair,
|
||||||
StandardCopyOption.REPLACE_EXISTING);
|
null, null, 365);
|
||||||
atomicMoveIfPossible(updatedCert, clientCertPath);
|
Path updatedCertPath = tempDir.resolve("updated.crt");
|
||||||
|
try (BufferedWriter out = Files.newBufferedWriter(updatedCertPath);
|
||||||
|
JcaPEMWriter pemWriter = new JcaPEMWriter(out)) {
|
||||||
|
pemWriter.writeObject(cert);
|
||||||
|
}
|
||||||
|
atomicMoveIfPossible(updatedCertPath, clientCertPath);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("failed to modify file", e);
|
throw new RuntimeException("failed to modify file", e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// Client doesn't trust the Server certificate anymore so SSLHandshake should fail
|
||||||
final BiConsumer<X509ExtendedTrustManager, SSLConfiguration> trustManagerPostChecks = (updatedTrustManager, config) -> {
|
final Consumer<SSLContext> trustMaterialPostChecks = (updatedContext) -> {
|
||||||
Certificate[] updatedCerts = updatedTrustManager.getAcceptedIssuers();
|
try (CloseableHttpClient client = HttpClients.custom().setSSLContext(updatedContext).build()){
|
||||||
assertThat(updatedCerts.length, is(1));
|
SSLHandshakeException sslException = expectThrows(SSLHandshakeException.class, () ->
|
||||||
assertThat(((X509Certificate)updatedCerts[0]).getSubjectX500Principal().getName(), containsString("Test Node"));
|
privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close()));
|
||||||
|
assertThat(sslException.getCause().getMessage(), containsString("PKIX path building failed"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error closing CloseableHttpClient", e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
validateTrustConfigurationIsReloaded(settings, env, trustManagerPreChecks, modifier, trustManagerPostChecks);
|
validateSSLConfigurationIsReloaded(settings, env, trustMaterialPreChecks, modifier, trustMaterialPostChecks);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -316,15 +352,14 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// key manager checks
|
final SSLContext context = sslService.sslContextHolder(config).sslContext();
|
||||||
final X509ExtendedKeyManager keyManager = sslService.sslContextHolder(config).keyManager().getKeyManager();
|
|
||||||
|
|
||||||
// truncate the keystore
|
// truncate the keystore
|
||||||
try (OutputStream out = Files.newOutputStream(keystorePath, StandardOpenOption.TRUNCATE_EXISTING)) {
|
try (OutputStream out = Files.newOutputStream(keystorePath, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
||||||
assertThat(sslService.sslContextHolder(config).keyManager().getKeyManager(), sameInstance(keyManager));
|
assertThat(sslService.sslContextHolder(config).sslContext(), sameInstance(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -358,14 +393,14 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final X509ExtendedKeyManager keyManager = sslService.sslContextHolder(config).keyManager().getKeyManager();
|
final SSLContext context = sslService.sslContextHolder(config).sslContext();
|
||||||
|
|
||||||
// truncate the file
|
// truncate the file
|
||||||
try (OutputStream os = Files.newOutputStream(keyPath, StandardOpenOption.TRUNCATE_EXISTING)) {
|
try (OutputStream os = Files.newOutputStream(keyPath, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
||||||
assertThat(sslService.sslContextHolder(config).keyManager().getKeyManager(), sameInstance(keyManager));
|
assertThat(sslService.sslContextHolder(config).sslContext(), sameInstance(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -393,14 +428,14 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final X509ExtendedTrustManager trustManager = sslService.sslContextHolder(config).trustManager().getTrustManager();
|
final SSLContext context = sslService.sslContextHolder(config).sslContext();
|
||||||
|
|
||||||
// truncate the truststore
|
// truncate the truststore
|
||||||
try (OutputStream os = Files.newOutputStream(trustStorePath, StandardOpenOption.TRUNCATE_EXISTING)) {
|
try (OutputStream os = Files.newOutputStream(trustStorePath, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
||||||
assertThat(sslService.sslContextHolder(config).trustManager().getTrustManager(), sameInstance(trustManager));
|
assertThat(sslService.sslContextHolder(config).sslContext(), sameInstance(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -425,7 +460,7 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final X509ExtendedTrustManager trustManager = sslService.sslContextHolder(config).trustManager().getTrustManager();
|
final SSLContext context = sslService.sslContextHolder(config).sslContext();
|
||||||
|
|
||||||
// write bad file
|
// write bad file
|
||||||
Path updatedCert = tempDir.resolve("updated.crt");
|
Path updatedCert = tempDir.resolve("updated.crt");
|
||||||
|
@ -435,53 +470,13 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||||
atomicMoveIfPossible(updatedCert, clientCertPath);
|
atomicMoveIfPossible(updatedCert, clientCertPath);
|
||||||
|
|
||||||
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
||||||
assertThat(sslService.sslContextHolder(config).trustManager().getTrustManager(), sameInstance(trustManager));
|
assertThat(sslService.sslContextHolder(config).sslContext(), sameInstance(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the trust configuration aspect of the SSLConfiguration is reloaded
|
|
||||||
*/
|
|
||||||
private void validateTrustConfigurationIsReloaded(Settings settings, Environment env,
|
|
||||||
BiConsumer<X509ExtendedTrustManager, SSLConfiguration> trustManagerPreChecks,
|
|
||||||
Runnable modificationFunction,
|
|
||||||
BiConsumer<X509ExtendedTrustManager, SSLConfiguration> trustManagerPostChecks)
|
|
||||||
throws Exception {
|
|
||||||
validateSSLConfigurationIsReloaded(settings, env, false, true, null, trustManagerPreChecks, modificationFunction, null,
|
|
||||||
trustManagerPostChecks);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the trust configuration aspect of the SSLConfiguration is reloaded
|
|
||||||
*/
|
|
||||||
private void validateKeyConfigurationIsReloaded(Settings settings, Environment env,
|
|
||||||
BiConsumer<X509ExtendedKeyManager, SSLConfiguration> keyManagerPreChecks,
|
|
||||||
Runnable modificationFunction,
|
|
||||||
BiConsumer<X509ExtendedKeyManager, SSLConfiguration> keyManagerPostChecks)
|
|
||||||
throws Exception {
|
|
||||||
validateSSLConfigurationIsReloaded(settings, env, true, false, keyManagerPreChecks, null, modificationFunction,
|
|
||||||
keyManagerPostChecks, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates that both the key and trust configuration aspects of the SSLConfiguration are reloaded
|
|
||||||
*/
|
|
||||||
private void validateSSLConfigurationIsReloaded(Settings settings, Environment env,
|
private void validateSSLConfigurationIsReloaded(Settings settings, Environment env,
|
||||||
BiConsumer<X509ExtendedKeyManager, SSLConfiguration> keyManagerPreChecks,
|
Consumer<SSLContext> preChecks,
|
||||||
BiConsumer<X509ExtendedTrustManager, SSLConfiguration> trustManagerPreChecks,
|
|
||||||
Runnable modificationFunction,
|
Runnable modificationFunction,
|
||||||
BiConsumer<X509ExtendedKeyManager, SSLConfiguration> keyManagerPostChecks,
|
Consumer<SSLContext> postChecks)
|
||||||
BiConsumer<X509ExtendedTrustManager, SSLConfiguration> trustManagerPostChecks)
|
|
||||||
throws Exception {
|
|
||||||
validateSSLConfigurationIsReloaded(settings, env, true, true, keyManagerPreChecks, trustManagerPreChecks, modificationFunction,
|
|
||||||
keyManagerPostChecks, trustManagerPostChecks);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateSSLConfigurationIsReloaded(Settings settings, Environment env, boolean checkKeys, boolean checkTrust,
|
|
||||||
BiConsumer<X509ExtendedKeyManager, SSLConfiguration> keyManagerPreChecks,
|
|
||||||
BiConsumer<X509ExtendedTrustManager, SSLConfiguration> trustManagerPreChecks,
|
|
||||||
Runnable modificationFunction,
|
|
||||||
BiConsumer<X509ExtendedKeyManager, SSLConfiguration> keyManagerPostChecks,
|
|
||||||
BiConsumer<X509ExtendedTrustManager, SSLConfiguration> trustManagerPostChecks)
|
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
||||||
final CountDownLatch reloadLatch = new CountDownLatch(1);
|
final CountDownLatch reloadLatch = new CountDownLatch(1);
|
||||||
|
@ -494,50 +489,16 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||||
reloadLatch.countDown();
|
reloadLatch.countDown();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// Baseline checks
|
||||||
final X509ExtendedKeyManager keyManager;
|
preChecks.accept(sslService.sslContextHolder(config).sslContext());
|
||||||
if (checkKeys) {
|
|
||||||
keyManager = sslService.sslContextHolder(config).keyManager().getKeyManager();
|
|
||||||
} else {
|
|
||||||
keyManager = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final X509ExtendedTrustManager trustManager;
|
|
||||||
if (checkTrust) {
|
|
||||||
trustManager = sslService.sslContextHolder(config).trustManager().getTrustManager();
|
|
||||||
} else {
|
|
||||||
trustManager = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// key manager checks
|
|
||||||
if (checkKeys) {
|
|
||||||
keyManagerPreChecks.accept(keyManager, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
// trust manager checks
|
|
||||||
if (checkTrust) {
|
|
||||||
trustManagerPreChecks.accept(trustManager, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals("nothing should have called reload", 1, reloadLatch.getCount());
|
assertEquals("nothing should have called reload", 1, reloadLatch.getCount());
|
||||||
|
|
||||||
// modify
|
// modify
|
||||||
modificationFunction.run();
|
modificationFunction.run();
|
||||||
reloadLatch.await();
|
reloadLatch.await();
|
||||||
|
// checks after reload
|
||||||
// check key manager
|
postChecks.accept(sslService.sslContextHolder(config).sslContext());
|
||||||
if (checkKeys) {
|
|
||||||
final X509ExtendedKeyManager updatedKeyManager = sslService.sslContextHolder(config).keyManager().getKeyManager();
|
|
||||||
assertThat(updatedKeyManager, not(sameInstance(keyManager)));
|
|
||||||
keyManagerPostChecks.accept(updatedKeyManager, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check trust manager
|
|
||||||
if (checkTrust) {
|
|
||||||
final X509ExtendedTrustManager updatedTrustManager = sslService.sslContextHolder(config).trustManager().getTrustManager();
|
|
||||||
assertThat(updatedTrustManager, not(sameInstance(trustManager)));
|
|
||||||
trustManagerPostChecks.accept(updatedTrustManager, config);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void atomicMoveIfPossible(Path source, Path target) throws IOException {
|
private static void atomicMoveIfPossible(Path source, Path target) throws IOException {
|
||||||
|
@ -547,4 +508,41 @@ public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||||
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
|
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static MockWebServer getSslServer(Path keyStorePath, String keyStorePass) throws KeyStoreException, CertificateException,
|
||||||
|
NoSuchAlgorithmException, IOException, KeyManagementException, UnrecoverableKeyException {
|
||||||
|
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
try(InputStream is = Files.newInputStream(keyStorePath)) {
|
||||||
|
keyStore.load(is, keyStorePass.toCharArray());
|
||||||
|
}
|
||||||
|
final SSLContext sslContext = new SSLContextBuilder().loadKeyMaterial(keyStore, keyStorePass.toCharArray())
|
||||||
|
.build();
|
||||||
|
MockWebServer server = new MockWebServer(sslContext, false);
|
||||||
|
server.enqueue(new MockResponse().setResponseCode(200).setBody("body"));
|
||||||
|
server.start();
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CloseableHttpClient getSSLClient(Path trustStorePath, String trustStorePass) throws KeyStoreException,
|
||||||
|
NoSuchAlgorithmException,
|
||||||
|
KeyManagementException, IOException, CertificateException {
|
||||||
|
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
try(InputStream is = Files.newInputStream(trustStorePath)) {
|
||||||
|
trustStore.load(is, trustStorePass.toCharArray());
|
||||||
|
}
|
||||||
|
final SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(trustStore, null).build();
|
||||||
|
return HttpClients.custom().setSSLContext(sslContext).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void privilegedConnect(CheckedRunnable<Exception> runnable) throws Exception {
|
||||||
|
try {
|
||||||
|
AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
|
||||||
|
runnable.run();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
} catch (PrivilegedActionException e) {
|
||||||
|
throw (Exception) e.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,16 +32,20 @@ import org.junit.Before;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
import javax.net.ssl.HostnameVerifier;
|
import javax.net.ssl.HostnameVerifier;
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
import javax.net.ssl.SSLParameters;
|
import javax.net.ssl.SSLParameters;
|
||||||
import javax.net.ssl.SSLSocket;
|
import javax.net.ssl.SSLSocket;
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
import javax.net.ssl.X509ExtendedTrustManager;
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
|
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
|
import java.security.KeyStore;
|
||||||
import java.security.PrivilegedActionException;
|
import java.security.PrivilegedActionException;
|
||||||
import java.security.PrivilegedExceptionAction;
|
import java.security.PrivilegedExceptionAction;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
|
@ -446,22 +450,11 @@ public class SSLServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEmptyTrustManager() throws Exception {
|
public void testEmptyTrustManager() throws Exception {
|
||||||
X509ExtendedTrustManager trustManager = new SSLService.EmptyX509TrustManager();
|
Settings settings = Settings.builder().build();
|
||||||
|
final SSLService sslService = new SSLService(settings, env);
|
||||||
|
SSLConfiguration sslConfig = new SSLConfiguration(settings);
|
||||||
|
X509ExtendedTrustManager trustManager = sslService.sslContextHolder(sslConfig).getEmptyTrustManager();
|
||||||
assertThat(trustManager.getAcceptedIssuers(), emptyArray());
|
assertThat(trustManager.getAcceptedIssuers(), emptyArray());
|
||||||
final String message = "no certificates are trusted";
|
|
||||||
CertificateException ce =
|
|
||||||
expectThrows(CertificateException.class, () -> trustManager.checkClientTrusted(null, null, (Socket) null));
|
|
||||||
assertEquals(message, ce.getMessage());
|
|
||||||
ce = expectThrows(CertificateException.class, () -> trustManager.checkClientTrusted(null, null, (SSLEngine) null));
|
|
||||||
assertEquals(message, ce.getMessage());
|
|
||||||
ce = expectThrows(CertificateException.class, () -> trustManager.checkClientTrusted(null, null));
|
|
||||||
assertEquals(message, ce.getMessage());
|
|
||||||
ce = expectThrows(CertificateException.class, () -> trustManager.checkServerTrusted(null, null, (Socket) null));
|
|
||||||
assertEquals(message, ce.getMessage());
|
|
||||||
ce = expectThrows(CertificateException.class, () -> trustManager.checkServerTrusted(null, null, (SSLEngine) null));
|
|
||||||
assertEquals(message, ce.getMessage());
|
|
||||||
ce = expectThrows(CertificateException.class, () -> trustManager.checkServerTrusted(null, null));
|
|
||||||
assertEquals(message, ce.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReadCertificateInformation() throws Exception {
|
public void testReadCertificateInformation() throws Exception {
|
||||||
|
|
Loading…
Reference in New Issue