如何验证数字XML签名

10

我希望核实该文件未被任何方式修改。

从一个Java Web服务中,我收到了以下包含数字XML签名的Soap响应:

<?xml version="1.0" encoding="UTF-8"?><Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">  
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" SOAP-ENV:mustUnderstand="1">
<wsse:BinarySecurityToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" wsu:Id="CertId-38670273">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</wsse:BinarySecurityToken><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#id-32516734">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>Rx5L4j8kF5RVYnC+spUCdvhh5N0=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#id-7716709">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>fFr2j5DoKTgpEvX1Se7gTC55bWM=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
jG1BaGgNbbk9JCc3R6JsJKY56p++f0+8RM2aL6TGOXS34NAGv48Sp3iAHEAuUt9+JV6w3VDAcFct
no3nCEISa0P4dVWTlPQJue3GVTWnnlcXao95tjukh9o8lIU7vZGgYHBUZLU+jgS6ZcaUlNW4KFUl
AdrPxR5DmJcFyGEtRY2yclqYhnJdnUc+ZBu5eWbRZgbJzR4MgtGsEQcgtftFe2i0CvRbOSe4mt3T
JQzbGY81ssFCnB44vitgjhVLfPd/08amSa5Xn8KRptbNatp2uq1iGXAifJLVup8T0yS0RzaqhCJg
CaHRPFVKFN3WaJcqPZex75KBwSZMZaaJDZW7lQ==
</ds:SignatureValue>
<ds:KeyInfo Id="KeyId-16708261">
<wsse:SecurityTokenReference xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="STRId-32258670"><wsse:Reference URI="#CertId-38670273" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/></wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
<wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="id-32516734"><wsu:Created>2017-03-31T09:04:40.352Z</wsu:Created><wsu:Expires>2017-03-31T09:09:40.352Z</wsu:Expires></wsu:Timestamp></wsse:Security><wsa:MessageID xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" SOAP-ENV:mustUnderstand="0">uuid:12b93a00-15f1-11e7-af6b-f16d80f418ae</wsa:MessageID><wsa:To xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" SOAP-ENV:mustUnderstand="0">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To></SOAP-ENV:Header>


<SOAP-ENV:Body xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="id-7716709">
...
 </SOAP-ENV:Body></Envelope>

我在MSDN找到了一个有用的例子,用于验证xml文件:

  ' Verify the signature of an XML file and return the result.
Function VerifyXmlFile(ByVal Name As String) As [Boolean]
    ' Create a new XML document.
    Dim xmlDocument As New XmlDocument()

    ' Format using white spaces.
    xmlDocument.PreserveWhitespace = True

    ' Load the passed XML file into the document. 
    xmlDocument.Load(Name)

    ' Create a new SignedXml object and pass it
    ' the XML document class.
    Dim signedXml As New SignedXml(xmlDocument)

    ' Find the "Signature" node and create a new
    ' XmlNodeList object.
     Dim nodeList As XmlNodeList = xmlDocument.GetElementsByTagName("Signature", "http://www.w3.org/2000/09/xmldsig#")

    ' Load the signature node.
    signedXml.LoadXml(CType(nodeList(0), XmlElement))

    ' Check the signature and return the result.
    Return signedXml.CheckSignature()

End Function

很不幸,验证总是返回false。我很难理解为什么它不起作用。


更新1 根据Henk Holterman Simon Mourier的建议:

 Function VerifyXmlFile(ByVal Name As String) As Boolean

    ' <SOAP-ENV:Envelope ... > ... </SOAP-ENV:Envelope>
    Dim xDoc = XDocument.Load(Name)

    ' <wsse:binarySecurityToken ... > ... </wsse:binarySecurityToken>
    Dim xBinarySecurityToken = xDoc.Root.Descendants().Skip(2).FirstOrDefault

    ' <SOAP-ENV:Body ... > ... </SOAP-ENV:Body>
    Dim xBody = xDoc.Root.Elements().Skip(1).FirstOrDefault
    Dim signedXml = New SignedXml(ToXmlElement(xBody))

    Dim xmlDocument As New XmlDocument()
    xmlDocument.PreserveWhitespace = True
    xmlDocument.Load(Name)

    Dim nodeList = xmlDocument.GetElementsByTagName("Signature", "http://www.w3.org/2000/09/xmldsig#")
    signedXml.LoadXml(CType(nodeList(0), XmlElement))

    Dim byteCert = Encoding.UTF8.GetBytes(xBinarySecurityToken.Value)
    Dim cert = New X509Certificates.X509Certificate2(byteCert)

    Return signedXml.CheckSignature(cert, True)

End Function

Public Shared Function ToXmlElement(xElement As XElement) As XmlElement
    Dim xmlDoc = New XmlDocument() With {.PreserveWhitespace = True}
    xmlDoc.Load(xElement.CreateReader())
    Return xmlDoc.DocumentElement
End Function

调用函数 CheckSignature() 导致了一个CryptographicException,错误信息为 "Malformed reference element"。


根据 @SimonMourier 和 @lax1089 的建议,更新 2 和解决方案。

