Sunday, January 30, 2011

Xml Digital Signature: Signing the KeyInfo

@YaronNaveh

Frederic Vidal had shown me recently a nice trick with Xml digital signatures. Suppose you want to add a signature which looks like this:

<ds:Signature xmlns:ds='http://www.w3.org/2000/09/xmldsig#' Id='Signature001'>
   <ds:SignedInfo>
     <ds:CanonicalizationMethod Algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315' />
     <ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1' />
     <ds:Reference URI=''>
       <ds:Transforms>
         <ds:Transform Algorithm='http://www.w3.org/2000/09/xmldsig#enveloped-signature' />
       </ds:Transforms>
       <ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1' />
       <ds:DigestValue>sXe2PnaG...</ds:DigestValue>
     </ds:Reference>
     <ds:Reference URI='#KeyInfo001'>
       <ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1' />
       <ds:DigestValue>ZOS23PQ9TcDu+G...</ds:DigestValue>
     </ds:Reference>
   </ds:SignedInfo>
   <ds:SignatureValue>jTLX0/8XkY2aCte7...</ds:SignatureValue>
     <ds:KeyInfo Id='KeyInfo001'>
       <ds:X509Data>
         <ds:X509Certificate>E3wdSY4n7MgUmJzMIGfMA0...</ds:X509Certificate>
     </ds:X509Data>
   </ds:KeyInfo>
</ds:Signature>

