Search code examples
.netbouncycastleitext7

Digital Signature using itext7 and boundy castle (.net razor pages)


At the moment I wanted to create a digital signature using itext7 and boundy caste on .net razor pages. I found a old tutorial : https://viewbag.wordpress.com/2019/12/24/pdf-digital-signatures-itext7-bouncy-castle-net-core/ and I couldn't make to the end because I got this 2 errors:

Error CS1503 Argument 1: cannot convert from 'Org.BouncyCastle.Crypto.ICipherParameters' to 'iText.Commons.Bouncycastle.Crypto.IPrivateKey' -> pk value

Error CS1503 Argument 2: cannot convert from 'Org.BouncyCastle.X509.X509Certificate[]' to 'iText.Commons.Bouncycastle.Cert.IX509Certificate[]' -> chain value

Code:

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using iText.Kernel.Pdf;
using iText.Signatures;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using SSG.Core.Entitites;
using SSG.Infrastructure.Data;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;
using iText.Kernel.Geom;

namespace SSG.Web.Pages.Manufacturers
{
    public class IndexModel : PageModel
    {

        public async Task OnGetAsync()
        {

            string KEYSTORE = "C:\\Users\\steve\\Desktop\\resources\\cert.pfx";
            char[] PASSWORD = "xhbssjsjshjs".ToCharArray();

            Pkcs12Store pk12 = new Pkcs12Store(new FileStream(KEYSTORE,
            FileMode.Open, FileAccess.Read), PASSWORD);
            string alias = null;
            foreach (object a in pk12.Aliases)
            {
                alias = ((string)a);
                if (pk12.IsKeyEntry(alias))
                {
                    break;
                }
            }
            ICipherParameters pk = pk12.GetKey(alias).Key;

            X509CertificateEntry[] ce = pk12.GetCertificateChain(alias);
            X509Certificate[] chain = new X509Certificate[ce.Length];
            for (int k = 0; k < ce.Length; ++k)
            {
                chain[k] = ce[k].Certificate;
            }

        
            string DEST = "C:\\Users\\steve\\Desktop\\resources\\SignedPDF.pdf";
            string SRC = "C:\\Users\\steve\\Desktop\\resources\\SampleContract.pdf";

            PdfReader reader = new PdfReader(SRC);
            PdfSigner signer = new PdfSigner(reader,
            new FileStream(DEST, FileMode.Create),
            new StampingProperties());

            PdfSignatureAppearance appearance = signer.GetSignatureAppearance();
            appearance.SetReason("My reason to sign...")
                .SetLocation("Lahore")
                .SetPageRect(new Rectangle(36, 648, 200, 100))
                .SetPageNumber(1);
            signer.SetFieldName("MyFieldName");

            IExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256);
            signer.SignDetached(pks, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);



        }

    }
}


Packages installed:

<PackageReference Include="itext7" Version="8.0.1" />
<PackageReference Include="itext7.bouncy-castle-fips-adapter" Version="8.0.1" />
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />

What I'm doing wrong?

Expected to create a pdf with a digital signature.


Solution

  • There are some breaking changes in the iText signing API between version 7.x and 8.x. The background is that in version 8 both BouncyCastle and BouncyCastle-FIPS are supported. This was done by creating iText wrapper classes for the specific BouncyCastle[-FIPS] classes which have to be used as parameters to the signing methods.

    The signing examples in the i7ns-samples repository on github have been updated accordingly. Articles on this topic by third parties may take a bit longer to be updated if at all.

    In your case the TestSignBCSimple example class most likely is what you're looking for, with code like this:

    string testFileName = @"SOURCE.pdf";
    string storePath = @"KEYSTORE.p12";
    char[] storePass = "PASSWORD".ToCharArray();
    string storeAlias = "ALIAS";
    
    Pkcs12Store pkcs12 = new Pkcs12Store(new FileStream(storePath, FileMode.Open, FileAccess.Read), storePass);
    AsymmetricKeyParameter key = pkcs12.GetKey(storeAlias).Key;
    X509CertificateEntry[] chainEntries = pkcs12.GetCertificateChain(storeAlias);
    IX509Certificate[] chain = new IX509Certificate[chainEntries.Length];
    for (int i = 0; i < chainEntries.Length; i++)
        chain[i] = new X509CertificateBC(chainEntries[i].Certificate);
    PrivateKeySignature signature = new PrivateKeySignature(new PrivateKeyBC(key), "SHA384");
    
    using (PdfReader pdfReader = new PdfReader(testFileName))
    using (FileStream result = File.Create("DEST.pdf"))
    {
        PdfSigner pdfSigner = new PdfSigner(pdfReader, result, new StampingProperties().UseAppendMode());
        ITSAClient tsaClient = null;
    
        pdfSigner.SignDetached(signature, chain, null, null, tsaClient, 0, PdfSigner.CryptoStandard.CMS);
    }
    

    (TestSignBCSimple test methods TestSignSimple...)

    You can still add an appearance and set a field name like in your example.