Skip to content

Commit ab6de20

Browse files
committed
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.
1 parent 9a088c9 commit ab6de20

File tree

14 files changed

+181
-79
lines changed

14 files changed

+181
-79
lines changed

docs/reference/settings/security-settings.asciidoc

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,9 @@ The path to the Java Keystore file that contains a private key and certificate.
398398
`ssl.key` and `ssl.keystore.path` may not be used at the same time.
399399

400400
`ssl.keystore.type`::
401-
The format of the keystore file. Should be either `jks` to use the Java
402-
Keystore format, or `PKCS12` to use PKCS#12 files. The default is `jks`.
401+
The format of the keystore file. Should be `jks` to use the Java
402+
Keystore format, `PKCS12` to use PKCS#12 files, or `PKCS11` to use a PKCS#11 token.
403+
The default is `jks`.
403404

404405
`ssl.keystore.password`::
405406
The password to the keystore.
@@ -424,8 +425,9 @@ The password to the truststore.
424425
The password to the truststore.
425426

426427
`ssl.truststore.type`::
427-
The format of the keystore file. Should be either `jks` to use the Java
428-
Keystore format, or `PKCS12` to use PKCS#12 files. The default is `jks`.
428+
The format of the keystore file. Should be `jks` to use the Java
429+
Keystore format, `PKCS12` to use PKCS#12 files, or `PKCS11` to use a PKCS#11 token.
430+
The default is `jks`.
429431

430432
`ssl.verification_mode`::
431433
Indicates the type of verification when using `ldaps` to protect against man
@@ -647,8 +649,9 @@ The path to the Java Keystore file that contains a private key and certificate.
647649
`ssl.key` and `ssl.keystore.path` cannot be used at the same time.
648650

649651
`ssl.keystore.type`::
650-
The format of the keystore file. Should be either `jks` to use the Java
651-
Keystore format, or `PKCS12` to use PKCS#12 files. The default is `jks`.
652+
The format of the keystore file. Should be `jks` to use the Java
653+
Keystore format, `PKCS12` to use PKCS#12 files, or `PKCS11` to use a PKCS#11 token.
654+
The default is `jks`.
652655

653656
`ssl.truststore.password`::
654657
The password to the truststore.
@@ -662,8 +665,9 @@ The path to the Java Keystore file that contains the certificates to trust.
662665
same time.
663666

664667
`ssl.truststore.type`::
665-
The format of the truststore file. Should be either `jks` to use the Java
666-
Keystore format, or `PKCS12` to use PKCS#12 files. The default is `jks`.
668+
The format of the truststore file. Should be `jks` to use the Java
669+
Keystore format, `PKCS12` to use PKCS#12 files, or `PKCS11` to use a PKCS#11 token.
670+
The default is `jks`.
667671

668672
`ssl.verification_mode`::
669673
Indicates the type of verification when using `ldaps` to protect against man
@@ -1048,7 +1052,7 @@ Must be either a Java Keystore (jks) or a PKCS#12 file.
10481052
same time.
10491053

10501054
`ssl.truststore.type`::
1051-
The type of the truststore (`ssl.truststore.path`). Must be either `jks` or
1055+
The type of the truststore (`ssl.truststore.path`). Must be either `jks` or
10521056
`PKCS12`. If the keystore path ends in ".p12", ".pfx" or "pkcs12", this setting
10531057
defaults to `PKCS12`. Otherwise, it defaults to `jks`.
10541058

@@ -1303,6 +1307,32 @@ a PKCS#12 container includes trusted certificate ("anchor") entries look for
13031307
`openssl pkcs12 -info` output, or `trustedCertEntry` in the
13041308
`keytool -list` output.
13051309

