How To Use XmlDsigC14NTransform Class in C#

artsince picture artsince · Jul 29, 2010 · Viewed 7.8k times · Source

I am trying to canonicalize an xml node by using System.Security.Cryptography.Xml.XMLDsigC14nTransform class of c# .net Framework 2.0.

The instance expects three different input types, NodeList, Stream and XMLDocument. I try the transform with all of these input types but I get different results. What I really want to do is to canonicalize a single node, but as you can see in the output file, the output does not contain any of the inner xml.

Any suggestions about the proper way to canonicalize an XML Node are very appreciated. Best,

string path = @"D:\Test\xml imza\sign.xml";
XmlDocument xDoc = new XmlDocument();
xDoc.PreserveWhitespace = true;
using (FileStream fs = new FileStream(path, FileMode.Open))
{
    xDoc.Load(fs);
}

// canon node list
XmlNodeList nodeList = xDoc.SelectNodes("//Child1");

XmlDsigC14NTransform transform = new XmlDsigC14NTransform();
transform.LoadInput(nodeList);
MemoryStream ms = (MemoryStream)transform.GetOutput(typeof(Stream));

File.WriteAllBytes(@"D:\Test\xml imza\child1.xml", ms.ToArray());

// canon XMLDocument
transform = new XmlDsigC14NTransform();
transform.LoadInput(xDoc);
ms = (MemoryStream)transform.GetOutput(typeof(Stream));

File.WriteAllBytes(@"D:\Test\xml imza\doc.xml", ms.ToArray());

// Document to Stream
ms = new MemoryStream();
XmlWriter xw = XmlWriter.Create(ms);
xDoc.WriteTo(xw);
xw.Flush();
ms.Position = 0;

transform = new XmlDsigC14NTransform();
transform.LoadInput(ms);
ms = (MemoryStream)transform.GetOutput(typeof(Stream));

File.WriteAllBytes(@"D:\Test\xml imza\ms.xml", ms.ToArray());

// node to stream
ms = new MemoryStream();
xw = XmlWriter.Create(ms);
nodeList[0].WriteTo(xw);
xw.Flush();
ms.Position = 0;

transform = new XmlDsigC14NTransform();
transform.LoadInput(ms);
ms = (MemoryStream)transform.GetOutput(typeof(Stream));

File.WriteAllBytes(@"D:\Test\xml imza\ms2.xml", ms.ToArray());

sign.xml

<?xml version="1.0" encoding="utf-8" ?>
<Root Attr="root" xmlns:test="http://www.test.com/xades#">
  <Child1 Cttribute="c3" Attribute1="c1" Bttribute="c2">
    <child11 Attribute11="c11">Element11</child11>
  </Child1>
  <Child2 Attribute2="c2">
    <child21 Attribute21="c21">Element21</child21>
    <child22 Attribute22="c22">Element22</child22>
  </Child2>
  <Child3 Attribute3="c3">
    <child31 Attribute32="c31">
      <child311 Attribute311="c311">Element311</child311>
    </child31>
  </Child3>  
</Root>

Child1.xml

<Child1 xmlns:test="http://www.test.com/xades#"></Child1>

doc.xml

<Root xmlns:test="http://www.test.com/xades#" Attr="root">&#xD;
  <Child1 Attribute1="c1" Bttribute="c2" Cttribute="c3">&#xD;
    <child11 Attribute11="c11">Element11</child11>&#xD;
  </Child1>&#xD;
  <Child2 Attribute2="c2">&#xD;
    <child21 Attribute21="c21">Element21</child21>&#xD;
    <child22 Attribute22="c22">Element22</child22>&#xD;
  </Child2>&#xD;
  <Child3 Attribute3="c3">&#xD;
    <child31 Attribute32="c31">&#xD;
      <child311 Attribute311="c311">Element311</child311>&#xD;
    </child31>&#xD;
  </Child3>  &#xD;
</Root>

ms.xml

<Root xmlns:test="http://www.test.com/xades#" Attr="root">
  <Child1 Attribute1="c1" Bttribute="c2" Cttribute="c3">
    <child11 Attribute11="c11">Element11</child11>
  </Child1>
  <Child2 Attribute2="c2">
    <child21 Attribute21="c21">Element21</child21>
    <child22 Attribute22="c22">Element22</child22>
  </Child2>
  <Child3 Attribute3="c3">
    <child31 Attribute32="c31">
      <child311 Attribute311="c311">Element311</child311>
    </child31>
  </Child3>  
</Root>

ms2.xml

<Child1 Attribute1="c1" Bttribute="c2" Cttribute="c3">
    <child11 Attribute11="c11">Element11</child11>
  </Child1>

Answer

Jon Hanna picture Jon Hanna · Aug 6, 2010

I think, your answer is in your question, "What I really want to do is to canonicalize a single node, but as you can see in the output file, the output does not contain any of the inner xml."

If I understand you, then really you don't want to canonicalise a single node, or you'd be happy that it doesn't contain the inner XML. You want to canonicalise a single subtree.

XPath returns nodes, not subtrees. Some operations on the nodes returned by an XPath expression will then include their child and attribute nodes by default, but canonicalisation deliberately isn't one of these, as potentially some of those very child nodes could be mutable in ways that you are not signing. In signing, you are only signing precisely those nodes that you say you are signing.

Changing the line in your code from:

XmlNodeList nodeList = xDoc.SelectNodes("//Child1");

to:

XmlNodeList nodeList =
    xDoc.SelectNodes("//Child1/descendant-or-self::node()|//Child1//@*");

Means I get the following in child1.xml:

<Child1 xmlns:test="http://www.test.com/xades#" Attribute1="c1" Bttribute="c2" Cttribute="c3">&#xD;
    <child11 Attribute11="c11">Element11</child11>&#xD;
  </Child1>

Am I correct in thinking that this is what you want?

Incidentally, more precision along the lines of:

XmlNodeList nodeList =
    xDoc.SelectNodes("//Child1[1]/descendant-or-self::node()|//Child1[1]//@*");

May be useful, as then the xpath evaluation can stop when it gets to the first </Child1>, with a performance gain that could be significant if your real data is large.