悬崖笔记
.NET生成的数字签名使用.NET和Java WSS4J进行验证,Java WSS4J生成的签名不会使用.NET验证,而是使用Java验证。可能(?)由WSS4J中生成的XML命名空间引起。
问题的主要内容
我有以下方法验证SOAP XML中的数字签名:
public static bool VerifySignature(XmlDocument soap, bool verifySignatureOnly, bool verifyUsername)
{
// Create namespace manager needed for xpath selections
XmlNamespaceManager soapNamespaces = GetSoapNamespaces(soap.NameTable);
// Use xPath constants to get binary security token
string inputb64BinarySecurityToken = soap.SelectSingleNode(SxpBinarySecurityToken, soapNamespaces).InnerText;
// Get public key from the binary security token string
var certificate = new X509Certificate2(Convert.FromBase64String(inputb64BinarySecurityToken));
// Get signature element
var signature = soap.SelectSingleNode(SxpSignatureElement, soapNamespaces) as XmlElement;
// Load into SignedXml, verify and return
var xml = new SignedXml(soap);
xml.LoadXml(signature);
return xml.CheckSignature(certificate, verifySignatureOnly);
}
我有以下生成数字签名的方法
private static void PerformSoapSecurity(XmlDocument soapDom, XmlNamespaceManager soapNamespaces, string certificateThumbprint)
{
X509Certificate2 certificate = GetDigitalCertificate(certificateThumbprint);
long certId = GenerateCertificateId(certificate);
//Cerate Soap BinarySecurityToken Element
XmlDocumentFragment securityToken = soapDom.CreateDocumentFragment();
securityToken.InnerXml = GetBinarySecurityToken(certId, GetDigitalCertificate(certificateThumbprint));
// Create Soap Signature Element
// Here passing in the whole Xml document to 'GetSignature'
// means the generated signature element doesn't need to
// be imported into the dom before inserting]
XmlElement signature = GetSignature(soapDom, certificate);
//Create Soap Key Info Element
XmlDocumentFragment keyinfo = soapDom.CreateDocumentFragment();
keyinfo.InnerXml = GetKeyInfo(certId);
//Append the created elemnts into the Soap message ***the order here IS important***
soapDom.SelectSingleNode(SxpSecurityElement, soapNamespaces).AppendChild(securityToken);
soapDom.SelectSingleNode(SxpSecurityElement, soapNamespaces).AppendChild(signature);
soapDom.SelectSingleNode(SxpSignatureElement, soapNamespaces).AppendChild(keyinfo);
// Assign the subject CN of the signing certificate as the user name (removing CN= prefix)
soapDom.SelectSingleNode(SxpUsername, soapNamespaces).InnerText = certificate.Subject.Remove(0, 3);
}
private static XmlElement GetSignature(XmlDocument document, X509Certificate2 dsigCertificate)
{
Reference reference = new Reference("#Timestamp-1");
reference.AddTransform(new XmlDsigExcC14NTransform());
var signed = new SignedXmlWithId(document);
signed.SigningKey = dsigCertificate.PrivateKey;
signed.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
signed.AddReference(reference);
signed.ComputeSignature();
XmlElement signatureElement = signed.GetXml();
return signatureElement;
}
这是我用来创建XmlNamespaceManager
的方法:
private static XmlNamespaceManager GetSoapNamespaces(XmlNameTable nameTable)
{
XmlNamespaceManager soapNamespaces = new XmlNamespaceManager(nameTable);
soapNamespaces.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");
soapNamespaces.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#");
soapNamespaces.AddNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
soapNamespaces.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
soapNamespaces.AddNamespace("addressing", "http://www.w3.org/2005/08/addressing");
return soapNamespaces;
}
现在,所有这些工作正常我可以使用签名生成SOAP XML,然后验证.NET和Java中的签名。例如,如果我签署以下内容:
<wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-1">
<wsu:Created>2011-03-02T10:51:18.178Z</wsu:Created>
<wsu:Expires>2011-03-02T11:06:18.178Z</wsu:Expires>
</wsu:Timestamp>
我明白了:
<Signature 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="#Timestamp-1">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>eC4H+inxmOWEAYTBAZCwcnK4tUA=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>--- SIGNATURE BASE64 ----</SignatureValue>
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#" Id="KeyId--6846455287902010833">
<wsse:SecurityTokenReference 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" wsu:Id="STRId-A0FC813092018230">
<wsse:Reference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" URI="#CertId--6846455287902010832" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
</wsse:SecurityTokenReference>
</KeyInfo>
</Signature>
但是,我的应用程序还会从使用CXF
和WSS4J
的Java应用程序接收消息。上面发布的代码无法验证我从Java应用程序收到的签名。如果我使用Java签署上面发布的Timestamp XML片段,我会得到与.NET生成的签名不同的签名。花了一些时间研究这个似乎这可能与签名XML上的XML命名空间有关。作为示例,Java发送以下内容:
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="Signature-2">
<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="#Timestamp-1">
<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>eC4H+inxmOWEAYTBAZCwcnK4tUA=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>--- SIGNATURE BASE64 ----</ds:SignatureValue>
<ds:KeyInfo Id="KeyId-D5343AC748043302DD12990630782012">
<wsse:SecurityTokenReference 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" wsu:Id="STRId-D5343AC748043302DD12990630782013">
<wsse:Reference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" URI="#CertId-D5343AC748043302DD12990630781901" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
这里明显的区别是“ds”命名空间的用法。如果仔细观察,DigestValues是相同的,因此规范化和散列工作正常。但是输出签名是不同的。奇怪的是,如果我改变Java签名以删除ds命名空间并将我生成的.NET签名放入SignatureValue,那么签名就会验证。因此,似乎SignedXml
正在剥离命名空间,然后使用sans-namespace XML来检查签名。
花了一些时间查看System.Security.Cryptography.Xml.SignedXml
(支持所有这些)我无法找到一种方法来控制它如何处理命名空间。我有一种感觉,它忽略/删除它们(这就是为什么当我使用.NET生成一个Signature,结果不包含名称空间时)来自输入XML,这当然会使签名无效。
所以问题是,有人知道这里可能出现什么问题吗?
值得一提的是,无论如何我都无法更改Java应用程序。