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.
Related
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.
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.
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.
So, my question has some very specific parts, but I'll try to do my best to leave those out as much as possible.
What I'm trying to do: I'm inserting my eID card (electronic ID card). I should, theoretically, be able to save the keystore and certificates that are on it with Java KeyStore. I can load the eID card as such:
/*Load from eID*/
KeyStore keyStore = null;
try (FileOutputStream fos = new FileOutputStream(Constants.fullFileEID)) {
/*Load*/
Security.addProvider(new BeIDProvider());
keyStore = KeyStore.getInstance("BeID");
keyStore.load(null);
So I'm loading the keyStore (by adding the provider, which is possible thanks to an external library I added). For the record: Constants is a constants class I'm using and fullFileEID is the full filepath to where the EID should be saved (the path, except for the file itself, exists).
Then I try to print some details off (I'm logging to a file as I get a pretty huge log already in my System.out, just to keep things clear):
/*Test*/
FileLogging.writeToFile("Type: " + keyStore.getType());
FileLogging.writeToFile("Authentication certificate: " + keyStore.getCertificate("Authentication").getClass());
FileLogging.writeToFile("Authentication serialized: " + Serialization.serialize(keyStore.getCertificate("Authentication")));
FileLogging.writeToFile("Signature certificate: " + keyStore.getCertificate("Signature").getClass());
FileLogging.writeToFile("Signature serialized: " + Serialization.serialize(keyStore.getCertificate("Signature")));
FileLogging.writeToFile("Provider: " + keyStore.getProvider());
FileLogging.writeToFile("Should be saved in " + Constants.fullFileEID);
I get a lot of interesting details in there. It's not null, everything seems loaded perfectly (there are two aliases in my keyStore, "Authentication" and "Signature"). For the record: Serialization is another Helper class of mine, which will serialize and Base64-encode the object it receives - the serialize function receives a Serializable as parameter.
This is what I get (I truncated the serialized Strings):
Type: BeID
Authentication certificate: class sun.security.x509.X509CertImpl
Authentication serialized: rO0ABXNyAC1qYXZhLnNlY3VyaXR5LmNlcnQuQ2VydGlma[...]
Signature certificate: class sun.security.x509.X509CertImpl
Signature serialized: rO0ABXNyAC1qYXZhLnNlY3VyaXR5LmNlcnQuQ2VydGlmaWNhdG[...]
Provider: BeIDProvider version 1.0
Should be saved in [fullFilePath]\eID.jks
So the certificates itself are from the "normal" classes, they're not subclassed so nothing special that I'm not aware of should be present in the serialization methods... Hence, the serialization itself also works perfectly.
Ok, so tests are positive, I think: time to actually store my file.
/*Save*/
keyStore.store(fos, "xxxx".toCharArray());
System.out.println("keystore: ");
keyStore.store(System.out, "xxxx".toCharArray());
} catch (Exception e) {
Util.handleLogging(e);
}
So, I use the FileOutputStream I created above to store the keyStore to. ("xxxx" is the pincode - I'm not sure whether it needs to be the pincode of the card, as I could already read and print the contents of the card certificated without ever giving any pincode). I also try to "print" the output to System.out.
Now is the funny part: the file created is empty. It is created by this function (or an earlier file at least is overwritten). If the file doesn't exist is gets created. But it's 0kb big, and of course, it doesn't work.
What am I doing wrong? I really don't see what's wrong? I'd suspect if anything went wrong during the actual saving, I'd get an exception at least (like CertificateException, which should be thrown 'if any of the certificates included in the keystore data could not be stored' along the docs)?
I have to admit the fact that I don't like that no-one needs to provide a pin code for me to be able to read the data from the card. That doesn't feel right to me.
The Belgian eID has a specific implementation of the Java Keystore. If you have a look into the implementation be.fedict.commons.eid.jca.BeIDKeyStore, then you will see that the store method isn't implemented.
#Override
public void engineStore(final OutputStream stream, final char[] password)
throws IOException, NoSuchAlgorithmException, CertificateException {
}
I had the same problem and I haven't found a solution yet.
I've been working on settting passwords on PDFs to prevent copy/paste and allow printing, add watermarks and set an owner password to prevent further changes.
Everything works well as expected, no issue there.
Then I downloaded this free for 15 days pdf removal tool, a-pdf. In a blink it removes all protection, no matter the complexity of the password (tried with 50 char length will all kind of chars).
I see there are other methods in itextPDF to encrypt a document. I used the following:
File f = new File("C:/TEMP/zip/waterMarked.pdf");
String hardPassword = "D 5BaIZQ# CqAk+NQCW)7Dkgb#i&02ifu!2TMX*d 0TGK(j(Kq";
byte[] hardPasswordByte = hardPassword.getBytes();
PdfReader reader = new PdfReader("C:/TEMP/zip/Original_document-9.pdf");
FileOutputStream out = new FileOutputStream(f);
PdfStamper stamp = new PdfStamper(reader, out);
//first argument is the user password. If set to something it asks for password when opening file, not wanted.
stamp.setEncryption(null, hardPasswordByte, PdfWriter.ALLOW_PRINTING, true);
//do stuff on the stamper, save file.
Does anyone knows a better way to protect PDF documents from Java code ?
PDF files support 2 passwords: user password and owner password. A user can view the PDF file if he knows any of these passwords. If the file has a user password, when the file is opened with a PDF viewer, the viewer asks the user to enter a password and either the user or owner passwords will work. If the file has only an owner password, the document is displayed automatically and the password is required when trying to change the file's access rights. This is the flow of operations suggested by PDF specification, but in reality it works like this: if the file is protected with a user password, brute force approach is required for password cracking, the longer the password is the longer it takes to crack. Problem is your real users need the password to open the file. If the file is protected only with an owner password, there is a default decryption key (remember, any viewer can display the PDF file without requesting a password) and the application that processes the PDF file decides whether to respect or not the document access rights. Once the file has been decrypted, it is saved without encryption and the output file has no longer a password. Since your documents have only the owner password, the tool removes it without problems using the default decryption key.
There are a few solutions (more or less related to iText) depending on your audience:
simple PDF encryption (with the problems above) if your audience is widespread, for example you publish papers on a website; 3rd party DRM solution, more complex and requires various plugins installed on your users' computers; certificate encryption (no sure if iText supports it), again complex, requires each user to have a digital certificate and documents access is defined for each user. Last 2 options work in a controlled enterprise environment.