Skip to content

Commit 3128912

Browse files
committed
use in-memory keystore to avoid file locks
1 parent e7cae4a commit 3128912

4 files changed

Lines changed: 154 additions & 104 deletions

File tree

src/test-integration/java/com/bettercloud/vault/api/AuthBackendCertTests.java

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,16 @@
66
import com.bettercloud.vault.VaultException;
77
import com.bettercloud.vault.util.SSLUtils;
88
import com.bettercloud.vault.util.VaultContainer;
9-
import org.bouncycastle.operator.OperatorCreationException;
109
import org.junit.BeforeClass;
1110
import org.junit.ClassRule;
1211
import org.junit.Test;
1312

1413
import java.io.File;
1514
import java.io.IOException;
16-
import java.security.InvalidKeyException;
17-
import java.security.KeyStoreException;
18-
import java.security.NoSuchAlgorithmException;
19-
import java.security.NoSuchProviderException;
20-
import java.security.SignatureException;
21-
import java.security.cert.CertificateException;
15+
import java.security.KeyStore;
16+
import java.util.HashMap;
2217

18+
import static com.bettercloud.vault.util.TestConstants.PASSWORD;
2319
import static org.junit.Assert.assertNotNull;
2420
import static org.junit.Assert.assertNotSame;
2521

