TFS Build: How To Customize Work Item Association

Recently I got involved in implementing Team Foundation Server 2012 for a large development project. And even though I’ve worked with several versions of TFS over the years, this was the first time I really dove into the options for customization beyond your average workflow modifications. I’ve picked up some interesting tips and tricks over the last couple of months. Most of these customizations were already available somewhere on the web (see for example Ewald Hofman’s excellent series on customizing Team Build 2010, which in general applies quite nicely to TFS 2012 as well), but some modifications are my original work in that I did not find it anywhere else on the web. I thought I’d share some of the things I encountered.

A standard build based on the default build template will associate changesets and work items to builds. The way it does that is by retrieving the label for the previous successful build for the given build’s Build Definition, and by determining which changesets are included in the current build that were not included in the previous build. Some or all of those changesets might have work items associated with them, and those work items get associated with the build. Assuming the default build template, this is done as part of the “Compile, Test, and Associate Changesets and Work Items” parallel sequence. Look for a sequence like this in the template editor:

AssociateChangesetsAndWorkitems

Now if your team is anything like my team, they will not deliver a specific piece of functionality or fix a particular bug in a single checkin. A single functional requirement as documented in a Product Backlog Item may require the involvement of multiple persons, all with different specialties. So if one team member checks in his changes and associates it with a Task, that does not mean that the Task’s parent Product Backlog Item should be considered part of the next build. And the opposite can also be true. Let’s assume, for example, a scenario in which the project’s goal is to deliver a standard software product (an ERP system for example) and some customizations to go with it. Let’s also assume that the metadata for this ERP system does not reside in TFS. Instead, during a release build, the metadata is extracted from the development environment, committed to TFS, and from there pushed to QA, and ultimately to Production. Now, if a bug is filed, the bug may very well be caused by some configuration setting in the ERP system. The ERP guy on the team fixes the bug by modifying the configuration, which is pushed to QA with the next release. The bug is then marked as Resolved, but TFS Build will never associate this bug with a build because no changeset occurred that contained the fix.

To solve this problem, I tried my hand at an alternative method for associating a build with work items. What I wanted was to associate all work items that have a specific status (such as Done or Resolved), and are not yet associated to any previous builds. That means that we have to query TFS to get that specific set of work items, since the set is no longer a function of the changesets for this build. Now, querying TFS for work items is pretty well covered on the web, so I’m not going to elaborate on how to do that. The relevant part here is that the result of this query is stored in a variable scoped to the topmost Sequence, and that the query is the first thing that gets executed on the Build Agent. This is to ensure that the correct work items are retrieved before the sources are downloaded so as to avoid synchronicity issues. This variable is then used as a parameter in a custom build activity. For each work item in the list, the activity sets the value of the IntegrationBuild field to the build number, and it associates the work item with the build. The code for this activity looks something like the following:

using System;
using System.Activities;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Build.Workflow.Activities;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;

namespace CustomActivities
{
    public sealed class AssociateResolvedWorkItems: CodeActivity
    {
        [RequiredArgument]
        public InArgument<IList> WorkItems { get; set; }

        protected override void Execute(CodeActivityContext context)
        {
            var workItems = context.GetValue(WorkItems);

            var buildDetail = context.GetExtension<IBuildDetail>();
            var store = buildDetail.BuildServer.TeamProjectCollection.GetService<WorkItemStore>();

            foreach (WorkItem workItem in workItems)
            {
                // Update the workitem
                workItem.SyncToLatest();
                workItem.PartialOpen();
                workItem["Microsoft.VSTS.Build.IntegrationBuild"] = buildDetail.BuildNumber;
                workItem.History = ActivitiesResources.Format(ActivitiesResources.BuildUpdateForWorkItem, null);
            }
            var array = workItems.ToArray();
            var errors = store.BatchSave(array);

            var associated = new List();

            foreach (WorkItem workItem in workItems)
            {
                var error = errors.FirstOrDefault(item => item.WorkItem.Id == workItem.Id);
                if (error != null)
                {
                    // Alert update error
                    context.TrackBuildWarning(String.Concat("Unable to associate work item '", workItem.Id.ToString(CultureInfo.InvariantCulture), "': '", workItem.Title,
                                      "' - ", error.Exception), BuildMessageImportance.High);
                }
                else
                {
                    // Write update message...
                    context.TrackBuildMessage(String.Concat("The work item '", workItem.Id.ToString(CultureInfo.InvariantCulture), "' was updated with build label '",
                                      buildDetail.BuildNumber, "'."), BuildMessageImportance.Low);
                    // ... and add to the list of work items to associate
                    associated.Add(workItem);
                }
            }

            // Associate updated workitems to build
            buildDetail.Information.AddAssociatedWorkItems(associated.ToArray());
        }
    }
}

