SignAzureKeyVault.vb
''
'' This code is part of Document Solutions for PDF .NET demos.
'' Copyright (c) MESCIUS inc. All rights reserved.
''
Imports System
Imports System.IO
Imports System.Drawing
Imports System.Security.Cryptography.X509Certificates
Imports Org.BouncyCastle.Crypto
Imports Org.BouncyCastle.Crypto.Digests
Imports Org.BouncyCastle.Asn1
Imports Org.BouncyCastle.Asn1.X509
Imports Azure.Core
Imports Azure.Identity
Imports Azure.Security.KeyVault.Certificates
Imports Azure.Security.KeyVault.Keys
Imports Azure.Security.KeyVault.Keys.Cryptography
Imports GrapeCity.Documents.Pdf
Imports GrapeCity.Documents.Pdf.Security
Imports GrapeCity.Documents.Pdf.AcroForms
Imports GrapeCity.Documents.Text

'' This sample shows how to sign an existing PDF file that contains
'' an empty signature field with a certificate that is stored
'' in an Azure Key Vault.
''
'' The sample includes a ready to use utility class AzureSignatureGenerator
'' that implements the GrapeCity.Documents.Pdf.IPkcs7SignatureGenerator interface,
'' and can be used to sign PDFs with certificates stored in Azure Key Vault.
''
'' Please note that when run directly off the DsPdf demo site,
'' this sample will NOT sign the PDF, as it passes dummy Azure credentials
'' to the AzureSignatureGenerator's ctor. You will need to download the sample
'' and provide your own credentials for the sample code to actually sign a PDF.
Public Class SignAzureKeyVault
    Function CreatePDF(ByVal stream As Stream) As Integer
        Dim doc = New GcPdfDocument()
        Using s = File.OpenRead(Path.Combine("Resources", "PDFs", "SignAzureKeyVault.pdf"))
            doc.Load(s)

        Try
            '' This WILL NOT WORK due to dummy Azure credentials.
            '' Supply valid credentials to actually sign the PDF.
            Using sg = New AzureSignatureGenerator(
                    "keyVaultName",
                    "tenantId",
                    "clientId",
                    "clientSecret",
                    "certificateName")

                Dim sp = New SignatureProperties() With {
                    .SignatureBuilder = New Pkcs7SignatureBuilder() With {
                        .SignatureGenerator = sg,
                        .CertificateChain = New X509Certificate2() {sg.Certificate}
                    },
                    .SignatureField = doc.AcroForm.Fields(0)
                }
                doc.Sign(sp, stream)
            End Using
        Catch e As Exception
            Dim page = doc.Pages(0)
            Dim r = doc.AcroForm.Fields(0).Widgets(0).Rect
            Util.AddNote(
                "Signing failed because dummy Azure credentials were used." & vbLf &
                "Use valid Azure Key Vault credentials to sign the PDF.",
                page,
                New RectangleF(r.Left, r.Bottom + 24, page.Size.Width - r.Left * 2, 0))
            doc.Save(stream)
        End Try
        End Using

        '' Done.
        Return doc.Pages.Count
    End Function
End Class

''' <summary>
''' Implements IPkcs7SignatureGenerator
''' and allows generating a digital signature using
''' a certificate stored in Azure Key Vault.
''' </summary>
Public Class AzureSignatureGenerator
    Implements IPkcs7SignatureGenerator
    Implements IDisposable

    Private _certificateClient As CertificateClient
    Private _certificate As X509Certificate2
    Private _cryptographyClient As CryptographyClient

    ''' <summary>
    ''' Initializes a new instance of the AzureSignatureGenerator class.
    ''' </summary>
    Public Sub New(
            ByVal keyVaultName As String,
            ByVal tenantId As String,
            ByVal clientId As String,
            ByVal clientSecret As String,
            ByVal certificateName As String)
        Dim keyVaultUri = New Uri($"https://{keyVaultName}.vault.azure.net/")
        Dim credential As TokenCredential = New ClientSecretCredential(tenantId, clientId, clientSecret)
        _certificateClient = New CertificateClient(keyVaultUri, credential)
        Dim c = _certificateClient.GetCertificate(certificateName)
        _certificate = New X509Certificate2(c.Value.Cer)
        _cryptographyClient = New CryptographyClient(c.Value.KeyId, credential)
    End Sub

    ''' <summary>
    ''' Gets the ID of the hash algorithm.
    ''' </summary>
    Public ReadOnly Property HashAlgorithm As OID Implements IPkcs7SignatureGenerator.HashAlgorithm
        Get
            Return OID.HashAlgorithms.SHA256
        End Get
    End Property

    ''' <summary>
    ''' Gets the ID of the encryption algorithm.
    ''' </summary>
    Public ReadOnly Property DigestEncryptionAlgorithm As OID Implements IPkcs7SignatureGenerator.DigestEncryptionAlgorithm
        Get
            Return OID.EncryptionAlgorithms.RSA
        End Get
    End Property

    ''' <summary>
    ''' Gets the certificate.
    ''' </summary>
    Public ReadOnly Property Certificate As X509Certificate2
        Get
            Return _certificate
        End Get
    End Property

    ''' <summary>
    ''' Signs data.
    ''' </summary>
    Public Function SignData(ByVal input As Byte()) As Byte() Implements IPkcs7SignatureGenerator.SignData
        Dim hashDigest = New Sha256Digest()
        Dim hash = New Byte(hashDigest.GetDigestSize() - 1) {}
        hashDigest.Reset()
        hashDigest.BlockUpdate(input, 0, input.Length)
        hashDigest.DoFinal(hash, 0)
        Dim result As Byte() = _cryptographyClient.Sign(SignatureAlgorithm.RS256, hash).Signature
        Return result
    End Function

    ''' <summary>
    ''' Releases resources used by this object.
    ''' </summary>
    Public Sub Dispose() Implements IDisposable.Dispose
        _certificateClient = Nothing
        If _certificate IsNot Nothing Then
            _certificate.Dispose()
            _certificate = Nothing
        End If
        _cryptographyClient = Nothing
    End Sub
End Class