/*
* DSS - Digital Signature Services
*
* Copyright (C) 2013 European Commission, Directorate-General Internal Market and Services (DG MARKT), B-1049 Bruxelles/Brussel
*
* Developed by: 2013 ARHS Developments S.A. (rue Nicolas Bové 2B, L-1253 Luxembourg) http://www.arhs-developments.com
*
* This file is part of the "DSS - Digital Signature Services" project.
*
* "DSS - Digital Signature Services" 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 (at your option) any later version.
*
* DSS 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* "DSS - Digital Signature Services". If not, see <http://www.gnu.org/licenses/>.
*/
package eu.europa.ec.markt.dss.validation102853;
import java.io.IOException;
import java.io.StringWriter;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.ocsp.ResponderID;
import org.bouncycastle.asn1.x509.CRLReason;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.CertificateStatus;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.RespID;
import org.bouncycastle.cert.ocsp.RevokedStatus;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.cert.ocsp.UnknownStatus;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.europa.ec.markt.dss.DSSASN1Utils;
import eu.europa.ec.markt.dss.DSSRevocationUtils;
import eu.europa.ec.markt.dss.DSSUtils;
import eu.europa.ec.markt.dss.SignatureAlgorithm;
import eu.europa.ec.markt.dss.exception.DSSException;
import eu.europa.ec.markt.dss.exception.DSSNullException;
import eu.europa.ec.markt.dss.validation102853.certificate.CertificateSourceType;
/**
* OCSP Signed Token which encapsulate BasicOCSPResp (BC).
*
* @version $Revision: 1824 $ - $Date: 2013-03-28 15:57:23 +0100 (Thu, 28 Mar 2013) $
*/
public class OCSPToken extends RevocationToken {
private static final Logger LOG = LoggerFactory.getLogger(OCSPToken.class);
/**
* The encapsulated basic OCSP response.
*/
private transient final BasicOCSPResp basicOCSPResp;
private transient final SingleResp singleResp;
/**
* In case of online source this is the source URI.
*/
private String sourceURI;
/**
* The default constructor for OCSPToken.
*
* @param basicOCSPResp The basic OCSP response.
* @param singleResp
* @param certificatePool The certificate pool used to validate/hold the certificate used to sign this OCSP response.
*/
public OCSPToken(final BasicOCSPResp basicOCSPResp, final SingleResp singleResp, final CertificatePool certificatePool) {
if (basicOCSPResp == null) {
throw new DSSNullException(BasicOCSPResp.class);
}
if (singleResp == null) {
throw new DSSNullException(SingleResp.class);
}
if (certificatePool == null) {
throw new DSSNullException(CertificatePool.class);
}
this.basicOCSPResp = basicOCSPResp;
this.singleResp = singleResp;
this.issuingTime = basicOCSPResp.getProducedAt();
setStatus(singleResp.getCertStatus());
final ASN1ObjectIdentifier signatureAlgOID = basicOCSPResp.getSignatureAlgOID();
final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.forOID(signatureAlgOID.getId());
this.algorithmUsedToSignToken = signatureAlgorithm;
this.extraInfo = new TokenValidationExtraInfo();
final boolean found = extractSigningCertificateFromResponse(certificatePool);
if (!found) {
extractSigningCertificateFormResponderId(certificatePool);
}
if (LOG.isTraceEnabled()) {
LOG.trace("+{} created.", getAbbreviation());
}
}
private void extractSigningCertificateFormResponderId(final CertificatePool certificatePool) {
final RespID responderId = basicOCSPResp.getResponderId();
final ResponderID responderIdAsASN1Object = responderId.toASN1Object();
final DERTaggedObject derTaggedObject = (DERTaggedObject) responderIdAsASN1Object.toASN1Primitive();
if (2 == derTaggedObject.getTagNo()) {
throw new DSSException("Certificate's key hash management not implemented yet!");
}
final ASN1Primitive derObject = derTaggedObject.getObject();
final byte[] derEncoded = DSSASN1Utils.getDEREncoded(derObject);
final X500Principal x500Principal_ = new X500Principal(derEncoded);
final X500Principal x500Principal = DSSUtils.getX500Principal(x500Principal_);
final List<CertificateToken> certificateTokens = certificatePool.get(x500Principal);
for (final CertificateToken issuerCertificateToken : certificateTokens) {
if (isSignedBy(issuerCertificateToken)) {
break;
}
}
}
private boolean extractSigningCertificateFromResponse(final CertificatePool certificatePool) {
for (final X509CertificateHolder x509CertificateHolder : basicOCSPResp.getCerts()) {
final byte[] encoded = DSSUtils.getEncoded(x509CertificateHolder);
final X509Certificate x509Certificate = DSSUtils.loadCertificate(encoded);
final CertificateToken certToken = certificatePool.getInstance(x509Certificate, CertificateSourceType.OCSP_RESPONSE);
if (isSignedBy(certToken)) {
return true;
}
}
return false;
}
private void setStatus(final CertificateStatus certStatus) {
if (certStatus == null) {
status = true;
return;
}
if (LOG.isInfoEnabled()) {
LOG.info("OCSP certificate status: " + certStatus.getClass().getName());
}
if (certStatus instanceof RevokedStatus) {
if (LOG.isInfoEnabled()) {
LOG.info("OCSP status revoked");
}
final RevokedStatus revokedStatus = (RevokedStatus) certStatus;
status = false;
revocationDate = revokedStatus.getRevocationTime();
final int reasonId = revokedStatus.getRevocationReason();
final CRLReason crlReason = CRLReason.lookup(reasonId);
reason = crlReason.toString();
} else if (certStatus instanceof UnknownStatus) {
if (LOG.isInfoEnabled()) {
LOG.info("OCSP status unknown");
}
reason = "OCSP status: unknown";
}
}
/**
* @return the ocspResp
*/
public BasicOCSPResp getBasicOCSPResp() {
return basicOCSPResp;
}
@Override
public boolean isSignedBy(final CertificateToken issuerToken) {
if (this.issuerToken != null) {
return this.issuerToken.equals(issuerToken);
}
try {
signatureInvalidityReason = "";
JcaContentVerifierProviderBuilder jcaContentVerifierProviderBuilder = new JcaContentVerifierProviderBuilder();
jcaContentVerifierProviderBuilder.setProvider("BC");
final PublicKey publicKey = issuerToken.getCertificate().getPublicKey();
ContentVerifierProvider contentVerifierProvider = jcaContentVerifierProviderBuilder.build(publicKey);
signatureValid = basicOCSPResp.isSignatureValid(contentVerifierProvider);
if (signatureValid) {
this.issuerToken = issuerToken;
}
issuerX500Principal = issuerToken.getSubjectX500Principal();
} catch (OCSPException e) {
signatureInvalidityReason = e.getClass().getSimpleName() + " - " + e.getMessage();
signatureValid = false;
} catch (OperatorCreationException e) {
signatureInvalidityReason = e.getClass().getSimpleName() + " - " + e.getMessage();
signatureValid = false;
}
return signatureValid;
}
public String getSourceURL() {
return sourceURI;
}
public void setSourceURI(final String sourceURI) {
this.sourceURI = sourceURI;
}
@Override
public int hashCode() {
return basicOCSPResp != null ? basicOCSPResp.hashCode() : 0;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final OCSPToken ocspToken = (OCSPToken) o;
if (basicOCSPResp != null ? !basicOCSPResp.equals(ocspToken.basicOCSPResp) : ocspToken.basicOCSPResp != null) {
return false;
}
return true;
}
/**
* Indicates if the token signature is intact.
*
* @return {@code true} or {@code false}
*/
@Override
public boolean isValid() {
return signatureValid;
}
/**
* This method returns the DSS abbreviation of the certificate. It is used for debugging purpose.
*
* @return
*/
public String getAbbreviation() {
return "OCSPToken[" + DSSUtils.formatInternal(basicOCSPResp.getProducedAt()) + ", signedBy=" + (issuerToken == null ? "?" : issuerToken.getDSSIdAsString()) +
"]";
}
@Override
public String toString(String indentStr) {
final StringWriter out = new StringWriter();
out.append(indentStr).append("OCSPToken[");
out.append("ProductionTime: ").append(DSSUtils.formatInternal(issuingTime)).append("; ");
out.append("ThisUpdate: ").append(DSSUtils.formatInternal(singleResp.getThisUpdate())).append("; ");
out.append("NextUpdate: ").append(DSSUtils.formatInternal(singleResp.getNextUpdate())).append('\n');
out.append("SignedBy: ").append(issuerToken != null ? issuerToken.getDSSIdAsString() : null).append('\n');
indentStr += "\t";
out.append(indentStr).append("Signature algorithm: ").append(algorithmUsedToSignToken == null ? "?" : algorithmUsedToSignToken.getJCEId()).append('\n');
out.append(issuerToken != null ? issuerToken.toString(indentStr) : null).append('\n');
final List<String> validationExtraInfo = extraInfo.getValidationInfo();
if (validationExtraInfo.size() > 0) {
for (final String info : validationExtraInfo) {
out.append('\n').append(indentStr).append("\t- ").append(info);
}
out.append('\n');
}
indentStr = indentStr.substring(1);
out.append(indentStr).append("]");
return out.toString();
}
@Override
public byte[] getEncoded() {
final OCSPResp ocspResp = DSSRevocationUtils.fromBasicToResp(basicOCSPResp);
try {
final byte[] bytes = ocspResp.getEncoded();
return bytes;
} catch (IOException e) {
throw new DSSException("CRL encoding error: " + e.getMessage(), e);
}
}
}