You see that this is a two-way association. First, the build number is set as the value of the IntegrationBuild field on the work item. This makes sure that work item queries that use this field still work as expected. Second, the work item is added to the Build Information. This makes sure that the work items are displayed on the Build Details. And as an added bonus, Microsoft Test Manager’s Recommended Tests view also correctly displays the list of work items when comparing two builds.

The activity is inserted in the template at the same place as where the standard associations occurred:

AssociateChangesetsAndWorkitemsCustomized

One final thing to do is determine whether you also still want the standard association (based on changeset-related work items) to occur. If you don’t want that, the “Associate Changesets And Workitems” activity (which appears right above our newly inserted activity) has an interesting parameter for you to edit. If you right-click on the activity and select Properties, you notice that one of the parameters is named “UpdateWorkItems” and has the value ‘True’. Simply changing that to ‘False’ will make sure that only the work items you selected are associated with the build.

ACS Now Supports Federated Signout

For all of us who gave identity federation a try, federated signout has probably been a theme of some controversy. If your application supports it, you might have had to explain to users why logging out of your application also means that they are logged out to all other applications that happen to use the same Identity Provider. But if your application does not support it, you might have had a discussion or two about why the logoff functionality “does not work” – meaning that a user that is logged off can log back in to your application with re-authenticating to the Identity Provider.

This is a conceptual problem that, to my mind, is not quite solved yet. And it may prove impossible to solve as long as we don’t rethink the concepts of logging in and out of web applications. For example, if I use my Microsoft Account (formerly known as LiveID) to logon to some random web application and then log off again, I might be surprised that I am also logged out of the Windows Azure Management Portal, my Office365 environment, and my MDSN Subscriber pages. That’s not what I want if I click ‘Logoff’ at randombobswebshop.com. On the other hand, if I click logoff and browse away, and then return after a while, I am probably surprised to see that I am logged back in again without re-submitting my credentials. Now I’m a technical guy, and I will probably have noticed my browser flickering due to the redirects to the Microsoft Logon Page and back, but my manager – or my girlfriend for that matter – may not be as perceptive as me.

As said, this problem may very well turn out not to have a technical solution. It is, however, an interesting topic for more philosophically inspired moments. But no matter where you stand on this matter, if you used Azure Access Control Service you did not really have a choice whether to implement federated signout or not, simply because ACS did not support it. Attempting to send the correct messages to ACS simply resulted in a static page that made the omission clear.

Recently, however, I discovered that ACS has been updated to support federated signout. The update apparently happened back in December 2012, so it took me quite a while to stumble upon this new feature, but here is how to use it from an ASP.NET application that acts as a Relying Party and uses WIF:

var config = FederatedAuthentication.FederationConfiguration.WsFederationConfiguration;
WSFederationAuthenticationModule.FederatedSignOut(new Uri(config.Issuer), new Uri(config.Reply));

Simple, right? So now we cannot hide behind the limited signout functionality in ACS anymore when we have to choose whether or not to support federated signout. So I guess that leaves no alternative than to really start thinking about the experience we want our users to get used to.

Different Ways To Verify ACS Token Signing Certificate

Today, I had an interesting discussion with a colleague on the possible attack vectors on the communication between a Relying Party application and ACS (Azure Access Control Service, also known as Windows Azure Active Directory Access Control). The discussion focussed on the extend to which the RP can be sure that he is in fact communicating with the STS (Secure Token Service, which in this scenario is ACS). The discussion was a bit blurred by some other issues that arose so we didn’t really get to the heart of the matter, but we were both left with a nagging concern that communication was not as secure as we would want it to be.

Now it’s never a good idea to keep a factual disagreement undecided, and when it comes to security there’s all the more reason to figure out what’s really going on. So, to set the background: we have an RP that lets its users authenticate with ACS (which in turn uses LiveID as Identity Provider). We consciously did not configure the tokens to be encrypted, so we only have them signed by ACS. This feature cannot be disabled (or I did not find the button for it) and that makes good sense, too. Since the communication between the RP and the STS all flows through the client (see this post for an extensive description), we want to make sure that the client is not able to tamper with the SAML token that it gets from the STS to pass on to the RP. There’s no viable scenario in which you would want to enable clients fiddling with the tokens, so not being able to disable token signing is a good thing as far as I’m concerned.

