//
// 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;
}
}
}