Skip to main content
All docs
V24.2

DevExpress v24.2 Update — Your Feedback Matters

Our What's New in v24.2 webpage includes product-specific surveys. Your response to our survey questions will help us measure product satisfaction for features released in this major update and help us refine our plans for our next major release.

Take the survey Not interested

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.

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