org.tolven.config.model.CredentialManager.java Source code

Java tutorial

Introduction

Here is the source code for org.tolven.config.model.CredentialManager.java

Source

/*
 *  Copyright (C) 2006 Tolven Inc
 *
 * This library is free software; you can redistribute it and/or modify it under the terms of
 * the GNU Lesser General Public License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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 Lesser General Public License for more details.
 *
 * Contact: info@tolvenhealth.com
 */
package org.tolven.config.model;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.security.auth.x500.X500Principal;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.openssl.PasswordFinder;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.tolven.config.model.credential.bean.CertificateDetail;
import org.tolven.config.model.credential.bean.CertificateGroupDetail;
import org.tolven.config.model.credential.bean.CertificateKeyDetail;
import org.tolven.config.model.credential.bean.CertificateKeyStoreDetail;
import org.tolven.config.model.credential.bean.TrustStoreCertificateDetail;
import org.tolven.config.model.credential.bean.TrustStoreDetail;
import org.tolven.security.password.PasswordHolder;
import org.tolven.security.password.PasswordInfo;

/**
 * This class contains the main API for generating credentials.
 * It relies heavily for functionality on CredentialHelper and CredentialsValidator
 *
 * @author Joseph Isaac
 *
 */
public class CredentialManager {

    private TolvenConfigWrapper tolvenConfigWrapper;
    private SecureRandom secureRandom;
    private Logger logger = Logger.getLogger(CredentialManager.class);

    public CredentialManager(TolvenConfigWrapper tolvenConfigWrapper) {
        setTolvenConfigWrapper(tolvenConfigWrapper);
        //TODO Is this the best place for loading the provider
        Security.addProvider(new BouncyCastleProvider());
    }

    private TolvenConfigWrapper getTolvenConfigWrapper() {
        return tolvenConfigWrapper;
    }

    private void setTolvenConfigWrapper(TolvenConfigWrapper tolvenConfigWrapper) {
        this.tolvenConfigWrapper = tolvenConfigWrapper;
    }

    private PasswordHolder getPasswordHolder() {
        return getTolvenConfigWrapper().getPasswordHolder();
    }

    public void changeGroupCredentialPassword(PasswordInfo passwordInfo, char[] oldPassword, char[] newPassword)
            throws IOException, GeneralSecurityException {
        if (oldPassword == null)
            throw new RuntimeException("Old password '" + passwordInfo.getRefId() + "' is null");
        if (!getPasswordHolder().verify(passwordInfo, oldPassword))
            throw new RuntimeException("Old Password is invalid for '" + passwordInfo.getRefId() + "'");
        if (newPassword == null)
            throw new RuntimeException("New password '" + passwordInfo.getRefId() + "' is null");
        CertificateGroupDetail certGroup = getTolvenConfigWrapper().getCredentialGroup(passwordInfo.getRefId());
        CertificateKeyDetail keyDetail = certGroup.getKey();
        PrivateKey privateKey = getPrivateKey(keyDetail, oldPassword);
        File keyFile = new File(keyDetail.getSource());
        KeyStore keyStore = null;
        File keyStoreFile = null;
        CertificateKeyStoreDetail certKeyStoreDetail = certGroup.getKeyStore();
        if (certKeyStoreDetail != null) {
            keyStore = getTolvenConfigWrapper().getKeyStore(oldPassword, certKeyStoreDetail);
            keyStoreFile = new File(certKeyStoreDetail.getSource());
        }
        TrustStoreDetail trustStoreDetail = getTolvenConfigWrapper().getTrustStoreDetail(passwordInfo.getRefId());
        KeyStore trustStore = null;
        File trustStoreFile = null;
        if (trustStore != null) {
            trustStore = getTolvenConfigWrapper().getTrustStore(oldPassword, trustStoreDetail);
            trustStoreFile = new File(trustStoreDetail.getSource());
        }
        File tmpKey = null;
        File tmpKeyStore = null;
        File tmpTrustStore = null;
        boolean success = false;
        try {
            getTolvenConfigWrapper().getBuildDir().mkdirs();
            tmpKey = new File(getTolvenConfigWrapper().getBuildDir(), keyFile.getName());
            write(privateKey, keyDetail.getFormat(), tmpKey, newPassword);
            if (keyStoreFile != null) {
                tmpKeyStore = new File(getTolvenConfigWrapper().getBuildDir(), keyStoreFile.getName());
                String alias = keyStore.aliases().nextElement();
                Key key = keyStore.getKey(alias, oldPassword);
                Certificate[] chain = keyStore.getCertificateChain(alias);
                keyStore.setKeyEntry(alias, key, newPassword, chain);
                write(keyStore, tmpKeyStore, newPassword);
            }
            if (trustStoreFile != null) {
                tmpTrustStore = new File(getTolvenConfigWrapper().getBuildDir(), trustStoreFile.getName());
                write(trustStore, tmpTrustStore, newPassword);
            }
            FileUtils.copyFile(tmpKey, keyFile);
            if (keyStoreFile != null) {
                FileUtils.copyFile(tmpKeyStore, keyStoreFile);
            }
            if (trustStoreFile != null) {
                FileUtils.copyFile(tmpTrustStore, trustStoreFile);
            }
            success = true;
        } finally {
            if (success) {
                if (tmpKey != null) {
                    tmpKey.delete();
                }
                if (tmpKeyStore != null) {
                    tmpKeyStore.delete();
                }
                if (tmpKeyStore != null) {
                    tmpKeyStore.delete();
                }
                getPasswordHolder().changePassword(passwordInfo, oldPassword, newPassword);
            }
        }
    }

