Skip to content

Commit 4575c74

Browse files
yulian-gaponenkoUbuntu
authored andcommitted
Support CRLs in adbe-revocationInfoArchival signature attribute
DEVSIX-6151
1 parent 19dee4f commit 4575c74

File tree

1 file changed

+132
-13
lines changed

1 file changed

+132
-13
lines changed

sign/src/test/java/com/itextpdf/signatures/testutils/SignaturesCompareTool.java

Lines changed: 132 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ This file is part of the iText (R) project.
4343
*/
4444
package com.itextpdf.signatures.testutils;
4545

46+
import com.itextpdf.commons.utils.MessageFormatUtil;
4647
import com.itextpdf.io.util.UrlUtil;
4748
import com.itextpdf.kernel.pdf.PdfArray;
4849
import com.itextpdf.kernel.pdf.PdfDictionary;
@@ -53,6 +54,7 @@ This file is part of the iText (R) project.
5354
import com.itextpdf.kernel.pdf.ReaderProperties;
5455
import com.itextpdf.signatures.PdfSignature;
5556
import com.itextpdf.signatures.SignatureUtil;
57+
import com.itextpdf.test.ITextTest;
5658

5759
import java.io.ByteArrayInputStream;
5860
import java.io.FileWriter;
@@ -63,8 +65,7 @@ This file is part of the iText (R) project.
6365
import java.util.List;
6466
import java.util.Objects;
6567
import java.util.Set;
66-
67-
import com.itextpdf.test.ITextTest;
68+
import java.util.stream.Collectors;
6869
import org.bouncycastle.asn1.ASN1BitString;
6970
import org.bouncycastle.asn1.ASN1Encodable;
7071
import org.bouncycastle.asn1.ASN1GeneralizedTime;
@@ -76,7 +77,6 @@ This file is part of the iText (R) project.
7677
import org.bouncycastle.asn1.ASN1Set;
7778
import org.bouncycastle.asn1.ASN1TaggedObject;
7879
import org.bouncycastle.asn1.ASN1UTCTime;
79-
import org.bouncycastle.asn1.DERGeneralizedTime;
8080
import org.bouncycastle.asn1.util.ASN1Dump;
8181

