Skip to content

Commit b625e85

Browse files
iigoninbennygoerzigKarstenSchnitterKai Sternad
committed
Enable automatic detection of keystore type and support for FIPS-compliant BCFKS stores.
Signed-off-by: Iwan Igonin <iigonin@sternad.de> Co-authored-by: Benny Goerzig <benny.goerzig@sap.com> Co-authored-by: Karsten Schnitter <k.schnitter@sap.com> Co-authored-by: Kai Sternad <k.sternad@sternad.de>
1 parent fb024c2 commit b625e85

10 files changed

Lines changed: 119 additions & 32 deletions

File tree

src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import org.opensearch.test.framework.certificate.CertificateData;
7070
import org.opensearch.test.framework.certificate.TestCertificates;
7171

72+
import static org.opensearch.security.ssl.util.SSLConfigConstants.DEFAULT_STORE_TYPE;
7273
import static org.opensearch.test.framework.cluster.TestRestClientConfiguration.getBasicAuthHeader;
7374

7475
/**
@@ -273,7 +274,7 @@ private SSLContext getSSLContext(CertificateData useCertificateData) {
273274
trustCertificates = PemKeyReader.loadCertificatesFromFile(getTestCertificates().getRootCertificate());
274275

275276
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
276-
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
277+
KeyStore ks = KeyStore.getInstance(DEFAULT_STORE_TYPE);
277278

278279
ks.load(null);
279280

src/integrationTest/java/org/opensearch/test/framework/ldap/LdapServer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
import com.unboundid.ldif.LDIFReader;
4949
import com.unboundid.util.ssl.SSLUtil;
5050

51+
import static org.opensearch.security.ssl.util.SSLConfigConstants.DEFAULT_STORE_TYPE;
52+
5153
/**
5254
* Based on class org.opensearch.security.auth.ldap.srv.LdapServer from older tests
5355
*/
@@ -154,7 +156,7 @@ private void addLdapCertificatesToKeystore(KeyStore keyStore) throws KeyStoreExc
154156
}
155157

156158
private static KeyStore createEmptyKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
157-
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
159+
KeyStore keyStore = KeyStore.getInstance(DEFAULT_STORE_TYPE);
158160
keyStore.load(null);
159161
return keyStore;
160162
}

