Java tutorial
/************************************************************************* * * * 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.ByteArrayInputStream; import java.io.IOException; import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SignatureException; 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.Arrays; import java.util.Collection; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; import java.util.Map; 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.ejb.EJB; import javax.ejb.Local; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DERGeneralizedTime; import org.bouncycastle.asn1.DERObjectIdentifier; import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers; import org.bouncycastle.asn1.ocsp.RevokedInfo; import org.bouncycastle.asn1.x509.CRLReason; import org.bouncycastle.asn1.x509.X509Extension; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.ocsp.BasicOCSPResp; import org.bouncycastle.ocsp.BasicOCSPRespGenerator; import org.bouncycastle.ocsp.CertificateID; import org.bouncycastle.ocsp.OCSPException; import org.bouncycastle.ocsp.OCSPReq; import org.bouncycastle.ocsp.OCSPResp; import org.bouncycastle.ocsp.OCSPRespGenerator; import org.bouncycastle.ocsp.Req; import org.bouncycastle.ocsp.RespID; import org.bouncycastle.ocsp.RevokedStatus; import org.bouncycastle.ocsp.UnknownStatus; import org.bouncycastle.util.encoders.Hex; import org.cesecore.authentication.tokens.AuthenticationToken; import org.cesecore.authorization.AuthorizationDeniedException; import org.cesecore.certificates.ca.CADoesntExistsException; import org.cesecore.certificates.ca.SignRequestException; import org.cesecore.certificates.ca.SignRequestSignatureException; import org.cesecore.certificates.ca.extendedservices.ExtendedCAServiceNotActiveException; import org.cesecore.certificates.ca.extendedservices.ExtendedCAServiceRequestException; import org.cesecore.certificates.ca.extendedservices.IllegalExtendedCAServiceRequestException; import org.cesecore.certificates.certificate.CertificateStatus; import org.cesecore.certificates.certificate.CertificateStoreSessionLocal; import org.cesecore.certificates.certificateprofile.CertificateProfileConstants; import org.cesecore.certificates.ocsp.cache.CryptoTokenAndChain; import org.cesecore.certificates.ocsp.cache.DirectoryCache; import org.cesecore.certificates.ocsp.cache.OcspExtensionsCache; import org.cesecore.certificates.ocsp.cache.TokenAndChainCache; import org.cesecore.certificates.ocsp.exception.CryptoProviderException; import org.cesecore.certificates.ocsp.exception.MalformedRequestException; import org.cesecore.certificates.ocsp.exception.NotSupportedException; import org.cesecore.certificates.ocsp.exception.OcspFailureException; import org.cesecore.certificates.ocsp.extension.OCSPExtension; import org.cesecore.certificates.ocsp.logging.AuditLogger; import org.cesecore.certificates.ocsp.logging.GuidHolder; import org.cesecore.certificates.ocsp.logging.PatternLogger; import org.cesecore.certificates.ocsp.logging.TransactionCounter; import org.cesecore.certificates.ocsp.logging.TransactionLogger; import org.cesecore.certificates.util.AlgorithmTools; import org.cesecore.config.OcspConfiguration; import org.cesecore.internal.InternalResources; import org.cesecore.keys.token.CryptoTokenOfflineException; import org.cesecore.keys.token.IllegalCryptoTokenException; import org.cesecore.util.CertTools; import org.cesecore.util.log.ProbableErrorHandler; /** * Common abstract class for OCSP response generator session beans. * * @version $Id$ * */ enum CanLogCache { INSTANCE; private CanLogCache() { this.canLog = true; } private boolean canLog; public boolean canLog() { return canLog; } public void setCanLog(boolean canLog) { this.canLog = canLog; } } @Local interface OcspResponseSessionLocal { } @Stateless @TransactionAttribute(TransactionAttributeType.SUPPORTS) public abstract class OcspResponseSessionBean implements OcspResponseSessionLocal { private static final Logger log = Logger.getLogger(OcspResponseSessionBean.class); private static final InternalResources intres = InternalResources.getInstance(); /** Max size of a request is 100000 bytes */ private static final int MAX_REQUEST_SIZE = 100000; @EJB private CertificateStoreSessionLocal certificateStoreSession; /** * Use this method to perform any ops that can't be done in @PostConstruct, if necessary. */ protected abstract void initiateIfNecessary(); /** * * @return a reference to the extending bean's token and chain cache. Allows different children to use separate caches. */ protected abstract TokenAndChainCache getTokenAndChainCache(); // @Override public byte[] getOcspResponse(AuthenticationToken authenticationToken, byte[] request, X509Certificate[] requestCertificates, String remoteAddress, String remoteHost) throws AuthorizationDeniedException, MalformedRequestException { initiateIfNecessary(); // 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; int localTransactionId = TransactionCounter.INSTANCE.getTransactionNumber(); // Create the transaction logger for this transaction. TransactionLogger transactionLogger = new TransactionLogger(localTransactionId, GuidHolder.INSTANCE.getGlobalUid(), remoteAddress); // Create the audit logger for this transaction. AuditLogger auditLogger = new AuditLogger("", localTransactionId, GuidHolder.INSTANCE.getGlobalUid(), remoteAddress); // Start logging process time after we have received the request transactionLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME); auditLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME); auditLogger.paramPut(AuditLogger.OCSPREQUEST, new String(Hex.encode(request))); OCSPReq req; OCSPRespGenerator responseGenerator = new OCSPRespGenerator(); try { 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); } 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."); } transactionLogger.paramPut(TransactionLogger.STATUS, OCSPRespGenerator.SUCCESSFUL); auditLogger.paramPut(AuditLogger.STATUS, OCSPRespGenerator.SUCCESSFUL); CryptoTokenAndChain signerTokenAndChain = null; long maxAge = OcspConfiguration.getMaxAge(CertificateProfileConstants.CERTPROFILE_NO_PROFILE); long nextUpdate = OcspConfiguration .getUntilNextUpdate(CertificateProfileConstants.CERTPROFILE_NO_PROFILE); // Add standard response extensions Hashtable<DERObjectIdentifier, X509Extension> responseExtensions = getStandardResponseExtensions( req); // Look for extension OIDs final Collection<String> extensionOids = OcspConfiguration.getExtensionOids(); // Look over the status requests List<OCSPResponseItem> responseList = new ArrayList<OCSPResponseItem>(); for (Req ocspRequest : ocspRequests) { CertificateID certId = ocspRequest.getCertID(); transactionLogger.paramPut(TransactionLogger.SERIAL_NOHEX, certId.getSerialNumber().toByteArray()); // TODO:find text version of this or find out if it should be something else transactionLogger.paramPut(TransactionLogger.DIGEST_ALGOR, certId.getHashAlgOID()); transactionLogger.paramPut(TransactionLogger.ISSUER_NAME_HASH, certId.getIssuerNameHash()); transactionLogger.paramPut(TransactionLogger.ISSUER_KEY, certId.getIssuerKeyHash()); 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 signerTokenAndChain = getTokenAndChainCache().get(certId); /* * 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. */ if (signerTokenAndChain != null) { transactionLogger.paramPut(TransactionLogger.ISSUER_NAME_DN, signerTokenAndChain.getCaCertificate().getSubjectDN().getName()); } else { // We could not find certificate for this request so get certificate for default responder signerTokenAndChain = getTokenAndChainCache().getForDefaultResponder(); if (signerTokenAndChain != 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)); transactionLogger.paramPut(TransactionLogger.CERT_STATUS, OCSPResponseItem.OCSP_UNKNOWN); transactionLogger.writeln(); continue; } else { String errMsg = intres.getLocalizedMessage("ocsp.errorfindcacert", new String(Hex.encode(certId.getIssuerNameHash())), OcspConfiguration.getDefaultResponderId()); log.error(errMsg); continue; } } /* * Implement logic 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. */ final org.bouncycastle.ocsp.CertificateStatus certStatus; transactionLogger.paramPut(TransactionLogger.CERT_STATUS, OCSPResponseItem.OCSP_GOOD); // it seems to be correct // Check if the cacert (or the default responderid) is revoked final CertificateStatus signerIssuerCertStatus = certificateStoreSession.getStatus( CertTools.getSubjectDN(signerTokenAndChain.getCaCertificate()), CertTools.getSerialNumber(signerTokenAndChain.getCaCertificate())); String subjectDn = signerTokenAndChain.getCaCertificate().getSubjectDN().getName(); if (!signerIssuerCertStatus.equals(CertificateStatus.REVOKED)) { // Check if cert is revoked final CertificateStatus status = certificateStoreSession.getStatus(subjectDn, certId.getSerialNumber()); /* If we have different maxAge and untilNextUpdate for different certificate profiles, we have to fetch these values now that we have fetched the certificate status, that includes certificate profile.*/ nextUpdate = OcspConfiguration.getUntilNextUpdate(status.certificateProfileId); maxAge = OcspConfiguration.getMaxAge(status.certificateProfileId); if (log.isDebugEnabled()) { log.debug("Set nextUpdate=" + nextUpdate + ", and maxAge=" + maxAge + " for certificateProfileId=" + status.certificateProfileId); } final String sStatus; 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 '" + subjectDn + "'"); } /* * If we do not treat non existing certificates as good * OR * we don't actually handle requests for the CA issuing the certificate asked about * then we return unknown * */ if ((!OcspConfiguration.getNonExistingIsGood()) || (getTokenAndChainCache().get(certId) == null)) { sStatus = "unknown"; certStatus = new UnknownStatus(); } else { sStatus = "good"; certStatus = null; // null means "good" in OCSP } } else if (status.equals(CertificateStatus.REVOKED)) { // Revocation info available for this cert, handle it sStatus = "revoked"; certStatus = new RevokedStatus( new RevokedInfo(new DERGeneralizedTime(status.revocationDate), new CRLReason(status.revocationReason))); } else { sStatus = "good"; certStatus = null; } infoMsg = intres.getLocalizedMessage("ocsp.infoaddedstatusinfo", sStatus, certId.getSerialNumber().toString(16), subjectDn); log.info(infoMsg); responseList.add(new OCSPResponseItem(certId, certStatus, nextUpdate)); } else { certStatus = new RevokedStatus( new RevokedInfo(new DERGeneralizedTime(signerIssuerCertStatus.revocationDate), new CRLReason(signerIssuerCertStatus.revocationReason))); infoMsg = intres.getLocalizedMessage("ocsp.infoaddedstatusinfo", "revoked", certId.getSerialNumber().toString(16), subjectDn); log.info(infoMsg); responseList.add(new OCSPResponseItem(certId, certStatus, nextUpdate)); } for (String oidstr : extensionOids) { DERObjectIdentifier oid = new DERObjectIdentifier(oidstr); X509Extensions reqexts = req.getRequestExtensions(); if (reqexts != null) { X509Extension ext = reqexts.getExtension(oid); if (null != ext) { // 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 X509Certificate cert = null; cert = (X509Certificate) certificateStoreSession .findCertificateByIssuerAndSerno(subjectDn, certId.getSerialNumber()); if (cert != null) { // Call the OCSP extension Map<DERObjectIdentifier, X509Extension> 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 (signerTokenAndChain != null) { // Add responseExtensions X509Extensions exts = new X509Extensions(responseExtensions); // generate the signed response object final X509Certificate[] signerChain = signerTokenAndChain.getChain(); final PrivateKey privateKey = signerTokenAndChain.getPrivateKey(); final String privateKeyProvider = signerTokenAndChain.getSignProviderName(); BasicOCSPResp basicresp = signOcspResponse(req, responseList, exts, signerChain, privateKey, privateKeyProvider); ocspResponse = responseGenerator.generate(OCSPRespGenerator.SUCCESSFUL, basicresp); auditLogger.paramPut(AuditLogger.STATUS, OCSPRespGenerator.SUCCESSFUL); transactionLogger.paramPut(TransactionLogger.STATUS, OCSPRespGenerator.SUCCESSFUL); } else { // Only unknown CAs in requests and no default responder's cert String errMsg = intres.getLocalizedMessage("ocsp.errornocacreateresp"); log.error(errMsg); throw new OcspFailureException(errMsg); } } catch (MalformedRequestException e) { transactionLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME); auditLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME); String errMsg = intres.getLocalizedMessage("ocsp.errorprocessreq", e.getMessage()); log.info(errMsg); if (log.isDebugEnabled()) { log.debug(errMsg, e); } ocspResponse = responseGenerator.generate(OCSPRespGenerator.MALFORMED_REQUEST, null); // RFC 2560: responseBytes are not set on error. transactionLogger.paramPut(TransactionLogger.STATUS, OCSPRespGenerator.MALFORMED_REQUEST); transactionLogger.writeln(); auditLogger.paramPut(AuditLogger.STATUS, OCSPRespGenerator.MALFORMED_REQUEST); } catch (SignRequestException e) { transactionLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME); 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 ocspResponse = responseGenerator.generate(OCSPRespGenerator.SIG_REQUIRED, null); // RFC 2560: responseBytes are not set on error. transactionLogger.paramPut(TransactionLogger.STATUS, OCSPRespGenerator.SIG_REQUIRED); transactionLogger.writeln(); auditLogger.paramPut(AuditLogger.STATUS, OCSPRespGenerator.SIG_REQUIRED); } catch (SignRequestSignatureException e) { transactionLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME); 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 ocspResponse = responseGenerator.generate(OCSPRespGenerator.UNAUTHORIZED, null); // RFC 2560: responseBytes are not set on error. transactionLogger.paramPut(TransactionLogger.STATUS, OCSPRespGenerator.UNAUTHORIZED); transactionLogger.writeln(); auditLogger.paramPut(AuditLogger.STATUS, OCSPRespGenerator.UNAUTHORIZED); } catch (NoSuchAlgorithmException e) { ocspResponse = processDefaultError(responseGenerator, transactionLogger, auditLogger, e); } catch (CertificateException e) { ocspResponse = processDefaultError(responseGenerator, transactionLogger, auditLogger, e); } catch (ExtendedCAServiceNotActiveException e) { ocspResponse = processDefaultError(responseGenerator, transactionLogger, auditLogger, e); } catch (IllegalExtendedCAServiceRequestException e) { ocspResponse = processDefaultError(responseGenerator, transactionLogger, auditLogger, e); } catch (ExtendedCAServiceRequestException e) { ocspResponse = processDefaultError(responseGenerator, transactionLogger, auditLogger, e); } catch (CADoesntExistsException e) { ocspResponse = processDefaultError(responseGenerator, transactionLogger, auditLogger, e); } catch (IllegalCryptoTokenException e) { ocspResponse = processDefaultError(responseGenerator, transactionLogger, auditLogger, e); } catch (CryptoTokenOfflineException e) { ocspResponse = processDefaultError(responseGenerator, transactionLogger, auditLogger, e); } try { respBytes = ocspResponse.getEncoded(); auditLogger.paramPut(AuditLogger.OCSPRESPONSE, new String(Hex.encode(respBytes))); auditLogger.writeln(); auditLogger.flush(); transactionLogger.flush(); if (OcspConfiguration.getLogSafer()) { // See if the Errorhandler has found any problems if (hasErrorHandlerFailedSince(startTime)) { log.info("ProbableErrorhandler reported error, cannot answer request"); ocspResponse = responseGenerator.generate(OCSPRespGenerator.INTERNAL_ERROR, null); // RFC 2560: responseBytes are not set on // error. respBytes = ocspResponse.getEncoded(); } // See if the Appender has reported any problems if (!CanLogCache.INSTANCE.canLog()) { log.info("SaferDailyRollingFileAppender reported error, cannot answer request"); ocspResponse = responseGenerator.generate(OCSPRespGenerator.INTERNAL_ERROR, null); // RFC 2560: responseBytes are not set on // error. respBytes = ocspResponse.getEncoded(); } } } catch (IOException e) { log.error("", e); transactionLogger.flush(); auditLogger.flush(); } } catch (OCSPException e) { throw new OcspFailureException("OCSP response generation failed", e); } return respBytes; } public void setCanlog(boolean canLog) { CanLogCache.INSTANCE.setCanLog(canLog); } /** * Method that checks with ProbeableErrorHandler if an error has happened since a certain time. Uses reflection to call ProbeableErrorHandler * 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; } /** * This method exists solely to avoid code duplication when error handling in getOcspResponse. * * @param responseGenerator A OCSPRespGenerator 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(OCSPRespGenerator responseGenerator, TransactionLogger transactionLogger, AuditLogger auditLogger, Throwable e) throws OCSPException { transactionLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME); auditLogger.paramPut(PatternLogger.PROCESS_TIME, PatternLogger.PROCESS_TIME); String errMsg = intres.getLocalizedMessage("ocsp.errorprocessreq", e.getMessage()); log.error(errMsg, e); transactionLogger.paramPut(TransactionLogger.STATUS, OCSPRespGenerator.INTERNAL_ERROR); transactionLogger.writeln(); auditLogger.paramPut(AuditLogger.STATUS, OCSPRespGenerator.INTERNAL_ERROR); return responseGenerator.generate(OCSPRespGenerator.INTERNAL_ERROR, null); // RFC 2560: responseBytes are not set on error. } /** * This method takes byte array and translates it onto a OCSPReq class. * * @param authenticationToken An authentication token needed to perform validation. * @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 InvalidKeyException * @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 { OCSPReq result = null; try { result = new OCSPReq(request); } catch (IOException e) { throw new MalformedRequestException("Could not form OCSP request", e); } if (result.getRequestorName() == null) { if (log.isDebugEnabled()) { log.debug("Requestor name is null"); } } else { if (log.isDebugEnabled()) { log.debug("Requestor name is: " + result.getRequestorName().toString()); } transactionLogger.paramPut(TransactionLogger.REQ_NAME, result.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 : " + result.isSigned()); } if (result.isSigned()) { X509Certificate signercert = checkRequestSignature(remoteAddress, result); String signercertIssuerName = CertTools.getIssuerDN(signercert); BigInteger signercertSerNo = CertTools.getSerialNumber(signercert); String signercertSubjectName = CertTools.getSubjectDN(signercert); 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); if (OcspConfiguration.getEnforceRequestSigning()) { // If it verifies OK, check if it is revoked final CertificateStatus status = certificateStoreSession .getStatus(CertTools.getIssuerDN(signercert), CertTools.getSerialNumber(signercert)); /* * 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); } if (OcspConfiguration.getRestrictSignatures()) { DirectoryCache.INSTANCE.loadTrustDir(); switch (OcspConfiguration.getRestrictSignaturesByMethod()) { case OcspConfiguration.RESTRICTONSIGNER: if (!checkCertInList(signercert, DirectoryCache.INSTANCE.getTrustedReqSigSigners())) { String infoMsg = intres.getLocalizedMessage("ocsp.infosigner.notallowed", signercertSubjectName, signercertIssuerName, signercertSerNo.toString(16)); log.info(infoMsg); throw new SignRequestSignatureException(infoMsg); } break; case OcspConfiguration.RESTRICTONISSUER: X509Certificate signerca = certificateStoreSession .findLatestX509CertificateBySubject(signercertIssuerName); if ((signerca == null) || (!checkCertInList(signerca, DirectoryCache.INSTANCE.getTrustedReqSigIssuers()))) { String infoMsg = intres.getLocalizedMessage("ocsp.infosigner.notallowed", signercertSubjectName, signercertIssuerName, signercertSerNo.toString(16)); log.info(infoMsg); throw new SignRequestSignatureException(infoMsg); } break; default: // There must be an internal error. We do not want to send a response, just to be safe. throw new OcspFailureException( "m_reqRestrictMethod=" + OcspConfiguration.getRestrictSignaturesByMethod()); } } } } else { if (OcspConfiguration.getEnforceRequestSigning()) { // Signature required throw new SignRequestException("Signature required"); } } return result; } private BasicOCSPRespGenerator createOcspResponseGenerator(OCSPReq req, X509Certificate respondercert, int respIdType) throws OCSPException, NotSupportedException { if (null == req) { throw new IllegalArgumentException(); } BasicOCSPRespGenerator res = null; if (respIdType == OcspConfiguration.RESPONDERIDTYPE_NAME) { res = new BasicOCSPRespGenerator(new RespID(respondercert.getSubjectX500Principal())); } else { res = new BasicOCSPRespGenerator(respondercert.getPublicKey()); } X509Extensions reqexts = req.getRequestExtensions(); if (reqexts != null) { X509Extension ext = reqexts.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_response); if (null != ext) { // log.debug("Found extension AcceptableResponses"); ASN1OctetString oct = ext.getValue(); try { ASN1Sequence seq = ASN1Sequence.getInstance( new ASN1InputStream(new ByteArrayInputStream(oct.getOctets())).readObject()); @SuppressWarnings("unchecked") Enumeration<DERObjectIdentifier> en = seq.getObjects(); boolean supportsResponseType = false; while (en.hasMoreElements()) { DERObjectIdentifier oid = en.nextElement(); // log.debug("Found oid: "+oid.getId()); if (oid.equals(OCSPObjectIdentifiers.id_pkix_ocsp_basic)) { // This is the response type we support, so we are happy! Break the loop. supportsResponseType = true; log.debug("Response type supported: " + oid.getId()); continue; } } if (!supportsResponseType) { throw new NotSupportedException( "Required response type not supported, this responder only supports id-pkix-ocsp-basic."); } } catch (IOException e) { } } } return res; } /** * Checks the signature on an OCSP request and checks that it is signed by an allowed CA. 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; if (!req.isSigned()) { String infoMsg = intres.getLocalizedMessage("ocsp.errorunsignedreq", clientRemoteAddr); log.info(infoMsg); throw new SignRequestException(infoMsg); } // Get all certificates embedded in the request (probably a certificate chain) try { X509Certificate[] certs = req.getCerts("BC"); // Set, as a try, the signer to be the first certificate, so we have a name to log... String signer = null; if (certs.length > 0) { signer = CertTools.getSubjectDN(certs[0]); } // We must find a certificate to verify the signature with... boolean verifyOK = false; for (int i = 0; i < certs.length; i++) { if (req.verify(certs[i].getPublicKey(), "BC") == true) { signercert = certs[i]; signer = CertTools.getSubjectDN(signercert); Date now = new Date(); String signerissuer = CertTools.getIssuerDN(signercert); String infoMsg = intres.getLocalizedMessage("ocsp.infosigner", signer); log.info(infoMsg); verifyOK = true; /* * Also check that the signer certificate can be verified by one of the CA-certificates that we answer for */ X509Certificate signerca = certificateStoreSession .findLatestX509CertificateBySubject(CertTools.getIssuerDN(certs[i])); String subject = signer; String issuer = signerissuer; if (signerca != null) { try { signercert.verify(signerca.getPublicKey()); if (log.isDebugEnabled()) { log.debug("Checking validity. Now: " + now + ", signerNotAfter: " + signercert.getNotAfter()); } CertTools.checkValidity(signercert, now); // Move the error message string to the CA cert subject = CertTools.getSubjectDN(signerca); issuer = CertTools.getIssuerDN(signerca); CertTools.checkValidity(signerca, now); } catch (SignatureException e) { infoMsg = intres.getLocalizedMessage("ocsp.infosigner.invalidcertsignature", subject, issuer, e.getMessage()); log.info(infoMsg); verifyOK = false; } catch (InvalidKeyException e) { infoMsg = intres.getLocalizedMessage("ocsp.infosigner.invalidcertsignature", subject, issuer, e.getMessage()); log.info(infoMsg); verifyOK = false; } catch (CertificateNotYetValidException e) { infoMsg = intres.getLocalizedMessage("ocsp.infosigner.certnotyetvalid", subject, issuer, e.getMessage()); log.info(infoMsg); verifyOK = false; } catch (CertificateExpiredException e) { infoMsg = intres.getLocalizedMessage("ocsp.infosigner.certexpired", subject, issuer, e.getMessage()); log.info(infoMsg); verifyOK = false; } } else { infoMsg = intres.getLocalizedMessage("ocsp.infosigner.nocacert", signer, signerissuer); log.info(infoMsg); verifyOK = false; } break; } } if (!verifyOK) { String errMsg = intres.getLocalizedMessage("ocsp.errorinvalidsignature", signer); 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; } /** * Checks to see if a certificate is in a list of certificate. Comparison is made on SerialNumber * * @param cert the certificate to look for * @param trustedCerts the list (Hashtable) to look in * @return true if cert is in trustedCerts, false otherwise */ private boolean checkCertInList(X509Certificate cert, Map<String, X509Certificate> trustedCerts) { String key = cert.getIssuerDN() + ";" + cert.getSerialNumber().toString(16); return trustedCerts.get(key) != null; } /** * 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 Hashtable<DERObjectIdentifier, X509Extension> getStandardResponseExtensions(OCSPReq req) { X509Extensions reqexts = req.getRequestExtensions(); Hashtable<DERObjectIdentifier, X509Extension> result = new Hashtable<DERObjectIdentifier, X509Extension>(); if (reqexts != null) { // Table of extensions to include in the response X509Extension ext = reqexts.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce); if (null != ext) { result.put(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, ext); } } return result; } private BasicOCSPResp signOcspResponse(OCSPReq req, List<OCSPResponseItem> responseList, X509Extensions exts, final X509Certificate[] signerChain, final PrivateKey privateKey, String privateKeyProvider) throws CADoesntExistsException, ExtendedCAServiceRequestException, IllegalExtendedCAServiceRequestException, ExtendedCAServiceNotActiveException, AuthorizationDeniedException, CryptoTokenOfflineException, IllegalCryptoTokenException { final X509Certificate[] certChain = Arrays.asList(signerChain).toArray(new X509Certificate[0]); final X509Certificate signerCert = certChain[0]; final String sigAlgs = OcspConfiguration.getSignatureAlgorithm(); final PublicKey pk = signerCert.getPublicKey(); final String sigAlg = getSigningAlgFromAlgSelection(sigAlgs, pk); if (log.isDebugEnabled()) { log.debug("Signing algorithm: " + sigAlg); } final boolean includeChain = OcspConfiguration.getIncludeCertChain(); if (log.isDebugEnabled()) { log.debug("Include chain: " + includeChain); } final X509Certificate[] chain; if (includeChain) { chain = certChain; } else { chain = new X509Certificate[1]; chain[0] = signerCert; } try { final int respIdType = OcspConfiguration.getResponderIdType(); final BasicOCSPResp ocspresp = generateBasicOcspResp(req, exts, responseList, sigAlg, signerCert, privateKey, privateKeyProvider, chain, respIdType); // Now we can use the returned OCSPServiceResponse to get private key and cetificate chain to sign the ocsp response if (log.isDebugEnabled()) { Collection<X509Certificate> coll = Arrays.asList(chain); log.debug("Cert chain for OCSP signing is of size " + coll.size()); } if (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 (NotSupportedException e) { log.info("OCSP Request type not supported: ", e); throw new OcspFailureException(e); } catch (IllegalArgumentException e) { log.error("IllegalArgumentException: ", e); throw new OcspFailureException(e); } } private BasicOCSPResp generateBasicOcspResp(OCSPReq ocspRequest, X509Extensions exts, List<OCSPResponseItem> responses, String sigAlg, X509Certificate signerCert, PrivateKey signerKey, String provider, X509Certificate[] chain, int respIdType) throws NotSupportedException, OCSPException, NoSuchProviderException, CryptoTokenOfflineException { BasicOCSPResp returnval = null; BasicOCSPRespGenerator basicRes = null; basicRes = createOcspResponseGenerator(ocspRequest, signerCert, respIdType); if (responses != null) { for (OCSPResponseItem item : responses) { basicRes.addResponse(item.getCertID(), item.getCertStatus(), item.getThisUpdate(), item.getNextUpdate(), null); } } if (exts != null) { @SuppressWarnings("rawtypes") Enumeration oids = exts.oids(); if (oids.hasMoreElements()) { basicRes.setResponseExtensions(exts); } } /* * 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 ExecutorService service = Executors.newFixedThreadPool(1); final Future<BasicOCSPResp> task = service .submit(new HsmResponseThread(basicRes, sigAlg, signerKey, chain, provider)); try { returnval = task.get(HsmResponseThread.HSM_TIMEOUT_SECONDS, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new Error("OCSP response retrieval was interrupted while running. This should not happen", e); } catch (ExecutionException e) { throw new OcspFailureException("Failure encountered while retrieving OCSP response.", e); } catch (TimeoutException e) { 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()); RespID respId = null; if (respIdType == OcspConfiguration.RESPONDERIDTYPE_NAME) { respId = new RespID(signerCert.getSubjectX500Principal()); } else { respId = new RespID(signerCert.getPublicKey()); } if (!returnval.getResponderId().equals(respId)) { log.error("Response responderId does not match signer certificate responderId!"); } boolean verify = returnval.verify(signerCert.getPublicKey(), "BC"); if (verify) { log.debug("The OCSP response is verifying."); } else { log.error("The response is NOT verifying!"); } } return returnval; } /** * 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; } /** * Checks if a certificate is valid Does also print a WARN if the certificate is about to expire. * * @param signerCert the certificate to be tested * @return true if the certificate is valid */ private static boolean isCertificateValid(X509Certificate signerCert) { try { signerCert.checkValidity(); } catch (CertificateExpiredException e) { log.error(intres.getLocalizedMessage("ocsp.errorcerthasexpired", signerCert.getSerialNumber(), signerCert.getIssuerDN())); return false; } catch (CertificateNotYetValidException e) { log.error(intres.getLocalizedMessage("ocsp.errornotyetvalid", signerCert.getSerialNumber(), signerCert.getIssuerDN())); return false; } final long warnBeforeExpirationTime = OcspConfiguration.getWarningBeforeExpirationTime(); if (warnBeforeExpirationTime < 1) { return true; } final Date warnDate = new Date(new Date().getTime() + warnBeforeExpirationTime); try { signerCert.checkValidity(warnDate); } catch (CertificateExpiredException e) { log.warn(intres.getLocalizedMessage("ocsp.warncertwillexpire", signerCert.getSerialNumber(), signerCert.getIssuerDN(), signerCert.getNotAfter())); } catch (CertificateNotYetValidException e) { throw new Error("This should never happen.", e); } if (!log.isDebugEnabled()) { return true; } log.debug("Time for \"certificate will soon expire\" not yet reached. You will be warned after: " + new Date(signerCert.getNotAfter().getTime() - warnBeforeExpirationTime)); return true; } }