This C# sample signs a PDF file with a non-exportable private key stored in Azure Key Vault or Azure Managed HSM. The key never leaves Azure: SignLib prepares the data to sign and Azure Key Vault creates the signature value. The application uses only the public part of the certificate.
How it works
- The class
AzureKeyVaultExternalSignatureimplementsIExternalSignature. It reads the certificate and the identifier of its key withCertificateClient, and it signs withCryptographyClient.SignData(RS256, RS384 or RS512). - The PDF signature gets the public certificate in
DigitalSignatureCertificate, and the provider inDigitalCertificate.UseExternalSignatureProvider, set before every signature. - The signature is time-stamped (RFC 3161). The identity needs “Get” on the certificates and “Sign” on the keys (or the roles Key Vault Certificate User and Key Vault Crypto User).
C# code: the Azure Key Vault signature provider
/// <summary>
/// An external signature provider that creates the signature value with a non-exportable private key stored in Azure Key
/// Vault. The class implements SignLib.Certificates.IExternalSignature and its single method, ApplySignature.
/// </summary>
public class AzureKeyVaultExternalSignature : IExternalSignature
{
private readonly DefaultAzureCredential _credential = new DefaultAzureCredential();
private readonly Uri _privateKeyId;
/// <summary>
/// The public part of the certificate stored in Azure Key Vault.
/// </summary>
public X509Certificate2 SigningCertificate { get; }
/// <summary>
/// Gets the certificate from Azure Key Vault. The private key is not downloaded: only its identifier is read.
/// </summary>
/// <param name="keyVaultUrl">The URL of the key vault, for example https://my-key-vault.vault.azure.net/</param>
/// <param name="vaultCertificateName">The name of the certificate in the key vault.</param>
public AzureKeyVaultExternalSignature(Uri keyVaultUrl, string vaultCertificateName)
{
CertificateClient certificateClient = new CertificateClient(keyVaultUrl, _credential);
KeyVaultCertificateWithPolicy azureCertificate = certificateClient.GetCertificate(vaultCertificateName).Value;
if (azureCertificate.Policy.KeyType != CertificateKeyType.Rsa && azureCertificate.Policy.KeyType != CertificateKeyType.RsaHsm)
throw new NotSupportedException("This sample supports only the certificates with RSA keys. The key type of the certificate is " + azureCertificate.Policy.KeyType);
// The identifier of the private key. The key cannot be exported: it can only be used to sign inside Azure.
_privateKeyId = azureCertificate.KeyId;
// The public part of the certificate (DER encoded).
SigningCertificate = new X509Certificate2(azureCertificate.Cer);
}
/// <summary>
/// Signs a message with the private key stored in Azure Key Vault.
/// </summary>
/// <param name="message">The data to sign (SignLib decides what has to be signed). It is not a hash: Azure Key Vault hashes it.</param>
/// <param name="hashAlgorithm">The OID of the hash algorithm requested by SignLib.</param>
/// <returns>The signature value (RSA PKCS#1 v1.5).</returns>
public byte[] ApplySignature(byte[] message, Oid hashAlgorithm)
{
try
{
CryptographyClient cryptographyClient = new CryptographyClient(_privateKeyId, _credential);
// SignData hashes the message with the hash algorithm of the signature algorithm (RS256 = SHA-256 + RSA PKCS#1 v1.5)
// and sends only the hash to Azure Key Vault.
SignResult signResult = cryptographyClient.SignData(GetAzureSignatureAlgorithm(hashAlgorithm.Value), message);
return signResult.Signature;
}
catch (Exception ex)
{
throw new CryptographicException("The signature cannot be created by Azure Key Vault: " + ex.Message, ex);
}
}
/// <summary>
/// Converts the OID of the hash algorithm requested by SignLib to the signature algorithm of Azure Key Vault.
/// </summary>
private static Azure.Security.KeyVault.Keys.Cryptography.SignatureAlgorithm GetAzureSignatureAlgorithm(string hashAlgorithmOid)
{
switch (hashAlgorithmOid)
{
case "2.16.840.1.101.3.4.2.1":
return Azure.Security.KeyVault.Keys.Cryptography.SignatureAlgorithm.RS256;
case "2.16.840.1.101.3.4.2.2":
return Azure.Security.KeyVault.Keys.Cryptography.SignatureAlgorithm.RS384;
case "2.16.840.1.101.3.4.2.3":
return Azure.Security.KeyVault.Keys.Cryptography.SignatureAlgorithm.RS512;
default:
// SHA-1 is not supported by Azure Key Vault.
throw new NotSupportedException("The hash algorithm " + hashAlgorithmOid + " is not supported by Azure Key Vault.");
}
}
}
C# code: sign the PDF
/// <summary>
/// Signs a PDF file with a non-exportable private key stored in Azure Key Vault.
/// </summary>
static void SignPdfWithAzureKeyVaultKey(string unsignedPdfFile, string signedPdfFile)
{
PdfSignature pdfSignature = new PdfSignature(LibrarySerialNumber);
pdfSignature.LoadPdfDocument(unsignedPdfFile);
pdfSignature.SigningReason = "I approve this document";
pdfSignature.SigningLocation = "Accounting department";
pdfSignature.SignaturePosition = SignaturePosition.TopLeft;
pdfSignature.HashAlgorithm = SignLib.HashAlgorithm.SHA256;
// The certificate and the reference of its private key are read from Azure Key Vault.
AzureKeyVaultExternalSignature externalSignature = new AzureKeyVaultExternalSignature(new Uri(KeyVaultUrl), VaultCertificateName);
// The library receives only the public part of the certificate: the private key stays in Azure Key Vault.
pdfSignature.DigitalSignatureCertificate = externalSignature.SigningCertificate;
// Bind the external signature provider to the next signature. The provider is released by the library at the end
// of the signing operation, so it must be set again before every signature.
DigitalCertificate.UseExternalSignatureProvider = externalSignature;
// The signature is time-stamped (RFC 3161). Remove this line for a signature without time-stamp.
pdfSignature.TimeStamping.ServerUrl = TimestampServerUrl;
File.WriteAllBytes(signedPdfFile, pdfSignature.ApplyDigitalSignature());
Console.WriteLine("The PDF signature was created." + Environment.NewLine);
}
The sample supports RSA and RSA-HSM keys. The same mechanism works with any HSM or remote signing service: see External and remote PDF signature.
Run the full sample. The complete Visual Studio project PDF Digital Signature with Azure Key Vault Nonexportable HSM Certificate is included in the library download, in the folder NET Sample Projects (.NET 9, .NET 10 and .NET 11), with the test files and a test certificate. Without a serial number the library runs in demo mode. Download the library and the samples · User manual
Frequently asked questions
Is the PDF document sent to Azure?
No. SignData hashes the data to sign locally and sends only the hash to Azure Key Vault.
Can I use CAdES or XAdES signatures with Azure Key Vault?
Yes. The external signature provider works with all the signature formats of the library.