/* 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;
import static eu.europa.esig.dss.DigestAlgorithm.SHA256;
import static eu.europa.esig.dss.SignatureLevel.XAdES_BASELINE_B;
import static eu.europa.esig.dss.SignatureLevel.XAdES_BASELINE_LT;
import static eu.europa.esig.dss.SignatureLevel.XAdES_BASELINE_LTA;
import static org.apache.commons.codec.binary.Base64.decodeBase64;
import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.digidoc4j.impl.bdoc.ocsp.OcspSourceBuilder.anOcspSource;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.digidoc4j.Configuration;
import org.digidoc4j.DataFile;
import org.digidoc4j.DataToSign;
import org.digidoc4j.DigestAlgorithm;
import org.digidoc4j.EncryptionAlgorithm;
import org.digidoc4j.Signature;
import org.digidoc4j.SignatureBuilder;
import org.digidoc4j.SignatureProfile;
import org.digidoc4j.exceptions.ContainerWithoutFilesException;
import org.digidoc4j.exceptions.InvalidSignatureException;
import org.digidoc4j.exceptions.OCSPRequestFailedException;
import org.digidoc4j.exceptions.SignerCertificateRequiredException;
import org.digidoc4j.impl.SignatureFinalizer;
import org.digidoc4j.impl.bdoc.asic.DetachedContentCreator;
import org.digidoc4j.impl.bdoc.ocsp.SKOnlineOCSPSource;
import org.digidoc4j.impl.bdoc.xades.XadesSignature;
import org.digidoc4j.impl.bdoc.xades.XadesSigningDssFacade;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.europa.esig.dss.DSSDocument;
import eu.europa.esig.dss.DSSUtils;
import eu.europa.esig.dss.InMemoryDocument;
import eu.europa.esig.dss.Policy;
import eu.europa.esig.dss.SignerLocation;
import eu.europa.esig.dss.client.tsp.OnlineTSPSource;
public class BDocSignatureBuilder extends SignatureBuilder implements SignatureFinalizer {
private final static Logger logger = LoggerFactory.getLogger(BDocSignatureBuilder.class);
private static final SignatureProfile DEFAULT_SIGNATURE_PROFILE = SignatureProfile.LT;
private transient XadesSigningDssFacade facade;
private Date signingDate;
@Override
protected Signature invokeSigningProcess() {
logger.info("Signing BDoc container");
signatureParameters.setSigningCertificate(signatureToken.getCertificate());
byte[] dataToSign = getDataToBeSigned();
byte[] signatureValue = signatureToken.sign(signatureParameters.getDigestAlgorithm(), dataToSign);
return finalizeSignature(signatureValue);
}
@Override
public DataToSign buildDataToSign() throws SignerCertificateRequiredException, ContainerWithoutFilesException {
byte[] dataToSign = getDataToBeSigned();
byte[] digestToSign = calculateDigestToSign(dataToSign);
return new DataToSign(digestToSign, signatureParameters, this);
}
@Override
public Signature openAdESSignature(byte[] signatureDocument) {
if (signatureDocument == null) {
logger.error("Signature cannot be empty");
throw new InvalidSignatureException();
}
InMemoryDocument document = new InMemoryDocument(signatureDocument);
return createSignature(document);
}
@Override
public Signature finalizeSignature(byte[] signatureValueBytes) {
logger.info("Finalizing BDoc signature");
populateParametersForFinalizingSignature(signatureValueBytes);
Collection<DataFile> dataFilesToSign = getDataFiles();
validateDataFilesToSign(dataFilesToSign);
DSSDocument signedDocument = facade.signDocument(signatureValueBytes, dataFilesToSign);
return createSignature(signedDocument);
}
private Signature createSignature(DSSDocument signedDocument) {
logger.debug("Opening signed document validator");
Configuration configuration = getConfiguration();
DetachedContentCreator detachedContentCreator = new DetachedContentCreator().populate(getDataFiles());
List<DSSDocument> detachedContents = detachedContentCreator.getDetachedContentList();
BDocSignatureOpener signatureOpener = new BDocSignatureOpener(detachedContents, configuration);
List<BDocSignature> signatureList = signatureOpener.parse(signedDocument);
BDocSignature signature = signatureList.get(0); //Only one signature was created
validateOcspResponse(signature.getOrigin());
logger.info("Signing BDoc successfully completed");
return signature;
}
private byte[] getDataToBeSigned() {
logger.info("Getting data to sign");
initSigningFacade();
populateSignatureParameters();
Collection<DataFile> dataFilesToSign = getDataFiles();
validateDataFilesToSign(dataFilesToSign);
byte[] dataToSign = facade.getDataToSign(dataFilesToSign);
String signatureId = facade.getSignatureId();
signatureParameters.setSignatureId(signatureId);
return dataToSign;
}
private void populateSignatureParameters() {
setDigestAlgorithm();
setEncryptionAlgorithm();
setSignatureProfile();
setSignerInformation();
setSignatureId();
setSignaturePolicy();
setSigningCertificate();
setSigningDate();
setTimeStampProviderSource();
}
private void populateParametersForFinalizingSignature(byte[] signatureValueBytes) {
if (facade == null) {
initSigningFacade();
populateSignatureParameters();
}
Configuration configuration = getConfiguration();
facade.setCertificateSource(configuration.getTSL());
setOcspSource(signatureValueBytes);
}
private void initSigningFacade() {
if (facade == null) {
facade = new XadesSigningDssFacade();
}
}
private byte[] calculateDigestToSign(byte[] dataToDigest) {
DigestAlgorithm digestAlgorithm = signatureParameters.getDigestAlgorithm();
return DSSUtils.digest(digestAlgorithm.getDssDigestAlgorithm(), dataToDigest);
}
private Configuration getConfiguration() {
return ((BDocContainer) container).getConfiguration();
}
private List<DataFile> getDataFiles() {
return container.getDataFiles();
}
private void validateOcspResponse(XadesSignature xadesSignature) {
if (isBaselineSignatureProfile()) {
return;
}
List<BasicOCSPResp> ocspResponses = xadesSignature.getOcspResponses();
if (ocspResponses == null || ocspResponses.isEmpty()) {
logger.error("Signature does not contain OCSP response");
throw new OCSPRequestFailedException();
}
}
private boolean isBaselineSignatureProfile() {
return signatureParameters.getSignatureProfile() != null && (SignatureProfile.B_BES == signatureParameters.getSignatureProfile() || SignatureProfile.B_EPES == signatureParameters.getSignatureProfile());
}
private void setOcspSource(byte[] signatureValueBytes) {
SKOnlineOCSPSource ocspSource = anOcspSource().
withSignatureProfile(signatureParameters.getSignatureProfile()).
withSignatureValue(signatureValueBytes).
withConfiguration(getConfiguration()).
build();
facade.setOcspSource(ocspSource);
}
private void setTimeStampProviderSource() {
Configuration configuration = getConfiguration();
OnlineTSPSource tspSource = new OnlineTSPSource(configuration.getTspSource());
SkDataLoader dataLoader = SkDataLoader.createTimestampDataLoader(configuration);
dataLoader.setUserAgentSignatureProfile(signatureParameters.getSignatureProfile());
tspSource.setDataLoader(dataLoader);
facade.setTspSource(tspSource);
}
private void setDigestAlgorithm() {
if (signatureParameters.getDigestAlgorithm() == null) {
signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA256);
}
facade.setSignatureDigestAlgorithm(signatureParameters.getDigestAlgorithm());
}
private void setEncryptionAlgorithm() {
if (signatureParameters.getEncryptionAlgorithm() == EncryptionAlgorithm.ECDSA || isEcdsaCertificate()) {
logger.debug("Using ECDSA encryption algorithm");
facade.setEncryptionAlgorithm(eu.europa.esig.dss.EncryptionAlgorithm.ECDSA);
}
}
private boolean isEcdsaCertificate() {
X509Certificate certificate = signatureParameters.getSigningCertificate();
String algorithm = certificate.getPublicKey().getAlgorithm();
return algorithm.equals("EC") || algorithm.equals("ECC");
}
private void setSignatureProfile() {
if (signatureParameters.getSignatureProfile() != null) {
setSignatureProfile(signatureParameters.getSignatureProfile());
} else {
setSignatureProfile(DEFAULT_SIGNATURE_PROFILE);
signatureParameters.setSignatureProfile(DEFAULT_SIGNATURE_PROFILE);
}
}
private void setSignatureProfile(SignatureProfile profile) {
switch (profile) {
case B_BES:
facade.setSignatureLevel(XAdES_BASELINE_B);
break;
case B_EPES:
facade.setSignatureLevel(XAdES_BASELINE_B);
break;
case LTA:
facade.setSignatureLevel(XAdES_BASELINE_LTA);
break;
default:
facade.setSignatureLevel(XAdES_BASELINE_LT);
}
}
private void setSignaturePolicy() {
if (isTimeMarkProfile() || isEpesProfile()) {
Policy signaturePolicy = createBDocSignaturePolicy();
facade.setSignaturePolicy(signaturePolicy);
}
}
public static Policy createBDocSignaturePolicy() {
Policy signaturePolicy = new Policy();
signaturePolicy.setId("urn:oid:1.3.6.1.4.1.10015.1000.3.2.1");
signaturePolicy.setDigestValue(decodeBase64("3Tl1oILSvOAWomdI9VeWV6IA/32eSXRUri9kPEz1IVs="));
signaturePolicy.setDigestAlgorithm(SHA256);
signaturePolicy.setSpuri("https://www.sk.ee/repository/bdoc-spec21.pdf");
return signaturePolicy;
}
private void setSignatureId() {
if (StringUtils.isNotBlank(signatureParameters.getSignatureId())) {
facade.setSignatureId(signatureParameters.getSignatureId());
}
}
private void setSignerInformation() {
logger.debug("Adding signer information");
List<String> signerRoles = signatureParameters.getRoles();
if (!(isEmpty(signatureParameters.getCity()) && isEmpty(signatureParameters.getStateOrProvince())
&& isEmpty(signatureParameters.getPostalCode())
&& isEmpty(signatureParameters.getCountry()))) {
SignerLocation signerLocation = new SignerLocation();
if (!isEmpty(signatureParameters.getCity()))
signerLocation.setLocality(signatureParameters.getCity());
if (!isEmpty(signatureParameters.getStateOrProvince()))
signerLocation.setStateOrProvince(signatureParameters.getStateOrProvince());
if (!isEmpty(signatureParameters.getPostalCode()))
signerLocation.setPostalCode(signatureParameters.getPostalCode());
if (!isEmpty(signatureParameters.getCountry()))
signerLocation.setCountry(signatureParameters.getCountry());
facade.setSignerLocation(signerLocation);
}
facade.setSignerRoles(signerRoles);
}
private void setSigningCertificate() {
X509Certificate signingCert = signatureParameters.getSigningCertificate();
facade.setSigningCertificate(signingCert);
}
private void setSigningDate() {
if (signingDate == null) {
signingDate = new Date();
}
facade.setSigningDate(signingDate);
logger.debug("Signing date is going to be " + signingDate);
}
private void validateDataFilesToSign(Collection<DataFile> dataFilesToSign) {
if (dataFilesToSign.isEmpty()) {
logger.error("Container does not contain any data files");
throw new ContainerWithoutFilesException();
}
}
private boolean isTimeMarkProfile() {
if (signatureParameters.getSignatureProfile() == null) {
return false;
}
return signatureParameters.getSignatureProfile() == SignatureProfile.LT_TM;
}
private boolean isEpesProfile() {
if (signatureParameters.getSignatureProfile() != null) {
return signatureParameters.getSignatureProfile() == SignatureProfile.B_EPES;
}
return false;
}
}