How to detect a signed pdf is valid with iText? - java

For a project, I need to check if a digital signature is valid or not.
I already developed a piece of code to do it.
I tested with some pdf (correct or not) and the code seems to work.
But there is a case where the verification does not work.
In Adobe Reader I've the message "Document has been altered or corrupted since it was signed." with a "beautiful" red cross...
Unfortunately, I don't know how to test this case which is the most important.
Because if I understand, the signature is correct, but it's the document which is altered, not the signature.
I suppose we can use the hash of file, but I don't know how to extract correctly this information.
How to check that with Itext ?
For information, I tested the methods as below :
pdfpkcs7.verify(): return true
acroFields.signatureCoversWholeDocument(signame): return true
pdfReader.isRebuilt() : return false

Adobe has documented their selection of algorithms accepted in signatures in this document. In particular they therein state for all applicable PDF versions
DSA only supports SHA1 and adbe.pkcs7.detached
The OP's document, on the other hand, uses DSA with SHA256. The effect is that
Adobe Reader cannot positively validate the hash value but
iText (not restricting itself to those algorithm combinations) can.
Actually the signing algorithm information in signature itself is questionable to start with, it merely uses the OID 1.2.840.10040.4.1 which only indicates that DSA is used, not the digest algorithm it is used with. Strictly speaking a different OID should have been used:
for SHA1withDSA: 1.2.840.10040.4.3
for SHA256withDSA: 2.16.840.1.101.3.4.3.2
(There even are some alternatives most of which meanwhile have been deprecated.)
Finding only 1.2.840.10040.4.1 (only DSA) validators have to guess / deduce the digest, and assuming SHA1 (like Adobe Reader does) is not completely unreasonable.
iText most likely guesses the used SHA256 because this digest algorithm is also used to calculate the document digest in the signed attribute messageDigest.

iText :Avoiding the PDF Digital Signature Vulnerabilities with iText. links
In February 2019, a team of security researchers from the Ruhr-University Bochum in Germany published details of vulnerabilities in the digital signing system of many PDF viewers and online PDF digital signing services. After investigating these vulnerabilities, we found that recent updates to iText introduced in version 7.1.5 mean we are not vulnerable to the described attacks.
However, it was determined that the current names of the methods for checking and verifying signatures could be improved to better reflect their functionality. Therefore we have decided to deprecate the SignatureUtil#verifySignature and PdfPKCS7#verify methods, and replace them with SignatureUtil#readSignatureData and PdfPKCS7#verifySignatureIntegrityAndAuthenticity which have been introduced in iText 7.1.6.
Below are code examples showing how to use the signature integrity and authenticity checks for each of these versions of iText.
iText 7.1.6:
PdfDocument pdfDocument = new PdfDocument(new PdfReader(input));
// Checks that signature is genuine and the document was not modified.
boolean genuineAndWasNotModified = false;
String signatureFieldName = "Signature1";
SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
try {
PdfPKCS7 signature1 = signatureUtil.readSignatureData(signatureFieldName);
if (signature1 != null) {
genuineAndWasNotModified = signature1.verifySignatureIntegrityAndAuthenticity();
}
} catch (Exception ignored) {
// ignoring exceptions,
// we are only interested in signatures that are passing the check successfully
}
pdfDocument.close();
The second and third types of attack, Incremental Saving Attack (ISA) and Signature Wrapping (SWA), are based on attempts to insert into the PDF file some malicious data to override signed data.
In order to validate every signature, it is necessary to check if it covers the entire file, otherwise iText cannot be sure that signature in question indeed signs the data that constitutes the current PdfDocument and all its contents. Even though the signature is authentic and signed data integrity is intact, iText will always check that signed data is not only a part of the PDF content but is also a valid PDF file.
iText implements this check in the SignatureUtil.signatureCoversWholeDocument(String fieldName) method. For both ISA and SWA attacks, this method will return false, because some unsigned data was inserted into the file:
We actually addressed this specific issue back in November 2018 (before we were aware of the reported vulnerabilities) with a rewrite of the signatureCoversWholeDocument() method. Providing you use iText 7.1.5 (or newer) the following code should correctly validate the PDF:
PdfDocument pdfDocument = new PdfDocument(new PdfReader(input));
String signatureFieldName = "Signature1";
SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
Boolean completeDocumentIsSigned = signatureUtil.signatureCoversWholeDocument(signatureFieldName)}
if (!completeDocumentIsSigned)
{
// handle PDF file which contains NOT signed data
}
pdfDocument.close();
Note: Signatures that do not cover the entire document cannot be considered as verifying the PDF file, because content that is not covered by the signature might have been modified since the signature was created.