    public void processPasswords(Properties inputPasswordProperties) throws IOException, GeneralSecurityException {
        File credentialFile = null;
        String credentialId = null;
        char[] privateKeyPassword = null;
        for (CertificateGroupDetail certGroup : getTolvenConfigWrapper().getCertificateInfoDetail().getGroup()) {
            credentialId = certGroup.getId();
            if (inputPasswordProperties.containsKey(credentialId)) {
                credentialFile = new File(certGroup.getKey().getSource());
                privateKeyPassword = (char[]) inputPasswordProperties.get(credentialId);
                if (credentialFile.exists()) {
                    getPrivateKey(certGroup.getKey(), privateKeyPassword);
                }
                if (getPasswordHolder().getPasswordInfo(credentialId) == null) {
                    getPasswordHolder().addPassword(certGroup.getId(), PasswordHolder.CREDENTIAL_PASSWORD,
                            privateKeyPassword);
                }
            }
        }
    }

    public boolean verifyKeyStorePassword(PasswordInfo passwordInfo) {
        char[] password = getPasswordHolder().getPassword(passwordInfo.getRefId());
        if (password == null) {
            throw new RuntimeException("Password '" + passwordInfo.getRefId() + "' is does not exist.");
        }
        CertificateGroupDetail certGroup = getTolvenConfigWrapper().getCredentialGroup(passwordInfo.getRefId());
        CertificateKeyStoreDetail certKeyStoreDetail = certGroup.getKeyStore();
        getTolvenConfigWrapper().getKeyStore(password, certKeyStoreDetail);
        return true;
    }

    public boolean verifyPrivateKeyPassword(PasswordInfo passwordInfo)
            throws IOException, GeneralSecurityException {
        char[] password = getPasswordHolder().getPassword(passwordInfo.getRefId());
        if (password == null) {
            throw new RuntimeException("Password '" + passwordInfo.getRefId() + "' is does not exist.");
        }
        CertificateGroupDetail certGroup = getTolvenConfigWrapper().getCredentialGroup(passwordInfo.getRefId());
        CertificateKeyDetail keyDetail = certGroup.getKey();
        try {
            getPrivateKey(keyDetail, password);
        } catch (IOException ex) {
            throw new IOException("PrivateKey password verified, but failed with corresponding credential '"
                    + passwordInfo.getRefId() + "'");
        }
        return true;
    }

    public boolean verifyTrustStorePassword(PasswordInfo passwordInfo)
            throws IOException, GeneralSecurityException {
        char[] password = getPasswordHolder().getPassword(passwordInfo.getRefId());
        if (password == null) {
            throw new RuntimeException("Password '" + passwordInfo.getRefId() + "' is does not exist.");
        }
        TrustStoreDetail trustStoreDetail = getTolvenConfigWrapper().getTrustStoreDetail(passwordInfo.getRefId());
        try {
            getTolvenConfigWrapper().getTrustStore(password, trustStoreDetail);
        } catch (IOException ex) {
            throw new IOException("TrustStore password verified, but failed with corresponding credential '"
                    + passwordInfo.getRefId() + "'");
        }
        return true;
    }

