org.ejbca.core.ejb.ocsp.OcspKeyRenewalSessionBean.java Source code

Java tutorial

Introduction

Here is the source code for org.ejbca.core.ejb.ocsp.OcspKeyRenewalSessionBean.java

Source

/*************************************************************************
 *                                                                       *
 *  EJBCA Community: 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.ejb.ocsp;

import java.io.ByteArrayInputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.NoSuchObjectLocalException;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerService;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.x500.X500Principal;
import javax.xml.namespace.QName;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.util.encoders.Base64;
import org.cesecore.authentication.tokens.AlwaysAllowLocalAuthenticationToken;
import org.cesecore.authentication.tokens.AuthenticationToken;
import org.cesecore.authentication.tokens.UsernamePrincipal;
import org.cesecore.authorization.AuthorizationDeniedException;
import org.cesecore.certificates.certificate.CertificateStoreSessionLocal;
import org.cesecore.certificates.ocsp.OcspResponseGeneratorSessionLocal;
import org.cesecore.certificates.ocsp.cache.OcspSigningCache;
import org.cesecore.certificates.ocsp.cache.OcspSigningCacheEntry;
import org.cesecore.config.OcspConfiguration;
import org.cesecore.internal.InternalResources;
import org.cesecore.jndi.JndiConstants;
import org.cesecore.keybind.CertificateImportException;
import org.cesecore.keybind.InternalKeyBinding;
import org.cesecore.keybind.InternalKeyBindingMgmtSessionLocal;
import org.cesecore.keybind.InternalKeyBindingStatus;
import org.cesecore.keybind.InternalKeyBindingTrustEntry;
import org.cesecore.keybind.impl.AuthenticationKeyBinding;
import org.cesecore.keybind.impl.ClientX509KeyManager;
import org.cesecore.keys.token.CryptoToken;
import org.cesecore.keys.token.CryptoTokenManagementSessionLocal;
import org.cesecore.keys.token.CryptoTokenOfflineException;
import org.cesecore.keys.token.KeyRenewalFailedException;
import org.cesecore.keys.util.KeyTools;
import org.cesecore.util.CertTools;
import org.cesecore.util.provider.X509TrustManagerAcceptAll;
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.passgen.PasswordGeneratorFactory;
import org.ejbca.util.query.BasicMatch;

/**
 * @version $Id: OcspKeyRenewalSessionBean.java 20515 2015-01-07 16:17:08Z mikekushner $
 */
