netscape.security.pkcs.PKCS12Util.java Source code

Java tutorial

Introduction

Here is the source code for netscape.security.pkcs.PKCS12Util.java

Source

// --- BEGIN COPYRIGHT BLOCK ---
// 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; version 2 of the License.
//
// 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, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// (C) 2016 Red Hat, Inc.
// All rights reserved.
// --- END COPYRIGHT BLOCK ---
package netscape.security.pkcs;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Principal;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang.StringUtils;
import org.mozilla.jss.CryptoManager;
import org.mozilla.jss.asn1.ANY;
import org.mozilla.jss.asn1.ASN1Value;
import org.mozilla.jss.asn1.BMPString;
import org.mozilla.jss.asn1.OBJECT_IDENTIFIER;
import org.mozilla.jss.asn1.OCTET_STRING;
import org.mozilla.jss.asn1.SEQUENCE;
import org.mozilla.jss.asn1.SET;
import org.mozilla.jss.crypto.CryptoStore;
import org.mozilla.jss.crypto.CryptoToken;
import org.mozilla.jss.crypto.EncryptionAlgorithm;
import org.mozilla.jss.crypto.InternalCertificate;
import org.mozilla.jss.crypto.NoSuchItemOnTokenException;
import org.mozilla.jss.crypto.ObjectNotFoundException;
import org.mozilla.jss.crypto.PBEAlgorithm;
import org.mozilla.jss.crypto.PrivateKey;
import org.mozilla.jss.crypto.X509Certificate;
import org.mozilla.jss.pkcs11.PK11Store;
import org.mozilla.jss.pkcs12.AuthenticatedSafes;
import org.mozilla.jss.pkcs12.CertBag;
import org.mozilla.jss.pkcs12.PFX;
import org.mozilla.jss.pkcs12.PasswordConverter;
import org.mozilla.jss.pkcs12.SafeBag;
import org.mozilla.jss.pkix.primitive.Attribute;
import org.mozilla.jss.pkix.primitive.EncryptedPrivateKeyInfo;
import org.mozilla.jss.util.Password;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import netscape.ldap.LDAPDN;
import netscape.ldap.util.DN;
import netscape.security.x509.X509CertImpl;

public class PKCS12Util {

    private static Logger logger = LoggerFactory.getLogger(PKCS12Util.class);

    public final static String NO_ENCRYPTION = "none";

    public final static List<PBEAlgorithm> SUPPORTED_CERT_ENCRYPTIONS = Arrays.asList(new PBEAlgorithm[] { null, // none
            PBEAlgorithm.PBE_SHA1_RC2_40_CBC });

    public final static List<PBEAlgorithm> SUPPORTED_KEY_ENCRYPTIONS = Arrays
            .asList(new PBEAlgorithm[] { PBEAlgorithm.PBE_PKCS5_PBES2, PBEAlgorithm.PBE_SHA1_DES3_CBC });

    public final static PBEAlgorithm DEFAULT_CERT_ENCRYPTION = SUPPORTED_CERT_ENCRYPTIONS.get(0);
    public final static String DEFAULT_CERT_ENCRYPTION_NAME = NO_ENCRYPTION;

    public final static PBEAlgorithm DEFAULT_KEY_ENCRYPTION = SUPPORTED_KEY_ENCRYPTIONS.get(0);
    public final static String DEFAULT_KEY_ENCRYPTION_NAME = DEFAULT_KEY_ENCRYPTION.toString();

    SecureRandom random;
    PBEAlgorithm certEncryption = DEFAULT_CERT_ENCRYPTION;
    PBEAlgorithm keyEncryption = DEFAULT_KEY_ENCRYPTION;
    boolean trustFlagsEnabled = true;

    public PKCS12Util() throws Exception {
        random = SecureRandom.getInstance("pkcs11prng", "Mozilla-JSS");
    }

