SignWithECDSA.cs
//
// This code is part of GrapeCity Documents for PDF samples.
// Copyright (c) GrapeCity, Inc. All rights reserved.
//
using System;
using System.IO;
using System.Drawing;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Crypto.Parameters;

using GrapeCity.Documents.Pdf;
using GrapeCity.Documents.Pdf.Security;
using GrapeCity.Documents.Pdf.AcroForms;
using GrapeCity.Documents.Text;


namespace GcPdfWeb.Samples
{
    // This sample shows how to sign an existing PDF file that contains
    // an empty signature field with an Elliptic Curve Digital Signature Algorithm
    // (ECDSA) certificate.
    //
    // The sample includes a ready to use class BCSignatureGenerator that implements
    // the GrapeCity.Documents.Pdf.IPkcs7SignatureGenerator interface using the
    // Portable.BouncyCastle package. Because unlike the current .NET system libraries,
    // BouncyCastle supports ECDSA, the BCSignatureGenerator class can be used in your
    // applications to handle ECDSA certificates.
    public class SignWithECDSA
    {
        public int CreatePDF(Stream stream)
        {
            var certPath = Path.Combine("Resources", "Misc", "GcPdfTest3_ECDSA.pfx");
            var certPwd = "password";

            GcPdfDocument doc = new GcPdfDocument();
            using var s = File.OpenRead(Path.Combine("Resources", "PDFs", "SignWithECDSA.pdf"));
            doc.Load(s);

            SignatureProperties sp = new SignatureProperties()
            {
                SignatureBuilder = new Pkcs7SignatureBuilder()
                {
                    SignatureGenerator = new BCSignatureGenerator(certPath, certPwd, OID.HashAlgorithms.SHA256),
                    CertificateChain = SecurityUtils.GetCertificateChain(certPath, certPwd),
                },
                SignatureField = doc.AcroForm.Fields[0]
            };
            sp.SignatureAppearance.Caption = "ECDSA";
            doc.Sign(sp, stream);

            // Done.
            return doc.Pages.Count;
        }
    }

    /// <summary>
    /// Implements <see cref="IPkcs7SignatureGenerator"/>.
    /// This implementation uses BouncyCastle libraries which,
    /// unlike the current .NET system libraries, support ECDSA
    /// (Elliptic Curve Digital Signature Algorithm) keys.
    /// </summary>
    public class BCSignatureGenerator : IPkcs7SignatureGenerator
    {
        private OID _hashAlgorithm;
        private OID _encryptionAlgorithm;
        private string _encryptionAlgorithmName;
        private ICipherParameters _key;

        public BCSignatureGenerator(ICipherParameters key, OID hashAlgorithm)
        {
            _hashAlgorithm = hashAlgorithm;
            if (key is RsaKeyParameters)
            {
                _encryptionAlgorithm = OID.EncryptionAlgorithms.RSA;
                _encryptionAlgorithmName = "RSA";
            }
            else if (key is DsaKeyParameters)
            {
                _encryptionAlgorithm = OID.EncryptionAlgorithms.DSA;
                _encryptionAlgorithmName = "DSA";
            }
            else if (key is ECKeyParameters)
            {
                _encryptionAlgorithm = OID.EncryptionAlgorithms.ECDSA;
                _encryptionAlgorithmName = "ECDSA";
            }
            else
            {
                throw new Exception($"Unknown algorithm used in the private key [{key}]");
            }
            _key = key;
        }

        public BCSignatureGenerator(byte[] certificateData, string password, OID hashAlgorithm)
            : this(GetPrivateKey(certificateData, password), hashAlgorithm)
        {
        }

        public BCSignatureGenerator(Stream certificateStream, string password, OID hashAlgorithm)
            : this(GetPrivateKey(certificateStream, password), hashAlgorithm)
        {
        }

        public BCSignatureGenerator(string certificateFileName, string password, OID hashAlgorithm)
            : this(GetPrivateKey(certificateFileName, password), hashAlgorithm)
        {
        }

        public OID HashAlgorithm => _hashAlgorithm;

        public OID DigestEncryptionAlgorithm => _encryptionAlgorithm;

        public byte[] SignData(byte[] digest)
        {
            string han = DigestUtilities.GetAlgorithmName(new DerObjectIdentifier(_hashAlgorithm.ToString()));
            string algorithm = han + "with" + _encryptionAlgorithmName;
            ISigner sig = SignerUtilities.GetSigner(algorithm);
            sig.Init(true, _key);
            sig.BlockUpdate(digest, 0, digest.Length);
            return sig.GenerateSignature();
        }

        public static ICipherParameters GetPrivateKey(Stream certificateStream, string password)
        {
            char[] p = new char[password.Length];
            for (int i = 0; i < p.Length; i++)
                p[i] = password[i];
            Pkcs12Store pk12 = new Pkcs12Store(certificateStream, p);
            foreach (var a in pk12.Aliases)
            {
                string alias = (string)a;
                if (pk12.IsKeyEntry(alias))
                {
                    var key = pk12.GetKey(alias);
                    if (key.Key.IsPrivate)
                        return key.Key;
                }
            }
            throw new Exception("Cannot get private key.");
        }

        public static ICipherParameters GetPrivateKey(string certificateFilePath, string password)
        {
            using var fs = new FileStream(certificateFilePath, FileMode.Open);
            return GetPrivateKey(fs, password);
        }

        public static ICipherParameters GetPrivateKey(byte[] certificateData, string password)
        {
            using var ms = new MemoryStream(certificateData);
            return GetPrivateKey(ms, password);
        }
    }
}