We're trying to validate the digital signature of a Dutch government agency (UWV Verzekeringsbericht) including the authenticity of the file.
Adobe Acrobat Reader is able to validate this file correctly.
With a small proof of concept application we're able to verify the authenticity of various kind of digitally signed PDFs:
import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.security.PdfPKCS7;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.Security;
import java.util.ArrayList;
public class Verifier {
public static void main(String[] args) throws IOException, GeneralSecurityException {
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
new Verifier().run(args[0]);
}
private void run(String path) throws IOException, GeneralSecurityException {
final PdfReader reader = new PdfReader(path);
final AcroFields fields = reader.getAcroFields();
final ArrayList<String> signatureNames = fields.getSignatureNames();
for(String signatureName: signatureNames) {
System.out.println("Verify signature " + signatureName);
verifySignature(fields, signatureName);
}
}
private PdfPKCS7 verifySignature(final AcroFields fields, final String name) throws GeneralSecurityException {
System.out.println("Signature covers whole document: " + fields.signatureCoversWholeDocument(name));
System.out.println("Document revision: " + fields.getRevision(name) + " of " + fields.getTotalRevisions());
PdfPKCS7 pkcs7 = fields.verifySignature(name);
System.out.println("Integrity check OK? " + pkcs7.verify());
return pkcs7;
}
}
Using these (Maven) dependencies:
<dependencies>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-debug-jdk15on</artifactId>
<version>1.60</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.60</version>
</dependency>
</dependencies>
As you can guess, validating PDFs from this authority doesn't work.
The result of running this application is:
Exception in thread "main" java.lang.IllegalArgumentException: can't decode PKCS7SignedData object
at com.itextpdf.text.pdf.security.PdfPKCS7.<init>(PdfPKCS7.java:214)
This is caused within the PdfPKCS7 class which is instantiating a ASN1 input stream from the signature's content (line 203):
SN1InputStream din = new ASN1InputStream(new ByteArrayInputStream(contentsKey));
Which in turn results in a IOException: DER length more than 4 bytes: 31 So the signature seems invalid.
The AcroFields' verifySignature
method call tries to create a PdfPKCS7 instance. A snippet from this method:
if(!reader.isEncrypted()){
pk = new PdfPKCS7(contents.getOriginalBytes(), sub, provider);
}else{
pk = new PdfPKCS7(contents.getBytes(),sub,provider);
}
For some reason iTextPDF concludes the PDF is encrypted and uses the getBytes
variant for the signature verification.
However, the PDF is not encrypted (as far as I know), so it should use the getOriginalBytes
.
When I force using this original contents, while debugging, the verification succeeds!
So it seems like a bug within iTextPDF, maybe caused by an unusual combination of factors in the pdf.
Some details from the PDF's certificate:
Version: 3
Signature algorithm: SHA256 RSA
Key usage: Digital Signature, Encrypt Keys
Public Key: RSA (2048 bits)
Unfortunately I can't share the concerning PDF because it contains personal information. As a Dutch citizen you can download your own version from UWV, see these instructions.
Any help or suggestion is appreciated.