December, 2009

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

Page 1 of 1 (3 items)