Skip to main content
All docs
V24.1

How to: Use Azure Key Vault API to Sign a PDF Document

  • 7 minutes to read

This example demonstrates how to use the Azure Key Vault API to sign a PDF document.

View Example: How to sign a PDF document using Azure Key Vault API

Prerequisites

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