Skip to content

Commit db84b32

Browse files
committed
Add support for validating all signatures in a document
DEVSIX-8303
1 parent 03a2c75 commit db84b32

File tree

6 files changed

+158
-47
lines changed

6 files changed

+158
-47
lines changed

sign/src/main/java/com/itextpdf/signatures/validation/v1/SignatureValidator.java

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,20 @@ This file is part of the iText (R) project.
2222
*/
2323
package com.itextpdf.signatures.validation.v1;
2424

25+
import com.itextpdf.commons.bouncycastle.asn1.tsp.ITSTInfo;
2526
import com.itextpdf.commons.utils.DateTimeUtil;
2627
import com.itextpdf.commons.utils.MessageFormatUtil;
2728
import com.itextpdf.kernel.pdf.PdfArray;
2829
import com.itextpdf.kernel.pdf.PdfDictionary;
2930
import com.itextpdf.kernel.pdf.PdfDocument;
3031
import com.itextpdf.kernel.pdf.PdfName;
32+
import com.itextpdf.kernel.pdf.PdfReader;
3133
import com.itextpdf.kernel.pdf.PdfStream;
3234
import com.itextpdf.signatures.CertificateUtil;
3335
import com.itextpdf.signatures.IssuingCertificateRetriever;
3436
import com.itextpdf.signatures.PdfPKCS7;
3537
import com.itextpdf.signatures.SignatureUtil;
38+
import com.itextpdf.signatures.TimestampConstants;
3639
import com.itextpdf.signatures.validation.v1.context.CertificateSource;
3740
import com.itextpdf.signatures.validation.v1.context.TimeBasedContext;
3841
import com.itextpdf.signatures.validation.v1.context.ValidationContext;
@@ -42,18 +45,21 @@ This file is part of the iText (R) project.
4245
import com.itextpdf.signatures.validation.v1.report.ValidationReport;
4346

4447
import java.io.ByteArrayInputStream;
48+
import java.io.IOException;
4549
import java.security.GeneralSecurityException;
4650
import java.security.cert.Certificate;
4751
import java.security.cert.X509Certificate;
4852
import java.util.ArrayList;
4953
import java.util.Arrays;
54+
import java.util.Collections;
5055
import java.util.Date;
5156
import java.util.List;
5257