src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,12 +1064,16 @@ private List<String> getOtherName(List<?> altName) {
10641064
}
10651065
try (final ASN1InputStream in = new ASN1InputStream((byte[]) altName.get(1))) {
10661066
final ASN1Primitive asn1Primitive = in.readObject();
1067-
final ASN1Sequence sequence = ASN1Sequence.getInstance(asn1Primitive);
1067+
// BC X.509 (used in FIPS mode) returns the GeneralName [0] IMPLICIT encoding;
1068+
// JDK X.509 returns just the OtherName SEQUENCE body. Handle both.
1069+
final ASN1Sequence sequence = asn1Primitive instanceof ASN1TaggedObject
1070+
? ASN1Sequence.getInstance((ASN1TaggedObject) asn1Primitive, false)
1071+
: ASN1Sequence.getInstance(asn1Primitive);
10681072
final ASN1ObjectIdentifier asn1ObjectIdentifier = ASN1ObjectIdentifier.getInstance(sequence.getObjectAt(0));
1069-
final ASN1TaggedObject asn1TaggedObject = ASN1TaggedObject.getInstance(sequence.getObjectAt(1));
1073+
final ASN1TaggedObject asn1TaggedObject = (ASN1TaggedObject) sequence.getObjectAt(1).toASN1Primitive();
10701074
ASN1Object maybeTaggedAsn1Primitive = asn1TaggedObject.getObject();
10711075
if (maybeTaggedAsn1Primitive instanceof ASN1TaggedObject) {
1072-
maybeTaggedAsn1Primitive = ASN1TaggedObject.getInstance(maybeTaggedAsn1Primitive).getObject();
1076+
maybeTaggedAsn1Primitive = ((ASN1TaggedObject) maybeTaggedAsn1Primitive).getObject();
10731077
}
10741078
if (maybeTaggedAsn1Primitive instanceof ASN1String) {
10751079
return ImmutableList.of(asn1ObjectIdentifier.getId(), maybeTaggedAsn1Primitive.toString());

src/main/java/org/opensearch/security/ssl/config/Certificate.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,16 @@ private List<String> parseOtherName(List<?> altName) {
120120
}
121121
try (final ASN1InputStream in = new ASN1InputStream((byte[]) altName.get(1))) {
122122
final ASN1Primitive asn1Primitive = in.readObject();
123-
final ASN1Sequence sequence = ASN1Sequence.getInstance(asn1Primitive);
123+
// BC X.509 (used in FIPS mode) returns the GeneralName [0] IMPLICIT encoding;
124+
// JDK X.509 returns just the OtherName SEQUENCE body. Handle both.
125+
final ASN1Sequence sequence = asn1Primitive instanceof ASN1TaggedObject
126+
? ASN1Sequence.getInstance((ASN1TaggedObject) asn1Primitive, false)
127+
: ASN1Sequence.getInstance(asn1Primitive);
124128
final ASN1ObjectIdentifier asn1ObjectIdentifier = ASN1ObjectIdentifier.getInstance(sequence.getObjectAt(0));
125-
final ASN1TaggedObject asn1TaggedObject = ASN1TaggedObject.getInstance(sequence.getObjectAt(1));
129+
final ASN1TaggedObject asn1TaggedObject = (ASN1TaggedObject) sequence.getObjectAt(1).toASN1Primitive();
126130
ASN1Object maybeTaggedAsn1Primitive = asn1TaggedObject.getObject();
127131
if (maybeTaggedAsn1Primitive instanceof ASN1TaggedObject) {
128-
maybeTaggedAsn1Primitive = ASN1TaggedObject.getInstance(maybeTaggedAsn1Primitive).getObject();
132+
maybeTaggedAsn1Primitive = ((ASN1TaggedObject) maybeTaggedAsn1Primitive).getObject();
129133
}
130134
if (maybeTaggedAsn1Primitive instanceof ASN1String) {
131135
return ImmutableList.of(asn1ObjectIdentifier.getId(), maybeTaggedAsn1Primitive.toString());

src/main/java/org/opensearch/security/ssl/config/KeyStoreUtils.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
import io.netty.handler.ssl.ApplicationProtocolNegotiator;
4242
import io.netty.handler.ssl.SslContext;
4343

44+
import static org.opensearch.security.ssl.util.SSLConfigConstants.DEFAULT_STORE_TYPE;
45+
4446
final class KeyStoreUtils {
4547

4648
private final static Logger log = LogManager.getLogger(KeyStoreUtils.class);
@@ -113,7 +115,7 @@ public static KeyStore loadTrustStore(final Path path, final String type, final
113115
if (aliasCertificate == null) {
114116
throw new OpenSearchException("Couldn't find SSL certificate for alias " + alias);
115117
}
116-
keyStore = newKeyStore();
118+
keyStore = newKeyStore(type);
117119
keyStore.setCertificateEntry(alias, aliasCertificate);
118120
}
119121
return keyStore;
@@ -137,7 +139,11 @@ public static KeyStore newTrustStoreFromPem(final Path pemFile) {
137139
}
138140

139141
private static KeyStore newKeyStore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException {
140-
final var keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
142+
return newKeyStore(DEFAULT_STORE_TYPE);
143+
}
144+
145+
private static KeyStore newKeyStore(String type) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException {
146+
final var keyStore = KeyStore.getInstance(type);
141147
keyStore.load(null, null);
142148
return keyStore;
143149
}
@@ -235,7 +241,7 @@ public static KeyStore newKeyStore(
235241
throw new CertificateException("Couldn't find certificate chain for alias " + alias);
236242
}
237243
final var key = keyStore.getKey(alias, keyPassword);
238-
keyStore = newKeyStore();
244+
keyStore = newKeyStore(type);
239245
keyStore.setKeyEntry(alias, key, keyPassword, certificateChain);
240246
}
241247
return keyStore;

src/main/java/org/opensearch/security/ssl/config/SslCertificatesLoader.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
import org.opensearch.common.settings.SecureSetting;
2424
import org.opensearch.common.settings.Settings;
2525
import org.opensearch.env.Environment;
26+
import org.opensearch.security.support.PemKeyReader;
2627

2728
import static org.opensearch.security.ssl.SecureSSLSettings.SECURE_SUFFIX;
2829
import static org.opensearch.security.ssl.util.SSLConfigConstants.DEFAULT_STORE_PASSWORD;
29-
import static org.opensearch.security.ssl.util.SSLConfigConstants.DEFAULT_STORE_TYPE;
3030
import static org.opensearch.security.ssl.util.SSLConfigConstants.KEYSTORE_ALIAS;
3131
import static org.opensearch.security.ssl.util.SSLConfigConstants.KEYSTORE_FILEPATH;
3232
import static org.opensearch.security.ssl.util.SSLConfigConstants.KEYSTORE_KEY_PASSWORD;
@@ -126,9 +126,12 @@ private KeyStoreConfiguration.JdkKeyStoreConfiguration buildJdkKeyStoreConfigura
126126
final char[] keyStorePassword,
127127
final char[] keyPassword
128128
) {
129+
final Path path = resolvePath(environment.settings().get(sslConfigSuffix + KEYSTORE_FILEPATH), environment);
130+
final String explicitType = environment.settings().get(sslConfigSuffix + KEYSTORE_TYPE);
131+
final String resolvedType = PemKeyReader.extractStoreType(path.toString(), explicitType);
129132
return new KeyStoreConfiguration.JdkKeyStoreConfiguration(
130-
resolvePath(environment.settings().get(sslConfigSuffix + KEYSTORE_FILEPATH), environment),
131-
environment.settings().get(sslConfigSuffix + KEYSTORE_TYPE, DEFAULT_STORE_TYPE),
133+
path,
134+
resolvedType,
132135
settings.get(KEYSTORE_ALIAS, null),
133136
keyStorePassword,
134137
keyPassword
@@ -140,9 +143,12 @@ private TrustStoreConfiguration.JdkTrustStoreConfiguration buildJdkTrustStoreCon
140143
final Environment environment,
141144
final char[] trustStorePassword
142145
) {
146+
final Path path = resolvePath(environment.settings().get(sslConfigSuffix + TRUSTSTORE_FILEPATH), environment);
147+
final String explicitType = environment.settings().get(sslConfigSuffix + TRUSTSTORE_TYPE);
148+
final String resolvedType = PemKeyReader.extractStoreType(path.toString(), explicitType);
143149
return new TrustStoreConfiguration.JdkTrustStoreConfiguration(
144-
resolvePath(environment.settings().get(sslConfigSuffix + TRUSTSTORE_FILEPATH), environment),
145-
environment.settings().get(sslConfigSuffix + TRUSTSTORE_TYPE, DEFAULT_STORE_TYPE),
150+
path,
151+
resolvedType,
146152
settings.get(TRUSTSTORE_ALIAS, null),
147153
trustStorePassword
148154
);

src/main/java/org/opensearch/security/ssl/util/SSLConfigConstants.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@
1717

1818
package org.opensearch.security.ssl.util;
1919

20+
import java.security.KeyStore;
2021
import java.util.Arrays;
2122
import java.util.Collections;
2223
import java.util.List;
2324
import java.util.function.Function;
2425

26+
import org.bouncycastle.crypto.CryptoServicesRegistrar;
27+
2528
import org.opensearch.common.settings.Setting;
2629
import org.opensearch.common.settings.Settings;
2730
import org.opensearch.security.ssl.config.CertType;
@@ -42,7 +45,9 @@ public final class SSLConfigConstants {
4245
public static final String ENABLED = "enabled";
4346
public static final String CLIENT_AUTH_MODE = "clientauth_mode";
4447
public static final String ENFORCE_CERT_RELOAD_DN_VERIFICATION = "enforce_cert_reload_dn_verification";
45-
public static final String DEFAULT_STORE_TYPE = "JKS";
48+
public static final String DEFAULT_STORE_TYPE = CryptoServicesRegistrar.isInApprovedOnlyMode()
49+
? "BCFKS"
50+
: KeyStore.getDefaultType().toUpperCase();
4651
public static final String SSL_PREFIX = "plugins.security.ssl.";
4752

4853
public static final String KEYSTORE_TYPE = "keystore_type";

src/main/java/org/opensearch/security/support/PemKeyReader.java

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@
2626

2727
package org.opensearch.security.support;
2828

29+
import java.io.BufferedInputStream;
2930
import java.io.ByteArrayInputStream;
3031
import java.io.File;
3132
import java.io.FileInputStream;
3233
import java.io.IOException;
3334
import java.io.InputStream;
3435
import java.io.InputStreamReader;
36+
import java.io.UncheckedIOException;
3537
import java.nio.charset.StandardCharsets;
3638
import java.nio.file.Files;
3739
import java.nio.file.LinkOption;
@@ -51,6 +53,7 @@
5153
import java.security.spec.InvalidKeySpecException;
5254
import java.security.spec.PKCS8EncodedKeySpec;
5355
import java.util.Collection;
56+
import java.util.Locale;
5457
import javax.crypto.Cipher;
5558
import javax.crypto.EncryptedPrivateKeyInfo;
5659
import javax.crypto.NoSuchPaddingException;
@@ -60,18 +63,27 @@
6063

6164
import org.apache.logging.log4j.LogManager;
6265
import org.apache.logging.log4j.Logger;
66+
import org.bouncycastle.asn1.ASN1Encodable;
67+
import org.bouncycastle.asn1.ASN1InputStream;
68+
import org.bouncycastle.asn1.ASN1Integer;
69+
import org.bouncycastle.asn1.ASN1Sequence;
70+
import org.bouncycastle.crypto.CryptoServicesRegistrar;
6371
import org.bouncycastle.util.io.pem.PemObject;
6472
import org.bouncycastle.util.io.pem.PemReader;
6573

6674
import org.opensearch.OpenSearchException;
6775
import org.opensearch.common.settings.Settings;
6876
import org.opensearch.env.Environment;
6977

78+
import static org.opensearch.security.ssl.util.SSLConfigConstants.DEFAULT_STORE_TYPE;
79+
7080
public final class PemKeyReader {
7181

7282
private static final Logger log = LogManager.getLogger(PemKeyReader.class);
73-
static final String JKS = "JKS";
74-
static final String PKCS12 = "PKCS12";
83+
84+
public static final String JKS = "JKS";
85+
public static final String PKCS12 = "PKCS12";
86+
public static final String BCFKS = "BCFKS";
7587

7688
private static byte[] readPrivateKey(File file) throws KeyException {
7789
try (final InputStream in = new FileInputStream(file)) {
@@ -173,16 +185,13 @@ public static X509Certificate loadCertificateFromStream(InputStream in) throws E
173185
return (X509Certificate) fact.generateCertificate(in);
174186
}
175187

176-
public static KeyStore loadKeyStore(String storePath, String keyStorePassword, String type) throws Exception {
188+
public static KeyStore loadKeyStore(final String storePath, final String keyStorePassword, final String type) throws Exception {
177189
if (storePath == null) {
178190
return null;
179191
}
192+
String storeType = extractStoreType(storePath, type);
180193

181-
if (type == null || !type.toUpperCase().equals(JKS) || !type.toUpperCase().equals(PKCS12)) {
182-
type = JKS;
183-
}
184-
185-
final KeyStore store = KeyStore.getInstance(type.toUpperCase());
194+
final KeyStore store = KeyStore.getInstance(storeType);
186195
store.load(new FileInputStream(storePath), keyStorePassword == null ? null : keyStorePassword.toCharArray());
187196
return store;
188197
}
@@ -308,8 +317,7 @@ public static KeyStore toTruststore(final String trustCertificatesAliasPrefix, f
308317
return null;
309318
}
310319

311-
KeyStore ks = KeyStore.getInstance(JKS);
312-
ks.load(null);
320+
KeyStore ks = newEmptyStore();
313321

314322
if (trustCertificates != null && trustCertificates.length > 0) {
315323
for (int i = 0; i < trustCertificates.length; i++) {
@@ -328,8 +336,7 @@ public static KeyStore toKeystore(
328336
) throws Exception {
329337

330338
if (authenticationCertificateAlias != null && authenticationCertificate != null && authenticationKey != null) {
331-
KeyStore ks = KeyStore.getInstance(JKS);
332-
ks.load(null, null);
339+
KeyStore ks = newEmptyStore();
333340
ks.setKeyEntry(authenticationCertificateAlias, authenticationKey, password, authenticationCertificate);
334341
return ks;
335342
} else {
@@ -347,5 +354,57 @@ public static char[] randomChars(int len) {
347354
return ret;
348355
}
349356

357+
public static String extractStoreType(String storePath, String storeType) {
358+
if (null == storeType) {
359+
storeType = detectStoreType(storePath);
360+
}
361+
if (CryptoServicesRegistrar.isInApprovedOnlyMode() && !PemKeyReader.BCFKS.equalsIgnoreCase(storeType)) {
362+
throw new IllegalArgumentException(
363+
storeType.toUpperCase(Locale.ROOT) + " truststores are not supported in FIPS mode - use BCFKS."
364+
);
365+
}
366+
return storeType;
367+
}
368+
369+
private static String detectStoreType(String path) {
370+
371+
try (InputStream raw = new BufferedInputStream(new FileInputStream(path))) {
372+
raw.mark(32);
373+
byte[] magic = new byte[4];
374+
if (raw.read(magic) < 4) {
375+
throw new IllegalArgumentException("Cannot detect keystore type: file too short: " + path);
376+
}
377+
// JKS: 0xFEEDFEED
378+
if ((magic[0] & 0xFF) == 0xFE //
379+
&& (magic[1] & 0xFF) == 0xED //
380+
&& (magic[2] & 0xFF) == 0xFE //
381+
&& (magic[3] & 0xFF) == 0xED //
382+
) {
383+
return PemKeyReader.JKS;
384+
}
385+
// ASN.1: distinguish BCFKS from PKCS12 by outer structure
386+
// PKCS12 (RFC 7292): outer SEQUENCE starts with INTEGER (version = 3)
387+
// BCFKS: outer SEQUENCE starts with SEQUENCE (encrypted content envelope)
388+
if ((magic[0] & 0xFF) == 0x30) {
389+
raw.reset();
390+
try (ASN1InputStream asn1In = new ASN1InputStream(raw)) {
391+
ASN1Sequence outer = (ASN1Sequence) asn1In.readObject();
392+
ASN1Encodable first = outer.getObjectAt(0);
393+
if (first instanceof ASN1Integer) return PemKeyReader.PKCS12;
394+
if (first instanceof ASN1Sequence) return PemKeyReader.BCFKS;
395+
} catch (Exception ignored) {}
396+
}
397+
throw new IllegalArgumentException("Cannot detect keystore type for: " + path + ". Specify explicitly with -kst/-tst.");
398+
} catch (IOException e) {
399+
throw new UncheckedIOException(e);
400+
}
401+
}
402+
403+
private static KeyStore newEmptyStore() throws Exception {
404+
var ks = KeyStore.getInstance(DEFAULT_STORE_TYPE);
405+
ks.load(null, null);
406+
return ks;
407+
}
408+
350409
private PemKeyReader() {}
351410
}

src/main/java/org/opensearch/security/tools/SecurityAdmin.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -502,14 +502,14 @@ public static int execute(final String[] args) throws Exception {
502502
System.out.println(" ... done");
503503

504504
if (ks != null) {
505-
kst = kst == null ? (ks.endsWith(".jks") ? "JKS" : "PKCS12") : kst;
505+
kst = PemKeyReader.extractStoreType(ks, kst);
506506
if (kspass == null && promptForPassword) {
507507
kspass = promptForPassword("Keystore", "kspass", OPENDISTRO_SECURITY_KS_PASS);
508508
}
509509
}
510510

511511
if (ts != null) {
512-
tst = tst == null ? (ts.endsWith(".jks") ? "JKS" : "PKCS12") : tst;
512+
tst = PemKeyReader.extractStoreType(ts, tst);
513513
if (tspass == null && promptForPassword) {
514514
tspass = promptForPassword("Truststore", "tspass", OPENDISTRO_SECURITY_TS_PASS);
515515
}

src/test/java/org/opensearch/security/ssl/config/JdkSslCertificatesLoaderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ Path createKeyStore(final String type, final String password, final Map<String,
305305
}
306306

307307
KeyStore keyStore(final String type) throws Exception {
308-
final var keyStore = KeyStore.getInstance(isNull(type) ? KeyStore.getDefaultType() : type);
308+
final var keyStore = KeyStore.getInstance(isNull(type) ? DEFAULT_STORE_TYPE : type);
309309
keyStore.load(null, null);
310310
return keyStore;
311311
}

0 commit comments

Comments
 (0)