0

我需要使用 .NET Core 和 使用证书对 XML 文档进行签名SignedXml,但我不知道如何在签名标签中配置对方期望的一些元素。我检查了很多文章,其中一篇由 Rick Stahl 撰写,非常相似,但我仍然通过错误验证。不幸的是,我无法发送任何好的日志消息,我只有一个一般性的答案,即签名无效。

签名的预期内容如下所示。看起来所有细节——标签、前缀、标识符、模式——都是必需的。

        <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
            <ds:Signature Id="{{someValue}}" 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="soapenv urn urn1 urn2 urn3 urn4" 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="#{{someValue}}">
                        <ds:Transforms>
                            <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                                <ec:InclusiveNamespaces PrefixList="urn urn1 urn2 urn3 urn4" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                            </ds:Transform>
                        </ds:Transforms>
                        <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                        <ds:DigestValue>VALUEOFDIGIESTVALUE</ds:DigestValue>
                    </ds:Reference>
                </ds:SignedInfo>
                <ds:SignatureValue>VALUEOFSIGNATUREVALUE</ds:SignatureValue>
                <ds:KeyInfo Id="{{someValue}}">
                    <wsse:SecurityTokenReference wsu:Id="{{someValue}}">
                        <ds:X509Data>
                            <ds:X509IssuerSerial>
                                <ds:X509IssuerName>{{someValue}}</ds:X509IssuerName>
                                <ds:X509SerialNumber>{{someValue}}</ds:X509SerialNumber>
                            </ds:X509IssuerSerial>
                        </ds:X509Data>
                    </wsse:SecurityTokenReference>
                </ds:KeyInfo>
            </ds:Signature>
        </wsse:Security>

每个提示都有帮助,我不希望得到全面的答案,但我对以下问题最感兴趣:

  • 如何定义前缀dsec
  • 如何配置wsse:SecurityTokenReference
  • 在哪里可以找到所有标识符,标记为{{someValue}}

经过两周的战斗,SignedXml我需要在 2021 年确认相同的状态,这是Rick Stahl 在 2008 年描述的

难怪在 Windows 上如此轻而易举地使用证书的签名和加密安全性。这是一个皇家的痛苦,可怕的记录,需要大量的工具。

虽然Java县的同事说,这在国外要简单得多,但我认为问题在于SOAP服务的规范过于复杂。

4

1 回答 1

0

这是基于提到的 Rick Stahl 的文章的部分解决方案。假设我们有要签名的 XML 文档:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP:Envelope xmlns:r1="http://www.routeone.com/namespace.messaging.diag#"
               xmlns:star="http://www.starstandards.org/STAR"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:oa="http://www.openapplications.org/oagis">
  <SOAP:Body>
    <!-- data to be signed here -->
  </SOAP:Body>
</SOAP:Envelope>

使用该代码:

using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Xml;

