SignAzureKeyVault.cs
// // This code is part of Document Solutions for PDF demos. // Copyright (c) MESCIUS 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 DsPdfWeb.Demos { // 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 DsPdf 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) { var 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"); var 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) { var 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; } } }