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:
Ioannis Kakavas 2018-05-16 08:32:13 +03:00 committed by GitHub
parent fe3e0257ae
commit 2b09e90237
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 317 additions and 523 deletions

View File

@ -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];
} }
} }

View File

@ -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();
}
}
} }

View File

@ -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 {