Related

How to check whether a PKCS#7 signature valid or not in Java

I have been doing this task for more than 3 days, and finally I realised that I need some help. I have 2 files - a PKCS#7 file(base64 encoded) and a file which was signed by the PKCS#7(say, pdf). My question is how can I check whether the sign is right or not and the file is intact. I ended up with this algorithm:
Transform a pdf file to base64
Check all the certificates in PKCS#7(whether some is out of date, or not valid)
Get a hash from a pdf file(applying a specific hash algorithm)
Get a hash from a PKCS#7 file(applying the same algorithm)
Finally check these 2 hashs
I hope it's the right order and with it's help I can perform my task and finally check integrity. But I don't know how to perform it using Java. The library I supposed to use is Bouncy Castle. Before it, I just processed the PKCS#7 file like this:
String rawString = ASN1ObjectIdentifier.fromByteArray(bytesArray).toString();
String rawStringForSurname = rawString.substring(rawString.indexOf("2.5.4.4,") + 9, rawString.length());
String signSurname = rawStringForSurname.substring(0, rawStringForSurname.indexOf("]"));
String rawStringForGivenName = rawString.substring(rawString.indexOf("2.5.4.42,") + 10, rawString.length());
String signGivenName = rawStringForGivenName.substring(0, rawStringForGivenName.indexOf("]"));
I guess this approach is awful. Getting a String from the whole raw ASN.1 is a terrible idea, though it's the only solution I did by myself. And using this approach I can not somehow "compare" the pdf and the PKCS#7 somehow. How can I perform it using X509Cerftificate, SignedData and other feautures from Bouncy Castle? Please correct me if I wrong about the order of actions and the actions itself.

Adding additional page to signed pdf and sign it again

Is it possible to add additional page to the signed PDF and sign it again without breaking the first signature.
I read in the adobe documentation under incremental updates that it may be possible.
However, I'm not sure if that applies to all the content or just the Annotations (commenting), form fill-in and digital signatures.
I tried to do this by using Apache PDFBox in Java, by signing the document, then loading it, appending the page to it, saving it using saveIncremental() and signing it again.
However, the first signature gets invalidated.
Here is my generateTest method that generates the new PDF:
public byte[] generateTest(InputStream requestPdfIn) throws IOException {
// Create a document and add a page to it
// PDDocument document = new PDDocument();
PDDocument document = PDDocument.load(requestPdfIn);
PDPage page = new PDPage(PDRectangle.A4);
document.addPage(page);
COSBase item = document.getPages().getCOSObject().getItem(COSName.KIDS);
((COSUpdateInfo) item).setNeedToBeUpdated(true);
COSArray kids = (COSArray) item;
kids.setNeedToBeUpdated(true);
((COSUpdateInfo) kids.get(0)).setNeedToBeUpdated(true);
document.getPage(0).getCOSObject().setNeedToBeUpdated(true);
page.getCOSObject().setNeedToBeUpdated(true);
document.getPages().getCOSObject().setNeedToBeUpdated(true);
COSDictionary dict = page.getCOSObject();
while (dict.containsKey(COSName.PARENT)) {
COSBase parent = dict.getDictionaryObject(COSName.PARENT);
if (parent instanceof COSDictionary) {
dict = (COSDictionary) parent;
dict.setNeedToBeUpdated(true);
}
}
document.getDocumentCatalog().getCOSObject().setNeedToBeUpdated(true);
//document.getDocumentCatalog().getStructureTreeRoot().getCOSObject().setNeedToBeUpdated(true);
// Save the results and ensure that the document is properly closed:
ByteArrayOutputStream confirmationPdfOut = new ByteArrayOutputStream();
document.saveIncremental(confirmationPdfOut);
document.close();
return confirmationPdfOut.toByteArray();
}
I found in this post that all the COSObjects need to have a flag needToBeUpdated set to true.
However, that still doesn't help when trying to add another page to the document, as the first signature gets invalidated when I try to verify the signature using Acrobat Reader.
Is it even possible? Is it possible with the PDFBox?
No, it is not possible. Adding pages to an already signed PDF is not allowed.
In detail
I read in the adobe documentation under incremental updates that it may be possible.
Indeed it is possible to add changes to a PDF without touching the former revision. Thus, if that former revision was signed, the signature mathematically remains valid, it still signs the correct hash value.
But the PDF specification and its main interpretation (by Adobe that is) contain additional restrictions, cf. this stack overflow answer. As you find there at most the following changes are allowed to signed documents:
Adding signature fields
Adding or editing annotations
Supplying form field values
Digitally signing
At least Adobe Acrobat (Reader) does test for such changes in addition to the check for mathematical validity even if numerous other validation services don't.
Thus, your task to add additional page to the signed PDF and sign it again without breaking the first signature cannot be implemented.

