PDF External signing service i need to send the DTBS and receive a hashed byte[] and also the public certificates chain only

First of all i really like the simplicity of Syncfusion Controls, but  currently i have a rather complex scenario to deal with.

Exposing the case: i have am external service that i need to send the DTBS portion of the PDF and hash it to SHA256, then external service returns the public certificates chain (SIGN, ROOT, CA), Note i don't have any pfx or private key, only the return of a signed hash from the service and public certificates chain, do you support this scenario? 

Unfortunately for me is not very clear how to anchieve this based on the documentation provided  Working with Digital Signature | Syncfusion.


I do have a working sample from another PDF library (Itext) that i found could be done with , code below as follows:


publicstaticvoidPreparePdfForSignage()
   {
       byte[] pdf =System.IO.File.ReadAllBytes("../original.pdf");
       string certificate =Convert.ToBase64String(System.IO.File.ReadAllBytes("../Certificate.cer"));
       string digestAlgorithmOid ="2.16.840.1.101.3.4.2.1";
       var fieldName ="Signature1";


       byte[] preparedBytes;
       CMSContainer cmsContainer;
       usingMemoryStream unsignePdfStream =new(pdf);
       usingMemoryStream preparedPdfStream =new();
       using(PdfReader reader =newPdfReader(unsignePdfStream))
       using(MemoryStream outputStream = preparedPdfStream)
       {


           var parser =BcFactory.CreateX509CertificateParser();
           IX509Certificate[] certificateChain =[.. parser.ReadAllCerts(Convert.FromBase64String(certificate))];


           string digestAlgorithm =DigestAlgorithms.GetDigest(digestAlgorithmOid);


           // 1. Preparing the container to get a size estimate
           cmsContainer =newCMSContainer();
           cmsContainer.AddCertificates(certificateChain);
           cmsContainer.GetSignerInfo()
               .SetSigningCertificateAndAddToSignedAttributes(certificateChain[0], digestAlgorithmOid);
           // In a later version the default algorithm is extracted from the certificate


           var algorithmIdentifier =GetAlgorithmOidFromCertificate(certificateChain[0]);
           cmsContainer.GetSignerInfo().SetSignatureAlgorithm(algorithmIdentifier);
           cmsContainer.GetSignerInfo().SetDigestAlgorithm(new iText.Signatures.Cms.AlgorithmIdentifier(digestAlgorithmOid));




           // Get the estimated size
           long estimatedSize = cmsContainer.GetSizeEstimation();


           var digest =BcFactory.CreateIDigest(digestAlgorithm);
           // Add enough space for the digest
           estimatedSize += digest.GetDigestLength()*2L+2;
           // Duplicate the size for conversion to hex
           estimatedSize *=2;




           PdfTwoPhaseSigner signer =newPdfTwoPhaseSigner(reader, outputStream);
           signer.SetStampingProperties(newStampingProperties().UseAppendMode());



           // 2. Prepare the document by adding the signature field and getting the digest in return
           byte[] documentDigest = signer.PrepareDocumentForSignature(signerProperties, digestAlgorithm,
                   PdfName.Adobe_PPKLite,PdfName.Adbe_pkcs7_detached,(int)estimatedSize,true);


           // 3. Add the digest to the CMS container, because this will be part of the items to be signed
           cmsContainer.GetSignerInfo().SetMessageDigest(documentDigest);
           outputStream.Close();
           preparedBytes = outputStream.ToArray();
       }


       // 4. This step is completely optional. Add the CMS container to the document
       // to avoid having to build it again, or storing it separately from the document


       using(PdfReader reader =newPdfReader(newMemoryStream(preparedBytes)))
       using(PdfDocument document =newPdfDocument(reader))
       using(MemoryStream outputStream =newMemoryStream())
       {
           PdfTwoPhaseSigner.AddSignatureToPreparedDocument(document, fieldName, outputStream,
                   cmsContainer.Serialize());
           outputStream.Close();


           varPreparedFile= outputStream.ToArray();
           varBytesToSign= cmsContainer.GetSerializedSignedAttributes();


           // 5. The serialized signed attributes is what actually needs to be signed
           // sometimes we have to create a digest from it, sometimes this needs to be sent as is.
           using(System.IO.FileStream fs =System.IO.File.Create("../PreparedFile.pdf"))
           {
               fs.Write(PreparedFile,0,PreparedFile.Length);
           }


   // BytesToSign are the byte[] array needed to be sent to externa service, that i will hash in SHA256
           using(System.IO.FileStream fs =System.IO.File.Create("../DTBSbytestosign"))
           {
               fs.Write(BytesToSign,0,BytesToSign.Length);
           }
       }


   }


 Then i will reread document and sign, adding the signed hash to the cms container.

