blob: 3aed3ba9bc4e51e079f8f625ff1b3d407b69b292 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.synapse.transport.utils.sslcert.ocsp;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.ocsp.*;
import org.apache.synapse.transport.utils.sslcert.*;
import java.io.*;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
/**
* Used to check if a Certificate is revoked or not by its CA using Online Certificate
* Status Protocol (OCSP).
*/
public class OCSPVerifier implements RevocationVerifier {
private static final Log log = LogFactory.getLog(OCSPVerifier.class);
private OCSPCache cache;
public OCSPVerifier(OCSPCache cache) {
this.cache = cache;
}
/**
* Gets the revocation status (Good, Revoked or Unknown) of the given peer certificate.
*
* @param peerCert The certificate we want to check if revoked.
* @param issuerCert Needed to create OCSP request.
* @return revocation status of the peer certificate.
* @throws CertificateVerificationException
*
*/
public RevocationStatus checkRevocationStatus(X509Certificate peerCert, X509Certificate issuerCert)
throws CertificateVerificationException {
//check cache
if (cache != null) {
SingleResp resp = cache.getCacheValue(peerCert.getSerialNumber());
if (resp != null) {
//If cant be casted, we have used the wrong cache.
RevocationStatus status = getRevocationStatus(resp);
log.debug("OCSP response taken from cache....");
return status;
}
}
OCSPReq request = generateOCSPRequest(issuerCert, peerCert.getSerialNumber());
//This list will sometimes have non ocsp urls as well.
List<String> locations = getAIALocations(peerCert);
for (String serviceUrl : locations) {
SingleResp[] responses;
try {
OCSPResp ocspResponse = getOCSPResponse(serviceUrl, request);
if (OCSPRespStatus.SUCCESSFUL != ocspResponse.getStatus()) {
continue; // Server didn't give the response right.
}
BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResponse.getResponseObject();
responses = (basicResponse == null) ? null : basicResponse.getResponses();
//todo use the super exception
} catch (Exception e) {
continue;
}
if (responses != null && responses.length == 1) {
SingleResp resp = responses[0];
RevocationStatus status = getRevocationStatus(resp);
if (cache != null)
cache.setCacheValue(peerCert.getSerialNumber(), resp, request, serviceUrl);
return status;
}
}
throw new CertificateVerificationException("Cant get Revocation Status from OCSP.");
}
private RevocationStatus getRevocationStatus(SingleResp resp) throws CertificateVerificationException {
Object status = resp.getCertStatus();
if (status == CertificateStatus.GOOD) {
return RevocationStatus.GOOD;
} else if (status instanceof org.bouncycastle.ocsp.RevokedStatus) {
return RevocationStatus.REVOKED;
} else if (status instanceof org.bouncycastle.ocsp.UnknownStatus) {
return RevocationStatus.UNKNOWN;
}
throw new CertificateVerificationException("Cant recognize Certificate Status");
}
/**
* Gets an ASN.1 encoded OCSP response (as defined in RFC 2560) from the given service URL. Currently supports
* only HTTP.
*
* @param serviceUrl URL of the OCSP endpoint.
* @param request an OCSP request object.
* @return OCSP response encoded in ASN.1 structure.
* @throws CertificateVerificationException
*
*/
protected OCSPResp getOCSPResponse(String serviceUrl,
OCSPReq request) throws CertificateVerificationException {
try {
//Todo: Use http client.
byte[] array = request.getEncoded();
if (serviceUrl.startsWith("http")) {
HttpURLConnection con;
URL url = new URL(serviceUrl);
con = (HttpURLConnection) url.openConnection();
con.setRequestProperty("Content-Type", "application/ocsp-request");
con.setRequestProperty("Accept", "application/ocsp-response");
con.setDoOutput(true);
OutputStream out = con.getOutputStream();
DataOutputStream dataOut = new DataOutputStream(new BufferedOutputStream(out));
dataOut.write(array);
dataOut.flush();
dataOut.close();
//Check errors in response:
if (con.getResponseCode() / 100 != 2) {
throw new CertificateVerificationException("Error getting ocsp response." +
"Response code is " + con.getResponseCode());
}
//Get Response
InputStream in = (InputStream) con.getContent();
return new OCSPResp(in);
} else {
throw new CertificateVerificationException("Only http is supported for ocsp calls");
}
} catch (IOException e) {
throw new CertificateVerificationException("Cannot get ocspResponse from url: " + serviceUrl, e);
}
}
/**
* This method generates an OCSP Request to be sent to an OCSP endpoint.
*
* @param issuerCert is the Certificate of the Issuer of the peer certificate we are interested in.
* @param serialNumber of the peer certificate.
* @return generated OCSP request.
* @throws CertificateVerificationException
*/
private OCSPReq generateOCSPRequest(X509Certificate issuerCert, BigInteger serialNumber)
throws CertificateVerificationException {
//TODO: Have to check if this is OK with synapse implementation.
//Add provider BC
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
try {
// CertID structure is used to uniquely identify certificates that are the subject of
// an OCSP request or response and has an ASN.1 definition. CertID structure is defined
// in RFC 2560
CertificateID id = new CertificateID(CertificateID.HASH_SHA1, issuerCert, serialNumber);
// basic request generation with nonce
OCSPReqGenerator generator = new OCSPReqGenerator();
generator.addRequest(id);
// create details for nonce extension. The nonce extension is used to bind
// a request to a response to prevent replay attacks. As the name implies,
// the nonce value is something that the client should only use once within a reasonably
// small period.
BigInteger nonce = BigInteger.valueOf(System.currentTimeMillis());
Vector<ASN1ObjectIdentifier> objectIdentifiers = new Vector<ASN1ObjectIdentifier>();
Vector<X509Extension> values = new Vector<X509Extension>();
//to create the request Extension
objectIdentifiers.add(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
values.add(new X509Extension(false, new DEROctetString(nonce.toByteArray())));
generator.setRequestExtensions(new X509Extensions(objectIdentifiers, values));
return generator.generate();
} catch (OCSPException e) {
throw new CertificateVerificationException("Cannot generate OCSP Request with the " +
"given certificate", e);
}
}
/**
* Authority Information Access (AIA) is a non-critical extension in an X509 Certificate. This contains the
* URL of the OCSP endpoint if one is available.
* TODO: This might contain non OCSP urls as well. Handle this.
*
* @param cert is the certificate
* @return a lit of URLs in AIA extension of the certificate which will hopefully contain an OCSP endpoint.
* @throws CertificateVerificationException
*
*/
private List<String> getAIALocations(X509Certificate cert) throws CertificateVerificationException {
//Gets the DER-encoded OCTET string for the extension value for Authority information access Points
byte[] aiaExtensionValue = cert.getExtensionValue(X509Extensions.AuthorityInfoAccess.getId());
if (aiaExtensionValue == null) {
throw new CertificateVerificationException("Certificate doesn't have authority " +
"information access points");
}
//might have to pass an ByteArrayInputStream(aiaExtensionValue)
ASN1InputStream asn1In = new ASN1InputStream(aiaExtensionValue);
AuthorityInformationAccess authorityInformationAccess;
try {
DEROctetString aiaDEROctetString = (DEROctetString) (asn1In.readObject());
ASN1InputStream asn1InOctets = new ASN1InputStream(aiaDEROctetString.getOctets());
ASN1Sequence aiaASN1Sequence = (ASN1Sequence) asn1InOctets.readObject();
authorityInformationAccess = AuthorityInformationAccess.getInstance(aiaASN1Sequence);
} catch (IOException e) {
throw new CertificateVerificationException("Cannot read certificate to get OCSP URLs", e);
}
List<String> ocspUrlList = new ArrayList<String>();
AccessDescription[] accessDescriptions = authorityInformationAccess.getAccessDescriptions();
for (AccessDescription accessDescription : accessDescriptions) {
GeneralName gn = accessDescription.getAccessLocation();
if (gn.getTagNo() == GeneralName.uniformResourceIdentifier) {
DERIA5String str = DERIA5String.getInstance(gn.getName());
String accessLocation = str.getString();
ocspUrlList.add(accessLocation);
}
}
if (ocspUrlList.isEmpty()) {
throw new CertificateVerificationException("Cant get OCSP urls from certificate");
}
return ocspUrlList;
}
}