All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cesecore.keys.token.CachingKeyStoreWrapper Maven / Gradle / Ivy

/*************************************************************************
 *                                                                       *
 *  CESeCore: CE Security Core                                           *
 *                                                                       *
 *  This software 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 any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/
package org.cesecore.keys.token;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStore.Entry;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStore.ProtectionParameter;
import java.security.KeyStore.SecretKeyEntry;
import java.security.KeyStore.TrustedCertificateEntry;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Vector;
import java.util.concurrent.locks.ReentrantLock;

import javax.crypto.SecretKey;

import org.apache.log4j.Logger;

/**
 * Wrapper of a Java KeyStore to speed up key operations by caching key references.
 * 
 * Aliases and certificates are read when the class is initiated from the underlying key store.
 * PrivateKey and SecretKey objects are read and cached when the key specific protection is available (when used).
 * 
 * @version $Id: CachingKeyStoreWrapper.java 22480 2015-12-17 21:23:11Z primelars $
 */
public class CachingKeyStoreWrapper {
    /** Similar to Java's KeyStore.Entry */
    private static final Logger log = Logger.getLogger(CachingKeyStoreWrapper.class);
    private final ReentrantLock updateLock = new ReentrantLock(false);
    private final KeyStore keyStore;
    private final KeyStoreCache keyStoreCache;
    private class KeyStoreMapEntry {
        public final Key key;
        public final Certificate[] certificateChain;
        public final boolean isTrusted;
        public KeyStoreMapEntry(final String alias, final KeyStore keyStore) throws KeyStoreException {
            if (keyStore.isCertificateEntry(alias)) {
                // See if there is a TrustedCertificateEntry instead
                final Certificate certificate = keyStore.getCertificate(alias);
                this.certificateChain = new Certificate[] { certificate };
                this.key = null;
                this.isTrusted = true;
                return;
            }
            this.isTrusted = false;
            this.certificateChain = keyStore.getCertificateChain(alias);
            Key tmpKey;
            try {
                tmpKey = keyStore.getKey(alias, null);
            } catch (KeyStoreException e) {
                throw e;
            } catch (Exception e) {
                tmpKey = null;
            }
            this.key = tmpKey;
        }
        public KeyStoreMapEntry(
                final String alias, final KeyStore keyStore, final char password[],
                final KeyStoreMapEntry oldEntry)
                        throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException {
            assert !oldEntry.isTrusted;
            this.isTrusted = false;
            this.certificateChain = oldEntry.certificateChain;
            this.key = keyStore.getKey(alias, password);
        }
        public KeyStoreMapEntry( final Certificate certificate ) {
            this.key = null;
            this.isTrusted = true;
            this.certificateChain = new Certificate[] { certificate };
        }
        public KeyStoreMapEntry( final Certificate[] chain, final Key k ) {
            this.key = k;
            this.isTrusted = false;
            this.certificateChain = chain;
        }
        public KeyStoreMapEntry(
                final String alias, final ProtectionParameter protection,
                final KeyStore keyStore )
                        throws NoSuchAlgorithmException, UnrecoverableEntryException, KeyStoreException {
            this(keyStore.getEntry(alias, protection));
        }
        public KeyStoreMapEntry( final Entry entry ) {
            if ( entry instanceof PrivateKeyEntry ) {
                this.key = ((PrivateKeyEntry) entry).getPrivateKey();
                this.certificateChain = ((PrivateKeyEntry) entry).getCertificateChain();
                this.isTrusted = false;
                return;
            }
            if ( entry instanceof SecretKeyEntry ) {
                this.key =((SecretKeyEntry) entry).getSecretKey();
                this.certificateChain = null;
                this.isTrusted = false;
                return;
            }
            if ( entry instanceof TrustedCertificateEntry ) {
                this.key = null;
                this.certificateChain = new Certificate[] { ((TrustedCertificateEntry) entry).getTrustedCertificate() };
                this.isTrusted = true;
                return;
            }
            throw new Error("It should not be possible to reach this point!");
        }
        public Entry getEntry() {
            if ( this.isTrusted  ) {
                assert this.certificateChain!=null;
                // No constructor puts more than one certificate in the chain when trusted.
                assert this.certificateChain.length==1;
                return new TrustedCertificateEntry(this.certificateChain[0]);
            }
            assert this.key!=null;
            if ( this.certificateChain!=null ) {
                return new PrivateKeyEntry((PrivateKey) this.key, this.certificateChain);
            }
            return new SecretKeyEntry((SecretKey) this.key);
        }
    }

