Skip to content

Commit d2c929f

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 d2c929f

9 files changed

Lines changed: 100 additions & 27 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/util/SSLConfigConstants.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.List;
2323
import java.util.function.Function;
2424

25+
import org.bouncycastle.crypto.CryptoServicesRegistrar;
2526
import org.opensearch.common.settings.Setting;
2627
import org.opensearch.common.settings.Settings;
2728
import org.opensearch.security.ssl.config.CertType;
@@ -42,7 +43,7 @@ public final class SSLConfigConstants {
4243
public static final String ENABLED = "enabled";
4344
public static final String CLIENT_AUTH_MODE = "clientauth_mode";
4445
public static final String ENFORCE_CERT_RELOAD_DN_VERIFICATION = "enforce_cert_reload_dn_verification";
45-
public static final String DEFAULT_STORE_TYPE = "JKS";
46+
public static final String DEFAULT_STORE_TYPE = CryptoServicesRegistrar.isInApprovedOnlyMode() ? "BCFKS" : "JKS";
4647
public static final String SSL_PREFIX = "plugins.security.ssl.";
4748

4849
public static final String KEYSTORE_TYPE = "keystore_type";

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

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
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;
@@ -51,6 +52,7 @@
5152
import java.security.spec.InvalidKeySpecException;
5253
import java.security.spec.PKCS8EncodedKeySpec;
5354
import java.util.Collection;
55+
import java.util.Locale;
5456
import javax.crypto.Cipher;
5557
import javax.crypto.EncryptedPrivateKeyInfo;
5658
import javax.crypto.NoSuchPaddingException;
@@ -60,18 +62,27 @@
6062

6163
import org.apache.logging.log4j.LogManager;
6264
import org.apache.logging.log4j.Logger;
65+
import org.bouncycastle.asn1.ASN1Encodable;
66+
import org.bouncycastle.asn1.ASN1InputStream;
67+
import org.bouncycastle.asn1.ASN1Integer;
68+
import org.bouncycastle.asn1.ASN1Sequence;
69+
import org.bouncycastle.crypto.CryptoServicesRegistrar;
6370
import org.bouncycastle.util.io.pem.PemObject;
6471
import org.bouncycastle.util.io.pem.PemReader;
6572

6673
import org.opensearch.OpenSearchException;
6774
import org.opensearch.common.settings.Settings;
6875
import org.opensearch.env.Environment;
6976

77+
import static org.opensearch.security.ssl.util.SSLConfigConstants.DEFAULT_STORE_TYPE;
78+
7079
public final class PemKeyReader {
7180

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

7687
private static byte[] readPrivateKey(File file) throws KeyException {
7788
try (final InputStream in = new FileInputStream(file)) {
@@ -173,16 +184,13 @@ public static X509Certificate loadCertificateFromStream(InputStream in) throws E
173184
return (X509Certificate) fact.generateCertificate(in);
174185
}
175186

176-
public static KeyStore loadKeyStore(String storePath, String keyStorePassword, String type) throws Exception {
187+
public static KeyStore loadKeyStore(final String storePath, final String keyStorePassword, final String type) throws Exception {
177188
if (storePath == null) {
178189
return null;
179190
}
191+
String storeType = extractStoreType(storePath, type);
180192

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());
193+
final KeyStore store = KeyStore.getInstance(storeType);
186194
store.load(new FileInputStream(storePath), keyStorePassword == null ? null : keyStorePassword.toCharArray());
187195
return store;
188196
}
@@ -308,8 +316,7 @@ public static KeyStore toTruststore(final String trustCertificatesAliasPrefix, f
308316
return null;
309317
}
310318

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

314321
if (trustCertificates != null && trustCertificates.length > 0) {
315322
for (int i = 0; i < trustCertificates.length; i++) {
@@ -328,8 +335,7 @@ public static KeyStore toKeystore(
328335
) throws Exception {
329336

330337
if (authenticationCertificateAlias != null && authenticationCertificate != null && authenticationKey != null) {
331-
KeyStore ks = KeyStore.getInstance(JKS);
332-
ks.load(null, null);
338+
KeyStore ks = newEmptyStore();
333339
ks.setKeyEntry(authenticationCertificateAlias, authenticationKey, password, authenticationCertificate);
334340
return ks;
335341
} else {
@@ -347,5 +353,54 @@ public static char[] randomChars(int len) {
347353
return ret;
348354
}
349355

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

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)