0
votes

I am using itext7 version 7.1.5 in my application. My scenario is as following: - Take the hash of the document - Sign the hash from external signing server and get Pkcs7 - Embed the signed pkcs7 into the PDF document using itext7

After embedding adobe fails to validate my signature in the document. When I check the signature structure, the signature structure is wrong.

Here is the step wise code:

1: Document Hashing:

string hashAlgorithm = "SHA256";

Stream documentStream = new MemoryStream(_latestDocumentBytes);

PdfSigner _pdfSigner = new PdfSigner(pdfReader, outputStream, new StampingProperties());

_pdfSigner.SetFieldName("Signature1");

ImageData imageData = ImageDataFactory.Create(imageBytes);

PdfSignatureAppearance sigAppearance = _pdfSigner.GetSignatureAppearance();
sigAppearance.SetContact("ContactInfo");
sigAppearance.SetLocation("Location");
sigAppearance.SetPageNumber(1);
sigAppearance.SetReason("SigningReason");
sigAppearance.SetSignatureGraphic(imageData);
sigAppearance.SetRenderingMode(sigAppearance.RenderingMode.GRAPHIC)
sigAppearance.SetSignatureCreator("Malik");

PdfSignature signature = new PdfSignature(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
signature.SetContact(sigAppearance.GetContact());
signature.SetDate(new PdfDate(DateTime.Now));
signature.SetLocation(sigAppearance.GetLocation());
signature.SetReason(sigAppearance.GetReason());
signature.SetSignatureCreator(sigAppearance.GetSignatureCreator());
signature.MakeIndirect(_pdfDocument);

documentHash = DigestAlgorithms.Digest(documentStream, DigestAlgorithms.SHA256);

SHA256 sha256 = new SHA256Managed();
byte[] documentHash = sha256.ComputeHash(documentHash);

2: Get signing hash from document server

3: Embedding signature to PDF

Stream readerStream = new MemoryStream(_latestDocumentBytes);

PdfPKCS7 pdfPKCS7 = new PdfPKCS7(pdfSignatureBytes, PdfName.Adbe_pkcs7_detached);

PdfSignatureAppearance signatureAppearance = _pdfSigner.GetSignatureAppearance();

signatureAppearance.SetCertificate(pdfPKCS7.GetSigningCertificate());

signature.SetContents(pdfSignatureBytes);

IExternalSignatureContainer externalSignatureContainer = new ExternalBlankSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);

_pdfSigner.SignExternalContainer(externalSignatureContainer, 8192);

_latestDocumentBytes = ((MemoryStream)outputStream).ToArray();

My signature structure difference is as follows:

  • Correct signature structure:

    <</Type/Sig/Reason(I have approved ad signed the document)/Contents ><[CONTENT]>/Prop_Build<</App<</Name/Malik>>>>/ByteRange [0 10857 522859 2584 >]                                                          >/SubFilter/adbe.pkcs7.detached/Filter/Adobe.PPKLite/M(D:20190719103520+00'00>>')/ContactInfo(923399999999)/Name(John Clark)/Location(Pakistan)>>
    
  • InCorrect (Itext Implementation):

    <</ByteRange [0 157 16543 260086 ]  [Large Space] >/ContactInfo([email protected])/Contents ><[CONTENT]>/Filter/Adobe.PPKLite/Location(Pakistan)/M(D:20190719154813+05'00'>)/Prop_Build<</App<</Name/Malik>>>>/Reason(Test Signing >Reason)/SubFilter/adbe.pkcs7.detached/Type/Sig>>
    

While opening the signed PDF in Adobe, an error displayed while validating the signature that ERROR ENCOUNTERED WHILE BER DECODING

Original document is here: https://www.dropbox.com/s/ajscg8j74opuwxe/SigFieldDoc%20-%20Original.pdf?dl=0

Signed document is here: https://www.dropbox.com/s/h72u360rl5iy6fq/SigFieldDoc%20-%20AfterSign.pdf?dl=0

Any help in this regard will be highly appreciated.