    private class KeyStoreCache {
        private HashMap cache;

        @SuppressWarnings("synthetic-access")
        public KeyStoreCache(final KeyStore keyStore) throws KeyStoreException {
            this.cache = new HashMap<>();
            // Load the whole public KeyStore content (aliases and certificate) into the cache
            final Enumeration aliases = keyStore.aliases();
            while (aliases.hasMoreElements()) {
                final String alias = aliases.nextElement();
                this.cache.put(fixBadUTF8(alias), new KeyStoreMapEntry(alias, keyStore));
                if (log.isDebugEnabled()) {
                    log.debug("KeyStore has alias: " + alias);
                }
            }
        }
        public void addEntry(final String alias, final  KeyStoreMapEntry newEntry) {
            final HashMap clone = new HashMap<>(this.cache);
            clone.put(alias, newEntry);
            this.cache = clone;
        }
        public void removeEntry(final String alias) {
            final HashMap clone = new HashMap<>(this.cache);
            clone.remove(alias);
            this.cache = clone;
        }
        public KeyStoreMapEntry get(final String alias) {
            return this.cache.get(alias);
        }
        public Enumeration getAliases() {
            return new Vector<>(this.cache.keySet()).elements();
        }
    }

    private static boolean isSunP11( final KeyStore keyStore ) {
        return keyStore.getProvider().getName().indexOf("SunPKCS11")==0;
    }

