3

我正在向政府机构发送一条 XML 消息(使用该政府机构的规范,因此我无法控制生成的 XML 的外观),并且我正在使用 C# 进行开发(公司政策)。

两个在 C# 和互联网技术方面比我好得多的人在我之前审查了 XML,并告诉我 WCF 不支持在 XML 文档上生成签名所需的方法(这有点松了一口气,因为我还没有开发过任何 WCF 项目,而且很可怕,因为我知道 WCF 是一种成熟的 Web 技术)。

因此,我最终使用了 LINQ to XML 和 System.Xml 的组合来生成消息,并尝试对其进行签名。

下面是一个精简的 XML 示例:

<soapenv:Envelope 
  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:soap-sec="http://schemas.xmlsoap.org/security/2000-12" 
  xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy" 
  xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
  xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
  xmlns:cns="http://customNamespace1.com" 
  xmlns:cnt="http://customNamespace2.com" 
  >
  <soapenv:Header>
    <ns2:Element1 xmlns:ns2="http://namespace2.element1.com" wsu:Id="id-1">
      ...
    </ns2:Element1>
    <ns2:Element2 xmlns:ns2="http://namespace2.element2.com/" wsu:Id="id-2">
      ...
    </ns2:Element2>
    <wsse:Security SOAP-ENV:mustUnderstand="1" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
      <wsu:Timestamp wsu:Id="id-3">
        ...
      </wsu:Timestamp>
      <wsse:UsernameToken wsu:Id="id-4">
        ...
      </wsse:UsernameToken>
      <wsse:BinarySecurityToken ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-5bf699c7-5336-4695-b395-88d2b984fe54" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        ...
      </wsse:BinarySecurityToken>
      <ds:Signature Id="SIG-6" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
          <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
            <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec soapenv sp cns wsdl wsp wsse wsu xs xsi" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" />
          </ds:CanonicalizationMethod>
          <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
          <ds:Reference URI="#id-1">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec soapenv sp cns wsdl wsp wsse wsu xs xsi" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" />
              </ds:Transform>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
            <ds:DigestValue>...</ds:DigestValue>
          </ds:Reference>
          <ds:Reference URI="#id-2">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec soapenv sp cns wsdl wsp wsse wsu xs xsi" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" />
              </ds:Transform>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
            <ds:DigestValue>...</ds:DigestValue>
          </ds:Reference>
          <ds:Reference URI="#id-3">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec soapenv sp cns wsdl wsp wsse xs xsi" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" />
              </ds:Transform>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
            <ds:DigestValue>...</ds:DigestValue>
          </ds:Reference>
          <ds:Reference URI="#id-4">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec soapenv sp cns wsdl wsp wsu xs xsi" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" />
              </ds:Transform>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
            <ds:DigestValue>...</ds:DigestValue>
          </ds:Reference>
          <ds:Reference URI="#id-5">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec sp cns wsdl wsp wsse wsu xs xsi" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" />
              </ds:Transform>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
            <ds:DigestValue>...</ds:DigestValue>
          </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>
          ...
        </ds:SignatureValue>
        <ds:KeyInfo Id="KI-ABDCFEC7595B7819C213402151542862">
          <wsse:SecurityTokenReference wsu:Id="STR-ABDCFEC7595B7819C213402151542863">
            <wsse:Reference URI="#SecurityToken-5bf699c7-5336-4695-b395-88d2b984fe54" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" />
          </wsse:SecurityTokenReference>
        </ds:KeyInfo>
      </ds:Signature>
    </wsse:Security>
  </soapenv:Header>
  <soapenv:Body wsu:Id="id-5">
    <ns5:bodyelement xmlns:ns4="http://namespace4.com/" xmlns:ns3="http://namespace3.com/" xmlns:ns2="http://bodynamespace2.com/" xmlns:ns5="http://namespace5.com/">
      ...
    </ns5:bodyelement>
  </soapenv:Body>
</soapenv:Envelope>

这是我一直在尝试的一些代码(尝试使 uri 片段工作的 3 种不同方法)。我在这里只发布了一部分代码,因为它需要 200 多行代码来生成适当的 XML,我现在正在尝试对其进行签名:

RSACryptoServiceProvider rsacsp = (RSACryptoServiceProvider)Key;
SignedXml xmlWSig = new SignedXml(myDoc);
xmlWSig.SigningKey = Key;
xmlWSig.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
XmlDsigExcC14NTransform canMethod = (XmlDsigExcC14NTransform)xmlWSig.SignedInfo.CanonicalizationMethodObject;
canMethod.InclusiveNamespacesPrefixList = "SOAP-ENV cns soap-sec soapenv sp cnt wsdl wsp wsse wsu xs xsi";

