/*************************************************************************
* *
* EJBCA: The OpenSource Certificate Authority *
* *
* This software is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or any later version. *
* *
* See terms of license at gnu.org. *
* *
*************************************************************************/
package org.ejbca.core.protocol.ocsp;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
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.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
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.DERObjectIdentifier;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
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.OCSPException;
import org.bouncycastle.ocsp.OCSPReq;
import org.bouncycastle.ocsp.RespID;
import org.ejbca.config.OcspConfiguration;
import org.ejbca.core.model.InternalResources;
import org.ejbca.core.model.ca.NotSupportedException;
import org.ejbca.core.model.ca.SignRequestException;
import org.ejbca.core.model.ca.SignRequestSignatureException;
import org.ejbca.core.model.ca.caadmin.extendedcaservices.ExtendedCAServiceRequestException;
import org.ejbca.core.model.ca.caadmin.extendedcaservices.IllegalExtendedCAServiceRequestException;
import org.ejbca.core.model.ca.caadmin.extendedcaservices.OCSPCAServiceRequest;
import org.ejbca.core.model.ca.caadmin.extendedcaservices.OCSPCAServiceResponse;
import org.ejbca.core.model.util.AlgorithmTools;
import org.ejbca.core.protocol.certificatestore.HashID;
import org.ejbca.core.protocol.certificatestore.ICertificateCache;
import org.ejbca.util.CertTools;
import org.ejbca.util.FileTools;
/** Class with common methods used by both Internal and External OCSP responders
*
* @author tomas
* @version $Id: OCSPUtil.java 11154 2011-01-12 09:56:23Z jeklund $
*
*/
public class OCSPUtil {
private static final Logger m_log = Logger.getLogger(OCSPUtil.class);
/** Internal localization of logs and errors */
private static final InternalResources intres = InternalResources.getInstance();
public static BasicOCSPRespGenerator createOCSPResponse(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) {
//m_log.debug("Found extension AcceptableResponses");
ASN1OctetString oct = ext.getValue();
try {
ASN1Sequence seq = ASN1Sequence.getInstance(new ASN1InputStream(new ByteArrayInputStream(oct.getOctets())).readObject());
Enumeration en = seq.getObjects();
boolean supportsResponseType = false;
while (en.hasMoreElements()) {
DERObjectIdentifier oid = (DERObjectIdentifier) en.nextElement();
//m_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;
m_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;
}
public static BasicOCSPResp generateBasicOCSPResp(OCSPCAServiceRequest serviceReq, String sigAlg, X509Certificate signerCert, PrivateKey signerKey, String provider, X509Certificate[] chain, int respIdType)
throws NotSupportedException, OCSPException, NoSuchProviderException, IllegalArgumentException {
BasicOCSPResp returnval = null;
BasicOCSPRespGenerator basicRes = null;
basicRes = OCSPUtil.createOCSPResponse(serviceReq.getOCSPrequest(), signerCert, respIdType);
ArrayList responses = serviceReq.getResponseList();
if (responses != null) {
Iterator iter = responses.iterator();
while (iter.hasNext()) {
OCSPResponseItem item = (OCSPResponseItem)iter.next();
basicRes.addResponse(item.getCertID(), item.getCertStatus(), item.getThisUpdate(), item.getNextUpdate(), null);
}
}
X509Extensions exts = serviceReq.getExtensions();
if (exts != null) {
Enumeration oids = exts.oids();
if (oids.hasMoreElements()) {
basicRes.setResponseExtensions(exts);
}
}
returnval = basicRes.generate(sigAlg, signerKey, chain, new Date(), provider );
if (m_log.isDebugEnabled()) {
m_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)) {
m_log.error("Response responderId does not match signer certificate responderId!");
}
boolean verify = returnval.verify(signerCert.getPublicKey(), "BC");
if (verify) {
m_log.debug("The OCSP response is verifying.");
} else {
m_log.error("The response is NOT verifying!");
}
}
return returnval;
}
/**
* 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
*/
public static boolean isCertificateValid( X509Certificate signerCert ) {
try {
signerCert.checkValidity();
} catch (CertificateExpiredException e) {
m_log.error(intres.getLocalizedMessage("ocsp.errorcerthasexpired",
signerCert.getSerialNumber(), signerCert.getIssuerDN()));
return false;
} catch (CertificateNotYetValidException e) {
m_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) {
m_log.warn(intres.getLocalizedMessage("ocsp.warncertwillexpire", signerCert.getSerialNumber(),
signerCert.getIssuerDN(), signerCert.getNotAfter()));
} catch (CertificateNotYetValidException e) {
throw new Error("This should never happen.", e);
}
if ( !m_log.isDebugEnabled() ) {
return true;
}
m_log.debug("Time for \"certificate will soon expire\" not yet reached. You will be warned after: "+
new Date(signerCert.getNotAfter().getTime()-warnBeforeExpirationTime));
return true;
}
/**
* Method generates an ExtendedCAServiceResponse which is a OCSPCAServiceResponse wrapping the BasicOCSPRespfor usage
* internally in EJBCA.
*
* @param ocspServiceReq OCSPCAServiceRequest
* @param privKey PrivateKey used to sign the OCSP response
* @param providerName Provider for the private key, can be on HSM
* @param certChain Certificate chain for signing the OCSP response
* @return OCSPCAServiceResponse
* @throws IllegalExtendedCAServiceRequestException
* @throws ExtendedCAServiceRequestException
*/
public static OCSPCAServiceResponse createOCSPCAServiceResponse(OCSPCAServiceRequest ocspServiceReq, PrivateKey privKey, String providerName, X509Certificate[] certChain)
throws IllegalExtendedCAServiceRequestException, ExtendedCAServiceRequestException {
final X509Certificate signerCert = certChain[0];
final String sigAlgs = ocspServiceReq.getSigAlg();
final PublicKey pk = signerCert.getPublicKey();
final String sigAlg = OCSPUtil.getSigningAlgFromAlgSelection(sigAlgs, pk);
m_log.debug("Signing algorithm: "+sigAlg);
final boolean includeChain = ocspServiceReq.includeChain();
m_log.debug("Include chain: "+includeChain);
final X509Certificate[] chain;
if (includeChain) {
chain = certChain;
} else {
chain = new X509Certificate[1];
chain[0] = signerCert;
}
try {
final int respIdType = ocspServiceReq.getRespIdType();
final BasicOCSPResp ocspresp = OCSPUtil.generateBasicOCSPResp(ocspServiceReq, sigAlg, signerCert, privKey, providerName, chain, respIdType);
final OCSPCAServiceResponse result = new OCSPCAServiceResponse(ocspresp, Arrays.asList(chain));
isCertificateValid(signerCert);
return result;
} catch (OCSPException ocspe) {
throw new ExtendedCAServiceRequestException(ocspe);
} catch (NoSuchProviderException nspe) {
throw new ExtendedCAServiceRequestException(nspe);
} catch (NotSupportedException e) {
m_log.info("OCSP Request type not supported: ", e);
throw new IllegalExtendedCAServiceRequestException(e);
} catch (IllegalArgumentException e) {
m_log.error("IllegalArgumentException: ", e);
throw new IllegalExtendedCAServiceRequestException(e);
}
} // createOCSPCAServiceResponse
/**
* 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
*/
public 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;
}
}
m_log.debug("Using signature algorithm for response: "+sigAlg);
return sigAlg;
}
/** 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 hostname of the remote client that sent the request, can be null.
* @param req The signed OCSPReq
* @param cacerts a CertificateCache of Certificates, the authorized CA-certificates. The signer certificate must be issued by one of these.
* @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
*/
public static X509Certificate checkRequestSignature(String clientRemoteAddr, OCSPReq req, ICertificateCache cacerts)
throws SignRequestException, OCSPException,
NoSuchProviderException, CertificateException,
NoSuchAlgorithmException, InvalidKeyException,
SignRequestSignatureException {
X509Certificate signercert = null;
if (!req.isSigned()) {
String infoMsg = intres.getLocalizedMessage("ocsp.errorunsignedreq", clientRemoteAddr);
m_log.info(infoMsg);
throw new SignRequestException(infoMsg);
}
// Get all certificates embedded in the request (probably a certificate chain)
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 cert 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);
m_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 = cacerts.findLatestBySubjectDN(HashID.getFromIssuerDN(certs[i]));
String subject = signer;
String issuer = signerissuer;
if (signerca != null) {
try {
signercert.verify(signerca.getPublicKey());
if (m_log.isDebugEnabled()) {
m_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());
m_log.info(infoMsg);
verifyOK = false;
} catch (InvalidKeyException e) {
infoMsg = intres.getLocalizedMessage("ocsp.infosigner.invalidcertsignature", subject, issuer, e.getMessage());
m_log.info(infoMsg);
verifyOK = false;
} catch (CertificateNotYetValidException e) {
infoMsg = intres.getLocalizedMessage("ocsp.infosigner.certnotyetvalid", subject, issuer, e.getMessage());
m_log.info(infoMsg);
verifyOK = false;
} catch (CertificateExpiredException e) {
infoMsg = intres.getLocalizedMessage("ocsp.infosigner.certexpired", subject, issuer, e.getMessage());
m_log.info(infoMsg);
verifyOK = false;
}
} else {
infoMsg = intres.getLocalizedMessage("ocsp.infosigner.nocacert", signer, signerissuer);
m_log.info(infoMsg);
verifyOK = false;
}
break;
}
}
if (!verifyOK) {
String errMsg = intres.getLocalizedMessage("ocsp.errorinvalidsignature", signer);
m_log.info(errMsg);
throw new SignRequestSignatureException(errMsg);
}
return signercert;
}
/** returns an HashTable of responseExtensions to be added to the BacisOCSPResponseGenerator with
* <code>
* X509Extensions exts = new X509Extensions(table);
* basicRes.setResponseExtensions(responseExtensions);
* </code>
*
* @param req OCSPReq
* @return a Hashtable, can be empty nut not null
*/
public static Hashtable getStandardResponseExtensions(OCSPReq req) {
X509Extensions reqexts = req.getRequestExtensions();
Hashtable table = new Hashtable();
if (reqexts != null) {
// Table of extensions to include in the response
X509Extension ext = reqexts.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
if (null != ext) {
//m_log.debug("Found extension Nonce");
table.put(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, ext);
}
}
return table;
}
public static Hashtable getCertificatesFromDirectory(String certificateDir) throws IOException {
// read all files from trustDir, expect that they are PEM formatted certificates
CertTools.installBCProvider();
File dir = new File(certificateDir);
Hashtable trustedCerts = new Hashtable();
if (dir == null || dir.isDirectory() == false) {
m_log.error(dir.getCanonicalPath()+ " is not a directory.");
throw new IllegalArgumentException(dir.getCanonicalPath()+ " is not a directory.");
}
File files[] = dir.listFiles();
if (files == null || files.length == 0) {
String errMsg = intres.getLocalizedMessage("ocsp.errornotrustfiles", dir.getCanonicalPath());
m_log.error(errMsg);
}
for ( int i=0; i<files.length; i++ ) {
final String fileName = files[i].getCanonicalPath();
// Read the file, don't stop completely if one file has errors in it
try {
byte[] bytes = FileTools.getBytesFromPEM(FileTools.readFiletoBuffer(fileName),
CertTools.BEGIN_CERTIFICATE, CertTools.END_CERTIFICATE);
X509Certificate cert = (X509Certificate) CertTools.getCertfromByteArray(bytes);
String key = cert.getIssuerDN()+";"+cert.getSerialNumber().toString(16);
trustedCerts.put(key,cert);
} catch (CertificateException e) {
String errMsg = intres.getLocalizedMessage("ocsp.errorreadingfile", fileName, "trustDir", e.getMessage());
m_log.error(errMsg, e);
} catch (IOException e) {
String errMsg = intres.getLocalizedMessage("ocsp.errorreadingfile", fileName, "trustDir", e.getMessage());
m_log.error(errMsg, e);
}
}
return trustedCerts;
}
/**
* 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
*/
public static boolean checkCertInList(X509Certificate cert, Hashtable trustedCerts) {
//String key = CertTools.getIssuerDN(cert)+";"+cert.getSerialNumber().toString(16);
String key = cert.getIssuerDN()+";"+cert.getSerialNumber().toString(16);
Object found = trustedCerts.get(key);
if (found != null) {
return true;
}
return false;
}
}