/* * Copyright (c) 2011-2012 ICM Uniwersytet Warszawski All rights reserved. * See LICENCE file for licensing information. * * Parts of this class are derived from the glite.security.util-java module, * copyrighted as follows: * * Copyright (c) Members of the EGEE Collaboration. 2004. See * http://www.eu-egee.org/partners/ for details on the copyright holders. */ package eu.emi.security.authn.x509.impl; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.nio.charset.Charset; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.SecureRandom; import java.security.Security; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import javax.crypto.BadPaddingException; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMDecryptorProvider; import org.bouncycastle.openssl.PEMEncryptedKeyPair; import org.bouncycastle.openssl.PEMEncryptor; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator; import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder; import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder; import org.bouncycastle.operator.InputDecryptorProvider; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.OutputEncryptor; import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; import org.bouncycastle.pkcs.PKCSException; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemObjectGenerator; import org.bouncycastle.util.io.pem.PemWriter; import eu.emi.security.authn.x509.X509Credential; import eu.emi.security.authn.x509.helpers.CachedPEMReader; import eu.emi.security.authn.x509.helpers.CertificateHelpers; import eu.emi.security.authn.x509.helpers.CertificateHelpers.PEMContentsType; import eu.emi.security.authn.x509.helpers.CharArrayPasswordFinder; import eu.emi.security.authn.x509.helpers.FlexiblePEMReader; import eu.emi.security.authn.x509.helpers.KeyStoreHelper; import eu.emi.security.authn.x509.helpers.PKCS8DERReader; import eu.emi.security.authn.x509.helpers.PasswordSupplier; /** * Utility class with methods simplifying typical certificate related operations. * * @author K. Benedyczak * @author J. Hahkala */ public class CertificateUtils { static { configureSecProvider(); } /** * Definition of the encoding that can be used for reading or writing * certificates or keys. */ public static enum Encoding {PEM, DER} public static final String DEFAULT_KEYSTORE_ALIAS = "default"; public static final Charset ASCII = Charset.forName("US-ASCII"); /** * Configures security providers which are used by the library. Can be called * multiple times (subsequent calls won't have any effect). * <p> * This method must be called before any other usage of the code from canl API. */ public static void configureSecProvider() { if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) Security.addProvider(new BouncyCastleProvider()); } /** * Performs a trivial conversion by use of casting of a Certificate array * into X509Certificate array * @param chain to convert * @return converted chain * @throws ClassCastException if at least one entry in the source chain is not * an {@link X509Certificate} */ public static X509Certificate[] convertToX509Chain(Certificate []chain) throws ClassCastException { X509Certificate[] ret = new X509Certificate[chain.length]; for (int i=0; i<chain.length; i++) ret[i] = (X509Certificate) chain[i]; return ret; } /** * Produces a human readable text representation of the provided certificate. * It uses {@link X509Formatter} internally. * @param cert input certificate * @param mode controls how detailed the string representation should be * @return the text representation */ public static String format(X509Certificate cert, FormatMode mode) { X509Formatter formatter = new X509Formatter(mode); return formatter.format(cert); } /** * Produces a human readable text representation of the provided certificate chain. * It uses {@link X509Formatter} internally. * @param certChain input certificates * @param mode controls how detailed the string representation should be * @return the text representation */ public static String format(X509Certificate[] certChain, FormatMode mode) { X509Formatter formatter = new X509Formatter(mode); return formatter.format(certChain); } /** * Loads a single certificate from the provided input stream. The stream is always closed afterwards. * @param is input stream to read encoded certificate from * @param format encoding type * @return loaded certificate * @throws IOException if certificate can not be read or parsed */ public static X509Certificate loadCertificate(InputStream is, Encoding format) throws IOException { InputStream realIS = is; if (format.equals(Encoding.PEM)) { ByteArrayOutputStream buffer = new ByteArrayOutputStream(4096); Reader br = new InputStreamReader(is, ASCII); FlexiblePEMReader pemReader = new FlexiblePEMReader(br); try { PemObject pem = pemReader.readPemObject(); if (pem == null) throw new IOException("PEM data not found in the stream and its end was reached"); PEMContentsType type = CertificateHelpers.getPEMType(pem.getType()); if (!type.equals(PEMContentsType.CERTIFICATE)) throw new IOException("Expected PEM encoded certificate but found: " + type); buffer.write(pem.getContent()); realIS = new ByteArrayInputStream(buffer.toByteArray()); } finally { pemReader.close(); } } Certificate cert = CertificateHelpers.readDERCertificate(realIS); if (!(cert instanceof X509Certificate)) throw new IOException("The DER input contains a certificate which is not a " + "X.509Certificate, it is " + cert.getClass().getName()); return (X509Certificate)cert; } /** * Loads a private key from the provided input stream. The input stream must be encoded * in the PKCS8 format (PEM or DER). Additionally in case of PEM encoding the legacy * OpenSSL format for storing private keys is supported. Such PEM header names * has algorithm {RSA|DSA|EC} placed before the PRIVATE KEY string. * <p> * Currently supported key encryption algorithms are DES and 3 DES. RC2 is unsupported. * <p> * NOTE: currently it is unsupported to load DER private keys which were encoded with openssl * legacy encoding (e.g. with @verbatim openssl rsa -outform der ... @endverbatim). PEM files * in openssl legacy encoding are supported. * @param is input stream to read encoded key from * @param format encoding type (PEM or DER) * @param password key's encryption password (can be null is file is not encrypted) * @return loaded key * @throws IOException if key can not be read or parsed */ public static PrivateKey loadPrivateKey(InputStream is, Encoding format, char[] password) throws IOException { if (format.equals(Encoding.PEM)) { return loadPEMPrivateKey(is, getPF(password)); } else return loadDERPrivateKey(is, password); } /** * Loads a private key from the provided input stream. The input stream must be encoded * in the PEM format. This method is a special purpose version of the * {@link #loadPrivateKey(InputStream, Encoding, char[])}. It allows to provide {@link PasswordSupplier} * instead of the actual password. The {@link PasswordSupplier} implementation will be used only if * the source is encrypted. * <p> * All other limitations and features are as in the {@link #loadPrivateKey(InputStream, Encoding, char[])} * method. * @param is input stream to read encoded key from * @param pf password finder used to discover key's encryption password. * It is used only if the password is actually needed. * @return loaded key * @throws IOException if key can not be read or parsed */ public static PrivateKey loadPEMPrivateKey(InputStream is, PasswordSupplier pf) throws IOException { Reader reader = new InputStreamReader(is, Charset.forName("US-ASCII")); FlexiblePEMReader pemReader = new FlexiblePEMReader(reader); return internalLoadPK(pemReader, "PEM", pf); } private static PrivateKey parsePEMPrivateKey(PemObject pem, PasswordSupplier pf) throws IOException { CachedPEMReader pemReader = new CachedPEMReader(pem); return internalLoadPK(pemReader, "PEM", pf); } private static PrivateKey internalLoadPK(PEMParser pemReader, String type, PasswordSupplier pf) throws IOException { Object ret = null; try { ret = pemReader.readObject(); if (ret == null) throw new IOException("Can not load the " + type + " private key: no input data (empty source?)"); } catch (IOException e) { if (e.getCause() != null && e.getCause() instanceof BadPaddingException) { throw new IOException("Can not load " + type + " private key: the password is " + "incorrect or the " + type + " data is corrupted.", e); } throw new IOException("Can not load the " + type + " private key: " + e); } return convertToPrivateKey(ret, type, pf); } private static PrivateKey convertToPrivateKey(Object pemObject, String type, PasswordSupplier pf) throws IOException { PrivateKeyInfo pki; try { pki = resolvePK(type, pemObject, pf); } catch (OperatorCreationException e) { throw new IOException("Can't initialize decryption infrastructure", e); } catch (PKCSException e) { throw new IOException("Error decrypting private key: the password is " + "incorrect or the " + type + " data is corrupted.", e); } JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); return converter.getPrivateKey(pki); } private static PrivateKeyInfo resolvePK(String type, Object src, PasswordSupplier pf) throws IOException, OperatorCreationException, PKCSException { if (src instanceof PrivateKeyInfo) return (PrivateKeyInfo) src; if (src instanceof PEMKeyPair) return ((PEMKeyPair)src).getPrivateKeyInfo(); if (src instanceof PKCS8EncryptedPrivateKeyInfo) { JceOpenSSLPKCS8DecryptorProviderBuilder provBuilder = new JceOpenSSLPKCS8DecryptorProviderBuilder(); InputDecryptorProvider decProvider = provBuilder.build(pf.getPassword()); return ((PKCS8EncryptedPrivateKeyInfo)src).decryptPrivateKeyInfo(decProvider); } if (src instanceof PEMEncryptedKeyPair) { JcePEMDecryptorProviderBuilder provBuilder = new JcePEMDecryptorProviderBuilder(); PEMDecryptorProvider decProvider = provBuilder.build(pf.getPassword()); PEMKeyPair keyPair = ((PEMEncryptedKeyPair)src).decryptKeyPair(decProvider); return keyPair.getPrivateKeyInfo(); } throw new IOException("The " + type + " input does not contain a private key, " + "it was parsed as " + src.getClass().getName()); } private static PrivateKey loadDERPrivateKey(InputStream is, char[] password) throws IOException { String type = "DER"; Object ret = null; PKCS8DERReader derReader = new PKCS8DERReader(is, password != null); try { ret = derReader.readObject(); derReader.close(); if (ret == null) throw new IOException("Can not load the " + type + " private key: no input data (empty source?)"); } catch (IOException e) { if (e.getCause() != null && e.getCause() instanceof BadPaddingException) { throw new IOException("Can not load " + type + " private key: the password is " + "incorrect or the " + type + " data is corrupted.", e); } throw new IOException("Can not load the " + type + " private key: ", e); } return convertToPrivateKey(ret, type, getPF(password)); } /** * Loads a set of (possibly unrelated to each other) certificates from the provided input stream. * The input stream is always closed afterwards. * * @param is input stream to read encoded certificates from * @param format encoding type * @return loaded certificates array * @throws IOException if certificates can not be read or parsed */ public static X509Certificate[] loadCertificates(InputStream is, Encoding format) throws IOException { InputStream realIS = is; if (format.equals(Encoding.PEM)) { boolean readOne = false; ByteArrayOutputStream buffer = new ByteArrayOutputStream(4096); Reader br = new InputStreamReader(is, ASCII); FlexiblePEMReader pemReader = new FlexiblePEMReader(br); try { do { PemObject pem = pemReader.readPemObject(); if (pem == null && readOne == false) throw new IOException("PEM data not found in the stream and its end was reached"); if (pem == null) break; PEMContentsType type = CertificateHelpers.getPEMType(pem.getType()); if (!type.equals(PEMContentsType.CERTIFICATE)) throw new IOException("Expected PEM encoded certificate but found: " + type); readOne = true; buffer.write(pem.getContent()); } while (true); } finally { pemReader.close(); } realIS = new ByteArrayInputStream(buffer.toByteArray()); } return loadDERCertificates(realIS); } /** * Loads a chain of certificates from the provided input stream. The input stream is always closed afterwards. * @param is input stream to read encoded certificates from * @param format encoding type * @return loaded certificates array * @throws IOException if certificates can not be read or parsed */ public static X509Certificate[] loadCertificateChain(InputStream is, Encoding format) throws IOException { X509Certificate[] unsorted = loadCertificates(is, format); List<X509Certificate> unsortedList = new ArrayList<X509Certificate>(); Collections.addAll(unsortedList, unsorted); return CertificateHelpers.sortChain(unsortedList); } private static X509Certificate[] loadDERCertificates(InputStream is) throws IOException { Collection<? extends Certificate> certs = CertificateHelpers.readDERCertificates(is); Iterator<? extends Certificate> iterator = certs.iterator(); X509Certificate []ret = new X509Certificate[certs.size()]; for (int i=0; i<ret.length; i++) { Certificate c = iterator.next(); if (!(c instanceof X509Certificate)) throw new IOException("The DER input contains a certificate which is not a " + "X.509Certificate, it is " + c.getClass().getName()); ret[i] = (X509Certificate) c; } return ret; } /** * Loads certificates and private keys from the PEM input stream * (usually from file). Order of entries is not relevant. However it is assumed * that the input contains: * <ol> * <li> one private key K, * <li> one certificate C corresponding to the private key K, * <li> zero or more certificates that if present form a * chain of the certificate C. * </ol> * If more then one certificate is found then this method tries to sort them to * form a consistent chain (inability to do so is thrown as an exception) and assumes * that the last certificate in chain is the user's certificate corresponding * to the private key. * * @param is input stream to read from * @param password private key's encryption password or null if key is not encrypted. * @param ksPassword password which is used to encrypt the private key in the keystore. * Can not be null. * @return KeyStore with one private key typed entry, with alias * {@link #DEFAULT_KEYSTORE_ALIAS} of the JKS type. If password is != null then it is also * used to crypt the key in the keystore. If it is null then # * @throws IOException if input can not be read or parsed */ public static KeyStore loadPEMKeystore(InputStream is, char[] password, char[] ksPassword) throws IOException { return loadPEMKeystore(is, getPF(password), ksPassword); } /** * As {@link #loadPEMKeystore(InputStream, char[], char[])} but this version allows for providing input * key's encryption password only when needed. Input stream is always closed afterwards. * * @param is input stream to read from * @param pf implementation will be used to get the password needed to decrypt the private key * from the PEM keystore. Won't be used if the key happens to be unencrypted. * @param ksPassword password which is used to encrypt the private key in the keystore. * Can not be null. * @return KeyStore with one private key typed entry, with alias * {@link #DEFAULT_KEYSTORE_ALIAS} of the JKS type. If password is != null then it is also * used to crypt the key in the keystore. If it is null then # * @throws IOException if input can not be read or parsed */ public static KeyStore loadPEMKeystore(InputStream is, PasswordSupplier pf, char[] ksPassword) throws IOException { PrivateKey pk = null; List<X509Certificate> certChain = new ArrayList<X509Certificate>(); Reader br = new InputStreamReader(is, ASCII); FlexiblePEMReader pemReader = new FlexiblePEMReader(br); try { do { PemObject pem = pemReader.readPemObject(); if (pem == null) break; PEMContentsType type = CertificateHelpers.getPEMType(pem.getType()); if (type.equals(PEMContentsType.PRIVATE_KEY) || type.equals(PEMContentsType.LEGACY_OPENSSL_PRIVATE_KEY)) { if (pk != null) throw new IOException("Multiple private keys were found"); pk = parsePEMPrivateKey(pem, pf); } else if (type.equals(PEMContentsType.CERTIFICATE)) { X509Certificate[] certs = loadDERCertificates( new ByteArrayInputStream(pem.getContent())); for (X509Certificate cert: certs) certChain.add(cert); } else { throw new IOException("Unsupported PEM object found in the input: " + type); } } while (true); } finally { pemReader.close(); } if (pk == null) { throw new IOException("Private key was not found in the PEM keystore (" + certChain.size() + " certificate(s) was (were) found)."); } Certificate []chain = CertificateHelpers.sortChain(certChain); KeyStore ks; try { ks = KeyStoreHelper.getInstanceForCredential("JKS"); ks.load(null, null); ks.setKeyEntry(DEFAULT_KEYSTORE_ALIAS, pk, ksPassword, chain); } catch (KeyStoreException e) { throw new IOException("Can't setup the JKS keystore", e); } catch (NoSuchAlgorithmException e) { throw new IOException("Can't setup the JKS keystore", e); } catch (CertificateException e) { throw new IOException("Can't setup the JKS keystore", e); } return ks; } /** * Saves the provided certificate to the output file, using the requested encoding. * <b> WARNING </b> The output stream IS NOT closed afterwards. This is on purpose, * so it is possible to write additional output. * @param os where to write the encoded certificate to * @param cert certificate to save * @param format format to use * @throws IOException if the data can not be written */ public static void saveCertificate(OutputStream os, X509Certificate cert, Encoding format) throws IOException { if (format.equals(Encoding.PEM)) { @SuppressWarnings("resource") JcaPEMWriter writer = new JcaPEMWriter(new OutputStreamWriter(os, ASCII)); writer.writeObject(cert); writer.flush(); } else { try { os.write(cert.getEncoded()); } catch (CertificateEncodingException e) { throw new IOException("Can't encode the " + "certificate into ASN.1 DER format", e); } os.flush(); } } /** * As {@link #savePrivateKey(OutputStream, PrivateKey, Encoding, String, char[], boolean)} with * the last argument equal to false * * @param os where to write the encoded key to * @param pk key to save * @param format format to use * @param encryptionAlg encryption algorithm to be used. * See {@link #savePrivateKey(OutputStream, PrivateKey, Encoding, String, char[], boolean)} documentation * for details about allowed values. * @param encryptionPassword encryption password to be used. * @throws IOException if the data can not be written * @throws IllegalArgumentException if encryptionAlg is unsupported */ public static void savePrivateKey(OutputStream os, PrivateKey pk, Encoding format, String encryptionAlg, char[] encryptionPassword) throws IOException, IllegalArgumentException { savePrivateKey(os, pk, format, encryptionAlg, encryptionPassword, false); } /** * Saves the provided private key to the output file, using the requested encoding. * Allows for using PKCS #8 or the legacy openssl PKCS #1 encoding. * <b> WARNING </b> The output stream IS NOT closed afterwards. This is on purpose, * so it is possible to write additional output. * * @param os where to write the encoded key to * @param pk key to save * @param format format to use * @param encryptionAlg encryption algorithm to be used. * Use null if output must not be encrypted. * For PKCS8 output see {@link JceOpenSSLPKCS8EncryptorBuilder} constants for available names. * For the legacy openssl format, one can use the * algorithm names composed from 3 parts glued with hyphen. The first part determines algorithm, * one of AES, DES, BF and RC2. The second part determines key bits and is used for AES and * optionally for RC2. For AES it is possible to use values * 128, 192 and 256. For RC2 64, 40 can be used or nothing - then value 128 is used. * The last part determines the block mode: CFB, ECB, OFB, EDE and CBC. Additionally EDE3 * can be used in combination with DES to use DES3 with EDE. Examples: * AES-192-ECB or DES-EDE3. * @param encryptionPassword encryption password to be used. * @param opensslLegacyFormat if true the key is saved in the legacy openssl format. Otherwise a * PKCS #8 is used. * @throws IOException if the data can not be written * @throws IllegalArgumentException if encryptionAlg is unsupported * @since 1.1.0 */ public static void savePrivateKey(OutputStream os, PrivateKey pk, Encoding format, String encryptionAlg, char[] encryptionPassword, boolean opensslLegacyFormat) throws IOException, IllegalArgumentException { PemObjectGenerator gen; if (encryptionAlg != null) { try { if (!opensslLegacyFormat) { JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder( new ASN1ObjectIdentifier(encryptionAlg)); encryptorBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME); encryptorBuilder.setPasssword(encryptionPassword); OutputEncryptor oe = encryptorBuilder.build(); gen = new JcaPKCS8Generator(pk, oe); } else { JcePEMEncryptorBuilder builder = new JcePEMEncryptorBuilder(encryptionAlg); builder.setProvider(BouncyCastleProvider.PROVIDER_NAME); builder.setSecureRandom(new SecureRandom()); PEMEncryptor encryptor = builder.build(encryptionPassword); gen = new JcaMiscPEMGenerator(pk, encryptor); } } catch (OperatorCreationException e) { throw new IllegalArgumentException("Can't setup encryption modules, " + "likely the parameters (as algorithm) are invalid", e); } } else { if (!opensslLegacyFormat) gen = new JcaPKCS8Generator(pk, null); else { gen = new JcaMiscPEMGenerator(pk); } } if (format.equals(Encoding.PEM)) { @SuppressWarnings("resource") PemWriter writer = new PemWriter(new OutputStreamWriter(os, ASCII)); writer.writeObject(gen); writer.flush(); } else { if (encryptionAlg == null) { os.write(pk.getEncoded()); } else { PemObject pemO = gen.generate(); os.write(pemO.getContent()); } os.flush(); } } /** * Saves the provided certificate chain to the output stream, using the requested * encoding. * <b> WARNING </b> The output stream IS NOT closed afterwards. This is on purpose, * so it is possible to write additional output. * @param os where to write the encoded certificate to * @param chain certificate chain to save * @param format format to use * @throws IOException if the data can not be written */ public static void saveCertificateChain(OutputStream os, X509Certificate[] chain, Encoding format) throws IOException { if (format.equals(Encoding.PEM)) { for (X509Certificate cert: chain) saveCertificate(os, cert, Encoding.PEM); } else { byte [][] der = new byte[chain.length][]; for (int i=0; i<chain.length; i++) { try { der[i] = chain[i].getEncoded(); } catch (CertificateEncodingException e) { throw new IOException("Can't encode the certificate into ASN1 DER format", e); } } for (int i=0; i<der.length; i++) os.write(der[i]); os.flush(); } } /** * See {@link #savePEMKeystore(OutputStream, KeyStore, String, String, char[], char[], boolean)} * with the last argument equal to false. * * @param os where to write the encoded data to * @param ks keystore to read from * @param alias alias of the private key entry in the keystore * @param encryptionAlg encryption algorithm to be used. * See {@link #savePrivateKey(OutputStream, PrivateKey, Encoding, String, char[], boolean)} documentation * for details about allowed values. * @param keyPassword password of the private key in the keystore * @param encryptionPassword encryption password to be used. * @throws IOException if the data can not be written * @throws KeyStoreException if the provided alias does not exist in the keystore * or if it does not correspond to the private key entry. * @throws IllegalArgumentException if encriptionAlg is unsupported or alias is wrong * @throws NoSuchAlgorithmException if algorithm is not known * @throws UnrecoverableKeyException if key can not be recovered */ public static void savePEMKeystore(OutputStream os, KeyStore ks, String alias, String encryptionAlg, char[] keyPassword, char[] encryptionPassword) throws IOException, KeyStoreException, IllegalArgumentException, UnrecoverableKeyException, NoSuchAlgorithmException { savePEMKeystore(os, ks, alias, encryptionAlg, keyPassword, encryptionPassword, false); } /** * See {@link #savePEMKeystore(OutputStream, KeyStore, String, String, char[], char[], boolean)}. * This method allows for using the CANL {@link X509Credential} instead of low level * {@link KeyStore} as argument. * * @param os where to write the encoded data to * @param toSave CANL X509Credential to read from * @param encryptionAlg encryption algorithm to be used. * See {@link #savePrivateKey(OutputStream, PrivateKey, Encoding, String, char[], boolean)} documentation * for details about allowed values. * @param encryptionPassword encryption password to be used. * @param opensslLegacyFormat if true the key is saved in the legacy openssl format. Otherwise a * PKCS #8 is used. * @throws IOException if the data can not be written * @throws KeyStoreException if the provided alias does not exist in the keystore * or if it does not correspond to the private key entry. * @throws IllegalArgumentException if encriptionAlg is unsupported or alias is wrong * @throws NoSuchAlgorithmException if algorithm is not known * @throws UnrecoverableKeyException if key can not be recovered */ public static void savePEMKeystore(OutputStream os, X509Credential toSave, String encryptionAlg, char[] encryptionPassword, boolean opensslLegacyFormat) throws IOException, KeyStoreException, IllegalArgumentException, UnrecoverableKeyException, NoSuchAlgorithmException { savePEMKeystore(os, toSave.getKeyStore(), toSave.getKeyAlias(), encryptionAlg, toSave.getKeyPassword(), encryptionPassword, opensslLegacyFormat); } /** * Saves the chosen private key entry from the provided keystore as a plain * text PEM data. The produced PEM contains the private key first and then all * certificates which are stored in the provided keystore under the given alias. * The order from the keystore is preserved. The output stream is closed afterwards * only if the write operation was successful (there was no exception). * * @param os where to write the encoded data to * @param ks keystore to read from * @param alias alias of the private key entry in the keystore * @param encryptionAlg encryption algorithm to be used. * See {@link #savePrivateKey(OutputStream, PrivateKey, Encoding, String, char[], boolean)} documentation * for details about allowed values. * @param keyPassword password of the private key in the keystore * @param encryptionPassword encryption password to be used. * @param opensslLegacyFormat if true the key is saved in the legacy openssl format. Otherwise a * PKCS #8 is used. * @throws IOException if the data can not be written * @throws KeyStoreException if the provided alias does not exist in the keystore * or if it does not correspond to the private key entry. * @throws IllegalArgumentException if encriptionAlg is unsupported or alias is wrong * @throws NoSuchAlgorithmException if algorithm is not known * @throws UnrecoverableKeyException if key can not be recovered */ public static void savePEMKeystore(OutputStream os, KeyStore ks, String alias, String encryptionAlg, char[] keyPassword, char[] encryptionPassword, boolean opensslLegacyFormat) throws IOException, KeyStoreException, IllegalArgumentException, UnrecoverableKeyException, NoSuchAlgorithmException { Key k = ks.getKey(alias, keyPassword); if (k == null) throw new IllegalArgumentException("The specified alias does not correspond to any key entry"); if (!(k instanceof PrivateKey)) throw new IllegalArgumentException("The alias corresponds to a secret key, not to the private key"); savePrivateKey(os, (PrivateKey)k, Encoding.PEM, encryptionAlg, encryptionPassword, opensslLegacyFormat); X509Certificate[] certs = convertToX509Chain(ks.getCertificateChain(alias)); saveCertificateChain(os, certs, Encoding.PEM); os.close(); } public static PasswordSupplier getPF(char[] password) { return (password == null) ? null : new CharArrayPasswordFinder(password); } }