Search code examples
c#winformsitextdigital-signature

How to digital sign a pdf file using smartcard and iTextSharp?


I'm new in digital signing concept, I'm developing a winform application that allow an user to select a certificate from Windows certificate store, and use the selected certificate to sign to a pdf file, it is working perfectly when plug an usbtoken which contains the certificate and a midleware of the usbtoken installed, when I sign to a pdf file the midleware prompt me for entering pin and after enter the correct pin my application can easily sign to the pdf file. But when I try with a smartcard( TokenMe Evo – Bit4id) I can still get the certificate from windows store but when create signature by bellow line of code:

    IExternalSignature externalSignature = new X509Certificate2Signature(certificate, 
    "SHA-1");        

nothing promt me for entering pin and the bellow exception apear:

    System.ArgumentException: Unknown encryption algorithm 
    System.Security.Cryptography.RSACng

This is how i get the certificate from windows store:

    using iTextSharp.text.pdf.security;
    using iTextSharp.text.pdf;
    using Org.BouncyCastle.Security;
    using Org.BouncyCastle.X509;
    using System.Security.Cryptography.X509Certificates;
    using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
    using System.Windows.Forms;
    using System.Threading;
    using System.Collections.Concurrent;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using Org.BouncyCastle.Tls;
    using System.Text.Json;
    using Serilog;
    using iTextSharp.text;

    private static IList<X509Certificate> chain = new List<X509Certificate>();
    private static X509Certificate2 certificate = null;

    X509CertificateParser cp = new X509CertificateParser();

    //Get Sertifiacte
    X509Store st = new X509Store(StoreName.My, StoreLocation.CurrentUser);
    st.Open(OpenFlags.MaxAllowed);
    X509Certificate2Collection collection = 
    X509Certificate2UI.SelectFromCollection(st.Certificates,
        "Please select certificate:", "", X509SelectionFlag.SingleSelection);
    if (collection.Count > 0)
    {
        certificate = collection[0];
    }
    if (certificate == null)
    {
        MessageBox.Show("No certificate selected!");
        button3.Enabled = true;
        return;
    }
    st.Close();
    //Get Cert Chain

    X509Chain x509Chain = new X509Chain();

    x509Chain.Build(certificate);

    foreach (X509ChainElement x509ChainElement in x509Chain.ChainElements)
    {
        chain.Add(DotNetUtilities.FromX509Certificate(x509ChainElement.Certificate));
    }

And this is how I sign to the pdf file and the exception occur:

    private void signPdf(
        string inputFile, 
        string outputPath, 
        string imagePath, 
        int pageNum, 
        int position, 
        float imageWidth, 
        float imageHeight)
    {
        try
        {
            string fileName = Path.GetFileName(inputFile);
            ouputFile = ouputFile + "\\" + fileName;
            PdfReader inputPdf = new PdfReader(inputFile);

            FileStream signedPdf = new FileStream(ouputFile, FileMode.Create);

            PdfStamper pdfStamper = PdfStamper.CreateSignature(inputPdf, signedPdf, 
            '\0');

            IExternalSignature externalSignature = 
            new X509Certificate2Signature(certificate, "SHA-1");

            PdfSignatureAppearance signatureAppearance = 
            pdfStamper.SignatureAppearance;
            int NumberOfPages = inputPdf.NumberOfPages;
            if (imagePath != "" && imagePath != null)
            {
                signatureAppearance.SignatureGraphic = 
                iTextSharp.text.Image.GetInstance(imagePath);
            }


            iTextSharp.text.Rectangle pageSize = inputPdf.GetPageSize(pageNum);
            float x0 = 0;
            float y0 = 0;
            float x1 = 0;
            float y1 = 0;

            switch (position)
            {
                case 0:
                    x0 = 0;
                    y0 = pageSize.Height - imageHeight;
                    break;
                case 1:
                    x0 = pageSize.Width - imageWidth;
                    y0 = pageSize.Height - imageHeight;
                    break;
                case 2:
                    x0 = 0;
                    y0 = 0;
                    break;
                case 3:
                    x0 = pageSize.Width - imageWidth;
                    y0 = 0;
                    break;
                default:
                    break;
            }

            x1 = x0 + imageWidth;
            y1 = y0 + imageHeight;
            signatureAppearance.SetVisibleSignature(new iTextSharp.text.Rectangle(
                x0, 
                y0, 
                x1, 
                y1), 
                pageNum, 
                GetOrganizationName(certificate));
            if (renderingMode == 0)
            {
                signatureAppearance.SignatureRenderingMode = 
                PdfSignatureAppearance.RenderingMode.GRAPHIC_AND_DESCRIPTION;
            }
            else if (renderingMode == 1)
            {
                signatureAppearance.SignatureRenderingMode = 
                PdfSignatureAppearance.RenderingMode.GRAPHIC;
            }
            else if (renderingMode == 2)
            {
                signatureAppearance.SignatureRenderingMode = 
                PdfSignatureAppearance.RenderingMode.DESCRIPTION;
            }

            MakeSignature.SignDetached(signatureAppearance, externalSignature, chain, 
                null, null, null, 0,CryptoStandard.CMS);
            inputPdf.Close();
            pdfStamper.Close();
    }
    catch (Exception ex)
    {
        Log.Error(ex.ToString());

    }
}

