/*
* 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.certificatevalidation;
import junit.framework.TestCase;
import org.apache.synapse.transport.certificatevalidation.ocsp.OCSPCache;
import org.apache.synapse.transport.certificatevalidation.ocsp.OCSPVerifier;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.ocsp.*;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import java.io.IOException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Date;
public class OCSPVerifierTest extends TestCase {
private static final String BC = "BC";
/**
* A fake certificate signed by a fake CA is made as the revoked certificate. The created OCSP response to the
* OCSP request will say that that the fake peer certificate is revoked. the SingleResp derived from the OCSP
* response will be put the the cache against the serial number of the fake peer certificate. Since the SingleResp
* which corresponds to the revokedSerialNumber is in the cache, there will NOT be a call to a remote OCSP server.
* Note that the serviceUrl passed to cache.setCacheValue(..) is null since it is not needed.
*
* @throws Exception
*/
public void testOCSPVerifier() throws Exception{
//Add BouncyCastle as Security Provider.
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Utils utils = new Utils();
//Create fake CA certificate.
KeyPair caKeyPair = utils.generateRSAKeyPair();
X509Certificate caCert = utils.generateFakeRootCert(caKeyPair);
//Create fake peer certificate signed by the fake CA private key. This will be a revoked certificate.
KeyPair peerKeyPair = utils.generateRSAKeyPair();
BigInteger revokedSerialNumber = BigInteger.valueOf(111);
X509Certificate revokedCertificate = generateFakePeerCert(revokedSerialNumber, peerKeyPair.getPublic(),
caKeyPair.getPrivate(), caCert);
//Create OCSP request to check if certificate with "serialNumber == revokedSerialNumber" is revoked.
OCSPReq request = getOCSPRequest(caCert,revokedSerialNumber);
//Create OCSP response saying that certificate with given serialNumber is revoked.
//CertificateID revokedID = new CertificateID(CertificateID.HASH_SHA1, caCert, revokedSerialNumber);
byte[] issuerCertEnc = caCert.getEncoded();
X509CertificateHolder certificateHolder = new X509CertificateHolder(issuerCertEnc);
DigestCalculatorProvider digCalcProv = new JcaDigestCalculatorProviderBuilder().setProvider(BC).build();
// 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 revokedID = new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), certificateHolder, revokedSerialNumber);
OCSPResp response = generateOCSPResponse(request, certificateHolder, caKeyPair.getPrivate(), caKeyPair.getPublic(), revokedID);
SingleResp singleResp = ((BasicOCSPResp)response.getResponseObject()).getResponses()[0];
OCSPCache cache = OCSPCache.getCache();
cache.init(5,5);
cache.setCacheValue(revokedSerialNumber,singleResp, request, null);
OCSPVerifier ocspVerifier= new OCSPVerifier(cache);
RevocationStatus status = ocspVerifier.checkRevocationStatus(revokedCertificate, caCert);
//the cache will have the SingleResponse derived from the OCSP response and it will be checked to see if the
//fake certificate is revoked. So the status should be REVOKED.
assertTrue(status == RevocationStatus.REVOKED);
}
/**
* An OCSP request is made to be given to the fake CA. Reflection is used to call generateOCSPRequest(..) private
* method in OCSPVerifier.
*
* @param caCert the fake CA certificate.
* @param revokedSerialNumber the serial number of the certificate which needs to be checked if revoked.
* @return the created OCSP request.
* @throws Exception
*/
private OCSPReq getOCSPRequest(X509Certificate caCert, BigInteger revokedSerialNumber) throws Exception{
OCSPVerifier ocspVerifier = new OCSPVerifier(null);
Class ocspVerifierClass = ocspVerifier.getClass();
Method generateOCSPRequest = ocspVerifierClass.getDeclaredMethod("generateOCSPRequest", X509Certificate.class,
BigInteger.class);
generateOCSPRequest.setAccessible(true);
OCSPReq request = (OCSPReq)generateOCSPRequest.invoke(ocspVerifier, caCert, revokedSerialNumber);
return request;
}
/**
* This makes the corresponding OCSP response to the OCSP request which is sent to the fake CA. If the request
* has a certificateID which is marked as revoked by the CA, the OCSP response will say that the certificate
* which is referred to by the request, is revoked.
*
* @param request the OCSP request which asks if the certificate is revoked.
* @param caPrivateKey privateKey of the fake CA.
* @param caPublicKey publicKey of the fake CA
* @param revokedID the ID at fake CA which is checked against the certificateId in the request.
* @return the created OCSP response by the fake CA.
* @throws NoSuchProviderException
* @throws OCSPException
* @throws OperatorCreationException
*/
private OCSPResp generateOCSPResponse(OCSPReq request, X509CertificateHolder certificateHolder, PrivateKey caPrivateKey, PublicKey caPublicKey,
CertificateID revokedID) throws NoSuchProviderException, OCSPException, OperatorCreationException {
BasicOCSPRespBuilder basicOCSPRespBuilder = new BasicOCSPRespBuilder(new RespID(certificateHolder.getSubject()));
Extension extension = request.getExtension(new ASN1ObjectIdentifier(OCSPObjectIdentifiers.id_pkix_ocsp.getId()));
if (extension != null) {
basicOCSPRespBuilder.setResponseExtensions(new Extensions(extension));
}
Req[] requests = request.getRequestList();
for (Req req : requests) {
CertificateID certID = req.getCertID();
if (certID.equals(revokedID)) {
RevokedStatus revokedStatus = new RevokedStatus(new Date(), CRLReason.privilegeWithdrawn);
Date nextUpdate = new Date(new Date().getTime() + TestConstants.NEXT_UPDATE_PERIOD);
basicOCSPRespBuilder.addResponse(certID, revokedStatus, nextUpdate, null);
} else {
basicOCSPRespBuilder.addResponse(certID, CertificateStatus.GOOD);
}
}
X509CertificateHolder[] chain = {certificateHolder};
ContentSigner signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(caPrivateKey);
BasicOCSPResp basicResp = basicOCSPRespBuilder.build(signer, chain, new Date());
OCSPRespBuilder builder = new OCSPRespBuilder();
return builder.build(OCSPRespBuilder.SUCCESSFUL, basicResp);
}
private X509Certificate generateFakePeerCert(BigInteger serialNumber, PublicKey entityKey,
PrivateKey caKey, X509Certificate caCert)
throws Exception {
Utils utils = new Utils();
X509V3CertificateGenerator certGen = utils.getUsableCertificateGenerator(caCert,entityKey, serialNumber);
return certGen.generateX509Certificate(caKey, "BC");
}
}