Skip to content

Commit 556d6f1

Browse files
author
Eugene Bochilo
committed
Support various extension related to revocation data collection
DEVSIX-7927
1 parent 0ce2116 commit 556d6f1

18 files changed

+600
-38
lines changed

sign/src/main/java/com/itextpdf/signatures/LtvVerification.java

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ This file is part of the iText (R) project.
3030
import com.itextpdf.commons.bouncycastle.asn1.ocsp.IOCSPResponse;
3131
import com.itextpdf.commons.bouncycastle.asn1.ocsp.IOCSPResponseStatus;
3232
import com.itextpdf.commons.bouncycastle.asn1.ocsp.IResponseBytes;
33+
import com.itextpdf.commons.bouncycastle.cert.ocsp.AbstractOCSPException;
34+
import com.itextpdf.commons.bouncycastle.cert.ocsp.IBasicOCSPResp;
35+
import com.itextpdf.commons.bouncycastle.operator.AbstractOperatorCreationException;
3336
import com.itextpdf.commons.utils.MessageFormatUtil;
3437
import com.itextpdf.io.font.PdfEncodings;
3538
import com.itextpdf.io.source.ByteBuffer;
@@ -46,7 +49,9 @@ This file is part of the iText (R) project.
4649
import com.itextpdf.kernel.pdf.PdfStream;
4750
import com.itextpdf.kernel.pdf.PdfString;
4851
import com.itextpdf.kernel.pdf.PdfVersion;
52+
import com.itextpdf.signatures.OID.X509Extensions;
4953
import com.itextpdf.signatures.exceptions.SignExceptionMessageConstant;
54+
import com.itextpdf.signatures.logs.SignLogMessageConstant;
5055

5156
import java.io.ByteArrayInputStream;
5257
import java.io.IOException;
@@ -249,6 +254,11 @@ public boolean addVerification(String signatureName, IOcspClient ocsp, ICrlClien
249254
addRevocationDataForChain(signingCert, timestampCertsChain, ocsp, crl, level, certInclude, certOption,
250255
validationData, processedCerts);
251256
}
257+
if (certInclude == CertificateInclusion.YES) {
258+
for (X509Certificate processedCert : processedCerts) {
259+
validationData.certs.add(processedCert.getEncoded());
260+
}
261+
}
252262