    public String getKeySource(String groupId) {
        CertificateGroupDetail certGroupDetail = getTolvenConfigWrapper().getCredentialGroup(groupId);
        if (certGroupDetail != null) {
            CertificateKeyDetail certKeyDetail = certGroupDetail.getKey();
            File keyFile = new File(certKeyDetail.getSource());
            return keyFile.getPath();
        }
        return null;
    }

    public boolean keyExists(String groupId) {
        CertificateGroupDetail certGroupDetail = getTolvenConfigWrapper().getCredentialGroup(groupId);
        if (certGroupDetail != null) {
            CertificateKeyDetail certKeyDetail = certGroupDetail.getKey();
            File keyFile = new File(certKeyDetail.getSource());
            return keyFile.exists();
        }
        return false;
    }

    private byte[] convertToPEMBytes(char[] pass, Object object) throws IOException {
        OutputStreamWriter osw = null;
        PEMWriter pemWriter = null;
        ByteArrayOutputStream baos = null;
        try {
            baos = new ByteArrayOutputStream();
            try {
                osw = new OutputStreamWriter(baos);
                pemWriter = new PEMWriter(osw, "BC");
                if (pass == null) {
                    pemWriter.writeObject(object);
                } else {
                    pemWriter.writeObject(object, "DESEDE", pass, getSecureRandom());
                }
            } finally {
                if (pemWriter != null)
                    pemWriter.close();
                if (osw != null)
                    osw.close();
            }
            return baos.toByteArray();
        } finally {
            if (baos != null)
                baos.close();
        }
    }

    private byte[] convertToPEMBytes(Object object) throws IOException {
        return convertToPEMBytes(null, object);
    }

    private void createKeyStore(CertificateGroupDetail certGroup, char[] password)
            throws IOException, GeneralSecurityException {
        File file = null;
        try {
            CertificateKeyStoreDetail certificateKeyStore = certGroup.getKeyStore();
            KeyStore keyStore = KeyStore.getInstance(certificateKeyStore.getFormat());
            char[] keystorepass = password;
            if (keystorepass == null) {
                keystorepass = getPasswordHolder().getPassword(certGroup.getId());
                if (keystorepass == null) {
                    throw new RuntimeException(
                            "Password for keystore in group " + certGroup.getId() + " cannot be null");
                }
            }
            if (getPasswordHolder().getPassword(certGroup.getId()) == null) {
                getPasswordHolder().addPassword(certGroup.getId(), PasswordHolder.CREDENTIAL_PASSWORD,
                        keystorepass);
            }
            keyStore.load(null, keystorepass);
            CertificateKeyDetail keyDetail = certGroup.getKey();
            X509Certificate[] certificates = getX509CertificateChain(certGroup);
            char[] keypass = getPasswordHolder().getPassword(certGroup.getId());
            PrivateKey privateKey = getPrivateKey(keyDetail, keypass);
            String alias = certificates[0].getSubjectDN().getName();
            keyStore.setKeyEntry(alias, privateKey, keypass, certificates);
            file = new File(certificateKeyStore.getSource());
            write(keyStore, file, keystorepass);
        } catch (IOException ex) {
            ex.printStackTrace();
            if (file != null)
                file.delete();
            throw ex;
        } catch (GeneralSecurityException ex) {
            ex.printStackTrace();
            if (file != null)
                file.delete();
            throw ex;
        }
    }