    public void setCertEncryption(String name) throws Exception {

        for (PBEAlgorithm algorithm : SUPPORTED_CERT_ENCRYPTIONS) {

            if (algorithm == null) {
                if (NO_ENCRYPTION.equals(name)) {
                    this.certEncryption = null;
                    return;
                }

            } else if (algorithm.toString().equals(name)) {
                this.certEncryption = algorithm;
                return;
            }
        }

        throw new Exception("Unsupported certificate encryption: " + name);
    }

    public void setCertEncryption(PBEAlgorithm algorithm) throws Exception {
        this.certEncryption = algorithm;
    }

    public PBEAlgorithm getCertEncryption() {
        return certEncryption;
    }

    public void setKeyEncryption(String name) throws Exception {

        for (PBEAlgorithm algorithm : SUPPORTED_KEY_ENCRYPTIONS) {

            if (algorithm == null) {
                if (NO_ENCRYPTION.equals(name)) {
                    this.keyEncryption = null;
                    return;
                }

            } else if (algorithm.toString().equals(name)) {
                this.keyEncryption = algorithm;
                return;
            }
        }

        throw new Exception("Unsupported key encryption: " + name);
    }

    public void setKeyEncryption(PBEAlgorithm algorithm) throws Exception {
        this.keyEncryption = algorithm;
    }

    public PBEAlgorithm getKeyEncryption() {
        return keyEncryption;
    }

    public boolean isTrustFlagsEnabled() {
        return trustFlagsEnabled;
    }

    public void setTrustFlagsEnabled(boolean trustFlagsEnabled) {
        this.trustFlagsEnabled = trustFlagsEnabled;
    }

    public String getTrustFlags(X509Certificate cert) {

        InternalCertificate icert = (InternalCertificate) cert;

        StringBuilder sb = new StringBuilder();

        sb.append(PKCS12.encodeFlags(icert.getSSLTrust()));
        sb.append(",");
        sb.append(PKCS12.encodeFlags(icert.getEmailTrust()));
        sb.append(",");
        sb.append(PKCS12.encodeFlags(icert.getObjectSigningTrust()));

        return sb.toString();
    }

    public void setTrustFlags(X509Certificate cert, String trustFlags) throws Exception {

        InternalCertificate icert = (InternalCertificate) cert;

        String[] flags = trustFlags.split(",", -1); // don't remove empty string
        if (flags.length < 3)
            throw new Exception("Invalid trust flags: " + trustFlags);

        icert.setSSLTrust(PKCS12.decodeFlags(flags[0]));
        icert.setEmailTrust(PKCS12.decodeFlags(flags[1]));
        icert.setObjectSigningTrust(PKCS12.decodeFlags(flags[2]));
    }

    /**
     * Add a private key to the PKCS #12 object.
     *
     * The PKCS12KeyInfo object received comes about in two
     * different scenarios:
     *
     * - The private key could be in encrypted byte[] form (e.g.
     *   when we have merely loaded a PKCS #12 file for inspection
     *   or e.g. to delete a certificate and its associated key).
     *   In this case we simply re-use this encrypted private key
     *   info byte[].
     *
     * - The private key could be a be an NSS PrivateKey handle.  In
     *   this case we must export the PrivateKey from the token to
     *   obtain the EncryptedPrivateKeyInfo.
     *
     * The common final step is to add the encrypted private key
     * data to a "Shrouded Key Bag" to the PKCS #12 object.
     * Unencrypted key material is never seen.
     */
    public void addKeyBag(PKCS12KeyInfo keyInfo, Password password, SEQUENCE encSafeContents) throws Exception {

        logger.debug("Creating key bag for " + keyInfo.getFriendlyName());

        ASN1Value content;

        byte[] epkiBytes = keyInfo.getEncryptedPrivateKeyInfoBytes();

        if (epkiBytes != null) {
            // private key already encrypted
            content = new ANY(epkiBytes);

        } else {
            logger.debug("Encrypting private key for " + keyInfo.getFriendlyName());

            PrivateKey privateKey = keyInfo.getPrivateKey();
            if (privateKey == null) {
                throw new Exception("Missing private key for " + keyInfo.getFriendlyName());
            }

            CryptoToken token = CryptoManager.getInstance().getInternalKeyStorageToken();

            if (keyEncryption == PBEAlgorithm.PBE_SHA1_DES3_CBC) {
                content = create_EPKI_with_PBE_SHA1_DES3_CBC(token, privateKey, password);

            } else if (keyEncryption == PBEAlgorithm.PBE_PKCS5_PBES2) {
                content = create_EPKI_with_PBE_PKCS5_PBES2(token, privateKey, password);

            } else {
                throw new Exception("Unsupported key encryption: " + keyEncryption);
            }
        }

        SET keyAttrs = createKeyBagAttrs(keyInfo);

        SafeBag safeBag = new SafeBag(SafeBag.PKCS8_SHROUDED_KEY_BAG, content, keyAttrs);
        encSafeContents.addElement(safeBag);
    }

