Skip to content

Commit c4e3b08

Browse files
attempt to create keystore file via java
1 parent b9dceb8 commit c4e3b08

File tree

4 files changed

+151
-8
lines changed

4 files changed

+151
-8
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ docker-test:
420420

421421
docker-stop:
422422
docker compose --env-file src/test/resources/docker-env/.env -f src/test/resources/docker-env/docker-compose.yml down; \
423+
rm -rf /tmp/redis-env-work
423424

424425
prepare: stop
425426

pom.xml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,25 @@
589589
<scope>test</scope>
590590
</dependency>
591591

592+
593+
<!-- Bouncy Castle Provider (Crypto API) -->
594+
<dependency>
595+
<groupId>org.bouncycastle</groupId>
596+
<artifactId>bcprov-jdk18on</artifactId>
597+
<version>1.80</version>
598+
</dependency>
599+
600+
<!-- Bouncy Castle PKIX (X.509 Certificates, PKCS, CSR) -->
601+
<dependency>
602+
<groupId>org.bouncycastle</groupId>
603+
<artifactId>bcpkix-jdk18on</artifactId>
604+
<version>1.80</version>
605+
</dependency>
606+
607+
608+
609+
610+
592611
</dependencies>
593612

594613
<build>

src/test/java/io/lettuce/core/SslIntegrationTests.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ class SslIntegrationTests extends TestSupport {
109109

110110
private final RedisClient redisClient;
111111

112+
private static File keystore;
113+
112114
@Inject
113115
SslIntegrationTests(RedisClient redisClient) {
114116
this.redisClient = redisClient;
@@ -129,6 +131,13 @@ static void beforeClass() {
129131
truststoreFile2 = path2.toFile();
130132
cacertFile = envCa(Paths.get("redis-standalone-sentinel-controlled/work/tls")).toFile();
131133

134+
try {
135+
generateCertificates(testGenCertPath("redis-standalone-0/work/tls").toString(), "redis-standalone-0/work/tls");
136+
} catch (Exception e) {
137+
throw new RuntimeException(e);
138+
}
139+
140+
keystore = Paths.get("redis-standalone-0/work/tls/keystore.jks").toFile();
132141
assumeTrue(CanConnect.to(TestSettings.host(), sslPort()), "Assume that stunnel runs on port 6443");
133142
// Maybe we should do a list.
134143
assertThat(truststoreFile0).exists();
@@ -212,10 +221,10 @@ void standaloneWithJdkSslUsingTruststoreUrl() throws Exception {
212221

213222
@Test
214223
void standaloneWithClientCertificates() {
215-
// 6445
224+
// 6444
216225
SslOptions sslOptions = SslOptions.builder() //
217226
.jdkSslProvider() //
218-
.keystore(new File(KEYSTORE), "changeit".toCharArray()) //
227+
.keystore(keystore, "changeit".toCharArray()) //
219228
.truststore(truststoreFile0, "changeit") //
220229
.build();
221230
setOptions(sslOptions);

src/test/java/io/lettuce/test/settings/TlsSettings.java

Lines changed: 120 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,33 @@
11
package io.lettuce.test.settings;
22

3-
import java.io.FileInputStream;
4-
import java.io.FileOutputStream;
5-
import java.io.IOException;
3+
import io.lettuce.core.internal.LettuceStrings;
4+
import org.testcontainers.shaded.org.bouncycastle.cert.X509v3CertificateBuilder;
5+
import org.testcontainers.shaded.org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
6+
import org.testcontainers.shaded.org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
7+
import org.testcontainers.shaded.org.bouncycastle.operator.ContentSigner;
8+
import org.testcontainers.shaded.org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
9+
import org.testcontainers.shaded.org.bouncycastle.pkcs.PKCS10CertificationRequest;
10+
import org.testcontainers.shaded.org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
11+
import org.testcontainers.shaded.org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
12+
import org.testcontainers.shaded.org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
13+
import org.testcontainers.shaded.org.bouncycastle.util.io.pem.PemObject;
14+
import org.testcontainers.shaded.org.bouncycastle.util.io.pem.PemWriter;
15+
import sun.security.x509.X500Name;
16+
17+
import javax.security.auth.x500.X500Principal;
18+
import java.io.*;
19+
import java.math.BigInteger;
20+
import java.nio.file.Files;
621
import java.nio.file.Path;
722
import java.nio.file.Paths;
8-
import java.security.KeyStore;
9-
import java.security.KeyStoreException;
10-
import java.security.NoSuchAlgorithmException;
23+
import java.security.*;
1124
import java.security.cert.CertificateException;
1225
import java.security.cert.CertificateFactory;
1326
import java.security.cert.X509Certificate;
27+
import java.time.Duration;
28+
import java.time.Instant;
1429
import java.util.ArrayList;
30+
import java.util.Date;
1531
import java.util.List;
1632
import java.util.UUID;
1733

@@ -27,6 +43,10 @@ public class TlsSettings {
2743

2844
private static final String TEST_TRUSTSTORE = "truststore.jks";
2945

46+
private static final String TEST_KEYSTORE = "keystore.jks";
47+
48+
private static final String PASSWORD = "changeit";
49+
3050
public static Path envServerCert(Path certLocation) {
3151
return Paths.get(TEST_WORK_FOLDER, certLocation.toString(), TEST_SERVER_CERT);
3252
}
@@ -39,6 +59,14 @@ public static Path testTruststorePath(String name) {
3959
return Paths.get(TEST_WORK_FOLDER, name + '-' + TEST_TRUSTSTORE);
4060
}
4161

62+
public static Path testGenCertPath(String keystoreLocation) {
63+
return Paths.get(TEST_WORK_FOLDER, keystoreLocation);
64+
}
65+
66+
public static Path testKeyStorePath(String keystoreLocation) {
67+
return Paths.get(TEST_WORK_FOLDER, keystoreLocation, TEST_KEYSTORE);
68+
}
69+
4270
/**
4371
* Creates an empty truststore.
4472
*
@@ -121,4 +149,90 @@ public static Path createAndSaveTestTruststore(String trustStoreName, Path certi
121149
return createAndSaveTruststore(trustedCertPaths, trustStorePath, truststorePassword);
122150
}
123151

152+
public static void generateCertificates(String caDir, String keystoreFile) throws Exception {
153+
createDirectories(caDir);
154+
155+
KeyPair keyPair = generateKeyPair();
156+
157+
savePrivateKey(keyPair.getPrivate(), caDir);
158+
159+
PKCS10CertificationRequest csr = generateCSR(keyPair);
160+
161+
X509Certificate certificate = signCertificate(csr, keyPair);
162+
163+
saveCertificate(certificate, caDir);
164+
165+
createPKCS12(keyPair.getPrivate(), certificate, keystoreFile);
166+
}
167+
168+
private static void createDirectories(String caDir) throws IOException {
169+
Files.createDirectories(Paths.get(caDir, "private"));
170+
Files.createDirectories(Paths.get(caDir, "certs"));
171+
}
172+
173+
private static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
174+
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
175+
keyGen.initialize(2048);
176+
return keyGen.generateKeyPair();
177+
}
178+
179+
private static void savePrivateKey(PrivateKey privateKey, String caDir) throws Exception {
180+
String keyPath = Paths.get(caDir, "private", "client.key.pem").toString();
181+
try (PemWriter pemWriter = new PemWriter(new FileWriter(keyPath))) {
182+
pemWriter.writeObject(new PemObject("PRIVATE KEY", privateKey.getEncoded()));
183+
}
184+
185+
File keyFile = new File(keyPath);
186+
keyFile.setReadable(false, false);
187+
keyFile.setReadable(true, true);
188+
keyFile.setWritable(false, false);
189+
keyFile.setExecutable(false, false);
190+
}
191+
192+
private static PKCS10CertificationRequest generateCSR(KeyPair keyPair) throws Exception {
193+
X500Principal subject = new X500Principal("CN=client,O=lettuce,C=NN,ST=Unknown,L=Unknown");
194+
PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(subject, keyPair.getPublic());
195+
196+
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate());
197+
198+
return csrBuilder.build(signer);
199+
}
200+
201+
private static X509Certificate signCertificate(PKCS10CertificationRequest csr, KeyPair keyPair) throws Exception {
202+
org.bouncycastle.asn1.x500.X500Name issuerName = new org.bouncycastle.asn1.x500.X500Name(
203+
"CN=client,O=lettuce,C=NN,ST=Unknown,L=Unknown");
204+
205+
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis());
206+
Instant now = Instant.now();
207+
Date startDate = Date.from(now);
208+
Date endDate = Date.from(now.plus(Duration.ofDays(375)));
209+
210+
JcaPKCS10CertificationRequest jcaCsr = new JcaPKCS10CertificationRequest(csr);
211+
212+
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(org.testcontainers.shaded.org.bouncycastle.asn1.x500.X500Name.getInstance(issuerName), serialNumber, startDate, endDate,
213+
jcaCsr.getSubject(), jcaCsr.getPublicKey());
214+
215+
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate());
216+
217+
return new JcaX509CertificateConverter().getCertificate(certBuilder.build(signer));
218+
}
219+
220+
private static void saveCertificate(X509Certificate certificate, String caDir) throws Exception {
221+
String certPath = Paths.get(caDir, "certs", "client.cert.pem").toString();
222+
try (PemWriter pemWriter = new PemWriter(new FileWriter(certPath))) {
223+
pemWriter.writeObject(new PemObject("CERTIFICATE", certificate.getEncoded()));
224+
}
225+
}
226+
227+
private static void createPKCS12(PrivateKey privateKey, X509Certificate certificate, String keystoreFile) throws Exception {
228+
KeyStore keyStore = KeyStore.getInstance("PKCS12");
229+
keyStore.load(null, null);
230+
231+
keyStore.setKeyEntry("client", privateKey, PASSWORD.toCharArray(), new X509Certificate[] { certificate });
232+
233+
try (OutputStream output = Files.newOutputStream(testKeyStorePath(keystoreFile))) {
234+
keyStore.store(output, PASSWORD.toCharArray());
235+
}
236+
}
237+
124238
}

0 commit comments

Comments
 (0)