1310+
===== PKCS#11 tokens
1311+
1312+
When using a PKCS#11 cryptographic token, which contains the
1313+
private key, certificate, and certificates that should be trusted, use
1314+
the following settings:
1315+
1316+
`xpack.ssl.keystore.type`::
1317+
Set this to `PKCS11`.
1318+
1319+
`xpack.ssl.truststore.type`::
1320+
Set this to `PKCS11`.
1321+
1322+
1323+
[[pkcs11-truststore-note]]
1324+
[NOTE]
1325+
When configuring the PKCS#11 token that your JVM is configured to use as
1326+
a keystore or a truststore for Elasticsearch, the PIN for the token can be
1327+
configured by setting the appropriate value to `xpack.ssl.truststore.password`
1328+
or `xpack.ssl.truststore.secure_password`. In the absence of the above, {es} will
1329+
fallback to use he appropriate JVM setting (`-Djavax.net.ssl.trustStorePassword`)
1330+
if that s set.
1331+
Since there can only be one PKCS#11 token configured, only one keystore and
1332+
truststore will be usable for configuration in {es}. This in turn means
1333+
that only one certificate can be used for TLS both in the transport and the
1334+
http layer.
1335+
13061336
[[http-tls-ssl-settings]]
13071337
:ssl-prefix: xpack.security.http
13081338
:component: HTTP

docs/reference/settings/ssl-settings.asciidoc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,17 @@ Password to the PKCS#12 file.
145145

146146
+{ssl-prefix}.ssl.truststore.secure_password+ (<<secure-settings,Secure>>)::
147147
Password to the PKCS#12 file.
148+
149+
===== PKCS#11 Tokens
150+
151+
{security} can be configured to use a PKCS#11 token that contains the private key,
152+
certificate and certificates that should be trusted.
153+
154+
PKCS#11 token require additional configuration on the JVM level and can be enabled
155+
via the following settings:
156+
157+
+{ssl-prefix}.keystore.type+::
158+
Set this to `PKCS11` to indicate that the PKCS#11 token should be used as a keystore.
159+
160+
+{ssl-prefix}.truststore.type+::
161+
Set this to `PKCS11` to indicate that the PKCS#11 token should be used as a truststore.

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ The list does not include certificates that are sourced from the default SSL
3434
context of the Java Runtime Environment (JRE), even if those certificates are in
3535
use within {xpack}.
3636