Uri uri = new Uri("#id-1");
Reference ref1 = new Reference(uri.ToString());
XmlDsigExcC14NTransform transform1 = new XmlDsigExcC14NTransform("SOAP-ENV cns soap-sec soapenv sp cnt wsdl wsp wsse wsu xs xsi");
ref1.AddTransform(transform1);
ref1.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
xmlWSig.AddReference(ref1);

Reference ref2 = new Reference("#id-2"); 
XmlDsigExcC14NTransform transform2 = new XmlDsigExcC14NTransform("SOAP-ENV cns soap-sec soapenv sp cnt wsdl wsp wsse wsu xs xsi");
ref2.AddTransform(transform2);
ref2.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
xmlWSig.AddReference(ref2);

Reference ref3 = new Reference("");  
ref3.Uri = "#id-3";
XmlDsigExcC14NTransform transform3 = new XmlDsigExcC14NTransform("SOAP-ENV cns soap-sec soapenv sp cnt wsdl wsp wsse xs xsi");
ref3.AddTransform(transform3);
ref3.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
xmlWSig.AddReference(ref3);

//repeat things for id-4, and id-5

KeyInfo myKeyInfo = new KeyInfo();
myKeyInfo.AddClause(new RSAKeyValue((RSA)Key));
xmlWSig.KeyInfo = myKeyInfo;

xmlWSig.ComputeSignature();
XmlElement signedXmlElement = xmlWSig.GetXml();

密钥是从 X509 证书中获取的私钥(应该用作签署文档的密钥)。myDoc 是我生成的 System.Xml XmlDocument,我需要将签名插入其中。

方法 #1 给我一个 System.UriFormatException:无效的 URI:无法确定 URI 的格式。

方法 #2 给我一个 System.Security.Cryptography.CryptograpicException: Malformed reference element(如果我从 Uri 中删除 #,它会给我一个 System.UriFormatException: Invalid URI: The URI is empty)。

方法 #3 给我与方法 #2 相同的错误。

在所有关于使用 Uris 进行签名的文档中,只允许使用 Uri 片段(假设被引用的元素在同一个文档中),但 C# 中的 Uri 类似乎不接受片段作为可接受的 Uri。

Reference 类似乎也需要完整的 Uri,而不仅仅是 Fragment。

对于如何使用规范正确生成此 XML 中的签名,我愿意接受任何建议。

更新:虽然 SignedXml + Reference + Transform 似乎是最好的解决方案,但我现在开始相信 .NET 在这些库中存在重大差距,并且为了生成签名而下降到一些较低级别的库可能是必要的。

不幸的是,我仍在努力确定需要哪些库以及查找需要签名的算法。我对 Exclusive Canonicalization 的理解是,您只对 InclusiveNamespaces PrefixList 中列出的前缀指定的元素进行签名,但 Reference 中的 URI 指定了签名应该结束的子文档,但指定元素内的元素不'不使用大多数包含的命名空间。我是否理解这些参考文献的工作方式?

4

2 回答 2

1

看起来我可能必须为自己复制执行 URI + 包容性命名空间的算法(仍然需要弄清楚它是如何工作的),并将这些元素转换为字节数组。然后使用较低级别的库进行签名。

像这样的东西:

RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey;
signedMessage = rsa.SignData(originalMessage, CryptoConfig.MapNameToOID("SHA1"));

然后,signedMessage 可以转换为 Base64 字符串,并插入到每个适当的摘要值中。

比我希望的要多得多,但如果 .NET 不支持 URI 片段,我想我必须做我必须做的事情。

由于此解决方案的复杂性,如果有人有更流畅的解决方案,我绝对愿意接受替代方案。

编辑:在对规范化文档进行了几个小时的倾注之后,看起来独家规范化更多的是关于在将其撕掉以对其进行签名时将哪些名称空间插入到 XML 中。如果命名空间没有被直接使用,并且不包含在包含的命名空间前缀列表中,则在签署之前不要将该命名空间添加到要签署的元素中。这对我来说仍然很奇怪,因为您不一定希望在 XML 的更完整上下文中的元素中使用该名称空间,并且对其进行签名意味着它不能更改,但您不包括它。

更新:经过几个小时的测试,我终于让它工作了。但它有很多,很多非常可怕的代码。本质上,我必须将我们要退出的每个元素提取到副本中,并手动更新该元素中包含的命名空间,然后从中生成哈希(包括此时执行更完整的转换)。但它确实奏效了。

于 2013-11-07T14:55:14.503 回答
1

事实证明,.NET 不接受它的原因是 wsu 命名空间前缀。将 wsu:Id= 翻转为 id= 至少能够生成签名。然后,我遇到了这个: 'Malformed Reference Element' when added a reference based on an Id attribute with an SignedXml class

与我之前的开发答案相比,这使用的代码要少得多,并且更容易阅读/维护。

于 2013-12-18T21:28:41.120 回答