8282
public class SignaturesCompareTool {
@@ -85,6 +85,7 @@ public class SignaturesCompareTool {
8585
private static final String OID_TST_INFO = "1.2.840.113549.1.9.16.1.4";
8686
private static final String OID_SIGNING_TIME = "1.2.840.113549.1.9.5";
8787
private static final String OID_SIGNATURE_TIMESTAMP_ATTRIBUTE = "1.2.840.113549.1.9.16.2.14";
88+
private static final String OID_ADBE_REVOCATION_INFO_ARCHIVAL = "1.2.840.113583.1.1.8";
8889
private static final String OID_OCSP_RESPONSE = "1.3.6.1.5.5.7.48.1.1";
8990
private static final String OID_OCSP_NONCE_EXTENSION = "1.3.6.1.5.5.7.48.1.2";
9091

@@ -123,7 +124,7 @@ public static String compareSignatures(String dest, String cmp, ReaderProperties
123124
ASN1Sequence outSignedData = (ASN1Sequence) getSignatureContent(sig, outSigUtil);
124125
ASN1Sequence cmpSignedData = (ASN1Sequence) getSignatureContent(sig, cmpSigUtil);
125126

126-
boolean isEqual = compareSignatureObjects(outSignedData, cmpSignedData, errorText);
127+
boolean isEqual = compareSignedData(outSignedData, cmpSignedData, errorText);
127128

128129
if (!isEqual) {
129130
createTxtFilesFromAsn1Sequences(outSignedData, cmpSignedData, dest, sig, errorText);
@@ -236,7 +237,19 @@ private static boolean compareOcspResponses(ASN1Encodable[] outOcspResponse, ASN
236237
return compareSequencesWithSignatureValue(parsedOutResponse, parsedCmpResponse, errorText);
237238
}
238239

239-
private static boolean compareSignatureObjects(ASN1Sequence outSignedData, ASN1Sequence cmpSignedData,
240+
/**
241+
* SignedData is top-level CMS-object for signatures, see "5.1. SignedData Type" at
242+
* https://datatracker.ietf.org/doc/html/rfc5652#section-5.1 .
243+
*
244+
* @param outSignedData current output signed data
245+
* @param cmpSignedData reference signed data used for comparison as a ground truth
246+
* @param errorText string builder in order to accumulate errors
247+
*
248+
* @return true if signed data objects are the similar, false otherwise
249+
*
250+
* @throws IOException is thrown if object data parsing failed
251+
*/
252+
private static boolean compareSignedData(ASN1Sequence outSignedData, ASN1Sequence cmpSignedData,
240253
StringBuilder errorText) throws IOException {
241254
if (outSignedData.size() != cmpSignedData.size() || outSignedData.size() != 2) {
242255
addError(errorText, "Signature top level elements count is incorrect (should be exactly 2):",
@@ -300,8 +313,8 @@ private static boolean compareSignatureObjects(ASN1Sequence outSignedData, ASN1S
300313
return false;
301314
}
302315

303-
ASN1Sequence outSignerInfo = (ASN1Sequence) cmpSignerInfos.getObjectAt(0);
304-
ASN1Sequence cmpSignerInfo = (ASN1Sequence) outSignerInfos.getObjectAt(0);
316+
ASN1Sequence outSignerInfo = (ASN1Sequence) outSignerInfos.getObjectAt(0);
317+
ASN1Sequence cmpSignerInfo = (ASN1Sequence) cmpSignerInfos.getObjectAt(0);
305318

306319
return compareSequencesWithSignatureValue(outSignerInfo, cmpSignerInfo, errorText);
307320
}
@@ -346,15 +359,15 @@ private static boolean compareAsn1Structures(ASN1Primitive out, ASN1Primitive cm
346359

347360
if (cmp instanceof ASN1TaggedObject) {
348361
return compareAsn1Structures(
349-
((ASN1TaggedObject) cmp).getObject(), ((ASN1TaggedObject) out).getObject(), errorText);
362+
((ASN1TaggedObject) out).getObject(), ((ASN1TaggedObject) cmp).getObject(), errorText);
350363
} else if (cmp instanceof ASN1Sequence) {
351364
if (!compareContainers(((ASN1Sequence) out).toArray(), ((ASN1Sequence) cmp).toArray(), errorText)) {
352-
addError(errorText, "ASN1Sequence objects are different", null, null);
365+
addError(errorText, "ASN1Sequence objects are different");
353366
return false;
354367
}
355368
} else if (cmp instanceof ASN1Set) {
356369
if (!compareContainers(((ASN1Set) out).toArray(), ((ASN1Set) cmp).toArray(), errorText)) {
357-
addError(errorText, "ASN1Set objects are different", null, null);
370+
addError(errorText, "ASN1Set objects are different");
358371
return false;
359372
}
360373
} else if (cmp instanceof ASN1GeneralizedTime || cmp instanceof ASN1UTCTime) {
@@ -394,6 +407,9 @@ private static boolean compareContainers(ASN1Encodable[] outArray,
394407
if (OID_OCSP_RESPONSE.equals(cmpASN1ObjectId)) {
395408
return compareOcspResponses(outArray, cmpArray, errorText);
396409
}
410+
if (OID_ADBE_REVOCATION_INFO_ARCHIVAL.equals(cmpASN1ObjectId)) {
411+
return compareRevocationInfoArchivalAttribute(outArray, cmpArray, errorText);
412+
}
397413
for (int i = 0; i < cmpArray.length; i++) {
398414
if (!compareAsn1Structures(outArray[i].toASN1Primitive(), cmpArray[i].toASN1Primitive(), errorText)) {
399415
return false;
@@ -402,6 +418,108 @@ private static boolean compareContainers(ASN1Encodable[] outArray,
402418
return true;
403419
}
404420

421+
/**
422+
* See ISO 32000-2, 12.8.3.3.2 "Revocation of CMS-based signatures"
423+
*
424+
* @param out out signature revocation info attribute value
425+
* @param cmp cmp signature revocation info attribute value
426+
* @param errorText string builder in order to accumulate errors
427+
*
428+
* @return true if signed data objects are the similar, false otherwise
429+
*/
430+
private static boolean compareRevocationInfoArchivalAttribute(ASN1Encodable[] out, ASN1Encodable[] cmp,
431+
StringBuilder errorText) throws IOException {
432+
String structureIsInvalidError = "Signature revocation info archival attribute structure is invalid";
433+
if (!isExpectedRevocationInfoArchivalAttributeStructure(out)
434+
|| !isExpectedRevocationInfoArchivalAttributeStructure(cmp)) {
435+
addError(errorText, structureIsInvalidError,
436+
String.join("", Arrays.stream(out).map(e -> ASN1Dump.dumpAsString(e)).collect(Collectors.toList())),
437+
String.join("", Arrays.stream(cmp).map(e -> ASN1Dump.dumpAsString(e)).collect(Collectors.toList())));
438+
return false;
439+
}
440+
441+
ASN1Sequence outSequence = ((ASN1Sequence) ((ASN1Set) out[1]).getObjectAt(0).toASN1Primitive());
442+
ASN1Sequence cmpSequence = ((ASN1Sequence) ((ASN1Set) cmp[1]).getObjectAt(0).toASN1Primitive());
443+
if (outSequence.size() != cmpSequence.size()) {
444+
addError(errorText,
445+
"Signature revocation info archival attributes have different sets of revocation info types (different sizes)",
446+
String.valueOf(outSequence.size()), String.valueOf(cmpSequence.size()));
447+
return false;
448+
}
449+
450+
for (int i = 0; i < outSequence.size(); i++) {
451+
if (!(outSequence.getObjectAt(i) instanceof ASN1TaggedObject)
452+
|| !(cmpSequence.getObjectAt(i) instanceof ASN1TaggedObject)) {
453+
addError(errorText, structureIsInvalidError,
454+
String.join("", Arrays.stream(out).map(e -> ASN1Dump.dumpAsString(e)).collect(Collectors.toList())),
455+
String.join("", Arrays.stream(cmp).map(e -> ASN1Dump.dumpAsString(e)).collect(Collectors.toList())));
456+
return false;
457+
}
458+
ASN1TaggedObject outTaggedObject = (ASN1TaggedObject) outSequence.getObjectAt(i);
459+
ASN1TaggedObject cmpTaggedObject = (ASN1TaggedObject) cmpSequence.getObjectAt(i);
460+
if (outTaggedObject.getTagNo() != cmpTaggedObject.getTagNo()) {
461+
addError(errorText,
462+
"Signature revocation info archival attributes have different tagged objects tag numbers",
463+
String.valueOf(outTaggedObject.getTagNo()), String.valueOf(cmpTaggedObject.getTagNo()));
464+
return false;
465+
}
466+
467+
if (!(outTaggedObject.getObject() instanceof ASN1Sequence)
468+
|| !(cmpTaggedObject.getObject() instanceof ASN1Sequence)) {
469+
addError(errorText, structureIsInvalidError,
470+
String.join("", Arrays.stream(out).map(e -> ASN1Dump.dumpAsString(e)).collect(Collectors.toList())),
471+
String.join("", Arrays.stream(cmp).map(e -> ASN1Dump.dumpAsString(e)).collect(Collectors.toList())));
472+
return false;
473+
}
474+
475+
// revocation entries can be either CRLs or OCSPs in most cases
476+
ASN1Sequence outRevocationEntries = (ASN1Sequence) outTaggedObject.getObject();
477+
ASN1Sequence cmpRevocationEntries = (ASN1Sequence) cmpTaggedObject.getObject();
478+
if (outRevocationEntries.size() != cmpRevocationEntries.size()) {
479+
addError(errorText,
480+
"Signature revocation info archival attributes have different number of entries",
481+
String.valueOf(outRevocationEntries.size()), String.valueOf(cmpRevocationEntries.size()));
482+
return false;
483+
}
484+
485+
if (outTaggedObject.getTagNo() == 0) {
486+
// CRL revocation info case
487+
for (int j = 0; j < outRevocationEntries.size(); j++) {
488+
if (!(outRevocationEntries.getObjectAt(j) instanceof ASN1Sequence)
489+
|| !(outRevocationEntries.getObjectAt(j) instanceof ASN1Sequence)) {
490+
addError(errorText,
491+
"Signature revocation info attribute has unexpected CRL entry type",
492+
outRevocationEntries.getObjectAt(j).getClass().getName().toString(),
493+
cmpRevocationEntries.getObjectAt(j).getClass().getName().toString());
494+
return false;
495+
}
496+
if (!compareSequencesWithSignatureValue(
497+
((ASN1Sequence) outRevocationEntries.getObjectAt(j)),
498+
((ASN1Sequence) cmpRevocationEntries.getObjectAt(j)), errorText)) {
499+
addError(errorText,
500+
MessageFormatUtil.format(
501+
"Signature revocation info attribute CRLs at {0} are different",
502+
String.valueOf(j)));
503+
return false;
504+
}
505+
}
506+
} else {
507+
if (!compareAsn1Structures(outRevocationEntries, cmpRevocationEntries, errorText)) {
508+
addError(errorText, "Revocation info attribute entries are different");
509+
return false;
510+
}
511+
}
512+
}
513+
return true;
514+
}
515+
516+
private static boolean isExpectedRevocationInfoArchivalAttributeStructure(ASN1Encodable[] container) {
517+
return container.length == 2
518+
&& container[1] instanceof ASN1Set
519+
&& ((ASN1Set) container[1]).size() == 1
520+
&& ((ASN1Set) container[1]).getObjectAt(0).toASN1Primitive() instanceof ASN1Sequence;
521+
}
522+
405523
private static boolean compareTimestampAttributes(ASN1Encodable[] out, ASN1Encodable[] cmp,
406524
StringBuilder errorText) throws IOException {
407525
if (cmp.length == 2) {
@@ -410,13 +528,14 @@ private static boolean compareTimestampAttributes(ASN1Encodable[] out, ASN1Encod
410528
ASN1Primitive cmpSequence = ((ASN1Set) cmp[1]).getObjectAt(0).toASN1Primitive();
411529

412530
if (outSequence instanceof ASN1Sequence && cmpSequence instanceof ASN1Sequence) {
413-
return compareSignatureObjects((ASN1Sequence) outSequence, (ASN1Sequence) cmpSequence, errorText);
531+
return compareSignedData((ASN1Sequence) outSequence, (ASN1Sequence) cmpSequence, errorText);
414532
}
415533
}
416534
}
417535

418-
addError(errorText,
419-
"Signature timestamp attribute structure is invalid", Arrays.toString(out), Arrays.toString(cmp));
536+
addError(errorText, "Signature timestamp attribute structure is invalid",
537+
String.join("", Arrays.stream(out).map(e -> ASN1Dump.dumpAsString(e)).collect(Collectors.toList())),
538+
String.join("", Arrays.stream(cmp).map(e -> ASN1Dump.dumpAsString(e)).collect(Collectors.toList())));
420539
return false;
421540
}
422541

0 commit comments

Comments
 (0)