Java tutorial
/************************************************************************* * * * EJBCA: The OpenSource Certificate Authority * * * * This software is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or any later version. * * * * See terms of license at gnu.org. * * * *************************************************************************/ package org.ejbca.core.protocol.ocsp; 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; } }