Skip to content

Commit

Permalink
Support PKCS#11 tokens as keystores and truststores (#34063)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jkakavas committed Oct 4, 2018
1 parent 230cd10 commit 8de6726
Show file tree
Hide file tree
Showing 14 changed files with 181 additions and 79 deletions.
48 changes: 39 additions & 9 deletions docs/reference/settings/security-settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,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.
Expand All @@ -431,8 +432,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
Expand Down Expand Up @@ -654,8 +656,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.
Expand All @@ -669,8 +672,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
Expand Down Expand Up @@ -1067,7 +1071,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`.

Expand Down Expand Up @@ -1322,6 +1326,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
Expand Down
14 changes: 14 additions & 0 deletions docs/reference/settings/ssl-settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 4 additions & 0 deletions x-pack/docs/en/rest-api/security/ssl.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ public static X509ExtendedKeyManager getKeyManager(X509KeyPairSettings keyPair,
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");
Expand All @@ -212,10 +213,9 @@ static KeyConfig createKeyConfig(X509KeyPairSettings keyPair, Settings settings,
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;
Expand All @@ -224,7 +224,6 @@ static KeyConfig createKeyConfig(X509KeyPairSettings keyPair, Settings settings,
trustStoreAlgorithm);
}
return null;

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

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;

Expand All @@ -30,9 +31,14 @@
*/
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
Expand Down Expand Up @@ -76,13 +82,14 @@ public int hashCode() {
/**
* 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));
}
}

Expand All @@ -94,9 +101,10 @@ static TrustConfig merge(TrustConfig 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,11 @@ private static TrustConfig createTrustConfig(Settings settings, KeyConfig keyCon

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();
Expand All @@ -228,24 +227,39 @@ private static TrustConfig createCertChainTrustConfig(Settings settings, KeyConf
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
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;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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) {
Expand All @@ -82,16 +80,16 @@ X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) {
@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()) {
Expand All @@ -112,13 +110,16 @@ Collection<CertificateInfo> certificates(Environment environment) throws General

@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();
Expand All @@ -135,15 +136,6 @@ List<PrivateKey> privateKeys(@Nullable Environment environment) {
}
}

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()) {
Expand All @@ -152,9 +144,11 @@ private void checkKeyStore(KeyStore keyStore) throws KeyStoreException {
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;
Expand Down
Loading

0 comments on commit 8de6726

Please sign in to comment.