Skip to main content
All docs
V25.1
  • How to: Use Azure Key Vault API to Sign a PDF Document

    • 9 minutes to read

    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

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

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