SignWithECDSA.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.Asn1
Imports Org.BouncyCastle.Crypto
Imports Org.BouncyCastle.Security
Imports Org.BouncyCastle.Pkcs
Imports Org.BouncyCastle.Crypto.Parameters
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 an Elliptic Curve Digital Signature Algorithm
'' (ECDSA) certificate.
''
'' The sample includes a ready to use class BCSignatureGenerator that implements
'' the GrapeCity.Documents.Pdf.IPkcs7SignatureGenerator interface using the
'' BouncyCastle.Cryptography package. Because unlike the current .NET system libraries,
'' BouncyCastle supports ECDSA, the BCSignatureGenerator class can be used in your
'' applications to handle ECDSA certificates.
Public Class SignWithECDSA
    Function CreatePDF(ByVal stream As Stream) As Integer
        Dim certPath = Path.Combine("Resources", "Misc", "DsPdfTest3_ECDSA.pfx")
        Dim certPwd = "password"

        Dim doc = New GcPdfDocument()
        Using s = File.OpenRead(Path.Combine("Resources", "PDFs", "SignWithECDSA.pdf"))
            doc.Load(s)

            Dim sp = New SignatureProperties() With {
                .SignatureBuilder = New Pkcs7SignatureBuilder() With {
                    .SignatureGenerator = New BCSignatureGenerator(certPath, certPwd, OID.HashAlgorithms.SHA256),
                    .CertificateChain = SecurityUtils.GetCertificateChain(certPath, certPwd)
                },
                .SignatureField = doc.AcroForm.Fields(0)
            }
            sp.SignatureAppearance.Caption = "ECDSA"
            doc.Sign(sp, stream)
        End Using

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

''' <summary>
''' Implements IPkcs7SignatureGenerator.
''' This implementation uses BouncyCastle libraries which,
''' unlike the current .NET system libraries, support ECDSA
''' (Elliptic Curve Digital Signature Algorithm) keys.
''' </summary>
Public Class BCSignatureGenerator
    Implements IPkcs7SignatureGenerator

    Private _hashAlgorithm As OID
    Private _encryptionAlgorithm As OID
    Private _encryptionAlgorithmName As String
    Private _key As ICipherParameters

    Public Sub New(ByVal key As ICipherParameters, ByVal hashAlgorithm As OID)
        _hashAlgorithm = hashAlgorithm
        If TypeOf key Is RsaKeyParameters Then
            _encryptionAlgorithm = OID.EncryptionAlgorithms.RSA
            _encryptionAlgorithmName = "RSA"
        ElseIf TypeOf key Is DsaKeyParameters Then
            _encryptionAlgorithm = OID.EncryptionAlgorithms.DSA
            _encryptionAlgorithmName = "DSA"
        ElseIf TypeOf key Is ECKeyParameters Then
            _encryptionAlgorithm = OID.EncryptionAlgorithms.ECDSA
            _encryptionAlgorithmName = "ECDSA"
        Else
            Throw New Exception($"Unknown algorithm used in the private key [{key}]")
        End If
        _key = key
    End Sub

    Public Sub New(ByVal certificateData As Byte(), ByVal password As String, ByVal hashAlgorithm As OID)
        Me.New(GetPrivateKey(certificateData, password), hashAlgorithm)
    End Sub

    Public Sub New(ByVal certificateStream As Stream, ByVal password As String, ByVal hashAlgorithm As OID)
        Me.New(GetPrivateKey(certificateStream, password), hashAlgorithm)
    End Sub

    Public Sub New(ByVal certificateFileName As String, ByVal password As String, ByVal hashAlgorithm As OID)
        Me.New(GetPrivateKey(certificateFileName, password), hashAlgorithm)
    End Sub

    Public ReadOnly Property HashAlgorithm As OID Implements IPkcs7SignatureGenerator.HashAlgorithm
        Get
            Return _hashAlgorithm
        End Get
    End Property

    Public ReadOnly Property DigestEncryptionAlgorithm As OID Implements IPkcs7SignatureGenerator.DigestEncryptionAlgorithm
        Get
            Return _encryptionAlgorithm
        End Get
    End Property

    Public Function SignData(ByVal digest As Byte()) As Byte() Implements IPkcs7SignatureGenerator.SignData
        Dim han = DigestUtilities.GetAlgorithmName(New DerObjectIdentifier(_hashAlgorithm.ToString()))
        Dim algorithm = han & "with" & _encryptionAlgorithmName
        Dim sig As ISigner = SignerUtilities.GetSigner(algorithm)
        sig.Init(True, _key)
        sig.BlockUpdate(digest, 0, digest.Length)
        Return sig.GenerateSignature()
    End Function

    Public Shared Function GetPrivateKey(ByVal certificateStream As Stream, ByVal password As String) As ICipherParameters
        Dim p = New Char(password.Length - 1) {}
        For i = 0 To p.Length - 1
            p(i) = password(i)
        Next
        Dim pk12 As Pkcs12Store = New Pkcs12StoreBuilder().Build()
        pk12.Load(certificateStream, p)
        For Each a In pk12.Aliases
            Dim aliasStr = DirectCast(a, String)
            If pk12.IsKeyEntry(aliasStr) Then
                Dim keyEntry = pk12.GetKey(aliasStr)
                If keyEntry.Key.IsPrivate Then
                    Return keyEntry.Key
                End If
            End If
        Next
        Throw New Exception("Cannot get private key.")
    End Function

    Public Shared Function GetPrivateKey(ByVal certificateFilePath As String, ByVal password As String) As ICipherParameters
        Using fs = New FileStream(certificateFilePath, FileMode.Open)
            Return GetPrivateKey(fs, password)
        End Using
    End Function

    Public Shared Function GetPrivateKey(ByVal certificateData As Byte(), ByVal password As String) As ICipherParameters
        Using ms = New MemoryStream(certificateData)
            Return GetPrivateKey(ms, password)
        End Using
    End Function
End Class