Java tutorial
/************************************************************************* * * * 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.InternalResources; 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 15251 2012-08-07 12:22:42Z primelars $ */ class KeyRenewer { /** * Log object. */ static final private Logger m_log = Logger.getLogger(KeyRenewer.class); /** * Internal localization of logs and errors */ static private final InternalResources intres = InternalResources.getInstance(); /** * 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 { // NOPMD: we need to use threads, even if it's a JEE app final private boolean runOnce; Runner(boolean once) { this.runOnce = once; } @Override public synchronized void run() { if (KeyRenewer.this.privateKeyContainerKeyStore.certificate == null) { return; } if (this.runOnce) { m_log.debug("Servlet triggered rekeying started for CA \'" + KeyRenewer.this.privateKeyContainerKeyStore.certificate.getIssuerDN() + "\'"); updateKey(); return; } while (KeyRenewer.this.doUpdateKey) { final long timeToRenew = KeyRenewer.this.privateKeyContainerKeyStore.certificate.getNotAfter() .getTime() - new Date().getTime() - 1000 * (long) KeyRenewer.this.privateKeyContainerKeyStore.sessionData.mRenewTimeBeforeCertExpiresInSeconds; 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 { m_log.debug("Automatic rekeying started for CA \'" + KeyRenewer.this.privateKeyContainerKeyStore.certificate.getIssuerDN() + "\'"); 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 // 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]; m_log.info(intres.getLocalizedMessage("ocsp.info.new.cert", certChain[0].getIssuerX500Principal().getName(), userData.getUsername(), certChain[0].getSubjectX500Principal().getName(), certChain[0].getNotAfter())); } finally { this.privateKeyContainerKeyStore.keyGenerationFinished(); } } /** * 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.error(intres.getLocalizedMessage("ocsp.no.user.with.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, boolean runOnce) { this.privateKeyContainerKeyStore = privateKeyContainerKeyStore; this.caid = _caid; this.caChain = _caChain; this.doUpdateKey = false; if (runOnce) { this.runner = new Runner(true); this.doUpdateKey = true; new Thread(this.runner).start(); // NOPMD: we need to use threads, even if it's a JEE app return; } if (!this.privateKeyContainerKeyStore.sessionData.doKeyRenewal()) { this.runner = null; return; } this.runner = new Runner(false); this.doUpdateKey = true; new Thread(this.runner).start(); } /** * 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(); } } } }