@@ -34,14 +30,15 @@ public class AuthBackendCertTests {
3430

3531
@ClassRule
3632
public static final VaultContainer container = new VaultContainer();
33+
private static HashMap<String, Object> clientCertAndKey;
34+
private static String cert;
3735

3836
@BeforeClass
39-
public static void setupClass() throws IOException, InterruptedException, CertificateException, SignatureException,
40-
NoSuchAlgorithmException, KeyStoreException, OperatorCreationException, NoSuchProviderException,
41-
InvalidKeyException {
37+
public static void setupClass() throws IOException, InterruptedException {
38+
clientCertAndKey = SSLUtils.createClientCertAndKey();
39+
cert = (String) clientCertAndKey.get("cert");
4240
container.initAndUnsealVault();
43-
SSLUtils.createClientCertAndKey();
44-
container.setupBackendCert();
41+
container.setupBackendCert(cert);
4542
}
4643

4744
@Test
@@ -53,8 +50,8 @@ public void testLoginByCert_usingJksConfig() throws VaultException {
5350
.readTimeout(30)
5451
.sslConfig(
5552
new SslConfig()
56-
.keyStoreFile(new File(VaultContainer.CLIENT_KEYSTORE), "password")
57-
.trustStoreFile(new File(VaultContainer.CLIENT_TRUSTSTORE))
53+
.keyStore((KeyStore) clientCertAndKey.get("clientKeystore"), PASSWORD)
54+
.trustStore((KeyStore) clientCertAndKey.get("clientTrustStore"))
5855
.build()
5956
)
6057
.build();
@@ -76,8 +73,8 @@ public void testLoginByCert_usingPemConfig() throws VaultException {
7673
.sslConfig(
7774
new SslConfig()
7875
.pemFile(new File(VaultContainer.CERT_PEMFILE))
79-
.clientPemFile(new File(VaultContainer.CLIENT_CERT_PEMFILE))
80-
.clientKeyPemFile(new File(VaultContainer.CLIENT_PRIVATE_KEY_PEMFILE))
76+
.clientPemUTF8(cert)
77+
.clientKeyPemUTF8((String) clientCertAndKey.get("privateKey"))
8178
.build()
8279
)
8380
.build();

src/test-integration/java/com/bettercloud/vault/util/SSLUtils.java

Lines changed: 137 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.bouncycastle.asn1.x509.GeneralName;
77
import org.bouncycastle.asn1.x509.GeneralNames;
88
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
9+
import org.bouncycastle.cert.CertIOException;
910
import org.bouncycastle.cert.X509CertificateHolder;
1011
import org.bouncycastle.cert.X509v3CertificateBuilder;
1112
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
@@ -27,8 +28,6 @@
2728

2829
import javax.security.auth.x500.X500Principal;
2930
import java.io.ByteArrayOutputStream;
30-
import java.io.FileNotFoundException;
31-
import java.io.FileOutputStream;
3231
import java.io.FileReader;
3332
import java.io.IOException;
3433
import java.io.OutputStreamWriter;
@@ -51,6 +50,7 @@
5150
import java.security.cert.X509Certificate;
5251
import java.util.Base64;
5352
import java.util.Date;
53+
import java.util.HashMap;
5454

5555
/**
5656
* Static utility methods for generating client-side SSL certs and keys, for tests that use Vault's TLS Certificate
@@ -67,148 +67,191 @@ private SSLUtils() {
6767
*
6868
* <p>Also constructs a JKS keystore, with a client certificate to use for authentication with Vault's TLS
6969
* Certificate auth backend. Stores this cert as a PEM file as well, so that can be registered with Vault
70-
* as a recognized certificate in {@link VaultContainer#setupBackendCert()}.</p>
70+
* as a recognized certificate in {@link VaultContainer#setupBackendCert(String)}.</p>
7171
*
7272
* <p>This method must be called AFTER {@link VaultContainer#initAndUnsealVault()}, and BEFORE
73-
* {@link VaultContainer#setupBackendCert()}.</p>
73+
* {@link VaultContainer#setupBackendCert(String)}.</p>
7474
*
75-
* @throws KeyStoreException
76-
* @throws IOException
77-
* @throws CertificateException
78-
* @throws NoSuchAlgorithmException
75+
* @throws IOException When certificate was not created
76+
* @return
7977
*/
80-
public static void createClientCertAndKey() throws KeyStoreException, IOException, CertificateException,
81-
NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException,
82-
OperatorCreationException {
78+
public static HashMap<String, Object> createClientCertAndKey() throws IOException {
8379

8480
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
85-
final FileReader fileReader = new FileReader(CERT_PEMFILE);
86-
final PEMParser pemParser = new PEMParser(fileReader);
87-
final X509CertificateHolder certificateHolder = (X509CertificateHolder) pemParser.readObject();
88-
final X509Certificate vaultCertificate = new JcaX509CertificateConverter()
89-
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
90-
.getCertificate(certificateHolder);
81+
final X509CertificateHolder certificateHolder = getX509CertificateHolder();
82+
final X509Certificate vaultCertificate = getCertificate(certificateHolder);
83+
84+
KeyStore clientTrustStore = getClientTrustStore(vaultCertificate);
9185

9286
// Store the Vault's server certificate as a trusted cert in the truststore
93-
final KeyStore trustStore = KeyStore.getInstance("jks");
94-
trustStore.load(null);
95-
trustStore.setCertificateEntry("cert", vaultCertificate);
96-
try (final FileOutputStream keystoreOutputStream = new FileOutputStream(CLIENT_TRUSTSTORE)) {
97-
trustStore.store(keystoreOutputStream, "password".toCharArray());
98-
}
9987

10088
// Generate a client certificate, and store it in a Java keystore
10189
final KeyPair keyPair = generateKeyPair();
102-
final X509Certificate clientCertificate =
103-
generateCert(keyPair, "C=AU, O=The Legion of the Bouncy Castle, OU=Client Certificate, CN=localhost");
104-
final KeyStore keyStore = KeyStore.getInstance("jks");
105-
keyStore.load(null);
106-
keyStore.setKeyEntry("privatekey", keyPair.getPrivate(), "password".toCharArray(), new Certificate[]{clientCertificate});
107-
keyStore.setCertificateEntry("cert", clientCertificate);
108-
try (final FileOutputStream keystoreOutputStream = new FileOutputStream(CLIENT_KEYSTORE)) {
109-
keyStore.store(keystoreOutputStream, "password".toCharArray());
90+
final X509Certificate clientCertificate = generateCert(keyPair);
91+
if (clientCertificate == null) {
92+
throw new IOException("Failed to generate certificate");
11093
}
94+
final KeyStore clientKeystore = getClientKeystore(keyPair, clientCertificate);
11195

11296
// Also write the client certificate to a PEM file, so it can be registered with Vault
113-
writeCertToPem(clientCertificate, CLIENT_CERT_PEMFILE);
114-
writePrivateKeyToPem(keyPair.getPrivate(), CLIENT_PRIVATE_KEY_PEMFILE);
97+
String certToPem = certToPem(clientCertificate);
98+
String privateKeyToPem = privateKeyToPem(keyPair.getPrivate());
99+
return new HashMap<String, Object>() {
100+
{
101+
put("clientKeystore", clientKeystore);
102+
put("clientTrustStore", clientTrustStore);
103+
put("cert", certToPem);
104+
put("privateKey", privateKeyToPem);
105+
}
106+
};
107+
}
108+
109+
private static KeyStore getClientTrustStore(X509Certificate vaultCertificate) throws IOException {
110+
final KeyStore trustStore = emptyStore();
111+
try {
112+
trustStore.setCertificateEntry("cert", vaultCertificate);
113+
} catch (KeyStoreException e) {
114+
throw new IOException("Cannot create trust keystore.", e);
115+
}
116+
return trustStore;
117+
}
118+
119+
private static KeyStore getClientKeystore(KeyPair keyPair, X509Certificate clientCertificate) {
120+
try {
121+
final KeyStore keyStore = emptyStore();
122+
keyStore.setKeyEntry("privatekey", keyPair.getPrivate(), PASSWORD.toCharArray(), new Certificate[]{clientCertificate});
123+
keyStore.setCertificateEntry("cert", clientCertificate);
124+
return keyStore;
125+
} catch (KeyStoreException | IOException e) {
126+
return null;
127+
}
128+
}
129+
130+
private static X509CertificateHolder getX509CertificateHolder() {
131+
final PEMParser pemParser;
132+
try (FileReader fileReader = new FileReader(CERT_PEMFILE)) {
133+
pemParser = new PEMParser(fileReader);
134+
return (X509CertificateHolder) pemParser.readObject();
135+
} catch (IOException e) {
136+
return null;
137+
}
138+
}
139+
140+
private static X509Certificate getCertificate(X509CertificateHolder certificateHolder) {
141+
try {
142+
return new JcaX509CertificateConverter()
143+
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
144+
.getCertificate(certificateHolder);
145+
} catch (CertificateException e) {
146+
return null;
147+
}
115148
}
116149

117150
/**
118151
* See https://www.cryptoworkshop.com/guide/, chapter 3
119152
*
120153
* @return A 4096-bit RSA key pair
121-
* @throws NoSuchAlgorithmException
122154
*/
123-
private static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
124-
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", new BouncyCastleProvider());
125-
keyPairGenerator.initialize(4096);
126-
return keyPairGenerator.genKeyPair();
155+
private static KeyPair generateKeyPair() throws IOException {
156+
try {
157+
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", new BouncyCastleProvider());
158+
keyPairGenerator.initialize(4096);
159+
KeyPair keyPair = keyPairGenerator.genKeyPair();
160+
if (keyPair == null) {
161+
throw new IOException("Failed to generate keypair");
162+
}
163+
return keyPair;
164+
} catch (NoSuchAlgorithmException e) {
165+
throw new IOException("Failed to generate keypair", e);
166+
}
127167
}
128168

129169
/**
130170
* See http://www.programcreek.com/java-api-examples/index.php?api=org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder
131171
*
132172
* @param keyPair The RSA keypair with which to generate the certificate
133-
* @param issuer The issuer (and subject) to use for the certificate
134173
* @return An X509 certificate
135-
* @throws IOException
136-
* @throws CertificateException
137-
* @throws NoSuchProviderException
138-
* @throws NoSuchAlgorithmException
139-
* @throws InvalidKeyException
140-
* @throws SignatureException
141174
*/
142-
private static X509Certificate generateCert(final KeyPair keyPair, final String issuer) throws IOException,
143-
CertificateException, NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException,
144-
SignatureException, OperatorCreationException {
145-
final String subject = issuer;
175+
private static X509Certificate generateCert(final KeyPair keyPair) {
176+
String issuer = "C=AU, O=The Legion of the Bouncy Castle, OU=Client Certificate, CN=localhost";
146177
final X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(
147178
new X500Name(issuer),
148179
BigInteger.ONE,
149180
new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30),
150181
new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 30)),
151-
new X500Name(subject),
182+
new X500Name(issuer),
152183
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded())
153184
);
154185

155186
final GeneralNames subjectAltNames = new GeneralNames(new GeneralName(GeneralName.iPAddress, "127.0.0.1"));
156-
certificateBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAltNames);
187+
try {
188+
certificateBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAltNames);
189+
} catch (CertIOException e) {
190+
e.printStackTrace();
191+
return null;
192+
}
157193

158194
final AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1WithRSAEncryption");
159195
final AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
160196
final BcContentSignerBuilder signerBuilder = new BcRSAContentSignerBuilder(sigAlgId, digAlgId);
161-
final AsymmetricKeyParameter keyp = PrivateKeyFactory.createKey(keyPair.getPrivate().getEncoded());
162-
final ContentSigner signer = signerBuilder.build(keyp);
163-
final X509CertificateHolder x509CertificateHolder = certificateBuilder.build(signer);
164-
165-
final X509Certificate certificate = new JcaX509CertificateConverter()
166-
.getCertificate(x509CertificateHolder);
167-
certificate.checkValidity(new Date());
168-
certificate.verify(keyPair.getPublic());
197+
final X509CertificateHolder x509CertificateHolder;
198+
try {
199+
final AsymmetricKeyParameter keyp = PrivateKeyFactory.createKey(keyPair.getPrivate().getEncoded());
200+
final ContentSigner signer = signerBuilder.build(keyp);
201+
x509CertificateHolder = certificateBuilder.build(signer);
202+
} catch (IOException | OperatorCreationException e) {
203+
e.printStackTrace();
204+
return null;
205+
}
206+
207+
final X509Certificate certificate;
208+
try {
209+
certificate = new JcaX509CertificateConverter().getCertificate(x509CertificateHolder);
210+
certificate.checkValidity(new Date());
211+
certificate.verify(keyPair.getPublic());
212+
} catch (CertificateException | SignatureException | InvalidKeyException | NoSuchAlgorithmException | NoSuchProviderException e) {
213+
e.printStackTrace();
214+
return null;
215+
}
216+
169217
return certificate;
170218
}
171219

172220
/**
173221
* See https://stackoverflow.com/questions/3313020/write-x509-certificate-into-pem-formatted-string-in-java
174222
*
175223
* @param certificate An X509 certificate
176-
* @param filename The name (including path) of a file to which the certificate will be written in PEM format
177-
* @throws CertificateEncodingException
178-
* @throws FileNotFoundException
224+
* @return String certificate in pem format
179225
*/
180-
private static void writeCertToPem(final X509Certificate certificate, final String filename)
181-
throws CertificateEncodingException, FileNotFoundException {
226+
private static String certToPem(final X509Certificate certificate) throws IOException {
182227
final Base64.Encoder encoder = Base64.getMimeEncoder();
183228

184229
final String certHeader = "-----BEGIN CERTIFICATE-----\n";
185230
final String certFooter = "\n-----END CERTIFICATE-----";
186-
final byte[] certBytes = certificate.getEncoded();
187-
final String certContents = new String(encoder.encode(certBytes));
188-
final String certPem = certHeader + certContents + certFooter;
189-
try (final PrintWriter out = new PrintWriter(filename)) {
190-
out.println(certPem);
231+
final byte[] certBytes;
232+
try {
233+
certBytes = certificate.getEncoded();
234+
} catch (CertificateEncodingException e) {
235+
throw new IOException("Failed to encode certificate", e);
191236
}
237+
final String certContents = new String(encoder.encode(certBytes));
238+
return certHeader + certContents + certFooter;
192239
}
193240

194241
/**
195242
* See https://stackoverflow.com/questions/3313020/write-x509-certificate-into-pem-formatted-string-in-java
196243
*
197244
* @param key An RSA private key
198-
* @param filename The name (including path) of a file to which the private key will be written in PEM format
199-
* @throws FileNotFoundException
245+
* @return String private key in pem format
200246
*/
201-
private static void writePrivateKeyToPem(final PrivateKey key, final String filename) throws FileNotFoundException {
247+
private static String privateKeyToPem(final PrivateKey key) {
202248
final Base64.Encoder encoder = Base64.getMimeEncoder();
203249

204250
final String keyHeader = "-----BEGIN PRIVATE KEY-----\n";
205251
final String keyFooter = "\n-----END PRIVATE KEY-----";
206252
final byte[] keyBytes = key.getEncoded();
207253
final String keyContents = new String(encoder.encode(keyBytes));
208-
final String keyPem = keyHeader + keyContents + keyFooter;
209-
try (final PrintWriter out = new PrintWriter(filename)) {
210-
out.println(keyPem);
211-
}
254+
return keyHeader + keyContents + keyFooter;
212255
}
213256

214257
/**
@@ -228,13 +271,25 @@ public static String generatePKCS10(KeyPair kp, String CN, String OU, String O,
228271
ContentSigner signGen = new JcaContentSignerBuilder("SHA256withRSA").build(kp.getPrivate());
229272
PKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(subject, kp.getPublic());
230273
PKCS10CertificationRequest csr = builder.build(signGen);
231-
ByteArrayOutputStream output = new ByteArrayOutputStream();
232-
Writer osWriter = new OutputStreamWriter(output);
233-
JcaPEMWriter pem = new JcaPEMWriter(osWriter);
234-
pem.writeObject(csr);
235-
pem.close();
236-
return new String(output.toByteArray());
274+
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
275+
try (Writer osWriter = new OutputStreamWriter(output)) {
276+
try (JcaPEMWriter pem = new JcaPEMWriter(osWriter)) {
277+
pem.writeObject(csr);
278+
}
279+
}
280+
return new String(output.toByteArray());
281+
}
237282
}
238283

284+
public static KeyStore emptyStore() throws IOException {
285+
try {
286+
KeyStore ks = KeyStore.getInstance("JKS");
239287

288+
// Loading creates the store, can't do anything with it until it's loaded
289+
ks.load(null, PASSWORD.toCharArray());
290+
return ks;
291+
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) {
292+
throw new IOException("Cannot create empty keystore.", e);
293+
}
294+
}
240295
}

0 commit comments

Comments
 (0)