【问题标题】:.NET SignedXml with a Specific Namespace Prefix ("ds:") and without X509Data具有特定命名空间前缀 ("ds:") 且不带 X509Data 的 .NET SignedXml
【发布时间】:2017-07-23 15:55:15
【问题描述】:

我在尝试在 Microsoft .NET 中对肥皂信封进行数字签名时遇到了困难。 Webservice 拒绝了我的 .NET 签名请求,说“签名无效”。在这种情况下,webservice 是由第三方用 Java 编写的,所以我无法在服务器端进行任何更改。

服务器端期待带有 ds 前缀的签名元素。默认情况下,SignedXml 类不会为签名元素和子元素生成带有 ds 前缀的 xml。另一个问题与 ds:KeyInfo 有关,它应该具有 KeyValue 和 X509IssuerSerial 元素——在 .NET 中默认为 X509Data 元素。所以消息的结构应该是这样的,以便服务器接受请求:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header>
      <wsse:Security SOAP-ENV:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
         <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="#ea43a55321b243c082dadae4f53f32b5">
                  <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                  <ds:DigestValue>.........</ds:DigestValue>
               </ds:Reference>
            </ds:SignedInfo>
            <ds:SignatureValue>.......</ds:SignatureValue>
            <ds:KeyInfo>
               <ds:KeyValue>
                  <ds:RSAKeyValue>
                     <ds:Modulus>.......</ds:Modulus>
                     <ds:Exponent>....</ds:Exponent>
                  </ds:RSAKeyValue>
               </ds:KeyValue>
               <ds:X509IssuerSerial>
                  <ds:X509IssuerName>.......</ds:X509IssuerName>
                  <ds:X509SerialNumber>.......</ds:X509SerialNumber>
               </ds:X509IssuerSerial>
            </ds:KeyInfo>
         </ds:Signature>
      </wsse:Security>
   </SOAP-ENV:Header>
   <SOAP-ENV:Body ds:id="ea43a55321b243c082dadae4f53f32b5" xmlns:ds="http://schemas.xmlsoap.org/soap/security/2000-12">
            .................
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

