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.
This commit is contained in:
Ioannis Kakavas 2018-10-04 10:51:58 +03:00 committed by GitHub
parent e8b986cc37
commit 2c82b80b85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 181 additions and 79 deletions

View File

@ -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.key` and `ssl.keystore.path` may not be used at the same time.
`ssl.keystore.type`:: `ssl.keystore.type`::
The format of the keystore file. Should be either `jks` to use the Java The format of the keystore file. Should be `jks` to use the Java
Keystore format, or `PKCS12` to use PKCS#12 files. The default is `jks`. Keystore format, `PKCS12` to use PKCS#12 files, or `PKCS11` to use a PKCS#11 token.
The default is `jks`.
`ssl.keystore.password`:: `ssl.keystore.password`::
The password to the keystore. The password to the keystore.
@ -426,8 +427,9 @@ The password to the truststore.
The password to the truststore. The password to the truststore.
`ssl.truststore.type`:: `ssl.truststore.type`::
The format of the keystore file. Should be either `jks` to use the Java The format of the keystore file. Should be `jks` to use the Java
Keystore format, or `PKCS12` to use PKCS#12 files. The default is `jks`. Keystore format, `PKCS12` to use PKCS#12 files, or `PKCS11` to use a PKCS#11 token.
The default is `jks`.
`ssl.verification_mode`:: `ssl.verification_mode`::
Indicates the type of verification when using `ldaps` to protect against man 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.key` and `ssl.keystore.path` cannot be used at the same time.
`ssl.keystore.type`:: `ssl.keystore.type`::
The format of the keystore file. Should be either `jks` to use the Java The format of the keystore file. Should be `jks` to use the Java
Keystore format, or `PKCS12` to use PKCS#12 files. The default is `jks`. Keystore format, `PKCS12` to use PKCS#12 files, or `PKCS11` to use a PKCS#11 token.
The default is `jks`.
`ssl.truststore.password`:: `ssl.truststore.password`::
The password to the truststore. The password to the truststore.
@ -664,8 +667,9 @@ The path to the Java Keystore file that contains the certificates to trust.
same time. same time.
`ssl.truststore.type`:: `ssl.truststore.type`::
The format of the truststore file. Should be either `jks` to use the Java The format of the truststore file. Should be `jks` to use the Java
Keystore format, or `PKCS12` to use PKCS#12 files. The default is `jks`. Keystore format, `PKCS12` to use PKCS#12 files, or `PKCS11` to use a PKCS#11 token.
The default is `jks`.
`ssl.verification_mode`:: `ssl.verification_mode`::
Indicates the type of verification when using `ldaps` to protect against man 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. same time.
`ssl.truststore.type`:: `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 `PKCS12`. If the keystore path ends in ".p12", ".pfx" or "pkcs12", this setting
defaults to `PKCS12`. Otherwise, it defaults to `jks`. 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 `openssl pkcs12 -info` output, or `trustedCertEntry` in the
`keytool -list` output. `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]] [[http-tls-ssl-settings]]
:ssl-prefix: xpack.security.http :ssl-prefix: xpack.security.http
:component: HTTP :component: HTTP

View File

@ -145,3 +145,17 @@ Password to the PKCS#12 file.
+{ssl-prefix}.ssl.truststore.secure_password+ (<<secure-settings,Secure>>):: +{ssl-prefix}.ssl.truststore.secure_password+ (<<secure-settings,Secure>>)::
Password to the PKCS#12 file. 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.

View File

@ -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 context of the Java Runtime Environment (JRE), even if those certificates are in
use within {xpack}. 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 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 includes all certificates in that store, even though some of the certificates
might not be in active use within the cluster. might not be in active use within the cluster.

View File

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

View File

