How to: Use Azure Key Vault API to Sign a PDF Document
- 8 minutes to read
This example demonstrates how to use the Azure Key Vault API to sign a PDF document.
Prerequisites
- An Azure subscription.
- An existing Azure Key Vault. If you need to create an Azure Key Vault, you can use the Azure Portal or Azure CLI.
Obtain a Certificate
Use Azure Key Vault API to obtain a certificate or the whole certificate chain.
The AzureKeyVaultClient
class below uses the Azure Key Vault API to retrieve a certificate or a certificate chain, and signs the document hash with a private key stored on Azure.
public class AzureKeyVaultClient
{
public static AzureKeyVaultClient CreateClient(Uri keyVaultUri)
{
var tokenCredential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
ExcludeInteractiveBrowserCredential = false,
ExcludeVisualStudioCodeCredential = true
});
return new AzureKeyVaultClient(new KeyClient(keyVaultUri, tokenCredential), tokenCredential);
}
readonly KeyClient client;
TokenCredential clientCredentials;
AzureKeyVaultClient(KeyClient client, TokenCredential cryptoClientCredential)
{
this.client = client;
this.clientCredentials = cryptoClientCredential;
}
public byte[] Sign(string certificateName, SignatureAlgorithm algorithm, byte[] digest, string version = null)
{
KeyVaultKey cloudRsaKey = client.GetKey(certificateName, version: version);
var rsaCryptoClient = new CryptographyClient(cloudRsaKey.Id, clientCredentials);
SignResult rsaSignResult = rsaCryptoClient.Sign(algorithm, digest);
Debug.WriteLine($"Signed digest using the algorithm {rsaSignResult.Algorithm}, " +
$"with key {rsaSignResult.KeyId}. The resulting signature is {Convert.ToBase64String(rsaSignResult.Signature)}");
return rsaSignResult.Signature;
}
public KeyVaultCertificateWithPolicy GetCertificateData(string keyId)
{
var certificateClient = new CertificateClient(client.VaultUri, clientCredentials);
KeyVaultCertificateWithPolicy cert = certificateClient.GetCertificate(keyId);
return cert;
}
}
Create a Pkcs7SignerBase descendant to create a custom signer class. This class helps you to retrieve a certificate or a certificate chain, and sign the document hash. To calculate the document hash, you can use the DigestCalculator class. This class supports the following hashing algorithms: SHA1, SHA256, SHA384, and SHA512. Override the Pkcs7SignerBase.DigestCalculator
property to return a DigestCalculator
instance.
The code sample below shows the AzureKeyVaultSigner
class that is the Pkcs7SignerBase descendant. The SignDigest
method overload calls the AzureKeyVaultClient.Sign
method to sign the calculated document hash with a private key (specified by the keyId
variable).
public class AzureKeyVaultSigner : Pkcs7SignerBase
{
//OID for RSA signing algorithm:
const string PKCS1RsaEncryption = "1.2.840.113549.1.1.1";
readonly AzureKeyVaultClient keyVaultClient;
readonly KeyVaultCertificateWithPolicy certificate;
//Must match with key algorithm (RSA or ECDSA)
//For RSA PKCS1RsaEncryption(1.2.840.113549.1.1.1) OID can be used with any digest algorithm
//For ECDSA use OIDs from this family http://oid-info.com/get/1.2.840.10045.4.3
//Specified digest algorithm must be same with DigestCalculator algorithm.
protected override IDigestCalculator DigestCalculator => new DigestCalculator(HashAlgorithmType.SHA256); //Digest algorithm
protected override string SigningAlgorithmOID => PKCS1RsaEncryption;
/// <summary>
/// Construct an instance of AzureKeyVaultSigner
/// </summary>
/// <param name="keyVaultClient">API client used to communicate with </param>
/// <param name="certificateName">The name of the Azure Certificate, will not contain any slashes</param>
/// <param name="certificateVersion">The version of the Azure Certificate, looks like a UUID. Leave empty/null to use the version labelled in Azure Portal as the "Current Version"</param>
/// <param name="tsaClient"></param>
/// <param name="ocspClient"></param>
/// <param name="crlClient"></param>
/// <param name="profile"></param>
/// <exception cref="System.ArgumentException"></exception>
public AzureKeyVaultSigner(AzureKeyVaultClient keyVaultClient, string certificateName, string certificateVersion = null, ITsaClient tsaClient = null, IOcspClient ocspClient = null, ICrlClient crlClient = null, PdfSignatureProfile profile = PdfSignatureProfile.PAdES_BES) : base(tsaClient, ocspClient, crlClient, profile)
{
if (string.IsNullOrEmpty(certificateName))
throw new System.ArgumentException("Certificate name must not be null or empty.");
if (certificateName.Contains('/'))
throw new System.ArgumentException("Invalid certificate name. Certificate name must not contain '/' character.");
if (certificateVersion != null && certificateVersion.Contains('/'))
throw new System.ArgumentException("Invalid certificate version. Certificate version must not contain '/' character.");
this.keyVaultClient = keyVaultClient;
//Get certificate (without public key) via GetCertificateAsync API
//You can get the whole certificate chain here
this.certificate = keyVaultClient.GetCertificateData($"{certificateName}/{certificateVersion}");
}
protected override IEnumerable<byte[]> GetCertificates()
{
List<byte[]> certificateChain = new List<byte[]>();
var x509 = new X509Certificate2(certificate.Cer);
var chain = new X509Chain();
{
chain.Build(x509);
foreach (var item in chain.ChainElements)
certificateChain.Add(item.Certificate.GetRawCertData());
}
return certificateChain;
}
protected override byte[] SignDigest(byte[] digest)
{
var signature = keyVaultClient.Sign(certificate.Name, SignatureAlgorithm.RS256, digest);
return signature;
}
}
Sign the Document
Pass the Pkcs7SignerBase descendant object to the PdfSignatureBuilder object constructor to apply a signature to a new form field. Call the PdfDocumentSigner.SaveDocument(String, PdfSignatureBuilder[]) method and pass the PdfSignatureBuilder
object as the method parameter to sign the document and save the result.
using DevExpress.Office.DigitalSignatures;
using DevExpress.Office.Tsp;
using DevExpress.Pdf;
using System;
using System.Diagnostics;
// ...
using (var signer = new PdfDocumentSigner(@"Document.pdf"))
{
// Create a timestamp:
ITsaClient tsaClient = new TsaClient(new Uri(@"https://freetsa.org/tsr"), HashAlgorithmType.SHA256);
// Specify the signature's field name and location:
int pageNumber = 1;
var description = new PdfSignatureFieldInfo(pageNumber);
description.Name = "SignatureField";
description.SignatureBounds = new PdfRectangle(10, 10, 50, 150);
// Specify your Azure Key Vault URL - (vaultUri)
const string keyVaultUrl = "";
// Specify the Name of and Azure Key Vault Certificate (certId).
// This is listed in the "Name" column of your Azure Portal, Certificates page
string certificateName = "";
// Specify the Version for the Azure Key Vault certificate.
// This is listed in the "Version" column of your Azure Portal, Certificates/[Certificate Name] page.
// Leave this empty, or null, to auto-select the "Current" version of the given certificate.
// Warning: Auto-selection will not "fall back" to a non-current certificate should the current certificate
// be disabled, meaning that if the chosen certificate is disabled, signing will generate an error.
string certificateVersion = "";
// Create a custom signer object:
var client = AzureKeyVaultClient.CreateClient(new Uri(keyVaultUrl));
AzureKeyVaultSigner azureSigner = new AzureKeyVaultSigner(client,
certificateName: certificateName,
certificateVersion: certificateVersion,
tsaClient);
// Apply a signature to a new form field:
var signatureBuilder = new PdfSignatureBuilder(azureSigner, description);
// Specify an image and signer information:
signatureBuilder.SetImageData(System.IO.File.ReadAllBytes("signature.jpg"));
signatureBuilder.Location = "LOCATION";
// Sign and save the document:
string output = "SignedDocument.pdf";
signer.SaveDocument(output, signatureBuilder);
Process.Start(new ProcessStartInfo(output) { UseShellExecute = true });
}