    public ASN1Value create_EPKI_with_PBE_SHA1_DES3_CBC(CryptoToken token, PrivateKey privateKey, Password password)
            throws Exception {

        // Use the same salt size and number of iterations as in pk12util.

        byte[] salt = new byte[16];
        random.nextBytes(salt);

        return EncryptedPrivateKeyInfo.createPBE(PBEAlgorithm.PBE_SHA1_DES3_CBC, password, salt, 100000, // iterations
                new PasswordConverter(), privateKey, token);
    }

    public ASN1Value create_EPKI_with_PBE_PKCS5_PBES2(CryptoToken token, PrivateKey privateKey, Password password)
            throws Exception {

        CryptoStore store = token.getCryptoStore();

        byte[] bytes = store.getEncryptedPrivateKeyInfo(
                // For compatibility with OpenSSL and NSS >= 3.31,
                // do not BMPString-encode the passphrase when using
                // non-PKCS #12 PBE scheme such as PKCS #5 PBES2.
                //
                // The resulting PKCS #12 is not compatible with
                // NSS < 3.31.
                null, // password converter
                password,
                // NSS has a bug that causes any AES CBC encryption
                // to use AES-256, but AlgorithmID contains chosen
                // alg.  To avoid mismatch, use AES_256_CBC.
                EncryptionAlgorithm.AES_256_CBC, 0, // iterations (default)
                privateKey);

        return new ANY(bytes);
    }

    public void addCertBag(PKCS12CertInfo certInfo, SEQUENCE safeContents) throws Exception {

        logger.debug("Creating cert bag for " + certInfo.getFriendlyName());

        ASN1Value cert = new OCTET_STRING(certInfo.cert.getEncoded());
        CertBag certBag = new CertBag(CertBag.X509_CERT_TYPE, cert);

        SET certAttrs = createCertBagAttrs(certInfo);

        SafeBag safeBag = new SafeBag(SafeBag.CERT_BAG, certBag, certAttrs);
        safeContents.addElement(safeBag);
    }

    SET createKeyBagAttrs(PKCS12KeyInfo keyInfo) throws Exception {

        SET attrs = new SET();

        SEQUENCE subjectAttr = new SEQUENCE();
        subjectAttr.addElement(SafeBag.FRIENDLY_NAME);

        SET subjectSet = new SET();
        subjectSet.addElement(new BMPString(keyInfo.getFriendlyName()));
        subjectAttr.addElement(subjectSet);

        attrs.addElement(subjectAttr);

        SEQUENCE localKeyAttr = new SEQUENCE();
        localKeyAttr.addElement(SafeBag.LOCAL_KEY_ID);

        SET localKeySet = new SET();
        localKeySet.addElement(new OCTET_STRING(keyInfo.id));
        localKeyAttr.addElement(localKeySet);

        attrs.addElement(localKeyAttr);

        return attrs;
    }

