1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 package org.apache.zookeeper.common;
20 
21 import java.io.ByteArrayOutputStream;
22 import java.io.IOException;
23 import java.io.StringWriter;
24 import java.math.BigInteger;
25 import java.net.InetAddress;
26 import java.net.UnknownHostException;
27 import java.security.GeneralSecurityException;
28 import java.security.KeyPair;
29 import java.security.KeyPairGenerator;
30 import java.security.KeyStore;
31 import java.security.PrivateKey;
32 import java.security.PublicKey;
33 import java.security.SecureRandom;
34 import java.security.cert.Certificate;
35 import java.security.cert.CertificateException;
36 import java.security.cert.X509Certificate;
37 import java.security.spec.ECGenParameterSpec;
38 import java.security.spec.RSAKeyGenParameterSpec;
39 import java.util.Date;
40 import org.bouncycastle.asn1.DERIA5String;
41 import org.bouncycastle.asn1.DEROctetString;
42 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
43 import org.bouncycastle.asn1.x500.X500Name;
44 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
45 import org.bouncycastle.asn1.x509.BasicConstraints;
46 import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
47 import org.bouncycastle.asn1.x509.Extension;
48 import org.bouncycastle.asn1.x509.GeneralName;
49 import org.bouncycastle.asn1.x509.GeneralNames;
50 import org.bouncycastle.asn1.x509.KeyPurposeId;
51 import org.bouncycastle.asn1.x509.KeyUsage;
52 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
53 import org.bouncycastle.cert.X509CertificateHolder;
54 import org.bouncycastle.cert.X509v3CertificateBuilder;
55 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
56 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
57 import org.bouncycastle.crypto.util.PrivateKeyFactory;
58 import org.bouncycastle.jce.provider.BouncyCastleProvider;
59 import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
60 import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
61 import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder;
62 import org.bouncycastle.operator.ContentSigner;
63 import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
64 import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
65 import org.bouncycastle.operator.OperatorCreationException;
66 import org.bouncycastle.operator.OutputEncryptor;
67 import org.bouncycastle.operator.bc.BcContentSignerBuilder;
68 import org.bouncycastle.operator.bc.BcECContentSignerBuilder;
69 import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
72 
73 /**
74  * This class contains helper methods for creating X509 certificates and key pairs, and for serializing them
75  * to JKS, PEM or other keystore type files.
76  */
77 public class X509TestHelpers {
78 
79     private static final Logger LOG = LoggerFactory.getLogger(X509TestHelpers.class);
80 
81     private static final SecureRandom PRNG = new SecureRandom();
82     private static final int DEFAULT_RSA_KEY_SIZE_BITS = 2048;
83     private static final BigInteger DEFAULT_RSA_PUB_EXPONENT = RSAKeyGenParameterSpec.F4; // 65537
84     private static final String DEFAULT_ELLIPTIC_CURVE_NAME = "secp256r1";
85     // Per RFC 5280 section 4.1.2.2, X509 certificates can use up to 20 bytes == 160 bits for serial numbers.
86     private static final int SERIAL_NUMBER_MAX_BITS = 20 * Byte.SIZE;
87 
88     /**
89      * Uses the private key of the given key pair to create a self-signed CA certificate with the public half of the
90      * key pair and the given subject and expiration. The issuer of the new cert will be equal to the subject.
91      * Returns the new certificate.
92      * The returned certificate should be used as the trust store. The private key of the input key pair should be
93      * used to sign certificates that are used by test peers to establish TLS connections to each other.
94      * @param subject the subject of the new certificate being created.
95      * @param keyPair the key pair to use. The public key will be embedded in the new certificate, and the private key
96      *                will be used to self-sign the certificate.
97      * @param expirationMillis expiration of the new certificate, in milliseconds from now.
98      * @return a new self-signed CA certificate.
99      * @throws IOException
100      * @throws OperatorCreationException
101      * @throws GeneralSecurityException
102      */
newSelfSignedCACert( X500Name subject, KeyPair keyPair, long expirationMillis)103     public static X509Certificate newSelfSignedCACert(
104             X500Name subject, KeyPair keyPair, long expirationMillis) throws IOException, OperatorCreationException, GeneralSecurityException {
105         Date now = new Date();
106         X509v3CertificateBuilder builder = initCertBuilder(subject, // for self-signed certs, issuer == subject
107                                                            now, new Date(now.getTime()
108                                                                                  + expirationMillis), subject, keyPair.getPublic());
109         builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true)); // is a CA
110         builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature
111                                                                             | KeyUsage.keyCertSign
112                                                                             | KeyUsage.cRLSign));
113         return buildAndSignCertificate(keyPair.getPrivate(), builder);
114     }
115 
116     /**
117      * Using the private key of the given CA key pair and the Subject of the given CA cert as the Issuer, issues a
118      * new cert with the given subject and public key. The returned certificate, combined with the private key half
119      * of the <code>certPublicKey</code>, should be used as the key store.
120      * @param caCert the certificate of the CA that's doing the signing.
121      * @param caKeyPair the key pair of the CA. The private key will be used to sign. The public key must match the
122      *                  public key in the <code>caCert</code>.
123      * @param certSubject the subject field of the new cert being issued.
124      * @param certPublicKey the public key of the new cert being issued.
125      * @param expirationMillis the expiration of the cert being issued, in milliseconds from now.
126      * @return a new certificate signed by the CA's private key.
127      * @throws IOException
128      * @throws OperatorCreationException
129      * @throws GeneralSecurityException
130      */
newCert( X509Certificate caCert, KeyPair caKeyPair, X500Name certSubject, PublicKey certPublicKey, long expirationMillis)131     public static X509Certificate newCert(
132             X509Certificate caCert, KeyPair caKeyPair, X500Name certSubject, PublicKey certPublicKey, long expirationMillis) throws IOException, OperatorCreationException, GeneralSecurityException {
133         if (!caKeyPair.getPublic().equals(caCert.getPublicKey())) {
134             throw new IllegalArgumentException("CA private key does not match the public key in the CA cert");
135         }
136         Date now = new Date();
137         X509v3CertificateBuilder builder = initCertBuilder(new X500Name(caCert.getIssuerDN().getName()), now, new Date(
138                 now.getTime()
139                         + expirationMillis), certSubject, certPublicKey);
140         builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); // not a CA
141         builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature
142                                                                             | KeyUsage.keyEncipherment));
143         builder.addExtension(Extension.extendedKeyUsage, true, new ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth}));
144 
145         builder.addExtension(Extension.subjectAlternativeName, false, getLocalhostSubjectAltNames());
146         return buildAndSignCertificate(caKeyPair.getPrivate(), builder);
147     }
148 
149     /**
150      * Returns subject alternative names for "localhost".
151      * @return the subject alternative names for "localhost".
152      */
getLocalhostSubjectAltNames()153     private static GeneralNames getLocalhostSubjectAltNames() throws UnknownHostException {
154         InetAddress[] localAddresses = InetAddress.getAllByName("localhost");
155         GeneralName[] generalNames = new GeneralName[localAddresses.length + 1];
156         for (int i = 0; i < localAddresses.length; i++) {
157             generalNames[i] = new GeneralName(GeneralName.iPAddress, new DEROctetString(localAddresses[i].getAddress()));
158         }
159         generalNames[generalNames.length - 1] = new GeneralName(GeneralName.dNSName, new DERIA5String("localhost"));
160         return new GeneralNames(generalNames);
161     }
162 
163     /**
164      * Helper method for newSelfSignedCACert() and newCert(). Initializes a X509v3CertificateBuilder with
165      * logic that's common to both methods.
166      * @param issuer Issuer field of the new cert.
167      * @param notBefore date before which the new cert is not valid.
168      * @param notAfter date after which the new cert is not valid.
169      * @param subject Subject field of the new cert.
170      * @param subjectPublicKey public key to store in the new cert.
171      * @return a X509v3CertificateBuilder that can be further customized to finish creating the new cert.
172      */
initCertBuilder( X500Name issuer, Date notBefore, Date notAfter, X500Name subject, PublicKey subjectPublicKey)173     private static X509v3CertificateBuilder initCertBuilder(
174             X500Name issuer, Date notBefore, Date notAfter, X500Name subject, PublicKey subjectPublicKey) {
175         return new X509v3CertificateBuilder(issuer, new BigInteger(SERIAL_NUMBER_MAX_BITS, PRNG), notBefore, notAfter, subject, SubjectPublicKeyInfo.getInstance(subjectPublicKey.getEncoded()));
176     }
177 
178     /**
179      * Signs the certificate being built by the given builder using the given private key and returns the certificate.
180      * @param privateKey the private key to sign the certificate with.
181      * @param builder the cert builder that contains the certificate data.
182      * @return the signed certificate.
183      * @throws IOException
184      * @throws OperatorCreationException
185      * @throws CertificateException
186      */
buildAndSignCertificate( PrivateKey privateKey, X509v3CertificateBuilder builder)187     private static X509Certificate buildAndSignCertificate(
188             PrivateKey privateKey, X509v3CertificateBuilder builder) throws IOException, OperatorCreationException, CertificateException {
189         BcContentSignerBuilder signerBuilder;
190         if (privateKey.getAlgorithm().contains("RSA")) { // a little hacky way to detect key type, but it works
191             AlgorithmIdentifier signatureAlgorithm = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSAEncryption");
192             AlgorithmIdentifier digestAlgorithm = new DefaultDigestAlgorithmIdentifierFinder().find(signatureAlgorithm);
193             signerBuilder = new BcRSAContentSignerBuilder(signatureAlgorithm, digestAlgorithm);
194         } else { // if not RSA, assume EC
195             AlgorithmIdentifier signatureAlgorithm = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256withECDSA");
196             AlgorithmIdentifier digestAlgorithm = new DefaultDigestAlgorithmIdentifierFinder().find(signatureAlgorithm);
197             signerBuilder = new BcECContentSignerBuilder(signatureAlgorithm, digestAlgorithm);
198         }
199         AsymmetricKeyParameter privateKeyParam = PrivateKeyFactory.createKey(privateKey.getEncoded());
200         ContentSigner signer = signerBuilder.build(privateKeyParam);
201         return toX509Cert(builder.build(signer));
202     }
203 
204     /**
205      * Generates a new asymmetric key pair of the given type.
206      * @param keyType the type of key pair to generate.
207      * @return the new key pair.
208      * @throws GeneralSecurityException if your java crypto providers are messed up.
209      */
generateKeyPair(X509KeyType keyType)210     public static KeyPair generateKeyPair(X509KeyType keyType) throws GeneralSecurityException {
211         switch (keyType) {
212         case RSA:
213             return generateRSAKeyPair();
214         case EC:
215             return generateECKeyPair();
216         default:
217             throw new IllegalArgumentException("Invalid X509KeyType");
218         }
219     }
220 
221     /**
222      * Generates an RSA key pair with a 2048-bit private key and F4 (65537) as the public exponent.
223      * @return the key pair.
224      */
generateRSAKeyPair()225     public static KeyPair generateRSAKeyPair() throws GeneralSecurityException {
226         KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
227         RSAKeyGenParameterSpec keyGenSpec = new RSAKeyGenParameterSpec(DEFAULT_RSA_KEY_SIZE_BITS, DEFAULT_RSA_PUB_EXPONENT);
228         keyGen.initialize(keyGenSpec, PRNG);
229         return keyGen.generateKeyPair();
230     }
231 
232     /**
233      * Generates an elliptic curve key pair using the "secp256r1" aka "prime256v1" aka "NIST P-256" curve.
234      * @return the key pair.
235      */
generateECKeyPair()236     public static KeyPair generateECKeyPair() throws GeneralSecurityException {
237         KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
238         keyGen.initialize(new ECGenParameterSpec(DEFAULT_ELLIPTIC_CURVE_NAME), PRNG);
239         return keyGen.generateKeyPair();
240     }
241 
242     /**
243      * PEM-encodes the given X509 certificate and private key (compatible with OpenSSL), optionally protecting the
244      * private key with a password. Concatenates them both and returns the result as a single string.
245      * This creates the PEM encoding of a key store.
246      * @param cert the X509 certificate to PEM-encode.
247      * @param privateKey the private key to PEM-encode.
248      * @param keyPassword an optional key password. If empty or null, the private key will not be encrypted.
249      * @return a String containing the PEM encodings of the certificate and private key.
250      * @throws IOException if converting the certificate or private key to PEM format fails.
251      * @throws OperatorCreationException if constructing the encryptor from the given password fails.
252      */
pemEncodeCertAndPrivateKey( X509Certificate cert, PrivateKey privateKey, String keyPassword)253     public static String pemEncodeCertAndPrivateKey(
254             X509Certificate cert, PrivateKey privateKey, String keyPassword) throws IOException, OperatorCreationException {
255         return pemEncodeX509Certificate(cert) + "\n" + pemEncodePrivateKey(privateKey, keyPassword);
256     }
257 
258     /**
259      * PEM-encodes the given private key (compatible with OpenSSL), optionally protecting it with a password, and
260      * returns the result as a String.
261      * @param key the private key.
262      * @param password an optional key password. If empty or null, the private key will not be encrypted.
263      * @return a String containing the PEM encoding of the private key.
264      * @throws IOException if converting the key to PEM format fails.
265      * @throws OperatorCreationException if constructing the encryptor from the given password fails.
266      */
pemEncodePrivateKey( PrivateKey key, String password)267     public static String pemEncodePrivateKey(
268             PrivateKey key, String password) throws IOException, OperatorCreationException {
269         StringWriter stringWriter = new StringWriter();
270         JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter);
271         OutputEncryptor encryptor = null;
272         if (password != null && password.length() > 0) {
273             encryptor = new JceOpenSSLPKCS8EncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC).setProvider(BouncyCastleProvider.PROVIDER_NAME).setRandom(PRNG).setPasssword(password.toCharArray()).build();
274         }
275         pemWriter.writeObject(new JcaPKCS8Generator(key, encryptor));
276         pemWriter.close();
277         return stringWriter.toString();
278     }
279 
280     /**
281      * PEM-encodes the given X509 certificate (compatible with OpenSSL) and returns the result as a String.
282      * @param cert the certificate.
283      * @return a String containing the PEM encoding of the certificate.
284      * @throws IOException if converting the certificate to PEM format fails.
285      */
pemEncodeX509Certificate(X509Certificate cert)286     public static String pemEncodeX509Certificate(X509Certificate cert) throws IOException {
287         StringWriter stringWriter = new StringWriter();
288         JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter);
289         pemWriter.writeObject(cert);
290         pemWriter.close();
291         return stringWriter.toString();
292     }
293 
294     /**
295      * Encodes the given X509Certificate as a JKS TrustStore, optionally protecting the cert with a password (though
296      * it's unclear why one would do this since certificates only contain public information and do not need to be
297      * kept secret). Returns the byte array encoding of the trust store, which may be written to a file and loaded to
298      * instantiate the trust store at a later point or in another process.
299      * @param cert the certificate to serialize.
300      * @param keyPassword an optional password to encrypt the trust store. If empty or null, the cert will not be encrypted.
301      * @return the serialized bytes of the JKS trust store.
302      * @throws IOException
303      * @throws GeneralSecurityException
304      */
certToJavaTrustStoreBytes( X509Certificate cert, String keyPassword)305     public static byte[] certToJavaTrustStoreBytes(
306             X509Certificate cert, String keyPassword) throws IOException, GeneralSecurityException {
307         KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
308         return certToTrustStoreBytes(cert, keyPassword, trustStore);
309     }
310 
311     /**
312      * Encodes the given X509Certificate as a PKCS12 TrustStore, optionally protecting the cert with a password (though
313      * it's unclear why one would do this since certificates only contain public information and do not need to be
314      * kept secret). Returns the byte array encoding of the trust store, which may be written to a file and loaded to
315      * instantiate the trust store at a later point or in another process.
316      * @param cert the certificate to serialize.
317      * @param keyPassword an optional password to encrypt the trust store. If empty or null, the cert will not be encrypted.
318      * @return the serialized bytes of the PKCS12 trust store.
319      * @throws IOException
320      * @throws GeneralSecurityException
321      */
certToPKCS12TrustStoreBytes( X509Certificate cert, String keyPassword)322     public static byte[] certToPKCS12TrustStoreBytes(
323             X509Certificate cert, String keyPassword) throws IOException, GeneralSecurityException {
324         KeyStore trustStore = KeyStore.getInstance("PKCS12");
325         return certToTrustStoreBytes(cert, keyPassword, trustStore);
326     }
327 
328     /**
329      * Encodes the given X509Certificate as a BCFKS TrustStore, optionally protecting the cert with a password (though
330      * it's unclear why one would do this since certificates only contain public information and do not need to be
331      * kept secret). Returns the byte array encoding of the trust store, which may be written to a file and loaded to
332      * instantiate the trust store at a later point or in another process.
333      * @param cert the certificate to serialize.
334      * @param keyPassword an optional password to encrypt the trust store. If empty or null, the cert will not be encrypted.
335      * @return the serialized bytes of the BCFKS trust store.
336      * @throws IOException
337      * @throws GeneralSecurityException
338      */
certToBCFKSTrustStoreBytes( X509Certificate cert, String keyPassword)339     public static byte[] certToBCFKSTrustStoreBytes(
340       X509Certificate cert,
341       String keyPassword) throws IOException, GeneralSecurityException {
342         KeyStore trustStore = KeyStore.getInstance("BCFKS");
343         return certToTrustStoreBytes(cert, keyPassword, trustStore);
344     }
345 
certToTrustStoreBytes(X509Certificate cert, String keyPassword, KeyStore trustStore)346     private static byte[] certToTrustStoreBytes(X509Certificate cert, String keyPassword, KeyStore trustStore) throws IOException, GeneralSecurityException {
347         char[] keyPasswordChars = keyPassword == null ? new char[0] : keyPassword.toCharArray();
348         trustStore.load(null, keyPasswordChars);
349         trustStore.setCertificateEntry(cert.getSubjectDN().toString(), cert);
350         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
351         trustStore.store(outputStream, keyPasswordChars);
352         outputStream.flush();
353         byte[] result = outputStream.toByteArray();
354         outputStream.close();
355         return result;
356     }
357 
358     /**
359      * Encodes the given X509Certificate and private key as a JKS KeyStore, optionally protecting the private key
360      * (and possibly the cert?) with a password. Returns the byte array encoding of the key store, which may be written
361      * to a file and loaded to instantiate the key store at a later point or in another process.
362      * @param cert the X509 certificate to serialize.
363      * @param privateKey the private key to serialize.
364      * @param keyPassword an optional key password. If empty or null, the private key will not be encrypted.
365      * @return the serialized bytes of the JKS key store.
366      * @throws IOException
367      * @throws GeneralSecurityException
368      */
certAndPrivateKeyToJavaKeyStoreBytes( X509Certificate cert, PrivateKey privateKey, String keyPassword)369     public static byte[] certAndPrivateKeyToJavaKeyStoreBytes(
370             X509Certificate cert, PrivateKey privateKey, String keyPassword) throws IOException, GeneralSecurityException {
371         KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
372         return certAndPrivateKeyToBytes(cert, privateKey, keyPassword, keyStore);
373     }
374 
375     /**
376      * Encodes the given X509Certificate and private key as a PKCS12 KeyStore, optionally protecting the private key
377      * (and possibly the cert?) with a password. Returns the byte array encoding of the key store, which may be written
378      * to a file and loaded to instantiate the key store at a later point or in another process.
379      * @param cert the X509 certificate to serialize.
380      * @param privateKey the private key to serialize.
381      * @param keyPassword an optional key password. If empty or null, the private key will not be encrypted.
382      * @return the serialized bytes of the PKCS12 key store.
383      * @throws IOException
384      * @throws GeneralSecurityException
385      */
certAndPrivateKeyToPKCS12Bytes( X509Certificate cert, PrivateKey privateKey, String keyPassword)386     public static byte[] certAndPrivateKeyToPKCS12Bytes(
387             X509Certificate cert, PrivateKey privateKey, String keyPassword) throws IOException, GeneralSecurityException {
388         KeyStore keyStore = KeyStore.getInstance("PKCS12");
389         return certAndPrivateKeyToBytes(cert, privateKey, keyPassword, keyStore);
390     }
391 
392     /**
393      * Encodes the given X509Certificate and private key as a BCFKS KeyStore, optionally protecting the private key
394      * (and possibly the cert?) with a password. Returns the byte array encoding of the key store, which may be written
395      * to a file and loaded to instantiate the key store at a later point or in another process.
396      * @param cert the X509 certificate to serialize.
397      * @param privateKey the private key to serialize.
398      * @param keyPassword an optional key password. If empty or null, the private key will not be encrypted.
399      * @return the serialized bytes of the BCFKS key store.
400      * @throws IOException
401      * @throws GeneralSecurityException
402      */
certAndPrivateKeyToBCFKSBytes( X509Certificate cert, PrivateKey privateKey, String keyPassword)403     public static byte[] certAndPrivateKeyToBCFKSBytes(
404       X509Certificate cert,
405       PrivateKey privateKey,
406       String keyPassword) throws IOException, GeneralSecurityException {
407         KeyStore keyStore = KeyStore.getInstance("BCFKS");
408         return certAndPrivateKeyToBytes(cert, privateKey, keyPassword, keyStore);
409     }
410 
certAndPrivateKeyToBytes( X509Certificate cert, PrivateKey privateKey, String keyPassword, KeyStore keyStore)411     private static byte[] certAndPrivateKeyToBytes(
412             X509Certificate cert, PrivateKey privateKey, String keyPassword, KeyStore keyStore) throws IOException, GeneralSecurityException {
413         char[] keyPasswordChars = keyPassword == null ? new char[0] : keyPassword.toCharArray();
414         keyStore.load(null, keyPasswordChars);
415         keyStore.setKeyEntry("key", privateKey, keyPasswordChars, new Certificate[]{cert});
416         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
417         keyStore.store(outputStream, keyPasswordChars);
418         outputStream.flush();
419         byte[] result = outputStream.toByteArray();
420         outputStream.close();
421         return result;
422     }
423 
424     /**
425      * Convenience method to convert a bouncycastle X509CertificateHolder to a java X509Certificate.
426      * @param certHolder a bouncycastle X509CertificateHolder.
427      * @return a java X509Certificate
428      * @throws CertificateException if the conversion fails.
429      */
toX509Cert(X509CertificateHolder certHolder)430     public static X509Certificate toX509Cert(X509CertificateHolder certHolder) throws CertificateException {
431         return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certHolder);
432     }
433 
434 }
435