Browse Source

Support PKCS#11 tokens as keystores and truststores (#34063)

This enables Elasticsearch to use the JVM-wide configured
PKCS#11 token as a keystore or a truststore for its TLS configuration.
The JVM is assumed to be configured accordingly with the appropriate
Security Provider implementation that supports PKCS#11 tokens.
For the PKCS#11 token to be used as a keystore or a truststore for an
SSLConfiguration, the .keystore.type or .truststore.type must be
explicitly set to pkcs11 in the configuration.
The fact that the PKCS#11 token configuration is JVM wide implies that
there is only one available keystore and truststore that can be used by TLS
configurations in Elasticsearch.
The PIN for the PKCS#11 token can be set as a truststore parameter in
Elasticsearch or as a JVM parameter ( -Djavax.net.ssl.trustStorePassword).

The basic goal of enabling PKCS#11 token support is to allow PKCS#11-NSS in
FIPS mode to be used as a FIPS 140-2 enabled Security Provider.
Ioannis Kakavas 7 years ago
parent
commit
2c82b80b85

+ 39 - 9
docs/reference/settings/security-settings.asciidoc

@@ -400,8 +400,9 @@ The path to the Java Keystore file that contains a private key and certificate.
 `ssl.key` and `ssl.keystore.path` may not be used at the same time.
 
 `ssl.keystore.type`::
-The format of the keystore file. Should be either `jks` to use the Java
-Keystore format, or `PKCS12` to use PKCS#12 files. The default is `jks`.
+The format of the keystore file. Should be `jks` to use the Java
+Keystore format, `PKCS12` to use PKCS#12 files, or `PKCS11` to use a PKCS#11 token.
+The default is `jks`.
 
 `ssl.keystore.password`::
 The password to the keystore.
@@ -426,8 +427,9 @@ The password to the truststore.
 The password to the truststore.
 
 `ssl.truststore.type`::
-The format of the keystore file. Should be either `jks` to use the Java
-Keystore format, or `PKCS12` to use PKCS#12 files. The default is `jks`.
+The format of the keystore file. Should be `jks` to use the Java
+Keystore format, `PKCS12` to use PKCS#12 files, or `PKCS11` to use a PKCS#11 token.
+The default is `jks`.
 
 `ssl.verification_mode`::
 Indicates the type of verification when using `ldaps` to protect against man
@@ -649,8 +651,9 @@ The path to the Java Keystore file that contains a private key and certificate.
 `ssl.key` and `ssl.keystore.path` cannot be used at the same time.
 
 `ssl.keystore.type`::
-The format of the keystore file. Should be either `jks` to use the Java
-Keystore format, or `PKCS12` to use PKCS#12 files. The default is `jks`.
+The format of the keystore file. Should be `jks` to use the Java
+Keystore format, `PKCS12` to use PKCS#12 files, or `PKCS11` to use a PKCS#11 token.
+The default is `jks`.
 
 `ssl.truststore.password`::
 The password to the truststore.
@@ -664,8 +667,9 @@ The path to the Java Keystore file that contains the certificates to trust.
 same time.
 
 `ssl.truststore.type`::
-The format of the truststore file. Should be either `jks` to use the Java
-Keystore format, or `PKCS12` to use PKCS#12 files. The default is `jks`.
+The format of the truststore file. Should be `jks` to use the Java
+Keystore format, `PKCS12` to use PKCS#12 files, or `PKCS11` to use a PKCS#11 token.
+The default is `jks`.
 
 `ssl.verification_mode`::
 Indicates the type of verification when using `ldaps` to protect against man
@@ -1062,7 +1066,7 @@ Must be either a Java Keystore (jks) or a PKCS#12 file.
 same time.
 
 `ssl.truststore.type`::
-The type of the truststore (`ssl.truststore.path`). Must be either `jks` or 
+The type of the truststore (`ssl.truststore.path`). Must be either `jks` or
 `PKCS12`. If the keystore path ends in ".p12", ".pfx" or "pkcs12", this setting 
 defaults to `PKCS12`. Otherwise, it defaults to `jks`.
 