OK, so token signing is obligatory. The way this actually happens is – broadly – as follows if you don’t customize your ACS namespace: ACS generates a X.509 certificate that is used for signing the tokens. Upon signing a token, it uses this certificate’s private key to do the signing, and then embeds its own public key in the RequestSecurityTokenResponse that flows from ACS to the client and then to the RP. The RP then extracts this public key from the reponse, and uses it to validate the signed SAML token that is also part of this message.

So the question becomes: what is keeping a client from creating its own X.509 certificate on the fly, create its own SAML token and sign it with the private key of the roque certificate, and embed the certificate’s public key in the response? How will the RP, upon extracting the public key, detect that this is not the public key from the STS? Of course, the normal way of doing this would be to place the public key of the legitimate signing certificate in the Trusted Person Certificate Store on the client’s host (assuming it’s Windows). But that’s not how it is configured if you just use the Identity and Access Tool to register your app as an RP at your ACS namespace. In fact, the relevant part of your web.config will look like this:

  <system.identityModel>
    <identityConfiguration>
      <audienceUris>
        <add value="[your URI]" />
      </audienceUris>
      <issuerNameRegistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <trustedIssuers>
          <add thumbprint="[a certificate thumbprint]" name="[a friendly name]" />
        </trustedIssuers>
      </issuerNameRegistry>
      <certificateValidation certificateValidationMode="None" />
    </identityConfiguration>
  </system.identityModel>

As this snippet shows, the certificateValidationMode is None, meaning that you can put anything you like in whichever Certificate Store on the client host, but your app will not hit that store to validate anything. Of course, we can always switch the certificateValidationMode to PeerTrust or PeerOrChainTrust and add the public key of the signing certificate to the Trusted Person Certificate Store. But if that would be the only way to be sure that the token does indeed come from the STS, it would for example mean that, first of all, the default configuration as done by the Identity and Access Tool would leave you severely unprotected, and second of all it would mean that there’s no way to get any level of issuer assurance in, for example, an Azure Websites scenario.

Luckily, it turns out that things are not as bad as they seem, and the key to this is the <trustedIssuers> element in the snippet above. As you can see, this element contains a collection of certificate thumbprints. The one element that we have in our web.config in this case is placed there by the Identity and Access Tool when I first set up my application to use ACS as STS, and it is the thumbprint for the certificate that ACS uses to sign my tokens. So now, when the RP receives a RequestSecurityTokenResponse from the client and extracts the public part of the certificate from that message, it first validates the certificate by comparing its thumbprint to the one in the web.config. If the client forged the response with his own certificate, the thumbprint in the web.config will not match with the certificate used, and authentication does not occur. The class responsible for this is the ConfigurationBasedIssuerNameRegistry, which holds a registry of all valid issuers based on the items in the <trustedIssuers> element. This class is referenced in the <issuerNameRegistry> element as the class to use for this purpose.

Of course, there’s always a theoretical possibility that a second certificate does resolve to the same thumbprint. After all, the output space for thumbprints is limited and in fact smaller than the output space for certificate keys. But the change that an attacker is able to willingly create a certificate that results in the same thumbprint as the ACS certificate is extremely small. But hey, if the risk is still too big for your comfort, you can always take the Trusted Persons Certificate Store route on top of all this :).

UPDATE: Today the discussion with my colleague continued for a bit. He made the valid point that the above behavior (i.e. authentication fails if the thumbprints don’t match) is not intended as a security feature. Instead, what happens here is that the WIF framework wants to determine the name of the issuer based on the incoming public key. It does this by calling into the ConfigurationBasedIssuerNameRegistry, which will return an empty string if the incoming public key does not resolve to a known thumbprint. Further down the line, WIF throws an exception if the issuer name is null or empty. This means that the availability of a matching thumbprint is not really what is evaluated here; it’s just that it happens to function as the “bridge” between the public key and the issuer name. This becomes a real concern given the possibility to provide your own implementation of the IssuerNameRegistry instead of going with the default configuration-based variant. Now if that custom implementation happens to ignore the thumbprint while resolving the certificate into an issuer name, the RP can no longer be sure that the incoming token actually came from the STS.

So, to conclude: if there’s really no possibility to do proper certificate validation, you can be sure of untampered communication with the STS, but only as a side effect of resolving the certificate to a name through the thumbprint. If at all possible, it’s a recommended practice to set the certificateValidationMode to at least PeerTrust and to place the token signing certificate’s public key in the Trusted Persons Certificate Store.

