org.cesecore.certificates.ocsp.OcspResponseGeneratorSessionBean.java Source code

Java tutorial

Introduction

Here is the source code for org.cesecore.certificates.ocsp.OcspResponseGeneratorSessionBean.java

Source

/*************************************************************************
 *                                                                       *
 *  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.certificates.ocsp;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.UnrecoverableEntryException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
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.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.EJBException;
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 org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1GeneralizedTime;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.ocsp.RevokedInfo;
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.CRLReason;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.OCSPRespBuilder;
import org.bouncycastle.cert.ocsp.Req;
import org.bouncycastle.cert.ocsp.RevokedStatus;
import org.bouncycastle.cert.ocsp.UnknownStatus;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
import org.bouncycastle.util.encoders.Hex;
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.ca.CAConstants;
import org.cesecore.certificates.ca.CADoesntExistsException;
import org.cesecore.certificates.ca.CAInfo;
import org.cesecore.certificates.ca.CaSessionLocal;
import org.cesecore.certificates.ca.InvalidAlgorithmException;
import org.cesecore.certificates.ca.SignRequestException;
import org.cesecore.certificates.ca.SignRequestSignatureException;
import org.cesecore.certificates.ca.catoken.CAToken;
import org.cesecore.certificates.ca.catoken.CATokenConstants;
import org.cesecore.certificates.ca.internal.CaCertificateCache;
import org.cesecore.certificates.certificate.CertificateInfo;
import org.cesecore.certificates.certificate.CertificateStatus;
import org.cesecore.certificates.certificate.CertificateStatusHolder;
import org.cesecore.certificates.certificate.CertificateStoreSessionLocal;
import org.cesecore.certificates.certificate.HashID;
import org.cesecore.certificates.certificateprofile.CertificateProfileConstants;
import org.cesecore.certificates.certificatetransparency.CertificateTransparency;
import org.cesecore.certificates.certificatetransparency.CertificateTransparencyFactory;
import org.cesecore.certificates.ocsp.cache.OcspConfigurationCache;
import org.cesecore.certificates.ocsp.cache.OcspExtensionsCache;
import org.cesecore.certificates.ocsp.cache.OcspSigningCache;
import org.cesecore.certificates.ocsp.cache.OcspSigningCacheEntry;
import org.cesecore.certificates.ocsp.exception.CryptoProviderException;
import org.cesecore.certificates.ocsp.exception.MalformedRequestException;
import org.cesecore.certificates.ocsp.exception.OcspFailureException;
import org.cesecore.certificates.ocsp.extension.OCSPExtension;
import org.cesecore.certificates.ocsp.keys.CardKeys;
import org.cesecore.certificates.ocsp.logging.AuditLogger;
import org.cesecore.certificates.ocsp.logging.PatternLogger;
import org.cesecore.certificates.ocsp.logging.TransactionLogger;
import org.cesecore.certificates.util.AlgorithmTools;
import org.cesecore.config.ConfigurationHolder;
import org.cesecore.config.GlobalOcspConfiguration;
import org.cesecore.config.OcspConfiguration;
import org.cesecore.configuration.GlobalConfigurationSessionLocal;
import org.cesecore.internal.InternalResources;
import org.cesecore.jndi.JndiConstants;
import org.cesecore.keybind.CertificateImportException;
import org.cesecore.keybind.InternalKeyBindingDataSessionLocal;
import org.cesecore.keybind.InternalKeyBindingInfo;
import org.cesecore.keybind.InternalKeyBindingMgmtSessionLocal;
import org.cesecore.keybind.InternalKeyBindingNameInUseException;
import org.cesecore.keybind.InternalKeyBindingStatus;
import org.cesecore.keybind.InternalKeyBindingTrustEntry;
import org.cesecore.keybind.impl.AuthenticationKeyBinding;
import org.cesecore.keybind.impl.OcspKeyBinding;
import org.cesecore.keybind.impl.OcspKeyBinding.ResponderIdType;
import org.cesecore.keys.token.BaseCryptoToken;
import org.cesecore.keys.token.CachingKeyStoreWrapper;
import org.cesecore.keys.token.CryptoToken;
import org.cesecore.keys.token.CryptoTokenManagementSessionLocal;
import org.cesecore.keys.token.CryptoTokenOfflineException;
import org.cesecore.keys.token.CryptoTokenSessionLocal;
import org.cesecore.keys.token.PKCS11CryptoToken;
import org.cesecore.keys.token.SoftCryptoToken;
import org.cesecore.keys.token.p11.Pkcs11SlotLabelType;
import org.cesecore.keys.util.KeyTools;
import org.cesecore.util.CertTools;
import org.cesecore.util.log.ProbableErrorHandler;
import org.cesecore.util.log.SaferAppenderListener;
import org.cesecore.util.log.SaferDailyRollingFileAppender;

/**
 * This SSB generates OCSP responses. 
 * 
 * @version $Id: OcspResponseGeneratorSessionBean.java 21135 2015-04-23 12:53:16Z mikekushner $
 */