Snippet
public static void SignPreparedPdf()
   {
       byte[] preparedPdf = System.IO.File.ReadAllBytes("../PreparedFile.pdf");
       string fieldName = "Signature1";
       byte[] signature = System.IO.File.ReadAllBytes("../SignedHashBytes");
 
 
       using (PdfReader reader = new PdfReader((new MemoryStream(preparedPdf))))
       using (PdfDocument document = new PdfDocument(reader))
       using (MemoryStream outputStream = new MemoryStream())
       {
           // 1. Read the documents CMS container
           SignatureUtil su = new SignatureUtil(document);
           PdfSignature sig = su.GetSignature(fieldName);
           PdfString encodedCMS = sig.GetContents();
           byte[] encodedCMSdata = encodedCMS.GetValueBytes();
           CMSContainer cmsContainer = new CMSContainer(encodedCMSdata);
 
           // 2. Add the signatureValue to the CMS
           cmsContainer.GetSignerInfo().SetSignature(signature);
 
           PdfTwoPhaseSigner.AddSignatureToPreparedDocument(document, fieldName, outputStream,
                   cmsContainer.Serialize());
 
           outputStream.Close();
 
           using (System.IO.FileStream fs = System.IO.File.Create("../FinalSigned.pdf"))
           {
               fs.Write(outputStream.ToArray(), 0, outputStream.ToArray().Length);
           }
       }

So would be very appreciated if i could be pointed out how to anchieve the same result in Syncfusion PDF Library if possible!


6 Replies

IJ Irfana Jaffer Sadhik Syncfusion Team March 10, 2025 04:06 PM UTC

Hi Nuno,

Thank you for your positive feedback on Syncfusion controls. Regarding your complex scenario involving external digital signatures, Syncfusion's .NET PDF Library does support external signing processes similar to the one you've implemented with iText.

Our library allows you to sign PDF documents using external digital signatures created from various sources, such as Hardware Security Modules (HSM), USB tokens, smart cards, or other cloud services:

How digitally sign a PDF document using the USB token in WF application?

For detailed guidance on implementing external signatures, you can refer to our knowledge base article: 

https://support.syncfusion.com/kb/article/9463/digitally-sign-pdf-document-with-an-external-signature-using-c-and-vb-net?utm_source=chatgpt.com

Additionally, we have a GitHub repository with examples demonstrating how to digitally sign PDFs using C#, including scenarios with external signatures: 

PDF-Examples/Digital Signature/Externally-sign-a-PDF-document/.NET at master · SyncfusionExamples/PDF-Examples

These resources should help you achieve the desired functionality using Syncfusion's PDF Library.Please try this on your end and let us know the feedback.


Regards,

Irfana J





NR Nuno Relvao replied to Irfana Jaffer Sadhik March 10, 2025 05:02 PM UTC

Thank you for the fast answer, i will check it out and try to implement it and give feedback afterward.



NR Nuno Relvao March 10, 2025 08:43 PM UTC

I gave a look at some samples and maybe i am missing something ,but still can't find something related to the sample i provided.

Please note  that i would need a 2 phase process in the sense that i would need to create the pdf with the signing appearance etc and create the signing field and only add the public certificate chain at my disposal.

Then i need to send the signing DTBS portion of the signature field to be signed, so that in a 2nd phase i can reopen the pdf and sign it with the hash provided by the external service.


Would be of great help if ir could be provided a working sample close to what i have done in iText , Unfortunately on all your samples i still don't see a closed enough sample.

With your samples i could easily with  sign a pdf using a pfx certificate that has a private key, but as i mentioned this is not the case or scenario i am looking for.

Looking fforward if possible for a close  sample to what i provided on this thread . Would be amazingly helpfull.

Thanks.




IJ Irfana Jaffer Sadhik Syncfusion Team March 11, 2025 02:54 PM UTC

HI Nuno,

We have prepared a sample to achieve your requirement. Please try this on your end and let us know the result.

  1. Phase 1 – Create an empty signature field
    • Loads an existing PDF.
    • Adds a placeholder signature (without actual signing).
    • Embeds the public certificate.
    • Outputs a PDF with an empty signature field (EmptySignature.pdf).
  2. Phase 2 – Perform deferred signing
    • Loads the EmptySignature.pdf.
    • Replaces the empty signature with a signed hash from an external service.
    • Produces the final signed PDF (DeferredSign.pdf).

How this relates to your scenario:

  1. 2-phase signing process:
    • First, the PDF is prepared with the signature field and public certificate.
    • Later, you replace the empty signature with a valid signature from an external service.
  2. External service integration:
    • The SignEmpty class simulates sending the document hash to an external service.
    • The ExternalSigner class applies the signed hash to complete the signature.

This sample closely mirrors the iText 2-phase deferred signing approach.


Regards,

Irfana J.



Attachment: DeferredsigninginPDFdocument_1ece42bc.zip


NR Nuno Relvao March 11, 2025 03:23 PM UTC

Hi, thanks again  for the quick reply and the example, i did try this approach but with no luck , what is confusing for me is the fact that as i explained i dont have a Private Key  on the certificate neither is a pfx, so the method to sign the Hash on The SignEmpty class will never return the byte[] array to sign. 


Snippet
// Load the PFX file containing the private key. Here we are using pfx file for demonstrate this.
           X509Certificate2 digitalID = new X509Certificate2(Path.GetFullPath(@"../../../Data/PDF.pfx"), "password123"); 
 
           // Sign the document hash using the available RSA provider
           if (digitalID.PrivateKey is RSACryptoServiceProvider rsaProvider)
           {
               Program.SignedHash = rsaProvider.SignData(documentHash, HashAlgorithm);
           }
           else if (digitalID.PrivateKey is RSACng rsaCng)
           {
               Program.SignedHash = rsaCng.SignData(documentHash, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
           }
           else if (digitalID.PrivateKey is RSAOpenSsl rsaOpenSsl)
           {
               Program.SignedHash = rsaOpenSsl.SignData(documentHash, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
           }

But i guess i will have to try and get some info without the PrivateKey and check it out.

Thanks for all info.



IJ Irfana Jaffer Sadhik Syncfusion Team March 12, 2025 01:26 PM UTC

Hi Nuno,

We have used the private key in the signing part of the application solely for demonstration purposes. You can send the documentHash received from the SignEmpty external class to your service provider to obtain the signed hash. Therefore, you can ignore the .pfx file, as it is only included to complete this sample.

In this sample, only public certificates are used for creating the signature and its appearance.

Steps involved in the provided sample:

  1. Load the document and create a new signature using public certificates (root and intermediate).
  2. Generate the signature appearance.
  3. Implement the IPdfExternalSigner (SignEmpty) class.
  4. Retrieve the document hash from the SignEmpty class for future signing.
  5. Save the empty signed document.
  6. Use a third-party signer to sign the document hash obtained from the SignEmpty class and get the signed hash.
  7. Load the empty signed document.
  8. Implement the IPdfExternalSigner to return the signed hash.

Please let us know if you need any further details. We’ll be happy to assist you.


Regards,

Irfana J. 



Loader.
Up arrow icon