使用.NET验证使用Java CXF / WSS4J应用程序生成的数字签名 - 名称空间问题?

时间:2011-03-02 16:31:05

标签: c# java digital-signature

悬崖笔记

.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>

但是,我的应用程序还会从使用CXFWSS4J的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应用程序。

0 个答案:

没有答案