Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I sign a PDF document using a certificate from the Windows Cert Store?

I need to sign a PDF document using a certificate that exists in the Windows Certificate Store. I have been digging around all day trying to figure it out, and I am so close yet so far away.

All that is missing is this: How do I get an IExternalSignature object to sign the PDF file with?

Rahul Singla has written a beautiful example of how to sign a PDF document using the new iText 5.3.0 API - as long as you can access a .pfx file sitting around on your PC somewhere.

There is a previous question on signing using a certificate from the Windows Cert Store, except it was using a version of the API where SetCrypto still exists, and the signature was apparently optional. In iText 5.3.0, the API has changed, and SetCrypto is no longer a thing.

Here's what I have so far (comments added for posterity, since this might be the most complete and recent version of how to do this on the 'net):

using iTextSharp.text.pdf;
using iTextSharp.text.pdf.security;
using BcX509 = Org.BouncyCastle.X509;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Crypto;
using DotNetUtils = Org.BouncyCastle.Security.DotNetUtilities;

...

// Set up the PDF IO
PdfReader reader = new PdfReader(@"some\dir\SomeTemplate.pdf");
PdfStamper stamper = PdfStamper.CreateSignature(reader,
    new FileStream(@"some\dir\SignedPdf.pdf", FileMode.Create), '\0');
PdfSignatureAppearance sap = stamper.SignatureAppearance;

sap.Reason = "For no apparent raisin";
sap.Location = "...";

// Acquire certificate chain
var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly);

X509CertificateCollection certCollection =
    certStore.Certificates.Find(X509FindType.FindBySubjectName,
    "My.Cert.Subject", true);
X509Certificate cert = certCollection[0];
// iTextSharp needs this cert as a BouncyCastle X509 object; this converts it.
BcX509.X509Certificate bcCert = DotNetUtils.FromX509Certificate(cert);
var chain = new List<BcX509.X509Certificate> { bcCert };
certStore.Close();

// Ok, that's the certificate chain done. Now how do I get the PKS?
IExternalSignature signature = null; /* ??? */

// Sign the PDF file and finish up.
MakeSignature.SignDetached(sap, signature, chain, // the important stuff
    null, null, null, 0, CryptoStandard.CMS);
stamper.Close();

As you can see: I have everything but the signature, and I'm stumped as to how I should obtain it!

like image 904
doppelgreener Avatar asked Feb 21 '13 07:02

doppelgreener


2 Answers

public byte[] SignPdf(byte[] pdf)
{
    using (MemoryStream output = new MemoryStream())
    {
        //get certificate from path
        X509Certificate2 cert1 = new X509Certificate2(@"C:\temp\certtemp.pfx", "12345", X509KeyStorageFlags.Exportable);
        //get private key to sign pdf
        var pk = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(cert1.PrivateKey).Private;
        // convert the type to be used at .SetCrypt(); 
        Org.BouncyCastle.X509.X509Certificate bcCert = Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(cert1);
        // get the pdf u want to sign
        PdfReader pdfReader = new PdfReader(pdf);

        PdfStamper stamper = PdfStamper.CreateSignature(pdfReader, output, '\0');
        PdfSignatureAppearance pdfSignatureAppearance = stamper.SignatureAppearance;
        //.SetCrypt(); sign the pdf
        pdfSignatureAppearance.SetCrypto(pk, new Org.BouncyCastle.X509.X509Certificate[] { bcCert }, null, PdfSignatureAppearance.WINCER_SIGNED);

        pdfSignatureAppearance.Reason = "Este documento está assinado digitalmente pelo Estado Portugues";
        pdfSignatureAppearance.Location = " Lisboa, Portugal";
        pdfSignatureAppearance.SignDate = DateTime.Now;

        stamper.Close();

        return output.ToArray();
    }
} 

I use this code to get byte[] PDF and return again a byte[] PDF already signed.

It's iTextSharp-LGPL.

like image 54
Frederico Monteiro Avatar answered Oct 07 '22 09:10

Frederico Monteiro


X509Certificate cert = certCollection[0]; // Your code
X509Certificate2 signatureCert = new X509Certificate2(cert);

var pk = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(signatureCert.PrivateKey).Private;

If you have the pk, which can get as above, you create an IExternalSignature as follows:

IExternalSignature es = new PrivateKeySignature(pk, "SHA-256");

You may also find the following articles of use:

  • https://www.simple-talk.com/dotnet/.net-framework/beginning-with-digital-signatures-in-.net-framework/
  • http://msdn.microsoft.com/en-us/library/ms223098.aspx
like image 40
Sam Shiles Avatar answered Oct 07 '22 11:10

Sam Shiles