C# 中的 SignXml 无法验证它自己的签名

SignXml in C# fails to verify it's own signature

我正在努力替换不再维护的遗留应用程序。除了数字签名方法,我已经更换了大部分东西。我在 .net 核心中有一个实现,我有点困惑为什么它无法验证它自己的签名文档。

考虑以下xml(代码很乱,目前只是运行一个poc):

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <tns:Body id="Body" xmlns:tns="http://schemas.xmlsoap.org/soap/envelope/">This is a test</tns:Body>
</soap:Envelope>

使用以下 c#:

var publicCert = new X509Certificate2(File.ReadAllBytes("public.cer"));
var cert = GetSigningCertificate("private.pfx", "super amazing password");

var xml = File.ReadAllText("test.xml");
var doc = new XmlDocument {PreserveWhitespace = false};
doc.LoadXml(xml);

//remove <?xml?> tag
if (doc.FirstChild is XmlDeclaration)
{
    doc.RemoveChild(doc.FirstChild);
}

//create the header node:  NOTE: This is not on the original document but required by the vendor
var header = new StringBuilder();
header.AppendLine("<SOAP:Header xmlns:SOAP=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:SOAP-SEC=\"http://schemas.xmlsoap.org/soap/security/2000-12\">");
header.AppendLine("<SOAP-SEC:Signature>");
header.AppendLine("</SOAP-SEC:Signature>");
header.AppendLine("</SOAP:Header>");
var headerXml = new XmlDocument {PreserveWhitespace = false};
headerXml.LoadXml(header.ToString());
//add header document to the main document
if (headerXml.DocumentElement != null) doc.DocumentElement?.InsertBefore(doc.ImportNode(headerXml.DocumentElement, true), doc.DocumentElement?.FirstChild);
var domHeader = (XmlElement) doc.FirstChild.FirstChild;

// Create a SignedXml object.
var referenceUri = "#Body";
var signedXml = new SignedXml(domHeader) { SigningKey = cert.PrivateKey };  #NOTE: signing is done at the /SOAP:Header level per the vendor, not at the ParentDocument level
signedXml.SignedInfo.CanonicalizationMethod = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments";
signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
// Create a reference to be signed.
var reference = new Reference
{
    Uri = referenceUri,
    DigestMethod = "http://www.w3.org/2000/09/xmldsig#sha1"
};

// Add the reference to the SignedXml object.
signedXml.AddReference(reference);

//add key info
var keyInfo = new KeyInfo();
var certInfo = new KeyInfoX509Data();
if (cert.SerialNumber != null) certInfo.AddIssuerSerial(cert.Issuer, cert.SerialNumber);
keyInfo.AddClause(certInfo);
signedXml.KeyInfo = keyInfo;

// Compute the signature.
signedXml.ComputeSignature();

// Get the XML representation of the signature and save
// it to an XmlElement object.
var xmlDigitalSignature = signedXml.GetXml();

//get the SOAP-SEC header and add our signature to it
var soapSecurityList = doc.GetElementsByTagName("Signature", "http://schemas.xmlsoap.org/soap/security/2000-12");
if(soapSecurityList.Count == 0)
{
    throw new Exception("Could not find SOAP-SEC header!");
}
var soapSecurity = soapSecurityList.Item(0);

soapSecurity.AppendChild(xmlDigitalSignature);

这会产生以下内容 xml(cert、sig、hash、serial 是实际值的占位符,已编辑以在此处发布):

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP:Header xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-SEC="http://schemas.xmlsoap.org/soap/security/2000-12">
        <SOAP-SEC:Signature>
            <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
                <SignedInfo>
                    <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" />
                    <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
                    <Reference URI="#Body">
                        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                        <DigestValue>hash</DigestValue>
                    </Reference>
                </SignedInfo>
                <SignatureValue>sig</SignatureValue>
                <KeyInfo>
                    <X509Data>
                        <X509IssuerSerial>
                            <X509IssuerName>cert</X509IssuerName>
                            <X509SerialNumber>serial</X509SerialNumber>
                        </X509IssuerSerial>
                    </X509Data>
                </KeyInfo>
            </Signature>
        </SOAP-SEC:Signature>
    </SOAP:Header>
    <tns:Body id="Body" xmlns:tns="http://schemas.xmlsoap.org/soap/envelope/">This is a test</tns:Body>
</soap:Envelope>

但是,尝试验证它失败并显示以下代码:

//verify what we just did
var verifiedXml = new XmlDocument {PreserveWhitespace = false};
verifiedXml.LoadXml(doc.InnerXml);  //document object from above
var signature = verifiedXml.GetElementsByTagName("Signature", SignedXml.XmlDsigNamespaceUrl);
var validSignature = new SignedXml((XmlElement) signature[0].ParentNode);
return validSignature.CheckSignature(cert, true);  //also tried publicCert but no luck

