The CMS signature container embedded in your PDF has some questionable properties. In particular it has an encapsulated content, even though it merely is a byte array of length 0. This makes iText include this value in a test which then fails. I would propose creating cleaner signature containers.
In detail
The CMS object type EncapsulatedContentInfo
is specified as:
5.2. EncapsulatedContentInfo Type
The content is represented in the type EncapsulatedContentInfo:
EncapsulatedContentInfo ::= SEQUENCE {
eContentType ContentType,
eContent [0] EXPLICIT OCTET STRING OPTIONAL }
ContentType ::= OBJECT IDENTIFIER
The fields of type EncapsulatedContentInfo have the following
meanings:
eContentType is an object identifier. The object identifier
uniquely specifies the content type.
eContent is the content itself, carried as an octet string. The
eContent need not be DER encoded.
The optional omission of the eContent within the
EncapsulatedContentInfo field makes it possible to construct
"external signatures". In the case of external signatures, the
content being signed is absent from the EncapsulatedContentInfo value
included in the signed-data content type. If the eContent value
within EncapsulatedContentInfo is absent, then the signatureValue is
calculated and the eContentType is assigned as though the eContent
value was present.
(RFC 5652 section 5.2)
So for signature containers embedded into a PDF, eContent
should be omitted to indicate that not this eContent
is signed but separate data, the surrounding PDF data.
The signature container of the OP's signed document contains this EncapsulatedContentInfo
object:
<30 0F>
43 15: SEQUENCE {
<06 09>
45 9: OBJECT IDENTIFIER data (1 2 840 113549 1 7 1)
: (PKCS #7)
<A0 02>
56 2: [0] {
<04 00>
58 0: OCTET STRING
: }
: }
Thus, this signature container does not have omitted the eContent
and so makes iText include this array into its checks to eventually fail.
This actually is a bug, though
I formulated the above in a fairly restrained manner for a simple reason: Even though that use of the EncapsulatedContentInfo
type is misleading, it is not outright invalid in the case at hand!
For the original ISO 32000-1 integrated signatures, that norm specified
When PKCS#7 signatures are used, the value of Contents shall be a DER-encoded PKCS#7 binary data
object containing the signature. The PKCS#7 object shall conform to RFC3852 Cryptographic Message
Syntax. Different subfilters may be used and shall be registered in accordance with Annex E. SubFilter shall
take one of the following values:
- adbe.pkcs7.detached: The original signed message digest over the document’s byte range shall be
incorporated as the normal PKCS#7 SignedData field. No data shall be encapsulated in the PKCS#7
SignedData field.
- adbe.pkcs7.sha1: The SHA1 digest of the document’s byte range shall be encapsulated in the PKCS#7
SignedData field with ContentInfo of type Data. The digest of that SignedData shall be incorporated as the
normal PKCS#7 digest.
(ISO 32000-1 section 12.8.3.3 PKCS#7 Signatures as used in ISO 32000)
So for such signatures there either was no encapsulated data (not even a zero-length byte array) or the encapsulated data were a digest value to check.
This is why iText's verification code in one place assumes it is in the latter case: As there is some encapsulated content, it must be a hash to check.
The signature at hand, though, is not of one of those original types but it uses SubFilter ETSI.CAdES.detached. These signatures are originally specified by ETSI:
4.2 General Requirements
For all profiles covered in the present document:
b) A DER-encoded SignedData object as specified in CMS (RFC 3852) shall be included as the PDF
signature in the entry with the key Content of the signature dictionary as described in ISO 32000-1,
clause 12.8.1. This CMS object forms a CAdES signature described in TS 101 733 as it may contain
several attributes required by the rules given in the following clauses.
d) Requirements specified in ISO 32000-1, clauses 12.8.3.2 (PKCS#1) and 12.8.3.3 (PKCS#7) signatures as
used in ISO 32000-1 do not apply.
4.6.2 Document Digest
The verifier shall check that the document digest matches that in the signature as specified in ISO 32000-1,
clause 12.8.1
(ETSI TS 102 778-3 V1.1.2 (2009-12))
So for signatures as in the case at hand, the section from ISO 32000-1 partially quoted before does not apply, and the hash in the signature (the message-digest attribute) has to match the document digest (calculated as defined by ISO 32000-1, i.e. only for the defined byte ranges) without consideration of the encapsulated content.
A workaround
If you slightly patch the PdfPKCS7
object before calling PdfPKCS7.verify()
, you can positively verify the document with iText, cf. the test VerifySignature.java:
While the original verification
System.out.println("Signature name: " + name);
System.out.println("Signature covers whole document: " + acroFields.signatureCoversWholeDocument(name));
PdfPKCS7 pk = acroFields.verifySignature(name);
System.out.println("Subject: " + CertificateInfo.getSubjectFields(pk.getSigningCertificate()));
System.out.println("Document verifies: " + pk.verify());
returns a failure
Signature name: Signature1
Signature covers whole document: false
Subject: {SURNAME=[CHARPENTIER DIAZ], C=[CR], OU=[CIUDADANO], SN=[CPF-01-1093-0964], CN=[JOSE ALBERTO CHARPENTIER DIAZ (FIRMA)], GIVENNAME=[JOSE ALBERTO], O=[PERSONA FISICA]}
Document verifies: false
the patched verification
System.out.println("Signature name: " + name);
System.out.println("Signature covers whole document: " + acroFields.signatureCoversWholeDocument(name));
PdfPKCS7 pk = acroFields.verifySignature(name);
System.out.println("Subject: " + CertificateInfo.getSubjectFields(pk.getSigningCertificate()));
Field rsaDataField = PdfPKCS7.class.getDeclaredField("RSAdata");
rsaDataField.setAccessible(true);
Object rsaDataFieldContent = rsaDataField.get(pk);
if (rsaDataFieldContent != null && ((byte[])rsaDataFieldContent).length == 0)
{
System.out.println("Found zero-length encapsulated content: ignoring");
rsaDataField.set(pk, null);
}
System.out.println("Document verifies: " + pk.verify());
returns a success:
Signature name: Signature1
Signature covers whole document: false
Subject: {SURNAME=[CHARPENTIER DIAZ], C=[CR], OU=[CIUDADANO], SN=[CPF-01-1093-0964], CN=[JOSE ALBERTO CHARPENTIER DIAZ (FIRMA)], GIVENNAME=[JOSE ALBERTO], O=[PERSONA FISICA]}
Found zero-length encapsulated content: ignoring
Document verifies: true
(The patch attempts to be gentle and only patches zero-length byte arrays to null
.)