/* DigiDoc4J library
*
* This software is released under either the GNU Library General Public
* License (see LICENSE.LGPL).
*
* Note that the only valid version of the LGPL license as far as this
* project is concerned is the original GNU Library General Public License
* Version 2.1, February 1999
*/
package org.digidoc4j.impl.bdoc.ocsp;
import java.io.IOException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Date;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.digidoc4j.Configuration;
import org.digidoc4j.exceptions.ConfigurationException;
import org.digidoc4j.exceptions.DigiDoc4JException;
import org.digidoc4j.impl.bdoc.SkDataLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.europa.esig.dss.DSSException;
import eu.europa.esig.dss.DSSRevocationUtils;
import eu.europa.esig.dss.token.DSSPrivateKeyEntry;
import eu.europa.esig.dss.token.KSPrivateKeyEntry;
import eu.europa.esig.dss.token.Pkcs12SignatureToken;
import eu.europa.esig.dss.x509.CertificateToken;
import eu.europa.esig.dss.x509.ocsp.OCSPSource;
import eu.europa.esig.dss.x509.ocsp.OCSPToken;
/**
* SK OCSP source location.
*/
public abstract class SKOnlineOCSPSource implements OCSPSource {
private static final Logger logger = LoggerFactory.getLogger(SKOnlineOCSPSource.class);
/**
* The data loader used to retrieve the OCSP response.
*/
private SkDataLoader dataLoader;
private Configuration configuration;
/**
* SK Online OCSP Source constructor
*
* @param configuration configuration to use for this source
*/
public SKOnlineOCSPSource(Configuration configuration) {
this.configuration = configuration;
logger.debug("Initialized SK Online OCSP source");
}
/**
* Returns SK OCSP source location.
*
* @return OCSP source location
*/
public String getAccessLocation() {
logger.debug("");
String location = Configuration.TEST_OCSP_URL;
if (configuration != null)
location = configuration.getOcspSource();
logger.debug("OCSP Access location: " + location);
return location;
}
private byte[] buildOCSPRequest(final CertificateToken signCert, final CertificateToken issuerCert, Extension nonceExtension) throws
DSSException {
try {
logger.debug("Building OCSP request");
final CertificateID certId = DSSRevocationUtils.getOCSPCertificateID(signCert, issuerCert);
final OCSPReqBuilder ocspReqBuilder = new OCSPReqBuilder();
ocspReqBuilder.addRequest(certId);
ocspReqBuilder.setRequestExtensions(new Extensions(nonceExtension));
if (configuration.hasToBeOCSPRequestSigned()) {
logger.info("Using signed OCSP request");
JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA1withRSA");
if (!configuration.isOCSPSigningConfigurationAvailable()) {
throw new ConfigurationException("Configuration needed for OCSP request signing is not complete.");
}
DSSPrivateKeyEntry keyEntry = getOCSPAccessCertificatePrivateKey();
PrivateKey privateKey = ((KSPrivateKeyEntry)keyEntry).getPrivateKey();
X509Certificate ocspSignerCert = keyEntry.getCertificate().getCertificate();
ContentSigner contentSigner = signerBuilder.build(privateKey);
X509CertificateHolder[] chain = {new X509CertificateHolder(ocspSignerCert.getEncoded())};
GeneralName generalName = new GeneralName(new JcaX509CertificateHolder(ocspSignerCert).getSubject());
ocspReqBuilder.setRequestorName(generalName);
return ocspReqBuilder.build(contentSigner, chain).getEncoded();
}
return ocspReqBuilder.build().getEncoded();
} catch (Exception e) {
throw new DSSException(e);
}
}
@Override
public OCSPToken getOCSPToken(CertificateToken certificateToken, CertificateToken issuerCertificateToken) {
logger.debug("Getting OCSP token");
if (dataLoader == null) {
throw new RuntimeException("Data loader is null");
}
try {
final String dssIdAsString = certificateToken.getDSSIdAsString();
if (logger.isTraceEnabled()) {
logger.trace("--> OnlineOCSPSource queried for " + dssIdAsString);
}
final String ocspUri = getAccessLocation();
logger.debug("Getting OCSP token from URI: " + ocspUri);
if (ocspUri == null) {
return null;
}
Extension nonceExtension = createNonce();
final byte[] content = buildOCSPRequest(certificateToken, issuerCertificateToken, nonceExtension);
final byte[] ocspRespBytes = dataLoader.post(ocspUri, content);
final OCSPResp ocspResp = new OCSPResp(ocspRespBytes);
BasicOCSPResp basicOCSPResp = (BasicOCSPResp) ocspResp.getResponseObject();
if(basicOCSPResp == null) {
logger.error("OCSP response is empty");
return null;
}
checkNonce(basicOCSPResp, nonceExtension);
Date bestUpdate = null;
SingleResp bestSingleResp = null;
final CertificateID certId = DSSRevocationUtils.getOCSPCertificateID(certificateToken, issuerCertificateToken);
for (final SingleResp singleResp : basicOCSPResp.getResponses()) {
if (DSSRevocationUtils.matches(certId, singleResp)) {
final Date thisUpdate = singleResp.getThisUpdate();
if (bestUpdate == null || thisUpdate.after(bestUpdate)) {
bestSingleResp = singleResp;
bestUpdate = thisUpdate;
}
}
}
if (bestSingleResp != null) {
OCSPToken ocspToken = new OCSPToken();
ocspToken.setBasicOCSPResp(basicOCSPResp);
ocspToken.setBestSingleResp(bestSingleResp);
ocspToken.setSourceURL(ocspUri);
certificateToken.addRevocationToken(ocspToken);
return ocspToken;
}
} catch (OCSPException e) {
logger.error("OCSP error: " + e.getMessage(), e);
} catch (IOException e) {
throw new DSSException(e);
}
return null;
}
protected void checkNonce(BasicOCSPResp basicOCSPResp, Extension expectedNonceExtension) {
final Extension extension = basicOCSPResp.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
final DEROctetString expectedNonce = (DEROctetString) expectedNonceExtension.getExtnValue();
final DEROctetString receivedNonce = (DEROctetString) extension.getExtnValue();
if (!receivedNonce.equals(expectedNonce)) {
throw new DigiDoc4JException("The OCSP request was the victim of replay attack: nonce[sent:" + expectedNonce + "," +
" received:" + receivedNonce);
}
}
abstract Extension createNonce();
private DSSPrivateKeyEntry getOCSPAccessCertificatePrivateKey() {
Pkcs12SignatureToken signatureTokenConnection = new Pkcs12SignatureToken(configuration.getOCSPAccessCertificatePassword(), configuration.getOCSPAccessCertificateFileName());
return signatureTokenConnection.getKeys().get(0);
}
public void setDataLoader(SkDataLoader dataLoader) {
this.dataLoader = dataLoader;
}
Configuration getConfiguration() {
return configuration;
}
SkDataLoader getDataLoader() {
return dataLoader;
}
}