Skip to content

Commit 3eb973b

Browse files
author
Eugene Bochilo
committed
Support XML signature validation for LOTL files
DEVSIX-9200
1 parent fc58d1a commit 3eb973b

27 files changed

+3193
-12
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
<sonar.dependencyCheck.htmlReportPath>target/dependency-check-report.html</sonar.dependencyCheck.htmlReportPath>
108108
<sonar.dependencyCheck.reportPath>target/dependency-check-report.xml</sonar.dependencyCheck.reportPath>
109109
<surefire.version>3.0.0-M3</surefire.version>
110+
<apache.santuario.version>3.0.6</apache.santuario.version>
110111

111112
<sharpen.builddotnet>false</sharpen.builddotnet>
112113
<sharpen.showdiff>false</sharpen.showdiff>

sharpenConfiguration.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,10 @@
465465
<file path="com/itextpdf/forms/xfdf/ExceptionTestXmlParserFactory.java"/>
466466
</fileset>
467467
<!-- sign -->
468+
<fileset reason="XML validation is different in .NET">
469+
<file path="com/itextpdf/signatures/validation/XmlValidationUtils.java" />
470+
<file path="com/itextpdf/signatures/validation/CertificateSelector.java" />
471+
</fileset>
468472
<file path="com/itextpdf/signatures/ProviderDigest.java"/>
469473
<fileset reason="ProviderDigest class exists only on Java.">
470474
<file path="com/itextpdf/signatures/ProviderDigestUnitTest.java"/>

sign/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@
5656
<version>9.3.0-SNAPSHOT</version>
5757
<scope>compile</scope>
5858
</dependency>
59+
<dependency>
60+
<groupId>org.apache.santuario</groupId>
61+
<artifactId>xmlsec</artifactId>
62+
<version>${apache.santuario.version}</version>
63+
</dependency>
5964
</dependencies>
6065

6166
<profiles>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2025 Apryse Group NV
4+
Authors: Apryse Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.signatures.validation;
24+
25+
import java.security.cert.X509Certificate;
26+
27+
class CertificateSelector {
28+
private X509Certificate certificate;
29+
30+
public CertificateSelector() {
31+
// Empty constructor.
32+
}
33+
34+
public X509Certificate getCertificate() {
35+
return certificate;
36+
}
37+
38+
public CertificateSelector setCertificate(X509Certificate certificate) {
39+
this.certificate = certificate;
40+
return this;
41+
}
42+
}