iText verify integrity of a pdf in java

My web server generates a pdf, signs it, and give it to the client. The client sign it multiple times (with different tokens using Adobe Pro) and then upload it back to the server.
I want the server to verify if it is the pdf that was previously generated by the server. I read that the hash is changed when multiple signatures are applied. How can do this verification easily with iText ?
Multiple integrated signatures in the same PDF are applied by means of incremental updates if done properly, cf. this answer on Information Security Stack Exchange:
Thus, while in a way it is true what you read
I read that the hash is changed when multiple signatures are applied.
(indeed, the hash of the whole file changes unless you have an extremely unlikely collision), the hash of the bytes your initial signature applies to remains and can be retrieved from the document.
So depending on what you want to check and what information you still have, there are these obvious options:
Comparing the initial revision
My web server generates a pdf, signs it, and give it to the client
If you still have that initial signed PDF stored somewhere (e.g. in a database) and you want to check whether the PDF the client uploaded back to you is based on that very PDF, all you need to do is check whether the client's PDF byte stream starts with the bytes of the PDF you stored.
This does not require any extra use of cryptographic functions or PDF specific APIs, merely a comparison of bytes or blocks of them.
Checking the signature
My web server generates a pdf, signs it, and give it to the client
If you don't have that initial signed PDF stored anywhere or you only want to check whether the PDF the client uploaded back to you is based on one of many possible base PDFs signed by you, direct revision comparison is impossible or very resource intensive.
In this case you should check whether the initial signature of the document
is valid and
has been created by your server.
Checking the signature validity
There are many examples on how to check the integrity of signatures in a PDF, e.g.
public PdfPKCS7 verifySignature(AcroFields fields, String name) throws GeneralSecurityException, IOException {
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;
}
public void verifySignatures(String path) throws IOException, GeneralSecurityException {
System.out.println(path);
PdfReader reader = new PdfReader(path);
AcroFields fields = reader.getAcroFields();
ArrayList<String> names = fields.getSignatureNames();
for (String name : names) {
System.out.println("===== " + name + " =====");
verifySignature(fields, name);
}
System.out.println();
}
(C5_01_SignatureIntegrity.java from the iText digital signatures white paper)
For your task you actually can simply restrict yourself to checking the first signature only but checking the other ones, too, might also be a good idea.
Checking the signature certificate
To check whether the initial signature has been created by your server, let's assume your web server signs these PDFs using a X509 certificate dedicated to this task (or a known set of such certificates).
You can retrieve the certificate using which the initial signature was created with the method getSigningCertificate of the PdfPKCS7 object you retrieve from the verifySignature call for the first signature. All you have to do is check whether the certificate is the one certificate (or one of the set of certificates) you dedicated for this task.

iText adding ltv info