@@ -1316,6 +1320,32 @@ a PKCS#12 container includes trusted certificate ("anchor") entries look for
 `openssl pkcs12 -info` output, or `trustedCertEntry` in the
 `keytool -list` output.
 
+===== PKCS#11 tokens
+
+When using a PKCS#11 cryptographic token, which contains the
+private key, certificate, and certificates that should be trusted, use
+the following settings:
+
+`xpack.ssl.keystore.type`::
+Set this to `PKCS11`.
+
+`xpack.ssl.truststore.type`::
+Set this to `PKCS11`.
+
+
+[[pkcs11-truststore-note]]
+[NOTE]
+When configuring the PKCS#11 token that your JVM is configured to use as
+a keystore or a truststore for Elasticsearch, the PIN for the token can be
+configured by setting the appropriate value to `xpack.ssl.truststore.password`
+or `xpack.ssl.truststore.secure_password`. In the absence of the above, {es} will
+fallback to use he appropriate JVM setting (`-Djavax.net.ssl.trustStorePassword`)
+if that s set.
+Since there can only be one PKCS#11 token configured, only one keystore and
+truststore will be usable for configuration in {es}. This in turn means
+that only one certificate can be used for TLS both in the transport and the
+http layer.
+
 [[http-tls-ssl-settings]]
 :ssl-prefix:             xpack.security.http
 :component:              HTTP

+ 14 - 0
docs/reference/settings/ssl-settings.asciidoc

@@ -145,3 +145,17 @@ Password to the PKCS#12 file.
 
 +{ssl-prefix}.ssl.truststore.secure_password+ (<<secure-settings,Secure>>)::
 Password to the PKCS#12 file.
+
+===== PKCS#11 Tokens
+
+{security} can be configured to use a PKCS#11 token that contains the private key,
+certificate and certificates that should be trusted.
+
+PKCS#11 token require additional configuration on the JVM level and can be enabled
+via the following settings:
+
++{ssl-prefix}.keystore.type+::
+Set this to `PKCS11` to indicate that the PKCS#11 token should be used as a keystore.
+
++{ssl-prefix}.truststore.type+::
+Set this to `PKCS11` to indicate that the PKCS#11 token should be used as a truststore.

+ 4 - 0
x-pack/docs/en/rest-api/security/ssl.asciidoc

@@ -34,6 +34,10 @@ The list does not include certificates that are sourced from the default SSL
 context of the Java Runtime Environment (JRE), even if those certificates are in
 use within {xpack}.
 
+NOTE: When a PKCS#11 token is configured as the truststore of the JRE, the API
+will return all the certificates that are included in the PKCS#11 token
+irrespectively to whether these are used in the {es} TLS configuration or not.
+
 If {xpack} is configured to use a keystore or truststore, the API output
 includes all certificates in that store, even though some of the certificates
 might not be in active use within the cluster.

+ 2 - 3
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/CertParsingUtils.java