WCF Custom Headers: A Short How-To

Recently I was working on a project that entailed a web application that called into a back end WCF service. That WCF service was connected to a system for which the users of the web application were not known as users due to licensing issues, so the calls were made under the credentials of a service account. However, the back end system did have access to some information on the users of the web application and needed to make some authorization decisions based on that information. So I needed to make the username of the web user known to the back end WCF service, even though the call takes place under different credentials.

The solution to these types of requirements is usually a custom header, so that’s the approach I took. However, I did not want the developers to be required to manually modify every service proxy they create. Moreover, adding the header in code just does not seem as clean as it can be. So I decided to create message inspectors to handle the application of the headers, and let custom behaviors be responsible for applying the message inspectors. The behaviors can then be applied to both the WCF service and the client though the config files. I will go through these steps in this post, but if you just want a working sample, the sources are available here.

The Header

First off, I created a custom header that derives from MessageHeader.

    public class UsernameHeader : MessageHeader
    {
        public const string UserAttribute = "userName";

        public const string MessageHeaderName = "UserName";

        public const string MessageHeaderNamespace = "http://wcfheaderbehavior.com/services/username";

        public string UserName { get; private set; }

        public UsernameHeader(string userName)
        {
            if (String.IsNullOrEmpty(userName))
                throw new ArgumentNullException("userName");
            UserName = userName;
        }

        public override string Name
        {
            get { return MessageHeaderName; }
        }

        public override string Namespace
        {
            get { return MessageHeaderNamespace; }
        }

        protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
        {
            writer.WriteAttributeString(UserAttribute, UserName);
        }
    }

Of course I could have opted for an untyped header that would have slightly simplified extracting it from the request, but this gives more flexibility in case it’s not just a single value that needs to be transmitted.

The Client

The next step is to create the client part of the solution. For the message inspector, we need to create a class that overrides IClientMessageInspector. The method implementations look like this:

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            // TODO: fill the header below with the user name from the current thread, for example
            var header = new UsernameHeader("UserName");
            request.Headers.Add(header);

            return null;
        }

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
        }

As this implementation shows, I only need to handle the BeforeSendRequest method, because I only have to do something upon sending a request; not while receiving a response. In the BeforeSendRequest implementation, I simply create the UserNameHeader and initialize it with the username, and add it to the request headers.

Now I want to create a behavior that applies this message inspector to the runtime. This can be done by implementing IEndpointBehavior. Since both IEndpointBehavior and IClientMessageInspector are just interfaces (as opposed to base classes), I can use my existing class to implement this second interface. The implementation looks like this:

        public void Validate(ServiceEndpoint endpoint)
        {
        }

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(this);
        }

The only method with an actual implementation is ApplyClientBehavior, which adds the instance (i.e. the IClientMessageInspector implementation) to the collection of message inspectors of the passed in ClientRuntime.

All that’s left to do for the client behavior is to implement BehaviorExtensionElement so as to be able to add the behavior to the client through the config file. And since this is the only class we need to implement for our solution, we can use the same class that already implements the two interfaces. The implementation of BehaviorExtensionElement looks like this:

        protected override object CreateBehavior()
        {
            return this;
        }

        public override Type BehaviorType
        {
            get { return GetType(); }
        }

The Service

Now for the service side. We basically do the same thing as we did with the client, the only difference being in the interfaces to implement. Because we want to modify the service runtime now, we implement IDispatchMessageInspector instead of IClientMessageInspector. The implementation looks like this:

        public UsernameHeader UsernameHeader { get; private set; }

        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            var headerIndex = request.Headers.FindHeader(UsernameHeader.MessageHeaderName, UsernameHeader.MessageHeaderNamespace);
            if (headerIndex >= 0)
            {
                var reader = request.Headers.GetReaderAtHeader(headerIndex);
                UsernameHeader = ParseHeader(reader);
            }

            return null;
        }

        public void BeforeSendReply(ref Message reply, object correlationState)
        {
        }

        private static UsernameHeader ParseHeader(XmlDictionaryReader reader)
        {
            if (reader.IsStartElement(UsernameHeader.MessageHeaderName, UsernameHeader.MessageHeaderNamespace))
            {
                var originatingUser = reader.GetAttribute(UsernameHeader.UserAttribute);
                if (String.IsNullOrEmpty(originatingUser))
                {
                    throw new FaultException("No originating user provided", FaultCode.CreateSenderFaultCode(new FaultCode("ParseHeader")));
                }

                return new UsernameHeader(originatingUser);
            }

            return null;
        }

