SignAzureKeyVault.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.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;

using Azure.Core;
using Azure.Identity;
using Azure.Security.KeyVault.Certificates;
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;

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 a certificate that is stored
    // in an Azure Key Vault.
    //
    // The sample includes a ready to use utility class AzureSignatureGenerator
    // that implements the GrapeCity.Documents.Pdf.IPkcs7SignatureGenerator interface,
    // and can be used to sign PDFs with certificates stored in Azure Key Vault.
    //  
    // Please note that when run directly off the GcPdf demo site,
    // this sample will NOT sign the PDF, as it passes dummy Azure credentials
    // to the AzureSignatureGenerator's ctor. You will need to download the sample
    // and provide your own credentials for the sample code to actually sign a PDF.
    //
    public class SignAzureKeyVault
    {
        public int CreatePDF(Stream stream)
        {
            GcPdfDocument doc = new GcPdfDocument();
            using var s = File.OpenRead(Path.Combine("Resources", "PDFs", "SignAzureKeyVault.pdf"));
            doc.Load(s);

            try
            {
                // This WILL NOT WORK due to dummy Azure credentials.
                // Supply valid credentials to actually sign the PDF.
                using var sg = new AzureSignatureGenerator(
                    "keyVaultName",
                    "tenantId",
                    "clientId",
                    "clientSecret",
                    "certificateName");

                SignatureProperties sp = new SignatureProperties()
                {
                    SignatureBuilder = new Pkcs7SignatureBuilder()
                    {
                        SignatureGenerator = sg,
                        CertificateChain = new X509Certificate2[] { sg.Certificate },
                    },
                    SignatureField = doc.AcroForm.Fields[0]
                };
                doc.Sign(sp, stream);
            }
            catch (Exception)
            {
                var page = doc.Pages[0];
                var r = doc.AcroForm.Fields[0].Widgets[0].Rect;
                Common.Util.AddNote(
                    "Signing failed because dummy Azure credentials were used.\n" +
                    "Use valid Azure Key Vault credentials to sign the PDF.",
                    page,
                    new RectangleF(r.Left, r.Bottom + 24, page.Size.Width - r.Left * 2, 0));
                doc.Save(stream);
            }

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

    /// <summary>
    /// Implements <see cref="IPkcs7SignatureGenerator"/> 
    /// and allows generating a digital signature using
    /// a certificate stored in Azure Key Vault.
    /// </summary>
    public class AzureSignatureGenerator : IPkcs7SignatureGenerator, IDisposable
    {
        private CertificateClient _certificateClient;
        private X509Certificate2 _certificate;
        private CryptographyClient _cryptographyClient;

        /// <summary>
        /// Initializes a new instance of the <see cref="AzureSignatureGenerator"/> class.
        /// </summary>
        /// <param name="keyVaultName">
        /// The name of the Key Vault storage used to create a URL in the form
        /// <b>https://{keyVaultName}.vault.azure.net/</b> that will be passed to
        /// the <see cref="CertificateClient"/> ctor.</param>
        /// <param name="tenantId">
        /// The Azure Active Directory tenant (directory) ID of the service principal.
        /// This value will be used to create the <see cref="ClientSecretCredential"/>.</param>
        /// <param name="clientId">
        /// The client (application) ID of the service principal.
        /// This value will be used to create the <see cref="ClientSecretCredential"/>.</param>
        /// <param name="clientSecret">
        /// The client secret that was generated for the App Registration used to authenticate the client.
        /// This value will be used to create the <see cref="ClientSecretCredential"/>.</param>
        /// <param name="certificateName">
        /// The name of the certificate to be used for the signature.</param>
        public AzureSignatureGenerator(
            string keyVaultName,
            string tenantId,
            string clientId,
            string clientSecret,
            string certificateName)
        {
            Uri keyVaultUri = new Uri($"https://{keyVaultName}.vault.azure.net/");
            TokenCredential credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
            _certificateClient = new CertificateClient(keyVaultUri, credential);
            var c = _certificateClient.GetCertificate(certificateName);
            _certificate = new X509Certificate2(c.Value.Cer);
            _cryptographyClient = new CryptographyClient(c.Value.KeyId, credential);
        }

        /// <summary>
        /// Gets the ID of the hash algorithm.
        /// </summary>
        public OID HashAlgorithm => OID.HashAlgorithms.SHA256;

        /// <summary>
        /// Gets the ID of the encryption algorithm.
        /// </summary>
        public OID DigestEncryptionAlgorithm => OID.EncryptionAlgorithms.RSA;

        /// <summary>
        /// Gets the certificate.
        /// </summary>
        public X509Certificate2 Certificate => _certificate;

        /// <summary>
        /// Signs data.
        /// </summary>
        /// <param name="input">The input data to sign.</param>
        /// <returns>The signed data.</returns>
        public byte[] SignData(byte[] input)
        {
            var hashDigest = new Sha256Digest();
            byte[] hash = new byte[hashDigest.GetDigestSize()];
            hashDigest.Reset();
            hashDigest.BlockUpdate(input, 0, input.Length);
            hashDigest.DoFinal(hash, 0);
            byte[] result = _cryptographyClient.Sign(SignatureAlgorithm.RS256, hash).Signature;
            return result;
        }

        /// <summary>
        /// Releases resources used by this object.
        /// </summary>
        public void Dispose()
        {
            _certificateClient = null;
            if (_certificate != null)
            {
                _certificate.Dispose();
                _certificate = null;
            }
            _cryptographyClient = null;
        }
    }
}