officefileapi-403722-pdf-document-api-examples-document-protection-how-to-use-azure-key-vault-api-to-sign-a-document.md
This example uses the Azure Key Vault API to sign a PDF document.
View Example: How to sign a PDF document using Azure Key Vault API
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;
}
}
Public Class AzureKeyVaultClient
Public Shared Function CreateClient(ByVal keyVaultUri As Uri) As AzureKeyVaultClient
Dim tokenCredential = New DefaultAzureCredential(New DefaultAzureCredentialOptions With {
.ExcludeInteractiveBrowserCredential = False,
.ExcludeVisualStudioCodeCredential = True
})
Return New AzureKeyVaultClient(New KeyClient(keyVaultUri, tokenCredential), tokenCredential)
End Function
Private ReadOnly client As KeyClient
Private clientCredentials As TokenCredential
Private Sub New(ByVal client As KeyClient, ByVal cryptoClientCredential As TokenCredential)
Me.client = client
Me.clientCredentials = cryptoClientCredential
End Sub
Public Function Sign(ByVal certificateName As String, ByVal algorithm As SignatureAlgorithm, ByVal digest() As Byte, Optional ByVal version As String = Nothing) As Byte()
Dim cloudRsaKey As KeyVaultKey = client.GetKey(certificateName, version:= version)
Dim rsaCryptoClient = New CryptographyClient(cloudRsaKey.Id, clientCredentials)
Dim rsaSignResult As SignResult = rsaCryptoClient.Sign(algorithm, digest)
Debug.WriteLine($"Signed digest using the algorithm {rsaSignResult.Algorithm}, " & $"with key {1}. The resulting signature is {2}")
Return rsaSignResult.Signature
End Function
Public Function GetCertificateData(ByVal keyId As String) As KeyVaultCertificateWithPolicy
Dim certificateClient = New CertificateClient(client.VaultUri, clientCredentials)
Dim cert As KeyVaultCertificateWithPolicy = certificateClient.GetCertificate(keyId)
Return cert
End Function
End Class
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 following code snippet 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;
}
}
Public Class AzureKeyVaultSigner
Inherits Pkcs7SignerBase
'OID for RSA signing algorithm:
Private Const PKCS1RsaEncryption As String = "1.2.840.113549.1.1.1"
Private ReadOnly keyVaultClient As AzureKeyVaultClient
Private ReadOnly certificate As KeyVaultCertificateWithPolicy
'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 Overrides ReadOnly Property DigestCalculator() As IDigestCalculator 'Digest algorithm
Get
Return New DigestCalculator(HashAlgorithmType.SHA256)
End Get
End Property
Protected Overrides ReadOnly Property SigningAlgorithmOID() As String
Get
Return PKCS1RsaEncryption
End Get
End Property
''' <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 Sub New(ByVal keyVaultClient As AzureKeyVaultClient, ByVal certificateName As String, Optional ByVal certificateVersion As String = Nothing, Optional ByVal tsaClient As ITsaClient = Nothing, Optional ByVal ocspClient As IOcspClient = Nothing, Optional ByVal crlClient As ICrlClient = Nothing, Optional ByVal profile As PdfSignatureProfile = PdfSignatureProfile.PAdES_BES)
MyBase.New(tsaClient, ocspClient, crlClient, profile)
If String.IsNullOrEmpty(certificateName) Then
Throw New System.ArgumentException("Certificate name must not be null or empty.")
End If
If certificateName.Contains("/"c) Then
Throw New System.ArgumentException("Invalid certificate name. Certificate name must not contain '/' character.")
End If
If certificateVersion IsNot Nothing AndAlso certificateVersion.Contains("/"c) Then
Throw New System.ArgumentException("Invalid certificate version. Certificate version must not contain '/' character.")
End If
Me.keyVaultClient = keyVaultClient
'Get certificate (without public key) via GetCertificateAsync API
'You can get the whole certificate chain here
Me.certificate = keyVaultClient.GetCertificateData($"{certificateName}/{certificateVersion}")
End Sub
Protected Overrides Function GetCertificates() As IEnumerable(Of Byte())
Dim certificateChain As New List(Of Byte())()
Dim x509 = New X509Certificate2(certificate.Cer)
Dim chain = New X509Chain()
If True Then
chain.Build(x509)
For Each item In chain.ChainElements
certificateChain.Add(item.Certificate.GetRawCertData())
Next item
End If
Return certificateChain
End Function
Protected Overrides Function SignDigest(ByVal digest() As Byte) As Byte()
Dim signature = keyVaultClient.Sign(certificate.Name, SignatureAlgorithm.RS256, digest)
Return signature
End Function
End Class
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 });
}
Imports DevExpress.Office.DigitalSignatures
Imports DevExpress.Office.Tsp
Imports DevExpress.Pdf
Imports System
Imports System.Diagnostics
' ...
Using signer = New PdfDocumentSigner("Document.pdf")
' Create a timestamp:
Dim tsaClient As ITsaClient = New TsaClient(New Uri("https://freetsa.org/tsr"), HashAlgorithmType.SHA256)
' Specify the signature's field name and location:
Dim pageNumber As Integer = 1
Dim description = New PdfSignatureFieldInfo(pageNumber)
description.Name = "SignatureField"
description.SignatureBounds = New PdfRectangle(10, 10, 50, 150)
' Specify your Azure Key Vault URL - (vaultUri)
Const keyVaultUrl As String = ""
' Specify the Name of and Azure Key Vault Certificate (certId).
' This is listed in the "Name" column of your Azure Portal, Certificates page
Dim certificateName As String = ""
' 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.
Dim certificateVersion As String = ""
' Create a custom signer object:
Dim client = AzureKeyVaultClient.CreateClient(New Uri(keyVaultUrl))
Dim azureSigner As New AzureKeyVaultSigner(client, certificateName:= certificateName, certificateVersion:= certificateVersion, tsaClient)
' Apply a signature to a new form field:
Dim 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:
Dim output As String = "SignedDocument.pdf"
signer.SaveDocument(output, signatureBuilder)
Process.Start(New ProcessStartInfo(output) With {.UseShellExecute = True})
End Using