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("&#xD;", "")
            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.

IRMark.exe
IRMark.zip

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
		

Why's this happen?

Unhandled Exception: System.Security.SecurityException:
Request for the permission of type
System.Security.Permissions.EnvironmentPermission, mscorlib,
Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 failed.
Page Updated 27/03/09