Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement timemark hashcode validation with Digidoc4j #56

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package ee.openeid.siva.proxy;

import ee.openeid.siva.validation.document.ValidationDocument;
import eu.europa.esig.dss.utils.Utils;
import eu.europa.esig.dss.xml.common.definition.DSSNamespace;
import eu.europa.esig.dss.xml.utils.DomUtils;
import eu.europa.esig.xades.definition.xades132.XAdES132Element;
import org.digidoc4j.dss.xades.BDocTmSupport;
import org.springframework.stereotype.Service;
import org.w3c.dom.Element;

import java.util.Optional;

import static eu.europa.esig.dss.xml.common.definition.AbstractPath.allFromCurrentPosition;

@Service
public class HasBdocTimemarkPolicyService {
boolean hasBdocTimemarkPolicy(ValidationDocument validationDocument) {
return extractSigPolicyIdElement(validationDocument)
.map(this::extractSigPolicyIdValue)
.map(this::matchesBdocTimemarkPolicyId)
.orElse(false);
}

private Optional<Element> extractSigPolicyIdElement(ValidationDocument validationDocument) {
DomUtils.registerNamespace(new DSSNamespace("http://uri.etsi.org/01903/v1.3.2#", "xades132"));
return Optional.of(validationDocument.getBytes())
.filter(DomUtils::startsWithXmlPreamble)
.map(DomUtils::buildDOM)
.map(dom -> DomUtils.getElement(dom, allFromCurrentPosition(XAdES132Element.SIG_POLICY_ID)));
}

private String extractSigPolicyIdValue(Element sigPolicyId) {
return Utils.trim(DomUtils.getValue(sigPolicyId, allFromCurrentPosition(XAdES132Element.IDENTIFIER)));
}

private boolean matchesBdocTimemarkPolicyId(String sigPolicyIdValue) {
return Utils.areStringsEqualIgnoreCase(BDocTmSupport.BDOC_TM_POLICY_ID, sigPolicyIdValue);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package ee.openeid.siva.proxy;

import ee.openeid.siva.proxy.document.ProxyHashcodeDataSet;
import ee.openeid.siva.validation.document.SignatureFile;
import ee.openeid.siva.validation.document.ValidationDocument;
import ee.openeid.siva.validation.document.report.Reports;
import ee.openeid.siva.validation.document.report.ValidationConclusion;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class HashcodeValidationMapper {
public List<ValidationDocument> mapToValidationDocuments(ProxyHashcodeDataSet proxyRequest) {
return proxyRequest.getSignatureFiles()
.stream()
.map(signatureFile -> createValidationDocument(proxyRequest.getSignaturePolicy(), signatureFile))
.toList();
}

private ValidationDocument createValidationDocument(String signaturePolicy, SignatureFile signatureFile) {
ValidationDocument validationDocument = new ValidationDocument();
validationDocument.setSignaturePolicy(signaturePolicy);
validationDocument.setBytes(signatureFile.getSignature());
validationDocument.setDatafiles(signatureFile.getDatafiles());
return validationDocument;
}

Reports mergeReportsToOne(List<Reports> reportsList) {
int signaturesCount = 0;
int validSignaturesCount = 0;
Reports response = null;
for (Reports reports : reportsList) {
ValidationConclusion validationConclusion = reports.getSimpleReport().getValidationConclusion();
if (signaturesCount == 0) {
response = reports;
validSignaturesCount = validationConclusion.getValidSignaturesCount();
} else {
response.getSimpleReport().getValidationConclusion().getSignatures().addAll(validationConclusion.getSignatures());
validSignaturesCount = validSignaturesCount + validationConclusion.getValidSignaturesCount();
}
signaturesCount = signaturesCount + validationConclusion.getSignaturesCount();
}
if (response != null) {
ValidationConclusion validationConclusion = response.getSimpleReport().getValidationConclusion();
validationConclusion.setSignaturesCount(signaturesCount);
validationConclusion.setValidSignaturesCount(validSignaturesCount);
}
return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,55 +19,92 @@
import ee.openeid.siva.proxy.document.ProxyHashcodeDataSet;
import ee.openeid.siva.proxy.document.ReportType;
import ee.openeid.siva.statistics.StatisticsService;
import ee.openeid.siva.validation.document.SignatureFile;
import ee.openeid.siva.validation.document.Datafile;
import ee.openeid.siva.validation.document.ValidationDocument;
import ee.openeid.siva.validation.document.report.Reports;
import ee.openeid.siva.validation.document.report.SimpleReport;
import ee.openeid.siva.validation.service.ValidationService;
import ee.openeid.siva.validation.exception.MalformedSignatureFileException;
import ee.openeid.siva.validation.security.SecureSAXParsers;
import ee.openeid.validation.service.generic.SignatureXmlHandler;
import ee.openeid.validation.service.generic.HashcodeGenericValidationService;
import ee.openeid.validation.service.timemark.TimemarkHashcodeValidationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.xml.parsers.SAXParser;
import java.io.ByteArrayInputStream;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Optional;

@Service
public class HashcodeValidationProxy extends ValidationProxy {

private static final String HASHCODE_GENERIC_SERVICE = "hashcodeGeneric";
private final HasBdocTimemarkPolicyService hasBdocTimemarkPolicyService;
private final HashcodeValidationMapper hashcodeValidationMapper;
private final HashcodeGenericValidationService hashcodeGenericValidationService;
private final TimemarkHashcodeValidationService timemarkHashcodeValidationService;

@Autowired
public HashcodeValidationProxy(StatisticsService statisticsService, ApplicationContext applicationContext, Environment environment) {
public HashcodeValidationProxy(StatisticsService statisticsService,
ApplicationContext applicationContext,
Environment environment,
HasBdocTimemarkPolicyService hasBdocTimemarkPolicyService,
HashcodeValidationMapper hashcodeValidationMapper,
HashcodeGenericValidationService hashcodeGenericValidationService,
TimemarkHashcodeValidationService timemarkHashcodeValidationService) {
super(statisticsService, applicationContext, environment);
this.hasBdocTimemarkPolicyService = hasBdocTimemarkPolicyService;
this.hashcodeValidationMapper = hashcodeValidationMapper;
this.hashcodeGenericValidationService = hashcodeGenericValidationService;
this.timemarkHashcodeValidationService = timemarkHashcodeValidationService;
}

@Override
String constructValidatorName(ProxyRequest proxyRequest) {
return HASHCODE_GENERIC_SERVICE + SERVICE_BEAN_NAME_POSTFIX;
throw new IllegalStateException("Method is unimplemented");
}

@Override
public SimpleReport validateRequest(ProxyRequest proxyRequest) {
ValidationService validationService = getServiceForType(proxyRequest);
if (validationService instanceof HashcodeGenericValidationService && proxyRequest instanceof ProxyHashcodeDataSet) {

List<ValidationDocument> validationDocuments = ((ProxyHashcodeDataSet) proxyRequest).getSignatureFiles()
.stream()
.map(signatureFile -> createValidationDocument(proxyRequest.getSignaturePolicy(), signatureFile))
.collect(Collectors.toList());
Reports reports = ((HashcodeGenericValidationService) validationService).validate(validationDocuments);
return chooseReport(reports, ReportType.SIMPLE);
if (proxyRequest instanceof ProxyHashcodeDataSet) {
var reports = hashcodeValidationMapper.mapToValidationDocuments((ProxyHashcodeDataSet) proxyRequest)
.stream()
.map(this::validateDocument)
.toList();
return chooseReport(hashcodeValidationMapper.mergeReportsToOne(reports), ReportType.SIMPLE);
}
throw new IllegalStateException("Something went wrong with hashcode validation");
}

ValidationDocument createValidationDocument(String signaturePolicy, SignatureFile signatureFile) {
ValidationDocument validationDocument = new ValidationDocument();
validationDocument.setSignaturePolicy(signaturePolicy);
validationDocument.setBytes(signatureFile.getSignature());
validationDocument.setDatafiles(signatureFile.getDatafiles());
return validationDocument;
private Reports validateDocument(ValidationDocument validationDocument) {
Optional.ofNullable(getDataFileInfoIfNeeded(validationDocument))
.filter(dataFiles -> !dataFiles.isEmpty())
.ifPresent(validationDocument::setDatafiles);
if (hasBdocTimemarkPolicyService.hasBdocTimemarkPolicy(validationDocument)) {
return timemarkHashcodeValidationService.validateDocument(validationDocument);
} else {
return hashcodeGenericValidationService.validateDocument(validationDocument);
}
}

private List<Datafile> getDataFileInfoIfNeeded(ValidationDocument validationDocument) {
if (!CollectionUtils.isEmpty(validationDocument.getDatafiles())) {
return null;
} else {
try {
SAXParser saxParser = SecureSAXParsers.createParser();
SignatureXmlHandler handler = new SignatureXmlHandler();
saxParser.parse(new ByteArrayInputStream(validationDocument.getBytes()), handler);
return handler.getDatafiles();
} catch (Exception e) {
throw constructMalformedDocumentException(new RuntimeException(e));
}
}
}

private RuntimeException constructMalformedDocumentException(Exception cause) {
return new MalformedSignatureFileException(cause, "Signature file malformed");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package ee.openeid.siva.proxy;

import ee.openeid.siva.validation.document.ValidationDocument;
import ee.openeid.siva.validation.document.builder.DummyValidationDocumentBuilder;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

class HasBdocTimemarkPolicyServiceTest {
private final HasBdocTimemarkPolicyService hasBdocTimemarkPolicyService = new HasBdocTimemarkPolicyService();

@Test
void hasBdocTimemarkPolicy_whenInputIsNotXmlFile_shouldReturnFalse() {
assertFalse(
hasBdocTimemarkPolicyService.hasBdocTimemarkPolicy(createValidationDocument("timestamptoken-ddoc.asics"))
);
}

@Test
void hasBdocTimemarkPolicy_whenSignatureDoesNotHaveBdocTimemark_shouldReturnFalse() {
assertFalse(
hasBdocTimemarkPolicyService.hasBdocTimemarkPolicy(createValidationDocument("no_timemark_signature.xml"))
);
}

@Test
void hasBdocTimemarkPolicy_whenSignatureHasBdocTimemark_shouldReturnTrue() {
assertTrue(
hasBdocTimemarkPolicyService.hasBdocTimemarkPolicy(createValidationDocument("timemark_signature.xml"))
);
}

@SneakyThrows
private ValidationDocument createValidationDocument(String file) {
return DummyValidationDocumentBuilder
.aValidationDocument()
.withDocument("test-files/" + file)
.withName(file)
.build();
}
}
Loading