37+
NOTE: When a PKCS#11 token is configured as the truststore of the JRE, the API
38+
will return all the certificates that are included in the PKCS#11 token
39+
irrespectively to whether these are used in the {es} TLS configuration or not.
40+
3741
If {xpack} is configured to use a keystore or truststore, the API output
3842
includes all certificates in that store, even though some of the certificates
3943
might not be in active use within the cluster.

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ public static X509ExtendedKeyManager getKeyManager(X509KeyPairSettings keyPair,
197197
static KeyConfig createKeyConfig(X509KeyPairSettings keyPair, Settings settings, String trustStoreAlgorithm) {
198198
String keyPath = keyPair.keyPath.get(settings).orElse(null);
199199
String keyStorePath = keyPair.keystorePath.get(settings).orElse(null);
200+
String keyStoreType = getKeyStoreType(keyPair.keystoreType, settings, keyStorePath);
200201

201202
if (keyPath != null && keyStorePath != null) {
202203
throw new IllegalArgumentException("you cannot specify a keystore and key file");
@@ -212,10 +213,9 @@ static KeyConfig createKeyConfig(X509KeyPairSettings keyPair, Settings settings,
212213
return new PEMKeyConfig(keyPath, keyPassword, certPath);
213214
}
214215

215-
if (keyStorePath != null) {
216+
if (keyStorePath != null || keyStoreType.equalsIgnoreCase("pkcs11")) {
216217
SecureString keyStorePassword = keyPair.keystorePassword.get(settings);
217218
String keyStoreAlgorithm = keyPair.keystoreAlgorithm.get(settings);
218-
String keyStoreType = getKeyStoreType(keyPair.keystoreType, settings, keyStorePath);
219219
SecureString keyStoreKeyPassword = keyPair.keystoreKeyPassword.get(settings);
220220
if (keyStoreKeyPassword.length() == 0) {
221221
keyStoreKeyPassword = keyStorePassword;
@@ -224,7 +224,6 @@ static KeyConfig createKeyConfig(X509KeyPairSettings keyPair, Settings settings,
224224
trustStoreAlgorithm);
225225
}
226226
return null;
227-
228227
}
229228

230229
/**

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import org.elasticsearch.ElasticsearchException;
99
import org.elasticsearch.common.Nullable;
10+
import org.elasticsearch.common.settings.SecureString;
1011
import org.elasticsearch.env.Environment;
1112
import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;
1213

@@ -30,9 +31,14 @@
3031
*/
3132
class DefaultJDKTrustConfig extends TrustConfig {
3233

33-
static final DefaultJDKTrustConfig INSTANCE = new DefaultJDKTrustConfig();
34+
private SecureString trustStorePassword;
3435

35-
private DefaultJDKTrustConfig() {
36+
/**
37+
* @param trustStorePassword the password for the default jdk truststore defined either as a system property or in the Elasticsearch
38+
* configuration. It applies only when PKCS#11 tokens are user, is null otherwise
39+
*/
40+
DefaultJDKTrustConfig(@Nullable SecureString trustStorePassword) {
41+
this.trustStorePassword = trustStorePassword;
3642
}
3743

3844
@Override
@@ -76,13 +82,14 @@ public int hashCode() {
7682
/**
7783
* Merges the default trust configuration with the provided {@link TrustConfig}
7884
* @param trustConfig the trust configuration to merge with
85+
* @param trustStorePassword the password for the default jdk truststore. It applies only to PKCS#11 tokens
7986
* @return a {@link TrustConfig} that represents a combination of both trust configurations
8087
*/
81-
static TrustConfig merge(TrustConfig trustConfig) {
88+
static TrustConfig merge(TrustConfig trustConfig, SecureString trustStorePassword) {
8289
if (trustConfig == null) {
83-
return INSTANCE;
90+
return new DefaultJDKTrustConfig(trustStorePassword);
8491
} else {
85-
return new CombiningTrustConfig(Arrays.asList(INSTANCE, trustConfig));
92+
return new CombiningTrustConfig(Arrays.asList(new DefaultJDKTrustConfig(trustStorePassword), trustConfig));
8693
}
8794
}
8895

@@ -94,9 +101,10 @@ static TrustConfig merge(TrustConfig trustConfig) {
94101
* @return the KeyStore used as truststore for PKCS#11 initialized with the password, null otherwise
95102
*/
96103
private KeyStore getSystemTrustStore() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
97-
if (System.getProperty("javax.net.ssl.trustStoreType", "").equalsIgnoreCase("PKCS11")) {
104+
if (System.getProperty("javax.net.ssl.trustStoreType", "").equalsIgnoreCase("PKCS11")
105+
&& trustStorePassword != null) {
98106
KeyStore keyStore = KeyStore.getInstance("PKCS11");
99-
keyStore.load(null, System.getProperty("javax.net.ssl.trustStorePassword", "").toCharArray());
107+
keyStore.load(null, trustStorePassword.getChars());
100108
return keyStore;
101109
}
102110
return null;

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

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -212,12 +212,11 @@ private static TrustConfig createTrustConfig(Settings settings, KeyConfig keyCon
212212

213213
private static TrustConfig createCertChainTrustConfig(Settings settings, KeyConfig keyConfig, SSLConfiguration global) {
214214
String trustStorePath = SETTINGS_PARSER.truststorePath.get(settings).orElse(null);
215-
215+
String trustStoreType = getKeyStoreType(SETTINGS_PARSER.truststoreType, settings, trustStorePath);
216216
List<String> caPaths = getListOrNull(SETTINGS_PARSER.caPaths, settings);
217217
if (trustStorePath != null && caPaths != null) {
218218
throw new IllegalArgumentException("you cannot specify a truststore and ca files");
219219
}
220-
221220
VerificationMode verificationMode = SETTINGS_PARSER.verificationMode.get(settings).orElseGet(() -> {
222221
if (global != null) {
223222
return global.verificationMode();
@@ -228,24 +227,39 @@ private static TrustConfig createCertChainTrustConfig(Settings settings, KeyConf
228227
return TrustAllConfig.INSTANCE;
229228
} else if (caPaths != null) {
230229
return new PEMTrustConfig(caPaths);
231-
} else if (trustStorePath != null) {
232-
SecureString trustStorePassword = SETTINGS_PARSER.truststorePassword.get(settings);
230+
} else if (trustStorePath != null || trustStoreType.equalsIgnoreCase("pkcs11")) {
233231
String trustStoreAlgorithm = SETTINGS_PARSER.truststoreAlgorithm.get(settings);
234-
String trustStoreType = getKeyStoreType(SETTINGS_PARSER.truststoreType, settings, trustStorePath);
232+
SecureString trustStorePassword = SETTINGS_PARSER.truststorePassword.get(settings);
235233
return new StoreTrustConfig(trustStorePath, trustStoreType, trustStorePassword, trustStoreAlgorithm);
236234
} else if (global == null && System.getProperty("javax.net.ssl.trustStore") != null
237235
&& System.getProperty("javax.net.ssl.trustStore").equals("NONE") == false) {
238236
try (SecureString truststorePassword = new SecureString(System.getProperty("javax.net.ssl.trustStorePassword", ""))) {
239237
return new StoreTrustConfig(System.getProperty("javax.net.ssl.trustStore"), KeyStore.getDefaultType(), truststorePassword,
240-
System.getProperty("ssl.TrustManagerFactory.algorithm", TrustManagerFactory.getDefaultAlgorithm()));
238+
System.getProperty("ssl.TrustManagerFactory.algorithm", TrustManagerFactory.getDefaultAlgorithm()));
241239
}
242240
} else if (global != null && keyConfig == global.keyConfig()) {
243241
return global.trustConfig();
244242
} else if (keyConfig != KeyConfig.NONE) {
245-
return DefaultJDKTrustConfig.merge(keyConfig);
243+
return DefaultJDKTrustConfig.merge(keyConfig, getDefaultTrustStorePassword(settings));
246244
} else {
247-
return DefaultJDKTrustConfig.INSTANCE;
245+
return new DefaultJDKTrustConfig(getDefaultTrustStorePassword(settings));
246+
}
247+
}
248+
249+
private static SecureString getDefaultTrustStorePassword(Settings settings) {
250+
// We only handle the default store password if it's a PKCS#11 token
251+
if (System.getProperty("javax.net.ssl.trustStoreType", "").equalsIgnoreCase("PKCS11")) {
252+
try (SecureString systemTrustStorePassword =
253+
new SecureString(System.getProperty("javax.net.ssl.trustStorePassword", "").toCharArray())) {
254+
if (systemTrustStorePassword.length() == 0) {
255+
try (SecureString trustStorePassword = SETTINGS_PARSER.truststorePassword.get(settings)) {
256+
return trustStorePassword;
257+
}
258+
}
259+
return systemTrustStorePassword;
260+
}
248261
}
262+
return null;
249263
}
250264

251265
private static List<String> getListOrNull(Setting<List<String>> listSetting, Settings settings) {

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

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
import javax.net.ssl.X509ExtendedTrustManager;
1616

1717
import java.io.IOException;
18-
import java.io.InputStream;
19-
import java.nio.file.Files;
2018
import java.nio.file.Path;
2119
import java.security.GeneralSecurityException;
2220
import java.security.Key;
@@ -49,7 +47,7 @@ class StoreKeyConfig extends KeyConfig {
4947

5048
/**
5149
* Creates a new configuration that can be used to load key and trust material from a {@link KeyStore}
52-
* @param keyStorePath the path to the keystore file
50+
* @param keyStorePath the path to the keystore file or null when keyStoreType is pkcs11
5351
* @param keyStoreType the type of the keystore file
5452
* @param keyStorePassword the password for the keystore
5553
* @param keyPassword the password for the private key in the keystore
@@ -58,7 +56,7 @@ class StoreKeyConfig extends KeyConfig {
5856
*/
5957
StoreKeyConfig(String keyStorePath, String keyStoreType, SecureString keyStorePassword, SecureString keyPassword,
6058
String keyStoreAlgorithm, String trustStoreAlgorithm) {
61-
this.keyStorePath = Objects.requireNonNull(keyStorePath, "keystore path must be specified");
59+
this.keyStorePath = keyStorePath;
6260
this.keyStoreType = Objects.requireNonNull(keyStoreType, "keystore type must be specified");
6361
// since we support reloading the keystore, we must store the passphrase in memory for the life of the node, so we
6462
// clone the password and never close it during our uses below
@@ -71,7 +69,7 @@ class StoreKeyConfig extends KeyConfig {
7169
@Override
7270
X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) {
7371
try {
74-
KeyStore ks = getKeyStore(environment);
72+
KeyStore ks = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
7573
checkKeyStore(ks);
7674
return CertParsingUtils.keyManager(ks, keyPassword.getChars(), keyStoreAlgorithm);
7775
} catch (IOException | CertificateException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
@@ -82,16 +80,16 @@ X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) {
8280
@Override
8381
X509ExtendedTrustManager createTrustManager(@Nullable Environment environment) {
8482
try {
85-
return CertParsingUtils.trustManager(keyStorePath, keyStoreType, keyStorePassword.getChars(), trustStoreAlgorithm, environment);
86-
} catch (Exception e) {
83+
KeyStore ks = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
84+
return CertParsingUtils.trustManager(ks, trustStoreAlgorithm);
85+
} catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
8786
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
8887
}
8988
}
9089

9190
@Override
9291
Collection<CertificateInfo> certificates(Environment environment) throws GeneralSecurityException, IOException {
93-
final Path path = CertParsingUtils.resolvePath(keyStorePath, environment);
94-
final KeyStore trustStore = CertParsingUtils.readKeyStore(path, keyStoreType, keyStorePassword.getChars());
92+
final KeyStore trustStore = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
9593
final List<CertificateInfo> certificates = new ArrayList<>();
9694
final Enumeration<String> aliases = trustStore.aliases();
9795
while (aliases.hasMoreElements()) {
@@ -112,13 +110,16 @@ Collection<CertificateInfo> certificates(Environment environment) throws General
112110

113111
@Override
114112
List<Path> filesToMonitor(@Nullable Environment environment) {
113+
if (keyStorePath == null) {
114+
return Collections.emptyList();
115+
}
115116
return Collections.singletonList(CertParsingUtils.resolvePath(keyStorePath, environment));
116117
}
117118

118119
@Override
119120
List<PrivateKey> privateKeys(@Nullable Environment environment) {
120121
try {
121-
KeyStore keyStore = getKeyStore(environment);
122+
KeyStore keyStore = getStore(environment, keyStorePath, keyStoreType, keyStorePassword);
122123
List<PrivateKey> privateKeys = new ArrayList<>();
123124
for (Enumeration<String> e = keyStore.aliases(); e.hasMoreElements(); ) {
124125
final String alias = e.nextElement();
@@ -135,15 +136,6 @@ List<PrivateKey> privateKeys(@Nullable Environment environment) {
135136
}
136137
}
137138

138-
private KeyStore getKeyStore(@Nullable Environment environment)
139-
throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
140-
try (InputStream in = Files.newInputStream(CertParsingUtils.resolvePath(keyStorePath, environment))) {
141-
KeyStore ks = KeyStore.getInstance(keyStoreType);
142-
ks.load(in, keyStorePassword.getChars());
143-
return ks;
144-
}
145-
}
146-
147139
private void checkKeyStore(KeyStore keyStore) throws KeyStoreException {
148140
Enumeration<String> aliases = keyStore.aliases();
149141
while (aliases.hasMoreElements()) {
@@ -152,9 +144,11 @@ private void checkKeyStore(KeyStore keyStore) throws KeyStoreException {
152144
return;
153145
}
154146
}
155-
throw new IllegalArgumentException("the keystore [" + keyStorePath + "] does not contain a private key entry");
147+
final String message = null != keyStorePath ?
148+
"the keystore [" + keyStorePath + "] does not contain a private key entry" :
149+
"the configured PKCS#11 token does not contain a private key entry";
150+
throw new IllegalArgumentException(message);
156151
}
157-
158152
@Override
159153
public boolean equals(Object o) {
160154
if (this == o) return true;

0 commit comments

Comments
 (0)