Sign PDF with Azure Key Vault Non-Exportable Certificate

Mike 96 Reputation points
2021-06-25T17:44:46.687+00:00

Hi,

I am looking to sign a PDF using an AATL compliant certificate stored in Azure Key Vault. I am using Aspose PDF for my document creation and signing. It looks to me that they support signing as they support signing from the windows certificate store as seen in their documents sign-pdf-document-from-smart-card. Is there a way for me to retrieve the secret and key from Azure Key Vault (with the methods below) and build a X509 to pass to their method instead of loading from the key store?

https://myvault.vault.azure.net/keys/testSigning/{mycertid}
https://myvault.vault.azure.net/secrets/testSigning/{mycertid}

I can create an X509 from the secret, but when I add the key, Aspose throws an error saying the Private Key does not match.

Using document As New Document(sourcePath)

            Using signature As New PdfFileSignature(document)

                signature.BindPdf(document)

                Dim store As X509Store = New X509Store
                Dim data As Byte() = Convert.FromBase64String(secret)

                Dim cert As New X509Certificate2(data)
                Dim prov As RSACryptoServiceProvider = New RSACryptoServiceProvider() 
                key = Convert.ToBase64String(Encoding.Unicode.GetBytes(key))
                prov.FromXmlString("")
                cert.PrivateKey = prov

                Dim extSig As ExternalSignature = New ExternalSignature(cert)


                signature.Sign(1, True, New System.Drawing.Rectangle(300, 100, 400, 200), extSig)
                signature.Save(signedPath)

            End Using

        End Using

I know iText supports this, but I don't want to pay close to $10K a year for iText just to sign my documents.

Thanks,
Mike

Azure Key Vault
Azure Key Vault

An Azure service that is used to manage and protect cryptographic keys and other secrets used by cloud apps and services.


Answer accepted by question author
Mike 96 Reputation points
2021-06-30T15:19:33.187+00:00

Hi,

Thanks for your reply. I did look through the documentation. My challenge was figuring out how to sign a PDF with a certificate marked as non-exportable which does not allow the private key to leave Azure Key Vault not allowing me to create a .PFX to sign with. I ended up finding a PDF library called ABCPdf that supported using the KeyVaultClient.SignWithHttpMessagesAsync() method with is what I used to get this to work. I am posting my code below if anyone else is interested in this.

using WebSupergoo.ABCpdf12;
using WebSupergoo.ABCpdf12.Objects;

public void SignDoc()
{
    var sourcePath = @"C:\TestDoc.pdf";
    var signedPath = @"C:\TestDoc_Signed.pdf";

    var cert = AzureHelper.GetX509();

    using (Doc doc = new Doc())
    {
        doc.Read(sourcePath);
        doc.Form.AddSignature(new XRect(), "Signature1");

        Signature sig = (Signature)doc.Form.Fields("Signature1");
        sig.Rect = XRect.FromLtwh(50, 50, 50, 10);
        sig.Signer = "Signer Name";
        sig.Value = "Signer Name";
        sig.TimestampServiceUrl = new Uri("http://timestamp.digicert.com");
        sig.CustomSigner = ExternalSigner;

        sig.Sign(cert, true, new Oid(CryptoConfig.MapNameToOID("SHA512")), X509IncludeOption.EndCertOnly);
        doc.Save(signedPath);
    }
}

private byte[] ExternalSigner(byte[] data)
{
    return AzureHelper.SignPdf(data);
}

 //Create X509 from secret of certificate in Azure Key Vault
 Public Shared Function GetX509() As X509Certificate2

            Dim vaultUri As String = ConfigurationManager.AppSettings("AzureVaultUri")

            Dim client = New SecretClient(New Uri(vaultUri), New DefaultAzureCredential(True))

            Dim secret = client.GetSecret("testsign").Value.Value

            Dim store As X509Store = New X509Store

            Dim data As Byte() = Convert.FromBase64String(secret)

            Dim cert As New X509Certificate2(data)

            Return cert

        End Function

//Sign PDF digest using Azure KeyVaultClient.SignWithHttpMessagesAsync()
public static byte[] SignPdf(byte[] data)
    {
        var vaultRootUri = ConfigurationManager.AppSettings("AzureVaultUri");
        var keyName = ConfigurationManager.AppSettings("AzureKeyName");
        var keyVersion = ConfigurationManager.AppSettings("AzureKeyVersion");

        var hash = (new SHA512CryptoServiceProvider()).ComputeHash(data);
        var keyVault = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetAccessTokenAsync), new HttpClient());

        return keyVault.SignWithHttpMessagesAsync(vaultRootUri, keyName, keyVersion, JsonWebKeySignatureAlgorithm.RS512, hash).Result.Body.Result;
    }

    /// <summary>
        ///         ''' Gets an access token for the keyvaultclient to use to access the Azure Key Vault. Uses the DefaultAzureCredential Object
        ///         ''' </summary>
        ///         ''' <param name="authority"></param>
        ///         ''' <param name="resource"></param>
        ///         ''' <param name="scope"></param>
        ///         ''' <returns></returns>
    private static async Task<string> GetAccessTokenAsync(string authority, string resource, string scope)
    {
        var tokenCredential = new DefaultAzureCredential();
        var accesstoken = await tokenCredential.GetTokenAsync(new Azure.Core.TokenRequestContext(scopes: new string[] { "https://vault.azure.net/.default" }));

        return accesstoken.Token;
    }

