//https://www.pkcs11interop.net/
using Net.Pkcs11Interop.Common;
using Net.Pkcs11Interop.HighLevelAPI;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Math;
using SignLib.Certificates;
using SignLib.Pdf;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace SignLibTest
{


    class Program
    {

        //This demo project uses Pkcs11Interop library available here: https://www.pkcs11interop.net/

        /*************************************
        On the demo version of the library, a 10 seconds delay will be added for every operation.
        The certificate will be valid only 30 days on the demo version of the library
        */
        static string serialNumber = "YourSerialNumber";

        static void DigitallySignPDFFile(string unsignedDocument, string signedDocument)
        {
            PdfSignature ps = new PdfSignature(serialNumber);

            //load the PDF document
            ps.LoadPdfDocument(unsignedDocument);
            ps.SignaturePosition = SignaturePosition.TopRight;
            ps.SigningReason = "I approve this document";
            ps.SigningLocation = "Accounting department";

            ps.SignaturePosition = SignaturePosition.TopLeft;

            ps.HashAlgorithm = SignLib.HashAlgorithm.SHA256;

            //put your smart card PKCS#11 driver here and the smart card password
            //a list is availabe here: http://wiki.ncryptoki.com/Known-PKCS-11-modules.ashx
            ExternalSignature exSignature = new ExternalSignature(@"c:\Windows\System32\eTPKCS11.dll", "123456", 0);

            X509Certificate2Collection smartCardCertificates = exSignature.GetCertificatesFromSmartCard();

            if (smartCardCertificates.Count == 0)
                throw new Exception("No certificate was found on the smart card.");

            //add System.Secrutiy as reference
            X509Certificate2Collection selectedCertificate = X509Certificate2UI.SelectFromCollection(smartCardCertificates, "Select Certificate", "Select the signature certificate", X509SelectionFlag.SingleSelection);

            if (selectedCertificate.Count == 0)
                throw new Exception("No certificate was selected.");
            else
                exSignature.PKCS11SignatureCertificate = selectedCertificate[0];

            //set the certificate
            ps.DigitalSignatureCertificate = exSignature.PKCS11SignatureCertificate;

            //bind the external signature with the library
            SignLib.Certificates.DigitalCertificate.UseExternalSignatureProvider = exSignature;

            //write the signed file
            File.WriteAllBytes(signedDocument, ps.ApplyDigitalSignature());

            Console.WriteLine("The PDF signature was created." + Environment.NewLine);

        }


        static void Main(string[] args)
        {
            try
            {
                DigitallySignPDFFile("source.pdf", "source[signed].pdf");

                Console.WriteLine("All done!");
            }
            catch (Exception ex)
            {
                Console.Write("General exception: " + ex.Message);
            }

        }
    }

    /// <summary>
    /// Create the external signature using a third party engine. The class must be inherited from SignLib.Certificates.IExternalSignature interface and 
    /// must implements the method public byte[] ApplySignature(byte[] dataToSign, System.Security.Cryptography.Oid oid)
    /// </summary>
    public class ExternalSignature : IExternalSignature
    {
        private readonly string _pkcs11LibraryPath;
        private readonly string _smartCardPin;
        private readonly int _slotNumber;

        public ExternalSignature(string pathToPKCS11Module, string smartCardPin, int slotNumber)
        {
            _pkcs11LibraryPath = pathToPKCS11Module;
            _smartCardPin = smartCardPin;
            _slotNumber = slotNumber;
        }

        public X509Certificate2 PKCS11SignatureCertificate { get; set; }

        public X509Certificate2Collection GetCertificatesFromSmartCard()
        {
            X509Certificate2Collection col = new X509Certificate2Collection();

            var factories = new Pkcs11InteropFactories();

            using (IPkcs11Library pkcs11 = factories.Pkcs11LibraryFactory
                .LoadPkcs11Library(factories, _pkcs11LibraryPath, AppType.MultiThreaded))
            {
                IList<ISlot> slots = pkcs11.GetSlotList(SlotsType.WithTokenPresent);

                if (slots == null || slots.Count == 0)
                    throw new CryptographicException("PKCS#11 smart card is not present.");

                
                ISlot slot = slots[_slotNumber];

                using (ISession session = slot.OpenSession(SessionType.ReadOnly))
                {
                    session.Login(CKU.CKU_USER, _smartCardPin);

                    List<IObjectAttribute> objectAttributes = new List<IObjectAttribute>();
                    objectAttributes.Add(
                        factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_CERTIFICATE));
                    objectAttributes.Add(
                        factories.ObjectAttributeFactory.Create(CKA.CKA_CERTIFICATE_TYPE, CKC.CKC_X_509));

                    IList<IObjectHandle> certificateList = session.FindAllObjects(objectAttributes);

                    foreach (IObjectHandle certificate in certificateList)
                    {
                        List<CKA> attributes = new List<CKA>();
                        attributes.Add(CKA.CKA_VALUE);

                        IList<IObjectAttribute> certificateAttributes =
                            session.GetAttributeValue(certificate, attributes);

                        byte[] signingCertificate =
                            certificateAttributes[0].GetValueAsByteArray();

                        col.Add(new X509Certificate2(signingCertificate));
                    }
                }
            }

            return col;
        }

        private static byte[] EcdsaRawToDer(byte[] rs)
        {
            if (rs == null || rs.Length < 2 || (rs.Length & 1) != 0)
                throw new ArgumentException("Invalid ECDSA raw signature length", nameof(rs));

            int len = rs.Length / 2;

            var r = new Org.BouncyCastle.Math.BigInteger(1, rs, 0, len);
            var s = new Org.BouncyCastle.Math.BigInteger(1, rs, len, len);

            return new Org.BouncyCastle.Asn1.DerSequence(
                new Org.BouncyCastle.Asn1.DerInteger(r),
                new Org.BouncyCastle.Asn1.DerInteger(s)
            ).GetDerEncoded();
        }


        public byte[] ApplySignature(byte[] dataToSign, System.Security.Cryptography.Oid oid)
        {
            try
            {
                Pkcs11InteropFactories factories = new Pkcs11InteropFactories();

                using (IPkcs11Library pkcs11 =
                    factories.Pkcs11LibraryFactory.LoadPkcs11Library(
                        factories, _pkcs11LibraryPath, AppType.MultiThreaded))
                {
                    IList<ISlot> slots = pkcs11.GetSlotList(SlotsType.WithTokenPresent);
                    if (slots == null || slots.Count == 0)
                        throw new CryptographicException("PKCS#11 smart card is not present.");

                    ISlot slot = slots[0];

                    using (ISession session = slot.OpenSession(SessionType.ReadOnly))
                    {
                        session.Login(CKU.CKU_USER, _smartCardPin);

                        // =========================================================
                        // 1. Găsim CKA_ID al certificatului de semnare (o singură dată)
                        // =========================================================
                        string certificateId = null;
                        string keyAlgorithm = null;

                        List<IObjectAttribute> certSearchTemplate = new List<IObjectAttribute>();
                        certSearchTemplate.Add(
                            factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_CERTIFICATE));
                        certSearchTemplate.Add(
                            factories.ObjectAttributeFactory.Create(CKA.CKA_CERTIFICATE_TYPE, CKC.CKC_X_509));

                        IList<IObjectHandle> certs = session.FindAllObjects(certSearchTemplate);

                        foreach (IObjectHandle certHandle in certs)
                        {
                            List<CKA> attrs = new List<CKA>();
                            attrs.Add(CKA.CKA_VALUE);
                            attrs.Add(CKA.CKA_ID);

                            IList<IObjectAttribute> values =
                                session.GetAttributeValue(certHandle, attrs);

                            X509Certificate2 cert =
                                new X509Certificate2(values[0].GetValueAsByteArray());

                            if (cert.Thumbprint == PKCS11SignatureCertificate.Thumbprint)
                            {
                                certificateId = ConvertUtils.BytesToHexString(
                                    values[1].GetValueAsByteArray());

                                keyAlgorithm =
                                    new System.Security.Cryptography.Oid(
                                        cert.GetKeyAlgorithm()).FriendlyName;

                                break;
                            }
                        }

                        if (certificateId == null)
                            throw new Exception("Signing certificate not found on token.");

                        // =========================================================
                        // 2. Găsim cheia privată corespunzătoare (după CKA_ID)
                        // =========================================================
                        List<IObjectAttribute> keySearchTemplate = new List<IObjectAttribute>();
                        keySearchTemplate.Add(
                            factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY));

                        IList<IObjectHandle> privateKeys =
                            session.FindAllObjects(keySearchTemplate);

                        foreach (IObjectHandle privateKey in privateKeys)
                        {
                            List<CKA> keyAttrs = new List<CKA>();
                            keyAttrs.Add(CKA.CKA_ID);

                            IList<IObjectAttribute> keyValues =
                                session.GetAttributeValue(privateKey, keyAttrs);

                            string keyId = ConvertUtils.BytesToHexString(
                                keyValues[0].GetValueAsByteArray());

                            if (keyId != certificateId)
                                continue;

                            // =====================================================
                            // 3. RSA
                            // =====================================================
                            if (keyAlgorithm == "RSA")
                            {
                                //TRY: hash + signature
                                try
                                {
                                    IMechanism mechanism;

                                    switch (oid.Value)
                                    {
                                        case "2.16.840.1.101.3.4.2.1": // SHA-256
                                            mechanism = factories.MechanismFactory.Create(
                                                CKM.CKM_SHA256_RSA_PKCS);
                                            return session.Sign(mechanism, privateKey, dataToSign);

                                        case "1.3.14.3.2.26": // SHA-1
                                            mechanism = factories.MechanismFactory.Create(
                                                CKM.CKM_SHA1_RSA_PKCS);
                                            return session.Sign(mechanism, privateKey, dataToSign);

                                        case "2.16.840.1.101.3.4.2.2": // SHA-384
                                            mechanism = factories.MechanismFactory.Create(
                                                CKM.CKM_SHA384_RSA_PKCS);
                                            return session.Sign(mechanism, privateKey, dataToSign);

                                        case "2.16.840.1.101.3.4.2.3": // SHA-512
                                            mechanism = factories.MechanismFactory.Create(
                                                CKM.CKM_SHA512_RSA_PKCS);
                                            return session.Sign(mechanism, privateKey, dataToSign);

                                        default:
                                            throw new NotSupportedException(
                                                "Unsupported hash OID: " + oid.Value);
                                    }
                                }
                                catch 
                                {

                                    //FALLBACK: hash + DigestInfo + CKM_RSA_PKCS
                                    byte[] digest = ComputeDigest(dataToSign, oid.Value);
                                    byte[] digestInfo = CreateDigestInfo(digest, oid.Value);

                                    IMechanism fallbackMechanism =
                                        factories.MechanismFactory.Create(CKM.CKM_RSA_PKCS);

                                    return session.Sign(fallbackMechanism, privateKey, digestInfo);
                                }
                            }

                            // =====================================================
                            // 4. ECC
                            // =====================================================
                            if (keyAlgorithm == "ECC")
                            {
                                byte[] digest = ComputeDigest(dataToSign, oid.Value);

                                IMechanism mechanism =
                                    factories.MechanismFactory.Create(CKM.CKM_ECDSA);

                                byte[] rawSignature =
                                    session.Sign(mechanism, privateKey, digest);

                                return EcdsaRawToDer(rawSignature);
                            }

                            throw new Exception("Certificate algorithm not supported.");
                        }
                    }
                }

                throw new Exception("Private key was not found.");
            }
            catch (Exception ex)
            {
                throw new Exception(
                    "PKCS#11 signature cannot be performed: " + ex.Message, ex);
            }
        }


        private byte[] ComputeDigest(byte[] message, string hashOid)
        {
            try
            {
                System.Security.Cryptography.HashAlgorithm sha = null;

                if (hashOid == "1.3.14.3.2.26") //SHA1
                    sha = new System.Security.Cryptography.SHA1Managed();

                if (hashOid == "2.16.840.1.101.3.4.2.1") //SHA256
                    sha = new System.Security.Cryptography.SHA256Managed();

                if (hashOid == "2.16.840.1.101.3.4.2.2") //SHA384
                    sha = new System.Security.Cryptography.SHA384Managed();

                if (hashOid == "2.16.840.1.101.3.4.2.3") //SHA512
                    sha = new System.Security.Cryptography.SHA512Managed();

                return sha.ComputeHash(message);

            }
            catch
            {
                throw;
            }
        }

        private byte[] CreateDigestInfo(byte[] hash, string hashOid)
        {
            Org.BouncyCastle.Asn1.DerObjectIdentifier derObjectIdentifier = new Org.BouncyCastle.Asn1.DerObjectIdentifier(hashOid);
            Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier algorithmIdentifier = new Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier(derObjectIdentifier, null);
            Org.BouncyCastle.Asn1.X509.DigestInfo digestInfo = new Org.BouncyCastle.Asn1.X509.DigestInfo(algorithmIdentifier, hash);
            return digestInfo.GetDerEncoded();
        }


    }
}