66import org .bouncycastle .asn1 .x509 .GeneralName ;
77import org .bouncycastle .asn1 .x509 .GeneralNames ;
88import org .bouncycastle .asn1 .x509 .SubjectPublicKeyInfo ;
9+ import org .bouncycastle .cert .CertIOException ;
910import org .bouncycastle .cert .X509CertificateHolder ;
1011import org .bouncycastle .cert .X509v3CertificateBuilder ;
1112import org .bouncycastle .cert .jcajce .JcaX509CertificateConverter ;
2728
2829import javax .security .auth .x500 .X500Principal ;
2930import java .io .ByteArrayOutputStream ;
30- import java .io .FileNotFoundException ;
31- import java .io .FileOutputStream ;
3231import java .io .FileReader ;
3332import java .io .IOException ;
3433import java .io .OutputStreamWriter ;
5150import java .security .cert .X509Certificate ;
5251import java .util .Base64 ;
5352import 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