From 2b09e90237f825d8bdc45f519b8d6f20eb989056 Mon Sep 17 00:00:00 2001 From: Ioannis Kakavas Date: Wed, 16 May 2018 08:32:13 +0300 Subject: [PATCH] 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. --- .../xpack/core/ssl/SSLService.java | 297 ++-------- .../ssl/SSLConfigurationReloaderTests.java | 520 +++++++++--------- .../xpack/core/ssl/SSLServiceTests.java | 23 +- 3 files changed, 317 insertions(+), 523 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLService.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLService.java index c59a2889c28..e5150e3faad 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLService.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLService.java @@ -8,7 +8,6 @@ package org.elasticsearch.xpack.core.ssl; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; import org.apache.lucene.util.SetOnce; -import org.bouncycastle.operator.OperatorCreationException; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.CheckedSupplier; 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 javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSessionContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; -import javax.security.auth.DestroyFailedException; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.security.GeneralSecurityException; import java.security.KeyManagementException; -import java.security.KeyStoreException; +import java.security.KeyStore; 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.Arrays; import java.util.Collection; @@ -54,6 +49,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; 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 * for use later */ - public SSLService(Settings settings, Environment environment) throws CertificateException, UnrecoverableKeyException, - NoSuchAlgorithmException, IOException, DestroyFailedException, KeyStoreException, OperatorCreationException { + public SSLService(Settings settings, Environment environment) { super(settings); this.env = environment; this.globalSSLConfiguration = new SSLConfiguration(settings.getByPrefix(XPackSettings.GLOBAL_SSL_PREFIX)); @@ -403,10 +398,8 @@ public class SSLService extends AbstractComponent { if (logger.isDebugEnabled()) { logger.debug("using ssl settings [{}]", sslConfiguration); } - ReloadableTrustManager trustManager = - new ReloadableTrustManager(sslConfiguration.trustConfig().createTrustManager(env), sslConfiguration.trustConfig()); - ReloadableX509KeyManager keyManager = - new ReloadableX509KeyManager(sslConfiguration.keyConfig().createKeyManager(env), sslConfiguration.keyConfig()); + X509ExtendedTrustManager trustManager = sslConfiguration.trustConfig().createTrustManager(env); + X509ExtendedKeyManager keyManager = sslConfiguration.keyConfig().createKeyManager(env); return createSslContext(keyManager, trustManager, sslConfiguration); } @@ -417,7 +410,7 @@ public class SSLService extends AbstractComponent { * @param trustManager the trust manager to use * @return the created SSLContext */ - private SSLContextHolder createSslContext(ReloadableX509KeyManager keyManager, ReloadableTrustManager trustManager, + private SSLContextHolder createSslContext(X509ExtendedKeyManager keyManager, X509ExtendedTrustManager trustManager, SSLConfiguration sslConfiguration) { // Initialize sslContext 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 supportedCiphers(sslContext.getSupportedSSLParameters().getCipherSuites(), sslConfiguration.cipherSuites(), true); - return new SSLContextHolder(sslContext, trustManager, keyManager); + return new SSLContextHolder(sslContext, sslConfiguration); } catch (NoSuchAlgorithmException | KeyManagementException 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. */ - Map loadSSLConfigurations() throws CertificateException, - UnrecoverableKeyException, NoSuchAlgorithmException, IOException, DestroyFailedException, KeyStoreException, - OperatorCreationException { + Map loadSSLConfigurations() { Map sslConfigurations = new HashMap<>(); 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; - private final TrustConfig trustConfig; - - 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; + final class SSLContextHolder { + private volatile SSLContext context; private final KeyConfig keyConfig; + private final TrustConfig trustConfig; + private final SSLConfiguration sslConfiguration; - ReloadableX509KeyManager(X509ExtendedKeyManager keyManager, KeyConfig keyConfig) { - 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) { + SSLContextHolder(SSLContext context, SSLConfiguration sslConfiguration) { this.context = context; - this.trustManager = trustManager; - this.keyManager = keyManager; + this.sslConfiguration = sslConfiguration; + this.keyConfig = sslConfiguration.keyConfig(); + this.trustConfig = sslConfiguration.trustConfig(); } SSLContext sslContext() { 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} */ - private static void invalidateSessions(SSLSessionContext sslSessionContext) { + private void invalidateSessions(SSLSessionContext sslSessionContext) { Enumeration sessionIds = sslSessionContext.getIds(); while (sessionIds.hasMoreElements()) { byte[] sessionId = sessionIds.nextElement(); sslSessionContext.getSession(sessionId).invalidate(); } } - } - /** - * This is an empty key manager that is used in case a loaded key manager is null - */ - private static final class EmptyKeyManager extends X509ExtendedKeyManager { - - @Override - public String[] getClientAliases(String s, Principal[] principals) { - return new String[0]; + synchronized void reload() { + invalidateSessions(context.getClientSessionContext()); + invalidateSessions(context.getServerSessionContext()); + reloadSslContext(); } - @Override - public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { - return null; + private void reloadSslContext() { + try { + X509ExtendedKeyManager loadedKeyManager = Optional.ofNullable(keyConfig.createKeyManager(env)). + orElse(getEmptyKeyManager()); + X509ExtendedTrustManager loadedTrustManager = Optional.ofNullable(trustConfig.createTrustManager(env)). + orElse(getEmptyTrustManager()); + SSLContext loadedSslContext = SSLContext.getInstance(sslContextAlgorithm(sslConfiguration.supportedProtocols())); + loadedSslContext.init(new X509ExtendedKeyManager[]{loadedKeyManager}, + new X509ExtendedTrustManager[]{loadedTrustManager}, null); + supportedCiphers(loadedSslContext.getSupportedSSLParameters().getCipherSuites(), sslConfiguration.cipherSuites(), false); + this.context = loadedSslContext; + } catch (GeneralSecurityException | IOException e) { + throw new ElasticsearchException("failed to initialize the SSLContext", e); + } + } + X509ExtendedKeyManager getEmptyKeyManager() throws GeneralSecurityException, IOException { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, null); + return (X509ExtendedKeyManager) keyManagerFactory.getKeyManagers()[0]; } - @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; - } - } - - /** - * This is an empty trust manager that is used in case a loaded trust manager is null - */ - static final class EmptyX509TrustManager extends X509ExtendedTrustManager { - - @Override - public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException { - throw new CertificateException("no certificates are trusted"); - } - - @Override - public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException { - throw new CertificateException("no certificates are trusted"); - } - - @Override - 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]; + X509ExtendedTrustManager getEmptyTrustManager() throws GeneralSecurityException, IOException { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509"); + trustManagerFactory.init(keyStore); + return (X509ExtendedTrustManager) trustManagerFactory.getTrustManagers()[0]; } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationReloaderTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationReloaderTests.java index f19f13d38b7..2ccbd549105 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationReloaderTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationReloaderTests.java @@ -5,25 +5,33 @@ */ 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.JcePEMEncryptorBuilder; +import org.elasticsearch.common.CheckedRunnable; import org.elasticsearch.common.settings.MockSecureSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.env.TestEnvironment; 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.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; import org.junit.After; import org.junit.Before; -import javax.net.ssl.X509ExtendedKeyManager; -import javax.net.ssl.X509ExtendedTrustManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; import javax.security.auth.x500.X500Principal; +import java.io.BufferedWriter; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; @@ -32,20 +40,23 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; 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.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.CertificateException; import java.security.cert.X509Certificate; 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.equalTo; -import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.sameInstance; -import static org.hamcrest.core.Is.is; /** * 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 - * config and trust config is checked. + * Tests reloading a keystore that is used in the KeyManager of SSLContext */ public void testReloadingKeyStore() throws Exception { final Path tempDir = createTempDir(); @@ -86,127 +96,127 @@ public class SSLConfigurationReloaderTests extends ESTestCase { .setSecureSettings(secureSettings) .build(); final Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings); - - final BiConsumer keyManagerPreChecks = (keyManager, config) -> { - // key manager checks - String[] aliases = keyManager.getServerAliases("RSA", null); - assertNotNull(aliases); - assertThat(aliases.length, is(1)); - assertThat(aliases[0], is("testnode")); - }; - - final SetOnce trustedCount = new SetOnce<>(); - final BiConsumer trustManagerPreChecks = (trustManager, config) -> { - // trust manager checks - Certificate[] certificates = trustManager.getAcceptedIssuers(); - trustedCount.set(certificates.length); - }; - - final Runnable modifier = () -> { - try { - // modify it - KeyStore keyStore = KeyStore.getInstance("jks"); - keyStore.load(null, null); - final KeyPair keyPair = CertUtils.generateKeyPair(512); - X509Certificate cert = CertUtils.generateSignedCertificate(new X500Principal("CN=testReloadingKeyStore"), null, keyPair, - null, null, 365); - keyStore.setKeyEntry("key", keyPair.getPrivate(), "testnode".toCharArray(), new X509Certificate[] { cert }); - Path updated = tempDir.resolve("updated.jks"); - try (OutputStream out = Files.newOutputStream(updated)) { - keyStore.store(out, "testnode".toCharArray()); + //Load HTTPClient only once. Client uses the same store as a truststore + try (CloseableHttpClient client = getSSLClient(keystorePath, "testnode")) { + final Consumer keyMaterialPreChecks = (context) -> { + try (MockWebServer server = new MockWebServer(context, true)) { + server.enqueue(new MockResponse().setResponseCode(200).setBody("body")); + server.start(); + 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); } - atomicMoveIfPossible(updated, keystorePath); - } catch (Exception e) { - throw new RuntimeException("modification failed", e); - } - }; + }; - final BiConsumer keyManagerPostChecks = (updatedKeyManager, config) -> { - String[] aliases = updatedKeyManager.getServerAliases("RSA", null); - assertNotNull(aliases); - assertThat(aliases.length, is(1)); - assertThat(aliases[0], is("key")); - }; - final BiConsumer trustManagerPostChecks = (updatedTrustManager, config) -> { - assertThat(trustedCount.get() - updatedTrustManager.getAcceptedIssuers().length, is(5)); - }; - validateSSLConfigurationIsReloaded(settings, env, keyManagerPreChecks, trustManagerPreChecks, modifier, keyManagerPostChecks, - trustManagerPostChecks); + final Runnable modifier = () -> { + try { + // modify the keystore that the KeyManager uses + KeyStore keyStore = KeyStore.getInstance("jks"); + keyStore.load(null, null); + final KeyPair keyPair = CertUtils.generateKeyPair(512); + X509Certificate cert = CertUtils.generateSignedCertificate(new X500Principal("CN=localhost"), null, keyPair, + null, null, 365); + keyStore.setKeyEntry("key", keyPair.getPrivate(), "testnode".toCharArray(), new X509Certificate[]{cert}); + Path updated = tempDir.resolve("updated.jks"); + try (OutputStream out = Files.newOutputStream(updated)) { + keyStore.store(out, "testnode".toCharArray()); + } + atomicMoveIfPossible(updated, keystorePath); + } catch (Exception e) { + throw new RuntimeException("modification failed", e); + } + }; + // The new server certificate is not in the client's truststore so SSLHandshake should fail + final Consumer keyMaterialPostChecks = (updatedContext) -> { + try (MockWebServer server = new MockWebServer(updatedContext, true)) { + server.enqueue(new MockResponse().setResponseCode(200).setBody("body")); + server.start(); + 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); + } + }; + validateSSLConfigurationIsReloaded(settings, env, keyMaterialPreChecks, modifier, keyMaterialPostChecks); + } } /** - * 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 - * test. + * Tests the reloading of SSLContext when a PEM key and certificate are used. */ - public void testPEMKeyConfigReloading() throws Exception { - Path tempDir = createTempDir(); - Path keyPath = tempDir.resolve("testnode.pem"); - Path certPath = tempDir.resolve("testnode.crt"); - Path clientCertPath = tempDir.resolve("testclient.crt"); + public void testPEMKeyCertConfigReloading() throws Exception { + final Path tempDir = createTempDir(); + final Path keyPath = tempDir.resolve("testnode.pem"); + final Path certPath = tempDir.resolve("testnode.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.crt"), certPath); - Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath); MockSecureSettings secureSettings = new MockSecureSettings(); secureSettings.setString("xpack.ssl.secure_key_passphrase", "testnode"); final Settings settings = Settings.builder() - .put("path.home", createTempDir()) - .put("xpack.ssl.key", keyPath) - .put("xpack.ssl.certificate", certPath) - .putList("xpack.ssl.certificate_authorities", certPath.toString(), clientCertPath.toString()) - .setSecureSettings(secureSettings) - .build(); + .put("path.home", createTempDir()) + .put("xpack.ssl.key", keyPath) + .put("xpack.ssl.certificate", certPath) + .setSecureSettings(secureSettings) + .build(); final Environment env = randomBoolean() ? null : - TestEnvironment.newEnvironment(Settings.builder().put("path.home", createTempDir()).build()); - - final SetOnce privateKey = new SetOnce<>(); - final BiConsumer keyManagerPreChecks = (keyManager, config) -> { - String[] aliases = keyManager.getServerAliases("RSA", null); - assertNotNull(aliases); - assertThat(aliases.length, is(1)); - assertThat(aliases[0], is("key")); - privateKey.set(keyManager.getPrivateKey("key")); - assertNotNull(privateKey.get()); - }; - - final KeyPair keyPair = CertUtils.generateKeyPair(randomFrom(1024, 2048)); - final Runnable modifier = () -> { - 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 - // size is the same! - assertTrue(awaitBusy(() -> { - 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"); - try (OutputStream os = Files.newOutputStream(updatedKeyPath); - OutputStreamWriter osWriter = new OutputStreamWriter(os, StandardCharsets.UTF_8); - JcaPEMWriter writer = new JcaPEMWriter(osWriter)) { - writer.writeObject(keyPair, - new JcePEMEncryptorBuilder("DES-EDE3-CBC").setProvider(CertUtils.BC_PROV).build("testnode".toCharArray())); + TestEnvironment.newEnvironment(Settings.builder().put("path.home", createTempDir()).build()); + // Load HTTPClient once. Client uses a keystore containing testnode key/cert as a truststore + try (CloseableHttpClient client = getSSLClient(clientTruststorePath, "testnode")) { + final Consumer keyMaterialPreChecks = (context) -> { + try (MockWebServer server = new MockWebServer(context, false)) { + server.enqueue(new MockResponse().setResponseCode(200).setBody("body")); + server.start(); + 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); } - atomicMoveIfPossible(updatedKeyPath, keyPath); - } catch (Exception e) { - throw new RuntimeException("failed to modify file", e); - } - }; + }; + final Runnable modifier = () -> { + try { + final KeyPair keyPair = CertUtils.generateKeyPair(512); + X509Certificate cert = CertUtils.generateSignedCertificate(new X500Principal("CN=localhost"), null, keyPair, + null, null, 365); + Path updatedKeyPath = tempDir.resolve("updated.pem"); + Path updatedCertPath = tempDir.resolve("updated.crt"); + try (OutputStream os = Files.newOutputStream(updatedKeyPath); + OutputStreamWriter osWriter = new OutputStreamWriter(os, StandardCharsets.UTF_8); + JcaPEMWriter writer = new JcaPEMWriter(osWriter)) { + writer.writeObject(keyPair, + 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(updatedCertPath, certPath); + } catch (Exception 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 Consumer keyMaterialPostChecks = (updatedContext) -> { + try (MockWebServer server = new MockWebServer(updatedContext, false)) { + server.enqueue(new MockResponse().setResponseCode(200).setBody("body")); + server.start(); + 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 keyManagerPostChecks = (keyManager, config) -> { - String[] aliases = keyManager.getServerAliases("RSA", null); - assertNotNull(aliases); - assertThat(aliases.length, is(1)); - assertThat(aliases[0], is("key")); - assertThat(keyManager.getPrivateKey(aliases[0]), not(equalTo(privateKey))); - assertThat(keyManager.getPrivateKey(aliases[0]), is(equalTo(keyPair.getPrivate()))); - }; - 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 { Path tempDir = createTempDir(); @@ -215,80 +225,106 @@ public class SSLConfigurationReloaderTests extends ESTestCase { MockSecureSettings secureSettings = new MockSecureSettings(); secureSettings.setString("xpack.ssl.truststore.secure_password", "testnode"); Settings settings = Settings.builder() - .put("xpack.ssl.truststore.path", trustStorePath) - .put("path.home", createTempDir()) - .setSecureSettings(secureSettings) - .build(); + .put("xpack.ssl.truststore.path", trustStorePath) + .put("path.home", createTempDir()) + .setSecureSettings(secureSettings) + .build(); Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings); - - final SetOnce trustedCount = new SetOnce<>(); - final BiConsumer trustManagerPreChecks = (trustManager, config) -> { - // trust manager checks - Certificate[] certificates = trustManager.getAcceptedIssuers(); - trustedCount.set(certificates.length); - }; - - - final Runnable modifier = () -> { - try { - Path updatedTruststore = tempDir.resolve("updated.jks"); - KeyStore keyStore = KeyStore.getInstance("jks"); - keyStore.load(null, null); - try (OutputStream out = Files.newOutputStream(updatedTruststore)) { - keyStore.store(out, "testnode".toCharArray()); + // Create the MockWebServer once for both pre and post checks + try(MockWebServer server = getSslServer(trustStorePath, "testnode")){ + final Consumer trustMaterialPreChecks = (context) -> { + try (CloseableHttpClient client = HttpClients.custom().setSSLContext(context).build()){ + privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close()); + } catch (Exception e) { + throw new RuntimeException("Error connecting to the mock server", e); } - atomicMoveIfPossible(updatedTruststore, trustStorePath); - } catch (Exception e) { - throw new RuntimeException("failed to modify file", e); - } - }; + }; - final BiConsumer trustManagerPostChecks = (updatedTrustManager, config) -> { - assertThat(trustedCount.get() - updatedTrustManager.getAcceptedIssuers().length, is(6)); - }; + final Runnable modifier = () -> { + try { + Path updatedTrustStore = tempDir.resolve("updated.jks"); + KeyStore keyStore = KeyStore.getInstance("jks"); + keyStore.load(null, null); + 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()); + } + atomicMoveIfPossible(updatedTrustStore, trustStorePath); + } catch (Exception e) { + throw new RuntimeException("failed to modify file", e); + } + }; - validateTrustConfigurationIsReloaded(settings, env, trustManagerPreChecks, modifier, trustManagerPostChecks); + // Client's truststore doesn't contain the server's certificate anymore so SSLHandshake should fail + final Consumer 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); + } + }; + + 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 - * trust config in this test. + * Test the reloading of SSLContext whose trust config is backed by PEM certificate files. */ public void testReloadingPEMTrustConfig() throws Exception { Path tempDir = createTempDir(); - Path clientCertPath = tempDir.resolve("testclient.crt"); - Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath); + Path clientCertPath = tempDir.resolve("testnode.crt"); + 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() .putList("xpack.ssl.certificate_authorities", clientCertPath.toString()) .put("path.home", createTempDir()) .build(); Environment env = randomBoolean() ? null : TestEnvironment.newEnvironment(settings); + // Create the MockWebServer once for both pre and post checks + try(MockWebServer server = getSslServer(keyStorePath, "testnode")){ + final Consumer trustMaterialPreChecks = (context) -> { + try (CloseableHttpClient client = HttpClients.custom().setSSLContext(context).build()){ + privilegedConnect(() -> client.execute(new HttpGet("https://localhost:" + server.getPort())).close()); + } catch (Exception e) { + throw new RuntimeException("Exception connecting to the mock server", e); + } + }; - final BiConsumer trustManagerPreChecks = (trustManager, config) -> { - // trust manager checks - Certificate[] certificates = trustManager.getAcceptedIssuers(); - assertThat(certificates.length, is(1)); - assertThat(((X509Certificate)certificates[0]).getSubjectX500Principal().getName(), containsString("Test Client")); - }; + final Runnable modifier = () -> { + try { + final KeyPair keyPair = CertUtils.generateKeyPair(512); + X509Certificate cert = CertUtils.generateSignedCertificate(new X500Principal("CN=localhost"), null, keyPair, + null, null, 365); + 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) { + throw new RuntimeException("failed to modify file", e); + } + }; + // Client doesn't trust the Server certificate anymore so SSLHandshake should fail + final Consumer 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); + } + }; - final Runnable modifier = () -> { - try { - Path updatedCert = tempDir.resolve("updated.crt"); - Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), updatedCert, - StandardCopyOption.REPLACE_EXISTING); - atomicMoveIfPossible(updatedCert, clientCertPath); - } catch (Exception e) { - throw new RuntimeException("failed to modify file", e); - } - }; - - final BiConsumer trustManagerPostChecks = (updatedTrustManager, config) -> { - Certificate[] updatedCerts = updatedTrustManager.getAcceptedIssuers(); - assertThat(updatedCerts.length, is(1)); - assertThat(((X509Certificate)updatedCerts[0]).getSubjectX500Principal().getName(), containsString("Test Node")); - }; - - 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 X509ExtendedKeyManager keyManager = sslService.sslContextHolder(config).keyManager().getKeyManager(); + final SSLContext context = sslService.sslContextHolder(config).sslContext(); // truncate the keystore try (OutputStream out = Files.newOutputStream(keystorePath, StandardOpenOption.TRUNCATE_EXISTING)) { } // 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 try (OutputStream os = Files.newOutputStream(keyPath, StandardOpenOption.TRUNCATE_EXISTING)) { } // 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 try (OutputStream os = Files.newOutputStream(trustStorePath, StandardOpenOption.TRUNCATE_EXISTING)) { } // 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 Path updatedCert = tempDir.resolve("updated.crt"); @@ -435,53 +470,13 @@ public class SSLConfigurationReloaderTests extends ESTestCase { atomicMoveIfPossible(updatedCert, clientCertPath); // 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 trustManagerPreChecks, - Runnable modificationFunction, - BiConsumer 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 keyManagerPreChecks, - Runnable modificationFunction, - BiConsumer 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, - BiConsumer keyManagerPreChecks, - BiConsumer trustManagerPreChecks, + Consumer preChecks, Runnable modificationFunction, - BiConsumer keyManagerPostChecks, - BiConsumer 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 keyManagerPreChecks, - BiConsumer trustManagerPreChecks, - Runnable modificationFunction, - BiConsumer keyManagerPostChecks, - BiConsumer trustManagerPostChecks) + Consumer postChecks) throws Exception { final CountDownLatch reloadLatch = new CountDownLatch(1); @@ -494,50 +489,16 @@ public class SSLConfigurationReloaderTests extends ESTestCase { reloadLatch.countDown(); } }; - - final X509ExtendedKeyManager keyManager; - 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); - } + // Baseline checks + preChecks.accept(sslService.sslContextHolder(config).sslContext()); assertEquals("nothing should have called reload", 1, reloadLatch.getCount()); // modify modificationFunction.run(); reloadLatch.await(); - - // check key manager - 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); - } + // checks after reload + postChecks.accept(sslService.sslContextHolder(config).sslContext()); } 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); } } + + 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 runnable) throws Exception { + try { + AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + runnable.run(); + return null; + }); + } catch (PrivilegedActionException e) { + throw (Exception) e.getCause(); + } + } + } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLServiceTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLServiceTests.java index 598a0f8a77a..bcb4b638654 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLServiceTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLServiceTests.java @@ -32,16 +32,20 @@ import org.junit.Before; import org.mockito.ArgumentCaptor; import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedTrustManager; import java.net.Socket; import java.nio.file.Path; import java.security.AccessController; +import java.security.KeyStore; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.cert.CertificateException; @@ -446,22 +450,11 @@ public class SSLServiceTests extends ESTestCase { } 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()); - 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 {