What this does upon receiving a request is parse out the UsernameHeader and set it to a property. This way, the deserialized header becomes availabe for user code as a property of the service behavior because we will implement the behavior and the message inspector in the same class, just like we did with the client. Again, I could have opted for a simpler approach that involves extracting the header directly from the OperationContext when needed. Going through the extra step of accessing it through the behavior, however, is a cleaner approach. First of all I just need to extract the header once, but more importantly this approach conforms to the general idea of the behavior as the primary WCF extension and interaction point.

Now the interface we implement for the behavior is IServiceBehavior. This differs slightly from the IEndpointBehavior interface, which can be an alternative in some scenarios. The implementation is, again, very simple:

        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
        }

        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection endpoints, BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
            {
                foreach (var endpointDispatcher in channelDispatcher.Endpoints)
                {
                    endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
                }
            }
        }

What we do here is apply the instance (i.e. the IDispatchMessageInspector) to all endpoints. And just as with the client, we have our class inherit from the BehaviorExtensionElement so as to make it configurable through the config file.

Connecting The Dots

Now all that’s left to do is modify the config files to make all this work. For both the client and the service, we need to register the custom BehaviorExtensionElement like this:

<system.serviceModel>
  <extensions>
    <behaviorExtensions>
      <add name="UsernameBehavior" type="[Fully qualified assembly name of wither the client or the service behavior]" />
    </behaviorExtensions>
  </extensions>
</system.serviceModel>

Then, we need to register the behavior for the client:

<system.serviceModel>
    <client>
        <endpoint behaviorConfiguration="ClientBehavior" />
    </client>
  <behaviors>
    <endpointBehaviors>
      <behavior name="ClientBehavior">
        <UsernameBehavior />
      </behavior>
    </endpointBehaviors>
  </behaviors>
</system.serviceModel>

And the service:

<behaviors>
  <serviceBehaviors>
    <behavior>
      <UsernameBehavior/>
    </behavior>
  </serviceBehaviors>
</behaviors>

To actually get the value from the header, simply extract the behavior from the OperationContext:

            if (OperationContext.Current != null)
            {
                var usernameBehavior = OperationContext.Current.Host.Description.Behaviors.Find<UsernameServiceBehavior>();
                if (usernameBehavior != null && usernameBehavior.UsernameHeader != null)
                {
                    return usernameBehavior.UsernameHeader.UserName;
                }
            }
            return null;

And that’s it! Hope this helps. Full source code is available here.

UPDATE: I added a small code snippet that shows how to actually access the header on the service side. I also elaborated a bit on the reasoning behind the approach of making the header available through the service behavior instead of accessing it directly.

Authentication Methods Compared: Who Needs To Know What About Your Users?

For a course in secure development that I am teaching, I recently outlined some of the different ways available to implement authentication in .NET-aplications. The purpose was mainly to highlight the different types of data that cross the wire (and which endpoints are part of the interaction), so that developers are just a little more aware of the risks involved in each authentication method, and the ways to mitigate those risks.

Direct Authentication

The first two authentication methods, Forms Authentication and NTLM, can be labeled direct authentication, as opposed to brokered authentication (below). ‘Direct’ here refers to the fact that the client only interacts directly with the server during the authentication handshake.

Forms Authentication

The first direct method of authentication, widely used back in the days of membership providers and the like, is plain old forms authentication; the request/response sequence is shown in the image below.

Forms Authentication

Forms Authentication

As you probably know, this authentication method simply redirects the unauthenticated user to a login page, where the user can fill out his credentials. These credentials are then posted over the wire, and upon successful authentication the user is redirected back to the page he initially tried to visit. So unless the connection is protected with SSL, the credentials are sent over the wire in clear-text at step 5.

NTLM

The other direct authentication method I want to discuss is NTLM. This authentication method is typically used to authenticate against a domain-joined web server, whose users are stored in Active Directory. As you can see in the image below, an NTLM handshake does not redirect the user to a login page; instead it kind of abuses the “401 Unauthorized” HTTP status code.

NTLM

NTLM

