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() 是您实际加载签名本身的地方(不在构造函数中)。解决这个问题解决了我所有的问题。
我正在努力替换不再维护的遗留应用程序。除了数字签名方法,我已经更换了大部分东西。我在 .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() 是您实际加载签名本身的地方(不在构造函数中)。解决这个问题解决了我所有的问题。