June, 2010

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

     

Page 1 of 1 (3 items)