I'm trying to 'complete to ltv' a pdf that is already signed and i found this code using itext:
http://developers.itextpdf.com/question/how-enable-ltv-timestamp-signature
public void addLtv(String src, String dest, OcspClient ocsp, CrlClient crl, TSAClient tsa)
throws IOException, DocumentException, GeneralSecurityException {
PdfReader r = new PdfReader(src);
FileOutputStream fos = new FileOutputStream(dest);
PdfStamper stp = PdfStamper.createSignature(r, fos, '\0', null, true);
LtvVerification v = stp.getLtvVerification();
AcroFields fields = stp.getAcroFields();
List<String> names = fields.getSignatureNames();
String sigName = names.get(names.size() - 1);
PdfPKCS7 pkcs7 = fields.verifySignature(sigName);
if (pkcs7.isTsp()) {
v.addVerification(sigName, ocsp, crl,
LtvVerification.CertificateOption.SIGNING_CERTIFICATE,
LtvVerification.Level.OCSP_CRL,
LtvVerification.CertificateInclusion.NO);
}
else {
for (String name : names) {
v.addVerification(name, ocsp, crl,
LtvVerification.CertificateOption.WHOLE_CHAIN,
LtvVerification.Level.OCSP_CRL,
LtvVerification.CertificateInclusion.NO);
}
}
PdfSignatureAppearance sap = stp.getSignatureAppearance();
LtvTimestamp.timestamp(sap, tsa, null);
}
I read it has a 'problem' with Adobe because the timestamp applied is not recognized as LTV-enabled and suggests applying a new dss to solve this.
My questions:
Can this info be added before applying the timestamp? If i'm adding a dss to complete, i could add TSA timestamp info (ocsp, crl...) too (maybe with a fake sign to get info) and then apply the timestamp without needing a new dss again.
If yes... this is approved by ETSI? Can iText handle it? I noticed that addVerification adds info from signatures already included, but seems i can't add the required info with this method. There's another way to add 'free' verifications or addVerification let's me and i didn't notice?
If no... Why? Then why i not need to timestamp again the new dss added?
As you can see... i'm not an expert and i need some help.
Thanks a lot for your help!
My questions:
Can this info be added before applying the timestamp? If i'm adding a dss to complete, i could add TSA timestamp info (ocsp, crl...) too (maybe with a fake sign to get info) and then apply the timestamp without needing a new dss again.
If yes... this is approved by ETSI? Can iText handle it? I noticed that addVerification adds info from signatures already included, but seems i can't add the required info with this method. There's another way to add 'free' verifications or addVerification let's me and i didn't notice?
If no... Why? Then why i not need to timestamp again the new dss added?
Technically you can add any validation related information before applying the signature / time stamp the relate to. Actually you even have to do this in case of ol'fashioned ISO 32000-1 signatures which required validation information to be in a signed attribute.
Whether such information are accepted by verifiers, depends.
ETSI TS 102 778-4 V1.1.1 says:
4.3 Validation Process
It is recommended that that validation process be as follows:
The "latest" document Time-stamp should be validated at current time with validation data collected at the
current time.
The "inner" document Time-stamp should be validated at previous document Time-stamp time with the
validation data present (and time-stamped for the successive enveloping time-stamps) in the previous DSS.
The signature and the signature Time-stamp should be validated at the latest innermost LTV document Timestamp
time using the validation data stored in the DSS and time-stamped (by the successive enveloping timestamps)
Validation of documents without document Time-stamps is outside the scope of this profile.
If a verifier validates according to these recommendations, it will not accept your validation information as you want it to, at least it will not recognize the time stamp stamping the information for its validation.
But as these only are recommendations and other TS or EN documents might recommend differently, the verifiers you are interested in may accept your validation information as desired by you.

LTV of Certifying Signatures