    SET createCertBagAttrs(PKCS12CertInfo certInfo) throws Exception {

        SET attrs = new SET();

        SEQUENCE nicknameAttr = new SEQUENCE();
        nicknameAttr.addElement(SafeBag.FRIENDLY_NAME);

        SET nicknameSet = new SET();
        nicknameSet.addElement(new BMPString(certInfo.getFriendlyName()));
        nicknameAttr.addElement(nicknameSet);

        attrs.addElement(nicknameAttr);

        if (certInfo.getID() != null) {
            SEQUENCE localKeyAttr = new SEQUENCE();
            localKeyAttr.addElement(SafeBag.LOCAL_KEY_ID);

            SET localKeySet = new SET();
            localKeySet.addElement(new OCTET_STRING(certInfo.id));
            localKeyAttr.addElement(localKeySet);

            attrs.addElement(localKeyAttr);
        }

        if (certInfo.trustFlags != null && trustFlagsEnabled) {
            SEQUENCE trustFlagsAttr = new SEQUENCE();
            trustFlagsAttr.addElement(PKCS12.CERT_TRUST_FLAGS_OID);

            SET trustFlagsSet = new SET();
            trustFlagsSet.addElement(new BMPString(certInfo.trustFlags));
            trustFlagsAttr.addElement(trustFlagsSet);

            attrs.addElement(trustFlagsAttr);
        }

        return attrs;
    }

    public void loadFromNSS(PKCS12 pkcs12) throws Exception {
        loadFromNSS(pkcs12, true, true);
    }

    public void loadFromNSS(PKCS12 pkcs12, boolean includeKey, boolean includeChain) throws Exception {

        logger.info("Loading all certificate and keys from NSS database");

        CryptoManager cm = CryptoManager.getInstance();
        CryptoToken token = cm.getInternalKeyStorageToken();
        CryptoStore store = token.getCryptoStore();

        for (X509Certificate cert : store.getCertificates()) {
            loadCertFromNSS(pkcs12, cert, includeKey, includeChain);
        }
    }

    public void loadCertFromNSS(PKCS12 pkcs12, String nickname, boolean includeKey, boolean includeChain)
            throws Exception {

        loadCertFromNSS(pkcs12, nickname, includeKey, includeChain, null);
    }

    public void loadCertFromNSS(PKCS12 pkcs12, String nickname, boolean includeKey, boolean includeChain,
            String friendlyName) throws Exception {

        CryptoManager cm = CryptoManager.getInstance();

        X509Certificate[] certs = cm.findCertsByNickname(nickname);

        if (certs == null || certs.length == 0) {
            throw new Exception("Certificate not found: " + nickname);
        }

        for (X509Certificate cert : certs) {
            loadCertFromNSS(pkcs12, cert, includeKey, includeChain, friendlyName);
        }
    }

    public void loadCertFromNSS(PKCS12 pkcs12, X509Certificate cert, boolean includeKey, boolean includeChain)
            throws Exception {

        loadCertFromNSS(pkcs12, cert, includeKey, includeChain, null);
    }

    public void loadCertFromNSS(PKCS12 pkcs12, X509Certificate cert, boolean includeKey, boolean includeChain,
            String friendlyName) throws Exception {

        CryptoManager cm = CryptoManager.getInstance();

        byte[] id = SafeBag.getLocalKeyIDFromCert(cert.getEncoded());
        logger.debug("ID: " + Hex.encodeHexString(id));

        // load cert info
        loadCertInfoFromNSS(pkcs12, cert, id, true, friendlyName);

        if (includeKey) {
            // load key info if exists
            loadKeyInfoFromNSS(pkcs12, cert, id, friendlyName);
        }

        if (includeChain) {
            // load cert chain
            X509Certificate[] certChain = cm.buildCertificateChain(cert);
            for (int i = 1; i < certChain.length; i++) {
                X509Certificate c = certChain[i];
                byte[] cid = SafeBag.getLocalKeyIDFromCert(c.getEncoded());
                loadCertInfoFromNSS(pkcs12, c, cid, false);
            }
        }
    }

    public void loadCertInfoFromNSS(PKCS12 pkcs12, X509Certificate cert, byte[] id, boolean replace)
            throws Exception {

        loadCertInfoFromNSS(pkcs12, cert, id, replace, null);
    }

