C#中的SignXml无法验证其自身的签名

时间:2019-07-02 14:37:35

标签: c# .net .net-core xml-dsig

我正在努力替换不再维护的旧版应用程序。除了数字签名方法,我已替换了大多数东西。我在.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 core。我也尝试过sha256,但仍然存在相同的问题。

1 个答案:

答案 0 :(得分:0)

找到了我想要的答案。

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()构造函数的Element是签名上下文(因此是SOAP:Header元素)。 LoadXml()是实际加载签名本身的位置(不在构造函数中)。解决此问题解决了我所有的问题。