IRMark With C14N and SHA-1 In VB.Net
I've just managed to get a program finished to generate a hash key for use when submitting to the government gateway. Seeing as you've found this page, this might be of some use to you. Only use this is an example to get your code working, I'm not saying this is perfect, just that it works for me! I was originally trying to get it working in VB6, but wasted so much time trying to get it to work, I decided to write it in vb.net, compile as a command line application, and call it from the VB6 program.
The spec said to work with just the <Body> section, strip out any existing IRMark tags, normalise the result with C14N Canonicalisation, then use SHA-1 hashing to work out an SHA-1 hash code of the end result. Phew.
I sure wish I could have found an example like this yesterday!
This is a VB.Net console application and works with VB.Net 1.1.
It should be called like:
IRMark.exe xmlinputfile.xml out.txt http://www.govtalk.gov.uk/taxation/PAYE/MOV/08-09/1 http://www.govtalk.gov.uk/CM/envelope
The resulting hash code will be output to the console and also the output text file.
Here's the code. Good luck!
Imports System.Xml
Imports System.Security.Cryptography
Imports System.Security.Cryptography.xml
Imports System.IO
Module MMain
Sub Main()
Dim s() As String = System.Environment.GetCommandLineArgs()
Dim sInput, sOutput, sTaxNamespace, sEnvelopeNamespace As String
Try
' Validate/parse the command line
If s.Length <> 5 Then
Throw New Exception("INCORRECT PARAMETERS SPECIFIED" & vbCrLf & _
" Specify IRMark.exe <InputFile> <OutputFile> " & _
"<TaxNamespace> <EnvelopeNamespace>")
End If
sInput = s(1)
sOutput = s(2)
sTaxNamespace = s(3)
sEnvelopeNamespace = s(4)
' Do the work
Call process(sInput, sOutput, sTaxNamespace, sEnvelopeNamespace)
System.Environment.Exit(0)
Catch ex As Exception
Console.WriteLine(ex.Message)
System.Environment.Exit(1)
End Try
End Sub
Private Sub process(ByVal sInput As String, ByVal sOutput As String, _
ByVal sTaxNamespace As String, ByVal sEnvelopeNamespace As String)
Dim xmlDoc, xmlBody As XmlDocument
Dim nodeBody, nodeIr As XmlNode
Dim c14n As XmlDsigC14NTransform
Dim s As Stream
Dim sha1 As SHA1
Dim hash As Byte()
Dim sHash As String
Dim swOut As StreamWriter
Try
' Load XML file
xmlDoc = New XmlDocument
xmlDoc.PreserveWhitespace = True
xmlDoc.Load(sInput)
' Create namespace from parameters
Dim ns As New XmlNamespaceManager(xmlDoc.NameTable)
ns.AddNamespace("tax", sTaxNamespace)
ns.AddNamespace("env", sEnvelopeNamespace)
' Get just the body portion of the XML document
nodeBody = xmlDoc.SelectSingleNode("//env:Body", ns)
' Create an XML document of just the body section
xmlBody = New XmlDocument
xmlBody.PreserveWhitespace = True
xmlBody.LoadXml(nodeBody.OuterXml)
' Remove any existing IRMark
nodeIr = xmlBody.SelectSingleNode("//tax:IRmark", ns)
If Not nodeIr Is Nothing Then
nodeIr.ParentNode.RemoveChild(nodeIr)
End If
' Normalise the document using C14N (Canonicalisation)
c14n = New XmlDsigC14NTransform
c14n.LoadInput(xmlBody)
s = CType(c14n.GetOutput(GetType(System.IO.Stream)), Stream)
' Read the normalised document back into a string
Dim b(CInt(s.Length)) As Byte
s.Read(b, 0, CInt(s.Length))
Dim enc As New System.Text.ASCIIEncoding
Dim sNormalised As String = enc.GetString(b)
' Modify the string to conform to to normalised spec
sNormalised = sNormalised.Replace("
", "")
sNormalised = sNormalised.Replace(vbCrLf, vbLf)
sNormalised = sNormalised.Replace(vbCr, vbLf)
sNormalised = sNormalised.Substring(0, sNormalised.Length - 1)
' Convert the final document back into a byte array
b = System.Text.Encoding.UTF8.GetBytes(sNormalised)
' Create the SHA-1 hash from the final document
SHA1 = SHA1.Create
hash = sha1.ComputeHash(b)
sHash = Convert.ToBase64String(hash)
' Display the hash on the console
Console.WriteLine("Calculated IRmark: " + sHash)
' Write the hash out to the required file
swOut = New StreamWriter(sOutput)
swOut.Write(sHash)
swOut.Flush()
swOut.Close()
Catch ex As Exception
Throw
Finally
swOut = Nothing
hash = Nothing
sha1 = Nothing
s = Nothing
c14n = Nothing
nodeIr = Nothing
xmlBody = Nothing
nodeBody = Nothing
xmlDoc = Nothing
End Try
End Sub
End Module
Here is a pre-compiled version of the above, but let me say again, this may have bugs in! Don't blame me if it doesn't work! It requires the .net framework version 1.1, which should be already installed on XP or above. If it isn't, you can find it as an option in Windows Update.
Comment Left 04/11/2011:
Minor bug found means you can't enter the £ symbol. Dim enc As New System.Text.ASCIIEncoding should be Dim enc As New System.Text.UTF8Encoding
