2017-07-21 4 views
0

Je luttais en essayant de signer numériquement enveloppe de savon dans Microsoft .NET. Webservice rejetait ma demande signée .NET en disant "signature invalide". Dans ce cas, webservice a été écrit en Java par un tiers, donc je ne pouvais pas faire de changements du côté serveur..NET SignedXml avec un préfixe spécifique d'espace de noms ("ds:") et sans X509Data

Le côté serveur attendait un élément de signature avec le préfixe ds. La classe SignedXml par défaut ne produisait pas de xml avec le préfixe ds pour l'élément de signature et les éléments enfants. Un autre problème était lié à ds: KeyInfo qui devrait avoir des éléments KeyValue et X509IssuerSerial - par défaut dans .NET c'est l'élément X509Data. Donc la structure du message devrait ressembler à ceci pour que le serveur accepte la demande:

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

Répondre

0

Je veux partager ma solution. Peut-être que cela sera utile aux autres, aux prises avec des problèmes similaires. J'ai créé cela avec .NET 3.5 et utilisait Microsoft Web Services Enhancements (WSE) 3.0.

donc j'overrided par défaut XmlDocument avoir setPrefix et méthodes 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; 
     } 

    } 

Puis overrided classe SignedXml pour soutenir l'attribut id de l'espace de noms http://schemas.xmlsoap.org/soap/security/2000-12 dans l'élément de corps

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; 
     } 
} 

Et puis un peu en désordre, mais le code de travail pour signer le document 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; 
     } 
} 

Dans mon XmlDocument d'entrée de cas est passé à la méthode SignSoapBody ressemble à ceci:

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

Espérons que cela sera utile à quelqu'un ...

P.S. Si je tente de signer avec la signature .NET 4.0 devient invalide, donc pour cette raison que j'utilise plus ancien .NET 3.5 (.NET 2.0 fonctionne bien aussi bien). La chose est que dans la version .NET 4.0 System.Security.dll a changé et je pense que pour cette raison, il rend les valeurs de signature invalides qui est pas acceptable pour le côté du serveur avec lequel j'ai besoin de communiquer.