@ -7,6 +7,7 @@ package org.elasticsearch.xpack.core.ssl;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo; import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
@ -30,9 +31,14 @@ import java.util.List;
*/ */
class DefaultJDKTrustConfig extends TrustConfig { 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 @Override
@ -76,13 +82,14 @@ class DefaultJDKTrustConfig extends TrustConfig {
/** /**
* Merges the default trust configuration with the provided {@link TrustConfig} * Merges the default trust configuration with the provided {@link TrustConfig}
* @param trustConfig the trust configuration to merge with * @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 * @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) { if (trustConfig == null) {
return INSTANCE; return new DefaultJDKTrustConfig(trustStorePassword);
} else { } 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 * @return the KeyStore used as truststore for PKCS#11 initialized with the password, null otherwise
*/ */
private KeyStore getSystemTrustStore() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { 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 keyStore = KeyStore.getInstance("PKCS11");
keyStore.load(null, System.getProperty("javax.net.ssl.trustStorePassword", "").toCharArray()); keyStore.load(null, trustStorePassword.getChars());
return keyStore; return keyStore;
} }
return null; return null;

View File

@ -212,12 +212,11 @@ public final class SSLConfiguration {
private static TrustConfig createCertChainTrustConfig(Settings settings, KeyConfig keyConfig, SSLConfiguration global) { private static TrustConfig createCertChainTrustConfig(Settings settings, KeyConfig keyConfig, SSLConfiguration global) {
String trustStorePath = SETTINGS_PARSER.truststorePath.get(settings).orElse(null); 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); List<String> caPaths = getListOrNull(SETTINGS_PARSER.caPaths, settings);
if (trustStorePath != null && caPaths != null) { if (trustStorePath != null && caPaths != null) {
throw new IllegalArgumentException("you cannot specify a truststore and ca files"); throw new IllegalArgumentException("you cannot specify a truststore and ca files");
} }
VerificationMode verificationMode = SETTINGS_PARSER.verificationMode.get(settings).orElseGet(() -> { VerificationMode verificationMode = SETTINGS_PARSER.verificationMode.get(settings).orElseGet(() -> {
if (global != null) { if (global != null) {
return global.verificationMode(); return global.verificationMode();
@ -228,26 +227,41 @@ public final class SSLConfiguration {
return TrustAllConfig.INSTANCE; return TrustAllConfig.INSTANCE;
} else if (caPaths != null) { } else if (caPaths != null) {
return new PEMTrustConfig(caPaths); return new PEMTrustConfig(caPaths);
} else if (trustStorePath != null) { } else if (trustStorePath != null || trustStoreType.equalsIgnoreCase("pkcs11")) {
SecureString trustStorePassword = SETTINGS_PARSER.truststorePassword.get(settings);
String trustStoreAlgorithm = SETTINGS_PARSER.truststoreAlgorithm.get(settings); 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); return new StoreTrustConfig(trustStorePath, trustStoreType, trustStorePassword, trustStoreAlgorithm);
} else if (global == null && System.getProperty("javax.net.ssl.trustStore") != null } else if (global == null && System.getProperty("javax.net.ssl.trustStore") != null
&& System.getProperty("javax.net.ssl.trustStore").equals("NONE") == false) { && System.getProperty("javax.net.ssl.trustStore").equals("NONE") == false) {
try (SecureString truststorePassword = new SecureString(System.getProperty("javax.net.ssl.trustStorePassword", ""))) { try (SecureString truststorePassword = new SecureString(System.getProperty("javax.net.ssl.trustStorePassword", ""))) {
return new StoreTrustConfig(System.getProperty("javax.net.ssl.trustStore"), KeyStore.getDefaultType(), 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()) { } else if (global != null && keyConfig == global.keyConfig()) {
return global.trustConfig(); return global.trustConfig();
} else if (keyConfig != KeyConfig.NONE) { } else if (keyConfig != KeyConfig.NONE) {
return DefaultJDKTrustConfig.merge(keyConfig); return DefaultJDKTrustConfig.merge(keyConfig, getDefaultTrustStorePassword(settings));
} else { } 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) { private static List<String> getListOrNull(Setting<List<String>> listSetting, Settings settings) {
return getListOrDefault(listSetting, settings, null); return getListOrDefault(listSetting, settings, null);
} }

View File

@ -15,8 +15,6 @@ import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509ExtendedTrustManager;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.Key; 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} * 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 keyStoreType the type of the keystore file
* @param keyStorePassword the password for the keystore * @param keyStorePassword the password for the keystore
* @param keyPassword the password for the private key in 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, StoreKeyConfig(String keyStorePath, String keyStoreType, SecureString keyStorePassword, SecureString keyPassword,
String keyStoreAlgorithm, String trustStoreAlgorithm) { 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"); 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 // 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 // clone the password and never close it during our uses below
@ -71,7 +69,7 @@ class StoreKeyConfig extends KeyConfig {
@Override @Override
X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) { X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) {
try { try {
KeyStore ks = getKeyStore(environment); KeyStore ks = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
checkKeyStore(ks); checkKeyStore(ks);
return CertParsingUtils.keyManager(ks, keyPassword.getChars(), keyStoreAlgorithm); return CertParsingUtils.keyManager(ks, keyPassword.getChars(), keyStoreAlgorithm);
} catch (IOException | CertificateException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) { } catch (IOException | CertificateException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
@ -82,16 +80,16 @@ class StoreKeyConfig extends KeyConfig {
@Override @Override
X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) { X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
try { try {
return CertParsingUtils.trustManager(keyStorePath, keyStoreType, keyStorePassword.getChars(), trustStoreAlgorithm, environment); KeyStore ks = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
} catch (Exception e) { return CertParsingUtils.trustManager(ks, trustStoreAlgorithm);
} catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e); throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
} }
} }
@Override @Override
Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException { Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
final Path path = CertParsingUtils.resolvePath(keyStorePath, environment); final KeyStore trustStore = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
final KeyStore trustStore = CertParsingUtils.readKeyStore(path, keyStoreType, keyStorePassword.getChars());
final List<CertificateInfo> certificates = new ArrayList<>(); final List<CertificateInfo> certificates = new ArrayList<>();
final Enumeration<String> aliases = trustStore.aliases(); final Enumeration<String> aliases = trustStore.aliases();
while (aliases.hasMoreElements()) { while (aliases.hasMoreElements()) {
@ -112,13 +110,16 @@ class StoreKeyConfig extends KeyConfig {
@Override @Override
List<Path> filesToMonitor(@Nullable Environment environment) { List<Path> filesToMonitor(@Nullable Environment environment) {
if (keyStorePath == null) {
return Collections.emptyList();
}
return Collections.singletonList(CertParsingUtils.resolvePath(keyStorePath, environment)); return Collections.singletonList(CertParsingUtils.resolvePath(keyStorePath, environment));
} }
@Override @Override
List<PrivateKey> privateKeys(@Nullable Environment environment) { List<PrivateKey> privateKeys(@Nullable Environment environment) {
try { try {
KeyStore keyStore = getKeyStore(environment); KeyStore keyStore = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
List<PrivateKey> privateKeys = new ArrayList<>(); List<PrivateKey> privateKeys = new ArrayList<>();
for (Enumeration<String> e = keyStore.aliases(); e.hasMoreElements(); ) { for (Enumeration<String> e = keyStore.aliases(); e.hasMoreElements(); ) {
final String alias = e.nextElement(); 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 { private void checkKeyStore(KeyStore keyStore) throws KeyStoreException {
Enumeration<String> aliases = keyStore.aliases(); Enumeration<String> aliases = keyStore.aliases();
while (aliases.hasMoreElements()) { while (aliases.hasMoreElements()) {
@ -152,9 +144,11 @@ class StoreKeyConfig extends KeyConfig {
return; 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 @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View File

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

View File

@ -7,14 +7,21 @@ package org.elasticsearch.xpack.core.ssl;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo; import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509ExtendedTrustManager;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.GeneralSecurityException; 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.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -58,6 +65,38 @@ abstract class TrustConfig {
*/ */
public abstract int hashCode(); 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 * 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 * configuration returns a trust manager verifies certificates against both the default JDK trusted configurations and the specific

View File

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

View File

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

View File

@ -5,6 +5,7 @@
*/ */
package org.elasticsearch.xpack.core.ssl; package org.elasticsearch.xpack.core.ssl;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.env.TestEnvironment;
@ -16,6 +17,7 @@ import javax.net.ssl.X509ExtendedKeyManager;
import java.security.PrivateKey; import java.security.PrivateKey;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
@ -31,6 +33,23 @@ public class StoreKeyConfigTests extends ESTestCase {
tryReadPrivateKeyFromKeyStore("PKCS12", ".p12"); 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) { private void tryReadPrivateKeyFromKeyStore(String type, String extension) {
final Settings settings = Settings.builder().put("path.home", createTempDir()).build(); 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(); final String path = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode" + extension).toString();

View File

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

View File

@ -58,7 +58,7 @@ public class WebhookHttpsIntegrationTests extends AbstractWatcherIntegrationTest
public void startWebservice() throws Exception { public void startWebservice() throws Exception {
Settings settings = getInstanceFromMaster(Settings.class); Settings settings = getInstanceFromMaster(Settings.class);
TestsSSLService sslService = new TestsSSLService(settings, getInstanceFromMaster(Environment.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(); webServer.start();
} }