I've been working with iText to do digital signatures on PDF files for the past weeks and based on what i've understood that there is two ways to add the information to the PDF to make it LTV enabled:
Adding the information with the code provided in the iText example, this method requires the signature to be already present because the DSS & VRI dictionaries it creates references the signature.
Embedding the crl bytes & ocsp response in the signature at signing time.
Eventhough the first method results in a nice and tidy pdf file the problem with is is that it modifies the pdf file to create/append the entries which results in an invalidation of the certifying signature,
the second one works fine but it increases the pdf size substantially depending on the size of the crl list (that will also probably increase overtime).
Wrapping up, is there any other way to make the certifying signature LTV enabled other than embedding the information in the signature itself? Is there any way to create the dds/vri dictionaries at signing time?
EDIT: Here's more info as requested on the comments:
The code used to add the ltv information:
public static void processDocumentLtv(String filePath) throws IOException, GeneralSecurityException, DocumentException {
long startTime = System.currentTimeMillis();
File original = new File(filePath);
File temp = new File(filePath + ".ltv");
PdfReader reader = new PdfReader(filePath);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(temp), '\0', true);
LtvVerification ltvVerification = stamper.getLtvVerification();
OcspClient ocspClient = new OcspClientBouncyCastle();
AcroFields fields = stamper.getAcroFields();
List<String> signatureNames = fields.getSignatureNames();
String sigName = signatureNames.get(signatureNames.size() - 1);
PdfPKCS7 pkcs7 = fields.verifySignature(sigName);
Certificate[] chain = pkcs7.getSignCertificateChain();
X509Certificate x509certificate = pkcs7.getSigningCertificate();
byte[] ocspResponse = ocspClient.getEncoded(x509certificate, CertificateUtils.getParent(x509certificate, chain), null);
Collection<byte[]> crlBytes = CertificateUtils.fetchCrlBytes(x509certificate, chain);
Collection<byte[]> ocspBytes = null;
if (ocspResponse != null) {
ocspBytes = new ArrayList<>();
ocspBytes.add(ocspResponse);
}
ltvVerification.addVerification(sigName, ocspBytes, crlBytes, null);
ltvVerification.merge();
stamper.close();
reader.close();
Files.copy(temp.toPath(), original.toPath(), StandardCopyOption.REPLACE_EXISTING);
temp.delete();
logger.info("Took {}ms to do add the ltv information to the document.", (System.currentTimeMillis() - startTime));
}
The document samples:
Before trying to add LTV data: https://www.dropbox.com/s/79ll23ndt9mbh3g/pdf-sample-pre-ltv.pdf?dl=0
After running through the code above: https://www.dropbox.com/s/hjl73es6hrqspi3/pdf-sample-post-ltv.pdf?dl=0
I'm using Adobe Reader DC v15.017.20053 as my PDF Viewer.
Observations with the sample file
I performed some tests with the OP's sample PDF. Indeed, Adobe Acrobat (Reader) does not like PAdES-4 LTV additions generated by iText to a PDF with no-changes-allowed certification, and it is a bit brain-split about it, saying both
Some of the changes that have been made to this document since this signature was applied are not permitted by the document author.
and
There have been no changes made to this document since this signature was applied.
(Adobe Acrobat signature properties dialog after pressing Compute Modifications List)
This situation remained even when I removed any change in excess of the addition of the LTV information (iText additionally adjusts the document modification date meta data), I eventually even removed the added Extensions entry ESIC (BaseVersion 1.7, ExtensionLevel 5) which indicates to a PDF viewer that PAdES-4 LTV content may be present, only the DSS reference and contents remained.
Thus, Adobe Acrobat violates the PAdES-4 specification which requires
DocMDP restrictions (see ISO 32000-1 1 clause 12.8.2.2) shall not apply to incremental updates to a PDF document containing a DSS dictionary and associated VRI, Certs, CRLs and OCSPs.
(ETSI TS 102 778-4 V1.1.2 (2009-12) Annex A.1 Document Security Store)
even though Leonard Rosenthol (the Adobe PDF evangelist at the time) assured on the iText mailing list
I checked with my engineers and they indeed verified that LTV is fully supported on DocMDP/Cert signatures.
(Reply to "Verify certified (with transform method DocMDP) signatures" dated Jan 17, 2012; 3:15pm)
I have not checked two options, though, probably Adobe Acrobat only adheres to the PAdES-4 requirement above if the certification signature is a PAdES-3 signature, or if the certified document already in the just certified version at least contains an Extensions entry ESIC (BaseVersion 1.7, ExtensionLevel 5).
The document at hand contains a legacy ISO 32000-1 signature (which can be regarded a PAdES-2 signature but which can also be regarded a PAdES-unaware signature) and indicates PDF version 1.3 without an ESIC extension entry.
Before finally calling it an Adobe Acrobat bug, I'd try changing using a PAdES-3 signature and the ESIC extension entry (or an ADBE one according to PAdES-4 section 4.4).
The question itself
Wrapping up, is there any other way to make the certifying signature LTV enabled other than embedding the information in the signature itself? Is there any way to create the dds/vri dictionaries at signing time?
The PAdES-4 additions are described as referring to signatures in prior revisions of the document, not to signatures added in the same revision. Thus, while it would be technically possible to add the information in the same revision, there is no guarantee they will be used by a conforming PDF viewer.

Categories

Resources