Private Function VerifyXmlfile(Name As String) As Boolean
    CryptoConfig.AddAlgorithm(GetType(MyXmlDsigC14NTransform), "http://www.w3.org/TR/2001/REC-xml-c14n-20010315")

    Dim xmlDocument As New XmlDocument()
    xmlDocument.PreserveWhitespace = True
    xmlDocument.Load(Name)
    MyXmlDsigC14NTransform.document = xmlDocument

    Dim soapBody As XmlElement = xmlDocument.GetElementsByTagName("SOAP-ENV:Body")(0)
    Dim securityToken = xmlDocument.GetElementsByTagName("SOAP-ENV:Header")(0).FirstChild.NextSibling.FirstChild.NextSibling.InnerText

    Dim signedXml = New SignedXmlWithId(soapBody)

    Dim nodeList = xmlDocument.GetElementsByTagName("Signature", "http://www.w3.org/2000/09/xmldsig#")
    signedXml.LoadXml(CType(nodeList(0), XmlElement))

    Dim byteCert = Convert.FromBase64String(securityToken)
    Dim cert = New X509Certificates.X509Certificate2(byteCert)

    Return signedXml.CheckSignature(cert, True)

End Function

Dog Ears 偷来的辅助类。

Public Class SignedXmlWithId
Inherits SignedXml

Public Sub New(xml As XmlDocument)
    MyBase.New(xml)
End Sub

Public Sub New(xmlElement As XmlElement)
    MyBase.New(xmlElement)
End Sub

Public Overrides Function GetIdElement(doc As XmlDocument, id As String) As XmlElement
    ' check to see if it's a standard ID reference
    Dim idElem As XmlElement = MyBase.GetIdElement(doc, id)

    If idElem Is Nothing Then
        Dim nsManager As New XmlNamespaceManager(doc.NameTable)
        nsManager.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")

        idElem = TryCast(doc.SelectSingleNode((Convert.ToString("//*[@wsu:Id=""") & id) + """]", nsManager), XmlElement)
    End If

    Return idElem
End Function

End Class

感谢@SimonMourier@CarlosLopez-MSFT


1
Dim signedXml As New SignedXml(xmlDocument) 这部分选择整个文档作为签署的部分。我认为你需要选择 SOAP-ENV:Body。请注意id-7716709。但是您需要查看文档以确保。时间戳似乎也被签署了,我不知道那是否可选的。 - H H
1
这是因为默认情况下,SignedXml看不到任何公钥(它不能单独解码wsse:BinarySecurityToken(它是一个base64编码的X509证书)。但是,如果您使用已解码为字节数组的wsse:BinarySecurityToken内容创建X509Certificate,并将其传递给CheckSignature,则应该可以工作。但是,对我来说仍然没有用。可能是因为我没有所需的根证书(它来自瑞士机构)... - Simon Mourier
这里有另一个例子来自[链接]https://msdn.microsoft.com/en-us/library/ms229950(v=vs.110).aspx。在那里,您可以看到如何生成密钥并将其用于算法中,以查看缺少密钥作为函数`signedXml.CheckSignature(Key)`参数是否与您的问题有关。 - mago
1
就像我说的那样,字节必须由Convert.FromBase64String(xBinarySecurityToken.Value)创建,而不是UTF8编码。PS:使用@前缀通知我(或其他人)你已经回答了评论。 - Simon Mourier
1个回答

2

事实上,这是一个已知并记录在案的问题:

.NET 3.5和.NET 4.0之间的规范化实现已经发生了变化。

我不知道这是否适用于所有XML签名,但根据我所做的测试,它是有效的。

将以下C14N转换类添加到您的项目中:

public class MyXmlDsigC14NTransform: XmlDsigC14NTransform {
  static XmlDocument _document;
  public static XmlDocument document {
    set {
      _document = value;
    }
  }

  public MyXmlDsigC14NTransform() {}

  public override Object GetOutput() {
    return base.GetOutput();
  }

  public override void LoadInnerXml(XmlNodeList nodeList) {
    base.LoadInnerXml(nodeList);
  }

  protected override XmlNodeList GetInnerXml() {
    XmlNodeList nodeList = base.GetInnerXml();
    return nodeList;
  }

  public XmlElement GetXml() {
    return base.GetXml();
  }

  public override void LoadInput(Object obj) {
    int n;
    bool fDefaultNS = true;

    XmlElement element = ((XmlDocument) obj).DocumentElement;

    if (element.Name.Contains("SignedInfo")) {
      XmlNodeList DigestValue = element.GetElementsByTagName("DigestValue", element.NamespaceURI);
      string strHash = DigestValue[0].InnerText;
      XmlNodeList nodeList = _document.GetElementsByTagName(element.Name);

      for (n = 0; n < nodeList.Count; n++) {
        XmlNodeList DigestValue2 = ((XmlElement) nodeList[n]).GetElementsByTagName("DigestValue", ((XmlElement) nodeList[n]).NamespaceURI);
        string strHash2 = DigestValue2[0].InnerText;
        if (strHash == strHash2) break;
      }

      XmlNode node = nodeList[n];

      while (node.ParentNode != null) {
        XmlAttributeCollection attrColl = node.ParentNode.Attributes;
        if (attrColl != null) {
          for (n = 0; n < attrColl.Count; n++) {
            XmlAttribute attr = attrColl[n];
            if (attr.Prefix == "xmlns") {
              element.SetAttribute(attr.Name, attr.Value);
            } else if (attr.Name == "xmlns") {
              if (fDefaultNS) {
                element.SetAttribute(attr.Name, attr.Value);
                fDefaultNS = false;
              }
            }
          }
        }

        node = node.ParentNode;
      }
    }

    base.LoadInput(obj);
  }
}

可以按照以下示例使用CryptoConfig.AddAlgorithm方法注册类:

CryptoConfig.AddAlgorithm(typeof(MyXmlDsigC14NTransform), "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"); 

var message = new XmlDocument();
message.PreserveWhitespace = true;
message.Load("XmlSig.xml");

MyXmlDsigC14NTransform.document = message; // The transform class needs the xml document

// Validate signature as normal.  

这样做应该可以正确验证XML签名并解决您的问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接