/* 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.xades.validation;
import static org.apache.commons.lang.StringUtils.equalsIgnoreCase;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.digidoc4j.SignatureValidationResult;
import org.digidoc4j.exceptions.CertificateRevokedException;
import org.digidoc4j.exceptions.DigiDoc4JException;
import org.digidoc4j.exceptions.InvalidOcspNonceException;
import org.digidoc4j.exceptions.InvalidTimestampException;
import org.digidoc4j.exceptions.MiltipleSignedPropertiesException;
import org.digidoc4j.exceptions.SignedPropertiesMissingException;
import org.digidoc4j.exceptions.WrongPolicyIdentifierException;
import org.digidoc4j.exceptions.WrongPolicyIdentifierQualifierException;
import org.digidoc4j.impl.bdoc.OcspNonceValidator;
import org.digidoc4j.impl.bdoc.xades.XadesSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import eu.europa.esig.dss.validation.MessageTag;
import eu.europa.esig.dss.validation.policy.rules.Indication;
import eu.europa.esig.dss.validation.reports.DetailedReport;
import eu.europa.esig.dss.validation.reports.Reports;
import eu.europa.esig.dss.validation.reports.SimpleReport;
import eu.europa.esig.dss.validation.reports.wrapper.DiagnosticData;
import eu.europa.esig.dss.x509.SignaturePolicy;
import eu.europa.esig.dss.xades.DSSXMLUtils;
import eu.europa.esig.dss.xades.XPathQueryHolder;
import eu.europa.esig.dss.xades.validation.XAdESSignature;
public class XadesSignatureValidator implements SignatureValidator {
private final static Logger logger = LoggerFactory.getLogger(XadesSignatureValidator.class);
public static final String TM_POLICY = "urn:oid:1.3.6.1.4.1.10015.1000.3.2.1";
private static final String OIDAS_URN = "OIDAsURN";
private static final String XADES_SIGNED_PROPERTIES = "http://uri.etsi.org/01903#SignedProperties";
private transient Reports validationReport;
private transient SimpleReport simpleReport;
private List<DigiDoc4JException> validationErrors = new ArrayList<>();
private List<DigiDoc4JException> validationWarnings = new ArrayList<>();
private String signatureId;
private XadesSignature signature;
public XadesSignatureValidator(XadesSignature signature) {
this.signature = signature;
signatureId = signature.getId();
}
@Override
public SignatureValidationResult extractValidationErrors() {
logger.debug("Extracting validation errors");
XadesValidationResult validationResult = signature.validate();
validationReport = validationResult.getReport();
Map<String, SimpleReport> simpleReports = validationResult.extractSimpleReports();
simpleReport = getSimpleReport(simpleReports);
populateValidationErrors();
return createValidationResult();
}
protected void populateValidationErrors() {
addPolicyValidationErrors();
addSignedPropertiesReferenceValidationErrors();
addReportedErrors();
addReportedWarnings();
addTimestampErrors();
addOcspErrors();
}
private void addPolicyValidationErrors() {
logger.debug("Extracting policy validation errors");
SignaturePolicy policy = getDssSignature().getPolicyId();
if(policy != null) {
String policyIdentifier = policy.getIdentifier().trim();
if (!StringUtils.equals(TM_POLICY, policyIdentifier)) {
addValidationError(new WrongPolicyIdentifierException("Wrong policy identifier: " + policyIdentifier));
} else {
addPolicyIdentifierQualifierValidationErrors();
}
}
}
private void addPolicyIdentifierQualifierValidationErrors() {
logger.debug("Extracting policy identifier qualifier validation errors");
XPathQueryHolder xPathQueryHolder = getDssSignature().getXPathQueryHolder();
Element signatureElement = getDssSignature().getSignatureElement();
Element element = DSSXMLUtils.getElement(signatureElement, xPathQueryHolder.XPATH_SIGNATURE_POLICY_IDENTIFIER);
Element identifier = DSSXMLUtils.getElement(element, "./xades:SignaturePolicyId/xades:SigPolicyId/xades:Identifier");
String qualifier = identifier.getAttribute("Qualifier");
if (!StringUtils.equals(OIDAS_URN, qualifier)) {
addValidationError(new WrongPolicyIdentifierQualifierException("Wrong policy identifier qualifier: " + qualifier));
}
}
private void addSignedPropertiesReferenceValidationErrors() {
logger.debug("Extracting signed properties reference validation errors");
int propertiesReferencesCount = findSignedPropertiesReferencesCount();
String signatureId = getDssSignature().getId();
if(propertiesReferencesCount == 0) {
logger.error("Signed properties are missing for signature " + signatureId);
addValidationError(new SignedPropertiesMissingException("Signed properties missing"));
}
if (propertiesReferencesCount > 1) {
logger.error("Multiple signed properties for signature " + signatureId);
DigiDoc4JException error = new MiltipleSignedPropertiesException("Multiple signed properties");
addValidationError(error);
}
}
private int findSignedPropertiesReferencesCount() {
List<Element> signatureReferences = getDssSignature().getSignatureReferences();
int nrOfSignedPropertiesReferences = 0;
for (Element signatureReference : signatureReferences) {
String type = signatureReference.getAttribute("Type");
if (StringUtils.equals(XADES_SIGNED_PROPERTIES, type))
nrOfSignedPropertiesReferences++;
}
return nrOfSignedPropertiesReferences;
}
private void addReportedErrors() {
logger.debug("Extracting reported errors");
if (simpleReport != null) {
for (String errorMessage : simpleReport.getErrors(signatureId)) {
if(isRedundantErrorMessage(errorMessage)) {
logger.debug("Ignoring redundant error message: " + errorMessage);
continue;
}
logger.error(errorMessage);
if(errorMessage.contains(MessageTag.BBB_XCV_ISCR_ANS.getMessage())) {
addValidationError(new CertificateRevokedException(errorMessage));
} else if(errorMessage.contains(MessageTag.PSV_IPSVC_ANS.getMessage())) {
addValidationError(new CertificateRevokedException(errorMessage));
} else {
addValidationError(new DigiDoc4JException(errorMessage));
}
}
}
}
private boolean isRedundantErrorMessage(String errorMessage) {
return equalsIgnoreCase(errorMessage, MessageTag.ADEST_ROBVPIIC_ANS.getMessage()) || equalsIgnoreCase(errorMessage, MessageTag.LTV_ABSV_ANS.getMessage()) || equalsIgnoreCase(errorMessage, MessageTag.ARCH_LTVV_ANS.getMessage()) || equalsIgnoreCase(errorMessage, MessageTag.BBB_XCV_RFC_ANS.getMessage()) || equalsIgnoreCase(errorMessage, MessageTag.BBB_XCV_SUB_ANS.getMessage());
}
private void addReportedWarnings() {
if (simpleReport != null) {
for (String warning : simpleReport.getWarnings(signatureId)) {
logger.warn(warning);
validationWarnings.add(new DigiDoc4JException(warning));
}
}
}
private void addTimestampErrors() {
if(!isTimestampValidForSignature()) {
logger.error("Signature " + signatureId + " has an invalid timestamp");
addValidationError(new InvalidTimestampException());
}
}
private boolean isTimestampValidForSignature() {
logger.debug("Finding timestamp errors for signature " + signatureId);
DiagnosticData diagnosticData = validationReport.getDiagnosticData();
if (diagnosticData == null) {
return true;
}
List<String> timestampIdList = diagnosticData.getTimestampIdList(signatureId);
if(timestampIdList == null || timestampIdList.isEmpty()) {
return true;
}
String timestampId = timestampIdList.get(0);
DetailedReport detailedReport = validationReport.getDetailedReport();
Indication indication = detailedReport.getTimestampValidationIndication(timestampId);
return isIndicationValid(indication);
}
private SimpleReport getSimpleReport(Map<String, SimpleReport> simpleReports) {
SimpleReport simpleReport = simpleReports.get(signatureId);
if (simpleReport != null && simpleReports.size() == 1) {
return simpleReports.values().iterator().next();
}
return simpleReport;
}
private void addOcspErrors() {
OcspNonceValidator ocspValidator = new OcspNonceValidator(getDssSignature());
if(!ocspValidator.isValid()) {
logger.error("OCSP nonce is invalid");
addValidationError(new InvalidOcspNonceException());
}
}
private SignatureValidationResult createValidationResult() {
SignatureValidationResult result = new SignatureValidationResult();
result.setErrors(validationErrors);
result.setWarnings(validationWarnings);
return result;
}
protected void addValidationError(DigiDoc4JException error) {
validationErrors.add(error);
}
private XAdESSignature getDssSignature() {
return signature.getDssSignature();
}
private boolean isIndicationValid(Indication indication) {
return indication == Indication.PASSED || indication == Indication.TOTAL_PASSED;
}
}