The interesting part is the second reference (in bold) – the signature signs the KeyInfo (#KeyInfo001), which is part of the signature element itself. The regular api to add reference to a signature is this:

var reference = new Reference();
reference.Uri = "#KeyInfo001";

However it will not work here since it will look for an element with this ID in the signed document (e.g. soap envelope). However the ID is inside the signature element itself, which is still not a part of the document because it was not create yet.
Frederic had found a nice trick: Inherit from SignedXml and override the default logic to find the references such that it will search in the signature itself (which is the base class).

public class CustomIdSignedXml : SignedXml
{
   public CustomIdSignedXml(XmlDocument doc) : base(doc)
   {
     return;
   }

   public override XmlElement GetIdElement(XmlDocument doc, string id)
   {
     if (String.Compare(id, this.KeyInfo.Id, StringComparison.OrdinalIgnoreCase) == 0)
       return this.KeyInfo.GetXml();
     else
       return base.GetIdElement(doc, id);
   }
}

This is applicable to web services, since many soap stacks will not allow to customize them to sign the KeyInfo, and if this is required you would need to sign the message like this yourself.

@YaronNaveh

What's next? get this blog rss updates or register for mail updates!

Saturday, January 29, 2011

gSoap and Wcf Routing Services are not friends

@YaronNaveh

One common patterns with web services is the router service. It can hide routing logic from the client or help with load balancing. Wcf 4 ships with libraries and samples to help build such a router very quickly.

Stephen Liedig had notified me recently on a problem which happens when a gSoap client calls a Wcf router. gSoap is a very popular CPP web services stack.

This was the deployment:



The gSoap client was sending this message to the router:

<soap:Envelope xmlns="http://tempuri.org/" xmlns:p="http://myNs/" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 
<soap:Body>
   
<AddUser>
     
<user xsi:type="p:User">
       
<name>yaron</name>
     
</user>
   
</AddUser>
 
</soap:Body>
</soap:Envelope>


If the router would call the service using the http endpoint, everything would work. If it used the tcp endpoint, it could send the message, but the service implementation would never be called and the service would report this exception:


The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:user. The InnerException message was 'Element 'http://tempuri.org/:user' contains data of the ':User' data contract. The deserializer has no knowledge of any type that maps to this contract. Add the type corresponding to 'User' to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding it to the list of known types passed to DataContractSerializer.'.  Please see InnerException for more details.

Analysis

We need to ask two questions: Why this error happens? Why not with Http?

First we must take a look in the message that the router sent to the service. Here is how it appears in the Wcf log:

<soap:Envelope xmlns="http://tempuri.org/" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 
<soap:Body>
   
<AddUser>
     
<user xsi:type="p:User">
       
<name>yaron</name>
     
</user>
   
</AddUser>
 
</soap:Body>
</soap:Envelope>


Something is missing – the “p” prefix declaration (xmlns:p=http://myNs/) which was present in the message form the client to the router does not appear. This makes this message invalid as it references the undeclared prefix "p" in the "p:User" derived type attribute. But why did the router sent an invalid message when the client sent it a good one? And why not with Http?

Let’s take a look at how Wcf routes messages. We can use the reflector to inspect System.ServiceModel.Routing.SoapProcessingBehavior+SoapProcessingInspector.MarshalMessage():

internal Message MarshalMessage(Message source, Uri to, MessageVersion targetVersion)
{
   
Message message;
   
MessageVersion version = source.Version;
   
if ((version == targetVersion) && !RoutingUtilities.IsMessageUsingWSSecurity(understoodHeaders))
   
{
       
...
       
return source;
   
}
   
...
   
else
   
{
       
XmlDictionaryReader readerAtBodyContents = source.GetReaderAtBodyContents();
       
message = Message.CreateMessage(targetVersion, headers.Action, readerAtBodyContents);
   
}

   
this.CloneHeaders(message.Headers, headers, to, understoodHeadersSet);    
   
return message;
}

Wcf uses this logic:

  • If the input message (from client) and output message (to server) have the same version, and no security is involved, then take the input as is and send it. This explains why the Http case works.
  • Otherwise take the input message body and copy it to a new message. Then copy custom user headers.

Since the “p” prefix is not defined under the body but under the root “envelope” element, this prefix is not sent which makes the message invalid. This explains the netTcp bug.

 

Why Wcf behaves in this way?

Dismissing this behavior as a bug will miss an important discussion. Consider the naïve fix: Parse each attribute under the body and search for the something:something pattern. Since the router has no information about the message semantics it has no way to know if the pattern relates to a prefix or is just a string which looks like this. Moreover doing this would be a huge performance hit, especially with big messages.

 

What should be the correct behavior?

In order to take both the functional and performance requirements into account, I would recommend the following approach:

  • Copy all namespace declarations from the root Envelope and Body elements of the original message into the corresponding elements of the new one (including the default namespace).
  • Make sure not to overload the previous prefixes with new definitions in both elements.

Yes, I can think of a few edge cases where this scheme fails. But a more comprehensive solution would cost in performance.


Conclusion

As it stands now, the default gSoap client fails to call the default Wcf router when protocol bridging is used, which is a real interoperability problem.

And Just to clarify, this web service does not define any derived (=inherited = known) types. For some reason the gSoap default message generation logic uses the xsi:type even on base types. This is  not forbidden since it uses the correct type name, although there is no real reason to do it. Anyway this makes this case quite common and not limited to services with derived types.

And, yes, this is one of the reasons not everyone likes Xml.

@YaronNaveh

What's next? get this blog rss updates or register for mail updates!

Friday, January 14, 2011

Security interop gotcha: Empty signature transformation

@YaronNaveh

Markó had challenged me recently with a security interoperability case between Wcf and WSIT (metro). A mutual X.509 protection was involved, and the Java server was returning the following soap fault:


com.sun.xml.wss.XWSSecurityException: Encryption Policy verification error: Looking for an Encryption Element  in Security header, but found com.sun.xml.wss.impl.policy.mls.SignaturePolicy@3657517

I have not seen this error before, but I have followed the book and asked for a sample working soap message:


<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd" 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" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" xmlns:exc14n="http://www.w3.org/2001/10/xml-exc-c14n#">

 
<S:Header>

   
<To xmlns="http://www.w3.org/2005/08/addressing" wsu:Id="_5006">http://server.com</To>

   
<Action xmlns="http://www.w3.org/2005/08/addressing" wsu:Id="_5005">someAction</Action>

   
<ReplyTo xmlns="http://www.w3.org/2005/08/addressing" wsu:Id="_5004">

     
<Address>http://www.w3.org/2005/08/addressing/anonymous</Address>

   
</ReplyTo>

   
<MessageID xmlns="http://www.w3.org/2005/08/addressing" wsu:Id="_5003">uuid:203aa9ea-eea6-4f96-b0b2-16575411e9a2</MessageID>

   
<wsse:Security S:mustUnderstand="1">

     
<wsu:Timestamp xmlns:ns19="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity" xmlns:ns18="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512" xmlns:ns17="http://www.w3.org/2003/05/soap-envelope" wsu:Id="_3">

       
<wsu:Created>2010-12-22T14:25:10Z</wsu:Created>

       
<wsu:Expires>2010-12-22T14:30:10Z</wsu:Expires>

     
</wsu:Timestamp>

     
<wsse:BinarySecurityToken xmlns:ns19="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity" xmlns:ns18="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512" xmlns:ns17="http://www.w3.org/2003/05/soap-envelope" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" wsu:Id="uuid_87b326f6-54fe-42d5-a3a5-aea7cb12f66b">PO7sWakqK...</wsse:BinarySecurityToken>

     
<xenc:EncryptedKey xmlns:ns19="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity" xmlns:ns18="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512" xmlns:ns17="http://www.w3.org/2003/05/soap-envelope" Id="_5002">

       
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" />

        <
ds:KeyInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="KeyInfoType">

         
<wsse:SecurityTokenReference>

           
<wsse:KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#ThumbprintSHA1" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">TzX5OGaS9Ftsw1t+eGyfBmJblWc=</wsse:KeyIdentifier>

         
</wsse:SecurityTokenReference>

       
</ds:KeyInfo>

       
<xenc:CipherData>

         
<xenc:CipherValue>WJU7hIO...</xenc:CipherValue>

       
</xenc:CipherData>

     
</xenc:EncryptedKey>

     
<xenc:ReferenceList xmlns:ns19="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity" xmlns:ns18="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512" xmlns:ns17="http://www.w3.org/2003/05/soap-envelope">

       
<xenc:DataReference URI="#_5008" />

      </
xenc:ReferenceList>

     
<ds:Signature xmlns:ns19="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity" xmlns:ns18="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512" xmlns:ns17="http://www.w3.org/2003/05/soap-envelope" Id="_1">

       
<ds:SignedInfo>

         
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">

           
<exc14n:InclusiveNamespaces PrefixList="wsse S" />

          </
ds:CanonicalizationMethod>

         
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1" />

          <
ds:Reference URI="#_5003">

           
<ds:Transforms>

             
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">

               
<exc14n:InclusiveNamespaces PrefixList="S" />

              </
ds:Transform>

           
</ds:Transforms>

           
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />

            <
ds:DigestValue>q9TWoUKb25xlel3AIA8bFUkl0hw=</ds:DigestValue>

         
</ds:Reference>

         
<ds:Reference URI="#_5004">

           
<ds:Transforms>

             
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">

               
<exc14n:InclusiveNamespaces PrefixList="S" />

              </
ds:Transform>

           
</ds:Transforms>

           
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />

            <
ds:DigestValue>5Ab1ebo4/FraGgck/A8iDx1J9+I=</ds:DigestValue>

         
</ds:Reference>

         
<ds:Reference URI="#_5005">

           
<ds:Transforms>

             
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">

               
<exc14n:InclusiveNamespaces PrefixList="S" />

              </
ds:Transform>

           
</ds:Transforms>

           
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />

            <
ds:DigestValue>ipPKrD6igfYF3tC10tsurnoHSks=</ds:DigestValue>

         
</ds:Reference>

         
<ds:Reference URI="#_5006">

           
<ds:Transforms>

             
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">

               
<exc14n:InclusiveNamespaces PrefixList="S" />

              </
ds:Transform>

           
</ds:Transforms>

           
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />

            <
ds:DigestValue>cMG2PvlZKNLoAWehtNhxruuRl9Y=</ds:DigestValue>

         
</ds:Reference>

         
<ds:Reference URI="#_5007">

           
<ds:Transforms>

             
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">

               
<exc14n:InclusiveNamespaces PrefixList="S" />

              </
ds:Transform>

           
</ds:Transforms>

           
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />

            <
ds:DigestValue>rRhCJKKcNv2gfh+Hpi62wDEirbE=</ds:DigestValue>

         
</ds:Reference>

         
<ds:Reference URI="#_3">

           
<ds:Transforms>

             
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">

               
<exc14n:InclusiveNamespaces PrefixList="wsu wsse S" />

              </
ds:Transform>

           
</ds:Transforms>

           
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />

            <
ds:DigestValue>OUm9PNmaQgH1CQ4JitIqVX5eY+g=</ds:DigestValue>

         
</ds:Reference>

         
<ds:Reference URI="#uuid_87b326f6-54fe-42d5-a3a5-aea7cb12f66b">

           
<ds:Transforms>

             
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">

               
<exc14n:InclusiveNamespaces PrefixList="wsu wsse S" />

              </
ds:Transform>

           
</ds:Transforms>

           
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />

            <
ds:DigestValue>r5dVsFsteThPivLkpsO+Fme5FIs=</ds:DigestValue>

         
</ds:Reference>

       
</ds:SignedInfo>

       
<ds:SignatureValue>8PLYt+ddRqRV2i7tX2R7PgIEJU=</ds:SignatureValue>

       
<ds:KeyInfo>

         
<wsse:SecurityTokenReference wsu:Id="uuid_ba14a75f-0bee-4ec6-ae77-2646ab277e2f" wsse11:TokenType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKey">

           
<wsse:Reference URI="#_5002" ValueType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKey" />

          </
wsse:SecurityTokenReference>

       
</ds:KeyInfo>

     
</ds:Signature>

   
</wsse:Security>

 
</S:Header>

 
<S:Body wsu:Id="_5007">

   
<xenc:EncryptedData xmlns:ns19="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity" xmlns:ns18="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512" xmlns:ns17="http://www.w3.org/2003/05/soap-envelope" Id="_5008" Type="http://www.w3.org/2001/04/xmlenc#Content">

     
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" />

      <
ds:KeyInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="KeyInfoType">

       
<wsse:SecurityTokenReference wsse11:TokenType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKey">

         
<wsse:Reference URI="#_5002" ValueType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKey" />

        </
wsse:SecurityTokenReference>

     
</ds:KeyInfo>

     
<xenc:CipherData>

       
<xenc:CipherValue>+VCg/Fg9BR...</xenc:CipherValue>

     
</xenc:CipherData>

   
</xenc:EncryptedData>

 
</S:Body>
</S:Envelope>



This looks a little intimidating, but I was actually able to create a Wcf custom binding which generates this exact message:


private static CustomBinding CreateCustomBinding(EndpointAddress address)
{

           
var res = new CustomBinding();

           
SymmetricSecurityBindingElement sec =
               
(SymmetricSecurityBindingElement)SecurityBindingElement.CreateMutualCertificateBindingElement(

                   
MessageSecurityVersion.
                       
WSSecurity11WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10);

           
res.Elements.Add(sec);

           
sec.SetKeyDerivation(false);

           
sec.MessageProtectionOrder = MessageProtectionOrder.SignBeforeEncrypt;

           
sec.DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic128;

           
sec.EndpointSupportingTokenParameters.Signed.Add(sec.EndpointSupportingTokenParameters.Endorsing[0]);

           
sec.EndpointSupportingTokenParameters.Endorsing.Clear();

           
sec.EnableUnsecuredResponse = true;            

           
res.Elements.Add(new CustomTextMessageBindingElement());

           
res.Elements.Add(new HttpTransportBindingElement());

           
return res; }


At this point everything was smelling like roses – I was able to get a real response from the server instead of the error above.

However from a reason unknown to me at the time the Wcf client was rejecting the response with this error:


Message security verification failed.

"End element 'Reference' from namespace 'http://www.w3.org/2000/09/xmldsig#' expected. Found element 'ds:DigestMethod' from namespace 'http://www.w3.org/2000/09/xmldsig#'. Line 1, position 2075."


Here is the relevant response section:


<ds:Signature Id="_1">

   
<ds:SignedInfo>

       ...

       
<ds:Reference URI="_5002">

          
<ds:Transforms />

           <
ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />

           <
ds:DigestValue>2jmj7l5rSw0yVb/vlWAYkK/YBwk=</ds:DigestValue>

       
</ds:Reference>

       ...

   
<ds:SignedInfo>
</ds:Signature>



This is the response signature, which seems pretty straight forward. Why would Wcf throw an exception?

Analysis

If you take a closer look at the response above, you can see how it contains an empty “transforms” element. A non empty one looks like this:



          <Reference URI="#_0">

           
<Transforms>

             
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />

            </
Transforms>

           
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />

            <
DigestValue>jcLdOx6w1477YMoCJR8TJQRJuKw=</DigestValue>

         
</Reference>




This is the framework code that parses the signature transforms element:



        public void ReadFrom(XmlDictionaryReader reader, TransformFactory transformFactory, DictionaryManager dictionaryManager)

       
{

           
reader.MoveToStartElement(dictionaryManager.XmlSignatureDictionary.Transforms, dictionaryManager.XmlSignatureDictionary.Namespace);

           
reader.Read();

 
           
while (reader.IsStartElement(dictionaryManager.XmlSignatureDictionary.Transform, dictionaryManager.XmlSignatureDictionary.Namespace)) 
           
{

               
//handle transformation
            } 
           
reader.MoveToContent();

           
reader.ReadEndElement(); // Transforms              ...
       
}




There are 3 cases that this code is supposed to handle:

  • In case the transforms element has childs (as in the last snippet before the above code), then the Read() call would put the cursor on the first child element (“transform” in singular). The while loop will end at the closing “transforms” element which is aligned with the following ReadEndElement() call.
  • In case there are no childs but there is a separate closing element:




              <Reference URI="#_0">

               
    <Transforms>

               
    </Transforms>

               
    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />

              </
    Reference> 



    then Read() would put us on the closing element, and since we will not enter the loop ReadEndElement will be right again.
  • In case of an empty element:



     <Reference URI="#_0">

                 
    <Transforms />

     
                <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />

      </
    Reference> 



    the Read() would advance us behind the transformations tag! The next tag is the opening DigestMethod which of course fails for ReadEndElement().


So the case of an empty transforms element is responsible for the interoperability bug.

Workaround

If you are in a position to change the output of the Java server then various workarounds may apply. But what if you can’t?

The only real solution seems to be implementing the relevant ws-security portion by yourself. Here I show how to do the bare minimum of such a scheme – use the wcf security stack for the sending of the message and decrypting the response by ourselves. The missing piece will be the signature validation, which is very important in production but we can continue with development even without it as at least we get a readable response.

My solution involves using a custom message encoder. I have used the Wcf SDK message encoder sample as a template. When the response comes back to the encoder it decrypts it and remove the security from it before passing it to the upstream channel.  To make sure the security channel will not fail (due to the now unsecured response) we verify our binding has EnableUnsecuredResponse  set to true.

Here is how the encoder code looks like:

        public override Message ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType)

       
{
           
var sr = new StreamReader(stream);

           
var wireResponse = sr.ReadToEnd();

           
var logicalResponse = GetDecryptedResponse(wireResponse);

           
logicalResponse = String.Format(

                    @"<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">


                            <s:Body>


                                {0}


                            </s:Body>


                        </s:Envelope>"
,

                   
logicalResponse);

           
XmlReader reader = XmlReader.Create(new StringReader(logicalResponse));

           
return Message.CreateMessage(reader, maxSizeOfHeaders, MessageVersion.Soap11);
       
}

       
private string GetDecryptedResponse(string encryptedResponse)
       
{
           
var doc = new XmlDocument();

           
doc.LoadXml(encryptedResponse);

           
var cipherNode = doc.SelectSingleNode("//*[local-name(.)='Body']//*[local-name(.)='CipherValue']");

           
byte[] cypher = Convert.FromBase64String(cipherNode.InnerText);

           
byte[] key = GetEncryptingKey();

           
byte[] iv = GetIV(cypher);

           
var AesManagedAlg = new AesCryptoServiceProvider();

           
AesManagedAlg.KeySize = 128;

           
AesManagedAlg.Key = key;

           
AesManagedAlg.IV = iv;

           
var body = ExtractIVAndDecrypt(AesManagedAlg, cypher, 0, cypher.Length);

           
return UTF8Encoding.UTF8.GetString(body);
       
}



So we find the ciphered portion of the message and decrypt it. We then use it to construct a new unsecured message which we will pass along in the pipeline. Here this is done in a dirty way which hard codes the soap11 envelope.

But where do we get the security key to decrypt the message from?

This key is encrypted in our request:


      <e:EncryptedKey Id="uuid-7674d043-9a35-495d-a4dc-27daee955237-1" xmlns:e="http://www.w3.org/2001/04/xmlenc#">

           …


       
<e:CipherData>

         
<e:CipherValue>HQ0UzEZnc9lq...</e:CipherValue>

       
</e:CipherData>       

      </e:EncryptedKey>


We cannot decrypt it since it is encrypted with the server public key and we do not have the private one. However the security channel must keep reference to this key somewhere so that it will be able to decrypt the response (which it would do anyway unless the empty transformation bug). We need to use some reflection tricks to get the session key:


        private void SaveEncryptionKey(Message message)
       
{
           
var secHeaderType = message.GetType().GetField("securityHeader", 
                                             
BindingFlags.NonPublic | BindingFlags.Instance);

           
var secHeader = secHeaderType.GetValue(message);

           
var encTokenType = secHeader.GetType().BaseType.BaseType.GetField("encryptingToken", 
                                             
BindingFlags.NonPublic | BindingFlags.Instance);

           
var token = (WrappedKeySecurityToken)encTokenType.GetValue(secHeader);

           
var securityKey = (InMemorySymmetricSecurityKey)token.SecurityKeys[0];

           
var symmetricKey = securityKey.GetType().GetField("symmetricKey", 
                                              
BindingFlags.NonPublic | BindingFlags.Instance);

           
this.key = (byte[])symmetricKey.GetValue(securityKey);
       
}



The full source code is here. It will not run since for privacy reasons I did not use the real wsdl / certificates. But it can be used as a reference in case you encounter a similar situation.

@YaronNaveh

What's next? get this blog rss updates or register for mail updates!