/*
* This file is part of rockframework.
*
* rockframework is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* rockframework is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>;.
*/
package br.net.woodstock.rockframework.security.cert.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.URL;
import java.net.URLConnection;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.X509Extension;
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.RevokedStatus;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
import br.net.woodstock.rockframework.security.cert.CertificateException;
import br.net.woodstock.rockframework.security.cert.CertificateValidator;
import br.net.woodstock.rockframework.security.cert.RevokeReason;
import br.net.woodstock.rockframework.security.cert.ValidationError;
import br.net.woodstock.rockframework.security.timestamp.impl.URLTimeStampProcessor;
import br.net.woodstock.rockframework.security.util.BouncyCastleProviderHelper;
import br.net.woodstock.rockframework.util.Assert;
import br.net.woodstock.rockframework.utils.Base64Utils;
import br.net.woodstock.rockframework.utils.CollectionUtils;
import br.net.woodstock.rockframework.utils.ConditionUtils;
import br.net.woodstock.rockframework.utils.IOUtils;
public class OCSPCertificateValidator implements CertificateValidator {
public static final String VALIDATOR_NAME = "OCSP Validator";
private static final String CONTENT_TYPE_PROPERTY = "Content-Type";
private static final String CONTENT_TYPE_VALUE = "application/ocsp-request";
private static final String CONTENT_TRANSFER_ENCODING_PROPERTY = "Content-Transfer-Encoding";
private static final String CONTENT_TRANSFER_ENCODING_BINARY = "binary";
private URL url;
public OCSPCertificateValidator() {
super();
}
public OCSPCertificateValidator(final URL url) {
super();
Assert.notNull(url, "url");
this.url = url;
}
@Override
public ValidationError[] validate(final Certificate[] chain) {
Assert.notEmpty(chain, "chain");
if (chain.length < 2) {
return new ValidationError[] { new ValidationError(OCSPCertificateValidator.VALIDATOR_NAME, "Certificate chain must be greater than 1(certificate and issuer certificate") };
}
try {
X509Certificate x509Certificate = (X509Certificate) chain[0];
X509Certificate x509Issuer = (X509Certificate) chain[1];
URL url = null;
if (this.url == null) {
URL[] urls = OCSPCertificateValidator.getOCSPUrl(x509Certificate);
if (ConditionUtils.isNotEmpty(urls)) {
url = urls[0];
}
} else {
url = this.url;
}
if (url == null) {
return new ValidationError[] { new ValidationError(OCSPCertificateValidator.VALIDATOR_NAME, "No url found for validation") };
}
OCSPReq req = this.buildRequest(x509Certificate, x509Issuer);
OCSPResp resp = this.sendRequest(req, url);
if (resp.getStatus() != OCSPResponseStatus.SUCCESSFUL) {
return new ValidationError[] { new ValidationError(OCSPCertificateValidator.VALIDATOR_NAME, "Response invalid") };
}
Object responseObject = resp.getResponseObject();
if (responseObject instanceof BasicOCSPResp) {
BasicOCSPResp basicOCSPResp = (BasicOCSPResp) responseObject;
SingleResp[] singleResps = basicOCSPResp.getResponses();
List<ValidationError> errors = new ArrayList<ValidationError>();
for (SingleResp singleResp : singleResps) {
CertificateStatus status = singleResp.getCertStatus();
if (status != null) {
RevokeReason revokeReason = null;
if (status instanceof RevokedStatus) {
RevokedStatus revokedStatus = (RevokedStatus) status;
revokeReason = RevokeReason.getByCode(revokedStatus.getRevocationReason());
}
if (revokeReason != null) {
errors.add(new ValidationError(OCSPCertificateValidator.VALIDATOR_NAME, "Certificate revoked(" + revokeReason.name() + ")"));
} else {
errors.add(new ValidationError(OCSPCertificateValidator.VALIDATOR_NAME, "Certificate revoked(Unknow)"));
}
}
}
return CollectionUtils.toArray(errors, ValidationError.class);
}
return new ValidationError[0];
} catch (Exception e) {
throw new CertificateException(e);
}
}
protected OCSPReq buildRequest(final X509Certificate certificate, final X509Certificate issuer) throws CertificateEncodingException, IOException, OperatorCreationException, OCSPException {
OCSPReqBuilder builder = new OCSPReqBuilder();
DigestCalculatorProvider provider = new BcDigestCalculatorProvider();
X509CertificateHolder holder = new X509CertificateHolder(issuer.getEncoded());
CertificateID certificateID = new CertificateID(provider.get(CertificateID.HASH_SHA1), holder, certificate.getSerialNumber());
BigInteger nonce = BigInteger.valueOf(System.currentTimeMillis());
builder.addRequest(certificateID);
ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator();
extensionsGenerator.addExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, new DEROctetString(nonce.toByteArray()));
return builder.build();
}
protected OCSPResp sendRequest(final OCSPReq req, final URL url) throws IOException {
URLConnection connection = url.openConnection();
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setUseCaches(false);
this.setConnectionProperties(connection);
byte[] requestBytes = req.getEncoded();
OutputStream outputStream = connection.getOutputStream();
this.writeBytes(outputStream, requestBytes);
outputStream.close();
InputStream inputStream = connection.getInputStream();
String encoding = connection.getContentEncoding();
byte[] bytes = this.readBytes(inputStream, encoding);
OCSPResp resp = new OCSPResp(bytes);
return resp;
}
protected void setConnectionProperties(final URLConnection connection) {
connection.setRequestProperty(OCSPCertificateValidator.CONTENT_TYPE_PROPERTY, OCSPCertificateValidator.CONTENT_TYPE_VALUE);
connection.setRequestProperty(OCSPCertificateValidator.CONTENT_TRANSFER_ENCODING_PROPERTY, OCSPCertificateValidator.CONTENT_TRANSFER_ENCODING_BINARY);
}
protected void writeBytes(final OutputStream outputStream, final byte[] bytes) throws IOException {
outputStream.write(bytes);
}
protected byte[] readBytes(final InputStream inputStream, final String encoding) throws IOException {
byte[] bytes = IOUtils.toByteArray(inputStream);
if (URLTimeStampProcessor.CONTENT_TRANSFER_ENCODING_BASE64.equals(encoding)) {
bytes = Base64Utils.fromBase64(bytes);
}
return bytes;
}
public static URL[] getOCSPUrl(final Certificate certificate) throws IOException {
X509Certificate x509Certificate = (X509Certificate) certificate;
byte[] bytes = x509Certificate.getExtensionValue(X509Extension.authorityInfoAccess.getId());
if (bytes == null) {
return new URL[0];
}
Set<URL> urls = new HashSet<URL>();
DEROctetString octetString = (DEROctetString) BouncyCastleProviderHelper.toASN1Primitive(bytes);
ASN1Sequence sequence = (ASN1Sequence) BouncyCastleProviderHelper.toASN1Primitive(octetString.getOctets());
AuthorityInformationAccess informationAccess = AuthorityInformationAccess.getInstance(sequence);
AccessDescription[] accessDescriptions = informationAccess.getAccessDescriptions();
// Colocar aqui 1.3.6.1.5.5.7.48.1
for (AccessDescription description : accessDescriptions) {
if (description.getAccessMethod().getId().equals(OCSPObjectIdentifiers.pkix_ocsp)) {
GeneralName generalName = description.getAccessLocation();
DERTaggedObject taggedObject = (DERTaggedObject) generalName.toASN1Primitive();
DERIA5String ia5String = DERIA5String.getInstance(taggedObject.getObject());
String urlStr = ia5String.getString();
URL url = new URL(urlStr);
urls.add(url);
}
}
return CollectionUtils.toArray(urls, URL.class);
}
}