    public void loadCertInfoFromNSS(PKCS12 pkcs12, X509Certificate cert, byte[] id, boolean replace,
            String friendlyName) throws Exception {

        String nickname = cert.getNickname();
        logger.info("Loading certificate \"" + nickname + "\" from NSS database");

        if (friendlyName == null) {
            friendlyName = nickname;
        }

        PKCS12CertInfo certInfo = new PKCS12CertInfo();
        certInfo.id = id;
        certInfo.setFriendlyName(friendlyName);
        certInfo.cert = new X509CertImpl(cert.getEncoded());
        certInfo.trustFlags = getTrustFlags(cert);

        pkcs12.addCertInfo(certInfo, replace);
    }

    public void loadKeyInfoFromNSS(PKCS12 pkcs12, X509Certificate cert, byte[] id) throws Exception {

        loadKeyInfoFromNSS(pkcs12, cert, id, null);
    }

    public void loadKeyInfoFromNSS(PKCS12 pkcs12, X509Certificate cert, byte[] id, String friendlyName)
            throws Exception {

        String nickname = cert.getNickname();
        logger.info("Loading private key for certificate \"" + nickname + "\" from NSS database");

        if (friendlyName == null) {
            friendlyName = nickname;
        }

        CryptoManager cm = CryptoManager.getInstance();

        try {
            PrivateKey privateKey = cm.findPrivKeyByCert(cert);
            logger.debug("Certificate \"" + nickname + "\" has private key");

            PKCS12KeyInfo keyInfo = new PKCS12KeyInfo(privateKey);
            keyInfo.id = id;
            keyInfo.setFriendlyName(friendlyName);

            pkcs12.addKeyInfo(keyInfo);

        } catch (ObjectNotFoundException e) {
            logger.debug("Certificate \"" + nickname + "\" has no private key");
        }
    }

    public PFX generatePFX(PKCS12 pkcs12, Password password) throws Exception {

        logger.info("Generating PKCS #12 data");

        AuthenticatedSafes authSafes = new AuthenticatedSafes();

        Collection<PKCS12KeyInfo> keyInfos = pkcs12.getKeyInfos();
        Collection<PKCS12CertInfo> certInfos = pkcs12.getCertInfos();

        if (!keyInfos.isEmpty()) {
            SEQUENCE keySafeContents = new SEQUENCE();

            for (PKCS12KeyInfo keyInfo : keyInfos) {
                addKeyBag(keyInfo, password, keySafeContents);
            }

            authSafes.addSafeContents(keySafeContents);
        }

        if (!certInfos.isEmpty()) {
            SEQUENCE certSafeContents = new SEQUENCE();

            for (PKCS12CertInfo certInfo : certInfos) {
                addCertBag(certInfo, certSafeContents);
            }

            if (certEncryption == null) {
                authSafes.addSafeContents(certSafeContents);

            } else if (certEncryption == PBEAlgorithm.PBE_SHA1_RC2_40_CBC) {

                byte[] salt = new byte[16];
                random.nextBytes(salt);

                authSafes.addEncryptedSafeContents(certEncryption, password, salt, 100000, // iterations
                        certSafeContents);

            } else {
                throw new Exception("Unsupported certificate encryption: " + certEncryption);
            }
        }

        PFX pfx = new PFX(authSafes);

        // Use the same salt size and number of iterations as in pk12util.

        byte[] salt = new byte[16];
        random.nextBytes(salt);
        pfx.computeMacData(password, salt, 100000);

        return pfx;
    }

    public void storeIntoFile(PKCS12 pkcs12, String filename, Password password) throws Exception {

        PFX pfx = generatePFX(pkcs12, password);

        logger.info("Storing data into PKCS #12 file");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        pfx.encode(bos);
        byte[] data = bos.toByteArray();

        try (FileOutputStream fos = new FileOutputStream(filename)) {
            fos.write(data);
        }
    }