@Stateless(mappedName = JndiConstants.APP_JNDI_PREFIX + "OcspKeyRenewalSessionRemote")
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class OcspKeyRenewalSessionBean implements OcspKeyRenewalSessionLocal, OcspKeyRenewalSessionRemote {

    private static final Logger log = Logger.getLogger(OcspKeyRenewalSessionBean.class);

    private static final InternalResources intres = InternalResources.getInstance();

    private static final long NO_SAFETY_MARGIN = Long.MAX_VALUE / 1000;

    private static volatile Integer timerId = null;

    // TODO: See if we can create local business methods for all calls where this is required
    private static final AuthenticationToken authenticationToken = new AlwaysAllowLocalAuthenticationToken(
            new UsernamePrincipal("OCSP key renewal"));

    @EJB
    private OcspResponseGeneratorSessionLocal ocspResponseGeneratorSession;
    @EJB
    private InternalKeyBindingMgmtSessionLocal internalKeyBindingMgmtSession;
    @EJB
    private CryptoTokenManagementSessionLocal cryptoTokenManagementSession;
    @EJB
    private CertificateStoreSessionLocal certificateStoreSession;

    @Resource
    private SessionContext sessionContext;

    /* When the sessionContext is injected, the timerService should be looked up.
     * This is due to the Glassfish EJB verifier complaining. 
     */
    private TimerService timerService;

    @PostConstruct
    public void postConstruct() {
        timerService = sessionContext.getTimerService();
        //Just do this once
        if (timerId == null) {
            synchronized (this) {
                if (timerId == null) {
                    // Any weak random number is fine
                    timerId = new Random().nextInt();
                }
            }
        }
    }

    /**
     * 
     * 
     * @param signerSubjectDN signerSubjectDN subject DN of the signing key to be renewed. The string "all" will result in all keys being renewed
     * @param safetyMargin the number of seconds before actual expiration that a keystore should be renewed
     * @throws CryptoTokenOfflineException if Crypto Token is not available or connected, or key with alias does not exist.
     * @throws InvalidKeyException if the public key in the tokenAndChain can not be used to verify a string signed by the private key, because the key 
     * is wrong or the signature operation fails for other reasons such as a NoSuchAlgorithmException or SignatureException.
     */
    private synchronized void renewKeyStores(String signerSubjectDN, long safetyMargin)
            throws InvalidKeyException, CryptoTokenOfflineException {
        //Cancel all running timers
        cancelTimers();
        try {
            final EjbcaWS ejbcaWS = getEjbcaWS();
            if (ejbcaWS == null) {
                if (log.isDebugEnabled()) {
                    log.debug(
                            "Could not locate a suitable web service for automatic OCSP key/certificate renewal.");
                }
                return;
            }
            final X500Principal target;
            try {
                target = signerSubjectDN.trim().equalsIgnoreCase(RENEW_ALL_KEYS) ? null
                        : new X500Principal(signerSubjectDN);
            } catch (IllegalArgumentException e) {
                log.error(intres.getLocalizedMessage("ocsp.rekey.triggered.dn.not.valid", signerSubjectDN));
                return;
            }
            final StringBuffer matched = new StringBuffer();
            final StringBuffer unMatched = new StringBuffer();
            for (final OcspSigningCacheEntry ocspSigningCacheEntry : OcspSigningCache.INSTANCE.getEntries()) {
                // Only perform renewal for non CA signing key OCSP signers
                if (!ocspSigningCacheEntry.isUsingSeparateOcspSigningCertificate()) {
                    continue;
                }
                final X509Certificate ocspSigningCertificate = ocspSigningCacheEntry.getOcspSigningCertificate();
                final long timeLeftBeforeRenewal = ocspSigningCertificate.getNotAfter().getTime()
                        - new Date().getTime();
                if (timeLeftBeforeRenewal < (1000 * safetyMargin)) {
                    final X500Principal src = ocspSigningCertificate.getSubjectX500Principal();
                    if (target != null && !src.equals(target)) {
                        unMatched.append(" '" + src.getName() + '\'');
                        continue;
                    }
                    matched.append(" '" + ocspSigningCertificate.getIssuerX500Principal().getName() + '\'');
                    try {
                        renewKeyStore(ejbcaWS, ocspSigningCacheEntry);
                    } catch (KeyRenewalFailedException e) {
                        String msg = intres.getLocalizedMessage("ocsp.rekey.failed.unknown.reason", target,
                                e.getLocalizedMessage());
                        log.error(msg, e);
                        continue;
                    }
                }
            }
            if (matched.length() < 1 && target != null) {
                log.error(intres.getLocalizedMessage("ocsp.rekey.triggered.dn.not.existing", target.getName(),
                        unMatched));
                return;
            }
            log.info(intres.getLocalizedMessage("ocsp.rekey.triggered", matched));
        } finally {
            //Set new timer to run, even if something breaks.
            addTimer(OcspConfiguration.getRekeyingUpdateTimeInSeconds());
        }
    }

    @Override
    public synchronized void renewKeyStores(String signerSubjectDN)
            throws KeyStoreException, CryptoTokenOfflineException, InvalidKeyException {
        renewKeyStores(signerSubjectDN, NO_SAFETY_MARGIN);
    }

    /**
     * Generate a new key pair and request a new certificate for this key pair using EJBCA WS.
     * 
     * @param ejbcaWS a reference to the remote EJBCA WS
     * @param ocspSigningCacheEntry the cached OCSP signing entry backed by an OcspKeyBinding
     * @throws InvalidKeyException if the new public key can not be used to verify a string signed by the private key, because the key is wrong or 
     * the signature operation fails for other reasons such as a NoSuchAlgorithmException or SignatureException.
     * @throws CryptoTokenOfflineException if Crypto Token is not available or connected, or key with alias does not exist.
     * @throws KeyRenewalFailedException if any error occurs during signing
     */
    private void renewKeyStore(EjbcaWS ejbcaWS, OcspSigningCacheEntry ocspSigningCacheEntry)
            throws InvalidKeyException, CryptoTokenOfflineException, KeyRenewalFailedException {
        //Generate the new key pair
        final int internalKeyBindingId = ocspSigningCacheEntry.getOcspKeyBinding().getId();
        try {
            internalKeyBindingMgmtSession.generateNextKeyPair(authenticationToken, internalKeyBindingId);
        } catch (InvalidAlgorithmParameterException e) {
            throw new KeyRenewalFailedException(e);
        } catch (AuthorizationDeniedException e) {
            throw new KeyRenewalFailedException(e);
        }
        //Sign the new keypair
        final X509Certificate signedCertificate = signCertificateByCa(ejbcaWS, ocspSigningCacheEntry);
        try {
            internalKeyBindingMgmtSession.importCertificateForInternalKeyBinding(authenticationToken,
                    internalKeyBindingId, signedCertificate.getEncoded());
        } catch (CertificateEncodingException e) {
            throw new KeyRenewalFailedException(e);
        } catch (CertificateImportException e) {
            throw new KeyRenewalFailedException(e);
        } catch (AuthorizationDeniedException e) {
            throw new KeyRenewalFailedException(e);
        }
        /*
         * Replace the alias and the chain at this step. If anything bad happened prior to this step the old alias and 
         * chain are still active, and no harm done. 
         */
        ocspResponseGeneratorSession.reloadOcspSigningCache();
    }

    /**
     * Get user data for the EJBCA user that will be used when creating the cert for the new key.
     * @param signingCertificate The OCSP signing certificate to get the end entity for
     * @param caId the ID of the OCSP signing certificate issuing CA
     * 
     * @return the data
     */
    private UserDataVOWS getUserDataVOWS(EjbcaWS ejbcaWS, final X509Certificate signingCertificate,
            final int caId) {
        final UserMatch match = new UserMatch();
        final String subjectDN = CertTools.getSubjectDN(signingCertificate);
        final String caName = getCAName(ejbcaWS, caId);
        if (caName == null) {
            throw new InvalidParameterException("No CA found for ID: " + caId);
        }
        match.setMatchtype(BasicMatch.MATCH_TYPE_EQUALS);
        match.setMatchvalue(subjectDN);
        match.setMatchwith(org.ejbca.util.query.UserMatch.MATCH_WITH_DN);
        final List<UserDataVOWS> users;
        try {
            users = ejbcaWS.findUser(match);
        } catch (Exception e) {
            log.error("WS not working", e);
            return null;
        }
        if (users == null || users.size() < 1) {
            log.error(intres.getLocalizedMessage("ocsp.no.user.with.subject.dn", subjectDN));
            return null;
        }
        log.debug("at least one user found for cert with DN: " + subjectDN + " Trying to match it with CA name: "
                + caName);
        UserDataVOWS result = null;
        for (UserDataVOWS userData : users) {
            if (caName.equals(userData.getCaName())) {
                result = userData;
                break;
            }
        }
        if (result == null) {
            log.error("No user found for certificate '" + subjectDN + "' on CA '" + caName + "'.");
            return null;
        }
        return result;
    }

    /**
     * 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(UserDataVOWS.STATUS_NEW);
        userData.setPassword(PasswordGeneratorFactory
                .getInstance(PasswordGeneratorFactory.PASSWORDTYPE_LETTERSANDDIGITS).getNewPassword(12, 12));
        userData.setTokenType(UserDataVOWS.TOKEN_TYPE_USERGENERATED);
        try {
            ejbcaWS.editUser(userData);
        } catch (Exception e) {
            log.error("Problem to edit user.", e);
            return false;
        }
        return true;
    }

    /**
     * Get the CA name
     * 
     * @param caId The ID of the sought CA
     * 
     * @return the name
     */
    private String getCAName(EjbcaWS ejbcaWS, int caId) {
        final Map<Integer, String> mCA = new HashMap<Integer, String>();
        try {
            for (NameAndId nameAndId : ejbcaWS.getAvailableCAs()) {
                mCA.put(Integer.valueOf(nameAndId.getId()), nameAndId.getName());
                log.debug("CA. id: " + nameAndId.getId() + " name: " + nameAndId.getName());
            }
        } catch (Exception e) {
            log.error("WS not working", e);
            return null;
        }
        return mCA.get(Integer.valueOf(caId));
    }

    /**
     * This method sends a keypair off to be signed by the CA that issued the original keychain.
     * 
     * @return a certificate that has been signed by the CA. 
     * @throws KeyRenewalFailedException if any error occurs during signing
     * @throws CryptoTokenOfflineException 
     */
    @SuppressWarnings("unchecked")
    private X509Certificate signCertificateByCa(EjbcaWS ejbcaWS, OcspSigningCacheEntry ocspSigningCacheEntry)
            throws KeyRenewalFailedException, CryptoTokenOfflineException {
        /* Construct a certification request in order to have the new keystore certified by the CA. 
         */
        //final int caId = CertTools.stringToBCDNString(tokenAndChain.getCaCertificate().getSubjectDN().toString()).hashCode();
        final int caId = CertTools.getSubjectDN(ocspSigningCacheEntry.getCaCertificateChain().get(0)).hashCode();
        final X509Certificate ocspSigningCertificate = ocspSigningCacheEntry.getOcspSigningCertificate();
        final UserDataVOWS userData = getUserDataVOWS(ejbcaWS, ocspSigningCertificate, caId);
        if (userData == null) {
            final String msg = "User data for certificate with subject DN '"
                    + CertTools.getSubjectDN(ocspSigningCertificate) + "' was not found.";
            log.error(msg);
            throw new KeyRenewalFailedException(msg);
        }
        editUser(ejbcaWS, userData);
        final int internalKeyBindingId = ocspSigningCacheEntry.getOcspKeyBinding().getId();
        final byte[] pkcs10CertificationRequest;
        try {
            pkcs10CertificationRequest = internalKeyBindingMgmtSession.generateCsrForNextKey(authenticationToken,
                    internalKeyBindingId);
        } catch (AuthorizationDeniedException e) {
            throw new KeyRenewalFailedException(e);
        }
        CertificateResponse certificateResponse;
        try {
            certificateResponse = ejbcaWS.pkcs10Request(userData.getUsername(), userData.getPassword(),
                    new String(Base64.encode(pkcs10CertificationRequest)), null,
                    CertificateHelper.RESPONSETYPE_CERTIFICATE);
        } catch (Exception e) {
            //Way too many silly exceptions to handle, wrap instead.
            throw new KeyRenewalFailedException(e);
        }
        if (certificateResponse == null) {
            throw new KeyRenewalFailedException("Certificate Response was not received");
        }

        Collection<X509Certificate> certificates;
        try {
            certificates = (Collection<X509Certificate>) CertificateFactory.getInstance("X.509")
                    .generateCertificates(new ByteArrayInputStream(Base64.decode(certificateResponse.getData())));
        } catch (CertificateException e) {
            throw new KeyRenewalFailedException(e);
        }
        final byte[] publicKeyBytes;
        try {
            publicKeyBytes = internalKeyBindingMgmtSession
                    .getNextPublicKeyForInternalKeyBinding(authenticationToken, internalKeyBindingId);
        } catch (AuthorizationDeniedException e) {
            throw new KeyRenewalFailedException(e);
        }
        if (log.isDebugEnabled()) {
            log.debug("Number of certificates returned from WS: " + certificates.size());
        }
        X509Certificate signedCertificate = null;
        final X509Certificate caCertificate = ocspSigningCacheEntry.getCaCertificateChain().get(0);
        final PublicKey caCertificatePublicKey = caCertificate.getPublicKey();
        for (X509Certificate certificate : certificates) {
            if (log.isDebugEnabled()) {
                log.debug("Verifying certificate with SubjectDN : '" + CertTools.getSubjectDN(certificate)
                        + "' using public key from CA certificate with subject '"
                        + CertTools.getSubjectDN(caCertificate) + "'.");
            }
            try {
                certificate.verify(caCertificatePublicKey);
            } catch (Exception e) {
                //Ugly, but inherited from legacy code
                signedCertificate = null;
                log.error("Exception was caught when verifying certificate", e);
                continue;
            }
            // Comparing public keys is dependent on provider used, so we must ensure same provider is used for the public keys
            // Otherwise this will fail, even though it should work
            // Both certPublicKey and nextPublicKey is obtained using KeyTools.getPublicKeyFromBytes, which uses the BC provider
            final PublicKey certPublicKey = KeyTools.getPublicKeyFromBytes(certificate.getPublicKey().getEncoded());
            final PublicKey nextPublicKey = KeyTools.getPublicKeyFromBytes(publicKeyBytes);
            if (nextPublicKey.equals(certPublicKey)) {
                signedCertificate = certificate;
                break;
            } else if (log.isDebugEnabled()) {
                log.debug("Matching public keys failed: ");
                log.debug("Certificate public key: " + certificate.getPublicKey());
                log.debug("Next public key: " + nextPublicKey);
            }
        }
        if (signedCertificate == null) {
            throw new KeyRenewalFailedException("No certificate signed by correct CA generated.");
        }
        return signedCertificate;
    }

    /** @return the EJBCA WS object. */
    private EjbcaWS getEjbcaWS() {
        String webUrl = OcspConfiguration.getEjbcawsracliUrl();
        if (StringUtils.isEmpty(webUrl)) {
            // Automatic renewal is not enabled
            if (log.isDebugEnabled()) {
                log.debug("Automatic OCSP key/certificate renewal is not enabled, "
                        + OcspConfiguration.REKEYING_WSURL + " is empty.");
            }
            return null;
        }
        final SSLSocketFactory sslSocketFactory = getSSLSocketFactory();
        if (sslSocketFactory == null) {
            log.warn("No AuthenticationKeyBinding is configured. Unable to authenticate to EJBCA WebService.");
            return null;
        }
        HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);
        final URL ws_url;
        try {
            ws_url = new URL(webUrl + "?wsdl");
        } catch (MalformedURLException e) {
            log.warn("Problem with URL: '" + webUrl + "'", e);
            return null;
        }
        final QName qname = new QName("http://ws.protocol.core.ejbca.org/", "EjbcaWSService");
        if (log.isDebugEnabled()) {
            log.debug("web service. URL: " + ws_url + " QName: " + qname);
        }
        return new EjbcaWSService(ws_url, qname).getEjbcaWSPort();
    }

    private SSLSocketFactory getSSLSocketFactory() {
        final List<Integer> authenticationKeyBindingIds = internalKeyBindingMgmtSession
                .getInternalKeyBindingIds(authenticationToken, AuthenticationKeyBinding.IMPLEMENTATION_ALIAS);
        AuthenticationKeyBinding authenticationKeyBinding = null;
        for (Integer internalKeyBindingId : authenticationKeyBindingIds) {
            try {
                final InternalKeyBinding internalKeyBinding = internalKeyBindingMgmtSession
                        .getInternalKeyBindingReference(authenticationToken, internalKeyBindingId);
                if (internalKeyBinding.getStatus().equals(InternalKeyBindingStatus.ACTIVE)) {
                    // Use first active one
                    authenticationKeyBinding = (AuthenticationKeyBinding) internalKeyBinding;
                    break;
                }
            } catch (AuthorizationDeniedException e) {
                throw new RuntimeException(e);
            }
        }
        if (authenticationKeyBinding == null) {
            return null;
        }
        final CryptoToken cryptoToken = cryptoTokenManagementSession
                .getCryptoToken(authenticationKeyBinding.getCryptoTokenId());
        final X509Certificate sslCertificate = (X509Certificate) certificateStoreSession
                .findCertificateByFingerprint(authenticationKeyBinding.getCertificateId());
        final List<X509Certificate> chain = new ArrayList<X509Certificate>();
        chain.add(sslCertificate);
        chain.addAll(getCaCertificateChain(sslCertificate));
        final List<X509Certificate> trustedCertificates = getListOfTrustedCertificates(
                authenticationKeyBinding.getTrustedCertificateReferences());
        final String alias = authenticationKeyBinding.getKeyPairAlias();
        try {
            final TrustManager trustManagers[];
            if (trustedCertificates == null || trustedCertificates.isEmpty()) {
                trustManagers = new X509TrustManager[] { new X509TrustManagerAcceptAll() };
            } else {
                throw new RuntimeException("Configurable trust not yet implemented.");
            }
            final KeyManager keyManagers[] = new X509KeyManager[] {
                    new ClientX509KeyManager(alias, cryptoToken.getPrivateKey(alias), chain) };
            // Now construct a SSLContext using these (possibly wrapped) KeyManagers, and the TrustManagers.
            // We still use a null SecureRandom, indicating that the defaults should be used.
            final SSLContext context = SSLContext.getInstance("TLS");
            context.init(keyManagers, trustManagers, null);
            // Finally, we get a SocketFactory, and pass it on.
            return context.getSocketFactory();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (CryptoTokenOfflineException e) {
            e.printStackTrace();
        }
        return null;
    }

    private List<X509Certificate> getListOfTrustedCertificates(
            List<InternalKeyBindingTrustEntry> trustedCertificateReferences) {
        if (trustedCertificateReferences == null || trustedCertificateReferences.isEmpty()) {
            return null;
        }
        // TODO: Here we need to lookup all the trusted certificates from the provided references so a X509TrustManager can do verification later
        log.warn("Trusted references was non-empty, but will be ignored. (Not yet implemented.)");
        return null;
    }

    // TODO: This method also exists in OcspResponseGenSSB.. merge! to method call in certificateStoreSession
    private List<X509Certificate> getCaCertificateChain(final X509Certificate leafCertificate) {
        final List<X509Certificate> caCertificateChain = new ArrayList<X509Certificate>();
        X509Certificate currentLevelCertificate = leafCertificate;
        while (!CertTools.getIssuerDN(currentLevelCertificate)
                .equals(CertTools.getSubjectDN(currentLevelCertificate))) {
            final String issuerDn = CertTools.getIssuerDN(currentLevelCertificate);
            currentLevelCertificate = certificateStoreSession.findLatestX509CertificateBySubject(issuerDn);
            if (currentLevelCertificate == null) {
                log.warn("Unable to build certificate chain for OCSP signing certificate with Subject DN '"
                        + CertTools.getSubjectDN(leafCertificate) + "'. CA with Subject DN '" + issuerDn
                        + "' is missing in the database.");
                return null;
            }
            caCertificateChain.add(currentLevelCertificate);
        }
        return caCertificateChain;
    }

    /**
     * When the timer expires, this method will check through the cache and automatically renew keystore matching the predefined criteria, 
     * and which expire within the designated time frame.
     * 
     * According to JSR 220 FR (18.2.2), this method may not throw any exceptions.
     * 
     * Glassfish 2.1.1:
     * "Timeout method ....timeoutHandler(javax.ejb.Timer)must have TX attribute of TX_REQUIRES_NEW or TX_REQUIRED or TX_NOT_SUPPORTED"
     * JBoss 5.1.0.GA: We cannot mix timer updates with our EJBCA DataSource transactions. 
     * 
     * @param timer The timer whose expiration caused this notification.
     * 
     */
    @Timeout
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public void timeoutHandler(Timer timer) {
        long rekeyingUpdateTime = OcspConfiguration.getRekeyingUpdateTimeInSeconds();
        try {
            renewKeyStores(RENEW_ALL_KEYS, OcspConfiguration.getRekeyingSafetyMarginInSeconds());
        } catch (InvalidKeyException e) {
            log.error("A cached crypto token contains an invalid key pair. Stopping timers.", e);
        } catch (CryptoTokenOfflineException e) {
            //Rescheduling is handled in a finally clause in OcspKeyRenewalSessionBean.renewKeyStores(String, long)
            log.error(
                    "Crypto token was offline or unavailable during automatic update. Rescheduling a new timer in "
                            + rekeyingUpdateTime + " seconds.",
                    e);
        }

    }

    @Override
    public void startTimer() {
        cancelTimers();
        addTimer(OcspConfiguration.getRekeyingUpdateTimeInSeconds());
    }

    /**
     * Adds a timer to the bean
     * 
     * @param intervalInSeconds the time from now for the next timer to fire
     */
    // We don't want the appserver to persist/update the timer in the same transaction if they are stored in different non XA DataSources. This method
    // should not be run from within a transaction.
    private Timer addTimer(long intervalInSeconds) {
        if (log.isDebugEnabled()) {
            log.debug("addTimer: " + timerId);
        }
        return timerService.createTimer(intervalInSeconds * 1000, timerId);
    }

    /**
     * This method cancels all timers associated with this bean.
     */
    private void cancelTimers() {
        Collection<Timer> timers = timerService.getTimers();
        for (Timer timer : timers) {
            try {
                timer.cancel();
            } catch (NoSuchObjectLocalException e) {
                if (log.isDebugEnabled()) {
                    log.debug("Timer was already expired or canceled: " + timer.getInfo());
                }
            }
        }
    }
}