Apart from the fact that NTLM does not use redirects, the main difference between NLTM and Forms is that the password does not travel over the wire in clear-text, even if no SSL is used. Instead, it uses hashing to let the user prove that he knows the password without actually having to disclose it. In step 2, the client receives a 401, as well as a WWW-Authenticate header with the value ‘NTLM’, to let the client know that a NTLM handshake is expected. In step 3, the client sends a so-called NLTM Type 1 Message to the server. This message contains the hostname and the domain name of the client. In response, the server again answers with a 401 in step 4, but this time it includes a NLTM Type 2 Message in the WWW-Authenticate header. This message contains a challenge called the nonce. For the purpose of this post, this is just a set of characters. The client performs a third GET to the server in step 5, and it includes an NLTM Type 3 Message. This message contains the user name, again the hostname and the domain name, and the two client responses to the challenge: a LanManager response and an NT response, calculated using the nonce and the LM password hash and the NT password hash respectively. Upon receiving this message, the server can use the user name to look up the users’ password hashes and caculate the expected reponse given the nonce. If the client-provided response matches with the expected reponse, access is granted.

It should be noted that, even though the password does not travel over the wire in clear-text, the LM and NT hashes are not exactly super-safe. And even though the addition of the nonce makes the responses a type of salted hashes, I would still highly recommend using SSL here.

Brokered Authentication

The other two authentication methods I want to discuss (Kerberos and claims) can be dubbed brokered authentication. This means that the client does not only interact with the server to complete the authentication handshake, but also (and even primarily) with a third party: an identity store. In .NET environments, this identity store is typically Active Directory.

Kerberos

Another way of authenticating in a Windows (read: Active Directory) environment, is by using Kerberos. Kerberos is a ticket-based method of authentication, and these tickets are distributed by a KDC (Key Distribution Center). This KDC resides on a Domain Controller, as does the user database of a Windows domain, Active Directory. The image below shows a Kerberos sequence in the order in which the different tickets are issued.

Kerberos (1)

Kerberos (1)

Before we dive into the contents of all the requests and responses as lined out in the image, a note on something we call ‘long-term keys’. These types of keys will frequently pop up when we try to make sense of Kerberos, so I want to make clear beforehand that those are all password-derived keys. So when we talk about a user’s long-term key, we mean a key that is derived from the user’s password. This key is accessible to the KDC, and can also be calculated by anyone who has knowledge of the password. The same goes for a service’s long-term key: that’s simply a key that is derived from the service account’s password.

Now, as you can see in the image, the user requests a TGT (Ticket-Granting Ticket) upon logging on to his workstation. The request contains the user principal name, the domain name, and a timestamp that is encrypted with the user’s long-term key. By looking up this long-term key and decrypting and evaluating the timestamp, the KDC can be sure (enough) that only a user with knowledge of the password could have created the request. (This is one of the reasons why the time on a workstation must be in sync with the KDC.) The KDC response provides the client with a TGS (Ticket-Granting Service) session key, and a TGT (Ticket-Granting Ticket). The TGS session key, which is the key the client will use in subsequent interactions with the KDC, is encrypted with the user’s long-term key. The TGT contains a copy of the TGS session key and some authorization data, and is encrypted with the KDC’s long-term key. So the client only has ‘real’ access to the TGS session key, and even though he possesses the TGT, he cannot see inside it or modify it without the KDC noticing.

When the client wants to access a service (say an intranet web application), he first goes back to the KDC and requests a ticket to access the service. The request contains the TGT, the name of the server and the server domain the user is trying to access, and an authenticator containing a timestamp. The authenticator is encrypted with the TGS session key that the client shares with the KDC. Note that the KDC does not have to hold on to the TGS session key itself: it is embedded in the client request because it is part of the TGT, which is encrypted with the KDC’s long-term key.

When the request for a service key is legitimate (which is verified by decrypting the authenticator that holds a timestamp), the KDC issues a service ticket and a service session key to the client. The session key is the key that the client will use in its interactions with the target server; it is encrypted with the user’s long-term key. The service ticket contains a copy of the service session key and is encrypted with the service’s long-term key.

Now the client can start interacting with the service it is trying to access by passing it an authenticator containing a timestamp, and the service ticket. The service session key is used to encrypt the authenticator, and since it is also embedded in the service ticket, the service gains possession of the session key so that it can decrypt and validate the authenticator.

Of course, when looking at things from the perspective of the client that is trying to access a service, the interaction is a bit different: the client first requests access to the service, which refuses access and tells to client to go get a Kerberos ticket first. This interaction is shown in the image below.

Kerberos (2)

Kerberos (2)

Claims-based Authentication

The last authentication method I want to discuss is claims-based authentication. As can be seen in the image below, it uses interactions with an endpoint other than the target server, just like Kerberos. But unlike Kerberos, all requests and responses are HTTP-based. This makes claims-based authentication much more useful for a variety of environments, and not just for a Windows environment with domains and domain trusts that are required for Kerberos.