@Stateless(mappedName = JndiConstants.APP_JNDI_PREFIX + "OcspResponseGeneratorSessionRemote")
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public class OcspResponseGeneratorSessionBean
        implements OcspResponseGeneratorSessionRemote, OcspResponseGeneratorSessionLocal, SaferAppenderListener {

    /** Max size of a request is 100000 bytes */
    private static final int MAX_REQUEST_SIZE = 100000;
    /** Timer identifiers */
    private static final int TIMERID_OCSPSIGNINGCACHE = 1;

    private static final String hardTokenClassName = OcspConfiguration.getHardTokenClassName();

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

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

    private static volatile ExecutorService service = Executors.newCachedThreadPool();

    @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;

    @EJB
    private CaSessionLocal caSession;
    @EJB
    private CertificateStoreSessionLocal certificateStoreSession;
    @EJB
    private CryptoTokenSessionLocal cryptoTokenSession;
    @EJB
    private CryptoTokenManagementSessionLocal cryptoTokenManagementSession;
    @EJB
    private InternalKeyBindingDataSessionLocal internalKeyBindingDataSession;
    @EJB
    private InternalKeyBindingMgmtSessionLocal internalKeyBindingMgmtSession;
    @EJB
    private GlobalConfigurationSessionLocal globalConfigurationSession;

    private JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter();

    @PostConstruct
    public void init() {
        if (OcspConfiguration.getLogSafer() == true) {
            SaferDailyRollingFileAppender.addSubscriber(this);
            log.info("Added us as subscriber: " + SaferDailyRollingFileAppender.class.getCanonicalName());
        }
        timerService = sessionContext.getTimerService();
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public void initTimers() {
        // Reload OCSP signing cache, and cancel/create timers if there are no timers or if the cache is empty (probably a fresh startup)
        if (getTimerCount(TIMERID_OCSPSIGNINGCACHE) == 0 || OcspSigningCache.INSTANCE.getEntries().isEmpty()) {
            reloadOcspSigningCache();
        } else {
            log.info("Not initing OCSP reload timers, there are already some.");
        }
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public void reloadOcspExtensionsCache() {
        OcspExtensionsCache.INSTANCE.reloadCache();
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public void clearCTFailFastCache() {
        final CertificateTransparency ct = CertificateTransparencyFactory.getInstance();
        if (ct != null) {
            ct.clearCaches();
        }
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public void reloadOcspSigningCache() {
        if (log.isTraceEnabled()) {
            log.trace(">reloadOcspSigningCache");
        }
        // Cancel any waiting timers of this type
        cancelTimers(TIMERID_OCSPSIGNINGCACHE);
        try {
            // Verify card key holder
            if (log.isDebugEnabled() && (CardKeyHolder.getInstance().getCardKeys() == null)) {
                log.debug(intres.getLocalizedMessage("ocsp.classnotfound", hardTokenClassName));
            }
            GlobalOcspConfiguration ocspConfiguration = (GlobalOcspConfiguration) globalConfigurationSession
                    .getCachedConfiguration(GlobalOcspConfiguration.OCSP_CONFIGURATION_ID);
            OcspSigningCache.INSTANCE.stagingStart();
            try {
                // Populate OcspSigningCache
                // Add all potential CA's as OCSP responders to the staging area
                for (final Integer caId : caSession.getAllCaIds()) {
                    final List<X509Certificate> caCertificateChain = new ArrayList<X509Certificate>();
                    try {
                        final CAInfo caInfo = caSession.getCAInfoInternal(caId.intValue());
                        if (caInfo.getCAType() == CAInfo.CATYPE_CVC) {
                            // Bravely ignore OCSP for CVC CAs
                            continue;
                        }
                        if (caInfo.getStatus() == CAConstants.CA_ACTIVE) {
                            //Cache active CAs as signers
                            if (log.isDebugEnabled()) {
                                log.debug(
                                        "Processing X509 CA " + caInfo.getName() + " (" + caInfo.getCAId() + ").");
                            }
                            final CAToken caToken = caInfo.getCAToken();
                            final CryptoToken cryptoToken = cryptoTokenSession
                                    .getCryptoToken(caToken.getCryptoTokenId());
                            if (cryptoToken == null) {
                                log.info("Excluding CA with id " + caId
                                        + " for OCSP signing consideration due to missing CryptoToken.");
                                continue;
                            }
                            for (final Certificate certificate : caInfo.getCertificateChain()) {
                                caCertificateChain.add((X509Certificate) certificate);
                            }
                            final String keyPairAlias;
                            try {
                                keyPairAlias = caToken.getAliasFromPurpose(CATokenConstants.CAKEYPURPOSE_CERTSIGN);
                            } catch (CryptoTokenOfflineException e) {
                                log.warn("Referenced private key with purpose "
                                        + CATokenConstants.CAKEYPURPOSE_CERTSIGN
                                        + " could not be used. CryptoToken is off-line for CA with id " + caId
                                        + ": " + e.getMessage());
                                continue;
                            }
                            final PrivateKey privateKey;
                            try {
                                privateKey = cryptoToken.getPrivateKey(keyPairAlias);
                            } catch (CryptoTokenOfflineException e) {
                                log.warn("Referenced private key with alias " + keyPairAlias
                                        + " could not be used. CryptoToken is off-line for CA with id " + caId
                                        + ": " + e.getMessage());
                                continue;
                            }
                            if (privateKey == null) {
                                log.warn("Referenced private key with alias " + keyPairAlias
                                        + " does not exist. Ignoring CA with id " + caId);
                                continue;
                            }
                            final String signatureProviderName = cryptoToken.getSignProviderName();
                            if (caCertificateChain.size() > 0) {
                                X509Certificate caCertificate = caCertificateChain.get(0);
                                CertificateStatus caCertificateStatus = certificateStoreSession.getStatus(
                                        CertTools.getIssuerDN(caCertificate),
                                        CertTools.getSerialNumber(caCertificate));
                                OcspSigningCache.INSTANCE.stagingAdd(new OcspSigningCacheEntry(caCertificate,
                                        caCertificateStatus, caCertificateChain, null, privateKey,
                                        signatureProviderName, null, OcspConfiguration.getResponderIdType()));
                                // Check if CA cert has been revoked somehow. Always make this check, even if this CA has an OCSP signing certificate, because
                                // signing will still fail even if the signing cert is valid. Shouldn't happen, but log it just in case.
                                if (caCertificateStatus.equals(CertificateStatus.REVOKED)) {
                                    log.warn("Active CA with subject DN '" + CertTools.getSubjectDN(caCertificate)
                                            + "' and serial number " + CertTools.getSerialNumber(caCertificate)
                                            + " has a revoked certificate.");
                                }
                                //Check if CA cert is expired
                                if (!CertTools.isCertificateValid(caCertificate)) {
                                    log.warn("Active CA with subject DN '" + CertTools.getSubjectDN(caCertificate)
                                            + "' and serial number " + CertTools.getSerialNumber(caCertificate)
                                            + " has an expired certificate.");
                                }
                            } else {
                                log.warn("CA with ID " + caId
                                        + " appears to lack a certificate in the database. This may be a serious error if not in a test environment.");
                            }
                        } else if (caInfo.getStatus() == CAConstants.CA_EXTERNAL) {
                            // If set, all external CA's without a keybinding (set below) will be responded to by the default responder. 
                            for (final Certificate certificate : caInfo.getCertificateChain()) {
                                caCertificateChain.add((X509Certificate) certificate);
                            }
                            CertificateStatus caCertificateStatus = certificateStoreSession.getStatus(
                                    CertTools.getIssuerDN(caCertificateChain.get(0)),
                                    CertTools.getSerialNumber(caCertificateChain.get(0)));
                            // Check if CA cert has been revoked somehow. Always make this check, even if this CA has an OCSP signing certificate, because
                            // signing will still fail even if the signing cert is valid. 
                            if (caCertificateStatus.equals(CertificateStatus.REVOKED)) {
                                log.warn("External CA with subject DN '"
                                        + CertTools.getSubjectDN(caCertificateChain.get(0)) + "' and serial number "
                                        + CertTools.getSerialNumber(caCertificateChain.get(0))
                                        + " has a revoked certificate.");
                            }
                            //Check if CA cert is expired
                            if (!CertTools.isCertificateValid(caCertificateChain.get(0))) {
                                log.warn("External CA with subject DN '"
                                        + CertTools.getSubjectDN(caCertificateChain.get(0)) + "' and serial number "
                                        + CertTools.getSerialNumber(caCertificateChain.get(0))
                                        + " has an expired certificate.");
                            }
                            //Add an entry with just a chain and nothing else
                            OcspSigningCache.INSTANCE.stagingAdd(
                                    new OcspSigningCacheEntry(caCertificateChain.get(0), caCertificateStatus, null,
                                            null, null, null, null, OcspConfiguration.getResponderIdType()));

                        }
                    } catch (CADoesntExistsException e) {
                        // Should only happen if the CA was deleted between the getAvailableCAs and the last one
                        log.warn("CA with Id " + caId + " disappeared during reload operation.");
                    }
                }
                // Add all potential InternalKeyBindings as OCSP responders to the staging area, overwriting CA entries from before
                for (final int internalKeyBindingId : internalKeyBindingDataSession
                        .getIds(OcspKeyBinding.IMPLEMENTATION_ALIAS)) {
                    final OcspKeyBinding ocspKeyBinding = (OcspKeyBinding) internalKeyBindingDataSession
                            .getInternalKeyBinding(internalKeyBindingId);
                    if (log.isDebugEnabled()) {
                        log.debug("Processing " + ocspKeyBinding.getName() + " (" + ocspKeyBinding.getId() + ")");
                    }
                    if (!ocspKeyBinding.getStatus().equals(InternalKeyBindingStatus.ACTIVE)) {
                        if (log.isDebugEnabled()) {
                            log.debug("Ignoring OcspKeyBinding since it is not active.");
                        }
                        continue;
                    }
                    final X509Certificate ocspSigningCertificate = (X509Certificate) certificateStoreSession
                            .findCertificateByFingerprint(ocspKeyBinding.getCertificateId());
                    if (ocspSigningCertificate == null) {
                        log.warn("OCSP signing certificate with referenced fingerprint "
                                + ocspKeyBinding.getCertificateId()
                                + " does not exist. Ignoring internalKeyBinding with id " + ocspKeyBinding.getId());
                        continue;
                    }
                    //Make the same check as above 
                    if (certificateStoreSession
                            .getStatus(CertTools.getIssuerDN(ocspSigningCertificate),
                                    CertTools.getSerialNumber(ocspSigningCertificate))
                            .equals(CertificateStatus.REVOKED)) {
                        log.warn("OCSP Responder certificate with subject DN '"
                                + CertTools.getSubjectDN(ocspSigningCertificate) + "' and serial number "
                                + CertTools.getSerialNumber(ocspSigningCertificate) + " is revoked.");
                    }
                    //Check if signing cert is expired
                    if (!CertTools.isCertificateValid(ocspSigningCertificate)) {
                        log.warn("OCSP Responder certificate with subject DN '"
                                + CertTools.getSubjectDN(ocspSigningCertificate) + "' and serial number "
                                + CertTools.getSerialNumber(ocspSigningCertificate) + " is expired.");
                    }

                    OcspSigningCacheEntry ocspSigningCacheEntry = makeOcspSigningCacheEntry(ocspSigningCertificate,
                            ocspKeyBinding);
                    if (ocspSigningCacheEntry == null) {
                        continue;
                    } else {
                        OcspSigningCache.INSTANCE.stagingAdd(ocspSigningCacheEntry);
                    }
                }
                OcspSigningCache.INSTANCE.stagingCommit(ocspConfiguration.getOcspDefaultResponderReference());
            } finally {
                OcspSigningCache.INSTANCE.stagingRelease();
            }
        } finally {
            // Schedule a new timer of this type
            addTimer(OcspConfiguration.getSigningCertsValidTimeInMilliseconds(), TIMERID_OCSPSIGNINGCACHE);
        }
    }

    /**
     * Constructs an OcspSigningCacheEntry from the given parameters.
     * 
     * @param ocspSigningCertificate The signing certificate associated with the key binding. May be found separately, so given as a separate parameter
     * @param ocspKeyBinding the Key Binding to base the cache entry off of. 
     * @return an OcspSigningCacheEntry, or null if any error was encountered.
     */
    private OcspSigningCacheEntry makeOcspSigningCacheEntry(X509Certificate ocspSigningCertificate,
            OcspKeyBinding ocspKeyBinding) {
        final List<X509Certificate> caCertificateChain = getCaCertificateChain(ocspSigningCertificate);
        if (caCertificateChain == null) {
            log.warn("OcspKeyBinding " + ocspKeyBinding.getName() + " ( " + ocspKeyBinding.getId()
                    + ") has an signing certificate, but no chain and will be ignored.");
            return null;
        }
        final CryptoToken cryptoToken = cryptoTokenSession.getCryptoToken(ocspKeyBinding.getCryptoTokenId());
        if (cryptoToken == null) {
            log.warn("Referenced CryptoToken with id " + ocspKeyBinding.getCryptoTokenId()
                    + " does not exist. Ignoring OcspKeyBinding with id " + ocspKeyBinding.getId());
            return null;
        }
        final PrivateKey privateKey;
        try {
            privateKey = cryptoToken.getPrivateKey(ocspKeyBinding.getKeyPairAlias());
        } catch (CryptoTokenOfflineException e) {
            log.warn("Referenced private key with alias " + ocspKeyBinding.getKeyPairAlias()
                    + " could not be used. CryptoToken is off-line for OcspKeyBinding with id "
                    + ocspKeyBinding.getId() + ": " + e.getMessage());
            return null;
        }
        if (privateKey == null) {
            log.warn("Referenced private key with alias " + ocspKeyBinding.getKeyPairAlias()
                    + " does not exist. Ignoring OcspKeyBinding with id " + ocspKeyBinding.getId());
            return null;
        }
        final String signatureProviderName = cryptoToken.getSignProviderName();
        if (log.isDebugEnabled()) {
            log.debug("Adding OcspKeyBinding " + ocspKeyBinding.getId() + ", " + ocspKeyBinding.getName());
        }
        CertificateStatus certificateStatus = certificateStoreSession.getStatus(
                CertTools.getIssuerDN(caCertificateChain.get(0)),
                CertTools.getSerialNumber(caCertificateChain.get(0)));
        final int respIdType;
        if (ResponderIdType.NAME.equals(ocspKeyBinding.getResponderIdType())) {
            respIdType = OcspConfiguration.RESPONDERIDTYPE_NAME;
        } else {
            respIdType = OcspConfiguration.RESPONDERIDTYPE_KEYHASH;
        }
        return new OcspSigningCacheEntry(caCertificateChain.get(0), certificateStatus, caCertificateChain,
                ocspSigningCertificate, privateKey, signatureProviderName, ocspKeyBinding, respIdType);
    }

    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;
    }

    @Override
    public void setCanlog(boolean canLog) {
        CanLogCache.INSTANCE.setCanLog(canLog);
    }

    /**
     * This method exists solely to avoid code duplication when error handling in getOcspResponse.
     * 
     * @param responseGenerator A OCSPRespBuilder for generating a response with state INTERNAL_ERROR.
     * @param transactionLogger The TransactionLogger for this call.
     * @param auditLogger The AuditLogger for this call.
     * @param e The thrown exception.
     * @return a response with state INTERNAL_ERROR.
     * @throws OCSPException if generation of the response failed.
     */
    private OCSPResp processDefaultError(OCSPRespBuilder responseGenerator, TransactionLogger transactionLogger,
            AuditLogger auditLogger, Throwable e) throws OCSPException {
        if (transactionLogger.isEnabled()) {
            transactionLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME);
        }
        if (auditLogger.isEnabled()) {
            auditLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME);
        }
        String errMsg = intres.getLocalizedMessage("ocsp.errorprocessreq", e.getMessage());
        log.error(errMsg, e);
        if (transactionLogger.isEnabled()) {
            transactionLogger.paramPut(TransactionLogger.STATUS, OCSPRespBuilder.INTERNAL_ERROR);
            transactionLogger.writeln();
        }
        if (auditLogger.isEnabled()) {
            auditLogger.paramPut(AuditLogger.STATUS, OCSPRespBuilder.INTERNAL_ERROR);
        }
        return responseGenerator.build(OCSPRespBuilder.INTERNAL_ERROR, null); // RFC 2560: responseBytes are not set on error.
    }

    /**
     * Select the preferred OCSP response sigAlg according to RFC6960 Section 4.4.7 in the following order:
     * 
     *    1. Select an algorithm specified as a preferred signature algorithm in the client request if it is 
     *       an acceptable algorithm by EJBCA.
     *    2. Select the signature algorithm used to sign a certificate revocation list (CRL) issued by the 
     *       certificate issuer providing status information for the certificate specified by CertID.
     *       (NOT APPLIED)
     *    3. Select the signature algorithm used to sign the OCSPRequest if it is an acceptable algorithm in EJBCA.
     *    4. Select a signature algorithm that has been advertised as being the default signature algorithm for 
     *       the signing service using an out-of-band mechanism.
     *    5. Select a mandatory or recommended signature algorithm specified for the version of OCSP in use, aka. 
     *       specified in the properties file.
     * 
     *    The acceptable algorithm by EJBCA are the algorithms specified in ocsp.properties file in 'ocsp.signaturealgorithm'
     * 
     * @param req
     * @param ocspSigningCacheEntry
     * @param signerCert
     * @return
     */
    private String getSigAlg(OCSPReq req, final OcspSigningCacheEntry ocspSigningCacheEntry,
            final X509Certificate signerCert) {
        String sigAlg = null;
        PublicKey pk = signerCert.getPublicKey();
        // Start with the preferred signature algorithm in the OCSP request
        final Extension preferredSigAlgExtension = req
                .getExtension(new ASN1ObjectIdentifier(OCSPObjectIdentifiers.id_pkix_ocsp + ".8"));
        if (preferredSigAlgExtension != null) {
            final ASN1Sequence preferredSignatureAlgorithms = ASN1Sequence
                    .getInstance(preferredSigAlgExtension.getParsedValue());
            for (int i = 0; i < preferredSignatureAlgorithms.size(); i++) {
                final ASN1Encodable asn1Encodable = preferredSignatureAlgorithms.getObjectAt(i);
                final ASN1ObjectIdentifier algorithmOid;
                if (asn1Encodable instanceof ASN1ObjectIdentifier) {
                    // Handle client requests that were adapted to EJBCA 6.1.0's implementation
                    log.info(
                            "OCSP request's PreferredSignatureAlgorithms did not contain an PreferredSignatureAlgorithm, but instead an algorithm OID."
                                    + " This will not be supported in a future versions of EJBCA.");
                    algorithmOid = (ASN1ObjectIdentifier) asn1Encodable;
                } else {
                    // Handle client requests that provide a proper AlgorithmIdentifier as specified in RFC 6960 + RFC 5280
                    final ASN1Sequence preferredSignatureAlgorithm = ASN1Sequence.getInstance(asn1Encodable);
                    final AlgorithmIdentifier algorithmIdentifier = AlgorithmIdentifier
                            .getInstance(preferredSignatureAlgorithm.getObjectAt(0));
                    algorithmOid = algorithmIdentifier.getAlgorithm();
                }
                if (algorithmOid != null) {
                    sigAlg = AlgorithmTools.getAlgorithmNameFromOID(algorithmOid);
                    if (sigAlg != null && OcspConfiguration.isAcceptedSignatureAlgorithm(sigAlg)
                            && AlgorithmTools.isCompatibleSigAlg(pk, sigAlg)) {
                        if (log.isDebugEnabled()) {
                            log.debug(
                                    "Using OCSP response signature algorithm extracted from OCSP request extension. "
                                            + algorithmOid);
                        }
                        return sigAlg;
                    }
                }
            }
        }
        // the signature algorithm used to sign the OCSPRequest
        if (req.getSignatureAlgOID() != null) {
            sigAlg = AlgorithmTools.getAlgorithmNameFromOID(req.getSignatureAlgOID());
            if (OcspConfiguration.isAcceptedSignatureAlgorithm(sigAlg)
                    && AlgorithmTools.isCompatibleSigAlg(pk, sigAlg)) {
                if (log.isDebugEnabled()) {
                    log.debug(
                            "OCSP response signature algorithm: the signature algorithm used to sign the OCSPRequest. "
                                    + sigAlg);
                }
                return sigAlg;
            }
        }
        // The signature algorithm that has been advertised as being the default signature algorithm for the signing service using an
        // out-of-band mechanism.
        if (ocspSigningCacheEntry.isUsingSeparateOcspSigningCertificate()) {
            // If we have an OcspKeyBinding we use this configuration to override the default
            sigAlg = ocspSigningCacheEntry.getOcspKeyBinding().getSignatureAlgorithm();
            if (log.isDebugEnabled()) {
                log.debug(
                        "OCSP response signature algorithm: the signature algorithm that has been advertised as being the default signature algorithm "
                                + "for the signing service using an out-of-band mechanism. " + sigAlg);
            }
            return sigAlg;
        }
        // The signature algorithm specified for the version of OCSP in use.
        String sigAlgs = OcspConfiguration.getSignatureAlgorithm();
        sigAlg = getSigningAlgFromAlgSelection(sigAlgs, pk);
        if (log.isDebugEnabled()) {
            log.debug("Using configured signature algorithm to sign OCSP response. " + sigAlg);
        }
        return sigAlg;
    }

    /**
     * This method takes byte array and translates it onto a OCSPReq class.
     * 
     * @param request the byte array in question.
     * @param remoteAddress The remote address of the HttpRequest associated with this array.
     * @param transactionLogger A transaction logger.
     * @return
     * @throws MalformedRequestException
     * @throws SignRequestException thrown if an unsigned request was processed when system configuration requires that all requests be signed.
     * @throws CertificateException
     * @throws NoSuchAlgorithmException
     * @throws SignRequestSignatureException
     */
    private OCSPReq translateRequestFromByteArray(byte[] request, String remoteAddress,
            TransactionLogger transactionLogger) throws MalformedRequestException, SignRequestException,
            SignRequestSignatureException, CertificateException, NoSuchAlgorithmException {
        final OCSPReq ocspRequest;
        try {
            ocspRequest = new OCSPReq(request);
        } catch (IOException e) {
            throw new MalformedRequestException("Could not form OCSP request", e);
        }
        if (ocspRequest.getRequestorName() == null) {
            if (log.isDebugEnabled()) {
                log.debug("Requestor name is null");
            }
        } else {
            if (log.isDebugEnabled()) {
                log.debug("Requestor name is: " + ocspRequest.getRequestorName().toString());
            }
            if (transactionLogger.isEnabled()) {
                transactionLogger.paramPut(TransactionLogger.REQ_NAME, ocspRequest.getRequestorName().toString());
            }
        }

        /**
         * check the signature if contained in request. if the request does not contain a signature and the servlet is configured in the way the a
         * signature is required we send back 'sigRequired' response.
         */
        if (log.isDebugEnabled()) {
            log.debug("Incoming OCSP request is signed : " + ocspRequest.isSigned());
        }
        if (ocspRequest.isSigned()) {
            final X509Certificate signercert = checkRequestSignature(remoteAddress, ocspRequest);
            final String signercertIssuerName = CertTools.getIssuerDN(signercert);
            final BigInteger signercertSerNo = CertTools.getSerialNumber(signercert);
            final String signercertSubjectName = CertTools.getSubjectDN(signercert);
            if (transactionLogger.isEnabled()) {
                transactionLogger.paramPut(TransactionLogger.SIGN_ISSUER_NAME_DN, signercertIssuerName);
                transactionLogger.paramPut(TransactionLogger.SIGN_SERIAL_NO,
                        signercert.getSerialNumber().toByteArray());
                transactionLogger.paramPut(TransactionLogger.SIGN_SUBJECT_NAME, signercertSubjectName);
                transactionLogger.paramPut(PatternLogger.REPLY_TIME, TransactionLogger.REPLY_TIME);
            }
            // Check if we have configured request verification using the old property file way..
            boolean enforceRequestSigning = OcspConfiguration.getEnforceRequestSigning();
            // Next, check if there is an OcspKeyBinding where signing is required and configured for this request
            // In the case where multiple requests are bundled together they all must be trusting the signer
            for (final Req req : ocspRequest.getRequestList()) {
                OcspSigningCacheEntry ocspSigningCacheEntry = OcspSigningCache.INSTANCE.getEntry(req.getCertID());
                if (ocspSigningCacheEntry == null) {
                    if (log.isTraceEnabled()) {
                        log.trace("Using default responder to check signature.");
                    }
                    ocspSigningCacheEntry = OcspSigningCache.INSTANCE.getDefaultEntry();
                }
                if (ocspSigningCacheEntry != null
                        && ocspSigningCacheEntry.isUsingSeparateOcspSigningCertificate()) {
                    if (log.isTraceEnabled()) {
                        log.trace("ocspSigningCacheEntry.isUsingSeparateOcspSigningCertificate: "
                                + ocspSigningCacheEntry.isUsingSeparateOcspSigningCertificate());
                    }
                    final OcspKeyBinding ocspKeyBinding = ocspSigningCacheEntry.getOcspKeyBinding();
                    if (log.isTraceEnabled()) {
                        log.trace("OcspKeyBinding " + ocspKeyBinding.getId() + ", RequireTrustedSignature: "
                                + ocspKeyBinding.getRequireTrustedSignature());
                    }
                    if (ocspKeyBinding.getRequireTrustedSignature()) {
                        enforceRequestSigning = true;
                        boolean isTrusted = false;
                        final List<InternalKeyBindingTrustEntry> trustedCertificateReferences = ocspKeyBinding
                                .getTrustedCertificateReferences();
                        if (trustedCertificateReferences.isEmpty()) {
                            // We trust ANY cert from a known CA
                            isTrusted = true;
                        } else {
                            for (final InternalKeyBindingTrustEntry trustEntry : trustedCertificateReferences) {
                                final int trustedCaId = trustEntry.getCaId();
                                final BigInteger trustedSerialNumber = trustEntry.fetchCertificateSerialNumber();
                                if (log.isTraceEnabled()) {
                                    log.trace("Processing trustedCaId=" + trustedCaId + " trustedSerialNumber="
                                            + trustedSerialNumber + " signercertIssuerName.hashCode()="
                                            + signercertIssuerName.hashCode() + " signercertSerNo="
                                            + signercertSerNo);
                                }
                                if (trustedCaId == signercertIssuerName.hashCode()) {
                                    if (trustedSerialNumber == null) {
                                        // We trust any certificate from this CA
                                        isTrusted = true;
                                        if (log.isTraceEnabled()) {
                                            log.trace(
                                                    "Trusting request signature since ANY certificate from issuer "
                                                            + trustedCaId + " is trusted.");
                                        }
                                        break;
                                    } else if (signercertSerNo.equals(trustedSerialNumber)) {
                                        // We trust this particular certificate from this CA
                                        isTrusted = true;
                                        if (log.isTraceEnabled()) {
                                            log.trace(
                                                    "Trusting request signature since certificate with serialnumber "
                                                            + trustedSerialNumber + " from issuer " + trustedCaId
                                                            + " is trusted.");
                                        }
                                        break;
                                    }
                                }
                            }
                        }
                        if (!isTrusted) {
                            final String infoMsg = intres.getLocalizedMessage("ocsp.infosigner.notallowed",
                                    signercertSubjectName, signercertIssuerName, signercertSerNo.toString(16));
                            log.info(infoMsg);
                            throw new SignRequestSignatureException(infoMsg);
                        }
                    }
                }
            }
            if (enforceRequestSigning) {
                // If it verifies OK, check if it is revoked
                final CertificateStatus status = certificateStoreSession.getStatus(signercertIssuerName,
                        signercertSerNo);
                /*
                 * If rci == null it means the certificate does not exist in database, we then treat it as ok, because it may be so that only revoked
                 * certificates is in the (external) OCSP database.
                 */
                if (status.equals(CertificateStatus.REVOKED)) {
                    String serno = signercertSerNo.toString(16);
                    String infoMsg = intres.getLocalizedMessage("ocsp.infosigner.revoked", signercertSubjectName,
                            signercertIssuerName, serno);
                    log.info(infoMsg);
                    throw new SignRequestSignatureException(infoMsg);
                }
            }
        } else {
            if (OcspConfiguration.getEnforceRequestSigning()) {
                // Signature required
                throw new SignRequestException("Signature required");
            }
            // Next, check if there is an OcspKeyBinding where signing is required and configured for this request
            // In the case where multiple requests are bundled together they all must be trusting the signer
            for (final Req req : ocspRequest.getRequestList()) {
                OcspSigningCacheEntry ocspSigningCacheEntry = OcspSigningCache.INSTANCE.getEntry(req.getCertID());
                if (ocspSigningCacheEntry == null) {
                    ocspSigningCacheEntry = OcspSigningCache.INSTANCE.getDefaultEntry();
                }
                if (ocspSigningCacheEntry != null
                        && ocspSigningCacheEntry.isUsingSeparateOcspSigningCertificate()) {
                    final OcspKeyBinding ocspKeyBinding = ocspSigningCacheEntry.getOcspKeyBinding();
                    if (ocspKeyBinding.getRequireTrustedSignature()) {
                        throw new SignRequestException("Signature required");
                    }
                }
            }
        }
        return ocspRequest;
    }

    /**
     * Checks the signature on an OCSP request. Does not check for revocation of the signer certificate
     * 
     * @param clientRemoteAddr The IP address or host name of the remote client that sent the request, can be null.
     * @param req The signed OCSPReq
     * @return X509Certificate which is the certificate that signed the OCSP request
     * @throws SignRequestSignatureException if signature verification fail, or if the signing certificate is not authorized
     * @throws SignRequestException if there is no signature on the OCSPReq
     * @throws OCSPException if the request can not be parsed to retrieve certificates
     * @throws NoSuchProviderException if the BC provider is not installed
     * @throws CertificateException if the certificate can not be parsed
     * @throws NoSuchAlgorithmException if the certificate contains an unsupported algorithm
     * @throws InvalidKeyException if the certificate, or CA key is invalid
     */
    private X509Certificate checkRequestSignature(String clientRemoteAddr, OCSPReq req) throws SignRequestException,
            SignRequestSignatureException, CertificateException, NoSuchAlgorithmException {
        X509Certificate signercert = null;
        // Get all certificates embedded in the request (probably a certificate chain)
        try {
            final X509CertificateHolder[] certs = req.getCerts();
            String signerSubjectDn = null;
            // We must find a certificate to verify the signature with...
            boolean verifyOK = false;
            for (int i = 0; i < certs.length; i++) {
                final X509Certificate certificate = certificateConverter.getCertificate(certs[i]);
                try {
                    if (req.isSignatureValid(
                            new JcaContentVerifierProviderBuilder().build(certificate.getPublicKey()))) {
                        signercert = certificate; // if the request signature verifies by this certificate, this is the signer cert 
                        signerSubjectDn = CertTools.getSubjectDN(signercert);
                        log.info(intres.getLocalizedMessage("ocsp.infosigner", signerSubjectDn));
                        verifyOK = true;
                        // Check that the signer certificate can be verified by one of the CA-certificates that we answer for
                        final X509Certificate signerca = CaCertificateCache.INSTANCE
                                .findLatestBySubjectDN(HashID.getFromIssuerDN(signercert));
                        if (signerca != null) {
                            try {
                                signercert.verify(signerca.getPublicKey());
                                final Date now = new Date();
                                if (log.isDebugEnabled()) {
                                    log.debug("Checking validity. Now: " + now + ", signerNotAfter: "
                                            + signercert.getNotAfter());
                                }
                                try {
                                    // Check validity of the request signing certificate
                                    CertTools.checkValidity(signercert, now);
                                } catch (CertificateNotYetValidException e) {
                                    log.info(intres.getLocalizedMessage("ocsp.infosigner.certnotyetvalid",
                                            signerSubjectDn, CertTools.getIssuerDN(signercert), e.getMessage()));
                                    verifyOK = false;
                                } catch (CertificateExpiredException e) {
                                    log.info(intres.getLocalizedMessage("ocsp.infosigner.certexpired",
                                            signerSubjectDn, CertTools.getIssuerDN(signercert), e.getMessage()));
                                    verifyOK = false;
                                }
                                try {
                                    // Check validity of the CA certificate
                                    CertTools.checkValidity(signerca, now);
                                } catch (CertificateNotYetValidException e) {
                                    log.info(intres.getLocalizedMessage("ocsp.infosigner.certnotyetvalid",
                                            CertTools.getSubjectDN(signerca), CertTools.getIssuerDN(signerca),
                                            e.getMessage()));
                                    verifyOK = false;
                                } catch (CertificateExpiredException e) {
                                    log.info(intres.getLocalizedMessage("ocsp.infosigner.certexpired",
                                            CertTools.getSubjectDN(signerca), CertTools.getIssuerDN(signerca),
                                            e.getMessage()));
                                    verifyOK = false;
                                }
                            } catch (SignatureException e) {
                                log.info(intres.getLocalizedMessage("ocsp.infosigner.invalidcertsignature",
                                        signerSubjectDn, CertTools.getIssuerDN(signercert), e.getMessage()));
                                verifyOK = false;
                            } catch (InvalidKeyException e) {
                                log.info(intres.getLocalizedMessage("ocsp.infosigner.invalidcertsignature",
                                        signerSubjectDn, CertTools.getIssuerDN(signercert), e.getMessage()));
                                verifyOK = false;
                            }
                        } else {
                            log.info(intres.getLocalizedMessage("ocsp.infosigner.nocacert", signerSubjectDn,
                                    CertTools.getIssuerDN(signercert)));
                            verifyOK = false;
                        }
                        break;
                    }
                } catch (OperatorCreationException e) {
                    // Very fatal error
                    throw new EJBException("Can not create Jca content signer: ", e);
                }
            }
            if (!verifyOK) {
                if (signerSubjectDn == null && certs.length > 0) {
                    signerSubjectDn = CertTools.getSubjectDN(certificateConverter.getCertificate(certs[0]));
                }
                String errMsg = intres.getLocalizedMessage("ocsp.errorinvalidsignature", signerSubjectDn);
                log.info(errMsg);
                throw new SignRequestSignatureException(errMsg);
            }
        } catch (OCSPException e) {
            throw new CryptoProviderException("BouncyCastle was not initialized properly.", e);
        } catch (NoSuchProviderException e) {
            throw new CryptoProviderException("BouncyCastle was not found as a provider.", e);
        }
        return signercert;
    }

    private void assertAcceptableResponseExtension(OCSPReq req) throws OcspFailureException {
        if (null == req) {
            throw new IllegalArgumentException();
        }
        if (req.hasExtensions()) {
            final Extension acceptableResponsesExtension = req
                    .getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_response);
            if (acceptableResponsesExtension != null) {
                // RFC 6960 4.4.3 AcceptableResponses ::= SEQUENCE OF OBJECT IDENTIFIER
                final ASN1Sequence sequence = ASN1Sequence
                        .getInstance(acceptableResponsesExtension.getExtnValue().getOctets());
                @SuppressWarnings("unchecked")
                final Enumeration<ASN1ObjectIdentifier> oids = sequence.getObjects();
                boolean supportsResponseType = false;
                while (oids.hasMoreElements()) {
                    final ASN1ObjectIdentifier oid = oids.nextElement();
                    if (oid.equals(OCSPObjectIdentifiers.id_pkix_ocsp_basic)) {
                        // This is the response type we support, so we are happy! Break the loop.
                        supportsResponseType = true;
                        if (log.isDebugEnabled()) {
                            log.debug("Response type supported: " + oid.getId());
                        }
                        break;
                    }
                }
                if (!supportsResponseType) {
                    final String msg = "Required response type not supported, this responder only supports id-pkix-ocsp-basic.";
                    log.info("OCSP Request type not supported: " + msg);
                    throw new OcspFailureException(msg);
                }
            }
        }
    }

    /**
     * When a timer expires, this method will update
     * 
     * According to JSR 220 FR (18.2.2), this method may not throw any exceptions.
     * 
     * @param timer The timer whose expiration caused this notification.
     * 
     */
    @Timeout
    /* 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. 
     */
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public void timeoutHandler(Timer timer) {
        if (log.isTraceEnabled()) {
            log.trace(">timeoutHandler: " + timer.getInfo().toString());
        }
        // reloadTokenAndChainCache cancels old timers and adds a new timer
        reloadOcspSigningCache();
        if (log.isTraceEnabled()) {
            log.trace("<timeoutHandler");
        }
    }

    /**
     * This method cancels all timers associated with this bean.
     */
    // 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 void cancelTimers(final int id) {
        if (log.isTraceEnabled()) {
            log.trace(">cancelTimers");
        }
        @SuppressWarnings("unchecked")
        final Collection<Timer> timers = timerService.getTimers();
        for (final Timer timer : timers) {
            final int currentTimerId = ((Integer) timer.getInfo()).intValue();
            if (currentTimerId == id) {
                timer.cancel();
            }
        }
        if (log.isTraceEnabled()) {
            log.trace("<cancelTimers, timers canceled: " + timers.size());
        }
    }

    private int getTimerCount(final int id) {
        if (log.isTraceEnabled()) {
            log.trace(">getTimerCount");
        }
        int count = 0;
        @SuppressWarnings("unchecked")
        final Collection<Timer> timers = timerService.getTimers();
        for (final Timer timer : timers) {
            final int currentTimerId = ((Integer) timer.getInfo()).intValue();
            if (currentTimerId == id) {
                count++;
            }
        }
        if (log.isTraceEnabled()) {
            log.trace("<getTimerCount, timers: " + count);
        }
        return count;
    }

    /**
     * Adds a timer to the bean
     * 
     * @param id the id of the timer
     */
    // 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 interval, Integer id) {
        if (log.isTraceEnabled()) {
            log.trace(">addTimer: " + id + ", interval: " + interval);
        }
        Timer ret = null;
        if (interval > 0) {
            ret = timerService.createTimer(interval, id);
            if (log.isTraceEnabled()) {
                log.trace("<addTimer: " + id + ", interval: " + interval + ", " + ret.getNextTimeout().toString());
            }
        }
        return ret;
    }

    @Override
    public OcspResponseInformation getOcspResponse(final byte[] request,
            final X509Certificate[] requestCertificates, String remoteAddress, String remoteHost,
            StringBuffer requestUrl, final AuditLogger auditLogger, final TransactionLogger transactionLogger)
            throws MalformedRequestException, OCSPException {
        //Check parameters
        if (auditLogger == null) {
            throw new InvalidParameterException(
                    "Illegal to pass a null audit logger to OcspResponseSession.getOcspResponse");
        }
        if (transactionLogger == null) {
            throw new InvalidParameterException(
                    "Illegal to pass a null transaction logger to OcspResponseSession.getOcspResponse");
        }
        // Validate byte array.
        if (request.length > MAX_REQUEST_SIZE) {
            final String msg = intres.getLocalizedMessage("request.toolarge", MAX_REQUEST_SIZE, request.length);
            throw new MalformedRequestException(msg);
        }
        byte[] respBytes = null;
        final Date startTime = new Date();
        OCSPResp ocspResponse = null;
        // Start logging process time after we have received the request
        if (transactionLogger.isEnabled()) {
            transactionLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME);
        }
        if (auditLogger.isEnabled()) {
            auditLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME);
            auditLogger.paramPut(AuditLogger.OCSPREQUEST, new String(Hex.encode(request)));
        }
        OCSPReq req;
        long maxAge = OcspConfiguration.getMaxAge(CertificateProfileConstants.CERTPROFILE_NO_PROFILE);
        OCSPRespBuilder responseGenerator = new OCSPRespBuilder();
        try {
            req = translateRequestFromByteArray(request, remoteAddress, transactionLogger);
            // Get the certificate status requests that are inside this OCSP req
            Req[] ocspRequests = req.getRequestList();
            if (ocspRequests.length <= 0) {
                String infoMsg = intres.getLocalizedMessage("ocsp.errornoreqentities");
                log.info(infoMsg);
                throw new MalformedRequestException(infoMsg);
            }
            final int maxRequests = 100;
            if (ocspRequests.length > maxRequests) {
                String infoMsg = intres.getLocalizedMessage("ocsp.errortoomanyreqentities", maxRequests);
                log.info(infoMsg);
                throw new MalformedRequestException(infoMsg);
            }
            if (log.isDebugEnabled()) {
                log.debug("The OCSP request contains " + ocspRequests.length + " simpleRequests.");
            }
            if (transactionLogger.isEnabled()) {
                transactionLogger.paramPut(TransactionLogger.NUM_CERT_ID, ocspRequests.length);
                transactionLogger.paramPut(TransactionLogger.STATUS, OCSPRespBuilder.SUCCESSFUL);
            }
            if (auditLogger.isEnabled()) {
                auditLogger.paramPut(AuditLogger.STATUS, OCSPRespBuilder.SUCCESSFUL);
            }
            OcspSigningCacheEntry ocspSigningCacheEntry = null;
            long nextUpdate = OcspConfiguration
                    .getUntilNextUpdate(CertificateProfileConstants.CERTPROFILE_NO_PROFILE);
            // Add standard response extensions
            Map<ASN1ObjectIdentifier, Extension> responseExtensions = getStandardResponseExtensions(req);
            // Look for extension OIDs
            final Collection<String> extensionOids = OcspConfiguration.getExtensionOids();
            // Look over the status requests
            List<OCSPResponseItem> responseList = new ArrayList<OCSPResponseItem>();
            boolean addExtendedRevokedExtension = false;
            Date producedAt = null;
            for (Req ocspRequest : ocspRequests) {
                CertificateID certId = ocspRequest.getCertID();
                ASN1ObjectIdentifier certIdhash = certId.getHashAlgOID();
                if (!OIWObjectIdentifiers.idSHA1.equals(certIdhash)
                        && !NISTObjectIdentifiers.id_sha256.equals(certIdhash)) {
                    throw new InvalidAlgorithmException(
                            "CertID with SHA1 and SHA256 are supported, not: " + certIdhash.getId());
                }
                if (transactionLogger.isEnabled()) {
                    transactionLogger.paramPut(TransactionLogger.SERIAL_NOHEX,
                            certId.getSerialNumber().toByteArray());
                    transactionLogger.paramPut(TransactionLogger.DIGEST_ALGOR, certId.getHashAlgOID().toString());
                    transactionLogger.paramPut(TransactionLogger.ISSUER_NAME_HASH, certId.getIssuerNameHash());
                    transactionLogger.paramPut(TransactionLogger.ISSUER_KEY, certId.getIssuerKeyHash());
                }
                if (auditLogger.isEnabled()) {
                    auditLogger.paramPut(AuditLogger.ISSUER_KEY, certId.getIssuerKeyHash());
                    auditLogger.paramPut(AuditLogger.SERIAL_NOHEX, certId.getSerialNumber().toByteArray());
                    auditLogger.paramPut(AuditLogger.ISSUER_NAME_HASH, certId.getIssuerNameHash());
                }
                byte[] hashbytes = certId.getIssuerNameHash();
                String hash = null;
                if (hashbytes != null) {
                    hash = new String(Hex.encode(hashbytes));
                }
                String infoMsg = intres.getLocalizedMessage("ocsp.inforeceivedrequest",
                        certId.getSerialNumber().toString(16), hash, remoteAddress);
                log.info(infoMsg);
                // Locate the CA which gave out the certificate
                ocspSigningCacheEntry = OcspSigningCache.INSTANCE.getEntry(certId);
                if (ocspSigningCacheEntry == null) {
                    //Could it be that we haven't updated the OCSP Signing Cache?
                    ocspSigningCacheEntry = findAndAddMissingCacheEntry(certId);
                }
                if (ocspSigningCacheEntry != null) {
                    if (transactionLogger.isEnabled()) {
                        // This will be the issuer DN of the signing certificate, whether an OCSP responder or an internal CA  
                        String issuerNameDn = CertTools
                                .getIssuerDN(ocspSigningCacheEntry.getFullCertificateChain().get(0));
                        transactionLogger.paramPut(TransactionLogger.ISSUER_NAME_DN, issuerNameDn);
                    }
                } else {
                    /*
                     * if the certId was issued by an unknown CA 
                     * 
                     * The algorithm here: 
                     * We will sign the response with the CA that issued the last certificate(certId) in the request. If the issuing CA is not available on 
                     * this server, we sign the response with the default responderId (from params in web.xml). We have to look up the ca-certificate for 
                     * each certId in the request though, as we will check for revocation on the ca-cert as well when checking for revocation on the certId.
                     */
                    // We could not find certificate for this request so get certificate for default responder
                    ocspSigningCacheEntry = OcspSigningCache.INSTANCE.getDefaultEntry();
                    if (ocspSigningCacheEntry != null) {
                        String errMsg = intres.getLocalizedMessage("ocsp.errorfindcacertusedefault",
                                new String(Hex.encode(certId.getIssuerNameHash())));
                        log.info(errMsg);
                        // If we can not find the CA, answer UnknowStatus
                        responseList.add(new OCSPResponseItem(certId, new UnknownStatus(), nextUpdate));
                        if (transactionLogger.isEnabled()) {
                            transactionLogger.paramPut(TransactionLogger.CERT_STATUS,
                                    OCSPResponseItem.OCSP_UNKNOWN);
                            transactionLogger.writeln();
                        }
                        continue;
                    } else {
                        GlobalOcspConfiguration ocspConfiguration = (GlobalOcspConfiguration) globalConfigurationSession
                                .getCachedConfiguration(GlobalOcspConfiguration.OCSP_CONFIGURATION_ID);
                        String defaultResponder = ocspConfiguration.getOcspDefaultResponderReference();
                        String errMsg = intres.getLocalizedMessage("ocsp.errorfindcacert",
                                new String(Hex.encode(certId.getIssuerNameHash())), defaultResponder);
                        log.error(errMsg);
                        // If we are responding to multiple requests, the last found ocspSigningCacheEntry will be used in the end
                        // so even if there are not any one now, it might be later when it is time to sign the responses.
                        // Since we only will sign the entire response once if there is at least one valid ocspSigningCacheEntry
                        // we might as well include the unknown requests.
                        responseList.add(new OCSPResponseItem(certId, new UnknownStatus(), nextUpdate));
                        continue;
                    }
                }

                final org.bouncycastle.cert.ocsp.CertificateStatus certStatus;
                // Check if the cacert (or the default responderid) is revoked
                X509Certificate caCertificate = ocspSigningCacheEntry.getIssuerCaCertificate();
                final CertificateStatus signerIssuerCertStatus = ocspSigningCacheEntry
                        .getIssuerCaCertificateStatus();
                final String caCertificateSubjectDn = CertTools.getSubjectDN(caCertificate);
                CertificateStatusHolder certificateStatusHolder = null;
                if (signerIssuerCertStatus.equals(CertificateStatus.REVOKED)) {
                    /*
                     * According to chapter 2.7 in RFC2560:
                     * 
                     * 2.7 CA Key Compromise If an OCSP responder knows that a particular CA's private key has been compromised, it MAY return the revoked
                     * state for all certificates issued by that CA.
                     */
                    // If we've ended up here it's because the signer issuer certificate was revoked. 
                    certStatus = new RevokedStatus(
                            new RevokedInfo(new ASN1GeneralizedTime(signerIssuerCertStatus.revocationDate),
                                    CRLReason.lookup(signerIssuerCertStatus.revocationReason)));
                    infoMsg = intres.getLocalizedMessage("ocsp.signcertissuerrevoked",
                            CertTools.getSerialNumberAsString(caCertificate),
                            CertTools.getSubjectDN(caCertificate));
                    log.info(infoMsg);
                    responseList.add(new OCSPResponseItem(certId, certStatus, nextUpdate));
                    if (transactionLogger.isEnabled()) {
                        transactionLogger.paramPut(TransactionLogger.CERT_STATUS, OCSPResponseItem.OCSP_REVOKED);
                        transactionLogger.writeln();
                    }
                } else {
                    /**
                     * Here is the actual check for the status of the sought certificate (easy to miss). Here we grab just the status if there aren't
                     * any OIDs defined (default case), but if there are we'll probably need the certificate as well. If that's the case, we'll grab
                     * the certificate in the same transaction.
                     */
                    final CertificateStatus status;
                    if (extensionOids.isEmpty()) {
                        status = certificateStoreSession.getStatus(caCertificateSubjectDn,
                                certId.getSerialNumber());
                    } else {
                        certificateStatusHolder = certificateStoreSession
                                .getCertificateAndStatus(caCertificateSubjectDn, certId.getSerialNumber());
                        status = certificateStatusHolder.getCertificateStatus();
                    }
                    // If we have an OcspKeyBinding configured for this request, we override the default value
                    if (ocspSigningCacheEntry.isUsingSeparateOcspSigningCertificate()) {
                        nextUpdate = ocspSigningCacheEntry.getOcspKeyBinding().getUntilNextUpdate() * 1000L;
                    }
                    // If we have an explicit value configured for this certificate profile, we override the the current value with this value
                    if (status.certificateProfileId != CertificateProfileConstants.CERTPROFILE_NO_PROFILE
                            && OcspConfiguration.isUntilNextUpdateConfigured(status.certificateProfileId)) {
                        nextUpdate = OcspConfiguration.getUntilNextUpdate(status.certificateProfileId);
                    }
                    // If we have an OcspKeyBinding configured for this request, we override the default value
                    if (ocspSigningCacheEntry.isUsingSeparateOcspSigningCertificate()) {
                        maxAge = ocspSigningCacheEntry.getOcspKeyBinding().getMaxAge() * 1000L;
                    }
                    // If we have an explicit value configured for this certificate profile, we override the the current value with this value
                    if (status.certificateProfileId != CertificateProfileConstants.CERTPROFILE_NO_PROFILE
                            && OcspConfiguration.isMaxAgeConfigured(status.certificateProfileId)) {
                        maxAge = OcspConfiguration.getMaxAge(status.certificateProfileId);
                    }

                    final String sStatus;
                    boolean addArchiveCutoff = false;
                    if (status.equals(CertificateStatus.NOT_AVAILABLE)) {
                        // No revocation info available for this cert, handle it
                        if (log.isDebugEnabled()) {
                            log.debug("Unable to find revocation information for certificate with serial '"
                                    + certId.getSerialNumber().toString(16) + "'" + " from issuer '"
                                    + caCertificateSubjectDn + "'");
                        }
                        /* 
                         * If we do not treat non existing certificates as good or revoked
                         * OR
                         * we don't actually handle requests for the CA issuing the certificate asked about
                         * then we return unknown 
                         * */
                        if (OcspConfigurationCache.INSTANCE.isNonExistingGood(requestUrl,
                                ocspSigningCacheEntry.getOcspKeyBinding())
                                && OcspSigningCache.INSTANCE.getEntry(certId) != null) {
                            sStatus = "good";
                            certStatus = null; // null means "good" in OCSP
                            if (transactionLogger.isEnabled()) {
                                transactionLogger.paramPut(TransactionLogger.CERT_STATUS,
                                        OCSPResponseItem.OCSP_GOOD);
                            }
                        } else if (OcspConfigurationCache.INSTANCE.isNonExistingRevoked(requestUrl,
                                ocspSigningCacheEntry.getOcspKeyBinding())
                                && OcspSigningCache.INSTANCE.getEntry(certId) != null) {
                            sStatus = "revoked";
                            certStatus = new RevokedStatus(new RevokedInfo(new ASN1GeneralizedTime(new Date(0)),
                                    CRLReason.lookup(CRLReason.certificateHold)));
                            if (transactionLogger.isEnabled()) {
                                transactionLogger.paramPut(TransactionLogger.CERT_STATUS,
                                        OCSPResponseItem.OCSP_REVOKED);
                            }
                            addExtendedRevokedExtension = true;
                        } else {
                            sStatus = "unknown";
                            certStatus = new UnknownStatus();
                            if (transactionLogger.isEnabled()) {
                                transactionLogger.paramPut(TransactionLogger.CERT_STATUS,
                                        OCSPResponseItem.OCSP_UNKNOWN);
                            }
                        }
                    } else if (status.equals(CertificateStatus.REVOKED)) {
                        // Revocation info available for this cert, handle it
                        sStatus = "revoked";
                        certStatus = new RevokedStatus(
                                new RevokedInfo(new ASN1GeneralizedTime(status.revocationDate),
                                        CRLReason.lookup(status.revocationReason)));
                        if (transactionLogger.isEnabled()) {
                            transactionLogger.paramPut(TransactionLogger.CERT_STATUS,
                                    OCSPResponseItem.OCSP_REVOKED);
                        }
                        // If we have an explicit value configured for this certificate profile, we override the the current value with this value
                        if (status.certificateProfileId != CertificateProfileConstants.CERTPROFILE_NO_PROFILE
                                && OcspConfiguration
                                        .isRevokedUntilNextUpdateConfigured(status.certificateProfileId)) {
                            nextUpdate = OcspConfiguration.getRevokedUntilNextUpdate(status.certificateProfileId);
                        }
                        // If we have an explicit value configured for this certificate profile, we override the the current value with this value
                        if (status.certificateProfileId != CertificateProfileConstants.CERTPROFILE_NO_PROFILE
                                && OcspConfiguration.isRevokedMaxAgeConfigured(status.certificateProfileId)) {
                            maxAge = OcspConfiguration.getRevokedMaxAge(status.certificateProfileId);
                        }
                    } else {
                        sStatus = "good";
                        certStatus = null;
                        if (transactionLogger.isEnabled()) {
                            transactionLogger.paramPut(TransactionLogger.CERT_STATUS, OCSPResponseItem.OCSP_GOOD);
                        }
                        addArchiveCutoff = checkAddArchiveCuttoff(caCertificateSubjectDn, certId);
                    }

                    if (log.isDebugEnabled()) {
                        log.debug("Set nextUpdate=" + nextUpdate + ", and maxAge=" + maxAge
                                + " for certificateProfileId=" + status.certificateProfileId);
                    }

                    infoMsg = intres.getLocalizedMessage("ocsp.infoaddedstatusinfo", sStatus,
                            certId.getSerialNumber().toString(16), caCertificateSubjectDn);
                    log.info(infoMsg);
                    OCSPResponseItem respItem = new OCSPResponseItem(certId, certStatus, nextUpdate);
                    if (addArchiveCutoff) {
                        addArchiveCutoff(respItem);
                        producedAt = new Date();
                    }
                    responseList.add(respItem);
                    if (transactionLogger.isEnabled()) {
                        transactionLogger.writeln();
                    }
                }
                for (String oidstr : extensionOids) {
                    boolean useAlways = false;
                    if (oidstr.startsWith("*")) {
                        oidstr = oidstr.substring(1, oidstr.length());
                        useAlways = true;
                    }
                    ASN1ObjectIdentifier oid = new ASN1ObjectIdentifier(oidstr);
                    Extension extension = null;
                    if (!useAlways) {
                        // Only check if extension exists if we are not already bound to use it
                        if (req.hasExtensions()) {
                            extension = req.getExtension(oid);
                        }
                    }
                    //If found, or if it should be used anyway
                    if (useAlways || extension != null) {
                        // We found an extension, call the extension class
                        if (log.isDebugEnabled()) {
                            log.debug("Found OCSP extension oid: " + oidstr);
                        }
                        OCSPExtension extObj = OcspExtensionsCache.INSTANCE.getExtensions().get(oidstr);
                        if (extObj != null) {
                            // Find the certificate from the certId
                            if (certificateStatusHolder != null
                                    && certificateStatusHolder.getCertificate() != null) {
                                X509Certificate cert = (X509Certificate) certificateStatusHolder.getCertificate();
                                // Call the OCSP extension
                                Map<ASN1ObjectIdentifier, Extension> retext = extObj.process(requestCertificates,
                                        remoteAddress, remoteHost, cert, certStatus);
                                if (retext != null) {
                                    // Add the returned X509Extensions to the responseExtension we will add to the basic OCSP response
                                    responseExtensions.putAll(retext);
                                } else {
                                    String errMsg = intres.getLocalizedMessage("ocsp.errorprocessextension",
                                            extObj.getClass().getName(),
                                            Integer.valueOf(extObj.getLastErrorCode()));
                                    log.error(errMsg);
                                }
                            }
                        }
                    }
                }
            }
            if (addExtendedRevokedExtension) {
                // id-pkix-ocsp-extended-revoke OBJECT IDENTIFIER ::= {id-pkix-ocsp 9}
                final ASN1ObjectIdentifier extendedRevokedOID = new ASN1ObjectIdentifier(
                        OCSPObjectIdentifiers.id_pkix_ocsp + ".9");
                try {
                    responseExtensions.put(extendedRevokedOID,
                            new Extension(extendedRevokedOID, false, DERNull.INSTANCE.getEncoded()));
                } catch (IOException e) {
                    throw new IllegalStateException("Could not get encodig from DERNull.", e);
                }
            }
            if (ocspSigningCacheEntry != null) {
                // Add responseExtensions
                Extensions exts = new Extensions(responseExtensions.values().toArray(new Extension[0]));
                // generate the signed response object
                BasicOCSPResp basicresp = signOcspResponse(req, responseList, exts, ocspSigningCacheEntry,
                        producedAt);
                ocspResponse = responseGenerator.build(OCSPRespBuilder.SUCCESSFUL, basicresp);
                if (auditLogger.isEnabled()) {
                    auditLogger.paramPut(AuditLogger.STATUS, OCSPRespBuilder.SUCCESSFUL);
                }
                if (transactionLogger.isEnabled()) {
                    transactionLogger.paramPut(TransactionLogger.STATUS, OCSPRespBuilder.SUCCESSFUL);
                }
            } else {
                // Only unknown CAs in requests and no default responder's cert, return an unsigned response
                if (log.isDebugEnabled()) {
                    log.debug(intres.getLocalizedMessage("ocsp.errornocacreateresp"));
                }
                ocspResponse = responseGenerator.build(OCSPRespBuilder.UNAUTHORIZED, null);
                if (auditLogger.isEnabled()) {
                    auditLogger.paramPut(AuditLogger.STATUS, OCSPRespBuilder.UNAUTHORIZED);
                }
                if (transactionLogger.isEnabled()) {
                    transactionLogger.paramPut(TransactionLogger.STATUS, OCSPRespBuilder.UNAUTHORIZED);
                }
            }
        } catch (SignRequestException e) {
            if (transactionLogger.isEnabled()) {
                transactionLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME);
            }
            if (auditLogger.isEnabled()) {
                auditLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME);
            }
            String errMsg = intres.getLocalizedMessage("ocsp.errorprocessreq", e.getMessage());
            log.info(errMsg); // No need to log the full exception here
            // RFC 2560: responseBytes are not set on error.
            ocspResponse = responseGenerator.build(OCSPRespBuilder.SIG_REQUIRED, null);
            if (transactionLogger.isEnabled()) {
                transactionLogger.paramPut(TransactionLogger.STATUS, OCSPRespBuilder.SIG_REQUIRED);
                transactionLogger.writeln();
            }
            if (auditLogger.isEnabled()) {
                auditLogger.paramPut(AuditLogger.STATUS, OCSPRespBuilder.SIG_REQUIRED);
            }
        } catch (SignRequestSignatureException e) {
            if (transactionLogger.isEnabled()) {
                transactionLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME);
            }
            if (auditLogger.isEnabled()) {
                auditLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME);
            }
            String errMsg = intres.getLocalizedMessage("ocsp.errorprocessreq", e.getMessage());
            log.info(errMsg); // No need to log the full exception here
            // RFC 2560: responseBytes are not set on error.
            ocspResponse = responseGenerator.build(OCSPRespBuilder.UNAUTHORIZED, null);
            if (transactionLogger.isEnabled()) {
                transactionLogger.paramPut(TransactionLogger.STATUS, OCSPRespBuilder.UNAUTHORIZED);
                transactionLogger.writeln();
            }
            if (auditLogger.isEnabled()) {
                auditLogger.paramPut(AuditLogger.STATUS, OCSPRespBuilder.UNAUTHORIZED);
            }
        } catch (InvalidAlgorithmException e) {
            if (transactionLogger.isEnabled()) {
                transactionLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME);
            }
            if (auditLogger.isEnabled()) {
                auditLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME);
            }
            String errMsg = intres.getLocalizedMessage("ocsp.errorprocessreq", e.getMessage());
            log.info(errMsg); // No need to log the full exception here
            // RFC 2560: responseBytes are not set on error.
            ocspResponse = responseGenerator.build(OCSPRespBuilder.MALFORMED_REQUEST, null);
            if (transactionLogger.isEnabled()) {
                transactionLogger.paramPut(TransactionLogger.STATUS, OCSPRespBuilder.MALFORMED_REQUEST);
                transactionLogger.writeln();
            }
            if (auditLogger.isEnabled()) {
                auditLogger.paramPut(AuditLogger.STATUS, OCSPRespBuilder.MALFORMED_REQUEST);
            }
        } catch (NoSuchAlgorithmException e) {
            ocspResponse = processDefaultError(responseGenerator, transactionLogger, auditLogger, e);
        } catch (CertificateException e) {
            ocspResponse = processDefaultError(responseGenerator, transactionLogger, auditLogger, e);
        } catch (CryptoTokenOfflineException e) {
            ocspResponse = processDefaultError(responseGenerator, transactionLogger, auditLogger, e);
        }
        try {
            respBytes = ocspResponse.getEncoded();
            if (auditLogger.isEnabled()) {
                auditLogger.paramPut(AuditLogger.OCSPRESPONSE, new String(Hex.encode(respBytes)));
                auditLogger.writeln();
                auditLogger.flush();
            }
            if (transactionLogger.isEnabled()) {
                transactionLogger.flush();
            }
            if (OcspConfiguration.getLogSafer()) {
                // See if the Errorhandler has found any problems
                if (hasErrorHandlerFailedSince(startTime)) {
                    log.info("ProbableErrorhandler reported error, cannot answer request");
                    // RFC 2560: responseBytes are not set on error.
                    ocspResponse = responseGenerator.build(OCSPRespBuilder.INTERNAL_ERROR, null);

                }
                // See if the Appender has reported any problems
                if (!CanLogCache.INSTANCE.canLog()) {
                    log.info("SaferDailyRollingFileAppender reported error, cannot answer request");
                    // RFC 2560: responseBytes are not set on error.
                    ocspResponse = responseGenerator.build(OCSPRespBuilder.INTERNAL_ERROR, null);
                }
            }
        } catch (IOException e) {
            log.error("Unexpected IOException caught.", e);
            if (transactionLogger.isEnabled()) {
                transactionLogger.flush();
            }
            if (auditLogger.isEnabled()) {
                auditLogger.flush();
            }
        }
        return new OcspResponseInformation(ocspResponse, maxAge);
    }

    private boolean checkAddArchiveCuttoff(String caCertificateSubjectDn, CertificateID certId) {
        if (OcspConfiguration.getExpiredArchiveCutoff() == -1) {
            return false;
        }
        CertificateInfo info = certificateStoreSession.findFirstCertificateInfo(caCertificateSubjectDn,
                certId.getSerialNumber());
        Date expDate = info.getExpireDate();
        if (expDate.before(new Date())) {
            log.info("Certificate with serial number '" + certId.getSerialNumber() + "' is not valid. "
                    + "Adding singleExtension id-pkix-ocsp-archive-cutoff");
            return true;
        }
        return false;
    }

    private void addArchiveCutoff(OCSPResponseItem respItem) {
        long archPeriod = OcspConfiguration.getExpiredArchiveCutoff();
        if (archPeriod == -1) {
            return;
        }
        long res = System.currentTimeMillis() - archPeriod;
        ExtensionsGenerator gen = new ExtensionsGenerator();
        try {
            gen.addExtension(OCSPObjectIdentifiers.id_pkix_ocsp_archive_cutoff, false,
                    new ASN1GeneralizedTime(new Date(res)));
        } catch (IOException e) {
            throw new IllegalStateException("IOException was caught when decoding static value.", e);
        }
        Extensions exts = gen.generate();
        respItem.setExtentions(exts);
    }

    /**
     * returns a Map of responseExtensions to be added to the BacisOCSPResponseGenerator with <code>
     * X509Extensions exts = new X509Extensions(table);
     * basicRes.setResponseExtensions(responseExtensions);
     * </code>
     * 
     * @param req OCSPReq
     * @return a HashMap, can be empty but not null
     */
    private Map<ASN1ObjectIdentifier, Extension> getStandardResponseExtensions(OCSPReq req) {
        HashMap<ASN1ObjectIdentifier, Extension> result = new HashMap<ASN1ObjectIdentifier, Extension>();
        if (req.hasExtensions()) {
            // Table of extensions to include in the response
            Extension ext = req.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
            if (null != ext) {
                result.put(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, ext);
            }
        }
        return result;
    }

    /**
     * This method handles cache misses where there exists an active key binding which hasn't been cached.
     * 
     * @param certId the CertificateID for the certificate being requested. 
     * @return the now cached entry, or null if none was found. 
     */
    private OcspSigningCacheEntry findAndAddMissingCacheEntry(CertificateID certId)
            throws CertificateEncodingException {
        OcspSigningCacheEntry ocspSigningCacheEntry = null;
        for (final int internalKeyBindingId : internalKeyBindingDataSession
                .getIds(OcspKeyBinding.IMPLEMENTATION_ALIAS)) {
            final OcspKeyBinding ocspKeyBinding = (OcspKeyBinding) internalKeyBindingDataSession
                    .getInternalKeyBinding(internalKeyBindingId);
            if (ocspKeyBinding.getStatus().equals(InternalKeyBindingStatus.ACTIVE)) {
                X509Certificate ocspCertificate = (X509Certificate) certificateStoreSession
                        .findCertificateByFingerprint(ocspKeyBinding.getCertificateId());
                if (ocspCertificate == null) {
                    // There may be key binding with missing certificates normally (waiting for certificate response?), so don't spam the log
                    if (log.isDebugEnabled()) {
                        log.debug("Could not find certificate for OCSP Key Binding '" + ocspKeyBinding.getName()
                                + "'. Certificate fingerprint: " + ocspKeyBinding.getCertificateId());
                    }
                } else {
                    X509Certificate issuingCertificate = certificateStoreSession
                            .findLatestX509CertificateBySubject(CertTools.getIssuerDN(ocspCertificate));
                    if (issuingCertificate == null) {
                        // There may be key binding with missing certificates normally (waiting for certificate response?), so don't spam the log
                        if (log.isDebugEnabled()) {
                            log.info("Could not find issuer certificate for OCSP Key Binding '"
                                    + ocspKeyBinding.getName() + "'. Issuer DN: "
                                    + ocspKeyBinding.getCertificateId());
                        }
                    } else {
                        try {
                            if (certId.matchesIssuer(new JcaX509CertificateHolder(issuingCertificate),
                                    new BcDigestCalculatorProvider())) {
                                //We found it! Unless it's not active, or something else was wrong with it. 
                                ocspSigningCacheEntry = makeOcspSigningCacheEntry(ocspCertificate, ocspKeyBinding);
                                //If it was all right, add it to the cache for future use.
                                if (ocspSigningCacheEntry != null) {
                                    OcspSigningCache.INSTANCE.addSingleEntry(ocspSigningCacheEntry);
                                    break;
                                }
                            }
                        } catch (OCSPException e) {
                            throw new IllegalStateException("Could not create BcDigestCalculatorProvider", e);
                        }
                    }
                }
            }
        }
        return ocspSigningCacheEntry;
    }

    private BasicOCSPResp signOcspResponse(OCSPReq req, List<OCSPResponseItem> responseList, Extensions exts,
            final OcspSigningCacheEntry ocspSigningCacheEntry, Date producedAt) throws CryptoTokenOfflineException {
        assertAcceptableResponseExtension(req);
        if (!ocspSigningCacheEntry.isSigningCertificateForOcspSigning()) {
            log.warn(
                    "Signing with non OCSP certificate (no 'OCSP Signing' Extended Key Usage) bound by OcspKeyBinding '"
                            + ocspSigningCacheEntry.getOcspKeyBinding().getName() + "'.");
        }
        final X509Certificate signerCert = ocspSigningCacheEntry.getSigningCertificate();
        final String sigAlg = getSigAlg(req, ocspSigningCacheEntry, signerCert);
        if (log.isDebugEnabled()) {
            log.debug("Signing algorithm: " + sigAlg);
        }
        try {
            // Now we can use the returned OCSPServiceResponse to get private key and certificate chain to sign the ocsp response
            final BasicOCSPResp ocspresp = generateBasicOcspResp(exts, responseList, sigAlg, signerCert,
                    ocspSigningCacheEntry, producedAt);
            if (CertTools.isCertificateValid(signerCert)) {
                return ocspresp;
            } else {
                throw new OcspFailureException("Response was not validly signed.");
            }
        } catch (OCSPException ocspe) {
            throw new OcspFailureException(ocspe);
        } catch (NoSuchProviderException nspe) {
            throw new OcspFailureException(nspe);
        } catch (IllegalArgumentException e) {
            log.error("IllegalArgumentException: ", e);
            throw new OcspFailureException(e);
        }
    }

    private BasicOCSPResp generateBasicOcspResp(Extensions exts, List<OCSPResponseItem> responses, String sigAlg,
            X509Certificate signerCert, OcspSigningCacheEntry ocspSigningCacheEntry, Date producedAt)
            throws OCSPException, NoSuchProviderException, CryptoTokenOfflineException {
        final PrivateKey signerKey = ocspSigningCacheEntry.getPrivateKey();
        final String provider = ocspSigningCacheEntry.getSignatureProviderName();
        BasicOCSPResp returnval = null;
        BasicOCSPRespBuilder basicRes = new BasicOCSPRespBuilder(ocspSigningCacheEntry.getRespId());
        if (responses != null) {
            for (OCSPResponseItem item : responses) {
                basicRes.addResponse(item.getCertID(), item.getCertStatus(), item.getThisUpdate(),
                        item.getNextUpdate(), item.getExtensions());
            }
        }
        if (exts != null) {
            @SuppressWarnings("rawtypes")
            Enumeration oids = exts.oids();
            if (oids.hasMoreElements()) {
                basicRes.setResponseExtensions(exts);
            }
        }
        final X509Certificate[] chain = ocspSigningCacheEntry.getResponseCertChain();
        if (log.isDebugEnabled()) {
            log.debug("The response certificate chain contains " + chain.length + " certificates");
        }
        /*
         * The below code breaks the EJB standard by creating its own thread pool and creating a single thread (of the HsmResponseThread 
         * type). The reason for this is that the HSM may deadlock when requesting an OCSP response, which we need to guard against. Since 
         * there is no way of performing this action within the EJB3.0 standard, we are consciously creating threads here. 
         * 
         * Note that this does in no way break the spirit of the EJB standard, which is to not interrupt EJB's transaction handling by 
         * competing with its own thread pool, since these operations have no database impact.
         */
        final Future<BasicOCSPResp> task = service
                .submit(new HsmResponseThread(basicRes, sigAlg, signerKey, chain, provider, producedAt));
        try {
            returnval = task.get(HsmResponseThread.HSM_TIMEOUT_SECONDS, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            task.cancel(true);
            throw new Error("OCSP response retrieval was interrupted while running. This should not happen", e);
        } catch (ExecutionException e) {
            task.cancel(true);
            throw new OcspFailureException("Failure encountered while retrieving OCSP response.", e);
        } catch (TimeoutException e) {
            task.cancel(true);
            throw new CryptoTokenOfflineException("HSM timed out while trying to get OCSP response", e);
        }
        if (log.isDebugEnabled()) {
            log.debug("Signing OCSP response with OCSP signer cert: " + signerCert.getSubjectDN().getName());
        }
        if (!returnval.getResponderId().equals(ocspSigningCacheEntry.getRespId())) {
            log.error("Response responderId does not match signer certificate responderId!");
            throw new OcspFailureException("Response responderId does not match signer certificate responderId!");
        }
        if (!ocspSigningCacheEntry.checkResponseSignatureVerified()) {
            // We only check the response signature the first time for each OcspSigningCacheEntry to detect a misbehaving HSM.
            // The client is still responsible for validating the signature, see RFC 6960 Section 3.2.2
            boolean verify;
            try {
                verify = returnval
                        .isSignatureValid(new JcaContentVerifierProviderBuilder().build(signerCert.getPublicKey()));
            } catch (OperatorCreationException e) {
                // Very fatal error
                throw new EJBException("Can not create Jca content signer: ", e);
            }
            if (verify) {
                if (log.isDebugEnabled()) {
                    log.debug("The OCSP response is verifying.");
                }
            } else {
                log.error("The response is NOT verifying! Attempted to sign using "
                        + CertTools.getSubjectDN(signerCert) + " but signature was not valid.");
                throw new OcspFailureException("Attempted to sign using " + CertTools.getSubjectDN(signerCert)
                        + " but signature was not valid.");
            }
        }
        return returnval;
    }

    /**
     * Method that checks with ProbableErrorHandler if an error has happened since a certain time. Uses reflection to call ProbableErrorHandler
     * because it is dependent on JBoss log4j logging, which is not available on other application servers.
     * 
     * @param startTime
     * @return true if an error has occurred since startTime
     */
    private boolean hasErrorHandlerFailedSince(Date startTime) {
        boolean result = true; // Default true. If something goes wrong we will fail
        result = ProbableErrorHandler.hasFailedSince(startTime);
        if (result) {
            log.error("Audit and/or account logging failed since " + startTime);
        }
        return result;
    }

    /**
     * Returns a signing algorithm to use selecting from a list of possible algorithms.
     * 
     * @param sigalgs the list of possible algorithms, ;-separated. Example "SHA1WithRSA;SHA1WithECDSA".
     * @param pk public key of signer, so we can choose between RSA, DSA and ECDSA algorithms
     * @return A single algorithm to use Example: SHA1WithRSA, SHA1WithDSA or SHA1WithECDSA
     */
    private static String getSigningAlgFromAlgSelection(String sigalgs, PublicKey pk) {
        String sigAlg = null;
        String[] algs = StringUtils.split(sigalgs, ';');
        for (int i = 0; i < algs.length; i++) {
            if (AlgorithmTools.isCompatibleSigAlg(pk, algs[i])) {
                sigAlg = algs[i];
                break;
            }
        }
        log.debug("Using signature algorithm for response: " + sigAlg);
        return sigAlg;
    }

    private static enum CanLogCache {
        INSTANCE;

        private boolean canLog;

        private CanLogCache() {
            this.canLog = true;
        }

        public boolean canLog() {
            return canLog;
        }

        public void setCanLog(boolean canLog) {
            this.canLog = canLog;
        }
    }

    @Override
    @Deprecated //Remove this method once upgrading from 5-6 is dropped
    public void adhocUpgradeFromPre60(char[] activationPassword) {
        AuthenticationToken authenticationToken = new AlwaysAllowLocalAuthenticationToken(new UsernamePrincipal(
                OcspResponseGeneratorSessionBean.class.getSimpleName() + ".adhocUpgradeFromPre60"));
        // Check if there are any OcspKeyBindings already, if so return
        if (!internalKeyBindingDataSession.getIds(OcspKeyBinding.IMPLEMENTATION_ALIAS).isEmpty()) {
            return;
        }
        // If ocsp.activation.doNotStorePasswordsInMemory=true, new Crypto Tokens should not be auto-actived
        final boolean globalDoNotStorePasswordsInMemory = OcspConfiguration.getDoNotStorePasswordsInMemory();
        if (globalDoNotStorePasswordsInMemory && activationPassword == null) {
            log.info(
                    "Postponing conversion of ocsp.properties configuration to OcspKeyBindings since password is not yet available.");
            return;
        }
        log.info("No OcspKeyBindings found. Processing ocsp.properties to see if we need to perform conversion.");
        final List<InternalKeyBindingTrustEntry> trustDefaults = getOcspKeyBindingTrustDefaults();
        // Create CryptoTokens and AuthenticationKeyBinding from:
        //  ocsp.rekeying.swKeystorePath = wsKeyStore.jks
        //  ocsp.rekeying.swKeystorePassword = foo123
        //  if "ocsp.rekeying.swKeystorePath" isn't set, search the p11 slot later on for an entry with an SSL certificate and use this
        final String swKeystorePath = ConfigurationHolder.getString("ocsp.rekeying.swKeystorePath");
        final String swKeystorePassword = ConfigurationHolder.getString("ocsp.rekeying.swKeystorePassword");
        if (swKeystorePath != null && (swKeystorePassword != null || activationPassword != null)) {
            final String password = swKeystorePassword == null ? new String(activationPassword)
                    : swKeystorePassword;
            processSoftKeystore(authenticationToken, new File(swKeystorePath), password, password,
                    globalDoNotStorePasswordsInMemory, trustDefaults);
        }
        if (OcspConfiguration.getP11Password() != null || activationPassword != null) {
            log.info(" Processing PKCS#11..");
            final String p11SharedLibrary = OcspConfiguration.getP11SharedLibrary();
            final String sunP11ConfigurationFile = OcspConfiguration.getSunP11ConfigurationFile();
            try {
                final String p11password = OcspConfiguration.getP11Password() == null
                        ? new String(activationPassword)
                        : OcspConfiguration.getP11Password();
                String cryptoTokenName = null;
                final Properties cryptoTokenProperties = new Properties();
                if (p11SharedLibrary != null && p11SharedLibrary.length() != 0) {
                    log.info(" Processing PKCS#11 with shared library " + p11SharedLibrary);
                    final String p11slot = OcspConfiguration.getP11SlotIndex();
                    cryptoTokenProperties.put(PKCS11CryptoToken.SHLIB_LABEL_KEY, p11SharedLibrary);
                    cryptoTokenProperties.put(PKCS11CryptoToken.SLOT_LABEL_VALUE, p11slot);
                    // Guess label type in order index, number or label 
                    Pkcs11SlotLabelType type;
                    if (Pkcs11SlotLabelType.SLOT_NUMBER.validate(p11slot)) {
                        type = Pkcs11SlotLabelType.SLOT_NUMBER;
                    } else if (Pkcs11SlotLabelType.SLOT_INDEX.validate(p11slot)) {
                        type = Pkcs11SlotLabelType.SLOT_INDEX;
                    } else {
                        type = Pkcs11SlotLabelType.SLOT_LABEL;
                    }
                    cryptoTokenProperties.put(PKCS11CryptoToken.SLOT_LABEL_TYPE, type.getKey());
                    cryptoTokenName = "PKCS11 slot " + p11slot;
                } else if (sunP11ConfigurationFile != null && sunP11ConfigurationFile.length() != 0) {
                    log.info(" Processing PKCS#11 with Sun property file " + sunP11ConfigurationFile);
                    // The following properties are of interest from this file
                    // We will bravely ignore attributes.. it wouldn't be to hard for the user to change the CryptoToken's attributes file later on
                    // name=SafeNet
                    // library=/opt/PTK/lib/libcryptoki.so
                    // slot=1
                    // slotListIndex=1
                    // attributes(...) = {..} 
                    // ...
                    final Properties p11ConfigurationFileProperties = new Properties();
                    p11ConfigurationFileProperties.load(new FileInputStream(sunP11ConfigurationFile));
                    String p11slot = p11ConfigurationFileProperties.getProperty("slot");
                    cryptoTokenProperties.put(PKCS11CryptoToken.SLOT_LABEL_VALUE, p11slot);
                    // Guess label type in order index, number or label 
                    Pkcs11SlotLabelType type;
                    if (Pkcs11SlotLabelType.SLOT_NUMBER.validate(p11slot)) {
                        type = Pkcs11SlotLabelType.SLOT_NUMBER;
                    } else if (Pkcs11SlotLabelType.SLOT_INDEX.validate(p11slot)) {
                        type = Pkcs11SlotLabelType.SLOT_INDEX;
                    } else {
                        type = Pkcs11SlotLabelType.SLOT_LABEL;
                    }
                    cryptoTokenProperties.put(PKCS11CryptoToken.SLOT_LABEL_TYPE, type.getKey());

                    cryptoTokenProperties.put(PKCS11CryptoToken.SHLIB_LABEL_KEY,
                            p11ConfigurationFileProperties.getProperty("library"));
                    //cryptoTokenProperties.put(PKCS11CryptoToken.ATTRIB_LABEL_KEY, null);
                    log.warn("Any attributes(..) = { ... } will be ignored and system defaults will be used."
                            + " You should reconfigure the CryptoToken later if this is not sufficient.");
                    cryptoTokenName = "PKCS11 slot " + p11ConfigurationFileProperties.getProperty("slot",
                            "i" + p11ConfigurationFileProperties.getProperty("slotListIndex"));
                }
                if (cryptoTokenName != null
                        && cryptoTokenManagementSession.getIdFromName(cryptoTokenName) == null) {
                    if (!globalDoNotStorePasswordsInMemory) {
                        log.info(" Auto-activation will be used.");
                        BaseCryptoToken.setAutoActivatePin(cryptoTokenProperties, new String(p11password), true);
                    } else {
                        log.info(" Auto-activation will not be used.");
                    }
                    final int p11CryptoTokenId = cryptoTokenManagementSession.createCryptoToken(authenticationToken,
                            cryptoTokenName, PKCS11CryptoToken.class.getName(), cryptoTokenProperties, null,
                            p11password.toCharArray());
                    // Use reflection to dig out the certificate objects for each alias so we can create an internal key binding for it
                    final Method m = BaseCryptoToken.class.getDeclaredMethod("getKeyStore");
                    m.setAccessible(true);
                    final CachingKeyStoreWrapper cachingKeyStoreWrapper = (CachingKeyStoreWrapper) m
                            .invoke(cryptoTokenManagementSession.getCryptoToken(p11CryptoTokenId));
                    createInternalKeyBindings(authenticationToken, p11CryptoTokenId,
                            cachingKeyStoreWrapper.getKeyStore(), trustDefaults);
                }
            } catch (Exception e) {
                log.error("", e);
            }
        }
        if (OcspConfiguration.getSoftKeyDirectoryName() != null
                && (OcspConfiguration.getStorePassword() != null || activationPassword != null)) {
            final String softStorePassword = OcspConfiguration.getStorePassword() == null
                    ? new String(activationPassword)
                    : OcspConfiguration.getStorePassword();
            final String softKeyPassword = OcspConfiguration.getKeyPassword() == null
                    ? new String(activationPassword)
                    : OcspConfiguration.getKeyPassword();
            final String dirName = OcspConfiguration.getSoftKeyDirectoryName();
            if (dirName != null) {
                final File directory = new File(dirName);
                if (directory.isDirectory()) {
                    log.info(" Processing Soft KeyStores..");
                    for (final File file : directory.listFiles()) {
                        processSoftKeystore(authenticationToken, file, softStorePassword, softKeyPassword,
                                globalDoNotStorePasswordsInMemory, trustDefaults);
                    }
                }
            }
        }
    }

    @Deprecated //Remove this method as soon as upgrading from 5.0->6.x is dropped
    private void processSoftKeystore(AuthenticationToken authenticationToken, File file, String softStorePassword,
            String softKeyPassword, boolean doNotStorePasswordsInMemory,
            List<InternalKeyBindingTrustEntry> trustDefaults) {
        KeyStore keyStore;
        final char[] passwordChars = softStorePassword.toCharArray();
        // Load keystore (JKS or PKCS#12)
        try {
            keyStore = KeyStore.getInstance("JKS");
            keyStore.load(new FileInputStream(file), passwordChars);
        } catch (Exception e) {
            try {
                keyStore = KeyStore.getInstance("PKCS12", "BC");
                keyStore.load(new FileInputStream(file), passwordChars);
            } catch (Exception e2) {
                try {
                    log.info("Unable to process " + file.getCanonicalPath() + " as a KeyStore.");
                } catch (IOException e3) {
                    log.warn(e3.getMessage());
                }
                return;
            }
        }

        // Strip issuer certs, etc. and convert to PKCS#12
        try {
            keyStore = makeKeysOnlyP12(keyStore, passwordChars);
        } catch (Exception e) {
            throw new RuntimeException("failed to convert keystore to P12 during keybindings upgrade", e);
        }

        final String name = file.getName();
        if (cryptoTokenManagementSession.getIdFromName(name) != null) {
            return; // already upgraded
        }
        log.info(" Processing Soft KeyStore '" + name + "' of type " + keyStore.getType());
        try {
            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // Save the store using the same password as the keys are protected with (not the store password)
            // so we don't have to replace the protection for each key
            keyStore.store(baos, softKeyPassword.toCharArray());
            final Properties cryptoTokenProperties = new Properties();
            if (!doNotStorePasswordsInMemory) {
                log.info(" Auto-activation will be used.");
                BaseCryptoToken.setAutoActivatePin(cryptoTokenProperties, new String(softKeyPassword), true);
            } else {
                log.info(" Auto-activation will not be used.");
            }
            final int softCryptoTokenId = cryptoTokenManagementSession.createCryptoToken(authenticationToken, name,
                    SoftCryptoToken.class.getName(), cryptoTokenProperties, baos.toByteArray(),
                    softKeyPassword.toCharArray());
            createInternalKeyBindings(authenticationToken, softCryptoTokenId, keyStore, trustDefaults);
        } catch (Exception e) {
            log.warn(e.getMessage());
        }
    }

    /** Creates a PKCS#12 KeyStore with keys only from an JKS file (no issuer certs or trusted certs) */
    @Deprecated //Remove this method as soon as upgrading from 5->6 is dropped
    private KeyStore makeKeysOnlyP12(KeyStore keyStore, char[] password)
            throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException,
            NoSuchProviderException, CertificateException, IOException {
        final KeyStore p12 = KeyStore.getInstance("PKCS12", "BC");
        final KeyStore.ProtectionParameter protParam = (password != null ? new KeyStore.PasswordProtection(password)
                : null);
        p12.load(null, password); // initialize

        final Enumeration<String> en = keyStore.aliases();
        while (en.hasMoreElements()) {
            final String alias = en.nextElement();
            if (!keyStore.isKeyEntry(alias))
                continue;
            try {
                KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, protParam);
                Certificate[] chain = new Certificate[] { entry.getCertificate() };
                p12.setKeyEntry(alias, entry.getPrivateKey(), password, chain);
            } catch (UnsupportedOperationException uoe) {
                KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null);
                Certificate[] chain = new Certificate[] { entry.getCertificate() };
                p12.setKeyEntry(alias, entry.getPrivateKey(), null, chain);
            }
        }
        return p12;
    }

    /** Create InternalKeyBindings for Ocsp signing and SSL client authentication certs during ad-hoc upgrades. */
    @Deprecated //Remove this method as soon as upgrading from 5->6 is dropped
    private void createInternalKeyBindings(AuthenticationToken authenticationToken, int cryptoTokenId,
            KeyStore keyStore, List<InternalKeyBindingTrustEntry> trustDefaults) throws KeyStoreException,
            CryptoTokenOfflineException, InternalKeyBindingNameInUseException, AuthorizationDeniedException,
            CertificateEncodingException, CertificateImportException, InvalidAlgorithmException {
        final Enumeration<String> aliases = keyStore.aliases();
        boolean noAliases = true;
        while (aliases.hasMoreElements()) {
            final String keyPairAlias = aliases.nextElement();
            noAliases = false;
            log.info("Found alias " + keyPairAlias
                    + ", trying to figure out if this is something we should convert into a new KeyBinding...");
            final Certificate[] chain = keyStore.getCertificateChain(keyPairAlias);
            if (chain == null || chain.length == 0) {
                log.info("Alias " + keyPairAlias + " does not contain any certificate and will be ignored.");
                continue; // Ignore entry
            }
            // Extract the default signature algorithm
            final String signatureAlgorithm = getSigningAlgFromAlgSelection(
                    OcspConfiguration.getSignatureAlgorithm(), chain[0].getPublicKey());
            if (OcspKeyBinding.isOcspSigningCertificate(chain[0])) {
                // Create the actual OcspKeyBinding
                log.info("Alias " + keyPairAlias
                        + " contains an OCSP certificate and will be converted to an OcspKeyBinding.");
                int internalKeyBindingId = internalKeyBindingMgmtSession.createInternalKeyBinding(
                        authenticationToken, OcspKeyBinding.IMPLEMENTATION_ALIAS,
                        "OcspKeyBinding for " + keyPairAlias, InternalKeyBindingStatus.DISABLED, null,
                        cryptoTokenId, keyPairAlias, signatureAlgorithm, getOcspKeyBindingDefaultProperties(),
                        trustDefaults);
                internalKeyBindingMgmtSession.importCertificateForInternalKeyBinding(authenticationToken,
                        internalKeyBindingId, chain[0].getEncoded());
                internalKeyBindingMgmtSession.setStatus(authenticationToken, internalKeyBindingId,
                        InternalKeyBindingStatus.ACTIVE);
            } else if (AuthenticationKeyBinding.isClientSSLCertificate(chain[0])) {
                log.info("Alias " + keyPairAlias
                        + " contains an SSL client certificate and will be converted to an AuthenticationKeyBinding.");
                // We are looking for an SSL cert, use this to create an AuthenticationKeyBinding
                int internalKeyBindingId = internalKeyBindingMgmtSession.createInternalKeyBinding(
                        authenticationToken, AuthenticationKeyBinding.IMPLEMENTATION_ALIAS,
                        "AuthenticationKeyBinding for " + keyPairAlias, InternalKeyBindingStatus.DISABLED, null,
                        cryptoTokenId, keyPairAlias, signatureAlgorithm, null, null);
                internalKeyBindingMgmtSession.importCertificateForInternalKeyBinding(authenticationToken,
                        internalKeyBindingId, chain[0].getEncoded());
                internalKeyBindingMgmtSession.setStatus(authenticationToken, internalKeyBindingId,
                        InternalKeyBindingStatus.ACTIVE);
            } else {
                log.info("Alias " + keyPairAlias + " contains certificate of unknown type and will be ignored.");
            }
        }
        if (noAliases) {
            log.info("No aliases to process were found in the key store.");
        }
    }

    /** @return a list of trusted signers or CAs */
    @Deprecated //This method is only used for upgrading to version 6
    private List<InternalKeyBindingTrustEntry> getOcspKeyBindingTrustDefaults() {
        // Import certificates used to verify OCSP request signatures and add these to each OcspKeyBinding's trust-list
        //  ocsp.signtrustdir=signtrustdir
        //  ocsp.signtrustvalidtime should be ignored
        final List<InternalKeyBindingTrustEntry> trustedCertificateReferences = new ArrayList<InternalKeyBindingTrustEntry>();
        if (OcspConfiguration.getEnforceRequestSigning() && OcspConfiguration.getRestrictSignatures()) {
            // Import certificates and configure Issuer+serialnumber in trustlist for each
            final String dirName = OcspConfiguration.getSignTrustDir();
            if (dirName != null) {
                final File directory = new File(dirName);
                if (directory.isDirectory()) {
                    for (final File file : directory.listFiles()) {
                        try {
                            final List<Certificate> chain = CertTools.getCertsFromPEM(new FileInputStream(file));
                            if (!chain.isEmpty()) {
                                final String issuerDn = CertTools.getIssuerDN(chain.get(0));
                                final String subjectDn = CertTools.getSubjectDN(chain.get(0));
                                if (OcspConfiguration
                                        .getRestrictSignaturesByMethod() == OcspConfiguration.RESTRICTONSIGNER) {
                                    final int caId = issuerDn.hashCode();
                                    final BigInteger serialNumber = CertTools.getSerialNumber(chain.get(0));
                                    if (!caSession.existsCa(caId)) {
                                        log.warn("Trusted certificate with serialNumber "
                                                + serialNumber.toString(16)
                                                + " is issued by an unknown CA with subject '" + issuerDn
                                                + "'. You should import this CA certificate as en external CA to make it known to the system.");
                                    }
                                    trustedCertificateReferences
                                            .add(new InternalKeyBindingTrustEntry(caId, serialNumber));
                                } else {
                                    final int caId = subjectDn.hashCode();
                                    if (!caSession.existsCa(caId)) {
                                        log.warn("Trusted CA certificate with with subject '" + subjectDn
                                                + "' should be imported as en external CA to make it known to the system.");
                                    }
                                    trustedCertificateReferences.add(new InternalKeyBindingTrustEntry(caId, null));
                                }
                            }
                        } catch (CertificateException e) {
                            log.warn(e.getMessage());
                        } catch (FileNotFoundException e) {
                            log.warn(e.getMessage());
                        }
                    }
                }
            }
        }
        return trustedCertificateReferences;
    }

    /** @return OcspKeyBinding properties set to the current file-based configuration (per cert profile config is ignored here) */
    private Map<String, Serializable> getOcspKeyBindingDefaultProperties() {
        // Use global config as defaults for each new OcspKeyBinding
        final Map<String, Serializable> dataMap = new HashMap<String, Serializable>();
        dataMap.put(OcspKeyBinding.PROPERTY_INCLUDE_CERT_CHAIN,
                Boolean.valueOf(OcspConfiguration.getIncludeCertChain()));
        if (OcspConfiguration.getResponderIdType() == OcspConfiguration.RESPONDERIDTYPE_NAME) {
            dataMap.put(OcspKeyBinding.PROPERTY_RESPONDER_ID_TYPE, ResponderIdType.NAME.name());
        } else {
            dataMap.put(OcspKeyBinding.PROPERTY_RESPONDER_ID_TYPE, ResponderIdType.KEYHASH.name());
        }
        dataMap.put(OcspKeyBinding.PROPERTY_MAX_AGE,
                (long) (OcspConfiguration.getMaxAge(CertificateProfileConstants.CERTPROFILE_NO_PROFILE) / 1000L));
        dataMap.put(OcspKeyBinding.PROPERTY_NON_EXISTING_GOOD,
                Boolean.valueOf(OcspConfiguration.getNonExistingIsGood()));
        dataMap.put(OcspKeyBinding.PROPERTY_NON_EXISTING_REVOKED,
                Boolean.valueOf(OcspConfiguration.getNonExistingIsRevoked()));
        dataMap.put(OcspKeyBinding.PROPERTY_UNTIL_NEXT_UPDATE,
                (long) (OcspConfiguration.getUntilNextUpdate(CertificateProfileConstants.CERTPROFILE_NO_PROFILE)
                        / 1000L));
        dataMap.put(OcspKeyBinding.PROPERTY_REQUIRE_TRUSTED_SIGNATURE,
                Boolean.valueOf(OcspConfiguration.getEnforceRequestSigning()));
        return dataMap;
    }

    @Override
    public String healthCheck() {
        final StringBuilder sb = new StringBuilder();
        // Check that there are no ACTIVE OcspKeyBindings that are not in the cache before checking usability..
        for (InternalKeyBindingInfo internalKeyBindingInfo : internalKeyBindingMgmtSession
                .getAllInternalKeyBindingInfos(OcspKeyBinding.IMPLEMENTATION_ALIAS)) {
            if (internalKeyBindingInfo.getStatus().equals(InternalKeyBindingStatus.ACTIVE)) {
                final Certificate ocspCertificate = certificateStoreSession
                        .findCertificateByFingerprint(internalKeyBindingInfo.getCertificateId());
                final X509Certificate issuingCertificate = certificateStoreSession
                        .findLatestX509CertificateBySubject(CertTools.getIssuerDN(ocspCertificate));
                OcspSigningCacheEntry ocspSigningCacheEntry = null;
                if (issuingCertificate != null) {
                    final List<CertificateID> certIds = OcspSigningCache
                            .getCertificateIDFromCertificate(issuingCertificate);
                    // We only need to use the first certId type to find an entry in the cache, certIds.get(0), since all of them should be in the cache
                    ocspSigningCacheEntry = OcspSigningCache.INSTANCE.getEntry(certIds.get(0));
                    if (ocspSigningCacheEntry == null) {
                        //Could be a cache issue?
                        try {
                            ocspSigningCacheEntry = findAndAddMissingCacheEntry(certIds.get(0));
                        } catch (CertificateEncodingException e) {
                            throw new IllegalStateException("Could not process certificate", e);
                        }
                    }
                } else {
                    log.info("Can not find issuer certificate from subject DN '"
                            + CertTools.getIssuerDN(ocspCertificate) + "'.");
                }

                if (ocspSigningCacheEntry == null) {
                    final String errMsg = intres.getLocalizedMessage("ocsp.signingkeynotincache",
                            internalKeyBindingInfo.getName());
                    sb.append('\n').append(errMsg);
                    log.error(errMsg);
                }
            }
        }
        if (!sb.toString().equals("")) {
            return sb.toString();
        }
        try {
            final Collection<OcspSigningCacheEntry> ocspSigningCacheEntries = OcspSigningCache.INSTANCE
                    .getEntries();
            if (ocspSigningCacheEntries.isEmpty()) {
                // Only report this in the server log. It is not an erroneous state to have no ACTIVE OcspKeyBindings.
                if (log.isDebugEnabled()) {
                    log.debug(intres.getLocalizedMessage("ocsp.errornosignkeys"));
                }
            } else {
                for (OcspSigningCacheEntry ocspSigningCacheEntry : ocspSigningCacheEntries) {
                    // Only verify non-CA responders
                    final X509Certificate ocspSigningCertificate = ocspSigningCacheEntry
                            .getOcspSigningCertificate();
                    if (ocspSigningCertificate == null) {
                        continue;
                    }
                    final String subjectDn = CertTools
                            .getSubjectDN(ocspSigningCacheEntry.getCaCertificateChain().get(0));
                    final String serialNumberForLog = CertTools
                            .getSerialNumberAsString(ocspSigningCacheEntry.getOcspSigningCertificate());
                    final String errMsg = intres.getLocalizedMessage("ocsp.errorocspkeynotusable", subjectDn,
                            serialNumberForLog);
                    final PrivateKey privateKey = ocspSigningCacheEntry.getPrivateKey();
                    if (privateKey == null) {
                        sb.append('\n').append(errMsg);
                        log.error("No key available. " + errMsg);
                        continue;
                    }
                    if (OcspConfiguration.getHealthCheckCertificateValidity()
                            && !CertTools.isCertificateValid(ocspSigningCertificate)) {
                        sb.append('\n').append(errMsg);
                        continue;
                    }
                    if (OcspConfiguration.getHealthCheckSignTest()) {
                        try {
                            final String providerName = ocspSigningCacheEntry.getSignatureProviderName();
                            KeyTools.testKey(privateKey, ocspSigningCertificate.getPublicKey(), providerName);
                        } catch (InvalidKeyException e) {
                            // thrown by testKey
                            sb.append('\n').append(errMsg);
                            log.error("Key not working. SubjectDN '" + subjectDn + "'. Error comment '" + errMsg
                                    + "'. Message '" + e.getMessage());
                            continue;
                        }
                    }
                    if (log.isDebugEnabled()) {
                        final String name = ocspSigningCacheEntry.getOcspKeyBinding().getName();
                        log.debug("Test of \"" + name + "\" OK!");
                    }
                }
            }
        } catch (Exception e) {
            final String errMsg = intres.getLocalizedMessage("ocsp.errorloadsigningcerts");
            log.error(errMsg, e);
            sb.append(errMsg).append(": ").append(errMsg);
        }
        return sb.toString();
    }

}

class CardKeyHolder {
    private static final InternalResources intres = InternalResources.getInstance();
    private static CardKeyHolder instance = null;
    private CardKeys cardKeys = null;

    private CardKeyHolder() {
        Logger log = Logger.getLogger(CardKeyHolder.class);
        String hardTokenClassName = OcspConfiguration.getHardTokenClassName();
        try {
            this.cardKeys = (CardKeys) OcspResponseGeneratorSessionBean.class.getClassLoader()
                    .loadClass(hardTokenClassName).newInstance();
            this.cardKeys.autenticate(OcspConfiguration.getCardPassword());
        } catch (ClassNotFoundException e) {
            log.debug(intres.getLocalizedMessage("ocsp.classnotfound", hardTokenClassName));
        } catch (Exception e) {
            log.info("Could not create CardKeyHolder", e);
        }
    }

    public static synchronized CardKeyHolder getInstance() {
        if (instance == null) {
            instance = new CardKeyHolder();
        }
        return instance;
    }

    public CardKeys getCardKeys() {
        return cardKeys;
    }

}