253263
if (validationData.crls.size() == 0 && validationData.ocsps.size() == 0) {
254264
return false;
@@ -372,6 +382,12 @@ private void addRevocationDataForCertificate(X509Certificate signingCert, Certif
372382
CertificateOption certOption, ValidationData validationData, Set<X509Certificate> processedCerts)
373383
throws IOException, CertificateException, CRLException {
374384
processedCerts.add(cert);
385+
byte[] validityAssured = SignUtils.getExtensionValueByOid(cert, X509Extensions.VALIDITY_ASSURED_SHORT_TERM);
386+
if (validityAssured != null) {
387+
LOGGER.info(MessageFormatUtil.format(SignLogMessageConstant.REVOCATION_DATA_NOT_ADDED_VALIDITY_ASSURED,
388+
cert.getSubjectX500Principal()));
389+
return;
390+
}
375391
byte[] ocspEnc = null;
376392
boolean revocationDataAdded = false;
377393
if (ocsp != null && level != Level.CRL) {
@@ -381,12 +397,8 @@ private void addRevocationDataForCertificate(X509Certificate signingCert, Certif
381397
revocationDataAdded = true;
382398
LOGGER.info("OCSP added");
383399
if (certOption == CertificateOption.ALL_CERTIFICATES) {
384-
Iterable<X509Certificate> certs = SignUtils.getCertsFromOcspResponse(
385-
BOUNCY_CASTLE_FACTORY.createBasicOCSPResp(
386-
BOUNCY_CASTLE_FACTORY.createBasicOCSPResponse(ocspEnc)));
387-
List<X509Certificate> certsList = iterableToList(certs);
388-
addRevocationDataForChain(signingCert, certsList.toArray(new X509Certificate[0]),
389-
ocsp, crl, level, certInclude, certOption, validationData, processedCerts);
400+
addRevocationDataForOcspCert(ocspEnc, signingCert, ocsp, crl, level, certInclude, certOption,
401+
validationData, processedCerts);
390402
}
391403
}
392404
}
@@ -421,9 +433,35 @@ private void addRevocationDataForCertificate(X509Certificate signingCert, Certif
421433
signingCert.equals(cert) && !revocationDataAdded) {
422434
throw new PdfException(SignExceptionMessageConstant.NO_REVOCATION_DATA_FOR_SIGNING_CERTIFICATE);
423435
}
424-
if (certInclude == CertificateInclusion.YES) {
425-
validationData.certs.add(cert.getEncoded());
436+
}
437+
438+
private void addRevocationDataForOcspCert(byte[] ocspEnc, X509Certificate signingCert, IOcspClient ocsp,
439+
ICrlClient crl, Level level, CertificateInclusion certInclude, CertificateOption certOption,
440+
ValidationData validationData, Set<X509Certificate> processedCerts)
441+
throws CertificateException, IOException, CRLException {
442+
IBasicOCSPResp ocspResp = BOUNCY_CASTLE_FACTORY.createBasicOCSPResp(
443+
BOUNCY_CASTLE_FACTORY.createBasicOCSPResponse(ocspEnc));
444+
Iterable<X509Certificate> certs = SignUtils.getCertsFromOcspResponse(ocspResp);
445+
List<X509Certificate> ocspCertsList = iterableToList(certs);
446+
X509Certificate ocspSigningCert = null;
447+
for (X509Certificate ocspCert : ocspCertsList) {
448+
try {
449+
if (SignUtils.isSignatureValid(ocspResp, ocspCert, BOUNCY_CASTLE_FACTORY.getProviderName())) {
450+
ocspSigningCert = ocspCert;
451+
break;
452+
}
453+
} catch (AbstractOperatorCreationException | AbstractOCSPException ignored) {
454+
// Wasn't possible to check if this cert is signing one, skip.
455+
}
456+
}
457+
if (ocspSigningCert != null && SignUtils.getExtensionValueByOid(
458+
ocspSigningCert, X509Extensions.ID_PKIX_OCSP_NOCHECK) != null) {
459+
// If ocsp_no_check extension is set on OCSP signing cert we shan't collect revocation data for this cert.
460+
ocspCertsList.remove(ocspSigningCert);
461+
processedCerts.add(ocspSigningCert);
426462
}
463+
addRevocationDataForChain(signingCert, ocspCertsList.toArray(new X509Certificate[0]),
464+
ocsp, crl, level, certInclude, certOption, validationData, processedCerts);
427465
}
428466

429467
private static List<X509Certificate> iterableToList(Iterable<X509Certificate> iterable) {

sign/src/main/java/com/itextpdf/signatures/OCSPVerifier.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,8 @@ && isSignatureValid(ocspResp, cert)) {
241241
// validating ocsp signers certificate
242242
// Check if responders certificate has id-pkix-ocsp-nocheck extension,
243243
// in which case we do not validate (perform revocation check on) ocsp certs for lifetime of certificate
244-
if (responderCert.getExtensionValue(
245-
BOUNCY_CASTLE_FACTORY.createOCSPObjectIdentifiers().getIdPkixOcspNoCheck().getId()) == null) {
244+
if (SignUtils.getExtensionValueByOid(responderCert, BOUNCY_CASTLE_FACTORY.createOCSPObjectIdentifiers()
245+
.getIdPkixOcspNoCheck().getId()) == null) {
246246
CRL crl;
247247
try {
248248
// TODO DEVSIX-5210 Implement a check heck for Authority Information Access according to

sign/src/main/java/com/itextpdf/signatures/OID.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ public static final class X509Extensions {
135135
*/
136136
public static final String ID_PKIX_OCSP_NOCHECK = "1.3.6.1.5.5.7.48.1.5";
137137

138+
/**
139+
* Extension for certificates from ETSI EN 319 412-1 V1.4.4.
140+
*/
141+
public static final String VALIDITY_ASSURED_SHORT_TERM = "0.4.0.194121.2.1";
142+
138143

139144
/**
140145
* According to https://tools.ietf.org/html/rfc5280 4.2. "Certificate Extensions":

sign/src/main/java/com/itextpdf/signatures/PdfPadesSigner.java

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,7 @@ public PdfPadesSigner(PdfReader reader, OutputStream outputStream) {
101101
*/
102102
public void signWithBaselineBProfile(SignerProperties signerProperties, Certificate[] chain,
103103
IExternalSignature externalSignature) throws GeneralSecurityException, IOException {
104-
Certificate[] fullChain = issuingCertificateRetriever.retrieveMissingCertificates(chain);
105-
performSignDetached(signerProperties, true, externalSignature, fullChain, null);
104+
performSignDetached(signerProperties, true, externalSignature, chain, null);
106105
}
107106

108107
/**
@@ -135,8 +134,7 @@ public void signWithBaselineBProfile(SignerProperties signerProperties, Certific
135134
*/
136135
public void signWithBaselineTProfile(SignerProperties signerProperties, Certificate[] chain,
137136
IExternalSignature externalSignature, ITSAClient tsaClient) throws GeneralSecurityException, IOException {
138-
Certificate[] fullChain = issuingCertificateRetriever.retrieveMissingCertificates(chain);
139-
performSignDetached(signerProperties, true, externalSignature, fullChain, tsaClient);
137+
performSignDetached(signerProperties, true, externalSignature, chain, tsaClient);
140138
}
141139

142140
/**
@@ -170,10 +168,9 @@ public void signWithBaselineTProfile(SignerProperties signerProperties, Certific
170168
*/
171169
public void signWithBaselineLTProfile(SignerProperties signerProperties, Certificate[] chain,
172170
IExternalSignature externalSignature, ITSAClient tsaClient) throws GeneralSecurityException, IOException {
173-
Certificate[] fullChain = issuingCertificateRetriever.retrieveMissingCertificates(chain);
174-
createRevocationClients(fullChain, true);
171+
createRevocationClients(chain[0], true);
175172
try {
176-
performSignDetached(signerProperties, false, externalSignature, fullChain, tsaClient);
173+
performSignDetached(signerProperties, false, externalSignature, chain, tsaClient);
177174
try (InputStream inputStream = createInputStream();
178175
PdfDocument pdfDocument = new PdfDocument(new PdfReader(inputStream),
179176
new PdfWriter(outputStream), new StampingProperties().useAppendMode())) {
@@ -216,10 +213,9 @@ public void signWithBaselineLTProfile(SignerProperties signerProperties, Certifi
216213
*/
217214
public void signWithBaselineLTAProfile(SignerProperties signerProperties, Certificate[] chain,
218215
IExternalSignature externalSignature, ITSAClient tsaClient) throws IOException, GeneralSecurityException {
219-
Certificate[] fullChain = issuingCertificateRetriever.retrieveMissingCertificates(chain);
220-
createRevocationClients(fullChain, true);
216+
createRevocationClients(chain[0], true);
221217
try {
222-
performSignDetached(signerProperties, false, externalSignature, fullChain, tsaClient);
218+
performSignDetached(signerProperties, false, externalSignature, chain, tsaClient);
223219
try (InputStream inputStream = createInputStream();
224220
PdfDocument pdfDocument = new PdfDocument(new PdfReader(inputStream),
225221
new PdfWriter(createOutputStream()), new StampingProperties().useAppendMode())) {
@@ -269,7 +265,7 @@ public void prolongSignatures(ITSAClient tsaClient)
269265
if (signatureNames.isEmpty()) {
270266
throw new PdfException(SignExceptionMessageConstant.NO_SIGNATURES_TO_PROLONG);
271267
}
272-
createRevocationClients(new Certificate[0], false);
268+
createRevocationClients(null, false);
273269
performLtvVerification(pdfDocument, signatureNames, LtvVerification.RevocationDataNecessity.OPTIONAL);
274270
if (tsaClient != null) {
275271
performTimestamping(pdfDocument, outputStream, tsaClient);
@@ -419,9 +415,10 @@ private void performTimestamping(PdfDocument document, OutputStream outputStream
419415
private void performSignDetached(SignerProperties signerProperties, boolean isFinal,
420416
IExternalSignature externalSignature, Certificate[] chain, ITSAClient tsaClient)
421417
throws GeneralSecurityException, IOException {
418+
Certificate[] fullChain = issuingCertificateRetriever.retrieveMissingCertificates(chain);
422419
PdfSigner signer = createPdfSigner(signerProperties, isFinal);
423420
try {
424-
signer.signDetached(externalDigest, externalSignature, chain, null, null, tsaClient,
421+
signer.signDetached(externalDigest, externalSignature, fullChain, null, null, tsaClient,
425422
estimatedSize, CryptoStandard.CADES);
426423
} finally {
427424
signer.originalOS.close();
@@ -503,16 +500,16 @@ private File getNextTempFile() {
503500
return tempFile;
504501
}
505502

506-
private void createRevocationClients(Certificate[] chain, boolean clientsRequired) {
503+
private void createRevocationClients(Certificate signingCert, boolean clientsRequired) {
507504
if (crlClient == null && ocspClient == null && clientsRequired) {
508-
X509Certificate signingCertificate = (X509Certificate) chain[0];
505+
X509Certificate signingCertificate = (X509Certificate) signingCert;
509506
if (CertificateUtil.getOCSPURL(signingCertificate) == null &&
510507
CertificateUtil.getCRLURL(signingCertificate) == null) {
511508
throw new PdfException(SignExceptionMessageConstant.DEFAULT_CLIENTS_CANNOT_BE_CREATED);
512509
}
513510
}
514511
if (crlClient == null) {
515-
crlClient = new CrlClientOnline(chain);
512+
crlClient = new CrlClientOnline();
516513
}
517514
if (ocspClient == null) {
518515
ocspClient = new OcspClientBouncyCastle(null);

sign/src/main/java/com/itextpdf/signatures/logs/SignLogMessageConstant.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ public final class SignLogMessageConstant {
3333
"Unexpected exception without message was thrown during keystore processing";
3434
public static final String UNABLE_TO_PARSE_AIA_CERT = "Unable to parse certificates coming from authority info "
3535
+ "access extension. Those won't be included into the certificate chain.";
36+
37+
public static final String REVOCATION_DATA_NOT_ADDED_VALIDITY_ASSURED =
38+
"Revocation data for certificate: \"{0}\" is not added due to validity assured - short term extension.";
3639

3740
private SignLogMessageConstant() {
3841
// Private constructor will prevent the instantiation of this class directly

sign/src/test/java/com/itextpdf/signatures/TestSignUtils.java

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ This file is part of the iText (R) project.
4646
import java.security.cert.CertificateException;
4747
import java.security.cert.X509CRL;
4848
import java.security.cert.X509Certificate;
49+
import java.util.ArrayList;
4950
import java.util.Arrays;
5051
import java.util.HashMap;
5152
import java.util.List;
@@ -56,23 +57,40 @@ public final class TestSignUtils {
5657
private static final IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.getFactory();
5758

5859
public static void assertDssDict(InputStream inputStream, Map<String, Integer> expectedNumberOfCrls,
59-
Map<String, Integer> expectedNumberOfOcsp)
60+
Map<String, Integer> expectedNumberOfOcsp, List<String> expectedCerts)
6061
throws IOException, CertificateException, CRLException, AbstractOCSPException {
6162
try (PdfDocument outDocument = new PdfDocument(new PdfReader(inputStream))) {
6263
PdfDictionary dss = outDocument.getCatalog().getPdfObject().getAsDictionary(PdfName.DSS);
64+
65+
PdfArray certs = dss.getAsArray(PdfName.Certs) == null ? new PdfArray() : dss.getAsArray(PdfName.Certs);
66+
List<String> realCertificates = createCertsList(certs);
6367

6468
PdfArray crls = dss.getAsArray(PdfName.CRLs) == null ? new PdfArray() : dss.getAsArray(PdfName.CRLs);
6569
Map<String, Integer> realNumberOfCrls = createCrlMap(crls);
6670

6771
PdfArray ocsps = dss.getAsArray(PdfName.OCSPs) == null ? new PdfArray() : dss.getAsArray(PdfName.OCSPs);
6872
Map<String, Integer> realNumberOfOcsp = createOcspMap(ocsps);
6973

74+
if (expectedCerts != null) {
75+
Assert.assertEquals("Certs entry in DSS dictionary isn't correct", realCertificates.size(),
76+
expectedCerts.size());
77+
// Order agnostic list comparison.
78+
for (String expectedCert : expectedCerts) {
79+
Assert.assertTrue(realCertificates.stream().anyMatch(cert -> cert.equals(expectedCert)));
80+
}
81+
}
7082
Assert.assertTrue("CRLs entry in DSS dictionary isn't correct",
7183
MapUtil.equals(expectedNumberOfCrls, realNumberOfCrls));
7284
Assert.assertTrue("OCSPs entry in DSS dictionary isn't correct",
7385
MapUtil.equals(expectedNumberOfOcsp, realNumberOfOcsp));
7486
}
7587
}
88+
89+
public static void assertDssDict(InputStream inputStream, Map<String, Integer> expectedNumberOfCrls,
90+
Map<String, Integer> expectedNumberOfOcsp)
91+
throws IOException, CertificateException, CRLException, AbstractOCSPException {
92+
assertDssDict(inputStream, expectedNumberOfCrls, expectedNumberOfOcsp, null);
93+
}
7694

7795
public static void basicCheckSignedDoc(String filePath, String signatureName) throws GeneralSecurityException, IOException {
7896
try (InputStream inputStream = FileUtil.getInputStreamForFile(filePath)) {
@@ -88,11 +106,12 @@ public static void basicCheckSignedDoc(InputStream inputStream, String signature
88106
}
89107
}
90108

91-
public static void signedDocumentContainsCerts(InputStream inputStream, List<X509Certificate> expectedCertificates)
109+
public static void signedDocumentContainsCerts(InputStream inputStream, List<X509Certificate> expectedCertificates,
110+
String signatureName)
92111
throws IOException {
93112
try (PdfDocument outDocument = new PdfDocument(new PdfReader(inputStream))) {
94113
SignatureUtil sigUtil = new SignatureUtil(outDocument);
95-
List<Certificate> actualCertificates = Arrays.asList(sigUtil.readSignatureData("Signature1").getCertificates());
114+
List<Certificate> actualCertificates = Arrays.asList(sigUtil.readSignatureData(signatureName).getCertificates());
96115
// Searching for every certificate we expect should be in the resulting document.
97116
Assert.assertEquals(expectedCertificates.size(), actualCertificates.size());
98117
for (X509Certificate expectedCert : expectedCertificates) {
@@ -101,6 +120,17 @@ public static void signedDocumentContainsCerts(InputStream inputStream, List<X50
101120
}
102121
}
103122
}
123+
124+
private static List<String> createCertsList(PdfArray certs) throws CertificateException {
125+
List<String> certsNames = new ArrayList<>();
126+
for (PdfObject cert : certs) {
127+
PdfStream certStream = (PdfStream) cert;
128+
byte[] certBytes = certStream.getBytes();
129+
X509Certificate certificate = (X509Certificate) SignUtils.readAllCerts(certBytes).toArray(new Certificate[0])[0];
130+
certsNames.add(certificate.getSubjectX500Principal().getName());
131+
}
132+
return certsNames;
133+
}
104134

105135
private static Map<String, Integer> createCrlMap(PdfArray crls) throws CertificateException, CRLException {
106136
Map<String, Integer> realNumberOfCrls = new HashMap<>();

sign/src/test/java/com/itextpdf/signatures/sign/PdfPadesAdvancedTest.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,12 @@ public void signWithAdvancedClientsTest()
203203
padesSigner.signWithBaselineLTAProfile(signerProperties, signRsaChain, pks, testTsa);
204204

205205
TestSignUtils.basicCheckSignedDoc(new ByteArrayInputStream(outputStream.toByteArray()), "Signature1");
206-
assertDss(outputStream, rootCert);
206+
assertDss(outputStream, rootCert, signRsaCert, (X509Certificate) tsaChain[0], (X509Certificate) tsaChain[1]);
207207
}
208208
}
209209

210-
private void assertDss(ByteArrayOutputStream outputStream, X509Certificate rootCert)
210+
private void assertDss(ByteArrayOutputStream outputStream, X509Certificate rootCert, X509Certificate signRsaCert,
211+
X509Certificate tsaCert, X509Certificate rootTsaCert)
211212
throws AbstractOCSPException, CertificateException, IOException, CRLException {
212213
Map<String, Integer> expectedNumberOfCrls = new HashMap<>();
213214
if (amountOfCrlsForRoot + amountOfCrlsForSign != 0) {
@@ -217,7 +218,14 @@ private void assertDss(ByteArrayOutputStream outputStream, X509Certificate rootC
217218
if (amountOfOcspsForRoot + amountOfOcspsForSign != 0) {
218219
expectedNumberOfOcsps.put(rootCert.getSubjectX500Principal().getName(), amountOfOcspsForRoot + amountOfOcspsForSign);
219220
}
220-
TestSignUtils.assertDssDict(new ByteArrayInputStream(outputStream.toByteArray()), expectedNumberOfCrls, expectedNumberOfOcsps);
221+
List<String> expectedCerts = Arrays.asList(getCertName(rootCert), getCertName(signRsaCert),
222+
getCertName(tsaCert), getCertName(rootTsaCert));
223+
TestSignUtils.assertDssDict(new ByteArrayInputStream(outputStream.toByteArray()), expectedNumberOfCrls,
224+
expectedNumberOfOcsps, expectedCerts);
225+
}
226+
227+
private String getCertName(X509Certificate certificate) {
228+
return certificate.getSubjectX500Principal().getName();
221229
}
222230

223231
private SignerProperties createSignerProperties() {

0 commit comments

Comments
 (0)