Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*************************************************************************
* *
* 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.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.CRL;
import java.security.cert.CRLException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXCertPathChecker;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.NullOutputStream;
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.DEROctetString;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.CertificateStatus;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.OCSPRespBuilder;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.cert.ocsp.jcajce.JcaCertificateID;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
import org.cesecore.certificates.ocsp.SHA1DigestCalculator;
/**
* A class to check whether a certificate is revoked or not using either OCSP or CRL.
* The revocation status will first be obtained using OCSP. If it turned out that that was not possible for
* some reason, a CRL will be used instead. If it was not possible to check the CRL for some reason, an
* exception will be thrown.
*
* @version $Id: PKIXCertRevocationStatusChecker.java 24567 2016-10-25 05:36:17Z anatom $
*
*/
public class PKIXCertRevocationStatusChecker extends PKIXCertPathChecker {
private final Logger log = Logger.getLogger(PKIXCertRevocationStatusChecker.class);
private String ocspUrl;
private String crlUrl;
private X509Certificate issuerCert;
private Collection caCerts;
private SingleResp ocspResponse=null;
private CRL crl=null;
/**
* With this constructor, the certificate revocation status will be checked using a CRL fetched using a URL
* extracted from the certificate's CRL Distribution Points extension.
*
* @param issuerCert The certificate of the issuer of the certificate whose revocation status is to be checked.
* if 'null', the issuer certificate will be looked for among the certificates specified in 'cacerts'
* @param cacerts A collection of certificates where one of them is the certificate of the issuer of the certificate
* whose status is to be checked. This parameter will be used only if 'issuerCert' is null
*/
public PKIXCertRevocationStatusChecker(final X509Certificate issuerCert, final Collection cacerts) {
this.ocspUrl = null;
this.crlUrl = null;
this.issuerCert = issuerCert;
this.caCerts = cacerts;
}
/**
* @param ocspurl The URL to use when sending an OCSP request. If 'null', the OCSP URL will be extracted from
* the certificate's AuthorityInformationAccess extension if it exists.
* @param crlurl The URL to fetch the CRL from. If 'null', the CRL URL will be extracted from the certificate's
* CRLDistributionPoints extension if exists.
* @param issuerCert The certificate of the issuer of the certificate whose revocation status is to be checked.
* if 'null', the issuer certificate will be looked for among the certificates specified in 'cacerts'
* @param cacerts A collection of certificates where one of them is the certificate of the issuer of the certificate
* whose status is to be checked. This parameter will be used only if 'issuerCert' is null
*/
public PKIXCertRevocationStatusChecker(final String ocspurl, final String crlurl, final X509Certificate issuerCert, final Collection cacerts) {
this.ocspUrl = ocspurl;
this.crlUrl = crlurl;
this.issuerCert = issuerCert;
this.caCerts = cacerts;
}
@Override
public void init(boolean forward) throws CertPathValidatorException {}
@Override
public boolean isForwardCheckingSupported() {
// Not used
return true;
}
@Override
public Set getSupportedExtensions() {
ArrayList exts = new ArrayList();
exts.add(Extension.cRLDistributionPoints.getId());
exts.add(Extension.authorityInfoAccess.getId());
return new HashSet(exts);
}
/**
* @return The OCSP response containing the certificate status of the saught out certificate. Or 'null' if an OCSP response
* could not be obtained for any reason.
*/
public SingleResp getOCSPResponse() {
return this.ocspResponse;
}
/**
* @return The CRLs that were checked. Or an empty Collection if no CRLs were checked
*/
public CRL getcrl() {
return this.crl;
}
/**
* Resets the OCSP response, the checked CRLs and whether the certificate was revoked in case the same instance of this class is
* used to check the revocation status of more that one certificate.
*/
private void clearResult() {
this.ocspResponse=null;
this.crl = null;
}
/**
* Checks the revocation status of 'cert'; first by sending on OCSP request. If that fails for any reason, then through a CRL
*/
@Override
public void check(Certificate cert, Collection unresolvedCritExts) throws CertPathValidatorException {
clearResult();
Certificate cacert = getCaCert(cert);
if(cacert == null) {
final String msg = "No issuer CA certificate was found. An issuer CA certificate is needed to create an OCSP request and to get the right CRL";
log.info(msg);
throw new CertPathValidatorException(msg);
}
ArrayList ocspurls = getOcspUrls(cert);
if(!ocspurls.isEmpty()) {
BigInteger certSerialnumber = CertTools.getSerialNumber(cert);
byte[] nonce = new byte[16];
final Random randomSource = new Random();
randomSource.nextBytes(nonce);
OCSPReq req = null;
try {
req = getOcspRequest(cacert, certSerialnumber, nonce);
} catch (CertificateEncodingException | OCSPException e) {
if(log.isDebugEnabled()) {
log.debug("Failed to create OCSP request. " + e.getLocalizedMessage());
}
fallBackToCrl(cert, CertTools.getSubjectDN(cacert));
return;
}
SingleResp ocspResp = null;
for(String url : ocspurls) {
ocspResp = getOCSPResponse(url, req, cert, nonce, OCSPRespBuilder.SUCCESSFUL, 200);
if(ocspResp != null) {
log.info("Obtained OCSP response from " + url);
break;
} else {
if(log.isDebugEnabled()) {
log.debug("Failed to obtain an OCSP reponse from " + url);
}
}
}
if(ocspResp==null) {
log.info("Failed to check certificate revocation status using OCSP. Falling back to check using CRL");
fallBackToCrl(cert, CertTools.getSubjectDN(cacert));
} else {
CertificateStatus status = ocspResp.getCertStatus();
this.ocspResponse = ocspResp;
if(log.isDebugEnabled()) {
log.debug("The certificate status is: " + (status==null? "Good" : status.toString()));
}
if(status != null) { // status==null -> certificate OK
throw new CertPathValidatorException("Certificate with serialnumber " + CertTools.getSerialNumberAsString(cert) + " was revoked");
}
if(unresolvedCritExts != null) {
unresolvedCritExts.remove(Extension.authorityInfoAccess.getId());
}
}
} else {
fallBackToCrl(cert, CertTools.getSubjectDN(cacert));
if(unresolvedCritExts != null) {
unresolvedCritExts.remove(Extension.cRLDistributionPoints.getId());
}
}
}
/**
* Check the revocation status of 'cert' using a CRL
* @param cert the certificate whose revocation status is to be checked
* @throws CertPathValidatorException
*/
private void fallBackToCrl(final Certificate cert, final String issuerDN) throws CertPathValidatorException {
final ArrayList crlUrls = getCrlUrl(cert);
if(crlUrls.isEmpty()) {
final String errmsg = "Failed to verify certificate status using the fallback CRL method. Could not find a CRL URL";
log.info(errmsg);
throw new CertPathValidatorException(errmsg);
}
if(log.isDebugEnabled()) {
log.debug("Found " + crlUrls.size() + " CRL URLs");
}
CRL crl = null;
for(URL url : crlUrls) {
crl = getCRL(url);
if(crl != null) {
if(isCorrectCRL(crl, issuerDN)) {
final boolean isRevoked = crl.isRevoked(cert);
this.crl = crl;
if(isRevoked) {
throw new CertPathValidatorException("Certificate with serialnumber " + CertTools.getSerialNumberAsString(cert) + " was revoked");
}
break;
}
}
}
if(this.crl==null) {
throw new CertPathValidatorException("Failed to verify certificate status using CRL. Could not find a CRL issued by " + issuerDN + " reasonably lately");
}
}
private boolean isCorrectCRL(final CRL crl, final String issuerDN) {
if(!(crl instanceof X509CRL)) {
return false;
}
X509CRL x509crl = (X509CRL) crl;
if(!StringUtils.equals(issuerDN, CertTools.getIssuerDN(x509crl))) {
return false;
}
final Date now = new Date(System.currentTimeMillis());
final Date nextUpdate = x509crl.getNextUpdate();
if(nextUpdate!=null) {
if(nextUpdate.after(now)) {
return true;
}
if(log.isDebugEnabled()) {
log.debug("CRL issued by " + issuerDN + " is out of date");
}
return false;
}
final Date thisUpdate = x509crl.getThisUpdate();
if(thisUpdate!=null) {
final GregorianCalendar gc = new GregorianCalendar();
gc.setTime(now);
gc.add(Calendar.HOUR, 1);
final Date expire = gc.getTime();
if(expire.before(now)) {
if(log.isDebugEnabled()) {
log.debug("Could not find when CRL issued by " + issuerDN + " should be updated and this CRL is over one hour old. Not using it");
}
return false;
}
log.warn("Could not find when CRL issued by " + issuerDN + " should be updated, but this CRL was issued less than an hour ago, so we are using it");
return true;
}
if(log.isDebugEnabled()) {
log.debug("Could not check issuance time for CRL issued by " + issuerDN);
}
return false;
}
private CRL getCRL(final URL url) {
CRL crl = null;
try {
final URLConnection con = url.openConnection();
final InputStream is = con.getInputStream();
final CertificateFactory cf = CertificateFactory.getInstance("X.509");
crl = cf.generateCRL(is);
is.close();
log.info("Downloaded CRL from " + url);
} catch(IOException | CertificateException | CRLException e) {
if(log.isDebugEnabled()) {
log.debug("Fetching CRL from " + url.toString() + " failed. " + e.getLocalizedMessage());
}
}
return crl;
}
/**
* Construct an OCSP request
* @param cacert The certificate of the issuer of the certificate to be checked
* @param certSerialnumber the serialnumber of the certificate to be checked
* @param nonce random nonce to be included in the OCSP request (OCSP POST)
* @return OCSPReq
* @throws CertificateEncodingException
* @throws OCSPException
*/
private OCSPReq getOcspRequest(Certificate cacert, BigInteger certSerialnumber, final byte[] nonce) throws CertificateEncodingException, OCSPException {
OCSPReqBuilder gen = new OCSPReqBuilder();
gen.addRequest(new JcaCertificateID(SHA1DigestCalculator.buildSha1Instance(), (X509Certificate) cacert, certSerialnumber));
Extension[] extensions = new Extension[1];
extensions[0] = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, new DEROctetString(nonce));
gen.setRequestExtensions(new Extensions(extensions));
return gen.build();
}
/**
* Sends an OCSP request, gets a response and verifies the response as much as possible before returning it to the caller.
*
* @return The OCSP response, or null of no correct response could be obtained.
*/
private SingleResp getOCSPResponse(final String ocspurl, final OCSPReq ocspRequest, final Certificate cert, final byte[] nonce, int expectedOcspRespCode, int expectedHttpRespCode) {
if(log.isDebugEnabled()) {
log.debug("Sending OCSP request to " + ocspurl + " regarding certificate with SubjectDN: " + CertTools.getSubjectDN(cert)
+ " - IssuerDN: " + CertTools.getIssuerDN(cert));
}
//----------------------- Open connection and send the request --------------//
OCSPResp response = null;
HttpURLConnection con = null;
try {
final URL url = new URL(ocspurl);
con = (HttpURLConnection)url.openConnection();
// we are going to do a POST
con.setDoOutput(true);
con.setRequestMethod("POST");
// POST it
con.setRequestProperty("Content-Type", "application/ocsp-request");
OutputStream os = con.getOutputStream();
os.write(ocspRequest.getEncoded());
os.close();
final int httpRespCode = ((HttpURLConnection)con).getResponseCode();
if (httpRespCode != expectedHttpRespCode) {
log.info("HTTP response from OCSP request was " + httpRespCode + ". Expected " + expectedHttpRespCode );
handleContentOfErrorStream(con.getErrorStream());
return null; // if it is an http error code we don't need to test any more
}
InputStream is = con.getInputStream();
response = new OCSPResp(IOUtils.toByteArray(is));
is.close();
} catch(IOException e) {
log.info("Unable to get an OCSP response. " + e.getLocalizedMessage());
if(con != null) {
handleContentOfErrorStream(con.getErrorStream());
}
return null;
}
// ------------ Verify the response signature --------------//
BasicOCSPResp brep = null;
try {
brep = (BasicOCSPResp) response.getResponseObject();
if ((expectedOcspRespCode != OCSPRespBuilder.SUCCESSFUL) && (brep!=null)) {
log.warn("According to RFC 2560, responseBytes are not set on error, but we got some.");
return null; // it messes up testing of invalid signatures... but is needed for the unsuccessful responses
}
if (brep==null) {
log.warn("Cannot extract OCSP response object. OCSP response status: " + response.getStatus());
return null;
}
X509CertificateHolder[] chain = brep.getCerts();
boolean verify = brep.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build(chain[0]));
if(!verify) {
log.warn("OCSP response signature was not valid");
return null;
}
} catch (OCSPException | OperatorCreationException | CertificateException e) {
if(log.isDebugEnabled()) {
log.debug("Failed to obtain or verify OCSP response. " + e.getLocalizedMessage());
}
return null;
}
// ------------- Verify the nonce ---------------//
byte[] noncerep;
try {
noncerep = brep.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce).getExtnValue().getEncoded();
} catch (IOException e) {
if(log.isDebugEnabled()) {
log.debug("Failed to read extension from OCSP response. " + e.getLocalizedMessage());
}
return null;
}
if(noncerep == null) {
log.warn("Sent an OCSP request containing a nonce, but the OCSP response does not contain a nonce");
return null;
}
try {
ASN1InputStream ain = new ASN1InputStream(noncerep);
ASN1OctetString oct = ASN1OctetString.getInstance(ain.readObject());
ain.close();
if(!Arrays.equals(nonce, oct.getOctets())) {
log.warn("The nonce in the OCSP request and the OCSP response do not match");
return null;
}
} catch (IOException e) {
if(log.isDebugEnabled()) {
log.debug("Failed to read extension from OCSP response. " + e.getLocalizedMessage());
}
return null;
}
// ------------ Extract the single response and verify that it concerns a cert with the right serialnumber ----//
SingleResp[] singleResps = brep.getResponses();
if((singleResps==null) || (singleResps.length==0)) {
if(log.isDebugEnabled()) {
log.debug("The OCSP response object contained no responses.");
}
return null;
}
SingleResp singleResponse = singleResps[0];
CertificateID certId = singleResponse.getCertID();
if(!certId.getSerialNumber().equals(CertTools.getSerialNumber(cert))) {
if(log.isDebugEnabled()) {
log.debug("Certificate serialnumber in response does not match certificate serialnumber in request.");
}
return null;
}
// ------------ Return the single response ---------------//
return singleResponse;
}
/**
* Reads the content of 'httpErrorStream' and ignores it.
*/
private void handleContentOfErrorStream(final InputStream httpErrorStream) {
if (httpErrorStream != null) {
try {
OutputStream os = new NullOutputStream();
IOUtils.copy(httpErrorStream, os);
httpErrorStream.close();
os.close();
} catch(IOException ex) {}
}
}
private ArrayList getOcspUrls(Certificate cert) {
ArrayList urls = new ArrayList();
if(StringUtils.isNotEmpty(this.ocspUrl)) {
urls.add(this.ocspUrl);
}
urls.addAll(CertTools.getAuthorityInformationAccessOcspUrls((X509Certificate)cert));
return urls;
}
private ArrayList getCrlUrl(final Certificate cert) {
ArrayList urls = new ArrayList();
if(StringUtils.isNotEmpty(this.crlUrl)) {
try {
urls.add(new URL(this.crlUrl));
} catch (MalformedURLException e) {
if(log.isDebugEnabled()) {
log.debug("Failed to parse '" + this.crlUrl + "' as a URL. " + e.getLocalizedMessage());
}
}
}
Collection crlUrlFromExtension = CertTools.getCrlDistributionPoints((X509Certificate)cert);
urls.addAll(crlUrlFromExtension);
return urls;
}
private X509Certificate getCaCert(final Certificate targetCert) {
if(this.issuerCert != null) {
return issuerCert;
}
if(this.caCerts==null) { // no CA specified
return null;
}
for(final X509Certificate cacert : this.caCerts) {
if(isIssuerCA(targetCert, cacert)) {
return cacert;
}
}
return null;
}
private boolean isIssuerCA(final Certificate cert, final Certificate cacert) {
if(!StringUtils.equals(CertTools.getIssuerDN(cert), CertTools.getSubjectDN(cacert))) {
return false;
}
try {
cert.verify(cacert.getPublicKey());
return true;
} catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException | NoSuchProviderException | SignatureException e) {
return false;
}
}
}