Claims

Claims

As the image shows, the target server (called an RP (Relying Party) in claims-lingo) redirects the unauthenticated client to something called an STS (Security Token Service) when the client tries to access a protected resource. The client then starts interacting with the STS. The STS typically interacts with a user store (called an IdP (Identity Provider)) to authenticate the user. The interaction in the image shows a simple forms-based authentication handshake going on between the client and the STS, but this could just as well be NTLM or Kerberos. It can even be a claims-based authentication sequence all over again, if the STS further redirects the client to another STS instead of authenticating him against an IdP.  But whichever mechanism is used to authenticate against the STS, if the client is successfully authenticated the STS provides the client with a Security Token and redirects him back to the RP. If the RP understands the Security Token and finds the claims it needs to authenticate and authorize the user, it starts serving the protected resource.

To make all this possible, we need to establish trust between the STS and the RP: the RP needs to know what kind of token to expect from the STS, and the STS needs to know what kind of token to prepare for the RP. This trust is established once and out-of-band, typically when the RP is first deployed.

When using claims-based authentication, the user’s credentials may or may not travel the wire in some form or another, depending on the authentication mechanism used at the STS. More important though in this context, the token travels from the STS to the client, and then from the client to the RP. And this token may contain sentitive user information, possibly a photograph (although I would not recommend putting it in there). It may also contain information that is used to authorize a user at the RP, such as role claims. But even though we apparently need to protect that token, SSL does not help here: SSL is point-to-point, so it is only protects the data as it travels from the STS to the client, and from the client to the RP. In other words: at the client itself, the token is visible in clear-text. So even though SSL might solve the problem of the ill-conceived photograph, it won’t help with the role claims: it may be no problem for the user to see and change its own photo, but it is certainly a problem if the user can change or add role claims.

What any STS worthy of the name enforces, therefore, is token signing. This works by means of an X509-certificate, the private key of which is only known at the STS. When trust between the STS and the RP is established, the public key is exported to the RP. Now, when a client requests a token from the STS, that token is signed using the private key at the STS. Then, when the token reaches the RP, the signature is verified with the public key. If the client (or someone else along the way) changed the token, signature verification will fail and the RP will not grant access.

If you also want to prevent the user (or anyone who can intercept the network traffic when SSL is not used) from reading the contents of his own token, you can optionally encrypt it. This also works using an X509-certificate, but this one’s private key is only available at the RP. Upon establishing trust, the public key is exported to the STS, so that it can be used to encrypt the token. The RP can then decrypt the token using its own private key and verify its signature, and be sure that the STS created the token and that nobody was able to read or change its contents.

So, is SSL redundant when using token signing and encryption? Well, no, not entirely. The connection between the client and the STS handles authentication against the STS, and that will most likely involve some form of credential-derived data travelling the wire. That in itself is enough to warrant SSL between the client and the STS. Now, theoretically, SSL is not required between the client and the RP when the token is both signed and encrypted. But if you go through all this trouble to protect the claims in the ticket, chances are that the resource you are protecting also involves sensitive data traveling back and forth between client and RP, making SSL required here, too.

Use ClaimsAuthenticationManager To Make Sense Of Federated Identites

OK, so you figured you don’t want the burden of managing your users’ identities anymore. You can very well live without the responsibility of keeping their credentials save, and you have chosen to let the smart boys and girls that manage LiveID handle all that for you; you’re just using some STS (for example: Azure ACS) to let your users login with their LiveID. Good for you! Identity Federation is the way of the future, so why not start using it right now?

It does present you with a challenge, though, that you didn’t have in the olden days (not in its current form, at least). Suddenly, your user has an identity that, out-of-the-box, doesn’t mean all that much to your application. Let’s take the case of a simple webshop. If you enable users to get in using their LiveID, how are you gonna trace whether they are new or recurring customers? Did they already register a shippng address, payment details, and that sort of stuff? Back in the days of Forms Authentication, this info was usually gathered when the user registered himself with the application; now it’s a separate step that a logged in user may or may not have taken already.

So, the problem is clear: how do I keep track of users whose identities are external to my application? Of course there are more than one pieces to this puzzel: I have to worry about gathering the additional application-specific information, and I have to store some token to associate identities with this extra information. And then there is the issue of deciding whether a user that has just logged in is a new or a recurring user. This post presents a possible solution to this last problem, and shows how the WIF framework let’s you handle this quite nicely.