    /**
     * Loads key bags (for IMPORT and other operations on existing
     * PKCS #12 files).  Does not decrypt EncryptedPrivateKeyInfo
     * values, but stores them in PKCS12KeyInfo objects for possible
     * later use.
     */
    public PKCS12KeyInfo getKeyInfo(SafeBag bag, Password password) throws Exception {

        PKCS12KeyInfo keyInfo = new PKCS12KeyInfo(bag.getBagContent().getEncoded());

        // get key attributes
        SET bagAttrs = bag.getBagAttributes();

        for (int i = 0; bagAttrs != null && i < bagAttrs.size(); i++) {

            Attribute attr = (Attribute) bagAttrs.elementAt(i);
            OBJECT_IDENTIFIER oid = attr.getType();

            if (oid.equals(SafeBag.FRIENDLY_NAME)) {

                SET values = attr.getValues();
                ANY value = (ANY) values.elementAt(0);

                ByteArrayInputStream bis = new ByteArrayInputStream(value.getEncoded());
                BMPString friendlyName = (BMPString) new BMPString.Template().decode(bis);

                keyInfo.setFriendlyName(friendlyName.toString());
                logger.debug("   Friendly name: " + keyInfo.getFriendlyName());

            } else if (oid.equals(SafeBag.LOCAL_KEY_ID)) {

                SET values = attr.getValues();
                ANY value = (ANY) values.elementAt(0);

                ByteArrayInputStream bis = new ByteArrayInputStream(value.getEncoded());
                OCTET_STRING keyID = (OCTET_STRING) new OCTET_STRING.Template().decode(bis);

                keyInfo.id = keyID.toByteArray();
                logger.debug("   ID: " + Hex.encodeHexString(keyInfo.id));
            }
        }

        return keyInfo;
    }

    public PKCS12CertInfo getCertInfo(SafeBag bag) throws Exception {

        PKCS12CertInfo certInfo = new PKCS12CertInfo();

        CertBag certBag = (CertBag) bag.getInterpretedBagContent();

        OCTET_STRING certStr = (OCTET_STRING) certBag.getInterpretedCert();
        byte[] x509cert = certStr.toByteArray();

        certInfo.cert = new X509CertImpl(x509cert);
        Principal subjectDN = certInfo.cert.getSubjectDN();
        logger.debug("   Subject DN: " + subjectDN);

        SET bagAttrs = bag.getBagAttributes();

        for (int i = 0; bagAttrs != null && i < bagAttrs.size(); i++) {

            Attribute attr = (Attribute) bagAttrs.elementAt(i);
            OBJECT_IDENTIFIER oid = attr.getType();

            if (oid.equals(SafeBag.FRIENDLY_NAME)) {

                SET values = attr.getValues();
                ANY value = (ANY) values.elementAt(0);

                ByteArrayInputStream bis = new ByteArrayInputStream(value.getEncoded());
                BMPString friendlyName = (BMPString) (new BMPString.Template()).decode(bis);

                certInfo.setFriendlyName(friendlyName.toString());
                logger.debug("   Friendly name: " + certInfo.getFriendlyName());

            } else if (oid.equals(SafeBag.LOCAL_KEY_ID)) {

                SET values = attr.getValues();
                ANY value = (ANY) values.elementAt(0);

                ByteArrayInputStream bis = new ByteArrayInputStream(value.getEncoded());
                OCTET_STRING keyID = (OCTET_STRING) new OCTET_STRING.Template().decode(bis);

                certInfo.id = keyID.toByteArray();
                logger.debug("   ID: " + Hex.encodeHexString(certInfo.id));

            } else if (oid.equals(PKCS12.CERT_TRUST_FLAGS_OID) && trustFlagsEnabled) {

                SET values = attr.getValues();
                ANY value = (ANY) values.elementAt(0);

                ByteArrayInputStream is = new ByteArrayInputStream(value.getEncoded());
                BMPString trustFlags = (BMPString) (new BMPString.Template()).decode(is);

                certInfo.trustFlags = trustFlags.toString();
                logger.debug("   Trust flags: " + certInfo.trustFlags);
            }
        }

        if (certInfo.id == null) {
            logger.debug("   ID not specified, generating new ID");
            certInfo.id = SafeBag.getLocalKeyIDFromCert(x509cert);
            logger.debug("   ID: " + Hex.encodeHexString(certInfo.id));
        }

        if (certInfo.getFriendlyName() == null) {
            logger.debug("   Generating new friendly name");
            DN dn = new DN(subjectDN.getName());
            String[] values = dn.explodeDN(true);
            certInfo.setFriendlyName(StringUtils.join(values, " - "));
            logger.debug("   Friendly name: " + certInfo.friendlyName);
        }

        return certInfo;
    }

