mitm.common.security.KeyAndCertStoreImpl.java Source code

Java tutorial

Introduction

Here is the source code for mitm.common.security.KeyAndCertStoreImpl.java

Source

/*
 * Copyright (c) 2008-2012, Martijn Brinkers, Djigzo.
 * 
 * This file is part of Djigzo email encryption.
 *
 * Djigzo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License 
 * version 3, 19 November 2007 as published by the Free Software 
 * Foundation.
 *
 * Djigzo 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public 
 * License along with Djigzo. If not, see <http://www.gnu.org/licenses/>
 *
 * Additional permission under GNU AGPL version 3 section 7
 * 
 * If you modify this Program, or any covered work, by linking or 
 * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, 
 * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, 
 * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, 
 * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, 
 * wsdl4j-1.6.1.jar (or modified versions of these libraries), 
 * containing parts covered by the terms of Eclipse Public License, 
 * tyrex license, freemarker license, dom4j license, mx4j license,
 * Spice Software License, Common Development and Distribution License
 * (CDDL), Common Public License (CPL) the licensors of this Program grant 
 * you additional permission to convey the resulting work.
 */
package mitm.common.security;

import java.io.IOException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertSelector;
import java.security.cert.CertStoreException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

import mitm.common.security.certificate.X509CertificateInspector;
import mitm.common.security.certstore.CertificateAlreadyExistsException;
import mitm.common.security.certstore.Expired;
import mitm.common.security.certstore.Match;
import mitm.common.security.certstore.MissingKeyAlias;
import mitm.common.security.certstore.X509CertStoreEntry;
import mitm.common.security.certstore.X509CertStoreExt;
import mitm.common.security.certstore.X509StoreEventListener;
import mitm.common.util.Check;
import mitm.common.util.CloseableIterator;
import mitm.common.util.CloseableIteratorException;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class KeyAndCertStoreImpl implements KeyAndCertStore {
    private static Logger logger = LoggerFactory.getLogger(KeyAndCertStoreImpl.class);

    private final X509CertStoreExt certStore;
    private final KeyStore keyStore;
    private final String keyEntryPassword;

    public KeyAndCertStoreImpl(X509CertStoreExt certStore, KeyStore keyStore, String keyEntryPassword) {
        Check.notNull(certStore, "certStore");

        this.certStore = certStore;
        this.keyStore = keyStore;
        this.keyEntryPassword = keyEntryPassword;
    }

    /**
     * @see X509CertStoreExt.getByEmail
     */
    @Override
    public CloseableIterator<? extends X509CertStoreEntry> getByEmail(String email, Match match, Expired expired,
            MissingKeyAlias missingKeyAlias) throws CertStoreException {
        return certStore.getByEmail(email, match, expired, missingKeyAlias);
    }

    /**
     * @see X509CertStoreExt.getByEmail
     */
    @Override
    public CloseableIterator<? extends X509CertStoreEntry> getByEmail(String email, Match match, Expired expired,
            MissingKeyAlias missingKeyAlias, Integer firstResult, Integer maxResults) throws CertStoreException {
        return certStore.getByEmail(email, match, expired, missingKeyAlias, firstResult, maxResults);
    }

    /**
     * @see X509CertStoreExt.getByEmailCount
     */
    @Override
    public long getByEmailCount(String email, Match match, Expired expired, MissingKeyAlias missingKeyAlias)
            throws CertStoreException {
        return certStore.getByEmailCount(email, match, expired, missingKeyAlias);
    }

    /**
     * @see X509CertStoreExt.searchBySubject
     */
    @Override
    public CloseableIterator<? extends X509CertStoreEntry> searchBySubject(String subject, Expired expired,
            MissingKeyAlias missingKeyAlias, Integer firstResult, Integer maxResults) throws CertStoreException {
        return certStore.searchBySubject(subject, expired, missingKeyAlias, firstResult, maxResults);
    }

    /**
     * @see X509CertStoreExt.getSearchBySubjectCount
     */
    @Override
    public long getSearchBySubjectCount(String subject, Expired expired, MissingKeyAlias missingKeyAlias)
            throws CertStoreException {
        return certStore.getSearchBySubjectCount(subject, expired, missingKeyAlias);
    }

    /**
     * @see X509CertStoreExt.searchByIssuer
     */
    @Override
    public CloseableIterator<? extends X509CertStoreEntry> searchByIssuer(String issuer, Expired expired,
            MissingKeyAlias missingKeyAlias, Integer firstResult, Integer maxResults) throws CertStoreException {
        return certStore.searchByIssuer(issuer, expired, missingKeyAlias, firstResult, maxResults);
    }

    /**
     * @see X509CertStoreExt.getSearchByIssuerCount
     */
    @Override
    public long getSearchByIssuerCount(String issuer, Expired expired, MissingKeyAlias missingKeyAlias)
            throws CertStoreException {
        return certStore.getSearchByIssuerCount(issuer, expired, missingKeyAlias);
    }

    /**
     * @see X509CertStoreExt.getByThumbprint
     */
    @Override
    public X509CertStoreEntry getByThumbprint(String thumbprint) throws CertStoreException {
        return certStore.getByThumbprint(thumbprint);
    }

    /**
     * @see X509CertStoreExt.getByCertificate
     */
    @Override
    public X509CertStoreEntry getByCertificate(X509Certificate certificate) throws CertStoreException {
        return certStore.getByCertificate(certificate);
    }

    /**
     * @see X509CertStoreExt.contains
     */
    @Override
    public boolean contains(X509Certificate certificate) throws CertStoreException {
        return certStore.contains(certificate);
    }

    /**
     * @see X509CertStoreExt.getLatest
     */
    @Override
    public X509CertStoreEntry getLatest() {
        return certStore.getLatest();
    }

    /**
     * @see X509CertStoreExt.size
     */
    @Override
    public long size() {
        return certStore.size();
    }

    /**
     * @see X509CertStoreExt.size
     */
    @Override
    public long size(Expired expired, MissingKeyAlias missingKeyAlias) {
        return certStore.size(expired, missingKeyAlias);
    }

    /**
     * @see X509CertStoreExt.addCertificate
     */
    @Override
    public X509CertStoreEntry addCertificate(X509Certificate certificate, String keyAlias)
            throws CertStoreException, CertificateAlreadyExistsException {
        return certStore.addCertificate(certificate, keyAlias);
    }

    /**
     * @see X509CertStoreExt.addCertificate
     */
    @Override
    public X509CertStoreEntry addCertificate(X509Certificate certificate)
            throws CertStoreException, CertificateAlreadyExistsException {
        return certStore.addCertificate(certificate);
    }

    /**
     * Removes the certificate and if the certificate has an associated key entry an attempt is made to remove the
     * associated key (note that not all KeyStores allow keys to be removed) 
     */
    @Override
    public void removeCertificate(X509Certificate certificate) throws CertStoreException {
        if (logger.isDebugEnabled()) {
            logger.debug("Removing certificate: {}", certificate);
        }

        X509CertStoreEntry entry = getByCertificate(certificate);

        if (entry == null) {
            throw new CertStoreException("CertStore entry not found.");
        }

        if (keyStore != null && entry.getKeyAlias() != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Deleting associated key with alias: {}", entry.getKeyAlias());
            }

            /* 
             * try to remove the associated key from the keyStore 
             */
            try {
                keyStore.deleteEntry(entry.getKeyAlias());
            } catch (KeyStoreException e) {
                logger.error("Unable to remove associated key for certificate: " + certificate, e);
            }
        }

        certStore.removeCertificate(certificate);
    }

    /**
     * Removes all certificate and key entries.
     */
    @Override
    public void removeAllEntries() throws CertStoreException {
        /* remove all entries from the certificate store */
        certStore.removeAllEntries();

        /* remove all entries from the KeyStore */
        if (keyStore != null) {
            try {
                Enumeration<String> aliases = keyStore.aliases();

                while (aliases.hasMoreElements()) {
                    String alias = aliases.nextElement();

                    keyStore.deleteEntry(alias);
                }
            } catch (KeyStoreException e) {
                throw new CertStoreException(e);
            }
        }
    }

    /**
     * @see X509CertStoreExt.getCertStoreIterator
     */
    @Override
    public CloseableIterator<? extends X509CertStoreEntry> getCertStoreIterator(CertSelector certSelector,
            MissingKeyAlias missingKeyAlias, Integer firstResult, Integer maxResults) throws CertStoreException {
        return certStore.getCertStoreIterator(certSelector, missingKeyAlias, firstResult, maxResults);
    }

    /**
     * @see X509CertStoreExt.getCertificates
     */
    @Override
    public Collection<X509Certificate> getCertificates(CertSelector certSelector) throws CertStoreException {
        return certStore.getCertificates(certSelector);
    }

    /**
     * @see X509CertStoreExt.getCertificateIterator
     */
    @Override
    public CloseableIterator<X509Certificate> getCertificateIterator(CertSelector certSelector)
            throws CertStoreException {
        return certStore.getCertificateIterator(certSelector);
    }

    /**
     * Sets the listener that listens for store events
     */
    @Override
    public void setStoreEventListener(X509StoreEventListener eventListener) {
        certStore.setStoreEventListener(eventListener);
    }

    /**
     * @See KeyAndCertStore.addKeyAndCertificate
     */
    @Override
    public boolean addKeyAndCertificate(KeyAndCertificate keyAndCertificate)
            throws CertStoreException, KeyStoreException {
        Check.notNull(keyAndCertificate, "keyAndCertificate");

        X509Certificate certificate = keyAndCertificate.getCertificate();

        PrivateKey privateKey = keyAndCertificate.getPrivateKey();

        Check.notNull(certificate, "certificate");

        if (logger.isDebugEnabled()) {
            logger.debug("Adding KeyAndCertificate: {}", certificate);
        }

        String keyAlias = null;

        if (keyStore != null && privateKey != null) {
            try {
                /* we set the key alias to the thumbprint of the associated certificate */
                keyAlias = X509CertificateInspector.getThumbprint(certificate);
            } catch (CertificateEncodingException e) {
                throw new CertStoreException(e);
            } catch (NoSuchAlgorithmException e) {
                throw new CertStoreException(e);
            } catch (NoSuchProviderException e) {
                throw new CertStoreException(e);
            }

            if (logger.isDebugEnabled()) {
                logger.debug("Setting Key entry with alias: {}", keyAlias);
            }

            keyStore.setKeyEntry(keyAlias, privateKey, getPassword(), new Certificate[] { certificate });
        }

        X509CertStoreEntry certStoreEntry = certStore.getByCertificate(keyAndCertificate.getCertificate());

        boolean added = false;

        if (certStoreEntry == null) {
            certStore.addCertificate(certificate, keyAlias);

            added = true;
        } else {
            /*
             * Only set the key alias if the key alias is set and never set the key alias to null if the new
             * key alias is null. If we would set the key alias to null for an existing cert store entry, it 
             * might happen that when we re-import a certificate but now without a key (for example from a 
             * p7b), the key alias will be set to null which is not want we want. Why? suppose you first
             * import a pfx file with certificates and keys and then import a p7b file containing a large
             * number of certificates. Now if the p7b file contains a certificate that was also stored in
             * the pfx together with the private key, if we would have set the key alias to null when we
             * imported the p7b, we no longer have the associated private key. The private key is still 
             * there but detached from it's certificate.
             */
            if (keyAlias != null) {
                /*
                 * Only set if the key alias is changing
                 */
                if (!keyAlias.equals(certStoreEntry.getKeyAlias())) {
                    certStoreEntry.setKeyAlias(keyAlias);

                    added = true;
                }
            }
        }

        return added;
    }

    /**
     * @See KeyAndCertStore.getKeyAndCertificate
     */
    @Override
    public KeyAndCertificate getKeyAndCertificate(X509CertStoreEntry certStoreEntry)
            throws CertStoreException, KeyStoreException {
        KeyAndCertificate keyAndCertificate = null;

        if (certStoreEntry != null) {
            PrivateKey privateKey;

            try {
                privateKey = getPrivateKey(certStoreEntry);
            } catch (UnrecoverableKeyException e) {
                throw new KeyStoreException(e);
            } catch (NoSuchAlgorithmException e) {
                throw new KeyStoreException(e);
            }

            keyAndCertificate = new KeyAndCertificateImpl(privateKey, certStoreEntry.getCertificate());
        }

        return keyAndCertificate;

    }

    /**
     * @See KeyAndCertStore.getMatchingKeys
     */
    @Override
    public Collection<? extends PrivateKey> getMatchingKeys(KeyIdentifier keyIdentifier) throws KeyStoreException {
        return getMatchingKeys(keyIdentifier, null, null);
    }

    /**
     * @See KeyAndCertStore.getMatchingKeys
     */
    @Override
    public Collection<? extends PrivateKey> getMatchingKeys(KeyIdentifier keyIdentifier, Integer firstResult,
            Integer maxResults) throws KeyStoreException {
        Set<PrivateKey> keys = new HashSet<PrivateKey>();

        if (keyIdentifier == null) {
            logger.debug("null keyIdentifier.");

            return keys;
        }

        CertSelector certSelector = null;

        try {
            if (keyIdentifier instanceof CertSelectorKeyIdentifier) {
                CertSelectorKeyIdentifier certSelectorKeyIdentifier = (CertSelectorKeyIdentifier) keyIdentifier;

                certSelector = certSelectorKeyIdentifier.getSelector();
            } else {
                logger.warn("Unsupported KeyIdentifier '" + keyIdentifier.getClass().getCanonicalName() + "'");
            }

            /* only get entries with for which the key alias is set */
            CloseableIterator<? extends X509CertStoreEntry> certStoreIterator = certStore
                    .getCertStoreIterator(certSelector, MissingKeyAlias.NOT_ALLOWED, firstResult, maxResults);

            try {
                while (certStoreIterator.hasNext()) {
                    X509CertStoreEntry certStoreEntry = certStoreIterator.next();

                    PrivateKey privateKey = getPrivateKey(certStoreEntry);

                    if (privateKey != null) {
                        keys.add(privateKey);
                    }
                }
            } finally {
                certStoreIterator.close();
            }
        } catch (IOException e) {
            throw new KeyStoreException(e);
        } catch (CertStoreException e) {
            throw new KeyStoreException(e);
        } catch (CloseableIteratorException e) {
            throw new KeyStoreException(e);
        } catch (NoSuchAlgorithmException e) {
            throw new KeyStoreException(e);
        } catch (UnrecoverableKeyException e) {
            throw new KeyStoreException(e);
        }

        return keys;
    }

    /**
     * @See KeyAndCertStore.sync
     */
    @Override
    public void sync(SyncMode syncMode) throws KeyStoreException, CertStoreException {
        if (keyStore == null) {
            logger.warn("keyStore is null.");

            return;
        }

        if (syncMode == SyncMode.CERT_STORE || syncMode == SyncMode.ALL) {
            logger.info("Syncing Key store --> Certificate store");

            Enumeration<String> aliases = keyStore.aliases();

            /* 
             * sync keyStore by getting the certificates and keys from the keyStore and placing it in the certStore.
             */
            while (aliases.hasMoreElements()) {
                String alias = aliases.nextElement();

                Certificate certificate = keyStore.getCertificate(alias);

                if (!(certificate instanceof X509Certificate)) {
                    /*
                     * only X509Certificates are supported. If the key entry does not have an
                     * associated certificate we will not import the key because the key
                     * would not be used.
                     */
                    continue;
                }

                X509Certificate x509Certificate = (X509Certificate) certificate;

                try {
                    String keyAlias = null;

                    if (keyStore.isKeyEntry(alias)) {
                        keyAlias = alias;
                    }

                    X509CertStoreEntry certEntry = getByCertificate(x509Certificate);

                    if (certEntry != null) {
                        /*
                         * Certificate already exists. Set key alias if different
                         */
                        if (!StringUtils.equals(certEntry.getKeyAlias(), keyAlias)) {
                            logger.info("Certificate with thumbprint {} already exist. Setting new key alias: {}",
                                    X509CertificateInspector.getThumbprint(certificate), keyAlias);

                            certEntry.setKeyAlias(keyAlias);
                        }
                    } else {
                        logger.info("Adding certificate with thumbprint {} and key alias: {}",
                                X509CertificateInspector.getThumbprint(certificate), keyAlias);

                        addCertificate(x509Certificate, keyAlias);
                    }
                } catch (Exception e) {
                    logger.error("Error syncing the item with alias: " + alias, e);
                }
            }
        }

        if (syncMode == SyncMode.KEY_STORE || syncMode == SyncMode.ALL) {
            logger.info("Syncing Certificate store --> Key store");

            /* 
             * synchronize certStore entries to make sure that all key aliases references valid keys. If a key alias 
             * references a key that is no longer available the key entry will be set to null.
             * 
             * Warning: if a removable keyStore (like smartcard) is used and the keyStore is temporarily unavailable
             * synchronizing will result in the deassociation of the key with the certificate.
             */
            CloseableIterator<? extends X509CertStoreEntry> certStoreIterator = certStore.getCertStoreIterator(null,
                    MissingKeyAlias.NOT_ALLOWED, null, null);

            try {
                try {
                    while (certStoreIterator.hasNext()) {
                        X509CertStoreEntry certStoreEntry = certStoreIterator.next();

                        String keyAlias = certStoreEntry.getKeyAlias();

                        if (keyAlias != null) {
                            if (!keyStore.isKeyEntry(keyAlias)) {
                                logger.warn("Key with alias {} cannot be found. Removing key alias.", keyAlias);

                                certStoreEntry.setKeyAlias(null);
                            }
                        }
                    }
                } finally {
                    certStoreIterator.close();
                }
            } catch (CloseableIteratorException e) {
                throw new CertStoreException(e);
            }
        }

        logger.info("Syncing finished");
    }

    private PrivateKey getPrivateKey(X509CertStoreEntry certStoreEntry)
            throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException {
        PrivateKey privateKey = null;

        if (keyStore == null) {
            logger.warn("keyStore is null.");

            return null;
        }

        if (certStoreEntry != null) {
            String keyAlias = certStoreEntry.getKeyAlias();

            if (keyAlias != null) {
                Key key = keyStore.getKey(keyAlias, getPassword());

                if (key instanceof PrivateKey) {
                    privateKey = (PrivateKey) key;
                } else {
                    if (key != null) {
                        logger.warn("Key with alias '" + keyAlias + "' is not a private key. Skipping this key.");
                    } else {
                        logger.warn("Key with alias '" + keyAlias + "' does not exist. Skipping this key.");
                    }
                }
            } else {
                logger.debug("keyAlias is null.");
            }
        }

        return privateKey;
    }

    private char[] getPassword() {
        return (keyEntryPassword != null ? keyEntryPassword.toCharArray() : null);
    }
}