2013-05-13 129 views
2

对于相当冗长的代码块,我提前表示歉意,但它是我可以生成的最小的可编译示例。我已经省略了原始代码中的所有错误检查。我使用的是Visual Studio 2012和.NET 4.5,虽然这对于4.5来说并不是什么新东西,但它应该适用于任何版本。XmlElement的Xml签名无法验证

我试图签署一份XML文档的元素来保护它们免遭篡改。我不想保护整个文件,但只保留某些元素。也许甚至有不同的键的不同元素。

但是,当我签署三个示例元素并尝试验证它们时,第一个元素总是验证,另外两个元素失败。更糟糕的是,如果我在签名后修改它,第一个甚至会成功。我搜索了很多,阅读了很多教程,甚至asked a theoretical question here,但我没有任何线索我做错了什么。任何人都可以发现我的错误吗?

注意:我很乐意提供与周五的问题相同的赏金给任何解决此问题的人。

的证书是由执行创建:

“C:\ Program Files文件(x86)的\微软的SDK \的Windows \ v7.1A \ BIN \ makecert” -r -pe -n“CN = XMLDSIG_Test “-b 2013年1月1日-e 2014年1月1日签署-sky我-sS

测试XML文件:

<?xml version="1.0" encoding="utf-8" ?> 
<PackageRoot> 
    <Package> 
    <Changes > 
    <Change/> 
    </Changes> 
    </Package> 
    <Package> 
    <Changes> 
    <Change/> 
    <Change/> 
    </Changes> 
    </Package> 
    <Package> 
    <Changes> 
    <Change/> 
    <Change/> 
    <Change/> 
    </Changes> 
    </Package> 
</PackageRoot> 

的代码签名和验证:

namespace SOExample 
{ 
    using System; 
    using System.Security.Cryptography.X509Certificates; 
    using System.Security.Cryptography.Xml; 
    using System.Xml; 

    public static class Program 
    { 
    public static void Sign(this XmlElement element, X509Certificate2 certificate) 
    { 
     var identifier = Guid.NewGuid().ToString(); 

     element.SetAttribute("Id", identifier); 

     var signedXml = new SignedXml(element) { SigningKey = certificate.PrivateKey }; 

     var reference = new Reference("#" + identifier); 

     reference.AddTransform(new XmlDsigEnvelopedSignatureTransform()); 

     signedXml.AddReference(reference); 

     signedXml.ComputeSignature(); 

     var xmlDigitalSignature = signedXml.GetXml(); 

     element.AppendChild(element.OwnerDocument.ImportNode(xmlDigitalSignature, true)); 
    } 

    public static bool VerifySignature(this XmlElement element, X509Certificate2 certificate) 
    { 
     var signedXml = new SignedXml(element); 

     XmlNodeList nodeList = element.GetElementsByTagName("Signature"); 

     if (nodeList.Count != 1) return false; 

     signedXml.LoadXml((XmlElement)nodeList[0]); 

     return signedXml.CheckSignature(certificate, true); 
    } 

    public static void Main() 
    { 
     var xmlDoc = new XmlDocument { PreserveWhitespace = true }; 

     xmlDoc.Load("ExamplePackage.xml"); 

     var certificate = GetCertificateBySubject("CN=XMLDSIG_Test"); 

     foreach (XmlElement root in xmlDoc.GetElementsByTagName("PackageRoot")) 
     { 
      foreach (XmlElement package in root.GetElementsByTagName("Package")) 
      { 
      package.Sign(certificate); 
      } 
     } 

     xmlDoc.Save("test_signed.xml"); 

     Console.WriteLine("XML file signed."); 
     Console.WriteLine("Press any key to verify"); 
     Console.ReadLine(); 

     var signedDoc = new XmlDocument(); 

     signedDoc.Load("test_signed.xml"); 

     foreach (XmlElement root in xmlDoc.GetElementsByTagName("PackageRoot")) 
     { 
      foreach (XmlElement package in root.GetElementsByTagName("Package")) 
      { 
      Console.Write("Verifying Package " + package.GetAttribute("Id")); 
      var success = package.VerifySignature(certificate); 
      Console.WriteLine(success ? " successful!" : " failed!"); 
      } 
     } 

     Console.WriteLine("Done."); 
     Console.ReadLine(); 
    } 

    private static X509Certificate2 GetCertificateBySubject(string certificateSubject) 
    { 
     var store = new X509Store("My", StoreLocation.CurrentUser); 

     store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); 

     foreach (X509Certificate2 c in store.Certificates) 
     { 
     if (c.Subject == certificateSubject) 
     { 
      store.Close(); 
      return c; 
     } 
     } 

     store.Close(); 
     return null; 
    } 
    } 
} 
+2

如果你下了一些东西,请留下评论,这样海报可以改进。 – nvoigt 2013-05-23 18:44:04

回答

2

您的测试代码中存在一个错误。第二个foreach再次通过xmlDoc循环,而不是signedDoc。解决这个问题将改变所有节点失败的结果。
他们为什么失败我还不知道。


我找不到为什么他们会失败,但我找到了一种方法使它工作。 不同的是:所有的签名都是根元素的直接孩子的:

public static void Main() 
{ 

    // ... 

    var signedDoc = new XmlDocument { PreserveWhitespace = true }; 

    signedDoc.Load("test_signed.xml"); 

    foreach (XmlElement root in signedDoc.GetElementsByTagName("PackageRoot")) 
    { 
     foreach (XmlElement signature in root.GetElementsByTagName("Signature")) 
     { 
      var success = signature.VerifySignature(certificate); 
      Console.WriteLine(success ? " successful!" : " failed!"); 
     } 
    } 

    Console.WriteLine("Done."); 
    Console.ReadLine(); 
} 

public static void Sign(this XmlElement element, X509Certificate2 certificate) 
{ 
    var identifier = Guid.NewGuid().ToString("N"); 
    element.SetAttribute("Id", identifier); 

    var signedXml = new SignedXml(element) { SigningKey = certificate.PrivateKey }; 
    signedXml.AddReference(new Reference("#" + identifier)); 
    signedXml.ComputeSignature(); 

    var xmlDigitalSignature = signedXml.GetXml(); 

    element.OwnerDocument.DocumentElement.AppendChild(
     element.OwnerDocument.ImportNode(xmlDigitalSignature, true)); 
} 

public static bool VerifySignature(this XmlElement element, X509Certificate2 certificate) 
{ 
    var signedXml = new SignedXml(element.OwnerDocument); 
    signedXml.LoadXml(element); 

    return signedXml.CheckSignature(certificate, true); 
} 

一个重要的细节需要注意:PreserveWhitespace需要设置到truesignedDoc,太。

+1

Ouch,这是一个愚蠢的错误,感谢捕捉测试代码错误。我将实施这些更改并查看是否可以实现这一目标。 – nvoigt 2013-05-13 10:55:09

+0

它的工作,非常感谢!如果你发布一个单线程的答案链接回到这里的赏金(上面的链接)我也会接受。 – nvoigt 2013-05-13 11:09:05