de.carne.certmgr.store.provider.bouncycastle.BouncyCastleStoreProvider.java Source code

Java tutorial

Introduction

Here is the source code for de.carne.certmgr.store.provider.bouncycastle.BouncyCastleStoreProvider.java

Source

/*
 * Copyright (c) 2014-2016 Holger de Carne and contributors, All Rights Reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package de.carne.certmgr.store.provider.bouncycastle;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Properties;

import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.DERBMPString;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.CRLNumber;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.cert.X509CRLHolder;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CRLConverter;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509v2CRLBuilder;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.crypto.engines.DESedeEngine;
import org.bouncycastle.crypto.engines.RC2Engine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.EncryptionException;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.OutputEncryptor;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.bouncycastle.pkcs.PKCS12PfxPdu;
import org.bouncycastle.pkcs.PKCS12PfxPduBuilder;
import org.bouncycastle.pkcs.PKCS12SafeBag;
import org.bouncycastle.pkcs.PKCS12SafeBagBuilder;
import org.bouncycastle.pkcs.PKCSException;
import org.bouncycastle.pkcs.bc.BcPKCS12MacCalculatorBuilder;
import org.bouncycastle.pkcs.bc.BcPKCS12PBEOutputEncryptorBuilder;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import org.bouncycastle.pkcs.jcajce.JcaPKCS12SafeBagBuilder;

import de.carne.certmgr.store.PKCS10Object;
import de.carne.certmgr.store.PasswordCallback;
import de.carne.certmgr.store.PasswordRequiredException;
import de.carne.certmgr.store.StoreProviderException;
import de.carne.certmgr.store.provider.StoreProvider;
import de.carne.certmgr.store.x509.CertificateValidity;
import de.carne.certmgr.store.x509.EncodedX509Extension;
import de.carne.certmgr.store.x509.RevokeReason;
import de.carne.certmgr.store.x509.X509CRLParams;
import de.carne.certmgr.store.x509.X509CertificateParams;
import de.carne.certmgr.store.x509.X509Extension;
import de.carne.util.logging.Log;

/**
 * BouncyCastle Provider.
 */
public class BouncyCastleStoreProvider extends StoreProvider {

    private static final Log LOG = new Log(BouncyCastleStoreProvider.class);

    private static final Charset PEM_CHARSET = Charset.forName("US-ASCII");

    private static final String PEM_ENCRYPTOR_ALGORTIHM = "AES-128-CBC";

    private static final Provider PROVIDER = new BouncyCastleProvider();

    private static final String PROPERTIES_RESOURCE = "BouncyCastle.properties";

    private static final Properties PROPERTIES = new Properties();

    static {
        LOG.debug(null, "Adding BouncyCastle security provider...");
        Security.addProvider(PROVIDER);
        try (InputStream propertyStream = BouncyCastleStoreProvider.class
                .getResourceAsStream(PROPERTIES_RESOURCE)) {
            PROPERTIES.load(propertyStream);
        } catch (IOException e) {
            LOG.error(e, null, e.getLocalizedMessage());
        }
    }

    /*
     * (non-Javadoc)
     * @see de.carne.certmgr.store.provider.StoreProvider#getProperties()
     */
    @Override
    public Properties getProperties() {
        return PROPERTIES;
    }

