Skip to content

Commit 8540984

Browse files
author
Igonin
committed
Migrate from BC to BCFIPS libraries
Signed-off-by: Igonin <iigonin@sap.de>
1 parent a961ec7 commit 8540984

File tree

46 files changed

+443
-173
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+443
-173
lines changed

buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ public void execute(Task t) {
164164
test.systemProperty("tests.seed", BuildParams.getTestSeed());
165165
}
166166

167+
var securityFile = "java.security";
168+
test.systemProperty(
169+
"java.security.properties",
170+
project.getRootProject().getLayout().getProjectDirectory() + "/distribution/src/config/" + securityFile
171+
);
172+
167173
// don't track these as inputs since they contain absolute paths and break cache relocatability
168174
File gradleHome = project.getGradle().getGradleUserHomeDir();
169175
String gradleVersion = project.getGradle().getGradleVersion();

client/rest/build.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
*/
3030

3131
import de.thetaphi.forbiddenapis.gradle.CheckForbiddenApis
32+
import org.opensearch.gradle.precommit.TestingConventionsTasks
3233

3334
apply plugin: 'opensearch.build'
3435
apply plugin: 'opensearch.publish'
@@ -51,6 +52,9 @@ dependencies {
5152
api "commons-codec:commons-codec:${versions.commonscodec}"
5253
api "commons-logging:commons-logging:${versions.commonslogging}"
5354
api "org.slf4j:slf4j-api:${versions.slf4j}"
55+
runtimeOnly "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}"
56+
runtimeOnly "org.bouncycastle:bctls-fips:${versions.bouncycastle_tls}"
57+
runtimeOnly "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}"
5458

5559
// reactor
5660
api "io.projectreactor:reactor-core:${versions.reactor}"
@@ -70,6 +74,10 @@ dependencies {
7074
testImplementation "org.apache.logging.log4j:log4j-slf4j-impl:${versions.log4j}"
7175
}
7276

77+
tasks.named("dependencyLicenses").configure {
78+
mapping from: /bc.*/, to: 'bouncycastle'
79+
}
80+
7381
tasks.withType(CheckForbiddenApis).configureEach {
7482
//client does not depend on server, so only jdk and http signatures should be checked
7583
replaceSignatureFiles('jdk-signatures', 'http-signatures')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
c8df3d47f9854f3e9ca57e9fc862da18c9381fa9
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
9c0632a6c5ca09a86434cf5e02e72c221e1c930f
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1d37b7a28560684f5b8e4fd65478c9130d4015d0
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Copyright (c) 2000 - 2023 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
5+
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
6+
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7+
8+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
9+
Software.
10+
11+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
12+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
13+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
14+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,23 @@
3838
import com.sun.net.httpserver.HttpsServer;
3939

4040
import org.apache.hc.core5.http.HttpHost;
41+
import org.apache.hc.core5.ssl.SSLContextBuilder;
4142
import org.junit.AfterClass;
4243
import org.junit.BeforeClass;
4344

4445
import javax.net.ssl.KeyManagerFactory;
4546
import javax.net.ssl.SSLContext;
46-
import javax.net.ssl.SSLHandshakeException;
47+
import javax.net.ssl.SSLException;
4748
import javax.net.ssl.TrustManagerFactory;
4849

4950
import java.io.IOException;
5051
import java.io.InputStream;
5152
import java.net.InetAddress;
5253
import java.net.InetSocketAddress;
53-
import java.nio.file.Files;
54-
import java.nio.file.Paths;
5554
import java.security.AccessController;
56-
import java.security.KeyFactory;
5755
import java.security.KeyStore;
5856
import java.security.PrivilegedAction;
59-
import java.security.cert.Certificate;
60-
import java.security.cert.CertificateFactory;
61-
import java.security.spec.PKCS8EncodedKeySpec;
57+
import java.security.SecureRandom;
6258

6359
import static org.hamcrest.Matchers.instanceOf;
6460
import static org.junit.Assert.assertEquals;
@@ -75,7 +71,7 @@ public class RestClientBuilderIntegTests extends RestClientTestCase {
7571
@BeforeClass
7672
public static void startHttpServer() throws Exception {
7773
httpsServer = HttpsServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
78-
httpsServer.setHttpsConfigurator(new HttpsConfigurator(getSslContext()));
74+
httpsServer.setHttpsConfigurator(new HttpsConfigurator(getSslContext(true)));
7975
httpsServer.createContext("/", new ResponseHandler());
8076
httpsServer.start();
8177
}
@@ -103,11 +99,11 @@ public void testBuilderUsesDefaultSSLContext() throws Exception {
10399
client.performRequest(new Request("GET", "/"));
104100
fail("connection should have been rejected due to SSL handshake");
105101
} catch (Exception e) {
106-
assertThat(e, instanceOf(SSLHandshakeException.class));
102+
assertThat(e.getCause(), instanceOf(SSLException.class));
107103
}
108104
}
109105

110-
SSLContext.setDefault(getSslContext());
106+
SSLContext.setDefault(getSslContext(false));
111107
try (RestClient client = buildRestClient()) {
112108
Response response = client.performRequest(new Request("GET", "/"));
113109
assertEquals(200, response.getStatusLine().getStatusCode());
@@ -122,34 +118,37 @@ private RestClient buildRestClient() {
122118
return RestClient.builder(new HttpHost("https", address.getHostString(), address.getPort())).build();
123119
}
124120

125-
private static SSLContext getSslContext() throws Exception {
126-
SSLContext sslContext = SSLContext.getInstance(getProtocol());
121+
private static SSLContext getSslContext(boolean server) throws Exception {
122+
SSLContext sslContext;
123+
char[] password = "password".toCharArray();
124+
SecureRandom secureRandom = SecureRandom.getInstance("DEFAULT", "BCFIPS");
125+
String fileExtension = ".jks";
126+
127127
try (
128-
InputStream certFile = RestClientBuilderIntegTests.class.getResourceAsStream("/test.crt");
129-
InputStream keyStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/test_truststore.jks")
128+
InputStream trustStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/test_truststore" + fileExtension);
129+
InputStream keyStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/testks" + fileExtension)
130130
) {
131-
// Build a keystore of default type programmatically since we can't use JKS keystores to
132-
// init a KeyManagerFactory in FIPS 140 JVMs.
133-
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
134-
keyStore.load(null, "password".toCharArray());
135-
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
136-
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(
137-
Files.readAllBytes(Paths.get(RestClientBuilderIntegTests.class.getResource("/test.der").toURI()))
138-
);
139-
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
140-
keyStore.setKeyEntry(
141-
"mykey",
142-
keyFactory.generatePrivate(privateKeySpec),
143-
"password".toCharArray(),
144-
new Certificate[] { certFactory.generateCertificate(certFile) }
145-
);
146-
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
147-
kmf.init(keyStore, "password".toCharArray());
131+
KeyStore keyStore = KeyStore.getInstance("JKS");
132+
keyStore.load(keyStoreFile, password);
133+
KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX", "BCJSSE");
134+
kmf.init(keyStore, password);
135+
148136
KeyStore trustStore = KeyStore.getInstance("JKS");
149-
trustStore.load(keyStoreFile, "password".toCharArray());
150-
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
137+
trustStore.load(trustStoreFile, password);
138+
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX", "BCJSSE");
151139
tmf.init(trustStore);
152-
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
140+
141+
SSLContextBuilder sslContextBuilder = SSLContextBuilder.create()
142+
.setProvider("BCJSSE")
143+
.setProtocol(getProtocol())
144+
.setSecureRandom(secureRandom);
145+
146+
if (server) {
147+
sslContextBuilder.loadKeyMaterial(keyStore, password);
148+
}
149+
sslContextBuilder.loadTrustMaterial(trustStore, null);
150+
sslContext = sslContextBuilder.build();
151+
153152
}
154153
return sslContext;
155154
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Security properties for non-approved mode 'org.bouncycastle.fips.approved_only=false'. Intended to be used complementary e.g.
2+
3+
security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
4+
security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
5+
security.provider.3=SUN
6+
security.provider.4=SunJGSS
7+
8+
ssl.KeyManagerFactory.algorithm=PKIX
9+
ssl.TrustManagerFactory.algorithm=PKIX

distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/KeyStoreWrapperTests.java

Lines changed: 78 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,13 @@
6868
import java.nio.file.Path;
6969
import java.security.GeneralSecurityException;
7070
import java.security.KeyStore;
71+
import java.security.KeyStoreException;
7172
import java.security.MessageDigest;
73+
import java.security.NoSuchAlgorithmException;
74+
import java.security.NoSuchProviderException;
7275
import java.security.SecureRandom;
76+
import java.security.cert.CertificateException;
77+
import java.security.spec.InvalidKeySpecException;
7378
import java.util.ArrayList;
7479
import java.util.Arrays;
7580
import java.util.Base64;
@@ -366,29 +371,8 @@ public void testIllegalSettingName() throws Exception {
366371

367372
public void testBackcompatV1() throws Exception {
368373
assumeFalse("Can't run in a FIPS JVM as PBE is not available", inFipsJvm());
374+
generateV1();
369375
Path configDir = env.configDir();
370-
NIOFSDirectory directory = new NIOFSDirectory(configDir);
371-
try (IndexOutput output = EndiannessReverserUtil.createOutput(directory, "opensearch.keystore", IOContext.DEFAULT)) {
372-
CodecUtil.writeHeader(output, "opensearch.keystore", 1);
373-
output.writeByte((byte) 0); // hasPassword = false
374-
output.writeString("PKCS12");
375-
output.writeString("PBE");
376-
377-
SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE");
378-
KeyStore keystore = KeyStore.getInstance("PKCS12");
379-
keystore.load(null, null);
380-
SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray()));
381-
KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]);
382-
keystore.setEntry("string_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter);
383-
384-
ByteArrayOutputStream keystoreBytesStream = new ByteArrayOutputStream();
385-
keystore.store(keystoreBytesStream, new char[0]);
386-
byte[] keystoreBytes = keystoreBytesStream.toByteArray();
387-
output.writeInt(keystoreBytes.length);
388-
output.writeBytes(keystoreBytes, keystoreBytes.length);
389-
CodecUtil.writeFooter(output);
390-
}
391-
392376
KeyStoreWrapper keystore = KeyStoreWrapper.load(configDir);
393377
keystore.decrypt(new char[0]);
394378
SecureString testValue = keystore.getString("string_setting");
@@ -397,47 +381,8 @@ public void testBackcompatV1() throws Exception {
397381

398382
public void testBackcompatV2() throws Exception {
399383
assumeFalse("Can't run in a FIPS JVM as PBE is not available", inFipsJvm());
384+
byte[] fileBytes = generateV2();
400385
Path configDir = env.configDir();
401-
NIOFSDirectory directory = new NIOFSDirectory(configDir);
402-
byte[] fileBytes = new byte[20];
403-
random().nextBytes(fileBytes);
404-
try (IndexOutput output = EndiannessReverserUtil.createOutput(directory, "opensearch.keystore", IOContext.DEFAULT)) {
405-
406-
CodecUtil.writeHeader(output, "opensearch.keystore", 2);
407-
output.writeByte((byte) 0); // hasPassword = false
408-
output.writeString("PKCS12");
409-
output.writeString("PBE"); // string algo
410-
output.writeString("PBE"); // file algo
411-
412-
output.writeVInt(2); // num settings
413-
output.writeString("string_setting");
414-
output.writeString("STRING");
415-
output.writeString("file_setting");
416-
output.writeString("FILE");
417-
418-
SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE");
419-
KeyStore keystore = KeyStore.getInstance("PKCS12");
420-
keystore.load(null, null);
421-
SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray()));
422-
KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]);
423-
keystore.setEntry("string_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter);
424-
425-
byte[] base64Bytes = Base64.getEncoder().encode(fileBytes);
426-
char[] chars = new char[base64Bytes.length];
427-
for (int i = 0; i < chars.length; ++i) {
428-
chars[i] = (char) base64Bytes[i]; // PBE only stores the lower 8 bits, so this narrowing is ok
429-
}
430-
secretKey = secretFactory.generateSecret(new PBEKeySpec(chars));
431-
keystore.setEntry("file_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter);
432-
433-
ByteArrayOutputStream keystoreBytesStream = new ByteArrayOutputStream();
434-
keystore.store(keystoreBytesStream, new char[0]);
435-
byte[] keystoreBytes = keystoreBytesStream.toByteArray();
436-
output.writeInt(keystoreBytes.length);
437-
output.writeBytes(keystoreBytes, keystoreBytes.length);
438-
CodecUtil.writeFooter(output);
439-
}
440-
441386
KeyStoreWrapper keystore = KeyStoreWrapper.load(configDir);
442387
keystore.decrypt(new char[0]);
443388
SecureString testValue = keystore.getString("string_setting");
@@ -497,6 +442,77 @@ public void testLegacyV3() throws GeneralSecurityException, IOException {
497442
assertThat(toByteArray(wrapper.getFile("file_setting")), equalTo("file_value".getBytes(StandardCharsets.UTF_8)));
498443
}
499444

445+
private void generateV1() throws IOException, NoSuchAlgorithmException, NoSuchProviderException, CertificateException,
446+
InvalidKeySpecException, KeyStoreException {
447+
Path configDir = env.configDir();
448+
NIOFSDirectory directory = new NIOFSDirectory(configDir);
449+
try (IndexOutput output = EndiannessReverserUtil.createOutput(directory, "opensearch.keystore", IOContext.DEFAULT)) {
450+
CodecUtil.writeHeader(output, "opensearch.keystore", 1);
451+
output.writeByte((byte) 0); // hasPassword = false
452+
output.writeString("PKCS12");
453+
output.writeString("PBE");
454+
455+
SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE", "SunJCE");
456+
KeyStore keystore = KeyStore.getInstance("PKCS12", "SUN");
457+
keystore.load(null, null);
458+
SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray()));
459+
KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]);
460+
keystore.setEntry("string_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter);
461+
462+
ByteArrayOutputStream keystoreBytesStream = new ByteArrayOutputStream();
463+
keystore.store(keystoreBytesStream, new char[0]);
464+
byte[] keystoreBytes = keystoreBytesStream.toByteArray();
465+
output.writeInt(keystoreBytes.length);
466+
output.writeBytes(keystoreBytes, keystoreBytes.length);
467+
CodecUtil.writeFooter(output);
468+
}
469+
}
470+
471+
private byte[] generateV2() throws Exception {
472+
Path configDir = env.configDir();
473+
NIOFSDirectory directory = new NIOFSDirectory(configDir);
474+
byte[] fileBytes = new byte[20];
475+
random().nextBytes(fileBytes);
476+
try (IndexOutput output = EndiannessReverserUtil.createOutput(directory, "opensearch.keystore", IOContext.DEFAULT)) {
477+
478+
CodecUtil.writeHeader(output, "opensearch.keystore", 2);
479+
output.writeByte((byte) 0); // hasPassword = false
480+
output.writeString("PKCS12");
481+
output.writeString("PBE"); // string algo
482+
output.writeString("PBE"); // file algo
483+
484+
output.writeVInt(2); // num settings
485+
output.writeString("string_setting");
486+
output.writeString("STRING");
487+
output.writeString("file_setting");
488+
output.writeString("FILE");
489+
490+
SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE", "SunJCE");
491+
KeyStore keystore = KeyStore.getInstance("PKCS12", "SUN");
492+
keystore.load(null, null);
493+
SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray()));
494+
KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]);
495+
keystore.setEntry("string_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter);
496+
497+
byte[] base64Bytes = Base64.getEncoder().encode(fileBytes);
498+
char[] chars = new char[base64Bytes.length];
499+
for (int i = 0; i < chars.length; ++i) {
500+
chars[i] = (char) base64Bytes[i]; // PBE only stores the lower 8 bits, so this narrowing is ok
501+
}
502+
secretKey = secretFactory.generateSecret(new PBEKeySpec(chars));
503+
keystore.setEntry("file_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter);
504+
505+
ByteArrayOutputStream keystoreBytesStream = new ByteArrayOutputStream();
506+
keystore.store(keystoreBytesStream, new char[0]);
507+
byte[] keystoreBytes = keystoreBytesStream.toByteArray();
508+
output.writeInt(keystoreBytes.length);
509+
output.writeBytes(keystoreBytes, keystoreBytes.length);
510+
CodecUtil.writeFooter(output);
511+
}
512+
513+
return fileBytes;
514+
}
515+
500516
private byte[] toByteArray(final InputStream is) throws IOException {
501517
final ByteArrayOutputStream os = new ByteArrayOutputStream();
502518
final byte[] buffer = new byte[1024];

0 commit comments

Comments
 (0)