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