namespace XmlSigner
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create a new XML document.
            var doc = new XmlDocument();

            // Format the document to ignore white spaces.
            doc.PreserveWhitespace = false;

            // Load the passed XML file using it's name.
            doc.Load(new XmlTextReader("C:\\Repos\\request.xml"));

            // Initialize the certificate.
            var certificate = new X509Certificate2("C:\\Repos\\certificate.pfx", "youdontguessthispassword");

            // Sign the document
            var signedDoc = SignSoapBody(doc, certificate);

            // Write to the output file.
            File.WriteAllText("C:\\Repos\\signed.xml", signedDoc.OuterXml);

            // Validate the result.
            if (ValidateSoapBodySignature(signedDoc, certificate))
                Console.WriteLine("Everything looks good, keep going.");
            else
                Console.WriteLine("You've screwed, back to work.");
        }

        private const string STR_SOAP_NS = "http://schemas.xmlsoap.org/soap/envelope/";
        private const string STR_SOAPSEC_NS = "http://schemas.xmlsoap.org/soap/security/2000-12";

        /// <summary>
        /// Signs the SOAP document and adds a digital signature to it.
        /// 
        /// Note a lot of optional settings are applied against
        /// key and certificate info to match the required XML document
        /// structure the server requests.
        /// </summary>
        /// <param name="xmlDoc"></param>
        /// <param name="certFriendlyName">Friendly Name of Cert installed in the Certificate Store under CurrentUser | Personal</param>
        /// <returns></returns>
        static public XmlDocument SignSoapBody(XmlDocument xmlDoc, X509Certificate2 cert)
        {
            // Add search Namespaces references to ensure we can reliably work 
            // against any SOAP docs regardless of tag naming
            XmlNamespaceManager ns = new XmlNamespaceManager(xmlDoc.NameTable);
            ns.AddNamespace("SOAP", STR_SOAP_NS);
            ns.AddNamespace("SOAP-SEC", STR_SOAPSEC_NS);

            // Grab the body element - this is what we create the signature from
            XmlElement body = xmlDoc.DocumentElement.SelectSingleNode(@"//SOAP:Body", ns) as XmlElement;
            if (body == null)
                throw new ApplicationException("No body tag found");

            // We'll only encode the <SOAP:Body> - add id: Reference as #Body
            var bodyId = $"id-{Guid.NewGuid()}";
            body.SetAttribute("id", bodyId);

            // Signed XML will create Xml Signature - Xml fragment
            SignedXml signedXml = new SignedXml(xmlDoc);
            signedXml.Signature.Id = $"sid-{Guid.NewGuid()}";
            signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";

            // Create a KeyInfo structure
            KeyInfo keyInfo = new KeyInfo();
            keyInfo.Id = $"kid-{Guid.NewGuid()}";

            // The actual key for signing - MAKE SURE THIS ISN'T NULL!
            signedXml.SigningKey = cert.PrivateKey;

            // Specifically use the issuer and serial number for the data rather than the default
            KeyInfoX509Data keyInfoData = new KeyInfoX509Data();
            keyInfoData.AddIssuerSerial(cert.Issuer, cert.GetSerialNumberString());
            keyInfo.AddClause(keyInfoData);


            // provide the certficate info that gets embedded - note this is only
            // for specific formatting of the message to provide the cert info
            signedXml.KeyInfo = keyInfo;

            // Again unusual - meant to make the document match template
            signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;

            // Now create reference to sign: Point at the Body element
            Reference reference = new Reference();
            reference.DigestMethod = "http://www.w3.org/2000/09/xmldsig#sha1";
            reference.Uri = $"#{bodyId}";  // reference id=body section in same doc
            reference.AddTransform(new XmlDsigExcC14NTransform());  // required to match doc
            signedXml.AddReference(reference);

            // Finally create the signature
            signedXml.ComputeSignature();

            // Result is an XML node with the signature detail below it
            // Now let's add the sucker into the SOAP-HEADER
            XmlElement signedElement = signedXml.GetXml();

            // Create SOAP-SEC:Signature element
            XmlElement soapSignature = xmlDoc.CreateElement("Security", STR_SOAPSEC_NS);
            soapSignature.Prefix = "wsse";
            //soapSignature.SetAttribute("MustUnderstand", "", "1");

            // And add our signature as content
            soapSignature.AppendChild(signedElement);

            // Now add the signature header into the master header
            XmlElement soapHeader = xmlDoc.DocumentElement.SelectSingleNode("//SOAP:Header", ns) as XmlElement;
            if (soapHeader == null)
            {
                soapHeader = xmlDoc.CreateElement("Header", STR_SOAP_NS);
                soapHeader.Prefix = "SOAP";
                xmlDoc.DocumentElement.InsertBefore(soapHeader, xmlDoc.DocumentElement.ChildNodes[0]);
            }
            soapHeader.AppendChild(soapSignature);

            return xmlDoc;
        }

        /// <summary>
        /// Validates the Xml Signature in a document.
        /// 
        /// This routine is significantly simpler because the key parameters
        /// are embedded into the signature itself. All that's needed is a
        /// certificate to provide the key - the rest can be read from the
        /// Signature itself.
        /// </summary>
        /// <param name="doc"></param>
        /// <param name="publicCertFileName"></param>
        /// <returns></returns>
        static public bool ValidateSoapBodySignature(XmlDocument doc, X509Certificate2 cert)
        {
            // Load the doc this time
            SignedXml sdoc = new SignedXml(doc);

            // Find the signature and load it into SignedXml
            XmlNodeList nodeList = doc.GetElementsByTagName("Signature");
            sdoc.LoadXml((XmlElement)nodeList[0]);

            // Now read the actual signature and validate
            bool result = sdoc.CheckSignature(cert, true);

            return result;
        }
    }
}

我有这个结果:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP:Envelope xmlns:r1="http://www.routeone.com/namespace.messaging.diag#" xmlns:star="http://www.starstandards.org/STAR" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/" xmlns:oa="http://www.openapplications.org/oagis">
    <SOAP:Header>
        <wsse:Security xmlns:wsse="http://schemas.xmlsoap.org/soap/security/2000-12">
            <Signature Id="sid-f3d74053-b47a-4740-b105-74ba1a550d38" xmlns="http://www.w3.org/2000/09/xmldsig#">
                <SignedInfo>
                    <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                    <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
                    <Reference URI="#id-9718651b-c7a4-47a0-bcd1-f89e1a5f8395">
                        <Transforms>
                            <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                        </Transforms>
                        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                        <DigestValue>SOME_SECRET_VALUES</DigestValue>
                    </Reference>
                </SignedInfo>
                <SignatureValue>SOME_SECRET_VALUES</SignatureValue>
                <KeyInfo Id="kid-626cac63-9f43-46a9-80e3-ed350ef6cea2">
                    <X509Data>
                        <X509IssuerSerial>
                            <X509IssuerName>CN=XX, OU=XX, O=XX, L=XX, C=XX</X509IssuerName>
                            <X509SerialNumber>SOME_SECRET_VALUES</X509SerialNumber>
                        </X509IssuerSerial>
                    </X509Data>
                </KeyInfo>
            </Signature>
        </wsse:Security>
    </SOAP:Header>
    <SOAP:Body id="id-9718651b-c7a4-47a0-bcd1-f89e1a5f8395"> <!-- data to be signed here -->
    </SOAP:Body>
</SOAP:Envelope>
于 2021-05-28T09:11:13.367 回答