    public void getKeyInfos(PKCS12 pkcs12, PFX pfx, Password password) throws Exception {

        logger.debug("Load encrypted private keys:");

        AuthenticatedSafes safes = pfx.getAuthSafes();

        for (int i = 0; i < safes.getSize(); i++) {

            SEQUENCE contents = safes.getSafeContentsAt(password, i);

            for (int j = 0; j < contents.size(); j++) {

                SafeBag bag = (SafeBag) contents.elementAt(j);
                OBJECT_IDENTIFIER oid = bag.getBagType();

                if (!oid.equals(SafeBag.PKCS8_SHROUDED_KEY_BAG))
                    continue;

                logger.debug(" - Private key:");
                PKCS12KeyInfo keyInfo = getKeyInfo(bag, password);
                pkcs12.addKeyInfo(keyInfo);
            }
        }
    }

    public void getCertInfos(PKCS12 pkcs12, PFX pfx, Password password) throws Exception {

        logger.debug("Loading certificates:");

        AuthenticatedSafes safes = pfx.getAuthSafes();

        for (int i = 0; i < safes.getSize(); i++) {

            SEQUENCE contents = safes.getSafeContentsAt(password, i);

            for (int j = 0; j < contents.size(); j++) {

                SafeBag bag = (SafeBag) contents.elementAt(j);
                OBJECT_IDENTIFIER oid = bag.getBagType();

                if (!oid.equals(SafeBag.CERT_BAG))
                    continue;

                logger.debug(" - Certificate:");
                PKCS12CertInfo certInfo = getCertInfo(bag);
                pkcs12.addCertInfo(certInfo, true);
            }
        }
    }

    public PKCS12 loadFromFile(String filename, Password password) throws Exception {

        logger.info("Loading PKCS #12 file");

        Path path = Paths.get(filename);
        byte[] b = Files.readAllBytes(path);
        return loadFromByteArray(b, password);
    }

    public PKCS12 loadFromByteArray(byte[] b, Password password) throws Exception {
        ByteArrayInputStream bis = new ByteArrayInputStream(b);

        PFX pfx = (PFX) (new PFX.Template()).decode(bis);

        PKCS12 pkcs12 = new PKCS12();

        StringBuffer reason = new StringBuffer();
        boolean valid = pfx.verifyAuthSafes(password, reason);

        if (!valid) {
            throw new Exception("Unable to validate PKCS #12 file: " + reason);
        }

        getKeyInfos(pkcs12, pfx, password);
        getCertInfos(pkcs12, pfx, password);

        return pkcs12;
    }

    public PKCS12 loadFromFile(String filename) throws Exception {
        return loadFromFile(filename, null);
    }

    public PrivateKey.Type getPrivateKeyType(PublicKey publicKey) {
        if (publicKey.getAlgorithm().equals("EC")) {
            return PrivateKey.Type.EC;
        }
        return PrivateKey.Type.RSA;
    }

    public PKCS12CertInfo getCertBySubjectDN(PKCS12 pkcs12, String subjectDN) throws CertificateException {

        for (PKCS12CertInfo certInfo : pkcs12.getCertInfos()) {
            Principal certSubjectDN = certInfo.cert.getSubjectDN();
            if (LDAPDN.equals(certSubjectDN.toString(), subjectDN))
                return certInfo;
        }

        return null;
    }