【问题讨论】:

    标签: .net soap digital-signature webservice-client signedxml


    【解决方案1】:

    我想分享我的解决方案。也许这对其他人有帮助,正在努力解决类似的问题。

    我使用 .NET 3.5 创建了它,并使用了 Microsoft Web Services Enhancements (WSE) 3.0。

    所以我重写了默认的 XmlDocument 以具有 SetPrefix 和 GetPrefix 方法:

     public class XmlDsigDocument : XmlDocument
     {
            // Constants
            public const string XmlDsigNamespacePrefix = "ds";
    
            /// <summary>
            /// Override CreateElement function as it is extensively used by SignedXml
            /// </summary>
            /// <param name="prefix"></param>
            /// <param name="localName"></param>
            /// <param name="namespaceURI"></param>
            /// <returns></returns>
            public override XmlElement CreateElement(string prefix, string localName, string namespaceURI)
            {
                // CAntonio. If this is a Digital signature security element, add the prefix. 
                if (string.IsNullOrEmpty(prefix))
                {
                    // !!! Note: If you comment this line, you'll get a valid signed file! (but without ds prefix)
                    // !!! Note: If you uncomment this line, you'll get an invalid signed file! (with ds prefix within 'Signature' object)
                    //prefix = GetPrefix(namespaceURI);
    
                    // The only way to get a valid signed file is to prevent 'Prefix' on 'SignedInfo' and descendants.
                    List<string> SignedInfoAndDescendants = new List<string>();
                    SignedInfoAndDescendants.Add("SignedInfo");
                    SignedInfoAndDescendants.Add("CanonicalizationMethod");
                    SignedInfoAndDescendants.Add("InclusiveNamespaces");
                    SignedInfoAndDescendants.Add("SignatureMethod");
                    SignedInfoAndDescendants.Add("Reference");
                    SignedInfoAndDescendants.Add("Transforms");
                    SignedInfoAndDescendants.Add("Transform");
                    SignedInfoAndDescendants.Add("InclusiveNamespaces");
                    SignedInfoAndDescendants.Add("DigestMethod");
                    SignedInfoAndDescendants.Add("DigestValue");
                    if (!SignedInfoAndDescendants.Contains(localName))
                    {
                        prefix = GetPrefix(namespaceURI);
                    }
                }
    
                return base.CreateElement(prefix, localName, namespaceURI);
            }
    
            /// <summary>
            /// Select the standar prefix for the namespaceURI provided
            /// </summary>
            /// <param name="namespaceURI"></param>
            /// <returns></returns>
            public static string GetPrefix(string namespaceURI)
            {
                if (namespaceURI == "http://www.w3.org/2001/10/xml-exc-c14n#")
                    return "ec";
                else if (namespaceURI == SignedXml.XmlDsigNamespaceUrl)
                    return "ds";
    
                return string.Empty;
            }
            /// <summary>
            /// Set the prefix to this and all its descendants.
            /// </summary>
            /// <param name="prefix"></param>
            /// <param name="node"></param>
            /// <returns></returns>
            public static XmlNode SetPrefix(string prefix, XmlNode node)
            {
                foreach (XmlNode n in node.ChildNodes)
                {
                    SetPrefix(prefix, n);
                }
                if (node.NamespaceURI == "http://www.w3.org/2001/10/xml-exc-c14n#")
                    node.Prefix = "ec";
                else if ((node.NamespaceURI == SignedXmlWithId.xmlDSignSecurityUrl) || (string.IsNullOrEmpty(node.Prefix)))
                    node.Prefix = prefix;
    
                return node;
            }
    
        }
    

    然后重写 SignedXml 类以支持 body 元素中命名空间http://schemas.xmlsoap.org/soap/security/2000-12 的 id 属性

     internal sealed class SignedXmlWithId : SignedXml
     {
            public SignedXmlWithId()
                : base()
            {
            }
    
            public SignedXmlWithId(XmlDocument doc)
                : base(doc)
            {
            }
    
            public SignedXmlWithId(XmlElement elem)
                : base(elem)
            {
            }
    
            public const string xmlSoapEnvelopeUrl = "http://schemas.xmlsoap.org/soap/envelope/";
    
            public const string xmlDSignSecurityUrl = "http://www.w3.org/2000/09/xmldsig#";
    
            public const string xmlBodyIDNamespaceUrl = "http://schemas.xmlsoap.org/soap/security/2000-12";
    
            public const string xmlOasisWSSSecurityExtUrl = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
    
            public override XmlElement GetIdElement(XmlDocument doc, string id)
            {
                // check to see if it's a standard ID reference
                XmlElement idElem = base.GetIdElement(doc, id);
    
                if (idElem == null)
                {
                    XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
                    nsManager.AddNamespace("ds", "http://schemas.xmlsoap.org/soap/security/2000-12");
    
                    idElem = doc.SelectSingleNode("//*[@ds:id=\"" + id + "\"]", nsManager) as XmlElement;
                }
    
                return idElem;
            }
    }
    

    然后有点乱,但可以工作的代码来签署 xml 文档:

    public class SignatureHelper
    {
            public XmlDsigDocument SignSoapBody(XmlDsigDocument xmlDoc, X509Certificate2 cert)
            {
                XmlNamespaceManager ns = new XmlNamespaceManager(xmlDoc.NameTable);
                ns.AddNamespace("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
    
                XmlElement body = xmlDoc.DocumentElement.SelectSingleNode(@"//SOAP-ENV:Body", ns) as XmlElement;
                if (body == null)
                    throw new Exception("No body tag found");
    
                string bodyId = Guid.NewGuid().ToString().Replace("-", "");
    
                body.SetAttribute("id", "http://schemas.xmlsoap.org/soap/security/2000-12", bodyId);
    
                SignedXmlWithId signedXml = new SignedXmlWithId(xmlDoc);
                signedXml.SigningKey = cert.PrivateKey;
    
                string mySerialNumber = "";
                string[] subjectArray = cert.Subject.Split(',');
    
                for (int i = 0; i < subjectArray.Length; i++)
                {
                    if (subjectArray[i].StartsWith("SERIALNUMBER="))
                    {
                        mySerialNumber = subjectArray[i].Replace("SERIALNUMBER=", "");
                        break;
                    }
                }
    
                RSAKeyValue rsa = new RSAKeyValue((System.Security.Cryptography.RSA)cert.PublicKey.Key);
                XmlElement rsaElem = rsa.GetXml();
    
                KeyInfo keyInfo = new KeyInfo();
                XmlDsigDocument doc = new XmlDsigDocument();
                doc.LoadXml("<x>" + rsaElem.OuterXml + "<X509IssuerSerial><X509IssuerName>" + cert.Issuer + "</X509IssuerName><X509SerialNumber>" + mySerialNumber + "</X509SerialNumber></X509IssuerSerial></x>");
    
                keyInfo = Microsoft.Web.Services3.Security.KeyInfoHelper.LoadXmlKeyInfo(doc.DocumentElement); //Microsoft WSE 3.0
                signedXml.KeyInfo = keyInfo;
    
                signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigC14NWithCommentsTransformUrl;
    
                Reference reference = new Reference();
                reference.Uri = "#" + bodyId;  
    
                signedXml.AddReference(reference);
                signedXml.ComputeSignature();
    
                XmlElement signedElement = signedXml.GetXml();
                signedElement.Prefix = "ds";
    
                for (int i = 0; i < signedElement.ChildNodes.Count; i++)
                {
                    signedElement.ChildNodes[i].Prefix = "ds";
    
                    for (int k = 0; k < signedElement.ChildNodes[i].ChildNodes.Count; k++)
                    {
                        signedElement.ChildNodes[i].ChildNodes[k].Prefix = "ds";
    
                        for (int m = 0; m < signedElement.ChildNodes[i].ChildNodes[k].ChildNodes.Count; m++)
                        {
                            signedElement.ChildNodes[i].ChildNodes[k].ChildNodes[m].Prefix = "ds";
    
                            for (int n = 0; n < signedElement.ChildNodes[i].ChildNodes[k].ChildNodes[m].ChildNodes.Count; n++)
                            {
                                signedElement.ChildNodes[i].ChildNodes[k].ChildNodes[m].ChildNodes[n].Prefix = "ds";
                            }
                        }
                    }
                }
    
                XmlElement soapSignature = xmlDoc.CreateElement("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
                soapSignature.Prefix = "wsse";
                soapSignature.SetAttribute("mustUnderstand", "http://schemas.xmlsoap.org/soap/envelope/", "1");
    
                signedElement.ChildNodes[1].ChildNodes[0].Value = Chunks(signedElement.ChildNodes[1].ChildNodes[0].Value);
                signedElement.ChildNodes[2].ChildNodes[0].ChildNodes[0].ChildNodes[0].ChildNodes[0].Value = Chunks(signedElement.ChildNodes[2].ChildNodes[0].ChildNodes[0].ChildNodes[0].ChildNodes[0].Value);
    
                soapSignature.AppendChild(signedElement);
    
                XmlElement soapHeader = xmlDoc.DocumentElement.SelectSingleNode("//SOAP-ENV:Header", ns) as XmlElement;
                if (soapHeader == null)
                {
                    soapHeader = xmlDoc.CreateElement("Header", "http://schemas.xmlsoap.org/soap/envelope/");
                    soapHeader.Prefix = "SOAP-ENV";
                    xmlDoc.DocumentElement.InsertBefore(soapHeader, xmlDoc.DocumentElement.ChildNodes[0]);
                }
                soapHeader.AppendChild(soapSignature);
    
    
                string xmlContent = xmlDoc.OuterXml;
    
                xmlContent = xmlContent.Replace("X509IssuerSerial", "ds:X509IssuerSerial");
                xmlContent = xmlContent.Replace("X509IssuerName", "ds:X509IssuerName");
                xmlContent = xmlContent.Replace("X509SerialNumber", "ds:X509SerialNumber");
    
                XmlDsigDocument xmlDocResult = new XmlDsigDocument();
                xmlDocResult.LoadXml(xmlContent);
    
                xmlDocResult = GenerateSignatureValue(xmlDocResult, cert);
    
                return xmlDocResult;
            }
    
            private string Chunks(string str)
            {
                byte[] bytes = Convert.FromBase64String(str);
                return Convert.ToBase64String(bytes, Base64FormattingOptions.InsertLineBreaks);
            }
    
            private XmlDsigDocument GenerateSignatureValue(XmlDsigDocument xmlDoc, X509Certificate2 cert)
            {
                RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)cert.PrivateKey;
    
                XmlNamespaceManager ns = new XmlNamespaceManager(xmlDoc.NameTable);
                ns.AddNamespace("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
    
                SignedXmlWithId signedXml = new SignedXmlWithId(xmlDoc);
    
                signedXml.SignedInfo.CanonicalizationMethod = SignedXmlWithId.XmlDsigC14NWithCommentsTransformUrl;
    
                XmlNodeList nodeList = xmlDoc.GetElementsByTagName("ds:Signature");
                signedXml.LoadXml((XmlElement)nodeList[0]);
    
                signedXml.SigningKey = privateKey;
                signedXml.ComputeSignature();
    
                XmlElement signedElement = signedXml.GetXml();
    
                bool ok = signedXml.CheckSignature();
    
                if(!ok)
                {
                    throw new Exception("Invalid signature");
                }
    
                xmlDoc.DocumentElement.ChildNodes[0].ChildNodes[0].RemoveChild(xmlDoc.DocumentElement.ChildNodes[0].ChildNodes[0].ChildNodes[0]);
    
                XmlElement soapHeader = xmlDoc.DocumentElement.SelectSingleNode("//SOAP-ENV:Header", ns) as XmlElement;
                if (soapHeader != null)
                    soapHeader.ChildNodes[0].AppendChild(signedElement);
    
                return xmlDoc;
            }
    }
    

    在我的情况下,传递给方法 SignSoapBody 的输入 XmlDocument 如下所示:

    <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
       <SOAP-ENV:Header>
       </SOAP-ENV:Header>
       <SOAP-ENV:Body>
          ..............
       </SOAP-ENV:Body>
    </SOAP-ENV:Envelope>
    

    希望这对某人有所帮助...

    附言如果我尝试使用 .NET 4.0 签名签名变得无效,那么对于 这就是我使用较旧的 .NET 3.5 的原因(.NET 2.0 也可以正常工作)。 问题是在 .NET 4.0 System.Security.dll 版本已经改变 我认为因此它会产生无效的签名值,即 我需要与之通信的服务器端不可接受。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-09
      相关资源
      最近更新 更多