2012-04-30 2 views
5

Sto utilizzando un client WCF per comunicare con un servizio Web non WCF.Specifica URI di riferimento di #Body durante la firma digitale della richiesta SOAP - utilizzando WCF

Questo servizio Web richiede che il corpo del messaggio SOAP sia firmato, tuttavia, sto riscontrando problemi nella generazione di una richiesta SOAP valida.

Ho implementato un ClientMessageInspector che eredita da IClientMessageInspector, dove modifico il messaggio nel metodo BeforeSendRequest per aggiungere la firma digitale XML. Io uso la classe SignedXML per fare questo.

Sto utilizzando lo strumento di convalida dei servizi Web IBM per WSDL e SOAP per verificare se la mia firma digitale viene verificata.

Il mio problema è che quando si specifica un riferimento completo di namespace nell'URI di riferimento, gli strumenti IBM che sto usando dicono che ho una firma valida. Dalle specifiche XML Digital Signature, dovrei essere in grado di fare riferimento all'attributo, senza lo spazio dei nomi, tuttavia, quando lo faccio, non ottengo una firma digitale valida.

Questa è la richiesta SOAP Attualmente sto generando, che i miei strumenti di dire che ha una firma valida, ma il servizio web non piace:

<?xml version="1.0" encoding="utf-8"?> 
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" 
      xmlns:s="http://www.w3.org/2003/05/soap-envelope" 
      xmlns:soapsec="http://schemas.xmlsoap.org/soap/security/2000-12" 
      xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <s:Header> 
    <soapsec:Signature> 
     <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> 
     <SignedInfo> 
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" /> 
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /> 
      <Reference URI="http://schemas.xmlsoap.org/soap/security/2000-12#Body"> 
      <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> 
      <DigestValue>4mt5wluUTu5tpR2d5UemVSLvqTs=</DigestValue> 
      </Reference> 
     </SignedInfo> 
     <SignatureValue>UZ7HzfE3GxIY9hg...</SignatureValue> 
     <KeyInfo> 
      <X509Data> 
      <X509Certificate>MIIEkTCCA3mgAwIBAgIQCu...</X509Certificate> 
      </X509Data> 
      <KeyValue> 
      <RSAKeyValue> 
       <Modulus>0C3e9HDx5Yq6FLUxIgjJ...</Modulus> 
       <Exponent>AQAB</Exponent> 
      </RSAKeyValue> 
      </KeyValue> 
     </KeyInfo> 
     </Signature> 
    </soapsec:Signature> 
    </s:Header> 
    <s:Body soapsec:id="Body"> 
    .... SOAP Body Here ... 
    </s:Body> 
</s:Envelope> 

Questa è la richiesta SOAP che voglio essere la generazione , ma i miei strumenti di dire che questo ha una firma non valida, e il servizio web mi dice anche la firma non è valida:

<?xml version="1.0" encoding="utf-8"?> 
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" 
      xmlns:s="http://www.w3.org/2003/05/soap-envelope" 
      xmlns:soapsec="http://schemas.xmlsoap.org/soap/security/2000-12" 
      xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <s:Header> 
    <soapsec:Signature> 
     <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> 
     <SignedInfo> 
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" /> 
      <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>4mt5wluUTu5tpR2d5UemVSLvqTs=</DigestValue> 
      </Reference> 
     </SignedInfo> 
     <SignatureValue>UZ7HzfE3GxIY9hg...</SignatureValue> 
     <KeyInfo> 
      <X509Data> 
      <X509Certificate>MIIEkTCCA3mgAwIBAgIQCu...</X509Certificate> 
      </X509Data> 
      <KeyValue> 
      <RSAKeyValue> 
       <Modulus>0C3e9HDx5Yq6FLUxIgjJ...</Modulus> 
       <Exponent>AQAB</Exponent> 
      </RSAKeyValue> 
      </KeyValue> 
     </KeyInfo> 
     </Signature> 
    </soapsec:Signature> 
    </s:Header> 
    <s:Body soapsec:id="Body"> 
    .... SOAP Body Here ... 
    </s:Body> 
</s:Envelope> 

e qui è il codice che ho in BeforeSendRequest per creare la firma, e modificare il messaggio di conseguenza:

public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel) 
    { 
     XmlDocument doc = new XmlDocument(); 
     doc.PreserveWhitespace = true; 
     doc.LoadXml(request.ToString()); 

     // Add the required namespaces to the SOAP Envelope element, if I don't do this, the web service I'm calling returns an error 
     string soapSecNS = "http://schemas.xmlsoap.org/soap/security/2000-12"; 
     string soapEnvNS = "http://www.w3.org/2003/05/soap-envelope"; 

     //Get the header element, so that we can add the digital signature to it 
     XmlNode headerNode = doc.GetElementsByTagName("Header", soapEnvNS)[0]; 

     // Set the ID attribute on the body element, so that we can reference it later 
     XmlNode bodyNode = doc.GetElementsByTagName("Body", soapEnvNS)[0]; 

     ((XmlElement)bodyNode).RemoveAllAttributes(); 
     ((XmlElement)bodyNode).SetAttribute("id", soapSecNS, "Body"); 

     XmlWriterSettings settings2 = new XmlWriterSettings(); 
     settings2.Encoding = new System.Text.UTF8Encoding(false); 

     // Load the certificate we want to use for signing 
     SignedXmlWithId signedXml = new SignedXmlWithId(doc); 
     X509Certificate2 cert = new X509Certificate2("C:\\myCertificate.pfx", "myPassword"); 

     signedXml.SigningKey = cert.PrivateKey; 

     //Populate the KeyInfo element correctly, with the public cert and public key 
     Signature sigElement = signedXml.Signature; 
     KeyInfoX509Data x509Data = new KeyInfoX509Data(cert); 
     sigElement.KeyInfo.AddClause(x509Data); 

     RSAKeyValue rsaKeyValue = new RSAKeyValue((RSA)cert.PublicKey.Key); 
     sigElement.KeyInfo.AddClause(rsaKeyValue); 

     // Create a reference to be signed, only sign the body of the SOAP request, which we have given an 
     // ID attribute to, in order to reference it correctly here 
     Reference reference = new Reference(); 
     reference.Uri = soapSecNS + "#Body"; 

     // Add the reference to the SignedXml object. 
     signedXml.AddReference(reference); 

     // Compute the signature. 
     signedXml.ComputeSignature(); 

     // Get the XML representation of the signature and save 
     // it to an XmlElement object. 
     XmlElement xmlDigitalSignature = signedXml.GetXml(); 

     XmlElement soapSignature = doc.CreateElement("Signature", soapSecNS); 
     soapSignature.Prefix = "soapsec"; 
     soapSignature.AppendChild(xmlDigitalSignature); 

     headerNode.AppendChild(soapSignature); 

     // Make sure the byte order mark doesn't get written out 
     XmlDictionaryReaderQuotas quotas = new XmlDictionaryReaderQuotas(); 
     Encoding encoderWithoutBOM = new System.Text.UTF8Encoding(false); 

     System.IO.MemoryStream ms = new System.IO.MemoryStream(encoderWithoutBOM.GetBytes(doc.InnerXml)); 

     XmlDictionaryReader xdr = XmlDictionaryReader.CreateTextReader(ms, encoderWithoutBOM, quotas, null); 

     //Create the new message, that has the digital signature in the header 
     Message newMessage = Message.CreateMessage(xdr, System.Int32.MaxValue, request.Version); 
     request = newMessage; 

     return null; 
    } 

Qualcuno sa come posso impostare l'URI di riferimento su #Body, ma ho anche una firma XML valida?

+0

Posso chiederti perché stai generando la firma manualmente invece di utilizzare le funzionalità di sicurezza WCF per generarlo per te? Hai trovato qualcosa che non ti permette di usare la sicurezza della WCF? Btw. hai WSDL con WS-Policy o la richiesta valida per il servizio da qualche client esistente? Questi sono in genere artefatti necessari per creare client funzionanti. –

+0

Sfortunatamente il servizio web non ha un WSDL. Ho provato a utilizzare la sicurezza WCF per ottenere questo risultato, ma mi sono imbattuto nel problema che ho descritto [qui] (http://stackoverflow.com/questions/10167814/wcf-the-service-certificate-is-not-provided-for- target-error-per-WCF-client). Le soluzioni suggerite non hanno funzionato, quindi mi sono trasferito a provare una tattica diversa, che è dove sono ora. Ho chiesto a coloro che ospitano il servizio di inviarmi una richiesta valida, ma non ne ho ancora uno. Il servizio con cui sto parlando non è WCF, quindi penso che sia più difficile usare WCF OOTB. – Cristy

+0

Tuttavia, per il servizio sono presenti file XSD, ho utilizzato XSD.exe per creare classi C# da questi e, sulla mia interfaccia di servizio, ho impostato il flag XmlSerializerFormat, per consentirmi di utilizzare le classi generate. – Cristy

risposta

2

Dal momento che stavo cercando di generare una firma per una richiesta SOAP, ho aggirato questo sottoclasse SignedXml, sovrascrivendo GetIdElement, in modo da poter restituire qualunque elemento sia quello che sto cercando. In questo caso, l'elemento id apparterrà allo spazio dei nomi http://schemas.xmlsoap.org/soap/security/2000-12

public class SignedXmlWithId : SignedXml 
{ 
    public SignedXmlWithId(XmlDocument xml) 
     : base(xml) 
    { 
    } 

    public SignedXmlWithId(XmlElement xmlElement) 
     : base(xmlElement) 
    { 
    } 

    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) 
     { 
      // I've just hardcoded it for the time being, but should be using an XPath expression here, and the id that is passed in 
      idElem = (XmlElement)doc.GetElementsByTagName("Body", "http://schemas.xmlsoap.org/soap/security/2000-12")[0]; 
     } 

     return idElem; 
    } 
} 

Ho anche capito che utilizzando strumenti come IBM Web Services Validation Tool per WSDL e SOAP per convalidare la firma digitale della richiesta SOAP doesn' lavoro. Invece sto usando il seguente metodo per verificare le firme:

public static bool verifyDigitalSignatureForString(string msgAsString) 
    { 
     XmlDocument verifyDoc = new XmlDocument(); 
     verifyDoc.PreserveWhitespace = true; 
     verifyDoc.LoadXml(msgAsString); 

     SignedXmlWithId verifyXml = new SignedXmlWithId(verifyDoc); 
     // Find the "Signature" node and create a new 
     // XmlNodeList object. 
     XmlNodeList nodeList = verifyDoc.GetElementsByTagName("Signature"); 

     // Load the signature node. 
     verifyXml.LoadXml((XmlElement)nodeList[0]); 

     if (verifyXml.CheckSignature()) 
     { 
      Console.WriteLine("Digital signature is valid"); 
      return true; 
     } 
     else 
     { 
      Console.WriteLine("Digital signature is not valid"); 
      return false; 
     } 
    }