SignUsbToken.vb
C# VB
'''' This code is part of Document Solutions for PDF .NET demos.'' Copyright (c) MESCIUS inc. All rights reserved.''Imports SystemImports System.IOImports System.DrawingImports System.TextImports System.Collections.GenericImports System.Security.Cryptography.X509CertificatesImports Org.BouncyCastle.CryptoImports Org.BouncyCastle.Crypto.DigestsImports Org.BouncyCastle.Asn1Imports Org.BouncyCastle.Asn1.X509Imports Net.Pkcs11Interop.CommonImports Net.Pkcs11Interop.HighLevelAPIImports GrapeCity.Documents.PdfImports GrapeCity.Documents.Pdf.Security
'' 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 FunctionEnd 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 FunctionEnd Class