    private X509CertificatePrivateKeyPair createSelfSignedCertificate(X500Principal subjectX500Principal)
            throws GeneralSecurityException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(1024);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        X509Certificate certificate = signCertificate(subjectX500Principal, keyPair.getPublic(),
                subjectX500Principal, keyPair.getPrivate());
        return new X509CertificatePrivateKeyPair(certificate, keyPair.getPrivate());
    }

    private PrivateKey getDERPrivateKey(CertificateKeyDetail keyDetail, char[] password)
            throws IOException, GeneralSecurityException {
        File privateKeyFile = new File(keyDetail.getSource());
        if (!privateKeyFile.exists()) {
            throw new RuntimeException("Cannot find PrivateKey file: " + privateKeyFile.getPath());
        }
        byte[] privateKey = FileUtils.readFileToByteArray(privateKeyFile);
        EncryptedPrivateKeyInfo encryptedKeyInfo = new EncryptedPrivateKeyInfo(privateKey);
        AlgorithmParameters params = encryptedKeyInfo.getAlgParameters();
        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(encryptedKeyInfo.getAlgName());
        PBEKeySpec passwordSpec = new PBEKeySpec(password);
        SecretKey secretKey = secretKeyFactory.generateSecret(passwordSpec);
        Cipher cipher = Cipher.getInstance(encryptedKeyInfo.getAlgName());
        cipher.init(Cipher.DECRYPT_MODE, secretKey, params);
        PKCS8EncodedKeySpec keySpec = encryptedKeyInfo.getKeySpec(cipher);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(keySpec);
    }

    private BigInteger getNextSerialNumber() {
        byte[] randomLong = new byte[8];
        getSecureRandom().nextBytes(randomLong);
        BigInteger serialNumber = new BigInteger(randomLong);
        if (serialNumber.compareTo(BigInteger.valueOf(0)) > 0) {
            return serialNumber;
        } else if (serialNumber.compareTo(BigInteger.valueOf(0)) < 0) {
            return BigInteger.valueOf(0).subtract(serialNumber);
        } else {
            return BigInteger.valueOf(1).add(serialNumber);
        }
    }

    private PrivateKey getPEMPrivateKey(CertificateKeyDetail keyDetail, final char[] password) throws IOException {
        FileInputStream fis = null;
        InputStreamReader inputStreamReader = null;
        PEMReader pemReader = null;
        File file = new File(keyDetail.getSource());
        if (!file.exists()) {
            throw new RuntimeException("Cannot find PrivateKey file: " + file.getPath());
        }
        try {
            fis = new FileInputStream(new File(keyDetail.getSource()));
            inputStreamReader = new InputStreamReader(fis, Charset.forName("UTF-8"));
            if (keyDetail.isPasswordProtected()) {
                pemReader = new PEMReader(inputStreamReader, new PasswordFinder() {
                    public char[] getPassword() {
                        return password;
                    }
                }, "BC");
            } else {
                pemReader = new PEMReader(inputStreamReader);
            }
            KeyPair keyPair = (KeyPair) pemReader.readObject();
            return keyPair.getPrivate();
        } finally {
            if (fis != null)
                fis.close();
            if (inputStreamReader != null)
                inputStreamReader.close();
            if (pemReader != null)
                pemReader.close();
        }
    }

    private PrivateKey getPrivateKey(CertificateKeyDetail keyDetail, char[] password)
            throws IOException, GeneralSecurityException {
        if (password == null && keyDetail.isPasswordProtected())
            throw new RuntimeException("The key " + keyDetail.getId() + " requires a password");
        if (TolvenConfigWrapper.TOLVEN_CREDENTIAL_FORMAT_PEM.equals(keyDetail.getFormat())) {
            return getPEMPrivateKey(keyDetail, password);
        } else {
            return getDERPrivateKey(keyDetail, password);
        }
    }

    private SecureRandom getSecureRandom() {
        //TODO: This may be better placed where it only gets created once for certain
        String algorithm = "SHA1PRNG";
        if (secureRandom == null) {
            try {
                secureRandom = SecureRandom.getInstance(algorithm);
            } catch (NoSuchAlgorithmException ex) {
                throw new RuntimeException("Could not get an instance of SecureRandom using algorithm" + algorithm,
                        ex);
            }
        }
        return secureRandom;
    }

    private X500Principal getX500Principal(CertificateGroupDetail certGroupDetail) {
        return new X500Principal(X509Name.EmailAddress + "=" + certGroupDetail.getEmail() + "," + X509Name.CN + "="
                + certGroupDetail.getCommonName() + "," + X509Name.OU + "="
                + certGroupDetail.getOrganizationUnitName() + "," + X509Name.O + "="
                + certGroupDetail.getOrganizationName() + "," + X509Name.ST + "="
                + certGroupDetail.getStateOrProvince() + "," + X509Name.C + "=" + certGroupDetail.getCountryName());
    }

    private X509Certificate[] getX509CertificateChain(CertificateGroupDetail certGroup) {
        List<X509Certificate> certificates = new ArrayList<X509Certificate>();
        X509Certificate certificate = getTolvenConfigWrapper().getX509Certificate(certGroup);
        certificates.add(certificate);
        if (!certificate.getIssuerDN().equals(certificate.getSubjectDN())) {
            X509Certificate issuingCertificate = null;
            do {
                CertificateGroupDetail issuingCertGroup = getTolvenConfigWrapper()
                        .getCredentialGroup(certGroup.getCertificate().getCaRefId());
                issuingCertificate = getTolvenConfigWrapper().getX509Certificate(issuingCertGroup);
                if (!certificates.contains(issuingCertificate))
                    certificates.add(issuingCertificate);

            } while ((!issuingCertificate.getIssuerDN().equals(issuingCertificate.getSubjectDN())));
        }
        X509Certificate[] certArr = new X509Certificate[certificates.size()];
        for (int i = 0; i < certificates.size(); i++)
            certArr[i] = certificates.get(i);
        return certArr;
    }

    public void processCredentialGroup(CertificateGroupDetail certGroup)
            throws IOException, GeneralSecurityException {
        processCredentialGroup(certGroup, null);
    }

    public void processCredentialGroup(CertificateGroupDetail certGroup, char[] password)
            throws IOException, GeneralSecurityException {
        CertificateDetail cert = certGroup.getCertificate();
        CertificateKeyDetail key = certGroup.getKey();
        if ((key == null && cert != null) || (key != null && cert == null)) {
            throw new RuntimeException("Group: " + certGroup.getId()
                    + " must either have a both key and certificate definition or neither");
        }
        if (key != null && cert != null) {
            File certFile = new File(cert.getSource());
            File keyFile = new File(key.getSource());
            if (key.isCommercial()) {
                if (!(certFile.exists() && keyFile.exists()))
                    throw new RuntimeException(certGroup.getId() + " is a commercial group. " + certFile.getPath()
                            + " and " + keyFile.getPath() + " must exist");
            } else {
                if (!certFile.exists() && !keyFile.exists()) {
                    logger.info("New credentials will be created for certificate " + cert.getId());
                    getTolvenConfigWrapper().getBuildDir().mkdirs();
                    X500Principal x500Principal = getX500Principal(certGroup);
                    X509CertificatePrivateKeyPair certPrivateKeyPair = createSelfSignedCertificate(x500Principal);
                    X509Certificate certificate = null;
                    if (cert.getId().equals(cert.getCaRefId())) {
                        certificate = certPrivateKeyPair.getX509Certificate();
                    } else {
                        certificate = signCertificate(cert, x500Principal,
                                certPrivateKeyPair.getX509Certificate().getPublicKey());
                    }
                    File tmpCertFile = null;
                    File tmpKeyFile = null;
                    try {
                        tmpCertFile = File.createTempFile("cert", ".tmp", getTolvenConfigWrapper().getBuildDir());
                        // Just require a path to a temporary file
                        tmpCertFile.delete();
                        tmpKeyFile = File.createTempFile("key", ".tmp", getTolvenConfigWrapper().getBuildDir());
                        // Just require a path to a temporary file
                        tmpKeyFile.delete();
                        char[] keypass = password;
                        if (key.isPasswordProtected()) {
                            if (keypass == null) {
                                keypass = getPasswordHolder().getPassword(certGroup.getId());
                                if (keypass == null) {
                                    throw new RuntimeException(
                                            "Password for key in group " + certGroup.getId() + " cannot be null");
                                }
                            }
                            if (getPasswordHolder().getPassword(certGroup.getId()) == null) {
                                getPasswordHolder().addPassword(certGroup.getId(),
                                        PasswordHolder.CREDENTIAL_PASSWORD, keypass);
                            }
                        } else {
                            if (password != null) {
                                throw new RuntimeException("The key for group " + certGroup.getId()
                                        + " is not protected, yet a password was supplied");
                            }
                        }
                        PrivateKey privateKey = certPrivateKeyPair.getPrivateKey();
                        if (TolvenConfigWrapper.TOLVEN_CREDENTIAL_FORMAT_PEM.equals(key.getFormat())) {
                            writePEMObject(certificate, tmpCertFile);
                            writePEMObject(keypass, privateKey, tmpKeyFile);
                        } else {
                            writeDER(certificate, tmpCertFile);
                            writeDER(keypass, privateKey, tmpKeyFile);
                        }
                        FileUtils.copyFile(tmpCertFile, certFile);
                        FileUtils.copyFile(tmpKeyFile, keyFile);
                    } catch (Exception ex) {
                        ex.printStackTrace();
                        if (certFile != null)
                            certFile.delete();
                        if (keyFile != null)
                            keyFile.delete();
                        throw new RuntimeException(
                                "Could not create key and certificate for group: " + certGroup.getId(), ex);
                    } finally {
                        if (tmpCertFile != null)
                            tmpCertFile.delete();
                        if (tmpKeyFile != null)
                            tmpKeyFile.delete();
                    }
                } else {
                    if (!(certFile.exists() && keyFile.exists()))
                        throw new RuntimeException(
                                certGroup.getId() + " is a non-commercial group with existing credentials. "
                                        + certFile.getPath() + " and " + keyFile.getPath() + " must exist");
                }
            }
        }
        CertificateKeyStoreDetail certificateKeyStoreDetail = certGroup.getKeyStore();
        if (certificateKeyStoreDetail != null && !new File(certificateKeyStoreDetail.getSource()).exists()) {
            logger.info("New keyStore will be created: " + certificateKeyStoreDetail.getId() + " at "
                    + certificateKeyStoreDetail.getSource());
            createKeyStore(certGroup, password);
        }
    }

    public void processCertificateInfo(List<CertificateGroupDetail> certGroupDetails)
            throws IOException, GeneralSecurityException {
        for (CertificateGroupDetail certGroupDetail : certGroupDetails) {
            CertificateDetail certDetail = certGroupDetail.getCertificate();
            if (certDetail != null) {
                processCredentialGroup(certGroupDetail);
            }
        }
    }

    public void processTrustStores(List<TrustStoreDetail> trustStores) {
        for (TrustStoreDetail trustStore : trustStores) {
            processTrustStore(trustStore);
        }
    }

    public void processTrustStore(String groupId) {
        TrustStoreDetail trustStoreDetail = getTolvenConfigWrapper().getTrustStoreDetail(groupId);
        if (trustStoreDetail == null) {
            throw new RuntimeException("Could not locate truststore for group: " + groupId);
        }
        processTrustStore(trustStoreDetail);
    }

    public void processTrustStore(TrustStoreDetail trustStoreDetail) {
        try {
            Set<X509Certificate> newTrustStoreCerts = new HashSet<X509Certificate>();
            Set<X509Certificate> previousTrustStoreCerts = new HashSet<X509Certificate>();
            Set<X509Certificate> resultingTrustStoreCerts = new HashSet<X509Certificate>();
            for (TrustStoreCertificateDetail trustStoreCertificateDetail : trustStoreDetail.getCertificate()) {
                CertificateGroupDetail certGroup = getTolvenConfigWrapper()
                        .getCredentialGroup(trustStoreCertificateDetail.getRefId());
                if (certGroup == null) {
                    throw new RuntimeException("The trusted group " + trustStoreCertificateDetail.getRefId()
                            + " in truststore " + trustStoreDetail.getId() + " does not exist");
                }
                X509Certificate trustStoreX509Certificate = getTolvenConfigWrapper().getX509Certificate(certGroup);
                newTrustStoreCerts.add(trustStoreX509Certificate);
            }
            File trustStoreFile = new File(trustStoreDetail.getSource());
            if (TolvenConfigWrapper.TOLVEN_CREDENTIAL_FORMAT_PEM.equals(trustStoreDetail.getFormat())) {
                if (trustStoreFile.exists()) {
                    previousTrustStoreCerts = getTolvenConfigWrapper().getX509Certificates(trustStoreFile);
                    for (X509Certificate cert : previousTrustStoreCerts) {
                        resultingTrustStoreCerts.add(cert);
                    }
                }
                // And now for what Java calls a Set intersection
                resultingTrustStoreCerts.retainAll(newTrustStoreCerts);
                if (resultingTrustStoreCerts.size() != newTrustStoreCerts.size()
                        || !resultingTrustStoreCerts.containsAll(newTrustStoreCerts)) {
                    FileOutputStream out = null;
                    try {
                        out = new FileOutputStream(trustStoreFile);
                        for (X509Certificate x509Certificate : newTrustStoreCerts) {
                            out.write(convertToPEMBytes(x509Certificate));
                        }
                    } finally {
                        if (out != null) {
                            out.close();
                        }
                    }
                    logger.info("Created truststore: " + trustStoreDetail.getId());
                }
            } else if (TolvenConfigWrapper.TOLVEN_CREDENTIAL_FORMAT_JKS.equals(trustStoreDetail.getFormat())
                    || TolvenConfigWrapper.TOLVEN_CREDENTIAL_FORMAT_PKCS12.equals(trustStoreDetail.getFormat())) {
                char[] truststorepass = getPasswordHolder().getPassword(trustStoreDetail.getId());
                if (trustStoreFile.exists()) {
                    KeyStore trustStore = getTolvenConfigWrapper().getKeyStore(truststorepass, trustStoreFile,
                            trustStoreDetail.getFormat());
                    Enumeration<String> enumeration = trustStore.aliases();
                    while (enumeration.hasMoreElements()) {
                        String alias = enumeration.nextElement();
                        X509Certificate cert = (X509Certificate) trustStore.getCertificate(alias);
                        previousTrustStoreCerts.add(cert);
                        resultingTrustStoreCerts.add(cert);
                    }
                }
                // And now for what Java calls a Set intersection
                resultingTrustStoreCerts.retainAll(newTrustStoreCerts);
                if (resultingTrustStoreCerts.size() != newTrustStoreCerts.size()
                        || !resultingTrustStoreCerts.containsAll(newTrustStoreCerts)) {
                    KeyStore trustStore = KeyStore.getInstance(trustStoreDetail.getFormat());
                    trustStore.load(null, truststorepass);
                    for (X509Certificate newCert : newTrustStoreCerts) {
                        String alias = newCert.getSubjectDN().getName();
                        trustStore.setCertificateEntry(alias, newCert);
                    }
                    trustStoreFile.getParentFile().mkdirs();
                    write(trustStore, trustStoreFile, truststorepass);
                    logger.info("Created truststore: " + trustStoreDetail.getId());
                }
            } else {
                throw new RuntimeException("Unrecognized keystore format: " + trustStoreDetail.getFormat());
            }
        } catch (Exception ex) {
            throw new RuntimeException("Failed to process truststore: " + trustStoreDetail.getId(), ex);
        }
    }

    private X509Certificate signCertificate(CertificateDetail certDetail, X500Principal subjectX500Principal,
            PublicKey subjectPublicKey) throws IOException, GeneralSecurityException {
        CertificateGroupDetail caCertGroupDetail = getTolvenConfigWrapper()
                .getCredentialGroup(certDetail.getCaRefId());
        CertificateKeyDetail caKeyDetail = caCertGroupDetail.getKey();
        char[] caKeyPass = getPasswordHolder().getPassword(caCertGroupDetail.getId());
        PrivateKey caPrivateKey = getPrivateKey(caKeyDetail, caKeyPass);
        X509Certificate caCertificate = getTolvenConfigWrapper().getX509Certificate(caCertGroupDetail);
        return signCertificate(subjectX500Principal, subjectPublicKey, caCertificate.getIssuerX500Principal(),
                caPrivateKey);
    }

    private X509Certificate signCertificate(X500Principal subjectX500Principal, PublicKey subjectPublicKey,
            X500Principal issuerX500Principal, PrivateKey issuerPrivateKey) throws GeneralSecurityException {
        X509V3CertificateGenerator gen = new X509V3CertificateGenerator();
        gen.setSignatureAlgorithm("SHA1withRSA");
        gen.setSubjectDN(subjectX500Principal);
        gen.setSerialNumber(getNextSerialNumber());
        gen.setIssuerDN(issuerX500Principal);
        gen.setNotBefore(new Date());
        gen.setNotAfter(new Date(new Date().getTime() + 1000000000000L));
        gen.setPublicKey(subjectPublicKey);
        return gen.generate(issuerPrivateKey);
    }

    private void write(KeyStore keyStore, File keyStoreFile, char[] password) {
        File file = null;
        FileOutputStream out = null;
        try {
            try {
                keyStoreFile.getParentFile().mkdirs();
                out = new FileOutputStream(keyStoreFile);
                keyStore.store(out, password);
            } catch (Exception ex) {
                if (file != null) {
                    file.delete();
                }
                throw new RuntimeException("Could not create keystore file: " + keyStoreFile.getPath(), ex);
            } finally {
                if (out != null)
                    out.close();
            }
        } catch (IOException ex) {
            throw new RuntimeException("Could not store keystore file " + keyStoreFile.getPath(), ex);
        }

    }

    private void write(PrivateKey privateKey, String privateKeyFormat, File file, char[] password)
            throws IOException, GeneralSecurityException {
        if (TolvenConfigWrapper.TOLVEN_CREDENTIAL_FORMAT_PEM.equals(privateKeyFormat)) {
            writePEMObject(password, privateKey, file);
        } else {
            writeDER(password, privateKey, file);
        }
    }

    private void writeDER(char[] password, PrivateKey privateKey, File file)
            throws IOException, GeneralSecurityException {
        byte[] bytes = null;
        if (password == null) {
            bytes = privateKey.getEncoded();
        } else {
            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
            PBEKeySpec passwordSpec = new PBEKeySpec(password);
            SecretKey secretKey = secretKeyFactory.generateSecret(passwordSpec);
            Cipher cipher = Cipher.getInstance(secretKey.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            byte[] encryptedPrivateKey = cipher.doFinal(privateKey.getEncoded());
            EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(cipher.getParameters(),
                    encryptedPrivateKey);
            bytes = encryptedPrivateKeyInfo.getEncoded();
        }
        FileUtils.writeByteArrayToFile(file, bytes);
    }

    private void writeDER(X509Certificate certificate, File file) throws IOException, GeneralSecurityException {
        FileUtils.writeByteArrayToFile(file, certificate.getEncoded());
    }

    private void writePEMObject(char[] password, Object object, File file) {
        file.getParentFile().mkdirs();
        FileWriter fileWriter = null;
        PEMWriter pemWriter = null;
        try {
            try {
                fileWriter = new FileWriter(file);
                pemWriter = new PEMWriter(fileWriter, "BC");
                if (password == null) {
                    pemWriter.writeObject(object);
                } else {
                    pemWriter.writeObject(object, "DESEDE", password, getSecureRandom());
                }
            } finally {
                if (pemWriter != null)
                    pemWriter.close();
                if (fileWriter != null)
                    fileWriter.close();
            }
        } catch (IOException ex) {
            new RuntimeException("Failed to write pem to file: " + file.getPath(), ex);
        }
    }

    private void writePEMObject(Object object, File file) {
        writePEMObject(null, object, file);
    }

    private byte[] toByteArray(KeyStore keyStore, char[] password) throws IOException, GeneralSecurityException {
        ByteArrayOutputStream baos = null;
        try {
            baos = new ByteArrayOutputStream();
            keyStore.store(baos, password);
            byte[] byteArr = baos.toByteArray();
            return byteArr;
        } finally {
            if (baos != null)
                baos.close();
        }
    }

    /**
     * Given an adminGroupId and an adminPassword, create the admin credentials, if they do not already exist. Attempt to load the password store.
     * 
     * @param adminGroupId
     * @param adminPassword
     * @throws Exception
     */
    public void processAdminPassword(String adminGroupId, char[] adminPassword) throws Exception {
        if (!getTolvenConfigWrapper().getAdminKeyStoreFile().exists()) {
            CertificateGroupDetail certGroup = getTolvenConfigWrapper().getCredentialGroup(adminGroupId);
            processCredentialGroup(certGroup, adminPassword);
        }
        getTolvenConfigWrapper().loadPasswordHolder(adminGroupId, adminPassword);
    }

    /**
     * During generation of a PrivateKey and Certificate pair, both components are often created simultaneously, so
     * this class is a wrapper which allows both to be returned together
     * @author Joseph Isaac
     *
     */
    private class X509CertificatePrivateKeyPair {

        private PrivateKey privateKey;
        private X509Certificate certificate;

        public X509CertificatePrivateKeyPair(X509Certificate certificate, PrivateKey privateKey) {
            this.certificate = certificate;
            this.privateKey = privateKey;
        }

        public PrivateKey getPrivateKey() {
            return privateKey;
        }

        public X509Certificate getX509Certificate() {
            return certificate;
        }

        public byte[] getX509CertificateByteArray() throws GeneralSecurityException {
            return getX509Certificate().getEncoded();
        }

        public KeyStore createPKCS12KeyStore(String alias, char[] password)
                throws GeneralSecurityException, IOException {
            // We can't use Bouncy Castle here because it requires unrestricted strength
            // which Tolven does not require (although it is allowed). 
            KeyStore keyStore = KeyStore.getInstance(TolvenConfigWrapper.TOLVEN_CREDENTIAL_FORMAT_PKCS12);
            keyStore.load(null, password);
            X509Certificate[] certificateChain = new X509Certificate[1];
            certificateChain[0] = getX509Certificate();
            keyStore.setKeyEntry(alias, getPrivateKey(), password, certificateChain);
            return keyStore;
        }

        public byte[] createPKCS12KeyStoreByteArray(String alias, char[] password)
                throws GeneralSecurityException, IOException {
            KeyStore keyStore = createPKCS12KeyStore(alias, password);
            return toByteArray(keyStore, password);
        }

    }

}