• A Tryst With SOA

    WCF Security Interoperability Guidelines – 2 : Reference Style of a Primary Signing Token inside a request

    • 2 Comments

    In my last post, I had discussed the supported ways to reference a primary signing token inside a response. We will focus on request this time round. Consider a scenario where we have a non-.NET client consuming a WCF service, security requirements of the underlying communication remaining the same as my last post:

     

                     -          Security is implemented at the message layer with either parties communicating over HTTP.

                     -          Both client and service mutually authenticates via X509 certificates.

                     -          Additional client authentication in terms of a supporting username token.

     

    Sometimes we might choose to pass only a pointer to X509Certificate (may be its issuer name and serial number) instead of the whole certificate encapsulated within a binary security token (BST). In such a case, incoming security header to a WCF service will look as follows:

     

         <o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">

              <u:Timestamp u:Id="uuid-31bf49bd-df18-41d4-9c9d-559e56a99045-1">

                   <u:Created>2010-05-24T21:48:37.010Z</u:Created>

                   <u:Expires>2010-05-24T21:53:37.010Z</u:Expires>

              </u:Timestamp>

              <o:UsernameToken u:Id="uuid-86024b35-af0f-40f9-8ba7-9e73381a9599-1">

                   <o:Username>

                            <!-- Removed-->

                   </o:Username>

                   <o:Password>

                           <!-- Removed-->

                   </o:Password>

              </o:UsernameToken>

              <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">

                        <SignedInfo>

                        .

                        .

                        <SignatureValue>GKClvYiiqAIbcjSnWXJoxuoaziY5Yg…………………</SignatureValue>

                        <KeyInfo>

                             <o:SecurityTokenReference>

                                  <X509Data>

                                      <X509IssuerSerial>

                                           <X509IssuerName>CN=XXXX</X509IssuerName>

                                           <X509SerialNumber>665497663279805416273</X509SerialNumber>

                                      </X509IssuerSerial>

                                  </X509Data>

                             </o:SecurityTokenReference>

                        </KeyInfo>

              </Signature>

         </o:Security>

     

    Such a request will fail at the service end with the following exception:

     

    <Exception>

    <ExceptionType>System.ServiceModel.Security.MessageSecurityException, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>

    <Message>Cannot resolve KeyInfo for verifying signature: KeyInfo 'SecurityKeyIdentifier

        (

        IsReadOnly = False,

        Count = 1,

        Clause[0] = X509IssuerSerialKeyIdentifierClause(Issuer = 'CN=XXXX', Serial = '665497663279805416273')

        )

    ', available tokens 'SecurityTokenResolver

        (

        TokenCount = 0,

        )

    '.</Message>

    <StackTrace>

    at System.ServiceModel.Security.WSSecurityOneDotZeroReceiveSecurityHeader.ResolveSignatureToken(SecurityKeyIdentifier keyIdentifier, SecurityTokenResolver resolver, Boolean isPrimarySignature)

    at System.ServiceModel.Security.WSSecurityOneDotZeroReceiveSecurityHeader.VerifySignature(SignedXml signedXml, Boolean isPrimarySignature, SecurityHeaderTokenResolver resolver, Object signatureTarget, String id)

    at System.ServiceModel.Security.ReceiveSecurityHeader.ProcessPrimarySignature(SignedXml signedXml, Boolean isFromDecryptedSource)

    at System.ServiceModel.Security.ReceiveSecurityHeader.ExecuteFullPass(XmlDictionaryReader reader)

    at System.ServiceModel.Security.ReceiveSecurityHeader.Process(TimeSpan timeout)

    at System.ServiceModel.Security.MessageSecurityProtocol.ProcessSecurityHeader(ReceiveSecurityHeader securityHeader, Message&amp; message, SecurityToken requiredSigningToken, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)

    at System.ServiceModel.Security.AsymmetricSecurityProtocol.VerifyIncomingMessageCore(Message&amp; message, String actor, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)

    at System.ServiceModel.Security.MessageSecurityProtocol.VerifyIncomingMessage(Message&amp; message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)

     

     

    Let us first see why the request fails at the service end. To start with, we need to understand what a System.ServiceModel.Security.SecurityTokenResolver is. This is a utility class used to retrieve security tokens from a message given a key identifier or a key identifier clause. More details about this class are available here. WCF runtime, upon receipt of a request, populates this class with the tokens present inside the security header. Corresponding call stack of such a add operation is as follows:

     

     

    0000000002cde4a8 000007fee4edc845 System.ServiceModel.Security.SecurityHeaderTokenResolver.Add(System.IdentityModel.Tokens.SecurityToken, System.ServiceModel.Security.Tokens.SecurityTokenReferenceStyle, System.ServiceModel.Security.Tokens.SecurityTokenParameters)

    0000000002cde4b0 000007fee403b85d System.ServiceModel.Security.ReceiveSecurityHeader.ReadToken(System.Xml.XmlDictionaryReader, Int32, Byte[], System.IdentityModel.Tokens.SecurityToken, System.String, System.TimeSpan)

    0000000002cde560 000007fee403afb9 System.ServiceModel.Security.ReceiveSecurityHeader.ExecuteFullPass(System.Xml.XmlDictionaryReader)

    0000000002cde5e0 000007fee403ab8b System.ServiceModel.Security.ReceiveSecurityHeader.Process(System.TimeSpan)

    0000000002cde680 000007fee4a41f02 System.ServiceModel.Security.MessageSecurityProtocol.ProcessSecurityHeader(System.ServiceModel.Security.ReceiveSecurityHeader, System.ServiceModel.Channels.Message ByRef, System.IdentityModel.Tokens.SecurityToken, System.TimeSpan, System.ServiceModel.Security.SecurityProtocolCorrelationState[])

    0000000002cde700 000007fee4039e2c System.ServiceModel.Security.AsymmetricSecurityProtocol.VerifyIncomingMessageCore(System.ServiceModel.Channels.Message ByRef, System.String, System.TimeSpan, System.ServiceModel.Security.SecurityProtocolCorrelationState[])

     

     

    Going by the security header mentioned in the beginning, SecurityHeaderTokenResolver.Add (it is also referred to as ‘primaryTokenResolver’) method is called once for UsernameToken present in the request. A pertinent question at this point: what happens to the X509SecurityToken referenced inside <KeyInfo> element? Why are we ignoring that while populating the resolver with security tokens? Such a token is referred to as an ‘outOfBandToken’. We need to handle such a token via a custom outOfBandTokenResolver. WCF runtime does not populate primaryTokenResolver with such outOfBandTokens. Neither does it use an outOfBandTokenResolver while processing a primary signature. You can verify that from ReceiveSecurityHeader.ProcessPrimarySignature method:

     

    private void ProcessPrimarySignature(SignedXml signedXml, bool isFromDecryptedSource)

    {

           this.orderTracker.OnProcessSignature(isFromDecryptedSource);

           this.primarySignatureValue = signedXml.GetSignatureValue();

           if (this.nonceCache != null)

           {

                  CheckNonce(this.nonceCache, this.primarySignatureValue);

           }

           SecurityToken token = this.VerifySignature(signedXml, true, this.primaryTokenResolver, null, null);

     

     

    What we have is a supporting username token inside primary token resolver and a X509IssuerKeyIdentifierClause key identifier passed to System.ServiceModel.Security.SecurityHeaderTokenResolver.ResolveToken method. Unable to resolve the referenced token, this method returns a NULL. Eventually a MessageSecurityException is thrown from WSSecurityOneDotZeroReceiveSecurityHeader.ResolveSignatureToken:

     

     

    if (token == null)

    {

           throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(SR.GetString("UnableToResolveKeyInfoForVerifyingSignature", new object[] { keyIdentifier,  resolver })));

    }

     

     

    So how a primary signing token should be referenced inside a request security header? ONLY way supported by WCF is passing an X509SecurityToken inside a BST. Working security header:

     

           <o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">

               <u:Timestamp u:Id="uuid-4244331a-1cd0-4c16-9385-318ef75a6f81-1">

                       <u:Created>2010-05-24T21:47:47.061Z</u:Created>

                       <u:Expires>2010-05-24T21:52:47.061Z</u:Expires>

               </u:Timestamp>

               <o:BinarySecurityToken>

                        <!-- Removed-->

               </o:BinarySecurityToken>

               <o:UsernameToken u:Id="uuid-f79a13e9-6edd-4bae-aa88-64381cd0324d-1">

                       <o:Username>

                               <!-- Removed-->

                       </o:Username>

                       <o:Password>

                               <!-- Removed-->

                       </o:Password>

               </o:UsernameToken>

               <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">

                       <SignedInfo>

                       .

                       .

                       <SignatureValue>aAP88k0ECFC3ao5S98mSA………..</SignatureValue>

                       <KeyInfo>

                              <o:SecurityTokenReference>

                                      <o:Reference URI="#uuid-f7913e9-6edd-4bae-aa88-64381cd0324d-3"></o:Reference>

                              </o:SecurityTokenReference>

                       </KeyInfo>

                </Signature>

           </o:Security>

     

    Next time you have a non-.NET client talking with a WCF service and you run into a similar security exception, make sure to place your primary signing token within a BST.

  • A Tryst With SOA

    WCF Security Interoperability Guidelines – 1 : Reference Style of a Primary Signing Token inside a response

    • 1 Comments

    Making things work with a disparate entity is always a difficult task to accomplish. Things are no different in WCF Interoperability space. While significant developments have been made over the years to standardize various protocols and develop products which adhere to these protocols, differences are always there. Through the course of this article and subsequent ones, I will present certain painful interoperability issues which we have encountered in WCF support life cycle so far. Note that these are simple guidelines of what works and what does not as it stands today. Workarounds will be presented for some, while the rest are by design.

     

    Such issues are always manifested by either communicating parties’ inability to process a request / response generated by their counterpart. It’s always either ‘a WCF service not able to process a request generated by a non - WCF client’ or ‘a WCF client notable to process a response received from a non – WCF web service’. My intention is to pick a couple such scenarios and perform a post – mortem.

     

    Scenario: Reference Style of a Primary Signing Token inside a response

     

    Consider a WCF client consuming a non – .NET web service. Following are the security attributes of the underlying communication:

                    -     Security is implemented at the message layer with either parties communicating over HTTP.

                    -     Both client and service mutually authenticates via X509 certificates.

                    -     Additional client authentication in terms of a supporting username token.

     

    One of the key requirements is to understand how any given security scenario can be represented in WCF world. Given the above points, we need to implement a custom binding (no standard binding can meet all the above requirements) which looks as follows:

     

    HttpTransportBindingElement http = new HttpTransportBindingElement();

     

    X509SecurityTokenParameters initiatorToken = new X509SecurityTokenParameters();

    initiatorToken.RequireDerivedKeys = false;

               

    X509SecurityTokenParameters receipientToken = new X509SecurityTokenParameters();

    receipientToken.RequireDerivedKeys = false;

               

    AsymmetricSecurityBindingElement asbe = new AsymmetricSecurityBindingElement(receipientToken,initiatorToken);

    asbe.IncludeTimestamp = true;

    asbe.RequireSignatureConfirmation = false;

               

    UserNameSecurityTokenParameters username = new UserNameSecurityTokenParameters();

    username.InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient;

    username.RequireDerivedKeys = false;

     

    asbe.EndpointSupportingTokenParameters.Signed.Add(username);

     

    TextMessageEncodingBindingElement tmbe = new TextMessageEncodingBindingElement(MessageVersion.Soap11,System.Text.Encoding.UTF8);

     

    return new CustomBinding(tmbe, asbe, http);

     

    Let’s now get to the issue. With this binding in place, a call to a non - .NET web service fails with the following exception:

     

    <Exception>

              <ExceptionType>System.Xml.XmlException, System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>

              <Message>There was an error deserializing the security key identifier clause XML. Please see the inner exception for more details.</Message>

    <StackTrace>

             at System.ServiceModel.Security.WSSecurityTokenSerializer.ReadKeyIdentifierClauseCore(XmlReader reader)

             at System.IdentityModel.Selectors.SecurityTokenSerializer.ReadKeyIdentifierClause(XmlReader reader)

             at System.ServiceModel.Security.XmlDsigSep2000.KeyInfoEntry.ReadKeyIdentifierCore(XmlDictionaryReader reader)

             at System.ServiceModel.Security.WSSecurityTokenSerializer.ReadKeyIdentifierCore(XmlReader reader)

             at System.IdentityModel.Selectors.SecurityTokenSerializer.ReadKeyIdentifier(XmlReader reader)

             at System.IdentityModel.Signature.ReadFrom(XmlDictionaryReader reader, DictionaryManager dictionaryManager)

             at System.IdentityModel.SignedXml.ReadFrom(XmlDictionaryReader reader)

             at System.ServiceModel.Security.WSSecurityOneDotZeroReceiveSecurityHeader.ReadSignatureCore(XmlDictionaryReader signatureReader)

             at System.ServiceModel.Security.ReceiveSecurityHeader.ReadSignature(XmlDictionaryReader reader, Int32 position, Byte[] decryptedBuffer)

             at System.ServiceModel.Security.ReceiveSecurityHeader.ExecuteFullPass(XmlDictionaryReader reader)

             at System.ServiceModel.Security.StrictModeSecurityHeaderElementInferenceEngine.ExecuteProcessingPasses(ReceiveSecurityHeader securityHeader, XmlDictionaryReader reader)

             at System.ServiceModel.Security.ReceiveSecurityHeader.Process(TimeSpan timeout)

             .

             .

             .

     

    A look at the above stack trace shows that processing of response security header throws an exception. How do we know that? Note the highlighted last line of the exception stack trace. Following is the inner exception:

     

    <InnerException>

             <ExceptionType>System.ArgumentNullException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>

             <Message>Value cannot be null.

             Parameter name: certificate</Message>

    <StackTrace>

             at System.IdentityModel.Tokens.X509RawDataKeyIdentifierClause.GetRawData(X509Certificate certificate)

             at System.IdentityModel.Tokens.X509RawDataKeyIdentifierClause..ctor(X509Certificate2 certificate)

             at System.ServiceModel.Security.XmlDsigSep2000.X509CertificateClauseEntry.ReadKeyIdentifierClauseCore(XmlDictionaryReader reader)

             at System.ServiceModel.Security.WSSecurityTokenSerializer.ReadKeyIdentifierClauseCore(XmlReader reader)

    </StackTrace>

    </InnerException>

     

    So we are failing to retrieve key identifiers of primary signing token from the received signature. Let’s have a look at the received primary signing token:

     

    <ds:KeyInfo>

        <ds:X509Data>

            <ds:X509IssuerSerial>

                <ds:X509IssuerName>XXXXXXXXXXXXXXXXXXX </ds:X509IssuerName>

                <ds:X509SerialNumber>

                           74990459506171493075258200981073178724

                </ds:X509SerialNumber>

            </ds:X509IssuerSerial>

        </ds:X509Data>

    </ds:KeyInfo>

     

    So what’s wrong with this? Why are we trying to construct a System.IdentityModel.Tokens.X509RawDataKeyIdentifierClause (based on the stack trace shared above) when the service has passed an IssuerSerial of the signing certificate? Let me explain a bit of what happens at the background. Take a look at the reflected code of System.ServiceModel.Security.WSSecurityTokenSerializer.ReadKeyIdentifierClauseCore:

     

          protected override SecurityKeyIdentifierClause ReadKeyIdentifierClauseCore(XmlReader reader)

          {

                  XmlDictionaryReader reader2 = XmlDictionaryReader.CreateDictionaryReader(reader);

                  for (int i = 0; i < this.keyIdentifierClauseEntries.Count; i++)

                  {

                   KeyIdentifierClauseEntry entry = this.keyIdentifierClauseEntries[i];

                          if (entry.CanReadKeyIdentifierClauseCore(reader2))

                          {

                             try

                             {

                                  return entry.ReadKeyIdentifierClauseCore(reader2);

                             }

     

    Couple of points to note at this point:

     

                 1.    System.ServiceModel.Security.WSSecurityTokenSerializer+KeyIdentifierClauseEntry: It represents core identifying properties of an X509Token. This is derived by a number of classes, one being  System.ServiceModel.Security.XmlDsigSep2000.X509CertificateClauseEntry. This class is of interest to us since it appears in the failing inner exception stack trace.

               2.    KeyIdentifierClauseEntry.CanReadKeyIdentifierClauseCore method: this method determines which KeyIdentifierClauseEntry class is used to retrieve key identifier. Reflected code of the method:

     

          public virtual bool CanReadKeyIdentifierClauseCore(XmlDictionaryReader reader)

          {

              return reader.IsStartElement(this.LocalName, this.NamespaceUri);

          }

     

    Consider the iteration when the value of entry variable in the above ReadKeyIdentifierClauseCore method is set to X509CertificateClauseEntry. At the same time:

     

                     a.     reader is pointing to <ds:X509Data>  element

                     b.     this.LocalName inside CanReadKeyIdentifierClauseCore is set to X509Data

     

    CanReadKeyIdentifierClauseCore returns true and control gets into X509CertificateClauseEntry.ReadKeyIdentifierClauseCore. Inside this method, we try to generate X509RawDataKeyIdentifierClause which fails. From this we can conclude that CanReadKeyIdentifierClauseCore method should not return TRUE in the first place. While the value of X509CertificateClauseEntry.LocalName is predefined (X509Data), it means that there is something wrong with the structure of primary token. There are 2 possibilities here:

     

                    a.     Either <ds:X509Data> should not be the immediate child element of <ds:KeyInfo>.

                    b.     If <ds:X509Data> is the immediate child of <ds:KeyInfo>, the key identifier clauses should be different.

      

    So how a primary signing token should be referenced? WCF supports 4 ways of referencing a primary X509 token. This is defined by System.ServiceModel.Security.Tokens.X509KeyIdentifierClauseType which is an enumeration type. Following are its possible values (SubjectKeyIdentifier is based on subject key identifier extension and is not explained below):

     

     

            1.     Any / IssuerSerial (default)    

     

            <KeyInfo>

              <o:SecurityTokenReference>

                      <X509Data>

                         <X509IssuerSerial>

                               <X509IssuerName>CN=XXXXX</X509IssuerName>

                               <X509SerialNumber>

                                         -128364659718044525824792132

                              </X509SerialNumber>

                         </X509IssuerSerial>

                     </X509Data>

             </o:SecurityTokenReference>

            </KeyInfo>

     

            2.     RawDataKeyIdentifier

     

            <KeyInfo>

             <X509Data>

                     <X509Certificate>MIICAjCCAWugAwIBAgIQwZyW5YOCXZxHg1MBV2CpvDANBgkhkiG9w0BAQnEdD9tI7IYAAoK4O+35EOzcXbvc4Kzz7BQnulQ=</X509Certificate>

             </X509Data>

            </KeyInfo>

     

            3.     Thumbprint

     

            <KeyInfo>

              <o:SecurityTokenReference>

                           <o:KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security1.1#ThumbprintSHA1">9JscCwWHk5IvR/6JLTSayTY7M=</o:KeyIdentifier>

              </o:SecurityTokenReference>

            </KeyInfo>

     

     

    Hence to answer (a) and (b):-

     

                    a.     When passing <X509IssuerSerial>, <X509Data> element should be child to a <o:SecurityTokenReference> element, not <KeyInfo> element.

                    b.     When <X509Data> is passed as immediate child element to <KeyInfo>, its child element should be <X509Certificate>. This proves why we see X509CertificateClauseEntry in the exception stack trace.

     

    Now that we know why the response failed at the client end, let’s talk about a workaround. A simple workaround will be to modify the incoming response using a custom channel. I will not get into the intricacies of how to write a custom channel in this blog article. That is available under ‘channel extensibility samples’ here.  Going by the primary signing token shared earlier:

     

            <ds:KeyInfo>

                 <o:SecurityTokenReference> ----------------------- INSERT THIS ELEMENT

                             <ds:X509Data>

                                    <ds:X509IssuerSerial>

                                               <ds:X509IssuerName>XXXXXXXXXXXXXXXXXXX </ds:X509IssuerName>

                                               <ds:X509SerialNumber>

                                                         74990459506171493075258200981073178724

                                               </ds:X509SerialNumber>

                                   </ds:X509IssuerSerial>

                             </ds:X509Data>

                </o:SecurityTokenReference>

            </ds:KeyInfo>

     

    Following are the steps achieve that (assuming that the message is only signed):

     

    A.Operate on a message object to insert necessary element as highlighted above

     

    private Message OnReceiveMessage(Message response)

    {

     

         MemoryStream responseStream = new MemoryStream();

         MessageBuffer responseBuffer = response.CreateBufferedCopy(Int32.MaxValue);

         responseBuffer.WriteMessage(responseStream);

         responseStream.Position = 0;

         byte[] respArr = responseStream.ToArray();

     

         string responseMessage = System.Text.Encoding.ASCII.GetString(respArr);

     

         XmlDocument resDom = new XmlDocument();

         resDom.LoadXml(responseMessage);

     

         /*

         * Fetch <X509Data> element from the received response message

         */

         XmlNodeList x509DataElement = resDom.GetElementsByTagName("X509Data");

         bool modificationRequired = true;

     

         if (x509DataElement[0].FirstChild.Name.Equals("X509Certificate"))

         {

       return response;

         }

         else

         {

       modificationRequired = true;

         }

     

         if (modificationRequired)

         {

       XmlElement securityTokenReference = resDom.CreateElement("o", "SecurityTokenReference", WSSecurityURI);

       securityTokenReference.AppendChild(x509DataElement[0]);

     

       XmlNodeList keyInfoElement = resDom.GetElementsByTagName("KeyInfo");

       keyInfoElement[0].AppendChild(securityTokenReference);

         }

               

         string modifiedResponseMessageStr = resDom.InnerXml;

     

         byte[] modifedResponseMessageInBytes = System.Text.Encoding.UTF8.GetBytes(modifiedResponseMessageStr);

     

         XmlDictionaryReaderQuotas quotas = new XmlDictionaryReaderQuotas();

     

         XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(modifedResponseMessageInBytes, quotas);

     

         Message newResponseMessage = Message.CreateMessage(reader, 2147483647, response.Version);

     

         MessageBuffer modifiedResponseBuffer = newResponseMessage.CreateBufferedCopy(Int32.MaxValue);

     

         return modifiedResponseBuffer.CreateMessage();  

    }

     

    B. Call ReceiveMessage from IRequestChannel.Request method implementation

     

    public Message Request(Message message)

    {

         Message retmsg = m_InnerRequestChannel.Request(message);

               

         MessageBuffer responseBufferCopy = retmsg.CreateBufferedCopy(Int16.MaxValue);

     

         return OnReceiveMessage(responseBufferCopy.CreateMessage());

    }

     

    With that we know what X509ReferenceStyles are supported by WCF and how to work around any mismatch arising out of it. Next up we will see interoperability issues arising from <o:BinarySecurityToken> present within a request security header.

  • A Tryst With SOA

    SSL Offloader Using WCF 4.0 Routing Service

    • 1 Comments

    WCF Routing Service is a new feature introduced with WCF 4.0. While this article is not about explaining what it is (complete details about WCF Routing Service is available here), I must admit that this new feature is simply awesome. It has made pain points like protocol bridging and handling communication exceptions a no brainer for any developer. Just get your configurations correct, everything else will be taken care of by the routing service. Through this example, I have covered the following features of routing service:

                    -      Content Based Routing

              -    Error Handling

              -    Protocol Bridging

               -  Custom Filters

     A SSL Offloader is an intermediary device which accepts an incoming HTTPS request, offloads SSL and forwards HTTP traffic to backend servers. Certain SSL Offloaders even do the work of authenticating a client. With the work of authentication delegated away, service performance improves. In this example, SSL Offloader does the following work:

                    1.       Receives a HTTPS request from a client.

                    2.       Authenticates the client.

                    3.       Offloads SSL and forwards HTTP request to backend available service.

                    4.       Load balances incoming requests in round robin manner between 2 backend services.

     Now let’s get into implementation specifics. In this example, we have 2 backend services: Primary Calculator Service and Secondary Calculator Service. Contracts of both these services are identical to enable round robin load balancing of requests in between these services. Following are their bindings and endpoints:

     Service Binding

           <basicHttpBinding>

            <binding>

              <security mode="None" />

            </binding>

          </basicHttpBinding>

    Primary Service Endpoint

             <host>

              <baseAddresses>

                <add baseAddress="http://localhost:8080/PrimaryCalculatorService"/>

              </baseAddresses>

            </host>

             <endpoint address=""

                      binding="basicHttpBinding"

                      contract="ICalculatorContract" />

    Endpoint of secondary service is identical, only hosted on port 8081. Note the missing bindingConfiguration property in the endpoint definition. This is a new feature of WCF 4.0. As long as there is a single binding definition of a given type (in this example basicHttpBinding), there is NO need to link that binding to an endpoint. That binding is considered as the standard binding. Hence no name property associated with the binding definition either. If you want to define another binding (say this time with security enabled), you need to give it a name and associate it with a endpoint using bindingConfiguration. There is one another cool feature of WCF 4.0 which I will like to mention here: Standard Endpoints. More details are available here. I have used it to define my mex endpoint.

    Standard MEX Endpoint

            <endpoint isSystemEndpoint="true"

                      kind="mexEndpoint"

                      address="mex" />

    Before I get into the SSL Offloader, let’s take a look at the client application first. Remember that a client needs to connect over HTTPS and pass some authentication tokens. In this example, we will use UsernameToken. Following is the client binding:

          <customBinding>

            <binding>

              <security authenticationMode="UserNameOverTransport"

                        includeTimestamp="false"

                        requireDerivedKeys="false" />

              <httpsTransport />

            </binding>

          </customBinding>

    Now let’s get into SSL Offloader implementation. To reiterate the requirements here:

                                                  1.       Communication in between the client and SSL Offloader is over HTTPS.

                                                  2.       Communication in between the SSL Offloader and calculator service is over HTTP.

                                                  3.       Client authentication is done by the SSL Offloader.

    Keeping these in mind, following is the offloader binding:

           <wsHttpBinding>

            <binding>

              <security mode="Transport">

                <transport clientCredentialType="None" />

              </security>

            </binding>

          </wsHttpBinding>

    Why is the clientCredentialType set to ‘none’? That’s where the custom filter comes into picture. It not only routes an incoming request in a round robin fashion, but also authenticates the client. Hence there is no need to implement a custom UserNamePasswordValidator. Note the seamless protocol bridging which happens here. Routing infrastructure takes care of routing a request from a HTTPS protocol to a HTTP protocol without a single line of application code being written.

    Other implementation details (SSL Offloader and client endpoints) are usual. Let’s get to the routing table implementation. This is how the routing table looks like:

        <routing>

          <filters>

            <filter name="primaryFilter" filterType="Custom"

              customType="SSLOffloaderCustomFilter, SSLOffloaderUsingCustomFilterService"

              filterData="group1"/>

            <filter name="secondaryFilter" filterType="Custom"

               customType="SSLOffloaderCustomFilter,SSLOffloaderUsingCustomFilterService"

               filterData="group1" />

          </filters>

           <filterTables>

            <filterTable name="primaryRoutingTable">

              <add filterName="primaryFilter" endpointName="primaryCalc"

                   backupList="primaryBackUp"/>

              <add filterName="secondaryFilter" endpointName="secondaryCalc"

                   backupList="secondaryBackUp"/>

            </filterTable>

          </filterTables>

          <backupLists>

            <backupList name="primaryBackUp">

              <add endpointName="secondaryCalc"/>

            </backupList> 

            <backupList name="secondaryBackUp">

              <add endpointName="primaryCalc"/>

            </backupList>                   

          </backupLists>     

        </routing>

    In addition to the custom filter, note the 2 backupList. It implements exception handling by routing a request to backup endpoint when primary endpoint is unavailable. Simple, isn’t it? To know more about error handling by routing service, refer here.

    Now let’s move to custom filter implementation details. Since I will not get into the basics of implementing a custom filter, I will recommend everyone to review ‘Advanced Filters’ sample available with WCF 4.0 samples. 2 important works are performed by this custom filter:

    Client Authentication

    What we do here is read the security header and validate the username and password tokens.

    private bool ValidateClientCredentials(Message requestMessage)

    {

           MemoryStream stream = new MemoryStream();

           MessageBuffer buffer = requestMessage.CreateBufferedCopy(Int32.MaxValue);

           buffer.WriteMessage(stream);

           stream.Position = 0;

           byte[] requestByte = stream.ToArray();

           string requestStr = System.Text.Encoding.UTF8.GetString(requestByte);

           XmlDocument doc = new XmlDocument();

           doc.LoadXml(requestStr);

           XmlNodeList securityNode = doc.GetElementsByTagName("o:Security");

           if (securityNode.Count == 0)

                  throw new InvalidOperationException("No security header is available in the incoming request");

           XmlNodeList usernameNode = doc.GetElementsByTagName("o:Username");

           XmlNodeList passwordNode = doc.GetElementsByTagName("o:Password");           

           if (usernameNode.Count == 0 || passwordNode.Count == 0)

           {

                  throw new InvalidOperationException("No username or password present in the incoming request");

           }

           else if( string.IsNullOrEmpty(usernameNode[0].InnerText) || string.IsNullOrEmpty(passwordNode[0].InnerText))

           {

                  throw new ArgumentNullException("Username / Password cannot be null or empty");

           }

           else if(usernameNode[0].InnerText.Equals("Microsoft") && passwordNode[0].InnerText.Equals("Microsoft"))

           {  

                  return true;

           }               

           return false;

    } 

    This method is called from all implementations of IMessageFilterTable<FilterData>.GetMatchingXXX methods. One such implementation is as follows:  

    public bool GetMatchingFilter(System.ServiceModel.Channels.MessageBuffer messageBuffer, out MessageFilter filter)

    {

           filter = null;

           if(ValidateClientCredentials(messageBuffer.CreateMessage()))

           {

                  filter = GetNextAvailableFilter();

           } 

           return (filter!= null);

    }

    Since we are operating on the message body, we need to set the following highlighted property to false inside service behaviors: 

    <routing filterTableName="primaryRoutingTable" routeOnHeadersOnly="false"/>

    This makes sure that only those variants of GetMatchingXXX are called by runtime whose 1st parameter is a MessageBuffer type. This ensures that we do not directly operate on an incoming Message object since a Message object is a read – once type. 

    Round Robin Load Balancing

    This is implemented inside the following private method inside filter table inner class: 

    List<SSLOffloaderCustomFilter> filters = new List<SSLOffloaderCustomFilter>();

    int filterIndex = 0;

    private SSLOffloaderCustomFilter GetNextAvailableFilter()

    {

           SSLOffloaderCustomFilter returnedFilter = null;

           int currentIndex = filterIndex;

           for (int i = currentIndex + 1; i < filters.Count; i++)

           {

                  returnedFilter = filters[i];

                  filterIndex = i;

           } 

           if (returnedFilter == null)

           {

                  for (int i = 0; i < currentIndex + 1; i++)

                  {

                         returnedFilter = filters[i];

                         filterIndex = i;

                         break;

                  }

           }

           return returnedFilter;

    } 

    As the name of the method indicates, all I am doing here is to choose one of the filters in a round robin fashion. With this I have covered the important implementation details which went behind developing this SSL Offloader example. Hope this demonstrates how awesome WCF 4.0 Routing Service is.

     

  • A Tryst With SOA

    Manipulate a WCF request / response using a custom encoder

    • 0 Comments

    Message Encoders are an interesting component within WCF channel stack. It’s primary job is to transform Message instances to and from the wire. While it has an independent existence within a WCF binding stack in the form of a Message Encoding binding element, in essence it is closely integrated with the underlying transport layer. This is unlike to any other binding elements, example a security binding element. Check out this MSDN article on message encoders for more details.

     

    Today I will concentrate my discussion on custom encoders. I will present a very simple example to demonstrate the power of a custom encoder to resolve a seemingly complex scenario. For those who are yet to work with custom encoders, you can find more details here. There is also a MSDN sample which demonstrates a custom text encoder.

     

    Consider a scenario where you are trying to consume a non - .NET web service from a WCF client application over MTOM. The client is configured with the following binding:

     

    <customBinding>

            <binding name="CustomTransferUsingMTOM">

              <mtomMessageEncoding maxBufferSize="65536"

                                   messageVersion="Soap12WSAddressing10" />

              <httpTransport authenticationScheme="Anonymous"

                              maxReceivedMessageSize="2147483647"

                              maxBufferSize="2147483647"

                              transferMode="Buffered" />

            </binding>

          </customBinding>

     

    Ideally things should work if both the participating components have identical implementation of the same standards. More often than not, things don’t follow the ideal path. In this scenario, a MTOM encoded response from the web service fails with the following exception:

     

    <ExceptionType>System.Xml.XmlException, System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>

    <Message>Root MIME part must contain non-zero length value for 'charset' parameter in Content-Type header.</Message>

    <StackTrace>

    at System.Xml.XmlMtomReader.ReadRootContentTypeHeader(ContentTypeHeader header, Encoding[] expectedEncodings, String expectedType)

    at System.Xml.XmlMtomReader.Initialize(Stream stream, String contentType, XmlDictionaryReaderQuotas quotas, Int32 maxBufferSize)

    at System.Xml.XmlMtomReader.SetInput(Stream stream, Encoding[] encodings, String contentType, XmlDictionaryReaderQuotas quotas, Int32 maxBufferSize, OnXmlDictionaryReaderClose onClose)

    at System.Xml.XmlDictionaryReader.CreateMtomReader(Stream stream, Encoding[] encodings, String contentType, XmlDictionaryReaderQuotas quotas, Int32 maxBufferSize, OnXmlDictionaryReaderClose onClose)

    at System.ServiceModel.Channels.MtomMessageEncoder.TakeStreamedReader(Stream stream, String contentType)

    </StackTrace>

     

    Let’s have a look at the incoming response to get a better understanding of things:

     

    HTTP/1.0 200 OK

    Content-Type: multipart/related;

    boundary="----=_Part_1_31588834.1260459496767";

    start="<88a8d13e5330b97c125391cf9ba>"; start-info="application/soap+xml";

    type="application/xop+xml"; charset=UTF-8

    MIME-Version: 1.0

    Content-Length: 309045

     

     

           ------=_Part_1_31588834.1260459496767

          content-type: application/xop+xml; type="application/soap+xml"

          content-transfer-encoding: binary

          content-id: <88a8d13e5330b97c125391cf9ba>

     

          <?xml version="1.0" encoding="UTF-8"?>

          <SOAP-ENV:Envelope xmlns:SOAP-ENV="

                  …………………………

    </SOAP-ENV:Envelope>

     

          ------=_Part_1_31588834.1260459496767

          content-type: application/octet-stream

          content-transfer-encoding: binary

          content-id: <19e0ed854e021b83125391cf9bb>

          

           Base64Encoded data goes here

     

    From the highlighted error message above, one can understand that the issue is with missing ‘charset’ parameter in Content-Type header. Check the root MIME header portion (highlighted above). Even though a charset parameter is present as part of HTTP header, it is indeed missing in the root MIME header. WCF does not like that. There are 2 ways to go about here: either we modify the service to generate a working response or we customize the WCF client so that it accepts this response. We will discuss about the later option. That’s where a custom encoder comes into picture.

     

    With the help of a custom encoder we can modify the response to accommodate a charset parameter in the correct place. This is not possible either with a message inspector or a custom channel as either works on a Message instance. Note the error is thrown before a Message instance is created by MTOM encoder. Primary customization will go inside ReadMessage method:

     

    public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)

     

    Algorithm we need to follow out here:

    -          Convert ArraySegment<byte> buffer into a string

    -          Manipulate generated string to accommodate the charset parameter

    -           Recreate ArraySegment<byte> and pass it back to the mtomencoder by calling MtomMessageEncoder.ReadMessage.

     

    Things become interesting if you have a binary data imbedded in the response, for example an image. In that case the first step becomes critical. Ideally we will use the following code to generate a string:

     

    byte[] incomingResponse = buffer.Array;

    string str = System.Text.Encoding.UTF8.GetString(incomingResponse);

     

    This will work for a text attachment, not for an image. This will not generate any runtime errors though. WCF client will be able to process the response (which means that the issue with ‘charset’ parameter is resolved). However the downloaded binary image, present in the response, will be corrupt. That is because of the encoding format used in the above line. We cannot convert a base64encoded image into string via UTF-8 encoding. If we want to retain the encoding, we need to use Convert.ToBase64String. In that case, we will not be able to perform any string manipulation operations.

     

    I decided to overcome this catch22 situation by converting the first ‘n’ bytes of an incoming response into string, instead of the entire response. This makes sense in this particular scenario since the size of the response till root MIME header will more or less be constant. Hence I wrote the following code piece which does the job of inserting a ‘charset’ parameter into a ‘Content-Type’ header:

     

    //Convert the received buffer into a string

    byte[] incomingResponse = buffer.Array;

     

    //read the first 500 bytes of the response

    string strFirst500 = System.Text.Encoding.UTF8.GetString(incomingResponse, 0, 500);

               

    /*

    * Check the last occurance of 'application/xop+xml' in the response. We check for the last

    * occurrence since the first one is present in the Content-Type HTTP Header. Once found,

    * append charset header to this string

    */

    int appIndex = strFirst500.LastIndexOf("application/xop+xml");

    modifiedResponse = strFirst500.Insert(appIndex + 19, "charset=utf-8");

     

    //convert the modified string back into a byte array

    byte[] ma = System.Text.Encoding.UTF8.GetBytes(modifiedResponse);

     

    //integrate the modified byte array back to the original byte array

    int increasedLength = ma.Length - 500;

    byte[] newArray = new byte[incomingResponse.Length + increasedLength];

     

    for (int count = 0; count < newArray.Length; count++)

    {

    if (count < ma.Length)

           {

                         newArray[count] = ma[count];

           }

           else

           {

                         newArray[count] = incomingResponse[count - increasedLength];

           }

    }

     

    /*

    * In this part generate a new ArraySegment<byte> buffer and pass it to the underlying MTOM

    * Encoder.

    */

    int size = newArray.Length;

    byte[] msg = manager.TakeBuffer(size);

    Array.Copy(newArray, msg, size);

    ArraySegment<byte> newResult = new ArraySegment<byte>(msg);

     

    With that we have resolved an interop issue using a custom encoder.  Likewise we can extrapolate the usage of a custom encoder to other scenarios where we need to operate on a request / response prior to it being acted upon by an encoder.

     

  • A Tryst With SOA

    Change the way you define your identity with .NET 3.5 SP1

    • 0 Comments

    A service’s endpoint identity is an important entity in an authentication process. Any WCF developer should have a good understanding of what it is, how it is set at the service and client and what part it plays in the overall authentication process. For those in doubt, MSDN article on ‘Service Identity and Authentication’ will surely make a lot of those things clear.

    Today I will concentrate my discussion on how a service’s endpoint identity can be set within a client application. Most common approach will be to configure it within a client configuration file, enclosed within a <identity> element. For the sake of the topic, I will consider the following binding:

         <wsHttpBinding>

            <binding name="Message_Windows">

              <security mode ="Message">

                <message clientCredentialType="Windows"

                         establishSecurityContext="false"

                         negotiateServiceCredential="false"

                         algorithmSuite="Basic128"/>

              </security>

            </binding>

          </wsHttpBinding>

     

    Let’s say that my service is hosted on IIS and the application pool is running under a custom service account. I have a HTTP SPN associated with that service account. Check this blog for more insights into SPN.

     

                    Account name: somedomain\tyler

                    Configured SPN: HTTP/tyler.durden.com

    Going back to how we can provide this information inside a client configuration file:

    <client>

                <endpoint address="http://somedomain.com/SimpleKerberosTest/Service.svc"

                    binding="wsHttpBinding" bindingConfiguration="Message_Windows"

                    contract="ServiceReference.IService" name="WSHttpBinding_IService">

                    <identity>

                        <servicePrincipalName value="HTTP/tyler.durden.com" />

                    </identity>

                </endpoint>

    </client>

     

    All we need now is to instantiate a service proxy within our client application passing to it this endpoint configuration name.

    ServiceClient proxy = new ServiceClient("WSHttpBinding_IService");

    Console.WriteLine(proxy.GetData(10));

     

    Sometimes there is a necessity for us to pass the endpoint address dynamically via the code. That can easily be accomplished by using any of System.ServiceModel.ClientBase<T> constructors which accept a EndpointAddress object as one of its parameters.

    ServiceClient proxy = new ServiceClient("WSHttpBinding_IService", <EndpointAddress object> or remote endpoint as a string input);

     

    This whole scenario works like a charm in .NET 3.5. With the application of SP1, the same scenario fails with the following exception message:

    <ExceptionType>System.ServiceModel.Security.MessageSecurityException, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>

    <Message>The token provider cannot get tokens for target 'http://somedomain.com/SimpleKerberosTest/Service.svc'.</Message>

     

    Inner Exception Details:

     

    <ExceptionString>System.ComponentModel.Win32Exception: The specified target is unknown or unreachable</ExceptionString>

    <NativeErrorCode>80090303</NativeErrorCode>

     

    How does the same environment now start complaining about an unreachable or unknown target? Collect WCF trace for the error and have a look at the determined identity (collect verbose tracing and look for ‘Identity was determined for an EndpointReference’):

    <Identity xmlns="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity">

    <Dns> somedomain.com </Dns>

    </Identity>

    <EndpointReference xmlns="http://www.w3.org/2005/08/addressing">

    <Address> http://somedomain.com/SimpleKerberosTest/Service.svc</Address>

    </EndpointReference>

     

    To our surprise what we see is a DNS generated by the framework. It then appends ‘HOST’ to this value to generate a SPN identity (HOST/somedomain.com). Eventually a HOST SPN is submitted by the client to active directory in request for a service ticket. In my test environment, I do not have any HOST SPNs registered. Hence the error message ‘target is unknown or unreachable’. Usually a HOST SPN will be associated with a computer account. In that scenario, the same request will fail with either of the following error messages:

     

    The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'Negotiate oYG7MIG4oAMKAQGigbAEga1ggaoGCSqGSIb3EgECAgMAfoGaMIGXoAMCAQWhAwIBHqQRGA8yMDA4MDgxNDEwMzk0OVqlBQIDBlSwpgMCASmpJRsjUEFSVFRFU1QuRVhUUkFORVRURVNULk1JQ1JPU09GVC5DT02qRTBDoAMCAQOhPDA6GwRob3N0GzJuZ2ltdHdpcGNybWFwcC5wYXJ0dGVzdC5leHRyYW5ldHRlc3QubWljcm9zb2Z0LmNvbQ

     

    OR

     

    The remote server returned an error: (401) Unauthorized

     

    This proves that with the application of SP1, we do not read the identity element from the client configuration file. This is a DESIGN CHANGE implemented with SP1. However one will encounter this issue only when using a ClientBase<T> which accepts a remote address as one of its parameters.

     

    Good news is that we have a couple very simple workarounds to resolve this issue:

     

    1.       Use ChannelFactory to create a proxy.

     

    IService proxy = null;

    ChannelFactory<IService> factory = null;

     

    EndpointIdentity identity = EndpointIdentity.CreateSpnIdentity("HTTP/tyler.durden.com");

    EndpointAddress address = new EndpointAddress(new Uri("http://somedomain.com/SimpleKerberosTest/Service.svc"), identity);

     

           factory = new ChannelFactory<IService>("WSHttpBinding_IService", address);

     

    proxy = factory.CreateChannel();

    Console.WriteLine(proxy.GetData(10));

                   

    In the above scenario, the only limitation is hard coding of identity inside the code. That can easily be altered by reading the value from a configuration file.

     

    2.       Use a dummy proxy to read the identity value from configuration file and use it to create another proxy object.

     

    ServiceClient proxy = new ServiceClient("WSHttpBinding_IService");

    EndpointIdentity identity = proxy.Endpoint.Address.Identity;

    EndpointAddress address = new EndpointAddress(new Uri("http://somedomain.com/SimpleKerberosTest/Service.svc "), identity);

     

    ServiceClient newProxy = new ServiceClient("WSHttpBinding_IService", address);

     

    Console.WriteLine(proxy.GetData(10));

                   

    This way we can read the identity element from configuration file as well as set endpoint address dynamically via code.

     

    Either ways will result in the client using correct SPN to submit to active directory. Now if you look into WCF trace one more time, you will see correct SPN:

     

    <EndpointReference xmlns="http://www.w3.org/2005/08/addressing">

    <Address> http://somedomain.com/SimpleKerberosTest/Service.svc </Address>

    <Identity xmlns="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity">

    <Spn>HTTP/tyler.durden.com</Spn>

    </Identity>

    </EndpointReference>

     

    While it was pretty simple for me to figure out why was the request failing (due to incorrect SPN), but it took me quite a while to figure out that it was a design change implemented by SP1. Hope this will save others a lot of precious time.

     

  • A Tryst With SOA

    Issued Tokens Renewal : Get your token expiration time set correctly by STS

    • 0 Comments

    Working with issued token is always fun. The whole possibility of making 3 components (client, RP and STS) to work seamlessly excites me to no end. Of late I worked on an interesting token renewal issue where the client was not requesting a new SAML token from the STS, even after expiration. An effect of that was client authentication failure at the service end as the presented token had already expired.

     

    Exception thrown by the service:

     

    <ExceptionType>System.IdentityModel.Tokens.SecurityTokenException, System.IdentityModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>

    <Message>The SamlToken is not time valid. The current time '9/22/2009 6:54:35 PM' is outside the Effective '9/22/2009 6:53:54 PM' and Expiration '9/22/2009 6:54:24 PM' time of the token.</Message>

    <StackTrace>

    at System.IdentityModel.Selectors.SamlSecurityTokenAuthenticator.ValidateTokenCore(SecurityToken token)

    at System.IdentityModel.Selectors.SecurityTokenAuthenticator.ValidateToken(SecurityToken token)

    at System.ServiceModel.Security.ReceiveSecurityHeader.ReadToken(XmlReader reader, SecurityTokenResolver tokenResolver, IList`1 allowedTokenAuthenticators, SecurityTokenAuthenticator&amp; usedTokenAuthenticator)

    at System.ServiceModel.Security.ReceiveSecurityHeader.ReadToken(XmlDictionaryReader reader, Int32 position, Byte[] decryptedBuffer, SecurityToken encryptionToken, String idInEncryptedForm, TimeSpan timeout)

    at System.ServiceModel.Security.ReceiveSecurityHeader.ExecuteFullPass(XmlDictionaryReader reader)

    at System.ServiceModel.Security.ReceiveSecurityHeader.Process(TimeSpan timeout)

    </StackTrace>

     

    Initial thought was that a lifetime of a SAML token was governed by the attributes set inside <saml:Condition> element :

     

               <saml:Conditions NotBefore="2009-09-22T18:53:54.425Z"

                                         NotOnOrAfter="2009-09-22T18:54:24.425Z"></saml:Conditions>

     

    Once de-serialized at the client, the ‘validTo’ and ‘validFrom’ properties of the returned SecurityToken at the client should reflect the same values. However in my case, things were a bit different:

     

    SecurityToken.ValidFrom  à DateTime.UtcNow

           SecurityToken.ValidTo    à DateTime.MaxValue

     

    This was really disconcerting. Where the heck were these values coming from ? A max value for ValidTo meant that the issued token would never be renewed !!! Had to dig into the reflected source code and figure out the root cause of the issue. Take a look at the method responsible for de-serializing a RSTR at the client. Just showing the lines of interest from System.ServiceModel.Security.WSTrust+Driver.CreateRequestSecurityTokenResponse:

     

    DateTime utcNow = DateTime.UtcNow;

    DateTime maxUtcDateTime = DateTime.MaxValue;

    if ((element2.LocalName == this.DriverDictionary.Lifetime.Value) && (element2.NamespaceURI == this.DriverDictionary.Namespace.Value))

    {

       XmlElement element4 = XmlHelper.GetChildElement(element2, "Created",  "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");

       if (element4 != null)

       {

          utcNow = DateTime.ParseExact(XmlHelper.ReadTextElementAsTrimmedString(element4),WSUtilitySpecificationVersion.AcceptedDateTimeFormats, DateTimeFormatInfo.InvariantInfo,DateTimeStyles.None).ToUniversalTime();

       }

       XmlElement element5 = XmlHelper.GetChildElement(element2, "Expires", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");

       if (element5 != null)

       {

          maxUtcDateTime = DateTime.ParseExact(XmlHelper.ReadTextElementAsTrimmedString(element5),WSUtilitySpecificationVersion.AcceptedDateTimeFormats,DateTimeFormatInfo.InvariantInfo,DateTimeStyles.None).ToUniversalTime();

       }

    }

    Note the highlighted elements above. WCF looks for these elements inside an incoming RSTR to set the lifetime of a security token, NOT inside SAML token. If not present, these individual values, ValidTo and ValidFrom, are set to their corresponding values as highlighted earlier. While the solution to this issue is very simple, I will come to that a bit later. First, we need to understand the significance of such an implementation. A saml:Condition defines the lifetime of a SAML token. A lifetime element defines the lifetime of a RSTR received by the client. This makes perfect sense since an issued token can be something other than a SAML token as well. Taking that into light, a token lifetime at the client and hence any subsequent renewal decision should not be tightly coupled with a SAML token lifetime.

     

    Coming back to the solution, just ensure that you define a lifetime element when generating a RSTR inside your custom STS. A lifetime element inside a RSTR looks as follows:

     

    <Lifetime>

    <Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2009-09-22T19:03:44.4697845Z</Created>

    <Expires xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2009-09-22T19:03:54.4697845Z</Expires>

    </Lifetime>

     

    A token with the above lifetime element will be valid for 10 minutes before a renewal request is made by the client to STS.

     

    Now let’s make things a bit more interesting by considering these 2 elements in union. Consider a generated security token with the following values set:

     

    saml:Conditions NotBefore : 2009-09-22T19:03:44.4697845Z

    saml:Conditions NotOnOrAfter : 2009-09-22T19:03:49.4697845Z

    Lifetime Created : 2009-09-22T19:03:44.4697845Z

    Lifetime Expires : 2009-09-22T19:03:54.4697845Z

     

    This means that while the lifetime of a SAML token is set to 5 minutes, that of the RSTR is set to 10 minutes. So the client will not renew the token till the 11th minute, while the SAML token will be invalidated by the service starting from the 6th minute. Make sure the lifetime of the issued security token by STS <= lifetime of SAML token to prevent such a scenario.

     

    There is one very important parameter which I have not spoken of in this article: binding. In this explained scenario, secure sessions were disabled. Things change when we enable that either through binding or use one which implicitly sets up a secure session (netTcpBinding). I will talk about that in my following post. Till then enjoy playing around with issued tokens.

  • A Tryst With SOA

    Mind the case and space when configuring your protocol bindings inside IIS 7.0 to enable net.tcp

    • 0 Comments

    We all are aware that WCF provides a new TCP-based network protocol (net.tcp://) for high performance communication. People who are familiar with this particular protocol will know that for a IIS 7.0 hosted WCF service to utilize this protocol, we need to enable appropriate protocol (namely net.tcp) at the web site and virtual directory level for things to work. Highlighted below is the section where the configurations goes in :

                                                   image

    Coming back to the issue. I was working on a basic WCF client – service scenario where I had a service hosted on IIS 7.0 over net.tcp protocol. Service binding was as follows

    <netTcpBinding>

            <binding name="TransportWithWindows">

              <security mode="Message">

                <message clientCredentialType ="Windows"/>

              </security>

            </binding>

          </netTcpBinding>

    With this simple setting in place, the client was failing with the following exception:

    at System.ServiceModel.Channels.ConnectionUpgradeHelper.DecodeFramingFault(ClientFramingDecoder decoder, IConnection connection, Uri via, String contentType, TimeoutHelper& timeoutHelper)

    at System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.SendPreamble(IConnection connection, ArraySegment`1 preamble, TimeoutHelper& timeoutHelper)

    at System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.DuplexConnectionPoolHelper.AcceptPooledConnection(IConnection connection, TimeoutHelper& timeoutHelper) at System.ServiceModel.Channels.ConnectionPoolHelper.EstablishConnection(TimeSpan timeout) at System.ServiceModel.Channels.ClientFramingDuplexSessionChannel.OnOpen(TimeSpan timeout) at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout) at System.ServiceModel.Channels.ServiceChannel.OnOpen(TimeSpan timeout) at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)

    That connectivity is the issue out here is evident from the above stack trace. However I had my bindings configured at IIS, Net.Tcp Port Sharing Service was running on my machine, ‘WCF Non-HTTP Activation’ was installed. Where do we look in this case ? Back to the same bindings as highlighted in the image above :

                                                               image

    While including the binding information, I had made the mistake of entering it in UPPER CASE instead of lower case (net.tcp). Quite an innocuous error but took quite a while to figure out.

    One other point to keep in their mind while configuring a protocol binding. DO NOT LEAVE ANY SPACE in between individual bindings.

    .NET does not like the following : http, net.tcp. (NOTE A SPACE IN BETWEEN ‘,’ AND ‘n’)

    Client will once again fail with a EndpointNotFoundException, while service activation will fail with the following exception :

    InvalidOperationException: Could not find a base address that matches scheme net.tcp for the endpoint with binding NetTcpBinding. Registered base address schemes are [http,https].]

    Correct : http,net.tcp

    Good news is that this little nuance has been resolved with .NET 4.0. Hope these will save some precious time which can be spent elsewhere.

  • A Tryst With SOA

    Generate proxy code for a web service dynamically

    • 1 Comments

    The first step to consuming a web service is to generate a proxy class from its wsdl. One will agree that with visual studio and a magic tool called wsdl.exe, generating the proxy class is the easiest of things. What if we do away with this concept of a proxy file from a client application? Generate one dynamically using the provided URL of the web service, consume the required web methods and forget about the web service. Took me some time to put the pieces together and come up with a solution.

     

    System.Web.Services.Description” namespace has a couple of classes which allows you to implement this quite easily. Two most important of those: ServiceDecription class and ServiceDescriptionImporter class. Use these in conjunction with System.CodeDom and you will get what you want. I wont go into the details of the functionalities exposed by each of these classes (msdn does a very good job of that). The path which I followed to implement the solution is as follows:

     

    1.     Generate proxy code from the downloaded wsdl file using ServiceDescriptionImporter and System.CodeDom

    2.     Generate a dynamic assembly which contains the compiled proxy code.

    3.     Reflect through the assembly to find out the web methods.

    4.     For each web method, find out its input parameter and return type.

     

    You can easily develop a UI application to display the required information (regarding web methods and parameters) to the end user and let him choose which web method to invoke. I won’t cover that part in this post. First let’s have a look into the code to generate a dynamic assembly. I have commented every important operation within the code and won't explain it again. Please let me know if I have missed out on something and i will provide an explanation for that as well.

    public string[] GenerateProxyAssembly()

    {

    //create a WebRequest object and fetch the WSDL file for the web service

    HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(this.uri);

          request.Credentials = CredentialCache.DefaultCredentials;

          HttpWebResponse response = (HttpWebResponse)request.GetResponse();

    System.IO.Stream stream = response.GetResponseStream();

     

    //read the downloaded WSDL file

    ServiceDescription desc = ServiceDescription.Read(stream);

     

          //find out the number of operations exposed by the web service

          //store the name of the operations inside the string array

          //iterating only through the first binding exposed as

          //the rest of the bindings will have the same number

    int i = 0;

          Binding binding = desc.Bindings[0];

    OperationBindingCollection opColl = binding.Operations;

          foreach (OperationBinding operation in opColl)

          {

                listOfOperations[i++] = operation.Name;

          }

     

    //initializing a ServiceDescriptionImporter object

    ServiceDescriptionImporter importer = new ServiceDescriptionImporter();

     

    //set the protocol to SOAP 1.1

    importer.ProtocolName = "Soap12";

     

          //setting the Style to Client in order to generate client proxy code

          importer.Style = ServiceDescriptionImportStyle.Client;

     

          //adding the ServiceDescription to the Importer object

          importer.AddServiceDescription(desc, null, null);

     

    importer.CodeGenerationOptions = CodeGenerationOptions.GenerateNewAsync;

     

    //Initialize the CODE DOM tree in which we will import the ServiceDescriptionImporter

          CodeNamespace nm = new CodeNamespace();

          CodeCompileUnit unit = new CodeCompileUnit();

          unit.Namespaces.Add(nm);

     

          //generating the client proxy code

          ServiceDescriptionImportWarnings warnings = importer.Import(nm, unit);

     

          if (warnings == 0)

          {

                //set the CodeDOMProvider to C# to generate the code in C#

                System.IO.StringWriter sw = new System.IO.StringWriter();

                CodeDomProvider provider = CodeDomProvider.CreateProvider("C#");

    provider.GenerateCodeFromCompileUnit(unit, sw, new CodeGeneratorOptions());

     

                //creating TempFileCollection

                //the path of the temp folder is hardcoded

    TempFileCollection coll = new TempFileCollection(@"C:\wmpub\tempFiles");

                coll.KeepFiles = false;

                   

                //setting the CompilerParameters for the temporary assembly

    string[] refAssembly = { "System.dll", "System.Data.dll", "System.Web.Services.dll", "System.Xml.dll" };

                CompilerParameters param = new CompilerParameters(refAssembly);

                param.GenerateInMemory = true;

                param.TreatWarningsAsErrors = false;

                param.OutputAssembly = "WebServiceReflector.dll";

                param.TempFiles = coll;

     

                //compile the generated code into an assembly

    //CompilerResults results = provider.CompileAssemblyFromDom(param, unitArr);

    CompilerResults results = provider.CompileAssemblyFromSource(param, sw.ToString());

                this.assem = results.CompiledAssembly;

    }

     

    //return the list of operations exposed by the web service

          return listOfOperations;

    }

    This method returns the list of operations exposed by the web service. I have used ServiceDescription to achieve that as I was not able to reflect only the web method names from the generated assmebly. With the operation names available, all that remains is to find out the input and return parameters for each method.

     

    public ParameterInfo[] ReturnInputParameters(string methodName)

    {

    //create an instance of the web service type

    //////////////to do/////////////////////////

    //get the name of the web service dynamically from the wsdl

    Object o = this.assem.CreateInstance("Service");

          Type service = o.GetType();

          ParameterInfo[] paramArr = null;

     

    //get the list of all public methods available in the generated //assembly

          MethodInfo[] infoArr = service.GetMethods();

               

          foreach (MethodInfo info in infoArr)

          {

          //get the input parameter information for the

          //required web method

               if (methodName.Equals(info.Name))

                {

                      paramArr = info.GetParameters();

    }

    }

     

          return paramArr;

    }

     

    This method returns the input parameters in ParameterInfo[] list. To get the output parameter, just replace the call to GetParamters() on MethodInfo class with ReturnParameter property and put that inside a new method. Put these 3 methods inside a dll and add a reference to it from any client application. That's all. Just provide the URL and consume the web service, any web service. You don't have to go through the procedure of creating a proxy file every time you want to consume a new web service.

  • A Tryst With SOA

    De-serialize whitespace characters using IXmlSerializable

    • 0 Comments

    Sometimes back I faced an issue where the customer was trying to de-serialize whitespace characters. Pretty innocuous from the look of it – why will someone try to de-serialize and preserve white space characters? Naïve – there is a reason for everything. Nonetheless it took me quite a while to figure out the resolution.

    Let’s take the following type definition as an example:

     

    public class FooClass

          {

                private string _someName;

                private string _someValue;

     

                public FooClass()

                { }

     

                [System.Xml.Serialization.XmlText()]

                public string SomeName

                {

                      get

                      {

                            return this._someName;

                      }

                      set

                      {

                            this._someName = value;

                      }

                }

     

                public string SomeValue

                {

                      get

                      {

                            return this._someValue;

                      }

     

                      set

                      {

                            this._someValue = value;

                      }

                }

                }

    Let’s say we are trying to de-serialize the following input xml file:

     

                <?xml version="1.0" encoding="utf-8"?>

    <FooClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

          <SomeName>             </SomeName>

                <SomeValue>some value</SomeValue>

    </FooClass>

    If you try to de-serialize this using XmlSerializer.Deserialize() method, the resultant type will content the following values :

                            this.SomeName = “”  //the whitespace characters are eliminated

                            this.SomeValue = “some value”

     

    Even using XmlTextReader with WhitespaceHandling property set to WhiteSpaceHandling.All will not help in this case. The magic is to implement IXmlSerializable while defining the type:

     

                #region IXmlSerializable Members

     

    public System.Xml.Schema.XmlSchema GetSchema()

          {

                return null;

          }

     

          public void ReadXml(XmlReader reader)

          {

                string xmlContent = reader.ReadOuterXml();

                XmlDocument doc = new XmlDocument();

                doc.PreserveWhitespace = true;

                doc.LoadXml(xmlContent);

                XmlNodeList list = doc.DocumentElement.ChildNodes;

     

                foreach (XmlNode node in list)

                {

                    if (node.Name.Equals("SomeName"))

                        this.SomeName = node.InnerXml;

                    else if (node.Name.Equals("SomeValue"))

                        this.SomeValue = node.InnerXml;

                }

     

          }

     

          public void WriteXml(XmlWriter writer)

          {

                writer.WriteStartElement("SomeName");

                writer.WriteString(this.SomeName);

                writer.WriteEndElement();

     

                writer.WriteStartElement("SomeValue");

                writer.WriteString(this.SomeValue);

                writer.WriteEndElement();

          }

     

          #endregion

    One of the most important things that you should take care of is setting the “PreserveWhitespace” property of the XmlDocument object. Or else, the whitespaces will be eliminated while loading into XmlDocument.The rest of the implementation code is pretty straight forward.

  • A Tryst With SOA

    The specified module could not be found. HRESULT : 0X800700E

    • 0 Comments

    This error message, by the look of it, is very straight forward. It clearly states that the application (irrespective of its type) failed due to a missing module. I had a similar feeling when I received this bright yellow message on my browser window the other day. Next obvious question: which module am I missing? Scan the whole error screen and you won’t find one. Let’s get down to business and find that out.

    First a bit of background about the application as it holds the clue to the whole thing. I have a .NET 3.5 web application developed in C#. This referenced a managed C++ dll (ManagedWrapper.dll) which was a wrapper over an unmanaged C++ dll (UnmanagedTest.dll). The native dll had a single “Hello” method which returned “Hello World” string.

                                    #include "StdAfx.h"

    #include "TestObject.h"

                TestObject::TestObject() {}

                std::string TestObject::Hello()

    {

                return "Hello World";

    }

    I included a reference of ManagedWrapper.dll inside my web application. Copied UnmanagedTest.dll inside the bin directory of the web application. My intention was to call the Hello() method present within the native dll from my web application. To test whether the setup will work or not before any development inside the web application, I published the web application on IIS 6.0 and browsed to the default.aspx page from IE7. It failed with the above mentioned error message. Managed Exception :

                                     System.IO.FileNotFoundException: The specified module could not be found. (Exception from HRESULT: 0x8007007E)

    Partial Stack Trace of the failing thread:

     

    [FileNotFoundException: The specified module could not be found. (Exception from HRESULT: 0x8007007E)]

    System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) +0

    System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) +54

    System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) +211

    System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) +141

    System.Reflection.Assembly.Load(String assemblyString) +25

    System.Web.Configuration.CompilationSection.LoadAssemblyHelper(String assemblyName, Boolean starDirective) +32

     

     

    First I enabled fusion logging for ASP.NET and reproduced the issue. The logs were not able to point me to the failing module. Next I collected process monitor trace and root cause was in front of me:

     

    w3wp.exe   2012   QueryOpen   C:\WINNT\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\managedwrappersite\5bbb713b\7beac3fa\assembly\dl3\bfbeeb54\95e2dc78_66d5c801\UnmanagedTest.dll      NAME NOT FOUND

     

    w3wp.exe   2012   QueryOpen   C:\WINNT\system32\UnmanagedTest.dll   NAME NOT FOUND

     

    w3wp.exe   2012   QueryOpen   C:\WINNT\system\UnmanagedTest.dll     NAME NOT FOUND

     

    w3wp.exe   2012   QueryOpen   C:\WINNT\UnmanagedTest.dll   NAME NOT FOUND

     

    w3wp.exe   2012   QueryOpen   C:\WINNT\system32\inetsrv\UnmanagedTest.dll   NAME NOT FOUND

     

    w3wp.exe   2012   QueryOpen   C:\WINNT\system32\UnmanagedTest.dll   NAME NOT FOUND

     

    w3wp.exe   2012   QueryOpen   C:\WINNT\UnmanagedTest.dll   NAME NOT FOUND

     

    w3wp.exe   2012   QueryOpen   C:\WINNT\system32\wbem\UnmanagedTest.dll   NAME NOT FOUND

     

    w3wp.exe   2012   QueryOpen   C:\Program Files\Windows Imaging\UnmanagedTest.dll   NAME NOT FOUND

     

    w3wp.exe   2012   QueryOpen   C:\Program Files\CA\SharedComponents\ScanEngine\UnmanagedTest.dll   NAME NOT FOUND

     

    w3wp.exe   2012   QueryOpen   C:\Program Files\CA\eTrust Antivirus\UnmanagedTest.dll   NAME NOT FOUND

     

    w3wp.exe   2012   QueryOpen   C:\Program Files\Microsoft Network Monitor 3\UnmanagedTest.dll   NAME NOT FOUND

     

    w3wp.exe   2012   QueryOpen   C:\Program Files\Microsoft SQL Server\90\Tools\Binn\UnmanagedTest.dll   NAME NOT FOUND

    This shows that we are not failing to load ManagedWrapper.dll, but rather UnmanagedTest.dll. Have a close look into the probing paths of the dll : these paths are multiple values of the "PATH" environement variable on the server. Why does w3wp.exe look for a native dll in these paths instead of the application bin directory ? There is a reason for that as well. .NET Framework loads any dll present in the application bin folder as a managed dll. When we place an unmanged native dll in that place, it fails to load the dll and complains, just as it is doing in this scenario.

     

    Resolution in this case will be to place the native dll in any of the probing paths specified in the above trace. We can also place the dll inside a custom folder and set its path to "PATH" environment variable at runtime. Next time you get this error message, make efficient use of the process monitor to catch the culprit.

  • A Tryst With SOA

    Modifying the security header generated by WSE runtime

    • 2 Comments

    Often I have come across customers who complain about the security header generated by WSE runtime (WSE 2.0 / WSE 3.0). A little bit of probing and one can understand that it is an interop issue between a .NET client application implementing WSE and a non .NET web service. Either it is the overall layout of the header which is not acceptable to the service or the value of individual elements which do not meet the security requirements at the other end.

    Let's take an example of a very simple web service which authenticates via user name / password. A corresponding .NET client needs to pass a username token to authenticate itself against the service. Following lines of code achieve that in WSE 2.0 :

    UsernameToken usToken = new UsernameToken("test user", "test password");

    <web service proxy object>.RequestSoapContext.Security.Tokens.Add(usToken);

     

    The <soap:Header> in the generated SOAP request(you will find this in the outputTrace.webInfo) is as follows :

     

                                    <soap:Header>

    <wsa:Action>http://edb.att.com/BOTContractData/setBOTContractData</wsa:Action>

    <wsa:MessageID>uuid:15db114e-fa3c-4754-be71-4f2e84339e09</wsa:MessageID>

    <wsa:ReplyTo>

    <wsa:Address>http://schemas.xmlsoap.org/ws/2004/03/addressing/role/anonymous</wsa:Address>

    </wsa:ReplyTo>

    <wsa:To>http://localhost/TestwebService/Service.asmx</wsa:To>

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

    <wsu:Timestamp wsu:Id="Timestamp-a558ebfa-420f-4c5e-8af3-8fe849935bbe">

    <wsu:Created>2008-06-16T22:12:56Z</wsu:Created>

    <wsu:Expires>2008-06-16T22:17:56Z</wsu:Expires>

    </wsu:Timestamp>

    <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-90db33dc-ba79-48e9-b4a9-1fbe5e1f76c9">

    <wsse:Username>test user</wsse:Username>

    <wsse:Nonce>RFWac0T64oOOpb3tsH78Cw==</wsse:Nonce>

    <wsu:Created>2008-06-16T22:12:56Z</wsu:Created>

    </wsse:UsernameToken>

    </wsse:Security>

    </soap:Header>

    The problem starts here. Either the receiving service does not like the addressing headers generated by WSE runtime or it complains about the values of certain elements like the "Nonce" and "Created". My take on this is to modify the SOAP Header according to the service requirements as WSE provides some really cool classes to plug in either a custom output filter (WSE 2.0) or a custom policy assertion (WSE 3.0). Lets first look into the implementation details required for a WSE 2.0 enabled client application.

    WSE 2.0 

    1. Create a custom output filter to generate a custom SOAP Header.

    namespace WSE2.Custom.OutputFilters

    {

    public class ModifyUsernameToken : SoapOutputFilter //inherit the custom class from SoapOutputFilter inside we want to modify a SOAP Request

    {

            public ModifyUsernameToken()

            { }

     

            public override void ProcessMessage(SoapEnvelope envelope)

            {

                //creating the <wsse:Security> element in the outgoing message

                XmlNode securityNode = envelope.CreateNode(XmlNodeType.Element, "wsse:Security","http://docs.oasis-open.org/wss/2004/01/oasis-

                200401-wss-wssecurity-secext-1.0.xsd");

                XmlAttribute securityAttr = envelope.CreateAttribute("soap:mustunderstand");

                securityAttr.Value = "1";

                                                 //creating the <wsse:usernameToken> element

                XmlNode usernameTokenNode = envelope.CreateNode(XmlNodeType.Element, "wsse:UsernameToken", "http://docs.oasis-

                open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");

                XmlElement userElement = usernameTokenNode as XmlElement;

                userElement.SetAttribute("xmlns:wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");

     

                //creating the <wsse:Username> element

                XmlNode userNameNode = envelope.CreateNode(XmlNodeType.Element, "wsse:Username", "http://docs.oasis-

                open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");

                userNameNode.InnerXml = "testuser";

     

                //creating the <wsse:password> element

                XmlNode pwdNode = envelope.CreateNode(XmlNodeType.Element, "wsse:Password", "http://docs.oasis-open.org/wss/2004/01/oasis-

                200401-wss-wssecurity-secext-1.0.xsd");

                XmlElement pwdElement = pwdNode as XmlElement;

                pwdElement.SetAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");

                pwdNode.InnerXml = "test password";

     

                usernameTokenNode.AppendChild(userNameNode);

                usernameTokenNode.AppendChild(pwdNode);

     

                securityNode.AppendChild(usernameTokenNode);

     

                envelope.ImportNode(securityNode, true);               

     

                XmlNode node = envelope.Header;

     

                node.AppendChild(securityNode);

     

                //removing Addressing headers from the outgoing request

     

                XmlNode actionNode = envelope.Header["wsa:Action"];

                envelope.Header.RemoveChild(actionNode);

     

                XmlNode messageNode = envelope.Header["wsa:MessageID"];

                envelope.Header.RemoveChild(messageNode);

     

                XmlNode replyToNode = envelope.Header["wsa:ReplyTo"];

                envelope.Header.RemoveChild(replyToNode);

     

                XmlNode toNode = envelope.Header["wsa:To"];

                envelope.Header.RemoveChild(toNode);

            }

        }

    }

     

    2. Include a reference to this class library inside the client application. Remove the standard SecurityOutputFilter from the SOAP Pipeline. This is very important or else we will have two <wsse:Security> element in the final SOAP Request.

                                    <web service proxy object>.Pipeline.OutputFilters.Remove(typeof(SecurityOutputFilter));                

    3. Include the custom output filter into the SOAP Pipeline.

     

    WSE2.Custom.OutputFilters.ModifyUsernameToken token = new WSE2.Custom.OutputFilters.ModifyUsernameToken();

    <web service proxy object>.Pipeline.OutputFilters.Add(token);

    Thats it. The generated SOAP request will have the custom SOAP header. I have only provided a sample implementation inside the ProcessMessage() method above. One can include its own algorithm to evaluate values for <wsse:Password>, <wsse:Nonce> or <wsu:Created> elements.

    WSE 3.0 

     

    With WSE 3.0, the concept of pipeline was replaced with "assertion". I won't go into explaning what an assertion is, but this makes the same implementation look very different.

     

    1.  Create a custom policy assertion by deriving from Microsoft.Web.Services3.Design.PolicyAssertion.

                                    namespace WSE3.CustomAssertion.RemoveAddressingHeaders

    {

        public class RemoveAddressingHeadersAssertion : PolicyAssertion

        {

     

            public override SoapFilter CreateClientInputFilter(FilterCreationContext context)

            {

                return new ClientInputFilter();

            }

     

            public override SoapFilter CreateClientOutputFilter(FilterCreationContext context)

            {

                return new ClientOutputFilter();

            }

     

            public override SoapFilter CreateServiceInputFilter(FilterCreationContext context)

            {

                return new ServiceInputFilter();

            }

     

            public override SoapFilter CreateServiceOutputFilter(FilterCreationContext context)

            {

                return new ServiceOutputFilter();

            }

     

            public override System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, Type>> GetExtensions()

            {

                return new KeyValuePair<string, Type>[] { new KeyValuePair<string, Type>("RemoveAddressingHeadersAssertion", this.GetType()) };

            }

     

            public override void ReadXml(XmlReader reader, IDictionary<string, Type> extensions)

            {

                reader.ReadStartElement("RemoveAddressingHeadersAssertion");

            }

        }

     

        public class ClientInputFilter : SoapFilter

        {

            public override SoapFilterResult ProcessMessage(SoapEnvelope envelope)

            {

                return SoapFilterResult.Continue;

            }

        }

     

        //provide implementation for only the ClientOutOutputFilter as we are trying to modify an outgoing soap request

        public class ClientOutputFilter : SoapFilter

        {

     

            public ClientOutputFilter()

                : base()

            {}

            

            public override SoapFilterResult ProcessMessage(SoapEnvelope envelope)

            {

                            //include similar code inside this method as pasted earlier inside the WSE 2.0 output filter's ProcessMessage() method

                            return SoapFilterResult.Continue;

            }

        }

     

        public class ServiceInputFilter : SoapFilter

        {

            public override SoapFilterResult ProcessMessage(SoapEnvelope envelope)

            {

                return SoapFilterResult.Continue;

            }

        }

     

        public class ServiceOutputFilter : SoapFilter

        {

            public override SoapFilterResult ProcessMessage(SoapEnvelope envelope)

            {

                return SoapFilterResult.Continue;

            }

        }

    }

     

    2. Include a reference to this class library inside the client application and modify the wse3policyCache.config file :

     

                                    <policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">

    <extensions>

     <extension name="RemoveAddressingHeadersAssertion" type="WSE3.Addresssing.CustomAssertion.RemoveAddressingHeaders.RemoveAddressingHeadersAssertion,RemoveAddressingHeadersAssertion"/>

    <extension name="requireActionHeader" type="Microsoft.Web.Services3.Design.RequireActionHeaderAssertion, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

                    </extensions>

                    <policy name="ClientPolicy">

                                    <requireActionHeader />

                                    <RemoveAddressingHeadersAssertion/>

    </policy>

    </policies>

    3. Add a reference to this policy file inside the configuration file of the application (app.config / web.config)  and set the policy inside the code.

                                     srvWSE.SetPolicy("ClientPolicy");

    The final SOAP Header generated after passing through the custom output filter / policy assertion :

     

    <soap:Header>

    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">

    <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">

                                                    <wsse:Username>testuser</wsse:Username>

    <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">test password</wsse:Password>

                                    </wsse:UsernameToken>

                     </wsse:Security>

    </soap:Header>

    Using this method, one can easily modify any SOAP Request generated by WSE runtime. This proves the flexibility which has been built into the runtime.

Page 1 of 1 (11 items)