From 4e208a75bd00f78cced7460a859f524c1f73fca1 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Fri, 16 Jul 2021 17:08:09 +1000 Subject: [PATCH] Support filtering of KeyStore entries --- .../common/ssl/KeyStoreUtil.java | 36 ++++++++++- .../common/ssl/SslConfigurationLoader.java | 14 ++++- .../common/ssl/StoreKeyConfig.java | 43 ++++++++++++-- .../common/ssl/KeyStoreUtilTests.java | 59 +++++++++++++++++++ .../common/ssl/StoreKeyConfigTests.java | 33 ++++++++--- .../xpack/core/ssl/SSLService.java | 38 +++++++++++- .../xpack/core/ssl/SslSettingsLoader.java | 15 ++++- .../core/ssl/SslSettingsLoaderTests.java | 57 +++++++++++++++--- 8 files changed, 269 insertions(+), 26 deletions(-) create mode 100644 libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/KeyStoreUtilTests.java diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/KeyStoreUtil.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/KeyStoreUtil.java index 2a29e931dffa2..5a8bf1d126065 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/KeyStoreUtil.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/KeyStoreUtil.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.Locale; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -89,6 +90,17 @@ public static KeyStore buildKeyStore(Collection certificateChain, P return keyStore; } + /** + * Filters a keystore using a predicate. + * The provided keystore is modified in place. + */ + public static KeyStore filter(KeyStore store, Predicate filter) { + stream(store, e -> new SslConfigException("Failed to apply filter to existing keystore", e)) + .filter(filter.negate()) + .forEach(e -> e.delete()); + return store; + } + /** * Construct an in-memory keystore with multiple trusted cert entries. * @@ -170,7 +182,7 @@ public static X509ExtendedTrustManager createTrustManager(Collection stream(KeyStore keyStore, + public static Stream stream(KeyStore keyStore, Function exceptionHandler) { try { return Collections.list(keyStore.aliases()).stream().map(a -> new KeyStoreEntry(keyStore, a, exceptionHandler)); @@ -179,7 +191,7 @@ static Stream stream(KeyStore keyStore, } } - static class KeyStoreEntry { + public static class KeyStoreEntry { private final KeyStore store; private final String alias; private final Function exceptionHandler; @@ -270,6 +282,26 @@ public List getX509CertificateChain() { } } + public void delete() { + try { + store.deleteEntry(alias); + } catch (KeyStoreException e) { + throw exceptionHandler.apply(e); + } + } + + public void copyTo(KeyStore otherStore, char[] keyPassword) { + try { + if (store.isKeyEntry(alias)) { + final Key key = store.getKey(alias, keyPassword); + otherStore.setKeyEntry(alias, key, keyPassword, store.getCertificateChain(alias)); + } else if (store.isCertificateEntry(alias)) { + otherStore.setCertificateEntry(alias, store.getCertificate(alias)); + } + } catch (GeneralSecurityException e) { + throw exceptionHandler.apply(e); + } + } } diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java index f904a152b09c0..2935666440d4e 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java @@ -13,6 +13,7 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; import java.nio.file.Path; +import java.security.KeyStore; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -166,6 +167,8 @@ public abstract class SslConfigurationLoader { private List defaultCiphers; private List defaultProtocols; + private Function keyStoreFilter; + /** * Construct a new loader with the "standard" default values. * @@ -235,6 +238,15 @@ public void setDefaultProtocols(List defaultProtocols) { this.defaultProtocols = defaultProtocols; } + + /** + * Apply a filter function to any keystore that is loaded. + * @see StoreKeyConfig + */ + public void setKeyStoreFilter(Function keyStoreFilter) { + this.keyStoreFilter = keyStoreFilter; + } + /** * Clients of this class should implement this method to determine whether there are any settings for a given prefix. * This is used to populate {@link SslConfiguration#isExplicitlyConfigured()}. @@ -363,7 +375,7 @@ public SslKeyConfig buildKeyConfig(Path basePath) { } final String storeType = resolveSetting(KEYSTORE_TYPE, Function.identity(), inferKeyStoreType(keyStorePath)); final String algorithm = resolveSetting(KEYSTORE_ALGORITHM, Function.identity(), KeyManagerFactory.getDefaultAlgorithm()); - return new StoreKeyConfig(keyStorePath, storePassword, storeType, keyPassword, algorithm, basePath); + return new StoreKeyConfig(keyStorePath, storePassword, storeType, keyStoreFilter, keyPassword, algorithm, basePath); } return defaultKeyConfig; diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/StoreKeyConfig.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/StoreKeyConfig.java index 15f61a61cd28e..ca16c30c7a8c6 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/StoreKeyConfig.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/StoreKeyConfig.java @@ -8,6 +8,7 @@ package org.elasticsearch.common.ssl; +import org.elasticsearch.core.Nullable; import org.elasticsearch.core.Tuple; import javax.net.ssl.KeyManagerFactory; @@ -28,6 +29,7 @@ import java.util.Enumeration; import java.util.List; import java.util.Objects; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -37,6 +39,7 @@ public class StoreKeyConfig implements SslKeyConfig { private final String keystorePath; private final String type; private final char[] storePassword; + private final Function filter; private final char[] keyPassword; private final String algorithm; private final Path configBasePath; @@ -46,18 +49,21 @@ public class StoreKeyConfig implements SslKeyConfig { * @param storePassword The password for the keystore * @param type The {@link KeyStore#getType() type} of the keystore (typically "PKCS12" or "jks"). * See {@link KeyStoreUtil#inferKeyStoreType}. + * @param filter A function to process the keystore after it is loaded. See {@link KeyStoreUtil#filter} * @param keyPassword The password for the key(s) within the keystore - * (see {@link javax.net.ssl.KeyManagerFactory#init(KeyStore, char[])}). + * (see {@link KeyManagerFactory#init(KeyStore, char[])}). * @param algorithm The algorithm to use for the Key Manager (see {@link KeyManagerFactory#getAlgorithm()}). * @param configBasePath The base path for configuration files (used for error handling) */ - public StoreKeyConfig(String path, char[] storePassword, String type, char[] keyPassword, String algorithm, Path configBasePath) { + public StoreKeyConfig(String path, char[] storePassword, String type, @Nullable Function filter, + char[] keyPassword, String algorithm, Path configBasePath) { + this.keystorePath = Objects.requireNonNull(path, "Keystore path cannot be null"); this.storePassword = Objects.requireNonNull(storePassword, "Keystore password cannot be null (but may be empty)"); + this.type = Objects.requireNonNull(type, "Keystore type cannot be null"); + this.filter = filter; this.keyPassword = Objects.requireNonNull(keyPassword, "Key password cannot be null (but may be empty)"); this.algorithm = Objects.requireNonNull(algorithm, "Keystore algorithm cannot be null"); this.configBasePath = Objects.requireNonNull(configBasePath, "Config path cannot be null"); - this.keystorePath = Objects.requireNonNull(path, "Keystore path cannot be null"); - this.type = Objects.requireNonNull(type, "Keystore type cannot be null"); } @Override @@ -80,10 +86,23 @@ private Path resolvePath() { return configBasePath.resolve(keystorePath); } + /** + * Equivalent to {@link #getKeys(boolean) getKeys(false)}. + */ @Override public List> getKeys() { + return getKeys(false); + } + + /** + * Return the list of keys inside the configured keystore, optionally applying the {@code filter} that was set during construction. + */ + public List> getKeys(boolean filterKeystore) { final Path path = resolvePath(); - final KeyStore keyStore = readKeyStore(path); + KeyStore keyStore = readKeyStore(path); + if (filterKeystore) { + keyStore = this.processKeyStore(keyStore); + } return KeyStoreUtil.stream(keyStore, ex -> keystoreException(path, ex)) .filter(KeyStoreUtil.KeyStoreEntry::isKeyEntry) .map(entry -> { @@ -122,7 +141,8 @@ public X509ExtendedKeyManager createKeyManager() { private X509ExtendedKeyManager createKeyManager(Path path) { try { - final KeyStore keyStore = readKeyStore(path); + KeyStore keyStore = readKeyStore(path); + keyStore = processKeyStore(keyStore); checkKeyStore(keyStore, path); return KeyStoreUtil.createKeyManager(keyStore, keyPassword, algorithm); } catch (GeneralSecurityException e) { @@ -130,6 +150,17 @@ private X509ExtendedKeyManager createKeyManager(Path path) { } } + private KeyStore processKeyStore(KeyStore keyStore) { + if (filter == null) { + return keyStore; + } + final KeyStore processedKeystore = filter.apply(keyStore); + if (processedKeystore != null) { + return processedKeystore; + } + return keyStore; + } + private KeyStore readKeyStore(Path path) { try { return KeyStoreUtil.readKeyStore(path, type, storePassword); diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/KeyStoreUtilTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/KeyStoreUtilTests.java new file mode 100644 index 0000000000000..c586ead12b002 --- /dev/null +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/KeyStoreUtilTests.java @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.common.ssl; + +import org.elasticsearch.test.ESTestCase; + +import java.nio.file.Path; +import java.security.KeyStore; +import java.util.Collections; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; + +public class KeyStoreUtilTests extends ESTestCase { + private static final char[] P12_PASS = "p12-pass".toCharArray(); + + public void testFilter() throws Exception { + assumeFalse("Can't use PKCS#12 keystores in a FIPS JVM", inFipsJvm()); + + final Path p12 = getDataPath("/certs/cert-all/certs.p12"); + final KeyStore original = KeyStoreUtil.readKeyStore(p12, "PKCS12", P12_PASS); + + // No-op filter + final KeyStore clone = KeyStoreUtil.filter(KeyStoreUtil.readKeyStore(p12, "PKCS12", P12_PASS), entry -> true); + assertThat(Collections.list(clone.aliases()), containsInAnyOrder("cert1", "cert2")); + assertSameEntry(original, clone, "cert1", P12_PASS); + assertSameEntry(original, clone, "cert2", P12_PASS); + + // Filter by alias + final KeyStore cert1 = KeyStoreUtil.filter( + KeyStoreUtil.readKeyStore(p12, "PKCS12", P12_PASS), + entry -> entry.getAlias().equals("cert1") + ); + assertThat(Collections.list(cert1.aliases()), containsInAnyOrder("cert1")); + assertSameEntry(original, cert1, "cert1", P12_PASS); + + // Filter by cert + final KeyStore cert2 = KeyStoreUtil.filter( + KeyStoreUtil.readKeyStore(p12, "PKCS12", P12_PASS), + entry -> entry.getX509Certificate().getSubjectX500Principal().getName().equals("CN=cert2") + ); + assertThat(Collections.list(cert2.aliases()), containsInAnyOrder("cert2")); + assertSameEntry(original, cert2, "cert2", P12_PASS); + } + + private void assertSameEntry(KeyStore ks1, KeyStore ks2, String alias, char[] keyPassword) throws Exception { + assertThat(ks1.isKeyEntry(alias), equalTo(ks2.isKeyEntry(alias))); + assertThat(ks1.isCertificateEntry(alias), equalTo(ks2.isCertificateEntry(alias))); + assertThat(ks1.getCertificate(alias), equalTo(ks2.getCertificate(alias))); + assertThat(ks1.getCertificateChain(alias), equalTo(ks2.getCertificateChain(alias))); + assertThat(ks1.getKey(alias, P12_PASS), equalTo(ks2.getKey(alias, keyPassword))); + } +} diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java index cffb0a95ec4c9..ea0b8248b4a42 100644 --- a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/StoreKeyConfigTests.java @@ -19,11 +19,13 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.security.GeneralSecurityException; +import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.List; +import java.util.function.Function; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.X509ExtendedKeyManager; @@ -44,6 +46,8 @@ public class StoreKeyConfigTests extends ESTestCase { private static final char[] P12_PASS = "p12-pass".toCharArray(); private static final char[] JKS_PASS = "jks-pass".toCharArray(); + private static final String KEY_MGR_ALGORITHM = KeyManagerFactory.getDefaultAlgorithm(); + private static final char[] KEY_PASS = "key-pass".toCharArray(); private Path configBasePath; @@ -68,11 +72,23 @@ public void testLoadMultipleKeyPKCS12() throws Exception { assertKeysLoaded(keyConfig, "cert1", "cert2"); } + public void testFilterMultipleKeyPKCS12() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + final Path p12 = getDataPath("/certs/cert-all/certs.p12"); + final StoreKeyConfig keyConfig = config( + p12, + P12_PASS, + "PKCS12", + ks -> KeyStoreUtil.filter(ks, entry -> entry.getAlias().equals("cert1")) + ); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(p12)); + assertKeysLoaded(keyConfig, "cert1"); + } + public void testLoadMultipleKeyJksWithSeparateKeyPassword() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final String jks = "cert-all/certs.jks"; - final StoreKeyConfig keyConfig = new StoreKeyConfig(jks, JKS_PASS, "jks", "key-pass".toCharArray(), - KeyManagerFactory.getDefaultAlgorithm(), configBasePath); + final StoreKeyConfig keyConfig = new StoreKeyConfig(jks, JKS_PASS, "jks", null, KEY_PASS, KEY_MGR_ALGORITHM, configBasePath); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(configBasePath.resolve(jks))); assertKeysLoaded(keyConfig, "cert1", "cert2"); } @@ -80,8 +96,7 @@ public void testLoadMultipleKeyJksWithSeparateKeyPassword() throws Exception { public void testKeyManagerFailsWithIncorrectStorePassword() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final String jks = "cert-all/certs.jks"; - final StoreKeyConfig keyConfig = new StoreKeyConfig(jks, P12_PASS, "jks", "key-pass".toCharArray(), - KeyManagerFactory.getDefaultAlgorithm(), configBasePath); + final StoreKeyConfig keyConfig = new StoreKeyConfig(jks, P12_PASS, "jks", null, KEY_PASS, KEY_MGR_ALGORITHM, configBasePath); final Path path = configBasePath.resolve(jks); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(path)); assertPasswordIsIncorrect(keyConfig, path); @@ -149,8 +164,12 @@ public void testKeyConfigReloadsFileContents() throws Exception { } private StoreKeyConfig config(Path path, char[] password, String type) { + return config(path, password, type, null); + } + + private StoreKeyConfig config(Path path, char[] password, String type, Function filter) { final String pathName = path == null ? null : path.toString(); - return new StoreKeyConfig(pathName, password, type, password, KeyManagerFactory.getDefaultAlgorithm(), configBasePath); + return new StoreKeyConfig(pathName, password, type, filter, password, KeyManagerFactory.getDefaultAlgorithm(), configBasePath); } private void assertKeysLoaded(StoreKeyConfig keyConfig, String... names) throws CertificateParsingException { @@ -175,7 +194,7 @@ private void assertKeysLoaded(StoreKeyConfig keyConfig, String... names) throws )); } - final List> keys = keyConfig.getKeys(); + final List> keys = keyConfig.getKeys(true); assertThat(keys, iterableWithSize(names.length)); for (Tuple tup : keys) { PrivateKey privateKey = tup.v1(); @@ -186,7 +205,7 @@ private void assertKeysLoaded(StoreKeyConfig keyConfig, String... names) throws assertThat(certificate.getIssuerDN().getName(), is("CN=Test CA 1")); } } - + private void assertKeysNotLoaded(StoreKeyConfig keyConfig, String... names) throws CertificateParsingException { final X509ExtendedKeyManager keyManager = keyConfig.createKeyManager(); assertThat(keyManager, notNullValue()); 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 8e87e3974a6b4..c09659c927427 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 @@ -21,6 +21,7 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.ssl.DiagnosticTrustManager; +import org.elasticsearch.common.ssl.KeyStoreUtil; import org.elasticsearch.common.ssl.SslConfigException; import org.elasticsearch.common.ssl.SslConfiguration; import org.elasticsearch.common.ssl.SslDiagnostics; @@ -51,6 +52,7 @@ import java.net.Socket; import java.security.GeneralSecurityException; import java.security.KeyManagementException; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; @@ -68,6 +70,9 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -523,7 +528,7 @@ private static Map getSSLConfigurations(Environment en key = key.substring(0, key.length() - 1); } try { - sslConfigurationMap.put(key, SslSettingsLoader.load(sslSettings, null, env)); + sslConfigurationMap.put(key, SslSettingsLoader.load(sslSettings, null, env, getKeyStoreFilter(key))); } catch (SslConfigException e) { throw new ElasticsearchSecurityException("failed to load SSL configuration [{}] - {}", e, key, e.getMessage()); } @@ -531,6 +536,37 @@ private static Map getSSLConfigurations(Environment en return Collections.unmodifiableMap(sslConfigurationMap); } + private static Function getKeyStoreFilter(String sslContext) { + if (sslContext.equals("xpack.security.http.ssl")) { + final Function exceptionHandler = e -> new ElasticsearchSecurityException( + "Cannot process keystore for SSL configuration [" + sslContext + "] - " + e.getMessage(), + e + ); + final Predicate isCA = e -> e.getX509Certificate().getBasicConstraints() >= 0; + return ks -> { + final AtomicInteger keyCount = new AtomicInteger(0); + final AtomicInteger caCount = new AtomicInteger(0); + KeyStoreUtil.stream(ks, exceptionHandler).filter(e -> e.isKeyEntry()).forEach(e -> { + keyCount.incrementAndGet(); + if (isCA.test(e)) { + caCount.incrementAndGet(); + } + }); + if (keyCount.get() <= 1) { + // There's only 1 key in the keystore - don't filter it + return ks; + } + if (caCount.get() > 0 && caCount.get() < keyCount.get()) { + // There are both CAs & non-CAs in the keystore, filter out the CAs (they're probably there to support enrollment) + return KeyStoreUtil.filter(ks, e -> e.isKeyEntry() && isCA.test(e) == false); + } else { + return ks; + } + }; + } + return null; + } + static Map getSSLSettingsMap(Settings settings) { final Map sslSettingsMap = new HashMap<>(); sslSettingsMap.put(XPackSettings.HTTP_SSL_PREFIX, getHttpTransportSSLSettings(settings)); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SslSettingsLoader.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SslSettingsLoader.java index 32bca573f002b..1af1eac34927f 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SslSettingsLoader.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SslSettingsLoader.java @@ -18,9 +18,11 @@ import org.elasticsearch.common.ssl.SslKeyConfig; import org.elasticsearch.common.ssl.SslTrustConfig; import org.elasticsearch.common.ssl.SslVerificationMode; +import org.elasticsearch.core.Nullable; import org.elasticsearch.env.Environment; import java.nio.file.Path; +import java.security.KeyStore; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -113,7 +115,18 @@ public SslConfiguration load(Environment env) { } public static SslConfiguration load(Settings settings, String prefix, Environment env) { - return new SslSettingsLoader(settings, prefix, true).load(env); + return load(settings, prefix, env, null); + } + + public static SslConfiguration load( + Settings settings, + String prefix, + Environment env, + @Nullable Function keyStoreFilter + ) { + final SslSettingsLoader settingsLoader = new SslSettingsLoader(settings, prefix, true); + settingsLoader.setKeyStoreFilter(keyStoreFilter); + return settingsLoader.load(env); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SslSettingsLoaderTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SslSettingsLoaderTests.java index 8a0e07cf76903..e8878daccc823 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SslSettingsLoaderTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SslSettingsLoaderTests.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.ssl.CompositeTrustConfig; import org.elasticsearch.common.ssl.DefaultJdkTrustConfig; import org.elasticsearch.common.ssl.EmptyKeyConfig; +import org.elasticsearch.common.ssl.KeyStoreUtil; import org.elasticsearch.common.ssl.PemKeyConfig; import org.elasticsearch.common.ssl.PemTrustConfig; import org.elasticsearch.common.ssl.SslConfiguration; @@ -27,14 +28,18 @@ import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManager; +import javax.net.ssl.X509ExtendedKeyManager; import java.nio.file.Path; import java.security.cert.X509Certificate; import java.util.Arrays; +import java.util.stream.Collectors; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.everyItem; import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.in; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; @@ -78,9 +83,45 @@ public void testThatOnlyKeystoreInSettingsSetsTruststoreSettings() { assertThat(sslConfiguration.getKeyConfig(), instanceOf(StoreKeyConfig.class)); SslKeyConfig keyStore = sslConfiguration.getKeyConfig(); + assertThat(keyStore.getDependentFiles(), contains(path)); + assertThat(keyStore.hasKeyMaterial(), is(true)); + final X509ExtendedKeyManager keyManager = keyStore.createKeyManager(); + assertThat(keyManager, notNullValue()); + assertThat(keyStore.getKeys(), hasSize(3)); // testnode_ec, testnode_rsa, testnode_dsa + assertThat( + keyStore.getKeys().stream().map(t -> t.v1().getAlgorithm()).collect(Collectors.toUnmodifiableSet()), + containsInAnyOrder("RSA", "DSA", "EC") + ); + + assertCombiningTrustConfigContainsCorrectIssuers(sslConfiguration); + } + + public void testFilterAppliedToKeystore() { + final Path path = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.p12"); + MockSecureSettings secureSettings = new MockSecureSettings(); + secureSettings.setString("keystore.secure_password", "testnode"); + Settings settings = Settings.builder() + .put("keystore.path", path) + .setSecureSettings(secureSettings) + .build(); + final SslConfiguration sslConfiguration = SslSettingsLoader.load( + settings, + null, + environment, + ks -> KeyStoreUtil.filter(ks, e -> e.getAlias().endsWith("sa")) // "_dsa" & "_rsa" but not "_ec" + ); + assertThat(sslConfiguration.getKeyConfig(), instanceOf(StoreKeyConfig.class)); + StoreKeyConfig keyStore = (StoreKeyConfig) sslConfiguration.getKeyConfig(); + assertThat(keyStore.getDependentFiles(), contains(path)); assertThat(keyStore.hasKeyMaterial(), is(true)); assertThat(keyStore.createKeyManager(), notNullValue()); + assertThat(keyStore.getKeys(false), hasSize(3)); // testnode_ec, testnode_rsa, testnode_dsa + assertThat(keyStore.getKeys(true), hasSize(2)); // testnode_rsa, testnode_dsa + assertThat( + keyStore.getKeys(true).stream().map(t -> t.v1().getAlgorithm()).collect(Collectors.toUnmodifiableSet()), + containsInAnyOrder("RSA", "DSA") + ); assertCombiningTrustConfigContainsCorrectIssuers(sslConfiguration); } @@ -103,7 +144,7 @@ public void testKeystorePassword() { assertThat( ksKeyInfo, equalTo( - new StoreKeyConfig("path", PASSWORD, "type", PASSWORD, KEY_MGR_ALGORITHM, environment.configFile()) + new StoreKeyConfig("path", PASSWORD, "type", null, PASSWORD, KEY_MGR_ALGORITHM, environment.configFile()) ) ); } @@ -120,7 +161,7 @@ public void testKeystorePasswordBackcompat() { assertThat( ksKeyInfo, equalTo( - new StoreKeyConfig("path", PASSWORD, "type", PASSWORD, KEY_MGR_ALGORITHM, environment.configFile()) + new StoreKeyConfig("path", PASSWORD, "type", null, PASSWORD, KEY_MGR_ALGORITHM, environment.configFile()) ) ); assertSettingDeprecationsAndWarnings(new Setting[]{ @@ -142,7 +183,7 @@ public void testKeystoreKeyPassword() { assertThat( ksKeyInfo, equalTo( - new StoreKeyConfig("path", PASSWORD, "type", KEYPASS, KEY_MGR_ALGORITHM, environment.configFile()) + new StoreKeyConfig("path", PASSWORD, "type", null, KEYPASS, KEY_MGR_ALGORITHM, environment.configFile()) ) ); } @@ -160,7 +201,7 @@ public void testKeystoreKeyPasswordBackcompat() { assertThat( ksKeyInfo, equalTo( - new StoreKeyConfig("path", PASSWORD, "type", KEYPASS, KEY_MGR_ALGORITHM, environment.configFile()) + new StoreKeyConfig("path", PASSWORD, "type", null, KEYPASS, KEY_MGR_ALGORITHM, environment.configFile()) ) ); assertSettingDeprecationsAndWarnings(new Setting[]{ @@ -183,7 +224,7 @@ public void testInferKeystoreTypeFromJksFile() { assertThat( ksKeyInfo, equalTo( - new StoreKeyConfig("xpack/tls/path.jks", PASSWORD, "jks", KEYPASS, KEY_MGR_ALGORITHM, environment.configFile()) + new StoreKeyConfig("xpack/tls/path.jks", PASSWORD, "jks", null, KEYPASS, KEY_MGR_ALGORITHM, environment.configFile()) ) ); } @@ -204,7 +245,7 @@ public void testInferKeystoreTypeFromPkcs12File() { assertThat( ksKeyInfo, equalTo( - new StoreKeyConfig(path, PASSWORD, "PKCS12", KEYPASS, KEY_MGR_ALGORITHM, environment.configFile()) + new StoreKeyConfig(path, PASSWORD, "PKCS12", null, KEYPASS, KEY_MGR_ALGORITHM, environment.configFile()) ) ); } @@ -222,7 +263,7 @@ public void testInferKeystoreTypeFromUnrecognised() { StoreKeyConfig ksKeyInfo = (StoreKeyConfig) sslConfiguration.getKeyConfig(); assertThat( ksKeyInfo, - equalTo(new StoreKeyConfig("xpack/tls/path.foo", PASSWORD, "jks", KEYPASS, KEY_MGR_ALGORITHM, environment.configFile())) + equalTo(new StoreKeyConfig("xpack/tls/path.foo", PASSWORD, "jks", null, KEYPASS, KEY_MGR_ALGORITHM, environment.configFile())) ); } @@ -243,7 +284,7 @@ public void testExplicitKeystoreType() { StoreKeyConfig ksKeyInfo = (StoreKeyConfig) sslConfiguration.getKeyConfig(); assertThat( ksKeyInfo, - equalTo(new StoreKeyConfig(path, PASSWORD, type, KEYPASS, KEY_MGR_ALGORITHM, environment.configFile())) + equalTo(new StoreKeyConfig(path, PASSWORD, type, null, KEYPASS, KEY_MGR_ALGORITHM, environment.configFile())) ); }