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

Source Code of org.ejbca.core.protocol.ocsp.standalonesession.KeyRenewer

/*************************************************************************
*                                                                       *
*  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.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;

import org.apache.log4j.Logger;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.jce.PKCS10CertificationRequest;
import org.bouncycastle.util.encoders.Base64;
import org.ejbca.core.model.ra.UserDataConstants;
import org.ejbca.core.protocol.ws.client.gen.CertificateResponse;
import org.ejbca.core.protocol.ws.client.gen.EjbcaWS;
import org.ejbca.core.protocol.ws.client.gen.EjbcaWSService;
import org.ejbca.core.protocol.ws.client.gen.NameAndId;
import org.ejbca.core.protocol.ws.client.gen.UserDataVOWS;
import org.ejbca.core.protocol.ws.client.gen.UserMatch;
import org.ejbca.core.protocol.ws.common.CertificateHelper;
import org.ejbca.util.CertTools;
import org.ejbca.util.query.BasicMatch;

/**
* An object of this class is constructed when the key should be updated.
*
* @author primelars
* @version  $Id: KeyRenewer.java 11132 2011-01-10 21:27:08Z jeklund $
*/
class KeyRenewer {
    /**
     * Log object.
     */
    static final private Logger m_log = Logger.getLogger(KeyRenewer.class);
    /**
     * The keystore containing the key to authenticate with.
     * The {@link PrivateKeyContainerKeyStore} object must delete the reference to the {@link KeyRenewer}
     * object when {@link PrivateKeyContainerKeyStore} is not used any more.
     */
    private final PrivateKeyContainerKeyStore privateKeyContainerKeyStore;
    /**
     * Defines the thread that is doing the update.
     */
    final private Runner runner;
    /**
     * True as long as the object should renew the key when needed. Set to false when all signing entitys are reloaded.
     */
    private boolean doUpdateKey;
    /**
     * The CA chain. The CA signing the certificate for the new key is on top
     */
    final private List<X509Certificate> caChain;
    /**
     * EJBCA id for the CA that will sign the new key.
     */
    final private int caid;           
    /**
     * Class used for the thread doing the renewing.
     */
    private class Runner implements Runnable {
        /* (non-Javadoc)
         * @see java.lang.Runnable#run()
         */
        public synchronized void run() {
            while( KeyRenewer.this.doUpdateKey ) {
                if ( KeyRenewer.this.privateKeyContainerKeyStore.certificate==null ) {
                    return;
                }
                final long timeToRenew = KeyRenewer.this.privateKeyContainerKeyStore.certificate.getNotAfter().getTime()-new Date().getTime()-1000*(long)KeyRenewer.this.privateKeyContainerKeyStore.sessionData.mRenewTimeBeforeCertExpiresInSeconds;
                if (m_log.isDebugEnabled()) {
                    m_log.debug("time to renew signing key for CA \'"+KeyRenewer.this.privateKeyContainerKeyStore.certificate.getIssuerDN()+"\' : "+timeToRenew );
                }
                try {
                    wait(Math.max(timeToRenew, 15000)); // set to 15 seconds if long time to renew before expire
                } catch (InterruptedException e) {
                    throw new Error(e);
                }
                try {
                    updateKey();
                } catch( Throwable t ) {
                    m_log.error("Unknown problem when rekeying. Trying again.", t);
                }
            }
        }
    }
    /**
     * Updating of the key.
     */
    private void updateKey() {
        if ( !this.doUpdateKey ) {
            return;
        }
        this.privateKeyContainerKeyStore.sessionData.setNextKeyUpdate(new Date().getTime()); //  since a key is now reloaded we should wait an whole interval for next key update
        m_log.debug("rekeying started for CA \'"+this.privateKeyContainerKeyStore.certificate.getIssuerDN()+"\'");
        // Check that we at least potentially have a RA key available for P11 sessions
        if ("pkcs11".equalsIgnoreCase(System.getProperty("javax.net.ssl.keyStoreType")) && this.privateKeyContainerKeyStore.sessionData.mP11Password == null &&
                !this.privateKeyContainerKeyStore.sessionData.doNotStorePasswordsInMemory) {
            m_log.info("PKCS#11 slot password is not yet available. Cannot access RA admin key until token is activated.");
            return;
        }
        // TODO: If the password for the RA token was wrong, the SSL provider will crash and burn.. solve this.
        final EjbcaWS ejbcaWS = getEjbcaWS();
        if ( ejbcaWS==null ) {
            return;
        }
        final String caName = getCAName(ejbcaWS);
        if ( caName==null ) {
            m_log.debug("No CA for caid "+this.caid+" found.");
            return;
        }
        final UserDataVOWS userData=getUserDataVOWS(ejbcaWS, caName);
        if ( userData==null ) {
            return;
        }
        m_log.debug("user name found: "+ userData.getUsername());
        try {
            this.privateKeyContainerKeyStore.waitUntilKeyIsNotUsed();
            final KeyPair keyPair = generateKeyPair();
            if ( keyPair==null ) {
                return;
            }
            m_log.debug("public key: "+keyPair.getPublic() );
            if ( !editUser(ejbcaWS, userData) ) {
                return;
            }
            final X509Certificate certChain[] = storeKey(ejbcaWS, userData, keyPair);
            if ( certChain==null ) {
                return;
            }
            this.privateKeyContainerKeyStore.privateKey = keyPair.getPrivate();
            this.privateKeyContainerKeyStore.certificate = certChain[0];
        } finally {
            this.privateKeyContainerKeyStore.keyGenerationFinished();
        }
        m_log.info("New OCSP signing key generated for CA '"+ userData.getCaName()+"'. Username: '"+userData.getUsername()+"'. Subject DN: '"+userData.getSubjectDN()+"'.");
    }
    /**
     * Get WS object.
     * @return the EJBCA WS object.
     */
    private EjbcaWS getEjbcaWS() {
        final URL ws_url;
        try {
            ws_url = new URL(this.privateKeyContainerKeyStore.sessionData.webURL + "?wsdl");
        } catch (MalformedURLException e) {
            m_log.error("Problem with URL: '"+this.privateKeyContainerKeyStore.sessionData.webURL+"'", e);
            return null;
        }
        final QName qname = new QName("http://ws.protocol.core.ejbca.org/", "EjbcaWSService");
        m_log.debug("web service. URL: "+ws_url+" QName: "+qname);
        return new EjbcaWSService(ws_url, qname).getEjbcaWSPort();
    }
    /**
     * Get the CA name
     * @param ejbcaWS from {@link #getEjbcaWS()}
     * @return the name
     */
    private String getCAName(EjbcaWS ejbcaWS) {
            final Map<Integer, String> mCA = new HashMap<Integer, String>();
            final Iterator<NameAndId> i;
            try {
                i = ejbcaWS.getAvailableCAs().iterator();
            } catch (Exception e) {
                m_log.error("WS not working", e);
                return null;
            }
            while( i.hasNext() ) {
                final NameAndId nameAndId = i.next();
                mCA.put(new Integer(nameAndId.getId()), nameAndId.getName());
                m_log.debug("CA. id: "+nameAndId.getId()+" name: "+nameAndId.getName());
            }
            return mCA.get(new Integer(this.caid));
    }
    /**
     * Get user data for the EJBCA user that will be used when creating the cert for the new key.
     * @param ejbcaWS from {@link #getEjbcaWS()}
     * @param caName from {@link #getCAName(EjbcaWS)}
     * @return the data
     */
    private UserDataVOWS getUserDataVOWS(EjbcaWS ejbcaWS, String caName) {
        final UserMatch match = new org.ejbca.core.protocol.ws.client.gen.UserMatch();
        final String subjectDN = CertTools.getSubjectDN(this.privateKeyContainerKeyStore.certificate);
        match.setMatchtype(BasicMatch.MATCH_TYPE_EQUALS);
        match.setMatchvalue(subjectDN);
        match.setMatchwith(org.ejbca.util.query.UserMatch.MATCH_WITH_DN);
        final List<UserDataVOWS> result;
        try {
            result = ejbcaWS.findUser(match);
        } catch (Exception e) {
            m_log.error("WS not working", e);
            return null;
        }
        if ( result==null || result.size()<1) {
            m_log.info("no match for subject DN:"+subjectDN);
            return null;
        }
        m_log.debug("at least one user found for cert with DN: "+subjectDN+" Trying to match it with CA name: "+caName);
        UserDataVOWS userData = null;
        final Iterator<UserDataVOWS> i = result.iterator();
        while ( i.hasNext() ) {
            final UserDataVOWS tmpUserData = i.next();
            if ( caName.equals(tmpUserData.getCaName()) ) {
                userData = tmpUserData;
                break;
            }
        }
        if ( userData==null ) {
            m_log.error("No user found for certificate '"+subjectDN+"' on CA '"+caName+"'.");
            return null;
        }
        return userData;
    }
    /**
     * Generate the key.
     * @return the key
     */
    private KeyPair generateKeyPair() {

        final RSAPublicKey oldPublicKey; {
            final PublicKey tmpPublicKey = this.privateKeyContainerKeyStore.certificate.getPublicKey();
            if ( !(tmpPublicKey instanceof RSAPublicKey) ) {
                m_log.error("Only RSA keys could be renewed.");
                return null;
            }
            oldPublicKey = (RSAPublicKey)tmpPublicKey;
        }
        final KeyPairGenerator kpg;
        try {
            kpg = KeyPairGenerator.getInstance("RSA", this.privateKeyContainerKeyStore.providerName);
            kpg.initialize(oldPublicKey.getModulus().bitLength());
            return kpg.generateKeyPair();
        } catch (Throwable e) {
            m_log.error("Key generation problem.", e);
            return null;
        }
    }
    /**
     * setting status of EJBCA user to new and setting password of user.
     * @param ejbcaWS from {@link #getEjbcaWS()}
     * @param userData from {@link #getUserDataVOWS(EjbcaWS, String)}
     * @return true if success
     */
    private boolean editUser(EjbcaWS ejbcaWS, UserDataVOWS userData) {
        userData.setStatus(UserDataConstants.STATUS_NEW);
        userData.setPassword("foo123");
        userData.setTokenType(UserDataVOWS.TOKEN_TYPE_USERGENERATED);
        try {
            ejbcaWS.editUser(userData);
        } catch (Exception e) {
            m_log.error("Problem to edit user.", e);
            return false;
        }
        return true;
    }
    /**
     * Fetch a new certificate from EJBCA and stores the key with the certificate chain.
     * @param ejbcaWS from {@link #getEjbcaWS()}
     * @param userData from {@link #getUserDataVOWS(EjbcaWS, String)}
     * @param keyPair from {@link #generateKeyPair()}
     * @return the certificate chain of the stored key
     */
    private X509Certificate[] storeKey(EjbcaWS ejbcaWS, UserDataVOWS userData, KeyPair keyPair) {
        X509Certificate tmpCert = null;
        final Iterator<X509Certificate> i;
        try {
            final PKCS10CertificationRequest pkcs10 = new PKCS10CertificationRequest("SHA1WithRSA", CertTools.stringToBcX509Name("CN=NOUSED"), keyPair.getPublic(), new DERSet(),
                                                                                     keyPair.getPrivate(), this.privateKeyContainerKeyStore.providerName );
            final CertificateResponse certificateResponse = ejbcaWS.pkcs10Request(userData.getUsername(), userData.getPassword(),
                                                                                  new String(Base64.encode(pkcs10.getEncoded())),null,CertificateHelper.RESPONSETYPE_CERTIFICATE);
            i = (Iterator<X509Certificate>)CertificateFactory.getInstance("X.509").generateCertificates(new ByteArrayInputStream(Base64.decode(certificateResponse.getData()))).iterator();
        } catch (Exception e) {
            m_log.error("Certificate generation problem.", e);
            return null;
        }
        while ( i.hasNext() ) {
            tmpCert = i.next();
            try {
                tmpCert.verify(this.caChain.get(0).getPublicKey());
            } catch (Exception e) {
                tmpCert = null;
                continue;
            }
            if ( keyPair.getPublic().equals(tmpCert.getPublicKey()) )
                break;
            tmpCert = null;
        }
        if ( tmpCert==null ) {
            m_log.error("No certificate signed by correct CA generated.");
            return null;
        }
        final List<X509Certificate> lCertChain = new ArrayList<X509Certificate>(this.caChain);
        lCertChain.add(0, tmpCert);
        final X509Certificate certChain[] = lCertChain.toArray(new X509Certificate[0]);
        if ( this.privateKeyContainerKeyStore.fileName!=null && this.privateKeyContainerKeyStore.sessionData.mKeyPassword==null ) {
            m_log.error("Key password must be configured when updating SW keystore.");
            return null;
        }
        try {
            this.privateKeyContainerKeyStore.keyStore.setKeyEntry(this.privateKeyContainerKeyStore.alias, keyPair.getPrivate(),
                                                                  this.privateKeyContainerKeyStore.sessionData.mKeyPassword!=null ? this.privateKeyContainerKeyStore.sessionData.mKeyPassword.toCharArray() : null,
                                                                  certChain);
        } catch (Throwable e) {
            m_log.error("Problem to store new key in HSM.", e);
            return null;
        }
        if ( this.privateKeyContainerKeyStore.fileName!=null ) {
            try {
                this.privateKeyContainerKeyStore.keyStore.store(new FileOutputStream(this.privateKeyContainerKeyStore.fileName),
                                                                this.privateKeyContainerKeyStore.sessionData.mStorePassword.toCharArray());
            } catch (Throwable e) {
                m_log.error("Not possible to store keystore on file.",e);
            }
        }
        return certChain;
    }
    /**
     * Initialize renewing of keys.
     * @param privateKeyContainerKeyStore keystore to use
     * @param _caChain sets {@link #caChain}
     * @param _caid sets {@link #caid}
     */
    KeyRenewer(PrivateKeyContainerKeyStore privateKeyContainerKeyStore, List<X509Certificate> _caChain, int _caid) {
        this.privateKeyContainerKeyStore = privateKeyContainerKeyStore;
        this.caid = _caid;
        this.caChain = _caChain;
        this.doUpdateKey = false;
        if ( this.privateKeyContainerKeyStore.sessionData.doKeyRenewal() ) {
            this.runner = new Runner();
            this.doUpdateKey = true;
            new Thread(this.runner).start();
        } else {
            this.runner = null;
        }
    }
    /**
     * Shuts down the rekeying thread. Done when reloading OCSP signing keys.
     */
    void shutdown() {
        this.doUpdateKey = false;
        if ( this.runner!=null ) {
            synchronized( this.runner ) {
                this.runner.notifyAll();
            }
        }
    }
}
TOP

Related Classes of org.ejbca.core.protocol.ocsp.standalonesession.KeyRenewer

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.