@@ -43,6 +43,7 @@ This file is part of the iText (R) project.
43
43
*/
44
44
package com .itextpdf .signatures .testutils ;
45
45
46
+ import com .itextpdf .commons .utils .MessageFormatUtil ;
46
47
import com .itextpdf .io .util .UrlUtil ;
47
48
import com .itextpdf .kernel .pdf .PdfArray ;
48
49
import com .itextpdf .kernel .pdf .PdfDictionary ;
@@ -53,6 +54,7 @@ This file is part of the iText (R) project.
53
54
import com .itextpdf .kernel .pdf .ReaderProperties ;
54
55
import com .itextpdf .signatures .PdfSignature ;
55
56
import com .itextpdf .signatures .SignatureUtil ;
57
+ import com .itextpdf .test .ITextTest ;
56
58
57
59
import java .io .ByteArrayInputStream ;
58
60
import java .io .FileWriter ;
@@ -63,8 +65,7 @@ This file is part of the iText (R) project.
63
65
import java .util .List ;
64
66
import java .util .Objects ;
65
67
import java .util .Set ;
66
-
67
- import com .itextpdf .test .ITextTest ;
68
+ import java .util .stream .Collectors ;
68
69
import org .bouncycastle .asn1 .ASN1BitString ;
69
70
import org .bouncycastle .asn1 .ASN1Encodable ;
70
71
import org .bouncycastle .asn1 .ASN1GeneralizedTime ;
@@ -76,7 +77,6 @@ This file is part of the iText (R) project.
76
77
import org .bouncycastle .asn1 .ASN1Set ;
77
78
import org .bouncycastle .asn1 .ASN1TaggedObject ;
78
79
import org .bouncycastle .asn1 .ASN1UTCTime ;
79
- import org .bouncycastle .asn1 .DERGeneralizedTime ;
80
80
import org .bouncycastle .asn1 .util .ASN1Dump ;
81
81
82
82
public class SignaturesCompareTool {
@@ -85,6 +85,7 @@ public class SignaturesCompareTool {
85
85
private static final String OID_TST_INFO = "1.2.840.113549.1.9.16.1.4" ;
86
86
private static final String OID_SIGNING_TIME = "1.2.840.113549.1.9.5" ;
87
87
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" ;
88
89
private static final String OID_OCSP_RESPONSE = "1.3.6.1.5.5.7.48.1.1" ;
89
90
private static final String OID_OCSP_NONCE_EXTENSION = "1.3.6.1.5.5.7.48.1.2" ;
90
91
@@ -123,7 +124,7 @@ public static String compareSignatures(String dest, String cmp, ReaderProperties
123
124
ASN1Sequence outSignedData = (ASN1Sequence ) getSignatureContent (sig , outSigUtil );
124
125
ASN1Sequence cmpSignedData = (ASN1Sequence ) getSignatureContent (sig , cmpSigUtil );
125
126
126
- boolean isEqual = compareSignatureObjects (outSignedData , cmpSignedData , errorText );
127
+ boolean isEqual = compareSignedData (outSignedData , cmpSignedData , errorText );
127
128
128
129
if (!isEqual ) {
129
130
createTxtFilesFromAsn1Sequences (outSignedData , cmpSignedData , dest , sig , errorText );
@@ -236,7 +237,19 @@ private static boolean compareOcspResponses(ASN1Encodable[] outOcspResponse, ASN
236
237
return compareSequencesWithSignatureValue (parsedOutResponse , parsedCmpResponse , errorText );
237
238
}
238
239
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 ,
240
253
StringBuilder errorText ) throws IOException {
241
254
if (outSignedData .size () != cmpSignedData .size () || outSignedData .size () != 2 ) {
242
255
addError (errorText , "Signature top level elements count is incorrect (should be exactly 2):" ,
@@ -300,8 +313,8 @@ private static boolean compareSignatureObjects(ASN1Sequence outSignedData, ASN1S
300
313
return false ;
301
314
}
302
315
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 );
305
318
306
319
return compareSequencesWithSignatureValue (outSignerInfo , cmpSignerInfo , errorText );
307
320
}
@@ -346,15 +359,15 @@ private static boolean compareAsn1Structures(ASN1Primitive out, ASN1Primitive cm
346
359
347
360
if (cmp instanceof ASN1TaggedObject ) {
348
361
return compareAsn1Structures (
349
- ((ASN1TaggedObject ) cmp ).getObject (), ((ASN1TaggedObject ) out ).getObject (), errorText );
362
+ ((ASN1TaggedObject ) out ).getObject (), ((ASN1TaggedObject ) cmp ).getObject (), errorText );
350
363
} else if (cmp instanceof ASN1Sequence ) {
351
364
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" );
353
366
return false ;
354
367
}
355
368
} else if (cmp instanceof ASN1Set ) {
356
369
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" );
358
371
return false ;
359
372
}
360
373
} else if (cmp instanceof ASN1GeneralizedTime || cmp instanceof ASN1UTCTime ) {
@@ -394,6 +407,9 @@ private static boolean compareContainers(ASN1Encodable[] outArray,
394
407
if (OID_OCSP_RESPONSE .equals (cmpASN1ObjectId )) {
395
408
return compareOcspResponses (outArray , cmpArray , errorText );
396
409
}
410
+ if (OID_ADBE_REVOCATION_INFO_ARCHIVAL .equals (cmpASN1ObjectId )) {
411
+ return compareRevocationInfoArchivalAttribute (outArray , cmpArray , errorText );
412
+ }
397
413
for (int i = 0 ; i < cmpArray .length ; i ++) {
398
414
if (!compareAsn1Structures (outArray [i ].toASN1Primitive (), cmpArray [i ].toASN1Primitive (), errorText )) {
399
415
return false ;
@@ -402,6 +418,108 @@ private static boolean compareContainers(ASN1Encodable[] outArray,
402
418
return true ;
403
419
}
404
420
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
+
405
523
private static boolean compareTimestampAttributes (ASN1Encodable [] out , ASN1Encodable [] cmp ,
406
524
StringBuilder errorText ) throws IOException {
407
525
if (cmp .length == 2 ) {
@@ -410,13 +528,14 @@ private static boolean compareTimestampAttributes(ASN1Encodable[] out, ASN1Encod
410
528
ASN1Primitive cmpSequence = ((ASN1Set ) cmp [1 ]).getObjectAt (0 ).toASN1Primitive ();
411
529
412
530
if (outSequence instanceof ASN1Sequence && cmpSequence instanceof ASN1Sequence ) {
413
- return compareSignatureObjects ((ASN1Sequence ) outSequence , (ASN1Sequence ) cmpSequence , errorText );
531
+ return compareSignedData ((ASN1Sequence ) outSequence , (ASN1Sequence ) cmpSequence , errorText );
414
532
}
415
533
}
416
534
}
417
535
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 ())));
420
539
return false ;
421
540
}
422
541
0 commit comments