PDF Digital Signature with an Azure Key Vault Non-exportable HSM Certificate in C#

void DigitallySignPDFFile()
{
    PdfSignature ps = new PdfSignature("");

    //load the PDF document
    ps.LoadPdfDocument("unsigned.pdf");

    ps.HashAlgorithm = SignLib.HashAlgorithm.SHA256;

    string keyVaultUrl = "https://YOUR-KEYVAULT-NAME.vault.azure.net/";
    string vaultCertificateName = "NAME-OF-THE-CERTIFICATE";

    //Digital signature certificate will be loaded from Azure Key Vault
    ExternalSignature exSignature = new ExternalSignature(new Uri(keyVaultUrl), vaultCertificateName);

    //bind the external signature with the library
    DigitalCertificate.UseExternalSignatureProvider = exSignature;

    //set the certificate
    ps.DigitalSignatureCertificate = exSignature.RemoteSignatureCertificate;

    //the signature will be timestamped.
    ps.TimeStamping.ServerUrl = new Uri("https://ca.signfiles.com/TSAServer.aspx");

    //write the signed file
    File.WriteAllBytes("signed.pdf", ps.ApplyDigitalSignature());
}

public class ExternalSignature : Certificates.IExternalSignature
{
    Uri _keyVaultUrl;
    string _vaultCertName;
    Uri _privateKeyUrl;
    public X509Certificate2 RemoteSignatureCertificate { get; }

    public ExternalSignature(Uri keyVaultUrl, string vaultCertName)
    {
        //get the Azure key vault location
        _keyVaultUrl = keyVaultUrl;

        //get the Azure certificate name
        _vaultCertName = vaultCertName;

        //Get the Azure certificate reference
        var certClient = new CertificateClient(_keyVaultUrl, new DefaultAzureCredential());
        var azureCertificate = certClient.GetCertificate(_vaultCertName).Value;

        //get the private key Uri. The key cannot be exported. It can be only invoked
        _privateKeyUrl = azureCertificate.KeyId;

        //set the digital certificate in order to be used on signature creation
        RemoteSignatureCertificate = new X509Certificate2(azureCertificate.Cer);
    }
    
    //this methos is invoked by the PDF digital signature engine when the signature is applied
    public byte[] ApplySignature(byte[] dataToSign, Oid oid)
    {
        //the the Azure private key reference. The key is not exported
        var rsaCryptoClient = new CryptographyClient(_privateKeyUrl, new DefaultAzureCredential());

        //digitally sign the PDF data with the Azure unexportable private key
        SignResult rsaSignResult = rsaCryptoClient.SignData(SignatureAlgorithm.RS256, dataToSign);

        return rsaSignResult.Signature;
    }
}

See also: