| /* |
| * 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; |
| } |
| |
| } |