sign/src/main/java/com/itextpdf/signatures/validation/ValidatorChainBuilder.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public class ValidatorChainBuilder {
5555
private Supplier<DocumentRevisionsValidator> documentRevisionsValidatorFactory;
5656
private Supplier<IOcspClientBouncyCastle> ocspClientFactory;
5757
private Supplier<ICrlClient> crlClientFactory;
58+
private Supplier<XmlSignatureValidator> xmlSignatureValidatorFactory;
5859

5960
private Collection<Certificate> trustedCertificates;
6061
private Collection<Certificate> knownCertificates;
@@ -73,6 +74,7 @@ public ValidatorChainBuilder() {
7374
documentRevisionsValidatorFactory = () -> buildDocumentRevisionsValidator();
7475
ocspClientFactory = () -> new OcspClientBouncyCastle();
7576
crlClientFactory = () -> new CrlClientOnline();
77+
xmlSignatureValidatorFactory = () -> buildXmlSignatureValidator();
7678
}
7779

7880
/**
@@ -331,6 +333,15 @@ public AdESReportAggregator getAdESReportAggregator() {
331333
return adESReportAggregator;
332334
}
333335

336+
/**
337+
* Retrieves the explicitly added or automatically created {@link IResourceRetriever} instance.
338+
*
339+
* @return the explicitly added or automatically created {@link IResourceRetriever} instance.
340+
*/
341+
public IResourceRetriever getResourceRetriever() {
342+
return resourceRetrieverFactory.get();
343+
}
344+
334345
/**
335346
* Retrieves the explicitly added or automatically created {@link DocumentRevisionsValidator} instance.
336347
*
@@ -376,15 +387,6 @@ IOcspClientBouncyCastle getOcspClient() {
376387
return ocspClientFactory.get();
377388
}
378389

379-
/**
380-
* Retrieves the explicitly added or automatically created {@link IResourceRetriever} instance.
381-
*
382-
* @return the explicitly added or automatically created {@link IResourceRetriever} instance.
383-
*/
384-
public IResourceRetriever getResourceRetriever() {
385-
return resourceRetrieverFactory.get();
386-
}
387-
388390
/**
389391
* Retrieves the explicitly added or automatically created {@link CRLValidator} instance.
390392
*
@@ -403,6 +405,19 @@ OCSPValidator getOCSPValidator() {
403405
return ocspValidatorFactory.get();
404406
}
405407

408+
ValidatorChainBuilder withXmlSignatureValidator(Supplier<XmlSignatureValidator> xmlSignatureValidatorFactory) {
409+
this.xmlSignatureValidatorFactory = xmlSignatureValidatorFactory;
410+
return this;
411+
}
412+
413+
XmlSignatureValidator getXmlSignatureValidator() {
414+
return xmlSignatureValidatorFactory.get();
415+
}
416+
417+
XmlSignatureValidator buildXmlSignatureValidator() {
418+
return new XmlSignatureValidator(this);
419+
}
420+
406421
private IssuingCertificateRetriever buildIssuingCertificateRetriever() {
407422
IssuingCertificateRetriever result = new IssuingCertificateRetriever(this.resourceRetrieverFactory.get());
408423
if (trustedCertificates != null) {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2025 Apryse Group NV
4+
Authors: Apryse Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.signatures.validation;
24+
25+
import com.itextpdf.commons.utils.DateTimeUtil;
26+
import com.itextpdf.signatures.validation.context.CertificateSource;
27+
import com.itextpdf.signatures.validation.context.TimeBasedContext;
28+
import com.itextpdf.signatures.validation.context.ValidationContext;
29+
import com.itextpdf.signatures.validation.context.ValidatorContext;
30+
import com.itextpdf.signatures.validation.report.ReportItem;
31+
import com.itextpdf.signatures.validation.report.ReportItem.ReportItemStatus;
32+
import com.itextpdf.signatures.validation.report.ValidationReport;
33+
34+
import java.io.InputStream;
35+
36+
class XmlSignatureValidator {
37+
static final String XML_SIGNATURE_VERIFICATION = "XML Signature verification check.";
38+
static final String XML_SIGNATURE_VERIFICATION_EXCEPTION =
39+
"XML Signature verification threw exception. Validation wasn't successful.";
40+
static final String NO_CERTIFICATE =
41+
"XML signing certificate wasn't find in the document. Validation wasn't successful.";
42+
static final String XML_SIGNATURE_VERIFICATION_FAILED =
43+
"XML Signature verification wasn't successful. Signature is invalid.";
44+
private final CertificateChainValidator certificateChainValidator;
45+
private final SignatureValidationProperties properties;
46+
private final ValidationContext context;
47+
48+
XmlSignatureValidator(ValidatorChainBuilder builder) {
49+
this.certificateChainValidator = builder.getCertificateChainValidator();
50+
this.properties = builder.getProperties();
51+
this.context = new ValidationContext(
52+
ValidatorContext.XML_SIGNATURE_VALIDATOR, CertificateSource.LOTL_CERT, TimeBasedContext.PRESENT);
53+
}
54+
55+
ValidationReport validate(InputStream xmlDocumentInputStream) {
56+
ValidationReport report = new ValidationReport();
57+
CertificateSelector keySelector = new CertificateSelector();
58+
try {
59+
boolean coreValidity =
60+
XmlValidationUtils.createXmlDocumentAndCheckValidity(xmlDocumentInputStream, keySelector);
61+
if (!coreValidity) {
62+
report.addReportItem(new ReportItem(
63+
XML_SIGNATURE_VERIFICATION, XML_SIGNATURE_VERIFICATION_FAILED, ReportItemStatus.INVALID));
64+
}
65+
} catch (Exception e) {
66+
report.addReportItem(new ReportItem(
67+
XML_SIGNATURE_VERIFICATION, XML_SIGNATURE_VERIFICATION_EXCEPTION, e, ReportItemStatus.INVALID));
68+
}
69+
if (stopValidation(report, context)) {
70+
return report;
71+
}
72+
73+
if (keySelector.getCertificate() == null) {
74+
report.addReportItem(new ReportItem(XML_SIGNATURE_VERIFICATION, NO_CERTIFICATE, ReportItemStatus.INVALID));
75+
return report;
76+
}
77+
certificateChainValidator.validate(
78+
report, context, keySelector.getCertificate(), DateTimeUtil.getCurrentTimeDate());
79+
return report;
80+
}
81+
82+
private boolean stopValidation(ValidationReport result, ValidationContext context) {
83+
return !properties.getContinueAfterFailure(context)
84+
&& result.getValidationResult() == ValidationReport.ValidationResult.INVALID;
85+
}
86+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2025 Apryse Group NV
4+
Authors: Apryse Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.signatures.validation;
24+
25+
import com.itextpdf.bouncycastleconnector.BouncyCastleFactoryCreator;
26+
import com.itextpdf.kernel.exceptions.PdfException;
27+
import com.itextpdf.kernel.utils.XmlProcessorCreator;
28+
29+
import java.io.IOException;
30+
import java.io.InputStream;
31+
import java.security.cert.X509Certificate;
32+
import org.apache.xml.security.exceptions.XMLSecurityException;
33+
import org.apache.xml.security.signature.XMLSignature;
34+
import org.w3c.dom.Document;
35+
import org.w3c.dom.Element;
36+
import org.w3c.dom.NamedNodeMap;
37+
import org.w3c.dom.Node;
38+
import org.w3c.dom.NodeList;
39+
import org.xml.sax.SAXException;
40+
41+
final class XmlValidationUtils {
42+
static final String CERTIFICATE_NOT_FOUND =
43+
"\"X509Certificate\" object isn't found in the signature. Other certificate sources are not supported.";
44+
static final String KEY_INFO_NULL = "Key info in XML signature cannot be null.";
45+
46+
private XmlValidationUtils() {
47+
// Private constructor so that the class instance cannot be instantiated.
48+
}
49+
50+
public static boolean createXmlDocumentAndCheckValidity(InputStream xmlDocumentInputStream,
51+
CertificateSelector keySelector) throws IOException, SAXException, XMLSecurityException {
52+
Document xmlDocument = XmlProcessorCreator.createSafeDocumentBuilder(true, false)
53+
.parse(xmlDocumentInputStream);
54+
55+
preProcessXmlDocument(xmlDocument);
56+
Node sigElement = xmlDocument.getElementsByTagName("ds:Signature").item(0);
57+
if (sigElement == null) {
58+
sigElement = xmlDocument.getElementsByTagName("Signature").item(0);
59+
}
60+
org.apache.xml.security.Init.init();
61+
XMLSignature signature = new XMLSignature((Element) sigElement, "", true,
62+
BouncyCastleFactoryCreator.getFactory().getProvider());
63+
64+
if (signature.getKeyInfo() == null) {
65+
throw new PdfException(KEY_INFO_NULL);
66+
}
67+
X509Certificate cert = signature.getKeyInfo().getX509Certificate();
68+
if (cert == null) {
69+
throw new PdfException(CERTIFICATE_NOT_FOUND);
70+
}
71+
keySelector.setCertificate(cert);
72+
return signature.checkSignatureValue(cert);
73+
}
74+
75+
private static void preProcessXmlDocument(Document xmlDocument) {
76+
Element rootElement = xmlDocument.getDocumentElement();
77+
setIdRecursively(rootElement);
78+
}
79+
80+
private static void setIdRecursively(Element element) {
81+
NamedNodeMap attributes = element.getAttributes();
82+
for (int i = 0; i < attributes.getLength(); ++i) {
83+
Node attribute = attributes.item(i);
84+
if ("ID".equalsIgnoreCase(attribute.getLocalName())) {
85+
element.setIdAttribute(attribute.getNodeName(), true);
86+
}
87+
}
88+
89+
NodeList children = element.getChildNodes();
90+
for (int i = 0; i < children.getLength(); ++i) {
91+
Node child = children.item(i);
92+
if (child instanceof Element) {
93+
setIdRecursively((Element) child);
94+
}
95+
}
96+
}
97+
}

sign/src/main/java/com/itextpdf/signatures/validation/context/CertificateSource.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,9 @@ public enum CertificateSource {
4949
/**
5050
* The context while validating a timestamp issuer certificate.
5151
*/
52-
TIMESTAMP
52+
TIMESTAMP,
53+
/**
54+
* A certificate, which is used to sign List of Trusted Lists.
55+
*/
56+
LOTL_CERT
5357
}