@@ -197,6 +197,7 @@ public class CertParsingUtils {
     static KeyConfig createKeyConfig(X509KeyPairSettings keyPair, Settings settings, String trustStoreAlgorithm) {
         String keyPath = keyPair.keyPath.get(settings).orElse(null);
         String keyStorePath = keyPair.keystorePath.get(settings).orElse(null);
+        String keyStoreType = getKeyStoreType(keyPair.keystoreType, settings, keyStorePath);
 
         if (keyPath != null && keyStorePath != null) {
             throw new IllegalArgumentException("you cannot specify a keystore and key file");
@@ -212,10 +213,9 @@ public class CertParsingUtils {
             return new PEMKeyConfig(keyPath, keyPassword, certPath);
         }
 
-        if (keyStorePath != null) {
+        if (keyStorePath != null || keyStoreType.equalsIgnoreCase("pkcs11")) {
             SecureString keyStorePassword = keyPair.keystorePassword.get(settings);
             String keyStoreAlgorithm = keyPair.keystoreAlgorithm.get(settings);
-            String keyStoreType = getKeyStoreType(keyPair.keystoreType, settings, keyStorePath);
             SecureString keyStoreKeyPassword = keyPair.keystoreKeyPassword.get(settings);
             if (keyStoreKeyPassword.length() == 0) {
                 keyStoreKeyPassword = keyStorePassword;
@@ -224,7 +224,6 @@ public class CertParsingUtils {
                     trustStoreAlgorithm);
         }
         return null;
-
     }
 
     /**

+ 15 - 7
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/DefaultJDKTrustConfig.java

@@ -7,6 +7,7 @@ package org.elasticsearch.xpack.core.ssl;
 
 import org.elasticsearch.ElasticsearchException;
 import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.env.Environment;
 import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
 
@@ -30,9 +31,14 @@ import java.util.List;
  */
 class DefaultJDKTrustConfig extends TrustConfig {
 
-    static final DefaultJDKTrustConfig INSTANCE = new DefaultJDKTrustConfig();
+    private SecureString trustStorePassword;
 
-    private DefaultJDKTrustConfig() {
+    /**
+     * @param trustStorePassword the password for the default jdk truststore defined either as a system property or in the Elasticsearch
+     *                           configuration. It applies only when PKCS#11 tokens are user, is null otherwise
+     */
+    DefaultJDKTrustConfig(@Nullable SecureString trustStorePassword) {
+        this.trustStorePassword = trustStorePassword;
     }
 
     @Override
@@ -76,13 +82,14 @@ class DefaultJDKTrustConfig extends TrustConfig {
     /**
      * Merges the default trust configuration with the provided {@link TrustConfig}
      * @param trustConfig the trust configuration to merge with
+     * @param trustStorePassword the password for the default jdk truststore. It applies only to PKCS#11 tokens
      * @return a {@link TrustConfig} that represents a combination of both trust configurations
      */
-    static TrustConfig merge(TrustConfig trustConfig) {
+    static TrustConfig merge(TrustConfig trustConfig, SecureString trustStorePassword) {
         if (trustConfig == null) {
-            return INSTANCE;
+            return new DefaultJDKTrustConfig(trustStorePassword);
         } else {
-            return new CombiningTrustConfig(Arrays.asList(INSTANCE, trustConfig));
+            return new CombiningTrustConfig(Arrays.asList(new DefaultJDKTrustConfig(trustStorePassword), trustConfig));
         }
     }
 
@@ -94,9 +101,10 @@ class DefaultJDKTrustConfig extends TrustConfig {
      * @return the KeyStore used as truststore for PKCS#11 initialized with the password, null otherwise
      */
     private KeyStore getSystemTrustStore() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
-        if (System.getProperty("javax.net.ssl.trustStoreType", "").equalsIgnoreCase("PKCS11")) {
+        if (System.getProperty("javax.net.ssl.trustStoreType", "").equalsIgnoreCase("PKCS11")
+            && trustStorePassword != null) {
             KeyStore keyStore = KeyStore.getInstance("PKCS11");
-            keyStore.load(null, System.getProperty("javax.net.ssl.trustStorePassword", "").toCharArray());
+            keyStore.load(null, trustStorePassword.getChars());
             return keyStore;
         }
         return null;

+ 22 - 8
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfiguration.java

@@ -212,12 +212,11 @@ public final class SSLConfiguration {
 
     private static TrustConfig createCertChainTrustConfig(Settings settings, KeyConfig keyConfig, SSLConfiguration global) {
         String trustStorePath = SETTINGS_PARSER.truststorePath.get(settings).orElse(null);
-
+        String trustStoreType = getKeyStoreType(SETTINGS_PARSER.truststoreType, settings, trustStorePath);
         List<String> caPaths = getListOrNull(SETTINGS_PARSER.caPaths, settings);
         if (trustStorePath != null && caPaths != null) {
             throw new IllegalArgumentException("you cannot specify a truststore and ca files");
         }
-
         VerificationMode verificationMode = SETTINGS_PARSER.verificationMode.get(settings).orElseGet(() -> {
             if (global != null) {
                 return global.verificationMode();
@@ -228,24 +227,39 @@ public final class SSLConfiguration {
             return TrustAllConfig.INSTANCE;
         } else if (caPaths != null) {
             return new PEMTrustConfig(caPaths);
-        } else if (trustStorePath != null) {
-            SecureString trustStorePassword = SETTINGS_PARSER.truststorePassword.get(settings);
+        } else if (trustStorePath != null || trustStoreType.equalsIgnoreCase("pkcs11")) {
             String trustStoreAlgorithm = SETTINGS_PARSER.truststoreAlgorithm.get(settings);
-            String trustStoreType = getKeyStoreType(SETTINGS_PARSER.truststoreType, settings, trustStorePath);
+            SecureString trustStorePassword = SETTINGS_PARSER.truststorePassword.get(settings);
             return new StoreTrustConfig(trustStorePath, trustStoreType, trustStorePassword, trustStoreAlgorithm);
         } else if (global == null && System.getProperty("javax.net.ssl.trustStore") != null
             && System.getProperty("javax.net.ssl.trustStore").equals("NONE") == false) {
             try (SecureString truststorePassword = new SecureString(System.getProperty("javax.net.ssl.trustStorePassword", ""))) {
                 return new StoreTrustConfig(System.getProperty("javax.net.ssl.trustStore"), KeyStore.getDefaultType(), truststorePassword,
-                        System.getProperty("ssl.TrustManagerFactory.algorithm", TrustManagerFactory.getDefaultAlgorithm()));
+                    System.getProperty("ssl.TrustManagerFactory.algorithm", TrustManagerFactory.getDefaultAlgorithm()));
             }
         } else if (global != null && keyConfig == global.keyConfig()) {
             return global.trustConfig();
         } else if (keyConfig != KeyConfig.NONE) {
-            return DefaultJDKTrustConfig.merge(keyConfig);
+            return DefaultJDKTrustConfig.merge(keyConfig, getDefaultTrustStorePassword(settings));
         } else {
-            return DefaultJDKTrustConfig.INSTANCE;
+            return new DefaultJDKTrustConfig(getDefaultTrustStorePassword(settings));
+        }
+    }
+
+    private static SecureString getDefaultTrustStorePassword(Settings settings) {
+        // We only handle the default store password if it's a PKCS#11 token
+        if (System.getProperty("javax.net.ssl.trustStoreType", "").equalsIgnoreCase("PKCS11")) {
+            try (SecureString systemTrustStorePassword =
+                     new SecureString(System.getProperty("javax.net.ssl.trustStorePassword", "").toCharArray())) {
+                if (systemTrustStorePassword.length() == 0) {
+                    try (SecureString trustStorePassword = SETTINGS_PARSER.truststorePassword.get(settings)) {
+                        return trustStorePassword;
+                    }
+                }
+                return systemTrustStorePassword;
+            }
         }
+        return null;
     }
 
     private static List<String> getListOrNull(Setting<List<String>> listSetting, Settings settings) {

+ 15 - 21
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/StoreKeyConfig.java

@@ -15,8 +15,6 @@ import javax.net.ssl.X509ExtendedKeyManager;
 import javax.net.ssl.X509ExtendedTrustManager;
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
 import java.nio.file.Path;
 import java.security.GeneralSecurityException;
 import java.security.Key;
@@ -49,7 +47,7 @@ class StoreKeyConfig extends KeyConfig {
 
     /**
      * Creates a new configuration that can be used to load key and trust material from a {@link KeyStore}
-     * @param keyStorePath the path to the keystore file
+     * @param keyStorePath the path to the keystore file or null when keyStoreType is pkcs11
      * @param keyStoreType the type of the keystore file
      * @param keyStorePassword the password for the keystore
      * @param keyPassword the password for the private key in the keystore
@@ -58,7 +56,7 @@ class StoreKeyConfig extends KeyConfig {
      */
     StoreKeyConfig(String keyStorePath, String keyStoreType, SecureString keyStorePassword, SecureString keyPassword,
                    String keyStoreAlgorithm, String trustStoreAlgorithm) {
-        this.keyStorePath = Objects.requireNonNull(keyStorePath, "keystore path must be specified");
+        this.keyStorePath = keyStorePath;
         this.keyStoreType = Objects.requireNonNull(keyStoreType, "keystore type must be specified");
         // since we support reloading the keystore, we must store the passphrase in memory for the life of the node, so we
         // clone the password and never close it during our uses below
@@ -71,7 +69,7 @@ class StoreKeyConfig extends KeyConfig {
     @Override
     X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) {
         try {
-            KeyStore ks = getKeyStore(environment);
+            KeyStore ks = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
             checkKeyStore(ks);
             return CertParsingUtils.keyManager(ks, keyPassword.getChars(), keyStoreAlgorithm);
         } catch (IOException | CertificateException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
@@ -82,16 +80,16 @@ class StoreKeyConfig extends KeyConfig {
     @Override
     X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
         try {
-            return CertParsingUtils.trustManager(keyStorePath, keyStoreType, keyStorePassword.getChars(), trustStoreAlgorithm, environment);
-        } catch (Exception e) {
+            KeyStore ks = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
+            return CertParsingUtils.trustManager(ks, trustStoreAlgorithm);
+        } catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
             throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
         }
     }
 
     @Override
     Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
-        final Path path = CertParsingUtils.resolvePath(keyStorePath, environment);
-        final KeyStore trustStore = CertParsingUtils.readKeyStore(path, keyStoreType, keyStorePassword.getChars());
+        final KeyStore trustStore = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
         final List<CertificateInfo> certificates = new ArrayList<>();
         final Enumeration<String> aliases = trustStore.aliases();
         while (aliases.hasMoreElements()) {
@@ -112,13 +110,16 @@ class StoreKeyConfig extends KeyConfig {
 
     @Override
     List<Path> filesToMonitor(@Nullable Environment environment) {
+        if (keyStorePath == null) {
+            return Collections.emptyList();
+        }
         return Collections.singletonList(CertParsingUtils.resolvePath(keyStorePath, environment));
     }
 
     @Override
     List<PrivateKey> privateKeys(@Nullable Environment environment) {
         try {
-            KeyStore keyStore = getKeyStore(environment);
+            KeyStore keyStore = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
             List<PrivateKey> privateKeys = new ArrayList<>();
             for (Enumeration<String> e = keyStore.aliases(); e.hasMoreElements(); ) {
                 final String alias = e.nextElement();
@@ -135,15 +136,6 @@ class StoreKeyConfig extends KeyConfig {
         }
     }
 
-    private KeyStore getKeyStore(@Nullable Environment environment)
-                                throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
-        try (InputStream in = Files.newInputStream(CertParsingUtils.resolvePath(keyStorePath, environment))) {
-            KeyStore ks = KeyStore.getInstance(keyStoreType);
-            ks.load(in, keyStorePassword.getChars());
-            return ks;
-        }
-    }
-
     private void checkKeyStore(KeyStore keyStore) throws KeyStoreException {
         Enumeration<String> aliases = keyStore.aliases();
         while (aliases.hasMoreElements()) {
@@ -152,9 +144,11 @@ class StoreKeyConfig extends KeyConfig {
                 return;
             }
         }
-        throw new IllegalArgumentException("the keystore [" + keyStorePath + "] does not contain a private key entry");
+        final String message = null != keyStorePath ?
+            "the keystore [" + keyStorePath + "] does not contain a private key entry" :
+            "the configured PKCS#11 token does not contain a private key entry";
+        throw new IllegalArgumentException(message);
     }
-
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;

+ 3 - 4
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/StoreTrustConfig.java

@@ -55,8 +55,8 @@ class StoreTrustConfig extends TrustConfig {
     @Override
     X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
         try {
-            return CertParsingUtils.trustManager(trustStorePath, trustStoreType, trustStorePassword.getChars(),
-                    trustStoreAlgorithm, environment);
+            KeyStore trustStore = getStore(environment, trustStorePath, trustStoreType, trustStorePassword);
+            return CertParsingUtils.trustManager(trustStore, trustStoreAlgorithm);
         } catch (Exception e) {
             throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
         }
@@ -64,8 +64,7 @@ class StoreTrustConfig extends TrustConfig {
 
     @Override
     Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
-        final Path path = CertParsingUtils.resolvePath(trustStorePath, environment);
-        final KeyStore trustStore = CertParsingUtils.readKeyStore(path, trustStoreType, trustStorePassword.getChars());
+        final KeyStore trustStore = getStore(environment, trustStorePath, trustStoreType, trustStorePassword);
         final List<CertificateInfo> certificates = new ArrayList<>();
         final Enumeration<String> aliases = trustStore.aliases();
         while (aliases.hasMoreElements()) {

+ 39 - 0
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/TrustConfig.java

@@ -7,14 +7,21 @@ package org.elasticsearch.xpack.core.ssl;
 
 import org.elasticsearch.ElasticsearchException;
 import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.env.Environment;
 import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
 
 import javax.net.ssl.X509ExtendedTrustManager;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -58,6 +65,38 @@ abstract class TrustConfig {
      */
     public abstract int hashCode();
 
+    /**
+     * Loads and returns the appropriate {@link KeyStore} for the given configuration. The KeyStore can be backed by a file
+     * in any format that the Security Provider might support, or a cryptographic software or hardware token in the case
+     * of a PKCS#11 Provider.
+     *
+     * @param environment   the environment to resolve files against or null in the case of running in a transport client
+     * @param storePath     the path to the {@link KeyStore} to load, or null if a PKCS11 token is configured as the keystore/truststore
+     *                      of the JVM
+     * @param storeType     the type of the {@link KeyStore}
+     * @param storePassword the password to be used for decrypting the {@link KeyStore}
+     * @return the loaded KeyStore to be used as a keystore or a truststore
+     * @throws KeyStoreException        if an instance of the specified type cannot be loaded
+     * @throws CertificateException     if any of the certificates in the keystore could not be loaded
+     * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
+     * @throws IOException              if there is an I/O issue with the KeyStore data or the password is incorrect
+     */
+    KeyStore getStore(@Nullable Environment environment, @Nullable String storePath, String storeType, SecureString storePassword)
+        throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
+        if (null != storePath) {
+            try (InputStream in = Files.newInputStream(CertParsingUtils.resolvePath(storePath, environment))) {
+                KeyStore ks = KeyStore.getInstance(storeType);
+                ks.load(in, storePassword.getChars());
+                return ks;
+            }
+        } else if (storeType.equalsIgnoreCase("pkcs11")) {
+            KeyStore ks = KeyStore.getInstance(storeType);
+            ks.load(null, storePassword.getChars());
+            return ks;
+        }
+        throw new IllegalArgumentException("keystore.path or truststore.path can only be empty when using a PKCS#11 token");
+    }
+
     /**
      * A trust configuration that is a combination of a trust configuration with the default JDK trust configuration. This trust
      * configuration returns a trust manager verifies certificates against both the default JDK trusted configurations and the specific

+ 2 - 24
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationTests.java

@@ -228,20 +228,6 @@ public class SSLConfigurationTests extends ESTestCase {
         assertThat(ksTrustInfo.trustStoreAlgorithm, is(equalTo("trusted")));
     }
 
-    public void testThatEmptySettingsAreEqual() {
-        SSLConfiguration sslConfiguration = new SSLConfiguration(Settings.EMPTY);
-        SSLConfiguration sslConfiguration1 = new SSLConfiguration(Settings.EMPTY);
-        assertThat(sslConfiguration.equals(sslConfiguration1), is(equalTo(true)));
-        assertThat(sslConfiguration1.equals(sslConfiguration), is(equalTo(true)));
-        assertThat(sslConfiguration.equals(sslConfiguration), is(equalTo(true)));
-        assertThat(sslConfiguration1.equals(sslConfiguration1), is(equalTo(true)));
-
-        SSLConfiguration profileSSLConfiguration = new SSLConfiguration(Settings.EMPTY, sslConfiguration);
-        assertThat(sslConfiguration.equals(profileSSLConfiguration), is(equalTo(true)));
-        assertThat(profileSSLConfiguration.equals(sslConfiguration), is(equalTo(true)));
-        assertThat(profileSSLConfiguration.equals(profileSSLConfiguration), is(equalTo(true)));
-    }
-
     public void testThatSettingsWithDifferentKeystoresAreNotEqual() {
         SSLConfiguration sslConfiguration = new SSLConfiguration(Settings.builder()
                 .put("keystore.path", "path")
@@ -268,15 +254,6 @@ public class SSLConfigurationTests extends ESTestCase {
         assertThat(sslConfiguration1.equals(sslConfiguration1), is(equalTo(true)));
     }
 
-    public void testThatEmptySettingsHaveSameHashCode() {
-        SSLConfiguration sslConfiguration = new SSLConfiguration(Settings.EMPTY);
-        SSLConfiguration sslConfiguration1 = new SSLConfiguration(Settings.EMPTY);
-        assertThat(sslConfiguration.hashCode(), is(equalTo(sslConfiguration1.hashCode())));
-
-        SSLConfiguration profileSettings = new SSLConfiguration(Settings.EMPTY, sslConfiguration);
-        assertThat(profileSettings.hashCode(), is(equalTo(sslConfiguration.hashCode())));
-    }
-
     public void testThatSettingsWithDifferentKeystoresHaveDifferentHashCode() {
         SSLConfiguration sslConfiguration = new SSLConfiguration(Settings.builder()
                 .put("keystore.path", "path")
@@ -390,7 +367,8 @@ public class SSLConfigurationTests extends ESTestCase {
     private void assertCombiningTrustConfigContainsCorrectIssuers(SSLConfiguration sslConfiguration) {
         X509Certificate[] trustConfAcceptedIssuers = sslConfiguration.trustConfig().createTrustManager(null).getAcceptedIssuers();
         X509Certificate[] keyConfAcceptedIssuers = sslConfiguration.keyConfig().createTrustManager(null).getAcceptedIssuers();
-        X509Certificate[] defaultAcceptedIssuers = DefaultJDKTrustConfig.INSTANCE.createTrustManager(null).getAcceptedIssuers();
+        X509Certificate[] defaultAcceptedIssuers = new DefaultJDKTrustConfig(null).createTrustManager(null)
+            .getAcceptedIssuers();
         assertEquals(keyConfAcceptedIssuers.length + defaultAcceptedIssuers.length, trustConfAcceptedIssuers.length);
         assertThat(Arrays.asList(keyConfAcceptedIssuers), everyItem(isIn(trustConfAcceptedIssuers)));
         assertThat(Arrays.asList(defaultAcceptedIssuers), everyItem(isIn(trustConfAcceptedIssuers)));

+ 2 - 2
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLServiceTests.java

@@ -477,8 +477,8 @@ public class SSLServiceTests extends ESTestCase {
     public void testEmptyTrustManager() throws Exception {
         Settings settings = Settings.builder().build();
         final SSLService sslService = new SSLService(settings, env);
-        SSLConfiguration sslConfig = new SSLConfiguration(settings);
-        X509ExtendedTrustManager trustManager = sslService.sslContextHolder(sslConfig).getEmptyTrustManager();
+        X509ExtendedTrustManager trustManager = sslService.sslContextHolder(sslService.getSSLConfiguration("xpack.ssl"))
+            .getEmptyTrustManager();
         assertThat(trustManager.getAcceptedIssuers(), emptyArray());
     }
 

+ 19 - 0
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/StoreKeyConfigTests.java

@@ -5,6 +5,7 @@
  */
 package org.elasticsearch.xpack.core.ssl;
 
+import org.elasticsearch.ElasticsearchException;
 import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.env.TestEnvironment;
@@ -16,6 +17,7 @@ import javax.net.ssl.X509ExtendedKeyManager;
 
 import java.security.PrivateKey;
 
+import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.notNullValue;
 
@@ -31,6 +33,23 @@ public class StoreKeyConfigTests extends ESTestCase {
         tryReadPrivateKeyFromKeyStore("PKCS12", ".p12");
     }
 
+    public void testKeyStorePathCanBeEmptyForPkcs11() throws Exception {
+        assumeFalse("Can't run in a FIPS JVM", inFipsJvm());
+        final Settings settings = Settings.builder().put("path.home", createTempDir()).build();
+        final SecureString keyStorePassword = new SecureString("password".toCharArray());
+        final StoreKeyConfig keyConfig = new StoreKeyConfig(null, "PKCS12", keyStorePassword, keyStorePassword,
+            KeyManagerFactory.getDefaultAlgorithm(), TrustManagerFactory.getDefaultAlgorithm());
+        Exception e = expectThrows(IllegalArgumentException.class, () ->
+            keyConfig.createKeyManager(TestEnvironment.newEnvironment(settings)));
+        assertThat(e.getMessage(), equalTo("keystore.path or truststore.path can only be empty when using a PKCS#11 token"));
+        final StoreKeyConfig keyConfigPkcs11 = new StoreKeyConfig(null, "PKCS11", keyStorePassword, keyStorePassword,
+            KeyManagerFactory.getDefaultAlgorithm(), TrustManagerFactory.getDefaultAlgorithm());
+        ElasticsearchException ee = expectThrows(ElasticsearchException.class, () ->
+            keyConfigPkcs11.createKeyManager(TestEnvironment.newEnvironment(settings)));
+        assertThat(ee.getMessage(), containsString("failed to initialize a KeyManagerFactory"));
+        assertThat(ee.getCause().getMessage(), containsString("PKCS11 not found"));
+    }
+
     private void tryReadPrivateKeyFromKeyStore(String type, String extension) {
         final Settings settings = Settings.builder().put("path.home", createTempDir()).build();
         final String path = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode" + extension).toString();

+ 4 - 0
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/TestsSSLService.java

@@ -30,4 +30,8 @@ public class TestsSSLService extends SSLService {
     public SSLContext sslContext(Settings settings) {
         return sslContextHolder(super.sslConfiguration(settings)).sslContext();
     }
+
+    public SSLContext sslContext(String context) {
+        return sslContextHolder(super.getSSLConfiguration(context)).sslContext();
+    }
 }

+ 1 - 1
x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/actions/webhook/WebhookHttpsIntegrationTests.java

@@ -58,7 +58,7 @@ public class WebhookHttpsIntegrationTests extends AbstractWatcherIntegrationTest
     public void startWebservice() throws Exception {
         Settings settings = getInstanceFromMaster(Settings.class);
         TestsSSLService sslService = new TestsSSLService(settings, getInstanceFromMaster(Environment.class));
-        webServer = new MockWebServer(sslService.sslContext(settings.getByPrefix("xpack.http.ssl.")), false);
+        webServer = new MockWebServer(sslService.sslContext("xpack.http.ssl"), false);
         webServer.start();
     }