5358
/**
5459
* Validator class, which is expected to be used for signatures validation.
5560
*/
5661
class SignatureValidator {
62+
public static final String VALIDATING_SIGNATURE_NAME = "Validating signature {0}";
5763
static final String TIMESTAMP_VERIFICATION = "Timestamp verification check.";
5864
static final String SIGNATURE_VERIFICATION = "Signature verification check.";
5965
static final String CERTS_FROM_DSS = "Certificates from DSS check.";
@@ -62,44 +68,71 @@ class SignatureValidator {
6268
static final String CANNOT_VERIFY_SIGNATURE = "Signature {0} cannot be mathematically verified.";
6369
static final String DOCUMENT_IS_NOT_COVERED = "Signature {0} doesn't cover entire document.";
6470
static final String CANNOT_VERIFY_TIMESTAMP = "Signature timestamp attribute cannot be verified";
65-
66-
private final PdfDocument document;
71+
static final String REVISIONS_RETRIEVAL_FAILED = "Wasn't possible to retrieve document revisions.";
72+
private static final String TIMESTAMP_EXTRACTION_FAILED = "Unable to extract timestamp from timestamp signature";
6773
private final ValidationContext baseValidationContext;
6874
private final CertificateChainValidator certificateChainValidator;
6975
private final IssuingCertificateRetriever certificateRetriever;
7076
private final SignatureValidationProperties properties;
77+
private Date lastKnownPoE = (Date) TimestampConstants.UNDEFINED_TIMESTAMP_DATE;
7178

7279
/**
7380
* Create new instance of {@link SignatureValidator}.
7481
*
7582
* @param builder See {@link ValidatorChainBuilder}
7683
*/
77-
SignatureValidator(PdfDocument document, ValidatorChainBuilder builder) {
78-
this.document = document;
84+
SignatureValidator(ValidatorChainBuilder builder) {
7985
this.certificateRetriever = builder.getCertificateRetriever();
8086
this.properties = builder.getProperties();
8187
this.certificateChainValidator = builder.getCertificateChainValidator();
8288
this.baseValidationContext = new ValidationContext(ValidatorContext.SIGNATURE_VALIDATOR,
8389
CertificateSource.SIGNER_CERT, TimeBasedContext.PRESENT);
8490
}
8591

92+
/**
93+
* Validate all signatures in the document
94+
*
95+
* @param document the document to be validated
96+
* @return {@link ValidationReport} which contains detailed validation results
97+
*/
98+
public ValidationReport validateSignatures(PdfDocument document) {
99+
ValidationReport report = new ValidationReport();
100+
SignatureUtil util = new SignatureUtil(document);
101+
List<String> signatureNames = util.getSignatureNames();
102+
Collections.reverse(signatureNames);
103+
104+
for (String fieldName : signatureNames) {
105+
try (PdfDocument doc = new PdfDocument(new PdfReader(util.extractRevision(fieldName)))) {
106+
ValidationReport subReport = validateLatestSignature(doc);
107+
report.merge(subReport);
108+
} catch (IOException e) {
109+
report.addReportItem(new ReportItem(SIGNATURE_VERIFICATION, REVISIONS_RETRIEVAL_FAILED,
110+
e, ReportItemStatus.INDETERMINATE));
111+
}
112+
}
113+
return report;
114+
}
115+
116+
86117
/**
87118
* Validate the latest signature in the document.
88119
*
120+
* @param document the document of which to validate the latest signature
89121
* @return {@link ValidationReport} which contains detailed validation results
90122
*/
91-
public ValidationReport validateLatestSignature() {
123+
public ValidationReport validateLatestSignature(PdfDocument document) {
92124
ValidationReport validationReport = new ValidationReport();
93-
PdfPKCS7 pkcs7 = mathematicallyVerifySignature(validationReport);
125+
PdfPKCS7 pkcs7 = mathematicallyVerifySignature(validationReport, document);
94126
if (stopValidation(validationReport, baseValidationContext)) {
95127
return validationReport;
96128
}
97129

98-
List<Certificate> certificatesFromDss = getCertificatesFromDss(validationReport);
130+
List<Certificate> certificatesFromDss = getCertificatesFromDss(validationReport, document);
99131
certificateRetriever.addKnownCertificates(certificatesFromDss);
100132

101133
if (pkcs7.isTsp()) {
102-
return validateTimestampChain(validationReport, pkcs7.getCertificates(), pkcs7.getSigningCertificate());
134+
return validateTimestampChain(validationReport, pkcs7.getTimeStampTokenInfo(), pkcs7.getCertificates(),
135+
pkcs7.getSigningCertificate());
103136
}
104137

105138
Date signingDate = DateTimeUtil.getCurrentTimeDate();
@@ -117,7 +150,8 @@ public ValidationReport validateLatestSignature() {
117150
return validationReport;
118151
}
119152
Certificate[] timestampCertificates = pkcs7.getTimestampCertificates();
120-
validateTimestampChain(validationReport, timestampCertificates, (X509Certificate) timestampCertificates[0]);
153+
validateTimestampChain(validationReport, pkcs7.getTimeStampTokenInfo(), timestampCertificates,
154+
(X509Certificate) timestampCertificates[0]);
121155
if (stopValidation(validationReport, baseValidationContext)) {
122156
return validationReport;
123157
}
@@ -133,11 +167,14 @@ public ValidationReport validateLatestSignature() {
133167
signingCertificate, signingDate);
134168
}
135169

136-
private PdfPKCS7 mathematicallyVerifySignature(ValidationReport validationReport) {
170+
private PdfPKCS7 mathematicallyVerifySignature(ValidationReport validationReport, PdfDocument document) {
137171
SignatureUtil signatureUtil = new SignatureUtil(document);
138172
List<String> signatures = signatureUtil.getSignatureNames();
139173
String latestSignatureName = signatures.get(signatures.size() - 1);
140174
PdfPKCS7 pkcs7 = signatureUtil.readSignatureData(latestSignatureName);
175+
validationReport.addReportItem(new ReportItem(SIGNATURE_VERIFICATION,
176+
MessageFormatUtil.format(VALIDATING_SIGNATURE_NAME, latestSignatureName), ReportItemStatus.INFO));
177+
141178
if (!signatureUtil.signatureCoversWholeDocument(latestSignatureName)) {
142179
validationReport.addReportItem(new ReportItem(SIGNATURE_VERIFICATION,
143180
MessageFormatUtil.format(DOCUMENT_IS_NOT_COVERED, latestSignatureName), ReportItemStatus.INVALID));
@@ -154,17 +191,32 @@ private PdfPKCS7 mathematicallyVerifySignature(ValidationReport validationReport
154191
return pkcs7;
155192
}
156193

157-
private ValidationReport validateTimestampChain(ValidationReport validationReport, Certificate[] knownCerts,
158-
X509Certificate signingCert) {
194+
private ValidationReport validateTimestampChain(ValidationReport validationReport, ITSTInfo timeStampTokenInfo,
195+
Certificate[] knownCerts, X509Certificate signingCert) {
159196
certificateRetriever.addKnownCertificates(Arrays.asList(knownCerts));
160-
Date signingDate = DateTimeUtil.getCurrentTimeDate();
197+
Date signingDate = lastKnownPoE;
198+
if (signingDate == TimestampConstants.UNDEFINED_TIMESTAMP_DATE) {
199+
signingDate = DateTimeUtil.getCurrentTimeDate();
200+
}
161201

162-
return certificateChainValidator.validate(validationReport,
202+
ValidationReport tsValidationReport = new ValidationReport();
203+
204+
certificateChainValidator.validate(tsValidationReport,
163205
baseValidationContext.setCertificateSource(CertificateSource.TIMESTAMP),
164206
signingCert, signingDate);
207+
validationReport.merge(tsValidationReport);
208+
if (tsValidationReport.getValidationResult() == ValidationReport.ValidationResult.VALID) {
209+
try {
210+
lastKnownPoE = timeStampTokenInfo.getGenTime();
211+
} catch (Exception e) {
212+
validationReport.addReportItem(new ReportItem(TIMESTAMP_VERIFICATION, TIMESTAMP_EXTRACTION_FAILED, e,
213+
ReportItemStatus.INDETERMINATE));
214+
}
215+
}
216+
return validationReport;
165217
}
166218

167-
private List<Certificate> getCertificatesFromDss(ValidationReport validationReport) {
219+
private List<Certificate> getCertificatesFromDss(ValidationReport validationReport, PdfDocument document) {
168220
PdfDictionary dss = document.getCatalog().getPdfObject().getAsDictionary(PdfName.DSS);
169221
List<Certificate> certificatesFromDss = new ArrayList<>();
170222
if (dss != null) {
@@ -190,3 +242,4 @@ private boolean stopValidation(ValidationReport result, ValidationContext valida
190242
&& result.getValidationResult() != ValidationReport.ValidationResult.VALID;
191243
}
192244
}
245+

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ This file is part of the iText (R) project.
2222
*/
2323
package com.itextpdf.signatures.validation.v1;
2424

25-
import com.itextpdf.kernel.pdf.PdfDocument;
2625
import com.itextpdf.signatures.IssuingCertificateRetriever;
2726

2827
import java.security.cert.Certificate;
@@ -45,12 +44,10 @@ public class ValidatorChainBuilder {
4544
* Create a new {@link SignatureValidator} instance with the current configuration.
4645
* This method can be used to create multiple validators.
4746
*
48-
* @param document The {@link PdfDocument} to create the signatureValidator for.
49-
*
5047
* @return a new instance of a signature validator
5148
*/
52-
SignatureValidator buildSignatureValidator(PdfDocument document) {
53-
return new SignatureValidator(document, this);
49+
SignatureValidator buildSignatureValidator() {
50+
return new SignatureValidator(this);
5451
}
5552

5653
/**

sign/src/main/java/com/itextpdf/signatures/validation/v1/report/ValidationReport.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ public String toString() {
117117
return sb.toString();
118118
}
119119

120+
public void merge(ValidationReport subReport) {
121+
for (ReportItem item : subReport.getLogs()) {
122+
addReportItem(item);
123+
}
124+
}
125+
120126
/**
121127
* Enum representing possible validation results.
122128
*/

sign/src/test/java/com/itextpdf/signatures/validation/v1/SignatureValidatorIntegrationTest.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ public void validLatestSignatureTest() throws GeneralSecurityException, IOExcept
9494
certificateRetriever.setTrustedCertificates(Collections.singletonList(rootCert));
9595
addRevDataClients();
9696

97-
SignatureValidator signatureValidator = builder.buildSignatureValidator(document);
98-
report = signatureValidator.validateLatestSignature();
97+
SignatureValidator signatureValidator = builder.buildSignatureValidator();
98+
report = signatureValidator.validateLatestSignature(document);
9999
}
100100

101101
AssertValidationReport.assertThat(report, a -> a
@@ -133,13 +133,13 @@ public void latestSignatureIsTimestampTest() throws GeneralSecurityException, IO
133133
.setFreshness(ValidatorContexts.all(), CertificateSources.all(), TimeBasedContexts.all(),
134134
Duration.ofDays(-2));
135135

136-
SignatureValidator signatureValidator = builder.buildSignatureValidator(document);
137-
report = signatureValidator.validateLatestSignature();
136+
SignatureValidator signatureValidator = builder.buildSignatureValidator();
137+
report = signatureValidator.validateLatestSignature(document);
138138
}
139139

140140
AssertValidationReport.assertThat(report, a -> a
141141
.hasNumberOfFailures(0)
142-
.hasNumberOfLogs(2)
142+
.hasNumberOfLogs(3)
143143
.hasLogItems(2, 2, la -> la
144144
.withCheckName(CertificateChainValidator.CERTIFICATE_CHECK)
145145
.withMessage(CertificateChainValidator.CERTIFICATE_TRUSTED,
@@ -157,14 +157,14 @@ public void certificatesNotInLatestSignatureTest() throws GeneralSecurityExcepti
157157

158158
ValidationReport report;
159159
try (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "validDocWithoutChain.pdf"))) {
160-
SignatureValidator signatureValidator = builder.buildSignatureValidator(document);
160+
SignatureValidator signatureValidator = builder.buildSignatureValidator();
161161
certificateRetriever.setTrustedCertificates(Collections.singletonList(rootCert));
162162
parameters.setRevocationOnlineFetching(ValidatorContexts.all(), CertificateSources.all(),
163163
TimeBasedContexts.all(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH)
164164
.setFreshness(ValidatorContexts.all(), CertificateSources.all(), TimeBasedContexts.all(),
165165
Duration.ofDays(-2));
166166

167-
report = signatureValidator.validateLatestSignature();
167+
report = signatureValidator.validateLatestSignature(document);
168168
}
169169

170170
AssertValidationReport.assertThat(report, a -> a
@@ -197,8 +197,8 @@ public void certificatesNotInLatestSignatureButSetAsKnownTest() throws GeneralSe
197197
certificateRetriever.addKnownCertificates(Collections.singletonList(intermediateCert));
198198
addRevDataClients();
199199

200-
SignatureValidator signatureValidator = builder.buildSignatureValidator(document);
201-
report = signatureValidator.validateLatestSignature();
200+
SignatureValidator signatureValidator = builder.buildSignatureValidator();
201+
report = signatureValidator.validateLatestSignature(document);
202202
}
203203
AssertValidationReport.assertThat(report, a -> a
204204
.hasStatus(ValidationResult.VALID)
@@ -218,13 +218,13 @@ public void rootIsNotTrustedInLatestSignatureTest() throws GeneralSecurityExcept
218218

219219
ValidationReport report;
220220
try (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "validDoc.pdf"))) {
221-
SignatureValidator signatureValidator = builder.buildSignatureValidator(document);
221+
SignatureValidator signatureValidator = builder.buildSignatureValidator();
222222
parameters.setRevocationOnlineFetching(ValidatorContexts.all(), CertificateSources.all(),
223223
TimeBasedContexts.all(), SignatureValidationProperties.OnlineFetching.NEVER_FETCH)
224224
.setFreshness(ValidatorContexts.all(), CertificateSources.all(), TimeBasedContexts.all(),
225225
Duration.ofDays(-2));
226226

227-
report = signatureValidator.validateLatestSignature();
227+
report = signatureValidator.validateLatestSignature(document);
228228
}
229229

230230
AssertValidationReport.assertThat(report, a -> a

0 commit comments

Comments
 (0)