For the authentication I am using the DefaultAzureCredential to access the secret to build a X509 certificate, and I use it again to retrieve a token to be used to sign the pdf digest.

Was this answer helpful?


1 additional answer

Sort by: Most helpful
  1. Chinnu 0 Reputation points
    2023-03-06T09:01:26.82+00:00

    The Syncfusion PDF library external signing process can assist us in signing non-exportable (private key) certificates in Azure Key Vault. Please see the following information for reference.

    When using a non-exportable certificate, the corresponding secret will not include the private key component. Although we can retrieve and create an in-memory representation of the certificate, we cannot use its private key for signing. Instead, we require the public portion of the certificate and the Azure Key Vault to sign the PDF data. To accomplish this, we can use the IPdfExternalSigner API interface to create the external signing portion as shown below.

    internal class ExternalSigner : IPdfExternalSigner
    {
        public string HashAlgorithm => "SHA256";
        private CryptographyClient keyClient;
           
        public ExternalSigner(CryptographyClient client)
        {
            keyClient = client;            
        }
    
        public byte[] Sign(byte[] message, out byte[] timeStampResponse)
        {
            var digest = SHA256.Create().ComputeHash(message);
            timeStampResponse = null;
    
            return keyClient
                .SignAsync(
                    SignatureAlgorithm.RS256,
                    digest)
                .Result.Signature;
        }
    }
    
    

    To add an external signer, we need to provide the public portion of the certificate. We can obtain the public certificates using the following code.

    private X509Certificate2 GetPublicCertificate()
    {
        //Create certificate client.
        CertificateClient certificateClient = new CertificateClient(new Uri("https://keyvault23752.vault.azure.net/"), credential);
    
        //Get the certificate with public key. //TestCertificate123 - certificate name.
        KeyVaultCertificateWithPolicy certificate = certificateClient.GetCertificateAsync("TestCertificate123").Result;
                
        //Create and return the X509Certificate2.
        return new X509Certificate2(certificate.Cer);
    }
    
    

    If a certificate contains multiple certificates, such as a root, intermediate, and issuer certificate, the following code can be used to build the certificate chain.

    //Get the public certifiate.
    X509Certificate2 pubCertifciate = GetPublicCertificate();
    
    //Build the certificate chain.
    X509Chain chain = new X509Chain();
    chain.Build(pubCertifciate);
    
    List<X509Certificate2> certificates = new List<X509Certificate2>();
    for (int i = 0; i < chain.ChainElements.Count; i++)
    {
        certificates.Add(chain.ChainElements[i].Certificate);
    }
    
    

    With the assistance of external signing, we can use the following code to sign the PDF data.

    public ActionResult SignPdfWithNonExportableCertificate()
    {
    
        //Get the public certifiate.
        X509Certificate2 pubCertifciate = GetPublicCertificate();
    
        //Build the certificate chain.
        X509Chain chain = new X509Chain();           
        chain.Build(pubCertifciate);
    
        List<X509Certificate2> certificates = new List<X509Certificate2>();
        for (int i = 0; i < chain.ChainElements.Count; i++)
        {
            certificates.Add(chain.ChainElements[i].Certificate);
        }
    
        FileStream pdfFile = new FileStream(_hostingEnvironment.WebRootPath + "/Input.pdf", FileMode.Open);
    
        //Load the PDF document.
        PdfLoadedDocument loadedDocument = new PdfLoadedDocument(pdfFile);
    
        //Load the existing page.
        PdfLoadedPage? page = loadedDocument.Pages[0] as PdfLoadedPage;
    
        //Create a new PDF signature object.
        PdfSignature signature = new PdfSignature(loadedDocument, page!, null, "Sig1");
    
        signature.Bounds = new Syncfusion.Drawing.RectangleF(0, 0, 200, 100);            
    
        //Create CryptographyClient.
        CryptographyClient client = new CryptographyClient(new Uri("https://keyvault23752.vault.azure.net/keys/TestCertificate123/dbbd00926ef54d40931d3f5df84e223c"), credential);
    
        //Sing using external signer.
        signature.AddExternalSigner(new ExternalSigner(client), certificates, null);
    
        signature.Settings.DigestAlgorithm = DigestAlgorithm.SHA256;
    
        MemoryStream ms = new MemoryStream();
    
        //Save and close the document.
        loadedDocument.Save(ms);
    
        ms.Position = 0;
    
        loadedDocument.Close(true);
    
        return File(ms, "application/pdf", "SignedDocument.pdf");
    }
    
    

    For more information, please refer to the following user guide documentation.

    https://help.syncfusion.com/file-formats/pdf/working-with-digitalsignature#externally-sign-the-pdf-document-using-ipdfexternalsigner

    Was this answer helpful?

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.