Please show me what am I doing wrong, Thanks a lot.


Solution

  • I resloved this issue myself. This is because of the private key of the certificate is not in form of RSA or DSA, its is actually:

    System.Security.Cryptography.RSACng
    

    and the class X509Certificate2Signature from iTextSharp.text.pdf.security only implement two kind of certificate.PrivateKey are RSA and DSA like this:

    public X509Certificate2Signature(X509Certificate2 certificate, string 
        hashAlgorithm)
    {
        if (!certificate.HasPrivateKey)
        {
            throw new ArgumentException("No private key.");
        }
    
        this.certificate = certificate;
        this.hashAlgorithm = 
        DigestAlgorithms.GetDigest(
            DigestAlgorithms.GetAllowedDigests(hashAlgorithm));
        if (certificate.PrivateKey is RSACryptoServiceProvider)
        {
            encryptionAlgorithm = "RSA";
            return;
        }
    
        if (certificate.PrivateKey is DSACryptoServiceProvider)
        {
            encryptionAlgorithm = "DSA";
            return;
        }
    
        throw new ArgumentException("Unknown encryption algorithm " + 
        certificate.PrivateKey);
    }
    

    this is the cause of the Unknown encryption algorithm, beacuse when certificate.PrivateKey is System.Security.Cryptography.RSACng it won't set encryptionAlgorithm and throw new ArgumentException, I cannot edit the class X509Certificate2Signature of namespace iTextSharp.text.pdf.security, so I created a class named X509Certificate2Signature1 myself implement IExternalSignature interface and add additional code to manipulate if certificate.PrivateKey is System.Security.Cryptography.RSACng, and bellow is how I implement my X509Certificate2Signature1 class:

    public class X509Certificate2Signature1 : IExternalSignature
    {
        //
        // Summary:
        //     The certificate with the private key
        private X509Certificate2 certificate;
    
        private string hashAlgorithm;
    
        private string encryptionAlgorithm;
    
        //
        // Summary:
        //     Creates a signature using a X509Certificate2. It supports 
        //smartcards without
        //     exportable private keys.
        //
        // Parameters:
        //   certificate:
        //     The certificate with the private key
        //
        //   hashAlgorithm:
        //     The hash algorithm for the signature. As the Windows CAPI is 
        //used to do the signature
        //     the only hash guaranteed to exist is SHA-1
        public X509Certificate2Signature1(X509Certificate2 certificate, string 
            hashAlgorithm)
        {
            if (!certificate.HasPrivateKey)
            {
                throw new ArgumentException("No private key.");
            }
    
            this.certificate = certificate;
            this.hashAlgorithm = 
    
            DigestAlgorithms.GetDigest(
                DigestAlgorithms.GetAllowedDigests(hashAlgorithm));
            if (certificate.PrivateKey is RSACryptoServiceProvider)
            {
                encryptionAlgorithm = "RSA";
                return;
            }
            else if (certificate.PrivateKey is DSACryptoServiceProvider)
            {
                encryptionAlgorithm = "DSA";
                return;
            }
            else if (certificate.PrivateKey is 
                System.Security.Cryptography.RSACng)
            {
                encryptionAlgorithm = "RSA";
                return;
            }
            else
            {
                encryptionAlgorithm = "RSA";
                return;
            }
    
            throw new ArgumentException("Unknown encryption algorithm " + 
                certificate.PrivateKey);
        }
    
        public virtual byte[] Sign(byte[] message)
        {
            if (certificate.PrivateKey is RSACryptoServiceProvider)
            {
                RSACryptoServiceProvider rsa = 
                   (RSACryptoServiceProvider)certificate.PrivateKey;
                return rsa.SignData(message, hashAlgorithm);
            }
            else if (certificate.PrivateKey is 
               System.Security.Cryptography.RSACng)
            {
                System.Security.Cryptography.RSACng rSACng = 
                  (System.Security.Cryptography.RSACng)certificate.PrivateKey;
                return rSACng.SignData(message, HashAlgorithmName.SHA1, 
                    RSASignaturePadding.Pkcs1);
            }
            else
            {
                DSACryptoServiceProvider dsa = 
                    (DSACryptoServiceProvider)certificate.PrivateKey;
                return dsa.SignData(message);
            }
        }
    
        public virtual string GetHashAlgorithm()
        {
            return hashAlgorithm;
        }
    
        public virtual string GetEncryptionAlgorithm()
        {
            return encryptionAlgorithm;
        }
    }
    

    And I change the line of code which cause the issue from:

    externalSignature = new X509Certificate2Signature(certificate, 
        "SHA-1");
    

    To:

    externalSignature = new X509Certificate2Signature1(certificate, 
        "SHA-1");
    

    and now my application can digital signing to pdf files perfectly!