Skip to content

Commit b6139dd

Browse files
committed
Support revocation data retrieval from the signature container
DEVSIX-8388
1 parent 174e3e0 commit b6139dd

File tree

6 files changed

+239
-58
lines changed

6 files changed

+239
-58
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,7 @@ private void validateRequiredExtensions(ValidationReport result, ValidationConte
249249

250250
private void validateRevocationData(ValidationReport report, ValidationContext context, X509Certificate certificate,
251251
Date validationDate) {
252-
revocationDataValidator.validate(report,
253-
context, certificate, validationDate);
252+
revocationDataValidator.validate(report, context, certificate, validationDate);
254253
}
255254

256255
private void validateChain(ValidationReport result, ValidationContext context, X509Certificate certificate,

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

Lines changed: 104 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ This file is part of the iText (R) project.
2525
import com.itextpdf.commons.actions.contexts.IMetaInfo;
2626
import com.itextpdf.bouncycastleconnector.BouncyCastleFactoryCreator;
2727
import com.itextpdf.commons.bouncycastle.IBouncyCastleFactory;
28+
import com.itextpdf.commons.bouncycastle.asn1.ocsp.IBasicOCSPResponse;
2829
import com.itextpdf.commons.bouncycastle.asn1.tsp.ITSTInfo;
2930
import com.itextpdf.commons.bouncycastle.cert.ocsp.AbstractOCSPException;
3031
import com.itextpdf.commons.utils.DateTimeUtil;
@@ -52,6 +53,7 @@ This file is part of the iText (R) project.
5253
import java.io.ByteArrayInputStream;
5354
import java.io.IOException;
5455
import java.security.GeneralSecurityException;
56+
import java.security.cert.CRL;
5557
import java.security.cert.Certificate;
5658
import java.security.cert.X509CRL;
5759
import java.security.cert.X509Certificate;
@@ -156,9 +158,10 @@ public ValidationReport validateSignatures(PdfDocument document) {
156158

157159
ValidationReport validateLatestSignature(PdfDocument document) {
158160
ValidationReport validationReport = new ValidationReport();
159-
updateValidationOcspClient(validationReport, validationContext, document);
160-
updateValidationCrlClient(validationReport, validationContext, document);
161161
PdfPKCS7 pkcs7 = mathematicallyVerifySignature(validationReport, document);
162+
updateValidationClients(pkcs7, validationReport, validationContext, document);
163+
// We only retrieve not signed revocation data at the very beginning of signature processing.
164+
retrieveNotSignedRevocationInfoFromSignatureContainer(pkcs7, validationContext);
162165
if (stopValidation(validationReport, validationContext)) {
163166
return validationReport;
164167
}
@@ -167,66 +170,44 @@ ValidationReport validateLatestSignature(PdfDocument document) {
167170
certificateRetriever.addKnownCertificates(certificatesFromDss);
168171

169172
if (pkcs7.isTsp()) {
170-
validateTimestampChain(validationReport, pkcs7.getTimeStampTokenInfo(), pkcs7.getCertificates(),
171-
pkcs7.getSigningCertificate());
172-
updateValidationOcspClient(validationReport, validationContext, document);
173-
updateValidationCrlClient(validationReport, validationContext, document);
173+
validateTimestampChain(validationReport, pkcs7.getCertificates(), pkcs7.getSigningCertificate());
174+
if (updateLastKnownPoE(validationReport, pkcs7.getTimeStampTokenInfo())) {
175+
updateValidationClients(pkcs7, validationReport, validationContext, document);
176+
}
174177
return validationReport;
175178
}
176179

180+
boolean isPoEUpdated = false;
177181
Date previousLastKnowPoE = lastKnownPoE;
178182
ValidationContext previousValidationContext = validationContext;
179183
if (pkcs7.getTimeStampTokenInfo() != null) {
180-
try {
181-
if (!pkcs7.verifyTimestampImprint()) {
182-
validationReport.addReportItem(new ReportItem(TIMESTAMP_VERIFICATION, CANNOT_VERIFY_TIMESTAMP,
183-
ReportItemStatus.INVALID));
184-
}
185-
} catch (GeneralSecurityException e) {
186-
validationReport.addReportItem(new ReportItem(TIMESTAMP_VERIFICATION, CANNOT_VERIFY_TIMESTAMP, e,
187-
ReportItemStatus.INVALID));
188-
}
189-
if (stopValidation(validationReport, validationContext)) {
190-
return validationReport;
191-
}
192-
193-
PdfPKCS7 timestampSignatureContainer = pkcs7.getTimestampSignatureContainer();
194-
try {
195-
if (!timestampSignatureContainer.verifySignatureIntegrityAndAuthenticity()) {
196-
validationReport.addReportItem(new ReportItem(TIMESTAMP_VERIFICATION,
197-
CANNOT_VERIFY_TIMESTAMP, ReportItemStatus.INVALID));
198-
}
199-
} catch (GeneralSecurityException e) {
200-
validationReport.addReportItem(new ReportItem(TIMESTAMP_VERIFICATION,
201-
CANNOT_VERIFY_TIMESTAMP, e, ReportItemStatus.INVALID));
202-
}
203-
if (stopValidation(validationReport, validationContext)) {
204-
return validationReport;
184+
ValidationReport tsValidationReport = validateEmbeddedTimestamp(pkcs7);
185+
isPoEUpdated = updateLastKnownPoE(tsValidationReport, pkcs7.getTimeStampTokenInfo());
186+
if (isPoEUpdated) {
187+
PdfPKCS7 timestampSignatureContainer = pkcs7.getTimestampSignatureContainer();
188+
retrieveSignedRevocationInfoFromSignatureContainer(timestampSignatureContainer, validationContext);
189+
updateValidationClients(pkcs7, tsValidationReport, validationContext, document);
205190
}
206-
207-
Certificate[] timestampCertificates = timestampSignatureContainer.getCertificates();
208-
validateTimestampChain(validationReport, pkcs7.getTimeStampTokenInfo(), timestampCertificates,
209-
timestampSignatureContainer.getSigningCertificate());
210-
if (stopValidation(validationReport, validationContext)) {
191+
validationReport.merge(tsValidationReport);
192+
if (stopValidation(tsValidationReport, validationContext)) {
211193
return validationReport;
212194
}
213195
}
214-
updateValidationOcspClient(validationReport, validationContext, document);
215-
updateValidationCrlClient(validationReport, validationContext, document);
216196

217197
Certificate[] certificates = pkcs7.getCertificates();
218198
certificateRetriever.addKnownCertificates(Arrays.asList(certificates));
219199
X509Certificate signingCertificate = pkcs7.getSigningCertificate();
220200

221201
ValidationReport signatureReport = new ValidationReport();
222202
certificateChainValidator.validate(signatureReport, validationContext, signingCertificate, lastKnownPoE);
223-
if (signatureReport.getValidationResult() != ValidationResult.VALID) {
203+
if (isPoEUpdated && signatureReport.getValidationResult() != ValidationResult.VALID) {
224204
// We can only use PoE retrieved from timestamp attribute in case main signature validation is successful.
225-
// That's why if the result is not valid, we set back lastKnownPoE value, validation context and DSS.
205+
// That's why if the result is not valid, we set back lastKnownPoE value, validation context and rev data.
226206
lastKnownPoE = previousLastKnowPoE;
227207
validationContext = previousValidationContext;
228-
updateValidationOcspClient(validationReport, validationContext, document);
229-
updateValidationCrlClient(validationReport, validationContext, document);
208+
PdfPKCS7 timestampSignatureContainer = pkcs7.getTimestampSignatureContainer();
209+
retrieveSignedRevocationInfoFromSignatureContainer(timestampSignatureContainer, validationContext);
210+
updateValidationClients(pkcs7, validationReport, validationContext, document);
230211
}
231212
return validationReport.merge(signatureReport);
232213
}
@@ -255,32 +236,100 @@ private PdfPKCS7 mathematicallyVerifySignature(ValidationReport validationReport
255236
return pkcs7;
256237
}
257238

258-
private ValidationReport validateTimestampChain(ValidationReport validationReport, ITSTInfo timeStampTokenInfo,
259-
Certificate[] knownCerts, X509Certificate signingCert) {
260-
certificateRetriever.addKnownCertificates(Arrays.asList(knownCerts));
261-
239+
private ValidationReport validateEmbeddedTimestamp(PdfPKCS7 pkcs7) {
262240
ValidationReport tsValidationReport = new ValidationReport();
241+
try {
242+
if (!pkcs7.verifyTimestampImprint()) {
243+
tsValidationReport.addReportItem(new ReportItem(TIMESTAMP_VERIFICATION, CANNOT_VERIFY_TIMESTAMP,
244+
ReportItemStatus.INVALID));
245+
}
246+
} catch (GeneralSecurityException e) {
247+
tsValidationReport.addReportItem(new ReportItem(TIMESTAMP_VERIFICATION, CANNOT_VERIFY_TIMESTAMP, e,
248+
ReportItemStatus.INVALID));
249+
}
250+
if (stopValidation(tsValidationReport, validationContext)) {
251+
return tsValidationReport;
252+
}
263253

264-
certificateChainValidator.validate(tsValidationReport,
254+
PdfPKCS7 timestampSignatureContainer = pkcs7.getTimestampSignatureContainer();
255+
retrieveSignedRevocationInfoFromSignatureContainer(timestampSignatureContainer, validationContext);
256+
try {
257+
if (!timestampSignatureContainer.verifySignatureIntegrityAndAuthenticity()) {
258+
tsValidationReport.addReportItem(new ReportItem(TIMESTAMP_VERIFICATION,
259+
CANNOT_VERIFY_TIMESTAMP, ReportItemStatus.INVALID));
260+
}
261+
} catch (GeneralSecurityException e) {
262+
tsValidationReport.addReportItem(new ReportItem(TIMESTAMP_VERIFICATION,
263+
CANNOT_VERIFY_TIMESTAMP, e, ReportItemStatus.INVALID));
264+
}
265+
if (stopValidation(tsValidationReport, validationContext)) {
266+
return tsValidationReport;
267+
}
268+
269+
Certificate[] timestampCertificates = timestampSignatureContainer.getCertificates();
270+
validateTimestampChain(tsValidationReport, timestampCertificates,
271+
timestampSignatureContainer.getSigningCertificate());
272+
return tsValidationReport;
273+
}
274+
275+
private void validateTimestampChain(ValidationReport validationReport, Certificate[] knownCerts,
276+
X509Certificate signingCert) {
277+
certificateRetriever.addKnownCertificates(Arrays.asList(knownCerts));
278+
279+
certificateChainValidator.validate(validationReport,
265280
validationContext.setCertificateSource(CertificateSource.TIMESTAMP),
266281
signingCert, lastKnownPoE);
267-
validationReport.merge(tsValidationReport);
268-
if (tsValidationReport.getValidationResult() == ValidationReport.ValidationResult.VALID) {
282+
}
283+
284+
private boolean updateLastKnownPoE(ValidationReport tsValidationReport, ITSTInfo timeStampTokenInfo) {
285+
if (tsValidationReport.getValidationResult() == ValidationResult.VALID) {
269286
try {
270287
lastKnownPoE = timeStampTokenInfo.getGenTime();
271288
if (validationContext.getTimeBasedContext() == TimeBasedContext.PRESENT) {
272289
validationContext = validationContext.setTimeBasedContext(TimeBasedContext.HISTORICAL);
273290
}
291+
return true;
274292
} catch (Exception e) {
275-
validationReport.addReportItem(new ReportItem(TIMESTAMP_VERIFICATION, TIMESTAMP_EXTRACTION_FAILED, e,
293+
tsValidationReport.addReportItem(new ReportItem(TIMESTAMP_VERIFICATION, TIMESTAMP_EXTRACTION_FAILED, e,
276294
ReportItemStatus.INDETERMINATE));
277295
}
278296
}
279-
return validationReport;
297+
return false;
280298
}
281299

282-
private void updateValidationOcspClient(ValidationReport validationReport, ValidationContext context,
283-
PdfDocument document) {
300+
private void updateValidationClients(PdfPKCS7 pkcs7, ValidationReport validationReport,
301+
ValidationContext validationContext, PdfDocument document) {
302+
retrieveOcspResponsesFromDss(validationReport, validationContext, document);
303+
retrieveCrlResponsesFromDss(validationReport, validationContext, document);
304+
retrieveSignedRevocationInfoFromSignatureContainer(pkcs7, validationContext);
305+
}
306+
307+
private void retrieveSignedRevocationInfoFromSignatureContainer(PdfPKCS7 pkcs7,
308+
ValidationContext validationContext) {
309+
if (pkcs7.getCRLs() != null) {
310+
for (CRL crl : pkcs7.getCRLs()) {
311+
validationCrlClient.addCrl((X509CRL) crl, lastKnownPoE, validationContext.getTimeBasedContext());
312+
}
313+
}
314+
if (pkcs7.getOcsp() != null) {
315+
validationOcspClient.addResponse(BOUNCY_CASTLE_FACTORY.createBasicOCSPResp(pkcs7.getOcsp()), lastKnownPoE,
316+
validationContext.getTimeBasedContext());
317+
}
318+
}
319+
320+
private void retrieveNotSignedRevocationInfoFromSignatureContainer(PdfPKCS7 pkcs7,
321+
ValidationContext validationContext) {
322+
for (CRL crl : pkcs7.getSignedDataCRLs()) {
323+
validationCrlClient.addCrl((X509CRL) crl, lastKnownPoE, validationContext.getTimeBasedContext());
324+
}
325+
for (IBasicOCSPResponse oscp : pkcs7.getSignedDataOcsps()) {
326+
validationOcspClient.addResponse(BOUNCY_CASTLE_FACTORY.createBasicOCSPResp(oscp), lastKnownPoE,
327+
validationContext.getTimeBasedContext());
328+
}
329+
}
330+
331+
private void retrieveOcspResponsesFromDss(ValidationReport validationReport, ValidationContext context,
332+
PdfDocument document) {
284333
PdfDictionary dss = document.getCatalog().getPdfObject().getAsDictionary(PdfName.DSS);
285334
if (dss != null) {
286335
PdfArray ocsps = dss.getAsArray(PdfName.OCSPs);
@@ -300,8 +349,8 @@ private void updateValidationOcspClient(ValidationReport validationReport, Valid
300349
}
301350
}
302351

303-
private void updateValidationCrlClient(ValidationReport validationReport, ValidationContext context,
304-
PdfDocument document) {
352+
private void retrieveCrlResponsesFromDss(ValidationReport validationReport, ValidationContext context,
353+
PdfDocument document) {
305354
PdfDictionary dss = document.getCatalog().getPdfObject().getAsDictionary(PdfName.DSS);
306355
if (dss != null) {
307356
PdfArray crls = dss.getAsArray(PdfName.CRLs);
@@ -346,4 +395,3 @@ private boolean stopValidation(ValidationReport result, ValidationContext valida
346395
&& result.getValidationResult() == ValidationResult.INVALID;
347396
}
348397
}
349-

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

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,87 @@ public void shortValidityCertsWithCrlTest() throws GeneralSecurityException, IOE
195195
);
196196
}
197197

198+
@Test
199+
public void retrieveRevocationDataFromTheSignatureContainerTest() throws GeneralSecurityException, IOException {
200+
String rootCertName = CERTS_SRC + "rootRsa.pem";
201+
X509Certificate rootCert = (X509Certificate) PemFileHelper.readFirstChain(rootCertName)[0];
202+
203+
// We need to set infinite freshness for the signature validation. Otherwise, test will fail.
204+
builder.getProperties().setFreshness(
205+
ValidatorContexts.of(ValidatorContext.OCSP_VALIDATOR, ValidatorContext.CRL_VALIDATOR),
206+
CertificateSources.of(CertificateSource.SIGNER_CERT),
207+
TimeBasedContexts.of(TimeBasedContext.PRESENT), Duration.ofDays(999999));
208+
209+
ValidationReport report;
210+
// Signature container stores OCSP response with indeterminate status and less fresh but valid CRL response.
211+
try (PdfDocument document = new PdfDocument(
212+
new PdfReader(SOURCE_FOLDER + "revDataInTheSignatureContainer.pdf"))) {
213+
certificateRetriever.setTrustedCertificates(Collections.singletonList(rootCert));
214+
215+
SignatureValidator signatureValidator = builder.buildSignatureValidator();
216+
report = signatureValidator.validateSignatures(document);
217+
}
218+
219+
AssertValidationReport.assertThat(report, a -> a
220+
.hasStatus(ValidationResult.VALID)
221+
.hasNumberOfLogs(4).hasNumberOfFailures(0)
222+
.hasLogItem(al -> al
223+
.withCheckName(SignatureValidator.SIGNATURE_VERIFICATION)
224+
.withMessage(SignatureValidator.VALIDATING_SIGNATURE_NAME, i -> "Signature1"))
225+
.hasLogItem(al -> al
226+
.withCheckName(OCSPValidator.OCSP_CHECK)
227+
.withMessage(OCSPValidator.CERT_STATUS_IS_UNKNOWN)
228+
.withStatus(ReportItem.ReportItemStatus.INFO))
229+
.hasLogItems(2, al -> al
230+
.withCertificate(rootCert)
231+
.withCheckName(CertificateChainValidator.CERTIFICATE_CHECK)
232+
.withMessage(CertificateChainValidator.CERTIFICATE_TRUSTED,
233+
i -> rootCert.getSubjectX500Principal()))
234+
);
235+
}
236+
237+
@Test
238+
public void retrieveRevocationDataStoredInTheSignerInfoTest() throws GeneralSecurityException, IOException {
239+
String rootCertName = CERTS_SRC + "rootRsa.pem";
240+
X509Certificate rootCert = (X509Certificate) PemFileHelper.readFirstChain(rootCertName)[0];
241+
242+
// We need to set infinite freshness for the embedded timestamp validation. Otherwise, test will fail.
243+
builder.getProperties().setFreshness(
244+
ValidatorContexts.of(ValidatorContext.OCSP_VALIDATOR, ValidatorContext.CRL_VALIDATOR),
245+
CertificateSources.of(CertificateSource.TIMESTAMP),
246+
TimeBasedContexts.of(TimeBasedContext.PRESENT), Duration.ofDays(999999))
247+
.setFreshness(
248+
ValidatorContexts.of(ValidatorContext.CRL_VALIDATOR),
249+
CertificateSources.of(CertificateSource.SIGNER_CERT),
250+
TimeBasedContexts.of(TimeBasedContext.HISTORICAL), Duration.ofDays(2));
251+
252+
ValidationReport report;
253+
// Signer info authenticated attributes store OCSP response with indeterminate status and valid CRL response.
254+
try (PdfDocument document = new PdfDocument(new PdfReader(SOURCE_FOLDER + "revDataInTheSignerInfo.pdf"))) {
255+
certificateRetriever.setTrustedCertificates(Collections.singletonList(rootCert));
256+
257+
SignatureValidator signatureValidator = builder.buildSignatureValidator();
258+
report = signatureValidator.validateSignatures(document);
259+
}
260+
261+
AssertValidationReport.assertThat(report, a -> a
262+
.hasStatus(ValidationResult.VALID)
263+
.hasNumberOfLogs(6).hasNumberOfFailures(0)
264+
.hasLogItem(al -> al
265+
.withCheckName(SignatureValidator.SIGNATURE_VERIFICATION)
266+
.withMessage(SignatureValidator.VALIDATING_SIGNATURE_NAME, i -> "Signature1"))
267+
.hasLogItem(al -> al
268+
.withCheckName(OCSPValidator.OCSP_CHECK)
269+
.withMessage(OCSPValidator.CERT_STATUS_IS_UNKNOWN)
270+
.withStatus(ReportItem.ReportItemStatus.INFO))
271+
.hasLogItems(4, al -> al
272+
.withCertificate(rootCert)
273+
.withCheckName(CertificateChainValidator.CERTIFICATE_CHECK)
274+
.withMessage(CertificateChainValidator.CERTIFICATE_TRUSTED,
275+
i -> rootCert.getSubjectX500Principal()))
276+
);
277+
}
278+
198279
@Test
199280
public void latestSignatureIsTimestampTest() throws GeneralSecurityException, IOException,
200281
AbstractOperatorCreationException, AbstractPKCSException {

0 commit comments

Comments
 (0)