First off, let’s reformulate the problem in a more ‘claimsy’ type of language. In claims-world, everything there is to know about an identity is expressed in claims. For example: what we used to call a ‘role’ is now a claim of type ‘role’. An email address is now a claim of type ‘emailaddress’. So we might say that being a properly registered customer for our webshop (where ‘properly registered’ means: having provided the application with your shipping details and having received a customerId in return), means to have a claim of type ‘customerId’. So now we can distinguish between logged in users of our webshop: those that have a claim ‘customerId’ are all set; those that don’t still need to register some details before they can place an order.

The problem now is: how do we set this extra claim at runtime if a recurring user logs in? As said, WIF has an extensibility point that let’s you handle these types of scenario’s. First of all, this requires you to provide your implementation of the ClaimsAuthenticationManager-class. This basically involves implementing the Authenticate-method as follows:

        [SecuritySafeCritical]
        public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
        {
            if (incomingPrincipal.Identity != null && incomingPrincipal.Identity.IsAuthenticated)
            {
                var identity = incomingPrincipal.Identity as ClaimsIdentity;
                int? customerId = null;
                // TODO: add logic that evaluates the incoming principal and retrieves customerId if available
                if (customerId != null)
                {
                    identity.AddClaim(new Claim(ClaimTypes.UserData, customer.Id.ToString()));
                }
            }

            return base.Authenticate(resourceName, incomingPrincipal);
        }

As a sidenote: notice the SecuritySafeCritical attribute above this method? That’s required because the method AddClaim() on ClaimsIdentity is marked as SecurityCritical so that SecurityTransparent code cannot call into it. (I actually feel that CAS may be dying a silent death these days, for several reasons, but that’s another discussion.)

So, now you’ve created your own ClaimsAuthenticationManager. All that’s left to do now is letting your app know that you want to use it. That’s done through the web.config:

  <system.identityModel>
    <identityConfiguration>
      <!-- All sorts of config stuff -->
      <claimsAuthenticationManager type="[Your fully qualified type]" />
    </identityConfiguration>
  </system.identityModel>

Now, your custom ClaimsAuthenticationManager is called when a user is authenticated against the STS of your choice and returns to your application with a token. You can now handle the login event and add application-specific claims to the incoming identity. Of course, these claims can also come from an external attribute store or whatever source you can think of.

One final note: if you are going to stuff loads and loads of claims in here, it may be a good idea to store the actual claims data of the server instead of in a cookie that travels to the client; some browsers may take issue with the fact that you feel storing an image blob in a cookie is appropriate :). To do that, you have to edit the global.asax and handle the Application_Start event. Paste the following code in there:

            FederatedAuthentication.FederationConfigurationCreated += (s, args) =>
            {
                FederatedAuthentication.SessionAuthenticationModule.IsReferenceMode = true;
            };

What this does is tell the SessionAuthenticationModule to store the actual claims data in the session, and pass the reference to that session object to the client through a cookie. Note that this requires either a single server deployment, or a form of session management where sessions are shared between servers.

OK, happing federating everyone! Please leave a comment if you have any follow-up questions.

New coder’s blog on the net!

Hi all!

Welcome to yet another tech blog. To set the expectations right away: I will primarily be blogging about code, more specifically .NET. I am starting V O L A T I L E coding for a number of reasons. The first reason is, I guess, pretty common among (prospective) tech bloggers: when reading succesful blogs it always seems rewarding to see your ideas and solutions being helpful to others, and to be able to share and discuss ideas to get interesting feedback. However, between this ambition and an actual successful blog stands, among other things, the sheer multitude of other blogs. Up until some weeks ago, this always made me feel that a new coder’s blog was doomed to remain unnoticed.

But when I got more serious about using Identity Federation and WIF for my professional work a couple of weeks ago, I quickly discovered that WIF was not all that well covered in the blogosfere. Sure, I found some code samples, some Q&A – and of course there’s always Vittorio Bertocci – but I was not overloaded with information as is usually the case with .NET-related queries. So I figured that if I ever wanted to start a blog and actually having a slight change to end up in search engine results, I’d better start blogging about WIF as soon as I could find the time.

The other main reason for starting V O L A T I L E coding is more mundane: it enables me to document some of the issues I encounter in my professional life, and the solutions that I come up with. So hopefully it keeps me from re-inventing the wheel every now and then :).

OK, to close off this starting post, what should you expect? As said, I will primarily blog about .NET-related coding issues. Identity and WIF will be among the subjects, but so will application security and secure development, more generic .NET coding topics, maybe some EntLib-thingies, and possibly even SharePoint. So stay tuned, more is on the way!