如何在C#中使用XmlDsigC14NTransform类

时间:2010-07-29 12:33:27

标签: c# xml xml-signature canonicalization

我正在尝试使用System.Security.Cryptography.Xml.XMLDsigC14nTransform类c#.net Framework 2.0来规范化xml节点。

该实例需要三种不同的输入类型:NodeList,Stream和XMLDocument。我尝试使用所有这些输入类型的转换,但我得到不同的结果。 我真正想要做的是规范化单个节点,但正如您在输出文件中看到的那样,输出不包含任何内部xml。

非常感谢有关规范化XML节点的正确方法的任何建议。 最好,

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>

4 个答案:

答案 0 :(得分:3)

我认为,你的答案在你的问题中,“我真正想做的是规范化单个节点,但正如你在输出文件中看到的那样,输出不包含任何内部xml。” / p>

如果我理解你,那么你真的想要规范单个节点,或者你很高兴它不包含内部XML。您希望规范化单个子树

XPath返回节点,而不是子树。默认情况下,XPath表达式返回的节点上的某些操作将包含其子节点和属性节点,但故意规范化不是其中之一,因为这些子节点中的某些节点可能会以您未签名的方式变为可变。在签名时,您只需准确签署您说要签名的节点。

更改代码中的行:

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

为:

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

表示我在child1.xml中获得以下内容:

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

我认为这是你想要的吗?

顺便说一下,更精确的是:

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

可能很有用,因为xpath评估可以在到达第一个</Child1>时停止,如果您的实际数据很大,性能增益可能很大。

答案 1 :(得分:1)

我可能在MSDN找到解决方案如果我正确地解决了问题。

这是否解决了这个问题?:

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

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

var transform = new XmlDsigC14NTransform(true)
                    {
                        Algorithm = SignedXml.XmlDsigExcC14NTransformUrl
                    };

var validInTypes = transform.InputTypes;
var inputType = nodeList.GetType();
if (!validInTypes.Any(t => t.IsAssignableFrom(inputType)))
{
    throw new ArgumentException("Invalid Input");
}

transform.LoadInput(xDoc);
var innerTransform = new XmlDsigC14NTransform();

innerTransform.LoadInnerXml(xDoc.SelectNodes("//."));
var ms = (MemoryStream) transform.GetOutput(typeof (Stream));
ms.Flush();
File.WriteAllBytes(@"child1.xml", ms.ToArray());

在child1.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>

希望它有所帮助。 托拜厄斯

答案 2 :(得分:0)

你检查过MSDN:http://msdn.microsoft.com/en-us/library/fzh48tx1.aspx他们页面上的示例有一条评论,上面写着“此转换不包含内部XML元素” - 这意味着一个已知问题。

您可以尝试不同的XPath,例如// child1 / *或// child1 | // child1 / *或// child1 //或显式节点()选择(检查http://msdn.microsoft.com/en-us/library/ms256471.aspx处的完整XPath语法)但是你处于灰色地带 - 带着虫子赌博。

因此,在你的ms2.xml中你想要的实际输出你只需暂时进行中间序列化。

同时启动Reflector并查看 - 该课程可能并不十分复杂。

答案 3 :(得分:0)

关于如何处理XmlDocument不区分源(不应保留)中的U + 000D与源中的&#xd;等显式引用(应该保留)的事实的单独答案。

而不是:

using (FileStream fs = new FileStream(path, FileMode.Open))
{
    xDoc.Load(fs);
}

我们首先创建一个新行清理TextReader:

private class LineCleaningTextReader : TextReader
{
  private readonly TextReader _src;
  public LineCleaningTextReader(TextReader src)
  {
    _src = src;
  }
  public override int Read()
  {
    int r = _src.Read();
    switch(r)
    {
      case 0xD:// \r
        switch(_src.Peek())
        {
          case 0xA: case 0x85: // \n or NEL char
            _src.Read();
            break;
        }
        return 0xA;
      case 0x85://NEL
        return 0xA;
      default:
        return r;
    }
  }
}

然后我们在加载xDoc中使用它:

using (FileStream fs = new FileStream(path, FileMode.Open))
{
  using(TextReader tr = new StreamReader(fs))
    xDoc.Load(new LineCleaningTextReader(tr));
}

然后在处理之前将新行标准化,但保留显式  单独