Package org.ejbca.core.protocol.ocsp.standalonesession

Source Code of org.ejbca.core.protocol.ocsp.standalonesession.SigningEntityContainer$Mutex

/*************************************************************************
*                                                                       *
*  EJBCA: The OpenSource Certificate Authority                          *
*                                                                       *
*  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.ejbca.core.protocol.ocsp.standalonesession;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.KeyStore.PasswordProtection;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.log4j.Logger;
import org.ejbca.config.OcspConfiguration;
import org.ejbca.core.ejb.ca.store.CertificateStatus;
import org.ejbca.core.model.InternalResources;
import org.ejbca.core.model.log.Admin;
import org.ejbca.core.protocol.certificatestore.HashID;
import org.ejbca.ui.web.protocol.OCSPServletStandAlone;
import org.ejbca.util.CertTools;
import org.ejbca.util.keystore.KeyTools;

/**
* Holds a {@link SigningEntity} for each CA that the responder is capable of signing a response for.
*
* @author primelars
* @version  $Id: SigningEntityContainer.java 11143 2011-01-11 15:32:31Z jeklund $
*/
class  SigningEntityContainer {
    /**
     * Log object.
     */
    static private final Logger m_log = Logger.getLogger(SigningEntityContainer.class);
    /**
     * Internal localization of logs and errors
     */
    static private final InternalResources intres = InternalResources.getInstance();
    /**
     * The data of the session.
     */
    private final SessionData sessionData;
    /**
     * @param standAloneSession
     */
    SigningEntityContainer(SessionData _sessionData) {
        this.sessionData = _sessionData;
    }
    /**
     * Mapping of {@link SigningEntity} to EJBCA CA ID.
     */
    private Map<Integer, SigningEntity> signEntityMap;
    /**
     * Mutex used to assure that only one thread is executing a part of the code at a time.
     */
    private Mutex mutex = new Mutex();
    /**
     * Flag telling if the {@link #signEntityMap} is beeing updated.
     */
    private boolean updating = false;
    /**
     * Refernece of the object that handles all card OCSP signing keys.
     */
    private CardKeys cardKeys;
    /**
     * Last try of key reload.
     */
    private long lastTryOfKeyReload;
    /**
     * The implementation of the mutex.
     */
    private class Mutex {
        /**
         * This flag is true when this mutex is owned by a thread
         */
        private boolean locked;
        /**
         * Construct a mutex initially not owned by any thread,
         */
        Mutex() {
            super();
            this.locked = false;
            m_log.debug("mutex created.");
        }
        /**
         * Get ownership of the mutex.
         */
        synchronized void getMutex() {
            while( this.locked ) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    throw new Error(e);
                }
            }
            this.locked = true;
        }
        /**
         * Give away the mutex.
         */
        synchronized void releaseMutex() {
            this.locked = false;
            this.notify();
        }
    }
    /**
     * Test if all criterias for key loading is fulfilled.
     * If it is {@link #loadPrivateKeys2(Admin, String)} is called after calculating time fot new update.
     * @param adm Administrator to be used when getting the certificate chain from the DB.
     * @param password This password is only set if passwords should not be stored in memory.
     * @throws Exception
     */
    void loadPrivateKeys(final Admin adm, final String password ) throws Exception {
        try {
            this.mutex.getMutex();
            final long currentTime = new Date().getTime();
            // We will only load private keys if the cache time has run out
            if ( password==null && (
                    this.updating || this.lastTryOfKeyReload+10000>currentTime || !this.sessionData.isNotReloadingP11Keys ||
                    (this.signEntityMap!=null && this.signEntityMap.size()>0 && this.sessionData.data.mKeysValidTo>currentTime)
            ) ) {
                return;
            }
            this.sessionData.setNextKeyUpdate(currentTime);// set next time for key loading
        } finally {
            this.mutex.releaseMutex();
        }
        try {
          if (m_log.isTraceEnabled()) {
                m_log.trace(">loadPrivateKeys2");
          }
            this.updating  = true; // stops new action on token
            loadPrivateKeys2(adm, password);
        } finally {
            this.lastTryOfKeyReload = new Date().getTime();
            synchronized(this) {
                this.updating = false;
                this.notifyAll();
            }
          if (m_log.isTraceEnabled()) {
                m_log.trace("<loadPrivateKeys2");
          }
        }
    }
    /**
     * Create a {@link SigningEntity} for all OCSP signing keys that could be found.
     * @param adm Administrator to be used when getting the certificate chain from the DB.
     * @param password This password is only set if passwords should not be stored in memory.
     * @throws Exception
     */
    private void loadPrivateKeys2(Admin adm, String password ) throws Exception {
        if ( this.signEntityMap!=null ){
            Collection<SigningEntity> values=this.signEntityMap.values();
            if ( values!=null ) {
                final Iterator<SigningEntity> i = values.iterator();
                while( i.hasNext() ) {
                    i.next().shutDown();
                }
            }
        }
        if ( this.cardKeys==null && this.sessionData.hardTokenClassName!=null && this.sessionData.hardTokenClassName.length()>0 ) {
            final String tmpPassword = password!=null ? password : this.sessionData.cardPassword;
            if ( tmpPassword!=null  ) {
                try {
                    this.cardKeys = (CardKeys)OCSPServletStandAlone.class.getClassLoader().loadClass(this.sessionData.hardTokenClassName).newInstance();
                    this.cardKeys.autenticate(tmpPassword);
                } catch( ClassNotFoundException e) {
                    m_log.info(intres.getLocalizedMessage("ocsp.classnotfound", this.sessionData.hardTokenClassName));
                }
            } else {
                m_log.info(intres.getLocalizedMessage("ocsp.nocardpwd"));
            }
        } else {
            m_log.info( intres.getLocalizedMessage("ocsp.nohwsigningclass") );
        }
        final HashMap<Integer, SigningEntity> newSignEntity = new HashMap<Integer, SigningEntity>();
        synchronized(this) {
            this.wait(500); // wait for actions on token to get ready
        }
        loadFromP11HSM(adm, newSignEntity, password);
        final File dir = this.sessionData.mKeystoreDirectoryName!=null ? new File(this.sessionData.mKeystoreDirectoryName) : null;
        if ( dir!=null && dir.isDirectory() ) {
            final File files[] = dir.listFiles();
            if ( files!=null && files.length>0 ) {
                for ( int i=0; i<files.length; i++ ) {
                    final String fileName = files[i].getCanonicalPath();
                    if ( !loadFromSWKeyStore(adm, fileName, newSignEntity, password) ) {
                        loadFromKeyCards(adm, fileName, newSignEntity);
                    }
                }
            } else {
                m_log.debug("No files in soft key directory: " + dir.getCanonicalPath());
            }
        } else {
            m_log.debug((dir != null ? dir.getCanonicalPath() : "null") + " is not a directory.");
        }
        if ( newSignEntity.size()<1 ) {
            String sError = "No valid keys.";
            {
                final String dirStr = dir!=null ? dir.getCanonicalPath() : null;
                if ( dirStr!=null ) {
                    sError += " Key directory "+dirStr+".";
                } else {
                    sError += " No key directory.";
                }
            }{
                final String p11name = this.sessionData.slot!=null && this.sessionData.slot.getProvider()!=null ? this.sessionData.slot.getProvider().getName() : null;
                if ( p11name!=null ) {
                    sError += " P11 provider "+p11name+".";
                } else {
                    sError += " No P11 defined.";
                }
            }
            m_log.error(sError);
        }
        {
            final Iterator<Entry<Integer, SigningEntity>> i=newSignEntity.entrySet().iterator();
            while( i.hasNext() ) {
                Entry<Integer, SigningEntity> entry = i.next();
                entry.getValue().init(entry.getKey().intValue());
            }
        }
        this.signEntityMap = newSignEntity; // atomic change. after this new entity is used.
    }
    /**
     * Gets all {@link SigningEntity}s mapped to EJBCA CA IDs.
     * @return The map.
     */
    synchronized Map<Integer, SigningEntity> getSigningEntityMap() {
        while ( this.updating ) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new Error(e);
            }
        }
        return this.signEntityMap;
    }
    /**
     * Adds OCSP signing keys from the HSM to the newSignEntity (parameter).
     * @param adm Adminstrator to be used when getting the certificate chain from the DB.
     * @param newSignEntity The map where the signing entity should be stored for all keys found where the certificate is a valid OCSP certificate.
     * @param password Used for activation.
     * @return true if keys where found on the HSM
     * @throws Exception
     */
    private boolean loadFromP11HSM(Admin adm, Map<Integer, SigningEntity> newSignEntity,
                                   String password) {
        final PasswordProtection pwp = this.sessionData.getP11Pwd(password);
        if ( !checkPassword( pwp, OcspConfiguration.P11_PASSWORD) ) {
            return false;
        }
        if ( this.sessionData.slot==null ) {
            m_log.debug("no shared library");
            return false;          
        }
        this.sessionData.slot.reset();
        try {
            final P11ProviderHandler providerHandler = new P11ProviderHandler(this.sessionData);
            loadFromKeyStore(adm, providerHandler.getKeyStore(pwp), null,
                             this.sessionData.slot.toString(),
                             providerHandler, newSignEntity, null);
            pwp.destroy();
        } catch( Exception e) {
            m_log.error("load from P11 problem", e);
            return false;
        }
        return true;
    }
    /**
     * Waits 10 if no password object.
     * @param passObject The password object.
     * @param passPropertyKey Property key to define
     * @return Is password not null.
     */
    private boolean checkPassword( Object passObject, String passPropertyKey ) {
        if ( passObject!=null )  {
            return true;
        }
        m_log.warn("You have not specified "+passPropertyKey+" at build time. So you need to do a manual activation.");
        return false;
    }
    /**
     * Adds the OCSP signing key from the java SW keystore to the newSignEntity (parameter).
     * @param adm Adminstrator to be used when getting the certificate chain from the DB.
     * @param fileName The name of a file with a SW java keystore.
     * @param newSignEntity The map where the signing entity should be stored for the key if the certificate is a valid OCSP certificate.
     * @return true if the key in the SW java keystore was valid.
     */
    private boolean loadFromSWKeyStore(Admin adm, String fileName, HashMap<Integer, SigningEntity> newSignEntity,
                                       String password) {
        try {
            final String storePassword = this.sessionData.mStorePassword!=null ? this.sessionData.mStorePassword : password;
            if ( !checkPassword( storePassword, OcspConfiguration.STORE_PASSWORD) ) {
                return false;
            }
            m_log.trace(">loadFromSWKeyStore");
            KeyStore keyStore;
            try {
                keyStore = KeyStore.getInstance("JKS");
                keyStore.load(new FileInputStream(fileName), storePassword.toCharArray());
            } catch( IOException e ) {
                keyStore = KeyStore.getInstance("PKCS12", "BC");
                keyStore.load(new FileInputStream(fileName), storePassword.toCharArray());
            }
            final String keyPassword = this.sessionData.mKeyPassword!=null ? this.sessionData.mKeyPassword : password;
            loadFromKeyStore(adm, keyStore, keyPassword, fileName, new SWProviderHandler(), newSignEntity, fileName);
            m_log.trace("<loadFromSWKeyStore OK");
            return true;
        } catch( Exception e ) {
            m_log.debug("Unable to load key file "+fileName+". Exception: "+e.getMessage());
        }
        m_log.trace("<loadFromSWKeyStore failed");
        return false;
    }
    /**
     * Is OCSP extended key usage set for a certificate?
     * @param cert to check.
     * @return true if the extended key usage for OCSP is check
     */
    private boolean isOCSPCert(X509Certificate cert) {
        final String ocspKeyUsage = "1.3.6.1.5.5.7.3.9";
        final List<String> keyUsages;
        try {
            keyUsages = cert.getExtendedKeyUsage();
        } catch (CertificateParsingException e) {
            return false;
        }
        return keyUsages!=null && keyUsages.contains(ocspKeyUsage);
    }
    /**
     * Adds OCSP signing keys from a java keystore to the newSignEntity (parameter).
     * @param adm Adminstrator to be used when getting the certificate chain from the DB.
     * @param keyStore The keystore.
     * @param keyPassword Password for the key. Set to null if not protected.
     * @param errorComment Comment to be used in possible error message.
     * @param providerHandler The provider to be used.
     * @param newSignEntity The map where the signing entity should be stored for all keys found where the certificate is a valid OCSP certificate.
     * @param fileName Name of the keystore file. Use null for P11
     * @throws KeyStoreException
     */
    private void loadFromKeyStore(Admin adm, KeyStore keyStore, String keyPassword,
                                  String errorComment, ProviderHandler providerHandler,
                                  Map<Integer, SigningEntity> newSignEntity, String fileName) throws KeyStoreException {
        final Enumeration<String> eAlias = keyStore.aliases();
        while( eAlias.hasMoreElements() ) {
            final String alias = eAlias.nextElement();
            if ( this.sessionData.keyAlias!=null && !this.sessionData.keyAlias.contains(alias) ) {
                m_log.debug("Alias '"+alias+"' not in alias list. The key with this alias will not be used.");
                continue;
            }
            try {
                final X509Certificate cert = (X509Certificate)keyStore.getCertificate(alias);
                if ( cert==null ) {
                    m_log.debug("No certificate found for keystore alias '"+alias+"'");
                    continue;
                }
                if ( !isOCSPCert(cert) ) {
                    m_log.debug("Certificate "+cert.getSubjectDN()+" has not ocsp signing as extended key usage"+"', keystore alias '"+alias+"'");
                    continue;
                }
                final PrivateKeyContainer pkf = new PrivateKeyContainerKeyStore( this.sessionData, alias, keyPassword!=null ? keyPassword.toCharArray() : null,
                                                                                 keyStore, cert, providerHandler.getProviderName(), fileName );
                final PrivateKey key = pkf.getKey();
                if ( key==null ) {
                    m_log.debug("Key not available. Not adding signer entity for: "+pkf.getCertificate().getSubjectDN()+"', keystore alias '"+alias+"'");
                    continue;
                }
                try {
                  KeyTools.testKey(key, pkf.getCertificate().getPublicKey(), providerHandler.getProviderName());
                    m_log.debug("Adding sign entity for '"+pkf.getCertificate().getSubjectDN()+"', keystore alias '"+alias+"'");
                    putSignEntity(pkf, pkf.getCertificate(), adm, providerHandler, newSignEntity);
                } catch (InvalidKeyException e) {
                  // thrown by testKey
                    m_log.debug("Key not working. Not adding signer entity for: "+pkf.getCertificate().getSubjectDN()+"', keystore alias '"+alias+"'. Error comment '"+errorComment+"'. Message '"+e.getMessage());
                    continue;                 
                } finally {
                    pkf.releaseKey();
                }
            } catch (Exception e) {
                String errMsg = intres.getLocalizedMessage("ocsp.errorgetalias", alias, errorComment);
                m_log.error(errMsg, e);
            }
        }
    }
    /**
     * Gets the chain for a certificate. The certificate must be valid and in the DB.
     * @param cert The certificate that should be first in the chain.
     * @param adm  Administrator performing the operation.
     * @return The chain of the certificate. Null if the certificate is not valid.
     */
    private List<X509Certificate> getCertificateChain(X509Certificate cert, Admin adm) {
        String issuerDN = CertTools.getIssuerDN(cert);
        final CertificateStatus status = this.sessionData.data.certificateStoreSession.getStatus(issuerDN, CertTools.getSerialNumber(cert));
        if ( status.equals(CertificateStatus.NOT_AVAILABLE) ) {
            m_log.warn(intres.getLocalizedMessage("ocsp.signcertnotindb", CertTools.getSerialNumberAsString(cert), issuerDN));
            return null;
        }
        if ( status.equals(CertificateStatus.REVOKED) ) {
            m_log.warn(intres.getLocalizedMessage("ocsp.signcertrevoked", CertTools.getSerialNumberAsString(cert), issuerDN));
            return null;
        }
        final List<X509Certificate> list = new ArrayList<X509Certificate>();
        X509Certificate current = cert;
        while( true ) {
            if ( CertTools.isSelfSigned(current) ) {
                return list;
            }
            // Is there a CA certificate?
            final X509Certificate target = this.sessionData.data.m_caCertCache.findLatestBySubjectDN(HashID.getFromIssuerDN(current));
            if (target != null) {
                current = target;
                list.add(current);
            } else {
                break;             
            }
        }
        m_log.warn(intres.getLocalizedMessage("ocsp.signcerthasnochain", CertTools.getSerialNumberAsString(cert), issuerDN));
        return null;
    }
    /**
     * Constructs a new {@link SigningEntity} and puts it in the map 'newSignEntitys'.
     * If 'newSignEntitys' allready contains a certificate for the same CA which is newer than 'cert' the one in the map is kept (nothing is done).
     * @param keyContainer The key.
     * @param cert The certificate of the key
     * @param adm Adminstrator to be used when getting the certificate chain from the DB.
     * @param providerHandler The provider.
     * @param newSignEntitys The map where the signing entity should be stored for all keys found where the certificate is a valid OCSP certificate.
     * @return true if the key and certificate are valid for OCSP signing for one of the EJBCA CAs.
     */
    private boolean putSignEntity( PrivateKeyContainer keyContainer, X509Certificate cert, Admin adm, ProviderHandler providerHandler,
                                   final Map<Integer, SigningEntity> newSignEntitys) {
        if ( keyContainer==null || cert==null ) {
            return false;
        }
        final List<X509Certificate> chain = getCertificateChain(cert, adm);
        if ( chain==null || chain.size()<1 ) {
            return false;
        }
        final Integer caid = new Integer(this.sessionData.data.getCaid(chain.get(0)));
        {
            final SigningEntity entityForSameCA = newSignEntitys.get(caid);
            final X509Certificate otherChainForSameCA[] = entityForSameCA!=null ? entityForSameCA.getCertificateChain() : null;
            if ( otherChainForSameCA!=null && otherChainForSameCA[0].getNotBefore().after(cert.getNotBefore())) {
                m_log.debug("CA with ID "+caid+" has duplicated keys. Certificate for older key that is not used has serial number: "+cert.getSerialNumber().toString(0x10));
                return true; // the entity already in the map is newer.
            }
        }
        newSignEntitys.put( caid, new SigningEntity(chain, keyContainer, providerHandler) );
        m_log.debug("CA with ID "+caid+" now has a OCSP signing key. Certificate with serial number: "+cert.getSerialNumber().toString(0x10));
        return true;
    }
    /**
     * Constructs a new {@link SigningEntity} and puts it in the map 'newSignEntitys'.
     * If 'newSignEntitys' allready contains a certificate for the same CA which is newer than 'cert' the one in the map is kept (nothing is done).
     * @param cert The certificate of the key
     * @param adm Adminstrator to be used when getting the certificate chain from the DB.
     * @param newSignEntity The map where the signing entity should be stored for all keys found where the certificate is a valid OCSP certificate.
     * @return true if the key and certificate are valid for OCSP signing for one of the EJBCA CAs.
     */
    private boolean putSignEntityCard( Certificate cert, Admin adm,
                                       Map<Integer, SigningEntity> newSignEntity) {
        if ( cert!=null &&  cert instanceof X509Certificate) {
            final X509Certificate x509cert = (X509Certificate)cert;
            final PrivateKeyContainer keyContainer = new PrivateKeyContainerCard(x509cert, this.cardKeys);
            return putSignEntity( keyContainer, x509cert, adm, new CardProviderHandler(), newSignEntity );
        }
        return false;
    }
    /**
     * Adds OCSP signing keys from the card to the newSignEntity (parameter).
     * @param adm Adminstrator to be used when getting the certificate chain from the DB.
     * @param fileName The name of the file where the certificates are stored.
     * @param newSignEntity The map where the signing entity should be stored for all keys found where the certificate is a valid OCSP certificate.
     */
    private void loadFromKeyCards(Admin adm, String fileName, Map<Integer, SigningEntity> newSignEntity) {
        m_log.trace(">loadFromKeyCards");
        final CertificateFactory cf;
        try {
            cf = CertificateFactory.getInstance("X.509");
        } catch (java.security.cert.CertificateException e) {
            throw new Error(e);
        }
        String fileType = null;
        try {// read certs from PKCS#7 file
            final Collection<? extends Certificate> c = cf.generateCertificates(new FileInputStream(fileName));
            if ( c!=null && !c.isEmpty() ) {
                Iterator<? extends Certificate> i = c.iterator();
                while (i.hasNext()) {
                    if ( putSignEntityCard(i.next(), adm, newSignEntity) ) {
                        fileType = "PKCS#7";
                    }
                }
            }
        } catch( Exception e) {
            // do nothing
        }
        if ( fileType==null ) {
            try {// read concatenated certificate in PEM format
                final BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileName));
                while (bis.available() > 0) {
                    if ( putSignEntityCard(cf.generateCertificate(bis), adm, newSignEntity) ) {
                        fileType="PEM";
                    }
                }
            } catch(Exception e){
                // do nothing
            }
        }
        if ( fileType!=null ) {
            m_log.debug("Certificate(s) found in file "+fileName+" of "+fileType+".");
        } else {
            m_log.debug("File "+fileName+" has no cert.");
        }
        m_log.trace("<loadFromKeyCards");
    }
}
TOP

Related Classes of org.ejbca.core.protocol.ocsp.standalonesession.SigningEntityContainer$Mutex

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.