sign/src/main/java/com/itextpdf/signatures/validation/context/ValidatorContext.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ This file is part of the iText (R) project.
2727
import com.itextpdf.signatures.validation.OCSPValidator;
2828
import com.itextpdf.signatures.validation.RevocationDataValidator;
2929
import com.itextpdf.signatures.validation.DocumentRevisionsValidator;
30+
import com.itextpdf.signatures.validation.SignatureValidator;
3031

3132
/**
3233
* This enum lists all possible contexts related to the validator in which the validation is taking place.
@@ -49,11 +50,15 @@ public enum ValidatorContext {
4950
*/
5051
CERTIFICATE_CHAIN_VALIDATOR,
5152
/**
52-
* This value is expected to be used in SignatureValidator context.
53+
* This value is expected to be used in {@link SignatureValidator} context.
5354
*/
5455
SIGNATURE_VALIDATOR,
5556
/**
5657
* This value is expected to be used in {@link DocumentRevisionsValidator} context.
5758
*/
58-
DOCUMENT_REVISIONS_VALIDATOR
59+
DOCUMENT_REVISIONS_VALIDATOR,
60+
/**
61+
* This value is expected to be used in XmlSignatureValidator context.
62+
*/
63+
XML_SIGNATURE_VALIDATOR
5964
}

0 commit comments

Comments
 (0)