''
'' 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.Text
Imports System.Collections.Generic
Imports System.Security.Cryptography.X509Certificates
Imports Org.BouncyCastle.Crypto
Imports Org.BouncyCastle.Crypto.Digests
Imports Org.BouncyCastle.Asn1
Imports Org.BouncyCastle.Asn1.X509
Imports Net.Pkcs11Interop.Common
Imports Net.Pkcs11Interop.HighLevelAPI
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
'' on a USB Token for DSC (Digital Signature Certificate).
''
'' The sample includes a ready to use utility class Pkcs11SignatureGenerator
'' that implements the GrapeCity.Documents.Pdf.IPkcs7SignatureGenerator interface,
'' and can be used to sign PDFs with certificates stored on a USB Token for DSC.
''
'' Please note that when run directly off the DsPdf demo site,
'' this sample will NOT sign the PDF, as it passes dummy library name/parameters.
'' to the Pkcs11SignatureGenerator's ctor. You will need to download the sample
'' and provide your own library and parameters for the sample code to actually sign a PDF.
Public Class SignUsbToken
Function CreatePDF(ByVal stream As Stream) As Integer
Dim doc = New GcPdfDocument()
Using s = File.OpenRead(Path.Combine("Resources", "PDFs", "SignUsbToken.pdf"))
doc.Load(s)
Try
'' This WILL NOT WORK due to dummy USB Token for DSC library name/parameters.
'' Supply valid library name and parameters to actually sign the PDF.
Using sg = New Pkcs11SignatureGenerator(
"path-to-dummy-PKCS11.dll",
Nothing,
Nothing,
Encoding.ASCII.GetBytes("12345"),
Nothing,
Nothing,
OID.HashAlgorithms.SHA512)
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 a dummy USB Token for DSC library name and dummy parameters were used." & vbLf &
"Provide a valid USB Token library and correct parameters 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 on a USB Token for DSC (Digital Signature Certificate).
'''
''' The Pkcs11Interop NuGet package is used to manage the token.
''' </summary>
Public Class Pkcs11SignatureGenerator
Implements IPkcs7SignatureGenerator
Implements IDisposable
Public Shared ReadOnly Factories As New Pkcs11InteropFactories()
Private _pkcs11Library As IPkcs11Library
Private _slot As ISlot
Private _session As ISession
Private _privateKeyHandle As IObjectHandle
Private _ckaLabel As String
Private _ckaId As Byte()
Private _certificate As X509Certificate2
Private _hashAlgorithm As OID
Private _hashDigest As IDigest
''' <summary>
''' Initializes a new instance of the Pkcs11SignatureGenerator class.
''' </summary>
Public Sub New(ByVal libraryPath As String, ByVal tokenSerial As String, ByVal tokenLabel As String, ByVal pin As Byte(), ByVal ckaLabel As String, ByVal ckaId As Byte(), ByVal hashAlgorihtm As OID)
Init(libraryPath, tokenSerial, tokenLabel, pin, ckaLabel, ckaId, hashAlgorihtm)
End Sub
Protected Overrides Sub Finalize()
Dispose(False)
MyBase.Finalize()
End Sub
''' <summary>
''' Releases resources used by this object.
''' </summary>
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Protected Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If _certificate IsNot Nothing Then
_certificate.Dispose()
_certificate = Nothing
End If
If _session IsNot Nothing Then
_session.Dispose()
_session = Nothing
End If
If _pkcs11Library IsNot Nothing Then
_pkcs11Library.Dispose()
_pkcs11Library = Nothing
End If
End If
End Sub
Private Function FindSlot(ByVal tokenSerial As String, ByVal tokenLabel As String) As ISlot
If String.IsNullOrEmpty(tokenSerial) AndAlso String.IsNullOrEmpty(tokenLabel) Then
Throw New ArgumentException("Token serial and/or label has to be specified")
End If
Dim slots As List(Of ISlot) = _pkcs11Library.GetSlotList(SlotsType.WithTokenPresent)
For Each slot As ISlot In slots
Dim tokenInfo As ITokenInfo = Nothing
Try
tokenInfo = slot.GetTokenInfo()
Catch ex As Pkcs11Exception
If ex.RV <> CKR.CKR_TOKEN_NOT_RECOGNIZED AndAlso ex.RV <> CKR.CKR_TOKEN_NOT_PRESENT Then
Throw
End If
End Try
If tokenInfo Is Nothing Then
Continue For
End If
If Not String.IsNullOrEmpty(tokenSerial) Then
If String.Compare(tokenSerial, tokenInfo.SerialNumber, StringComparison.InvariantCultureIgnoreCase) <> 0 Then
Continue For
End If
End If
If Not String.IsNullOrEmpty(tokenLabel) Then
If String.Compare(tokenLabel, tokenInfo.Label, StringComparison.InvariantCultureIgnoreCase) <> 0 Then
Continue For
End If
End If
Return slot
Next
Return Nothing
End Function
Protected Sub Init(ByVal libraryPath As String, ByVal tokenSerial As String, ByVal tokenLabel As String, ByVal pin As Byte(), ByVal ckaLabel As String, ByVal ckaId As Byte(), ByVal hashAlgorihtm As OID)
If String.IsNullOrEmpty(libraryPath) Then
Throw New ArgumentNullException($"Invalid library path ""{libraryPath}"".")
End If
Try
_pkcs11Library = Factories.Pkcs11LibraryFactory.LoadPkcs11Library(Factories, libraryPath, AppType.SingleThreaded)
_slot = FindSlot(tokenSerial, tokenLabel)
If _slot Is Nothing Then
Throw New Exception(String.Format("Token with serial ""{0}"" and label ""{1}"" was not found", tokenSerial, tokenLabel))
End If
_session = _slot.OpenSession(SessionType.ReadOnly)
_session.Login(CKU.CKU_USER, pin)
'' initialize _privateKeyHandle and _certificate
Using session As ISession = _slot.OpenSession(SessionType.ReadOnly)
'' private key
Dim searchTemplate As New List(Of IObjectAttribute)()
searchTemplate.Add(Factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY))
searchTemplate.Add(Factories.ObjectAttributeFactory.Create(CKA.CKA_KEY_TYPE, CKK.CKK_RSA))
If Not String.IsNullOrEmpty(ckaLabel) Then
searchTemplate.Add(Factories.ObjectAttributeFactory.Create(CKA.CKA_LABEL, ckaLabel))
End If
If ckaId IsNot Nothing Then
searchTemplate.Add(Factories.ObjectAttributeFactory.Create(CKA.CKA_ID, ckaId))
End If
Dim foundObjects As List(Of IObjectHandle) = session.FindAllObjects(searchTemplate)
If foundObjects.Count < 1 Then
Throw New Exception(String.Format("Private key with label ""{0}"" and id ""{1}"" was not found.", ckaLabel, If(ckaId Is Nothing, Nothing, ConvertUtils.BytesToHexString(ckaId))))
ElseIf foundObjects.Count > 1 Then
Throw New Exception(String.Format("More than one private key with label ""{0}"" and id ""{1}"" was found.", ckaLabel, If(ckaId Is Nothing, Nothing, ConvertUtils.BytesToHexString(ckaId))))
End If
_privateKeyHandle = foundObjects(0)
'' certificate
searchTemplate.Clear()
searchTemplate.Add(Factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_CERTIFICATE))
If Not String.IsNullOrEmpty(ckaLabel) Then
searchTemplate.Add(Factories.ObjectAttributeFactory.Create(CKA.CKA_LABEL, ckaLabel))
End If
If ckaId IsNot Nothing Then
searchTemplate.Add(Factories.ObjectAttributeFactory.Create(CKA.CKA_ID, ckaId))
End If
foundObjects = session.FindAllObjects(searchTemplate)
If foundObjects.Count = 1 Then
Dim attributes As New List(Of CKA)()
attributes.Add(CKA.CKA_VALUE)
Dim certificateAttributes As List(Of IObjectAttribute) = session.GetAttributeValue(foundObjects(0), attributes)
Dim certificateData As Byte() = certificateAttributes(0).GetValueAsByteArray()
_certificate = New X509Certificate2(certificateData)
End If
End Using
_ckaLabel = ckaLabel
_ckaId = ckaId
If hashAlgorihtm = OID.HashAlgorithms.SHA1 Then
_hashDigest = New Sha1Digest()
ElseIf hashAlgorihtm = OID.HashAlgorithms.SHA256 Then
_hashDigest = New Sha256Digest()
ElseIf hashAlgorihtm = OID.HashAlgorithms.SHA384 Then
_hashDigest = New Sha384Digest()
ElseIf hashAlgorihtm = OID.HashAlgorithms.SHA512 Then
_hashDigest = New Sha512Digest()
Else
Throw New Exception($"Unsupported HASH algorithm {hashAlgorihtm}.")
End If
_hashAlgorithm = hashAlgorihtm
Catch
If _session IsNot Nothing Then
_session.Dispose()
_session = Nothing
End If
If _pkcs11Library IsNot Nothing Then
_pkcs11Library.Dispose()
_pkcs11Library = Nothing
End If
Throw
End Try
End Sub
''' <summary>
''' Gets the X509Certificate2 object found on the token
''' with same ckaLabel and ckaId as a private key.
''' </summary>
Public ReadOnly Property Certificate As X509Certificate2
Get
Return _certificate
End Get
End Property
''' <summary>
''' Gets the ID of the hash algorithm.
''' </summary>
Public ReadOnly Property HashAlgorithm As OID Implements IPkcs7SignatureGenerator.HashAlgorithm
Get
Return _hashAlgorithm
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>
''' Signs data.
''' </summary>
Public Function SignData(ByVal input As Byte()) As Byte() Implements IPkcs7SignatureGenerator.SignData
Using session As ISession = _slot.OpenSession(SessionType.ReadOnly)
Using mechanism As IMechanism = Factories.MechanismFactory.Create(CKM.CKM_RSA_PKCS)
Dim hash = New Byte(_hashDigest.GetDigestSize() - 1) {}
_hashDigest.Reset()
_hashDigest.BlockUpdate(input, 0, input.Length)
_hashDigest.DoFinal(hash, 0)
Dim derObjectIdentifier = New DerObjectIdentifier(_hashAlgorithm.ID)
Dim algorithmIdentifier = New AlgorithmIdentifier(derObjectIdentifier, DerNull.Instance)
Dim digestInfo = New DigestInfo(algorithmIdentifier, hash)
Dim digestInfoBytes As Byte() = digestInfo.GetDerEncoded()
Return session.Sign(mechanism, _privateKeyHandle, digestInfoBytes)
End Using
End Using
End Function
End Class