    /*
     * (non-Javadoc)
     * @see de.carne.certmgr.store.provider.StoreProvider#getInfo()
     */
    @Override
    public String getInfo() {
        return PROVIDER.getInfo();
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#getDefaultSecurityProvider(
     * )
     */
    @Override
    protected String getDefaultSecurityProvider() {
        return BouncyCastleProvider.PROVIDER_NAME;
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#generateAndSignCRT(java.
     * security.KeyPair, de.carne.certmgr.store.x509.X509CertificateParams,
     * de.carne.certmgr.store.x509.CertificateValidity, java.security.KeyPair,
     * java.security.cert.X509Certificate, java.math.BigInteger)
     */
    @Override
    public X509Certificate generateAndSignCRT(KeyPair key, X509CertificateParams certificateParams,
            CertificateValidity certificateValidity, KeyPair issuerKey, X509Certificate issuerCRT,
            BigInteger serial) throws IOException, GeneralSecurityException {
        X500Principal issuerSubjectDN = (issuerCRT != null ? issuerCRT.getSubjectX500Principal()
                : certificateParams.getSubjectDN());
        Date validFrom = Date
                .from(certificateValidity.getValidFrom().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
        Date validTo = Date
                .from(certificateValidity.getValidTo().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
        X500Principal subjectDN = certificateParams.getSubjectDN();
        X509v3CertificateBuilder crtBuilder = new JcaX509v3CertificateBuilder(issuerSubjectDN, serial, validFrom,
                validTo, subjectDN, key.getPublic());

        addKeyIdentifierExtensions(crtBuilder, key.getPublic(),
                (issuerKey != null ? issuerKey.getPublic() : key.getPublic()));
        addCustomExtensions(crtBuilder, certificateParams);

        LOG.notice(I18N.BUNDLE, I18N.STR_GENERATE_CRT, subjectDN);

        ContentSigner crtSigner;

        try {
            crtSigner = new JcaContentSignerBuilder(certificateParams.getSigAlg())
                    .build((issuerKey != null ? issuerKey.getPrivate() : key.getPrivate()));
        } catch (OperatorCreationException e) {
            throw new StoreProviderException(e.getMessage(), e);
        }
        return new JcaX509CertificateConverter().getCertificate(crtBuilder.build(crtSigner));
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#generateAndSignCRT(java.
     * security.cert.X509Certificate,
     * de.carne.certmgr.store.x509.X509CertificateParams,
     * de.carne.certmgr.store.x509.CertificateValidity, java.security.KeyPair)
     */
    @Override
    public X509Certificate generateAndSignCRT(X509Certificate crt, X509CertificateParams certificateParams,
            CertificateValidity certificateValidity, KeyPair issuerKey)
            throws IOException, GeneralSecurityException {
        X500Principal issuerSubjectDN = crt.getIssuerX500Principal();
        BigInteger serial = crt.getSerialNumber();
        Date validFrom = Date
                .from(certificateValidity.getValidFrom().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
        Date validTo = Date
                .from(certificateValidity.getValidTo().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
        X500Principal subjectDN = crt.getSubjectX500Principal();
        X509v3CertificateBuilder crtBuilder = new JcaX509v3CertificateBuilder(issuerSubjectDN, serial, validFrom,
                validTo, subjectDN, crt.getPublicKey());

        addKeyIdentifierExtensions(crtBuilder, crt.getPublicKey(), issuerKey.getPublic());
        addCustomExtensions(crtBuilder, certificateParams);

        LOG.notice(I18N.BUNDLE, I18N.STR_GENERATE_CRT, subjectDN);

        ContentSigner crtSigner;

        try {
            crtSigner = new JcaContentSignerBuilder(certificateParams.getSigAlg()).build(issuerKey.getPrivate());
        } catch (OperatorCreationException e) {
            throw new StoreProviderException(e.getMessage(), e);
        }
        return new JcaX509CertificateConverter().getCertificate(crtBuilder.build(crtSigner));
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#generateAndSignCSR(java.
     * security.KeyPair, de.carne.certmgr.store.x509.X509CertificateParams)
     */
    @Override
    public PKCS10Object generateAndSignCSR(KeyPair key, X509CertificateParams certificateParams)
            throws IOException, GeneralSecurityException {
        X500Principal subjectDN = certificateParams.getSubjectDN();
        PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(subjectDN,
                key.getPublic());

        addCustomExtensions(csrBuilder, certificateParams);

        LOG.notice(I18N.BUNDLE, I18N.STR_GENERATE_CSR, subjectDN);

        ContentSigner csrSigner;

        try {
            csrSigner = new JcaContentSignerBuilder(certificateParams.getSigAlg()).build(key.getPrivate());
        } catch (OperatorCreationException e) {
            throw new StoreProviderException(e.getMessage(), e);
        }
        return new BouncyCastlePKCS10Object(csrBuilder.build(csrSigner));
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#generateAndSignCSR(de.carne
     * .certmgr.store.PKCS10Object, java.security.KeyPair,
     * de.carne.certmgr.store.x509.X509CertificateParams)
     */
    @Override
    public PKCS10Object generateAndSignCSR(PKCS10Object csr, KeyPair key, X509CertificateParams certificateParams)
            throws IOException, GeneralSecurityException {
        X500Principal subjectDN = csr.getSubjectX500Principal();
        PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(subjectDN,
                key.getPublic());

        addCustomExtensions(csrBuilder, certificateParams);

        LOG.notice(I18N.BUNDLE, I18N.STR_GENERATE_CSR, subjectDN);

        ContentSigner csrSigner;

        try {
            csrSigner = new JcaContentSignerBuilder(certificateParams.getSigAlg()).build(key.getPrivate());
        } catch (OperatorCreationException e) {
            throw new StoreProviderException(e.getMessage(), e);
        }
        return new BouncyCastlePKCS10Object(csrBuilder.build(csrSigner));
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#generateAndSignCRL(java.
     * security.cert.X509CRL, de.carne.certmgr.store.x509.X509CRLParams,
     * java.util.Map, java.security.KeyPair, java.security.cert.X509Certificate)
     */
    @Override
    public X509CRL generateAndSignCRL(X509CRL currentCRL, X509CRLParams crlParams,
            Map<BigInteger, RevokeReason> revokeSerials, KeyPair issuerKey, X509Certificate issuerCRT)
            throws IOException, GeneralSecurityException {
        Date lastUpdate = Date
                .from(crlParams.getLastUpdate().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
        JcaX509v2CRLBuilder crlBuilder = new JcaX509v2CRLBuilder(issuerCRT.getSubjectX500Principal(), lastUpdate);
        LocalDate nextUpdateParam = crlParams.getNextUpdate();

        if (nextUpdateParam != null) {
            crlBuilder.setNextUpdate(
                    Date.from(nextUpdateParam.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()));
        }

        CRLNumber crlNumber;

        if (currentCRL != null) {
            X509CRLHolder crlHolder = new X509CRLHolder(currentCRL.getEncoded());
            ASN1Integer currentSerial = (ASN1Integer) crlHolder.getExtension(Extension.cRLNumber).getParsedValue();

            crlNumber = new CRLNumber(currentSerial.getValue().add(BigInteger.ONE));
        } else {
            crlNumber = new CRLNumber(BigInteger.ONE);
        }
        for (Map.Entry<BigInteger, RevokeReason> revokeListEntry : revokeSerials.entrySet()) {
            crlBuilder.addCRLEntry(revokeListEntry.getKey(), lastUpdate, revokeListEntry.getValue().value());
        }

        JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils();

        crlBuilder.addExtension(Extension.authorityKeyIdentifier, false,
                extensionUtils.createAuthorityKeyIdentifier(issuerCRT.getPublicKey()));
        crlBuilder.addExtension(Extension.cRLNumber, false, crlNumber);

        ContentSigner crlSigner;

        try {
            crlSigner = new JcaContentSignerBuilder(crlParams.getSigAlg()).build(issuerKey.getPrivate());
        } catch (OperatorCreationException e) {
            throw new StoreProviderException(e.getMessage(), e);
        }
        return new JcaX509CRLConverter().getCRL(crlBuilder.build(crlSigner));
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#writeKey(java.security.
     * KeyPair, java.nio.file.Path, de.carne.certmgr.store.PasswordCallback,
     * java.lang.String)
     */
    @Override
    public void writeKey(KeyPair key, Path keyFile, PasswordCallback password, String resource)
            throws PasswordRequiredException, IOException {
        writePEMObject(keyFile, key, password, resource);
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#readKey(java.nio.file.Path,
     * de.carne.certmgr.store.PasswordCallback, java.lang.String)
     */
    @Override
    public KeyPair readKey(Path keyFile, PasswordCallback password, String resource)
            throws PasswordRequiredException, IOException {
        return keyFromPEMObject(readPEMObject(keyFile), password, resource);
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#writeCRT(java.security.cert
     * .X509Certificate, java.nio.file.Path)
     */
    @Override
    public void writeCRT(X509Certificate crt, Path crtFile) throws IOException {
        writePEMObject(crtFile, crt);
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#readCRT(java.nio.file.Path)
     */
    @Override
    public X509Certificate readCRT(Path crtFile) throws IOException {
        return crtFromPEMObject(readPEMObject(crtFile));
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#writeCSR(de.carne.certmgr.
     * store.PKCS10Object, java.nio.file.Path)
     */
    @Override
    public void writeCSR(PKCS10Object csr, Path csrFile) throws IOException {
        writePEMObject(csrFile, csr.getObject());
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#readCSR(java.nio.file.Path)
     */
    @Override
    public PKCS10Object readCSR(Path csrFile) throws IOException {
        return csrFromPEMObject(readPEMObject(csrFile));
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#writeCRL(java.security.cert
     * .X509CRL, java.nio.file.Path)
     */
    @Override
    public void writeCRL(X509CRL crl, Path crlFile) throws IOException {
        writePEMObject(crlFile, crl);
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#readCRL(java.nio.file.Path)
     */
    @Override
    public X509CRL readCRL(Path crlFile) throws IOException {
        return crlFromPEMObject(readPEMObject(crlFile));
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#decodeExtension(java.lang.
     * String, boolean, byte[])
     */
    @Override
    public EncodedX509Extension decodeExtension(String oid, boolean critical, byte[] encoded) throws IOException {
        ASN1Primitive decoded = JcaX509ExtensionUtils.parseExtensionValue(encoded);

        return EncodedX509Extension.decode(oid, critical, new BouncyCastleASN1Decoder(decoded));
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#tryDecodePEM(java.lang.
     * String, de.carne.certmgr.store.PasswordCallback, java.lang.String)
     */
    @Override
    public Collection<Object> tryDecodePEM(String pemData, PasswordCallback password, String resource)
            throws IOException {
        ArrayList<Object> decoded = null;

        try (StringReader reader = new StringReader(pemData); PEMParser pemParser = new PEMParser(reader)) {
            Object pemObject;

            try {
                pemObject = pemParser.readObject();
                decoded = new ArrayList<>();
            } catch (IOException e) {
                pemObject = null;
            }
            while (pemObject != null) {

                assert decoded != null;

                if (pemObject instanceof PEMKeyPair || pemObject instanceof PEMEncryptedKeyPair) {
                    try {
                        decoded.add(keyFromPEMObject(pemObject, password, resource));
                    } catch (PasswordRequiredException e) {
                        LOG.info(null, "Skipping key object from ''{0}'' due to missing/invalid password",
                                resource);
                    }
                } else if (pemObject instanceof X509CertificateHolder) {
                    decoded.add(crtFromPEMObject(pemObject));
                } else if (pemObject instanceof PKCS10CertificationRequest) {
                    decoded.add(csrFromPEMObject(pemObject));
                } else if (pemObject instanceof X509CRLHolder) {
                    decoded.add(crlFromPEMObject(pemObject));
                } else {
                    LOG.info(null, "Skipping unknown object type ''{1}'' from ''{0}''", resource,
                            pemObject.getClass());
                }
                pemObject = pemParser.readObject();
            }
        }
        return decoded;
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#tryDecodePKCS12(byte[],
     * de.carne.certmgr.store.PasswordCallback, java.lang.String)
     */
    @Override
    public Collection<Object> tryDecodePKCS12(byte[] pkcs12Data, PasswordCallback password, String resource)
            throws IOException {
        Collection<Object> decoded;

        try {
            PKCS12Decoder pkcs12Decoder = new PKCS12Decoder(new PKCS12PfxPdu(pkcs12Data), password, resource);

            decoded = pkcs12Decoder.decode();
        } catch (IOException e) {
            LOG.info(e, null, "Unable to decode PKCS#12 data from ''{0}''", resource);
            decoded = null;
        }
        return decoded;
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#encodePEM(java.security.
     * cert.X509Certificate[], java.security.KeyPair,
     * de.carne.certmgr.store.PKCS10Object, java.security.cert.X509CRL,
     * de.carne.certmgr.store.PasswordCallback, java.lang.String)
     */
    @Override
    public String encodePEM(X509Certificate[] crtChain, KeyPair key, PKCS10Object csr, X509CRL crl,
            PasswordCallback password, String resource) throws IOException, PasswordRequiredException {
        String encoded;

        try (StringWriter stringWriter = new StringWriter();
                JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) {
            if (key != null) {
                if (password != null) {
                    String passwordInput = password.queryPassword(resource);

                    if (passwordInput == null) {
                        throw new PasswordRequiredException("Password input cancelled while writing key file");
                    }

                    JcePEMEncryptorBuilder encryptorBuilder = new JcePEMEncryptorBuilder(PEM_ENCRYPTOR_ALGORTIHM);

                    pemWriter.writeObject(key, encryptorBuilder.build(passwordInput.toCharArray()));
                } else {
                    pemWriter.writeObject(key);
                }
            }
            if (csr != null) {
                pemWriter.writeObject(csr.getObject());
            }
            if (crl != null) {
                pemWriter.writeObject(crl);
            }
            if (crtChain != null) {
                for (X509Certificate crt : crtChain) {
                    pemWriter.writeObject(crt);
                }
            }
            pemWriter.flush();
            encoded = stringWriter.toString();
        }
        return encoded;
    }

    /*
     * (non-Javadoc)
     * @see
     * de.carne.certmgr.store.provider.StoreProvider#encodePKCS12(java.security.
     * cert.X509Certificate[], java.security.KeyPair,
     * de.carne.certmgr.store.PKCS10Object, java.security.cert.X509CRL,
     * de.carne.certmgr.store.PasswordCallback, java.lang.String)
     */
    @Override
    public byte[] encodePKCS12(X509Certificate[] crtChain, KeyPair key, PKCS10Object csr, X509CRL crl,
            PasswordCallback password, String resource) throws IOException, PasswordRequiredException {
        String passwordInput = (password != null ? password.queryPassword(resource) : null);

        if (password != null && passwordInput == null) {
            throw new PasswordRequiredException("Password input cancelled while writing PKCS#12 file");
        }

        PKCS12SafeBagBuilder[] crtBagBuilders = new PKCS12SafeBagBuilder[crtChain != null ? crtChain.length : 0];
        DERBMPString crt0FriendlyName = null;
        SubjectKeyIdentifier subjectKeyIdentifier = null;

        if (crtChain != null) {
            int crtIndex = 0;

            for (X509Certificate crt : crtChain) {
                PKCS12SafeBagBuilder crtBagBuilder = crtBagBuilders[crtIndex] = new JcaPKCS12SafeBagBuilder(crt);
                DERBMPString crtFriendlyName = new DERBMPString(crt.getSubjectX500Principal().toString());

                crtBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, crtFriendlyName);
                if (crtIndex == 0) {
                    crt0FriendlyName = crtFriendlyName;
                    try {
                        JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils();

                        subjectKeyIdentifier = extensionUtils.createSubjectKeyIdentifier(crt.getPublicKey());
                    } catch (NoSuchAlgorithmException e) {
                        throw new StoreProviderException(e);
                    }
                }
                crtIndex++;
            }
        }

        PKCS12SafeBagBuilder keyBagBuilder = null;

        if (key != null) {
            if (passwordInput != null) {
                BcPKCS12PBEOutputEncryptorBuilder keyBagEncryptorBuilder = new BcPKCS12PBEOutputEncryptorBuilder(
                        PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC,
                        new CBCBlockCipher(new DESedeEngine()));
                OutputEncryptor keyBagEncrypter = keyBagEncryptorBuilder.build(passwordInput.toCharArray());

                keyBagBuilder = new JcaPKCS12SafeBagBuilder(key.getPrivate(), keyBagEncrypter);
            } else {
                keyBagBuilder = new JcaPKCS12SafeBagBuilder(key.getPrivate());
            }
            if (crtBagBuilders.length > 0) {
                crtBagBuilders[0].addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, subjectKeyIdentifier);
                keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, subjectKeyIdentifier);
                keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, crt0FriendlyName);
            }
        }

        PKCS12SafeBag[] crtBags = new PKCS12SafeBag[crtBagBuilders.length];
        int crtBagIndex = 0;

        for (PKCS12SafeBagBuilder crtBagBuilder : crtBagBuilders) {
            crtBags[crtBagIndex] = crtBagBuilder.build();
            crtBagIndex++;
        }

        PKCS12PfxPduBuilder pkcs12Builder = new PKCS12PfxPduBuilder();

        if (passwordInput != null) {
            BcPKCS12PBEOutputEncryptorBuilder crtBagEncryptorBuilder = new BcPKCS12PBEOutputEncryptorBuilder(
                    PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC, new CBCBlockCipher(new RC2Engine()));
            OutputEncryptor crtBagEncryptor = crtBagEncryptorBuilder.build(passwordInput.toCharArray());

            pkcs12Builder.addEncryptedData(crtBagEncryptor, crtBags);
        } else {
            for (PKCS12SafeBag crtBag : crtBags) {
                pkcs12Builder.addData(crtBag);
            }
        }
        if (keyBagBuilder != null) {
            pkcs12Builder.addData(keyBagBuilder.build());
        }

        PKCS12PfxPdu pkcs12;

        try {
            if (passwordInput != null) {
                pkcs12 = pkcs12Builder.build(new BcPKCS12MacCalculatorBuilder(), passwordInput.toCharArray());
            } else {
                pkcs12 = pkcs12Builder.build(null, null);
            }
        } catch (PKCSException e) {
            throw new StoreProviderException(e);
        }
        return pkcs12.getEncoded();
    }

    private void addKeyIdentifierExtensions(X509v3CertificateBuilder crtBuilder, PublicKey publicKey,
            PublicKey issuerPublicKey) throws IOException, GeneralSecurityException {
        JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils();

        crtBuilder.addExtension(Extension.subjectKeyIdentifier, false,
                extensionUtils.createSubjectKeyIdentifier(publicKey));
        if (!publicKey.equals(issuerPublicKey)) {
            crtBuilder.addExtension(Extension.authorityKeyIdentifier, false,
                    extensionUtils.createAuthorityKeyIdentifier(issuerPublicKey));
        }
    }

    private void addCustomExtensions(X509v3CertificateBuilder crtBuilder, X509CertificateParams certificateParams)
            throws IOException {
        for (X509Extension extension : certificateParams.getExtensions()) {
            ASN1ObjectIdentifier extensionOID = new ASN1ObjectIdentifier(extension.getOID());

            crtBuilder.addExtension(extensionOID, extension.isCritical(), new BouncyCastleASN1Encoder(extension));
        }
    }

    private void addCustomExtensions(PKCS10CertificationRequestBuilder csrBuilder,
            X509CertificateParams certificateParams) throws IOException {
        ExtensionsGenerator extensionGenerator = new ExtensionsGenerator();

        for (X509Extension extension : certificateParams.getExtensions()) {
            ASN1ObjectIdentifier extensionOID = new ASN1ObjectIdentifier(extension.getOID());

            extensionGenerator.addExtension(extensionOID, extension.isCritical(),
                    new BouncyCastleASN1Encoder(extension));
        }
        csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensionGenerator.generate());
    }

    private void writePEMObject(Path pemFile, Object object) throws IOException {
        writePEMObject(pemFile, object, null, null);
    }

    private void writePEMObject(Path pemFile, Object object, PasswordCallback password, String resource)
            throws PasswordRequiredException, IOException {
        String pemData;

        try (StringWriter stringWriter = new StringWriter();
                JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) {
            if (password != null) {
                String passwordInput = password.queryPassword(resource);

                if (passwordInput == null) {
                    throw new PasswordRequiredException("Password input cancelled while writing key file");
                }

                JcePEMEncryptorBuilder encryptorBuilder = new JcePEMEncryptorBuilder(PEM_ENCRYPTOR_ALGORTIHM);

                pemWriter.writeObject(object, encryptorBuilder.build(passwordInput.toCharArray()));
            } else {
                pemWriter.writeObject(object);
            }
            pemWriter.flush();
            pemData = stringWriter.toString();
        }
        try (Writer fileWriter = Files.newBufferedWriter(pemFile, PEM_CHARSET, StandardOpenOption.WRITE,
                StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
            fileWriter.write(pemData);
        }
    }

    private Object readPEMObject(Path pemFile) throws IOException {
        Object object;

        try (Reader fileReader = Files.newBufferedReader(pemFile, PEM_CHARSET);
                PEMParser parser = new PEMParser(fileReader)) {
            object = parser.readObject();
        }
        return object;
    }

    private KeyPair keyFromPEMObject(Object pemObject, PasswordCallback password, String resource)
            throws IOException {
        PEMKeyPair keyPair = null;

        if (pemObject instanceof PEMEncryptedKeyPair) {
            PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) pemObject;
            JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder();
            String passwordInput = (password != null ? password.queryPassword(resource) : null);
            Exception invalidPasswordException = null;

            while (keyPair == null) {
                if (passwordInput == null) {
                    throw new PasswordRequiredException("Password required for PEM object: '" + resource + "'",
                            invalidPasswordException);
                }

                assert password != null;

                PEMDecryptorProvider decryptorProvider = decryptorBuilder.build(passwordInput.toCharArray());

                try {
                    keyPair = encryptedKeyPair.decryptKeyPair(decryptorProvider);
                } catch (EncryptionException e) {
                    invalidPasswordException = e;
                    passwordInput = password.requeryPassword(resource, e);
                }
            }
        } else {
            keyPair = (PEMKeyPair) pemObject;
        }

        JcaPEMKeyConverter keyConverter = new JcaPEMKeyConverter();

        return keyConverter.getKeyPair(keyPair);
    }

    private X509Certificate crtFromPEMObject(Object pemObject) throws IOException {
        X509Certificate crt;

        try {
            X509CertificateHolder crtHolder = (X509CertificateHolder) pemObject;
            JcaX509CertificateConverter converter = new JcaX509CertificateConverter();

            crt = converter.getCertificate(crtHolder);
        } catch (Exception e) {
            throw new IOException(e.getLocalizedMessage(), e);
        }
        return crt;
    }

    private BouncyCastlePKCS10Object csrFromPEMObject(Object pemObject) throws IOException {
        JcaPKCS10CertificationRequest pkcs10Object;

        if (pemObject instanceof JcaPKCS10CertificationRequest) {
            pkcs10Object = (JcaPKCS10CertificationRequest) pemObject;
        } else {
            pkcs10Object = new JcaPKCS10CertificationRequest((PKCS10CertificationRequest) pemObject);
        }

        BouncyCastlePKCS10Object csr;

        try {
            csr = new BouncyCastlePKCS10Object(pkcs10Object);
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw new IOException(e.getLocalizedMessage(), e);
        }
        return csr;
    }

    private X509CRL crlFromPEMObject(Object pemObject) throws IOException {
        X509CRL crl;

        try {
            X509CRLHolder crlHolder = (X509CRLHolder) pemObject;
            JcaX509CRLConverter converter = new JcaX509CRLConverter();

            crl = converter.getCRL(crlHolder);
        } catch (Exception e) {
            throw new IOException(e.getLocalizedMessage(), e);
        }
        return crl;
    }

}