1
That sounds like your signature container is broken. And both of your "signature structures" are completely broken. Thus, please share a sample PDF signed by your code for analysis. And show more of your code for signing with iText 7. - mkl
@mkl I have updated the question with all the code and also add the document links of both unsigned and signed documents. - Muddassir Awan
In your PDF the Contents of the signature value contains only 0x00 bytes. This is where your signature value should be.It's plausible, though, that there are only those 0x00 bytes, ExternalBlankSignatureContainer contains the word Blank for a reason... - mkl
Ok, I looked though your code. But it unfortunately contains numerous errors, so it's much easier to start anew than try to repair it. I'll try to look into that later. One question, though: You mention you use an external signing server. What does it return? A plain signature value? (Which signing algorithm? Do you have the signer certificate available beforehand?) Or a full-fledged CMS signature container? - mkl
Yes that is also the issue in the document that in PDF the signature visibility is OK and signature field is rendered properly but while checking the internal structure the signature content is shown as 0x00 bytes. - Muddassir Awan

1 Answers

1
votes

To sign a PDF with iText 7 using a remote server to create the actual cryptographic signature, you can simply implement an iText signature interface which executes the call of the remote service in its Sign method for the given data.

Furthermore, you clarified in a comment that your

external signing server returns PKCS7 signature signed data.

Thus, the interface to use is an IExternalSignatureContainer (and not a IExternalSignature which one would have used if the server returned merely the primitive signature bytes).

I don't know how you communicate with your server; in particular I don't know whether the API you use requires you to provide the complete signed data or merely the hash thereof; and in the former case, whether to provide them as stream or array. Thus, I put hints into the Sign method for each case:

public class ExternalServiceSignatureContainer : IExternalSignatureContainer
{
    public void ModifySigningDictionary(PdfDictionary signDic)
    {
        signDic.Put(PdfName.Filter, PdfName.Adobe_PPKLite);
        signDic.Put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached);
    }

    public byte[] Sign(Stream data)
    {
        // Call your external signing service to create a CMS signature container
        // for the data in the InputStream

        // Depending on your API to access that service you may either be able to
        // directly call it with the stream
     // return YOUR_SIGNING_API_CALL_FOR_STREAM(data);
        // (or a byte[] generated from the stream contents)
     // return YOUR_SIGNING_API_CALL_FOR_ARRAY(StreamUtil.InputStreamToArray(data));
        // as parameter, or you may first have to hash the data yourself
        // (e.g. as follows) and send your hash to the service.
     // byte[] hash = DigestAlgorithms.Digest(data, DigestAlgorithms.SHA256);
     // return YOUR_SIGNING_API_CALL_FOR_HASH(hash)

        // dummy
        return new byte[0];
    }
}

You can then use this class as follows:

PdfReader pdfReader = new PdfReader(DOCUMENT_TO_SIGN);
PdfSigner pdfSigner = new PdfSigner(pdfReader, RESULT_STREAM, new StampingProperties().UseAppendMode());

pdfSigner.SetFieldName("Signature1");

ImageData imageData = ImageDataFactory.Create(SIGNATURE_IMAGE);

PdfSignatureAppearance sigAppearance = pdfSigner.GetSignatureAppearance();
sigAppearance.SetContact("ContactInfo");
sigAppearance.SetLocation("Location");
sigAppearance.SetPageNumber(1);
sigAppearance.SetPageRect(new Rectangle(100, 500, imageData.GetWidth() / 2, imageData.GetHeight() / 2));
sigAppearance.SetReason("SigningReason");
sigAppearance.SetSignatureGraphic(imageData);
sigAppearance.SetRenderingMode(RenderingMode.GRAPHIC);
sigAppearance.SetSignatureCreator("Malik");

pdfSigner.GetDocument().GetCatalog().SetModified();

int estimatedSize = 12000;
pdfSigner.SignExternalContainer(new ExternalServiceSignatureContainer(), estimatedSize);

Some remarks:

I added

sigAppearance.SetPageRect(new Rectangle(100, 500, imageData.GetWidth() / 2, imageData.GetHeight() / 2));

because I also used a different document to test. As that other document does not originally contain a signature field named Signature1, I needed to set the rectangle to get a visible signature. In case of your document which already has such a signature field, that line can be dropped but is ignored anyways.

The line

pdfSigner.GetDocument().GetCatalog().SetModified();

needs to be added because of a small bug in iText: When signing an existing field (not a new field), the AcroForm dictionary (in particular if contained directly in the Catalog like in your document), is not marked as modified and, therefore, not updated in the incremental update. This prevents the SigFlags in your document from being set which causes Adobe Reader not display the top signature bar.

You might want to change the estimate

int estimatedSize = 12000;

of the signature container size, in particular if the signature containers you retrieve always are much smaller or sometimes larger than 12000 bytes.