vrijdag 14 november 2008

VB.NET: Sign PDF in Visual Basic .NET

There are lots of expensive components out there to sign PDF files from your program, but what if you could do it for free? Below you will find my VB.NET code to sign a PDF file with your own PKCS 12 certificate. If you added the classes below to your project, signing a PDF file will become this simple:


Dim signer As New PdfSigner("[your certificate.pfx]", "[your certificate password]", meta)
signer.SignPdf("[your pdf file]", sPdfFile)


It couldn't be much easier right? Please also check the PdfSigner parameters for additional options. The following classes make use of the completely free iTextSharp library, that is available here. And now finally the classes:




Imports iTextSharp.text
Imports iTextSharp.text.pdf
Imports System.IO

'
' This class signs pdf files
'

Public Class PdfSigner
Public Enum enumCertification
NOT_CERTIFIED = 0
CERTIFIED_NO_CHANGES_ALLOWED = 1
CERTIFIED_FORM_FILLING = 2
CERTIFIED_FORM_FILLING_AND_ANNOTATIONS = 3
End Enum

Public Structure structSigMetaData
Dim sReason As String
Dim sContact As String
Dim sLocation As String
End Structure


Protected pdfMetaData As PdfMetaData
Protected certificate As Certificate

Public sLastError As String = ""
Public bMultipleSignatures As Boolean = False
Public bSignatureStamp As Boolean = False
Public Certification As enumCertification = enumCertification.CERTIFIED_NO_CHANGES_ALLOWED
Public SignatureMetaData As structSigMetaData
Public sSigImageFile As String = ""

#Region "Contructors"
Public Sub New(ByVal certificate As Certificate, Optional ByVal sPassword As String = "", Optional ByVal PdfMetaData As PdfMetaData = Nothing)
If IsNothing(PdfMetaData) Then PdfMetaData = New PdfMetaData

Me.pdfMetaData = PdfMetaData
Me.certificate = certificate

Me.SignatureMetaData.sReason = ""
Me.SignatureMetaData.sContact = ""
Me.SignatureMetaData.sLocation = ""
End Sub

Public Sub New(ByVal sCertificateFile As String, Optional ByVal sPassword As String = "", Optional ByVal PdfMetaData As PdfMetaData = Nothing)
If IsNothing(PdfMetaData) Then PdfMetaData = New PdfMetaData

Me.pdfMetaData = PdfMetaData
Me.certificate = New Certificate
Me.certificate.LoadCertificate(sCertificateFile, sPassword)

Me.SignatureMetaData.sReason = ""
Me.SignatureMetaData.sContact = ""
Me.SignatureMetaData.sLocation = ""
End Sub
#End Region

#Region "Functions"
'
' Sign the sInFile and write the signed file to sOutFile
'

Public Function SignPdf(ByVal sInFile As String, ByVal sOutFile As String) As Boolean
Dim st As PdfStamper
Dim fs As FileStream
Dim iWidth As Integer = 150
Dim iHeight As Integer = 150

Try
'Check if the input file exist
If Not File.Exists(sInFile) Then
Me.sLastError = "The input file " & sInFile & " does not exist"
Return False
End If

Dim reader As PdfReader = New PdfReader(sInFile)
fs = New FileStream(sOutFile, FileMode.Create, FileAccess.Write)
If Me.bMultipleSignatures Then
st = PdfStamper.CreateSignature(reader, fs, Chr(0), Nothing, True)
Else
st = PdfStamper.CreateSignature(reader, fs, Chr(0))
End If

st.MoreInfo = Me.pdfMetaData.MetaData
st.XmpMetadata = Me.pdfMetaData.GetMetaDataXmp


Dim sap As PdfSignatureAppearance = st.SignatureAppearance
sap.CertificationLevel = Me.Certification
sap.SetCrypto(Me.certificate.akp, Me.certificate.chain, Nothing, PdfSignatureAppearance.WINCER_SIGNED)

sap.Reason = Me.SignatureMetaData.sReason
sap.Contact = Me.SignatureMetaData.sContact
sap.Location = Me.SignatureMetaData.sLocation

If Me.sSigImageFile <> "" Then
If Not File.Exists(Me.sSigImageFile) Then
Me.sLastError = "The image file " & Me.sSigImageFile & " does not exist"
Return False
End If

Dim img As System.Drawing.Image = System.Drawing.Image.FromFile(Me.sSigImageFile)
sap.Image = Image.GetInstance(img, img.RawFormat)
iWidth = img.Width
iHeight = img.Height
End If