    public void importKey(PKCS12 pkcs12, Password password, String nickname, PKCS12KeyInfo keyInfo)
            throws Exception {

        PKCS12CertInfo certInfo = pkcs12.getCertInfoByID(keyInfo.getID());
        if (certInfo == null) {
            logger.debug("Private key has no certificate, ignore");
            return;
        }

        CryptoManager cm = CryptoManager.getInstance();
        CryptoToken token = cm.getInternalKeyStorageToken();
        PK11Store store = (PK11Store) token.getCryptoStore();

        X509Certificate cert = cm.importCACertPackage(certInfo.cert.getEncoded());

        // get public key
        PublicKey publicKey = cert.getPublicKey();

        byte[] epkiBytes = keyInfo.getEncryptedPrivateKeyInfoBytes();
        if (epkiBytes == null) {
            logger.debug("No EncryptedPrivateKeyInfo for key '" + keyInfo.getFriendlyName() + "'; skipping key");
        }
        try {
            // first true without BMPString-encoding the passphrase.
            store.importEncryptedPrivateKeyInfo(null, password, nickname, publicKey, epkiBytes);
        } catch (Exception e) {
            // if that failed, try again with BMPString-encoded
            // passphrase.  This is required for PKCS #12 PBE
            // schemes and for PKCS #12 files using PBES2 generated
            // by NSS < 3.31
            store.importEncryptedPrivateKeyInfo(new PasswordConverter(), password, nickname, publicKey, epkiBytes);
        }

        // delete the cert again (it will be imported again later
        // with the correct nickname)
        try {
            store.deleteCertOnly(cert);
        } catch (NoSuchItemOnTokenException e) {
            // this is OK
        }
    }

    /**
     * Store a certificate (and key, if present) in NSSDB.
     */
    public void storeCertIntoNSS(PKCS12 pkcs12, Password password, PKCS12CertInfo certInfo, boolean overwrite)
            throws Exception {
        CryptoManager cm = CryptoManager.getInstance();
        CryptoToken ct = cm.getInternalKeyStorageToken();
        CryptoStore store = ct.getCryptoStore();

        byte[] id = certInfo.getID();
        PKCS12KeyInfo keyInfo = pkcs12.getKeyInfoByID(id);

        String nickname = certInfo.getFriendlyName();
        for (X509Certificate cert : cm.findCertsByNickname(nickname)) {
            if (!overwrite) {
                return;
            }
            store.deleteCert(cert);
        }

        X509Certificate cert;
        if (keyInfo != null) { // cert has key
            logger.debug("Importing private key for " + certInfo.getFriendlyName());
            importKey(pkcs12, password, certInfo.getFriendlyName(), keyInfo);

            logger.debug("Importing user certificate " + certInfo.getFriendlyName());
            cert = cm.importUserCACertPackage(certInfo.cert.getEncoded(), certInfo.getFriendlyName());

        } else { // cert has no key
            logger.debug("Importing CA certificate " + certInfo.getFriendlyName());
            // Note: JSS does not preserve CA certificate nickname
            cert = cm.importCACertPackage(certInfo.cert.getEncoded());
        }

        if (certInfo.trustFlags != null && trustFlagsEnabled)
            setTrustFlags(cert, certInfo.trustFlags);
    }

    public void storeCertIntoNSS(PKCS12 pkcs12, Password password, String nickname, boolean overwrite)
            throws Exception {
        Collection<PKCS12CertInfo> certInfos = pkcs12.getCertInfosByFriendlyName(nickname);
        for (PKCS12CertInfo certInfo : certInfos) {
            storeCertIntoNSS(pkcs12, password, certInfo, overwrite);
        }
    }

    public void storeIntoNSS(PKCS12 pkcs12, Password password, boolean overwrite) throws Exception {
        logger.info("Storing data into NSS database");

        for (PKCS12CertInfo certInfo : pkcs12.getCertInfos()) {
            storeCertIntoNSS(pkcs12, password, certInfo, overwrite);
        }
    }
}