签名总是失败。我看过许多其他堆栈溢出问题,其中大部分涉及空白问题(所有文档都忽略空白,并且所有源在读入之前都已线性化)或在规范化方法期间将 "ds" 名称空间添加到签名前缀。我实施了 "ds" 修复并再次尝试。它产生以下 xml,但仍然无法验证。

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP:Header xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-SEC="http://schemas.xmlsoap.org/soap/security/2000-12">
        <SOAP-SEC:Signature>
            <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:SignedInfo>
                    <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" />
                    <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
                    <ds:Reference URI="#Body">
                        <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                        <ds:DigestValue>hash</ds:DigestValue>
                    </ds:Reference>
                </ds:SignedInfo>
                <ds:SignatureValue>sig</ds:SignatureValue>
                <ds:KeyInfo>
                    <ds:X509Data>
                        <ds:X509IssuerSerial>
                            <ds:X509IssuerName>cert</ds:X509IssuerName>
                            <ds:X509SerialNumber>serial</ds:X509SerialNumber>
                        </ds:X509IssuerSerial>
                    </ds:X509Data>
                </ds:KeyInfo>
            </ds:Signature>
        </SOAP-SEC:Signature>
    </SOAP:Header>
    <tns:Body id="Body" xmlns:tns="http://schemas.xmlsoap.org/soap/envelope/">This is a test</tns:Body>
</soap:Envelope>

更新 基于评论。尝试了以下方法:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <tns:Body id="Body" xmlns:tns="http://schemas.xmlsoap.org/soap/envelope/">This is a test</tns:Body>
</soap:Envelope>
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Security.Policy;

namespace XmlDsig
{
    public class XmlDsig
    {
        public bool Sign()
        {
            try
            {
                var cert = GetSigningCertificate("cert.pfx", "password");
                var xml = File.ReadAllText("test.xml");

                // Create a new XML document.
                var doc = new XmlDocument {PreserveWhitespace = false};
                doc.LoadXml(xml);


                //remove <?xml?> tag
                if (doc.FirstChild is XmlDeclaration)
                {
                    doc.RemoveChild(doc.FirstChild);
                }

                //create the header node
                var header = new StringBuilder();
                header.AppendLine("<SOAP:Header xmlns:SOAP=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:SOAP-SEC=\"http://schemas.xmlsoap.org/soap/security/2000-12\">");
                header.AppendLine("<SOAP-SEC:Signature>");
                header.AppendLine("</SOAP-SEC:Signature>");
                header.AppendLine("</SOAP:Header>");
                var headerXml = new XmlDocument {PreserveWhitespace = false};

                headerXml.LoadXml(header.ToString());


                //add header document to the main document
                if (headerXml.DocumentElement != null) doc.DocumentElement?.InsertBefore(doc.ImportNode(headerXml.DocumentElement, true), doc.DocumentElement?.FirstChild);

                var domHeader = (XmlElement) doc.FirstChild.FirstChild;

                SignXmlWithCertificate(domHeader, cert);


                var validatorXml = new XmlDocument();
                validatorXml.LoadXml(doc.OuterXml);
                var sigContext = validatorXml.GetElementsByTagName("Header", "http://schemas.xmlsoap.org/soap/envelope/");
                var validator = new SignedXml((XmlElement) sigContext[0]);          return validator.CheckSignature(cert, true);


            }
            catch (Exception e)
            {
                return false;
                // return e.ToString();
            }
        }



        public static void SignXmlWithCertificate(XmlElement assertion, X509Certificate2 cert)
        {
            var signedXml = new SignedXml(assertion) {SigningKey = cert.PrivateKey};
            var reference = new Reference {Uri = "#Body"};
            reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
            signedXml.AddReference(reference);

            var keyInfo = new KeyInfo();
            keyInfo.AddClause(new KeyInfoX509Data(cert));

            signedXml.KeyInfo = keyInfo;
            signedXml.ComputeSignature();
            var xmlsig = signedXml.GetXml();

            assertion.FirstChild.AppendChild(xmlsig);
        }
    }
}

仍然无法验证。

更新 2 现在我在调用 CheckSignature 时收到以下错误:

SignatureDescription could not be created for the signature algorithm supplied.

这似乎是使用 sha256 的 .net 4 上的常见问题,但我在当前示例中使用的是 sha1,而且我使用的是 dotnet 核心。我也尝试过 sha256,但同样的问题仍然存在。

找到了我想要的答案。

var verify_xml = new XmlDocument();
verify_xml.LoadXml(doc.OuterXml);  //doc = the signed document loaded from disk
var signature = verify_xml.GetElementsByTagName("Signature", "http://www.w3.org/2000/09/xmldsig#");
var verify = new SignedXml((XmlElement)verify_xml.DocumentElement.FirstChild);
verify.LoadXml((XmlElement) signature[0]);
verify.CheckSignature(cert, true);

传递给 SignedXml() 构造函数的元素是签名上下文(因此是 SOAP:Header 元素)。 LoadXml() 是您实际加载签名本身的地方(不在构造函数中)。解决这个问题解决了我所有的问题。