If Me.bSignatureStamp Then
sap.SetVisibleSignature(New iTextSharp.text.Rectangle(100, 100, 100 + iWidth, 100 + iHeight), 1, Nothing)
End If

Return True
Catch ex As Exception
Me.sLastError = ex.Message
Return False
Finally
Try
If Not IsNothing(st) Then st.Close()
Catch
End Try
End Try
End Function
#End Region

End Class




Imports System.IO

Imports Org.BouncyCastle.X509
Imports Org.BouncyCastle.Crypto
Imports Org.BouncyCastle.Pkcs

'
' This class hold the certificate and extract private key needed for e-signature
'

Public Class Certificate
Protected sPath As String = ""
Protected sPassword As String = ""

Public akp As AsymmetricKeyParameter
Public chain As X509Certificate()
Public sLastError As String = ""


'
' This method will load the given pkcs certificate in sFile
'

Public Function LoadCertificate(ByVal sPath As String, ByVal sPassword As String) As Boolean
Dim sAlias As String = Nothing
Dim pk12 As Pkcs12Store

Try
Me.sPath = sPath
Me.sPassword = sPassword

'Check if file exists
If Not File.Exists(sPath) Then
Me.sLastError = "The file " & sPath & " does not exist"
Return False
End If

'Read the certificate file
pk12 = New Pkcs12Store(New FileStream(sPath, FileMode.Open, FileAccess.Read), sPassword.ToCharArray())

'Iterate throught the certificate entries to find the private key entry
Dim i As IEnumerator = pk12.Aliases.GetEnumerator()
While (i.MoveNext())
sAlias = i.Current.ToString
If pk12.IsKeyEntry(sAlias) Then Exit While
End While

Me.akp = pk12.GetKey(sAlias).Key
Dim ce As X509CertificateEntry() = pk12.GetCertificateChain(sAlias)
ReDim Me.chain(0 To ce.Length - 1)
For k As Integer = 0 To ce.Length - 1
chain(k) = ce(k).Certificate
Next

Return True
Catch ex As Exception
Me.sLastError = ex.Message
Return False
End Try
End Function

End Class




Imports System.IO

Imports iTextSharp.text.xml.xmp

'
' This class wraps the PDF metadata
'

Public Class PdfMetaData
Protected info As Hashtable = New Hashtable()

#Region "Properties"
Public Property Author() As String
Get
Return info("Author")
End Get
Set(ByVal value As String)
If info.ContainsKey("Author") Then
info("Author") = value
Else
info.Add("Author", value)
End If
End Set
End Property

Public Property Title() As String
Get
Return info("Title")
End Get
Set(ByVal value As String)
If info.ContainsKey("Title") Then
info("Title") = value
Else
info.Add("Title", value)
End If
End Set
End Property

Public Property Subject() As String
Get
Return info("Subject")
End Get
Set(ByVal value As String)
If info.ContainsKey("Subject") Then
info("Subject") = value
Else
info.Add("Subject", value)
End If
End Set
End Property

Public Property Keywords() As String
Get
Return info("Keywords")
End Get
Set(ByVal value As String)
If info.ContainsKey("Keywords") Then
info("Keywords") = value
Else
info.Add("Keywords", value)
End If
End Set
End Property

Public Property Producer() As String
Get
Return info("Producer")
End Get
Set(ByVal value As String)
If info.ContainsKey("Producer") Then
info("Producer") = value
Else
info.Add("Producer", value)
End If
End Set
End Property

Public Property Creator() As String
Get
Return info("Creator")
End Get
Set(ByVal value As String)
If info.ContainsKey("Creator") Then
info("Creator") = value
Else
info.Add("Creator", value)
End If
End Set
End Property

Public ReadOnly Property MetaData() As Hashtable
Get
Return Me.info
End Get
End Property

#End Region

#Region "Initialization"
Public Sub New()
Me.Author = ""
Me.Title = ""
Me.Subject = ""
Me.Keywords = ""
Me.Producer = ""
Me.Creator = ""
End Sub
#End Region

#Region "Functions"
'
' Retruns the PDF metadata as an array of bytes
'

Public Function GetMetaDataXmp() As Byte()
Dim os As MemoryStream = New System.IO.MemoryStream()
Dim xmp As XmpWriter = New XmpWriter(os, Me.info)
xmp.Close()
Return os.ToArray()
End Function
#End Region

End Class