SignAzureKeyVault.cs
  1. //
  2. // This code is part of Document Solutions for PDF demos.
  3. // Copyright (c) MESCIUS inc. All rights reserved.
  4. //
  5. using System;
  6. using System.IO;
  7. using System.Drawing;
  8. using System.Security.Cryptography;
  9. using System.Security.Cryptography.X509Certificates;
  10.  
  11. using Org.BouncyCastle.Crypto;
  12. using Org.BouncyCastle.Crypto.Digests;
  13. using Org.BouncyCastle.Asn1;
  14. using Org.BouncyCastle.Asn1.X509;
  15.  
  16. using Azure.Core;
  17. using Azure.Identity;
  18. using Azure.Security.KeyVault.Certificates;
  19. using Azure.Security.KeyVault.Keys;
  20. using Azure.Security.KeyVault.Keys.Cryptography;
  21.  
  22. using GrapeCity.Documents.Pdf;
  23. using GrapeCity.Documents.Pdf.Security;
  24. using GrapeCity.Documents.Pdf.AcroForms;
  25. using GrapeCity.Documents.Text;
  26.  
  27.  
  28. namespace DsPdfWeb.Demos
  29. {
  30. // This sample shows how to sign an existing PDF file that contains
  31. // an empty signature field with a certificate that is stored
  32. // in an Azure Key Vault.
  33. //
  34. // The sample includes a ready to use utility class AzureSignatureGenerator
  35. // that implements the GrapeCity.Documents.Pdf.IPkcs7SignatureGenerator interface,
  36. // and can be used to sign PDFs with certificates stored in Azure Key Vault.
  37. //
  38. // Please note that when run directly off the DsPdf demo site,
  39. // this sample will NOT sign the PDF, as it passes dummy Azure credentials
  40. // to the AzureSignatureGenerator's ctor. You will need to download the sample
  41. // and provide your own credentials for the sample code to actually sign a PDF.
  42. //
  43. public class SignAzureKeyVault
  44. {
  45. public int CreatePDF(Stream stream)
  46. {
  47. var doc = new GcPdfDocument();
  48. using var s = File.OpenRead(Path.Combine("Resources", "PDFs", "SignAzureKeyVault.pdf"));
  49. doc.Load(s);
  50.  
  51. try
  52. {
  53. // This WILL NOT WORK due to dummy Azure credentials.
  54. // Supply valid credentials to actually sign the PDF.
  55. using var sg = new AzureSignatureGenerator(
  56. "keyVaultName",
  57. "tenantId",
  58. "clientId",
  59. "clientSecret",
  60. "certificateName");
  61.  
  62. var sp = new SignatureProperties()
  63. {
  64. SignatureBuilder = new Pkcs7SignatureBuilder()
  65. {
  66. SignatureGenerator = sg,
  67. CertificateChain = new X509Certificate2[] { sg.Certificate },
  68. },
  69. SignatureField = doc.AcroForm.Fields[0]
  70. };
  71. doc.Sign(sp, stream);
  72. }
  73. catch (Exception)
  74. {
  75. var page = doc.Pages[0];
  76. var r = doc.AcroForm.Fields[0].Widgets[0].Rect;
  77. Common.Util.AddNote(
  78. "Signing failed because dummy Azure credentials were used.\n" +
  79. "Use valid Azure Key Vault credentials to sign the PDF.",
  80. page,
  81. new RectangleF(r.Left, r.Bottom + 24, page.Size.Width - r.Left * 2, 0));
  82. doc.Save(stream);
  83. }
  84.  
  85. // Done.
  86. return doc.Pages.Count;
  87. }
  88. }
  89.  
  90. /// <summary>
  91. /// Implements <see cref="IPkcs7SignatureGenerator"/>
  92. /// and allows generating a digital signature using
  93. /// a certificate stored in Azure Key Vault.
  94. /// </summary>
  95. public class AzureSignatureGenerator : IPkcs7SignatureGenerator, IDisposable
  96. {
  97. private CertificateClient _certificateClient;
  98. private X509Certificate2 _certificate;
  99. private CryptographyClient _cryptographyClient;
  100.  
  101. /// <summary>
  102. /// Initializes a new instance of the <see cref="AzureSignatureGenerator"/> class.
  103. /// </summary>
  104. /// <param name="keyVaultName">
  105. /// The name of the Key Vault storage used to create a URL in the form
  106. /// <b>https://{keyVaultName}.vault.azure.net/</b> that will be passed to
  107. /// the <see cref="CertificateClient"/> ctor.</param>
  108. /// <param name="tenantId">
  109. /// The Azure Active Directory tenant (directory) ID of the service principal.
  110. /// This value will be used to create the <see cref="ClientSecretCredential"/>.</param>
  111. /// <param name="clientId">
  112. /// The client (application) ID of the service principal.
  113. /// This value will be used to create the <see cref="ClientSecretCredential"/>.</param>
  114. /// <param name="clientSecret">
  115. /// The client secret that was generated for the App Registration used to authenticate the client.
  116. /// This value will be used to create the <see cref="ClientSecretCredential"/>.</param>
  117. /// <param name="certificateName">
  118. /// The name of the certificate to be used for the signature.</param>
  119. public AzureSignatureGenerator(
  120. string keyVaultName,
  121. string tenantId,
  122. string clientId,
  123. string clientSecret,
  124. string certificateName)
  125. {
  126. var keyVaultUri = new Uri($"https://{keyVaultName}.vault.azure.net/");
  127. TokenCredential credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
  128. _certificateClient = new CertificateClient(keyVaultUri, credential);
  129. var c = _certificateClient.GetCertificate(certificateName);
  130. _certificate = new X509Certificate2(c.Value.Cer);
  131. _cryptographyClient = new CryptographyClient(c.Value.KeyId, credential);
  132. }
  133.  
  134. /// <summary>
  135. /// Gets the ID of the hash algorithm.
  136. /// </summary>
  137. public OID HashAlgorithm => OID.HashAlgorithms.SHA256;
  138.  
  139. /// <summary>
  140. /// Gets the ID of the encryption algorithm.
  141. /// </summary>
  142. public OID DigestEncryptionAlgorithm => OID.EncryptionAlgorithms.RSA;
  143.  
  144. /// <summary>
  145. /// Gets the certificate.
  146. /// </summary>
  147. public X509Certificate2 Certificate => _certificate;
  148.  
  149. /// <summary>
  150. /// Signs data.
  151. /// </summary>
  152. /// <param name="input">The input data to sign.</param>
  153. /// <returns>The signed data.</returns>
  154. public byte[] SignData(byte[] input)
  155. {
  156. var hashDigest = new Sha256Digest();
  157. byte[] hash = new byte[hashDigest.GetDigestSize()];
  158. hashDigest.Reset();
  159. hashDigest.BlockUpdate(input, 0, input.Length);
  160. hashDigest.DoFinal(hash, 0);
  161. byte[] result = _cryptographyClient.Sign(SignatureAlgorithm.RS256, hash).Signature;
  162. return result;
  163. }
  164.  
  165. /// <summary>
  166. /// Releases resources used by this object.
  167. /// </summary>
  168. public void Dispose()
  169. {
  170. _certificateClient = null;
  171. if (_certificate != null)
  172. {
  173. _certificate.Dispose();
  174. _certificate = null;
  175. }
  176. _cryptographyClient = null;
  177. }
  178. }
  179. }
  180.