Reputation: 33
I have an application that generates a PDF, and that needs to be signed.
We have not the certificates to sign the document, because they're in a HSM, and the only way we could make use of the certificates is using a webservice.
PdfReader reader = new PdfReader(src);
reader.setAppendable(true);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileOutputStream fout = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, fout, '\0');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason("Test");
appearance.setLocation("footer");
appearance.setVisibleSignature(new Rectangle(100, 100, 200, 200), 1, null);
appearance.setCertificate(certChain[0]);
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.setReason(appearance.getReason());
dic.setLocation(appearance.getLocation());
dic.setContact(appearance.getContact());
dic.setDate(new PdfDate(appearance.getSignDate()));
appearance.setCryptoDictionary(dic);
HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, new Integer(8192 * 2 + 2));
appearance.preClose(exc);
ExternalDigest externalDigest = new ExternalDigest()
{
public MessageDigest getMessageDigest(String hashAlgorithm) throws GeneralSecurityException
{
return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
}
};
PdfPKCS7 sgn = new PdfPKCS7(null, certChain, "SHA256", null, externalDigest, false);
InputStream data = appearance.getRangeStream();
byte[] hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));
Calendar cal = Calendar.getInstance();
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);
String hashPdf = new String(Base64.encodeBytes(sh));
String hashSignat = hashPdf;
This is our code, first, we get the signature appearance, and calculate the hash
In this point, we have the hash code of the document. Then we send the hash to the webservice, and we get the signed hash code.
Finally, we put the signed hash to the PDF:
sgn.setExternalDigest(Base64.decode(hashSignat), null, "RSA");
byte[] encodedSign = sgn.getEncodedPKCS7(hash, cal, null, null,
null, CryptoStandard.CMS);
byte[] paddedSig = new byte[8192];
System.arraycopy(encodedSign, 0, paddedSig, 0,
encodedSign.length);
PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
appearance.close(dic2);
In this point, we get a PDF signed, but with an invalid signature. Adobe says that "Document has been altered or corrupted since it was signed".
I have gone through Sign PDF using an external service and iText, PDF signature itext pkcs7 multi sign and Is it possible to sign a PDF document with hash and signed hash? but no luck.
Upvotes: 2
Views: 6335
Reputation: 96029
You shared an example file signed by your code in a comment to your question.
An analysis of that file (executed using the AnalyzeSignatures test testPriyankaSignatureSampleinformedconsent_Signed
) shows that the actually signed hash is
1134ED96F42AC7352E546BE0E906C0BF5D44229AEAFAC39145DB40A0BB7E817B
which should be the hash of the authenticated attributes but their hash is
E7101D9770ABF5E58D43670AAC6E9418265AE80F74B3BDFB67911C0CC5D5D949.
The signed byte ranges hash is
D7917CF3BA9FE5D5B626ED825965D699F7C54DBBF9F18DECE18EF8DD36DC4C28,
so its not this hash, either. Thus it was not clear where that signed hash came from.
It eventually turned out that
the encoding utility on web service was different due to which the decoded hash value was wrong and
MessageDigest(sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);)
is not needed. this line was giving the wrong hash.
Thereafter a new issue popped up:
when I am signing file which is already signed is getting sign but with an invalid signature. its showing "the signature byte range is invalid".
For signing already signed pdfs you have to use the append mode. For that you need a different PdfStamper.createSignature
overload with one more parameter, a boolean
, which you set to true
.
The reason is that usually (i.e. without activating the append mode) iText reorganizes the internal PDF structures and drops unused objects. In already signed PDFs this usually moves the position of the (already existing) signature which invalidates the signature structure. Using append mode, though, iText keeps the original PDF bytes as they were and only appends new data.
I m just confused that why the value of byte "sh" is getting change for the same file every time? actually, I want to store the hash value of the file. is it because of "cal"?
Indeed, each time you start manipulating a PDF, the result gets a new unique ID. Furthermore, the modification time is stored. And in case of a signing use case, also the signing time differs.
can I get the hash at a time and store it in db and sign the hash later and append in pdf?
You either have to
1st option is not possible for me. I didn't get the 2nd 3rd option. can you elaborate both or give any reference which I can refer?
Ok, first off the third option, patching iText, usually is something you don't want to do because it makes incorporating later iText updates difficult.
OpenPdf (an older iText fork) contains a patch adding EnforcedModificationDate
, OverrideFileId
, and IncludeFileID
properties to PdfStamper
. (PdfSignatureAppearance
already has a SignDate
property.) This patch has been applied to allow eSignature DSS to use OpenPdf in its signing process (which also includes creating the signed PDF twice and so requires fixed hash values).
You might not want to switch to this old iText fork because it misses many fixes and new options of newer iText versions.
Thus, you probably should instead keep the originally created file with an empty signature, e.g. in some temporary folder, and apply deferred signing as soon as you retrieve the final signature.
This essentially is what the iText example C4_09_DeferredSigning is all about, it first creates an intermediary PDF with everything in it, only the signatrue bytes are missing:
public void emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, fieldname);
appearance.setCertificate(chain[0]);
ExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.signExternalContainer(appearance, external, 8192);
}
Only in a second step an actual signature is injected:
public void createSignature(String src, String dest, String fieldname, PrivateKey pk, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
ExternalSignatureContainer external = new MyExternalSignatureContainer(pk, chain);
MakeSignature.signDeferred(reader, fieldname, os, external);
}
using
class MyExternalSignatureContainer implements ExternalSignatureContainer {
protected PrivateKey pk;
protected Certificate[] chain;
public MyExternalSignatureContainer(PrivateKey pk, Certificate[] chain) {
this.pk = pk;
this.chain = chain;
}
public byte[] sign(InputStream is) throws GeneralSecurityException {
try {
PrivateKeySignature signature = new PrivateKeySignature(pk, "SHA256", "BC");
String hashAlgorithm = signature.getHashAlgorithm();
BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
byte hash[] = DigestAlgorithms.digest(is, digest.getMessageDigest(hashAlgorithm));
Calendar cal = Calendar.getInstance();
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
byte[] extSignature = signature.sign(sh);
sgn.setExternalDigest(extSignature, null, signature.getEncryptionAlgorithm());
return sgn.getEncodedPKCS7(hash, cal, null, null, null, CryptoStandard.CMS);
}
catch (IOException ioe) {
throw new ExceptionConverter(ioe);
}
}
public void modifySigningDictionary(PdfDictionary signDic) {
}
}
Here the has is calculated in the second step, but you don't need to do so, you can
ExternalBlankSignatureContainer
as in emptySignature()
above but instead extending it to calculate the hash as MyExternalSignatureContainer
does,MyExternalSignatureContainer
that does not calculate the hash but instead injects exactly the returned signature.Upvotes: 2