    /**
     * The Sun p11 implementation of the {@link KeyStore} returns aliases that
     * are badly encoded. This method fix this encoding.
     * @param orig a badly encoded alias.
     * @return a correct encoded alias.
     */
    private String fixBadUTF8(final String orig) {
        if ( !isSunP11(this.keyStore) ) {
            return orig;
        }
        try {
            final byte bvIn[] = orig.getBytes("UTF-16BE");
            final byte bvOut[] = new byte[bvIn.length/2];
            for ( int i=1; i aliases() throws KeyStoreException {
        if (this.keyStoreCache==null) {
            return this.keyStore.aliases();
        }
        return this.keyStoreCache.getAliases();
    }

    /** @see java.security.KeyStore#store(OutputStream, char[]) */
    public void store(final OutputStream outputStream, final char[] password) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
        this.keyStore.store(outputStream, password);
    }

    /** @see java.security.KeyStore#setKeyEntry(String, Key, char[], Certificate[]) */
    public void setKeyEntry(final String alias, final Key key, final char[] password, final Certificate[] chain) throws KeyStoreException {
        this.keyStore.setKeyEntry(alias, key, password, chain);
        if (this.keyStoreCache==null) {
            return;
        }
        this.updateLock.lock();
        try {
            final KeyStoreMapEntry keyStoreMapEntry = new KeyStoreMapEntry(chain,key);
            this.keyStoreCache.addEntry(alias, keyStoreMapEntry);
        } finally {
            this.updateLock.unlock();
        }
        if (log.isDebugEnabled()) {
            log.debug("Updated key entry in cache for alias: " + alias);
        }
    }

    /** @see java.security.KeyStore#deleteEntry(String) */
    public void deleteEntry(final String alias) throws KeyStoreException {
        this.keyStore.deleteEntry(makeBadUTF8(alias));
        if (this.keyStoreCache==null) {
            return;
        }
        this.updateLock.lock();
        try {
            this.keyStoreCache.removeEntry(alias);
        } finally {
            this.updateLock.unlock();
        }
        if (log.isDebugEnabled()) {
            log.debug("Removed entry from cache for alias: " + alias);
        }
    }

    /** @see java.security.KeyStore#getEntry(String, ProtectionParameter) */
    public Entry getEntry(final String alias, final ProtectionParameter protParam) throws NoSuchAlgorithmException, UnrecoverableEntryException, KeyStoreException {
        if (this.keyStoreCache==null) {
            return this.keyStore.getEntry(alias, protParam);
        }
        {
            final KeyStoreMapEntry keyStoreMapEntry = this.keyStoreCache.get(alias);
            if (keyStoreMapEntry==null) {
                // If the alias exists it has a KeyStoreMapEntry. No need to query the key store here.
                return null;
            }
            if ( keyStoreMapEntry.isTrusted || keyStoreMapEntry.key!=null ) {
                return keyStoreMapEntry.getEntry();
            }
        }
        this.updateLock.lock();
        try {
            // Check if another thread has retrieved the key while we waited for the lock
            final KeyStoreMapEntry afterWaitEntry = this.keyStoreCache.get(alias);
            if ( afterWaitEntry.isTrusted || afterWaitEntry.key!=null ) {
                return afterWaitEntry.getEntry();
            }
            final KeyStoreMapEntry newEntry = new KeyStoreMapEntry(alias, protParam, this.keyStore);
            this.keyStoreCache.addEntry(alias, newEntry);
            return newEntry.getEntry();
        } finally {
            this.updateLock.unlock();
        }
    }

    /** @see java.security.KeyStore#setEntry(String, Entry, ProtectionParameter) */
    public void setEntry(final String alias, final Entry entry, final ProtectionParameter protParam) throws KeyStoreException {
        this.keyStore.setEntry(alias, entry, protParam);
        if (this.keyStoreCache==null) {
            return;
        }
        this.updateLock.lock();
        try {
            final KeyStoreMapEntry keyStoreMapEntry = new KeyStoreMapEntry(entry);
            this.keyStoreCache.addEntry(alias, keyStoreMapEntry);
        } finally {
            this.updateLock.unlock();
        }
    }

    /** @see java.security.KeyStore#getProvider() */
    public Provider getProvider() {
        return this.keyStore.getProvider();
    }

    /** @see java.security.KeyStore#getKey(String, char[]) */
    public Key getKey(final String alias, final char[] password) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException {
        if (this.keyStoreCache==null) {
            return this.keyStore.getKey(alias, password);
        }
        {
            final KeyStoreMapEntry keyStoreMapEntry = this.keyStoreCache.get(alias);
            if (keyStoreMapEntry==null) {
                // If the alias exists it has a KeyStoreMapEntry. No need to query the key store here.
                return null;
            }
            if ( keyStoreMapEntry.isTrusted || keyStoreMapEntry.key!=null ) {
                // There is no key if it is a trusted Entry.
                return keyStoreMapEntry.key;
            }
        }
        this.updateLock.lock();
        try {
            // Check if another thread has retrieved the key while we waited for the lock
            final KeyStoreMapEntry entryAfterWait = this.keyStoreCache.get(alias);
            if ( entryAfterWait.isTrusted || entryAfterWait.key!=null ) {
                return entryAfterWait.key;
            }
            final KeyStoreMapEntry newEntry = new KeyStoreMapEntry(alias, this.keyStore, password, entryAfterWait);
            this.keyStoreCache.addEntry(alias, newEntry);
            if (log.isDebugEnabled()) {
                log.debug("Caching key for alias: " + alias);
            }
            return newEntry.key;
        } finally {
            this.updateLock.unlock();
        }
    }

    /** @see java.security.KeyStore#isKeyEntry(String) */
    public boolean isKeyEntry(final String alias ) throws KeyStoreException {
        if (this.keyStoreCache==null) {
            return this.keyStore.isKeyEntry(alias);
        }
        final KeyStoreMapEntry keyStoreMapEntry = this.keyStoreCache.get(alias);
        return keyStoreMapEntry!=null && !keyStoreMapEntry.isTrusted;
    }

    /** @see java.security.KeyStore#getCertificateChain(String) */
    public Certificate[] getCertificateChain(String alias) throws KeyStoreException {
        if (this.keyStoreCache==null) {
            return this.keyStore.getCertificateChain(alias);
        }
        final KeyStoreMapEntry entry = this.keyStoreCache.get(alias);
        return entry!=null ? entry.certificateChain : null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy