Home / My Disclaimer / Who am I? / Search... / Sign in

// .NET

Real-time User Notification and Session Management with SignalR - Part 2

by Steve Syfuhs / March 24, 2013 02:31 PM

In Part 1 I introduced a basic usage of SignalR and talked about the goals we were trying to accomplish with the library.

In the next few posts I’m going to show how we can build a real-time user notification and session management system for a web application.

In this post I’ll show how we can implement a solution that accomplishes our goals.

Before diving back into SignalR it’s important to have a quick rundown of concepts for session management. If we think about how sessions work for a user in most applications it’s usually conceptually simple. A session is a mechanism to track user rights between the user logging in and logging out.  A session is usually tracked through a cookie attached to each request made to the server. A user has a session (or multiple sessions if they are logged in from another machine/browser) and each session is tied to a request or connection. Each time the user requests a page a new connection is opened to the server. As long as the session is active each connection is authorized to do whatever it needs to do (as defined by whatever authorization policies are in place).

image

When you kill a session each subsequent connection for that session is denied. The session is dead, no more access. Simple. A session is usually killed when a user explicitly logs out and destroys the session cookie or the browser is closed. This doesn’t normally kill any other sessions tied to the user though. The connections made from another browser are still authorized.

From a security perspective we may want to notify the user that another session is already active or was just created. We can then allow the user to destroy the other session if they want.

SignalR works really well in this scenario because it solves a nasty problem of timing. Normally when the server wants to tell the client something it has to wait for the client to make a request to the server and then the client has to act on the server’s message. A request to the server is usually only done when a user explicitly clicks something, or there’s a timer polling every 30 seconds or so. If we want to notify the user instantly of another session we can’t necessarily wait for the client to call. SignalR solves this problem because it can call the client directly from the server.

Now, allowing a user to control other sessions requires tracking sessions and connections. If we follow the diagram above we have a pretty simple relationship between users and sessions, and between sessions and connections. We could store this information in a database or other persistent storage, and in fact would want to for non-trivial applications, but for the sake of this post we’ll just store the data in memory.

Most session handlers these days (e.g. the SessionAuthenticationModule in WIF) create a cookie that contains everything the web application should know about the user. As long as that identity in the cookie is valid the user can do whatever the session handler allows. This is a mostly stateless process and aligns with various tenants of REST. Each request to the server contains the identity of the user, and the server doesn’t have to track anything. It’s simple and powerful.

However, in non-trivial applications this doesn’t always cut it. Security sometimes requires state. In this case we require state in the sense that the server needs to track all active sessions tied to a user. For this we’ll use the WIF SessionAuthenticationModule (SAM) and a custom SessionSecurityTokenHandler.

Before we can validate a session though, we need to track when a session is created. If the application is configured for federation you can create a custom ClaimsAuthenticationManager and call the session creation code, or if you are creating a session token manually you can call this code on login.

void CreateSession()
{
    string sess = CreateSessionKey();

    var principal = new ClaimsPrincipal(new[] { new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "myusername"), new Claim(ClaimTypes.Sid, sess) }, AuthenticationTypes.Password) });

    var token = FederatedAuthentication.SessionAuthenticationModule.CreateSessionSecurityToken(principal, "mycontext", DateTime.UtcNow, DateTime.UtcNow.AddDays(1), false);

    FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(token);

    NotificationHub.RegisterSession(sess, principal.Identity.Name);
}

private string CreateSessionKey()
{
    var rng = System.Security.Cryptography.RNGCryptoServiceProvider.Create();

    var bytes = new byte[32];

    rng.GetNonZeroBytes(bytes);

    return Convert.ToBase64String(bytes);
}

We’ll get back to the NotificationHub.RegisterSession method in a bit.

After the session is created, on subsequent requests the SessionSecurityTokenHandler validates whether a user’s session is still valid and authorized. The SAM calls the token handler when it receives a session cookie and generates an identity for the current request.

From here we can determine whether the user’s session was forced to logout. If we override the ValidateSession method we can check against the NotificationHub. Keep in mind this is an example – it’s not a good design decision to track session data in your notification hub. I’m also using ClaimTypes.Sid, which isn’t the best claim type to use either.

protected override void ValidateSession(SessionSecurityToken securityToken)
{
    base.ValidateSession(securityToken);

    var ident = securityToken.ClaimsPrincipal.Identity as IClaimsIdentity;

    if (ident == null)
        throw new SecurityTokenException();

    var sessionClaim = ident.Claims.Where(c => c.ClaimType == ClaimTypes.Sid).FirstOrDefault();

    if(sessionClaim == null)
        throw new SecurityTokenExpiredException();

    if (!NotificationHub.IsSessionValid(sessionClaim.Value))
    {
        throw new SecurityTokenExpiredException();
    }
}

Every time a client makes a request to the server the user’s session is validated against the internal list of valid sessions. If the session is unknown or invalid an exception is thrown which kills the request.

To configure the use of this SecurityTokenHandler you can add it to the web.config in the microsoft.identityModel/service section. Yes, this is still WIF 3.5/.NET 4.0.  There is no requirement for .NET 4.5 here.

<securityTokenHandlers>
    <remove type="Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler, Microsoft.IdentityModel" />
    <add type="Syfuhs.Demo.CustomSessionSecurityTokenHandler, MyDemo" />
</securityTokenHandlers>

Now that we can track sessions on the server side we need to track connections. To start tracking connections we need to start at our Hub. If we go back to our NotificationHub we can override a few methods, specifically OnConnected and OnDisconnected. Every time a page has loaded the SignalR hubs client library, OnConnected is called and every time the page is unloaded OnDisconnected is called. Between these two methods we can tie all active connections to a session. Before we do that though we need to make sure that all requests to our Hub are only from logged in users.

To ensure only active sessions talk to our hub we need to decorate our hub with the [Authorize] attribute.

[Authorize(RequireOutgoing = true)]
public class NotificationHub : Hub
{
    // snip
}

Then we override the OnConnected method. Within this method we can access what’s called the ConnectionId, and associate it to our session. The ConnectionId is unique for each page loaded and connected to the server.

For this demo we’ll store the tracking information in a couple dictionaries.

private static readonly Dictionary<string, string> UserSessions = new Dictionary<string, string>();

private static readonly Dictionary<string, List<string>> sessionConnections = new Dictionary<string, List<string>>();

public override Task OnConnected()
{
    var user = Context.User.Identity as IClaimsIdentity;

    if (user == null)
        throw new SecurityException();

    var sessionClaim = user.Claims.Where(c => c.ClaimType == ClaimTypes.Sid).FirstOrDefault();

    if (sessionClaim == null)
        throw new SecurityException();

    sessionConnections[sessionClaim.Value].Add(Context.ConnectionId);

    return base.OnConnected();
}

On disconnect we want to remove the connection associated with the session.

public override Task OnDisconnected()
{
    var user = Context.User.Identity as IClaimsIdentity;

    if (user == null)
        throw new SecurityException();

    var sessionClaim = user.Claims.Where(c => c.ClaimType == ClaimTypes.Sid).FirstOrDefault();

    if (sessionClaim == null)
        throw new SecurityException();

    sessionConnections[sessionClaim.Value].Remove(Context.ConnectionId);

    return base.OnDisconnected();
}

Now at this point we can map all active connections to their various sessions. When we create a new session from a user logging in we want to notify all active connections that the new session was created. This notification will allow us to kill the new session if necessary. Here’s where we implement that NotificationHub.RegisterSession method.

internal static void RegisterSession(string sessionId, string user)
{
    UserSessions[sessionId] = user;
    sessionConnections[sessionId] = new List<string>();

    var message = "You logged in to another session";

    var context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();

    var userCurrentSessions = UserSessions.Where(u => u.Value == user);

    foreach (var s in userCurrentSessions)
    {
        var connectionsTiedToSession = sessionConnections.Where(c => c.Key == s.Key).SelectMany(c => c.Value);

        foreach (var connectionId in connectionsTiedToSession)
            context.Clients.Client(connectionId).sessionRegistered(message, sessionId);
    }
}

This method will create a new session entry for us and look up all other sessions for the user. It will then loop through all connections for the sessions and notify the user that a new session was created.

So far so good, right? This takes care of almost all of the server side code. But next we’ll jump to the client side JavaScript and implement that notification.

When the server calls the client to notify the user about a new session we want to write the message out to screen and give the user the option of killing the session.

HTML:

<div class="notification"></div>

JavaScript:

var notifier = $.connection.notificationHub;

notifier.client.sessionRegistered = function (message, session) {
    $('.notification').text(message);

    $('.notification').append('<a class="killSession" href="#">End Session</a>');
    $('.notification').append('<a class="DismissNotification" href="#">Dismiss</a>');
    $('.killSession').click(function () {
        notifier.server.killSession(session);
        $('.notification').hide(500);
    });

    $('.DismissNotification').click(function () {
        $('.notification').hide(500);
    });
};

On session registration the notification div text is set to the message and a link is created to allow the user to kill the session. The click event calls the NotificationHub.KillSession method.

Back in the hub we implement the KillSession method to remove the session from the list of active sessions.

public void KillSession(string session)
{
    var connections = sessionConnections[session].ToList();

    sessionConnections.Remove(session);
    UserSessions.Remove(session);

    foreach (var c in connections)
    {
        Clients.Client(c).sessionEnded();
    }

}

Once the session is dead a call is made back to the clients associated with that session to notify the page that the session has ended. Back in the JavaScript we can hook into the sessionEnded function and reload the page.

notifier.client.sessionEnded = function () {
    location.reload();
}

Reloading the page will cause the browser to make a request to the server and the server will call our custom SessionSecurityTokenHandler where the ValidateSession method will throw an exception. Once this exception is thrown the request is stopped and all subsequent requests within the same session will have the same fate. The dead session should redirect to your login page.

To test this out all we have to do is load up our application and log in. Then if we create a new session by opening a new browser and logging in, e.g. switching from IE to Chrome, or within IE opening a new session via File > New Session, our first browser should notify you. If you click the End Session link you should automatically be logged out of the other session and redirected to your login page.

Pretty cool, huh?

Real-time User Notification and Session Management with SignalR - Part 1

by Steve Syfuhs / March 07, 2013 10:21 PM

As more and more applications and services are becoming always on and accessible from a wide range of devices it’s important that we are able to securely manage sessions for users across all of these systems.

Imagine that you have a web application that a user tends to stay logged into all day. Over time the application produces notifications for the user and those notifications should be shown fairly immediately. In this post I’m going to talk about a very important notification – when the user’s account has logged into another device while still logged into their existing session. If the user is logged into the application on their desktop at work it might be bad that they also just logged into their account from a computer on the other side of the country. Essentially, what we want is a way to notify the user that their account just logged in from another device. Why didn’t I just lead with that?

In the next few posts I’m going to show how we can build a real-time user notification and session management system for a web application.

To accomplish this task I’m going to use the SignalR library:

ASP.NET SignalR is a new library for ASP.NET developers that simplifies the process of adding real-time web functionality to your applications. Real-time web functionality is the ability to have server-side code push content to connected clients instantly as it becomes available.

Conceptually it’s exactly what we want to use – it allows us to notify a client (the user’s first browser session) from the server that another client (another browser or device) has logged in with the same account.

SignalR is based on a Remote Procedure Call (RPC) design pattern allowing messages to flow from the server to a client. The long and the short of it is that whenever a page is loaded in the browser a chunk of JavaScript is executed that calls back to the server and opens a connection either via websockets when supported or falls back to other methods like long polling or funky (but powerful) iframe business.

To understand how this works it’s necessary to get SignalR up and running. First, create a new web project of your choosing  in Visual Studio and open the Nuget Package Manager. Search online for the package “Microsoft.AspNet.SignalR” and install it. For the sake of simplicity this will install the entire SignalR library. Down the road you may decide to trim the installed components down to only the requisite pieces.

Locate the global.asax file in your project and open it. In the Application_Start method add this bit of code:

RouteTable.Routes.MapHubs();

This will register a hub (something we’ll create in a minute) to the “~/signalr/hubs” route. Next open your MasterPage or View and add the following script references somewhere after a reference to jQuery:

<script type="text/javascript" src="scripts/jquery.signalR-1.0.1.js"></script>
<script type="text/javascript" src="signalr/hubs"></script>

You’ll notice the second script reference is the same as our route that was added earlier. This script is dynamically generated and provides us a proxy for communicating with the hub on the server side.

At this point we haven’t done much. All we’ve done is set up our web application to use SignalR. It doesn’t do anything yet. In order for communication to occur we need something called a Hub.

A hub is the thing that offers us that RPC mechanism. We call into it to send messages. It then sends the messages to the given recipients based on the connections opened by the client-side JavaScript. To create a hub all we need to do is create a new class and inherit from Microsoft.AspNet.SignalR.Hub. I’ve created one called NotificationHub.

public class NotificationHub : Hub
{
    // Nothing to see here yet
}

A hub is conceptually a connector between your browser and your server. When a message is sent from your browser it is received by a hub and the hub sends it off to a given recipient. A hub receives messages through methods defined by you.

Before digging into specifics a quick demo is in order. In our NotificationHub class let’s create a new method:

public void Hello(string message)
{
     Debug.WriteLine(message);

}

For now that’s all we have to write server-side for the sake of this demo. It will receive a message and it will write it to the debug stream. Next, go back to your page to write some HTML and JavaScript.

First create a <div> and give it an Id of connected:

<div id=”connected”></div>

Then add some JavaScript:

$.connection.hub.start().done(function () {
        $('#connected').text('I'm connected with Id: ' + $.connection.hub.id);
    });
}

What this will do is open a proxy connection to the hub(s) and once it’s completed the connection dance, the proxy calls a function and sets the text to the Id of the proxy connection. This Id value is a unique identifier created every time the client connects back to the server.

Now that we have an open connection to our hub we can call our Hello method. To do this we need to get the proxy to our notification hub, which is done through the $.connection object.

var notifier = $.connection.notificationHub;

For each hub we create and map to a route, the connection object has a pointer to it’s equivalent JavaScript proxy. Mind the camel-casing though. Once we have our proxy we can call our method through the server property. This property maps functions to methods in the hub. So to call our Hello method in the hub we call this JavaScript:

notifier.server.hello(‘World!’);

Lets make that clickable.

<a id=”sayHi” href=”#”>Say Hello!</a>

$(‘#sayHi’).click(function() { notifier.server.hello(‘World!’); });

If you click that you should now see “World!” in your Debug window.

That’s all fine and dandy for sending messages to the server, but AJAX already does that. Boring! Let’s go back to our hub and update that Hello method. Add the following line of code:

public void Hello(string message)
{
    Clients.All.helloEveryone(message);
}

What this will do is broadcast our message to All connected clients. It will call a function on the client named helloEveryone. For more information on who can receive messages take a look at the Hubs documentation. However, for our clients to receive that message we need to hook in a function for our proxy to call when it receives the broadcast. Back in the HTML and JavaScript add this:

<div id=”msg”></div>

notifier.client.helloEveryone = function(message) {
    $('#msg').text(message);
}

We’ve hooked a function into the client object so that when the proxy receives the message to call the function, it will call our implementation. It’s really easy to build out a collection of calls to communicate both directions with this library. All calls that should be sent to the server should call notifier.server.{yourHubMethod} and all calls from the hub to the clients should be mapped to notifier.client.{eventListener}.

If you open a few browsers and click that link, all browsers should simultaneously receive the message and show “World!”. That’s pretty cool.

At this point we have nearly enough information to build out our session management and notification system. In the next post I’ll talk about how we can send messages directly to a specific user, as well as how to send messages from outside the scope of a hub.

Guide to Claims-Based Identity Second Edition

by Steve Syfuhs / December 13, 2011 10:28 AM

It looks like the Guide to Claims-Based Identity and Access Control was released as a second addition!

Take a look at the list of authors:

If you want a list of experts on security then look no further. These guys are some of the best in the industry and are my go-to for resources on Claims.

Strongly Typed Claims

by Steve Syfuhs / November 12, 2011 04:03 PM

Sometimes it's a pain in the neck working with Claims. A lot of times you need to look for particular claim and that usually means looping through the claims collection and parsing the value to a particular type.

This little dance is the trade-off for having such a simple interface to a potentially arbitrary collection of claims. Most of the time this works, but every once in a while you need to create a basic user object that contains some strongly typed properties. You could build up a basic object like:

public class User
{
    public string UserName { get; set; }

    public string EmailAddress { get; set; }

    public string Department { get; set; }

    public List<string> Roles { get; set; }
}

This would require you to intercept the IClaimsIdentity object and search through the claims collection setting each property manually whenever you wanted to get access to the data. This can get tiresome and is error prone.

I think I've come up with a relatively complete solution to this problem. Basically it works by creating a custom IClaimsIdentity class that sets a User property through reflection. You can then access the user through Thread.CurrentPrincipal.Identity like this:

TypedClaimsIdentity ident = Thread.CurrentPrincipal.Identity as TypedClaimsIdentity;
string email = ident.User.EmailAddress.Value;
var userRoles = ident.User.Roles;

Once you've defined the particular types and their associated claims, the particular values will be set through reflection. So to declare your user properties, create a class like this:

public class MyTypedClaimsUser : TypedClaims
{
    public MyTypedClaimsUser()
    {
        this.Name = new TypedClaim<string>();
        this.EmailAddress = new TypedClaim<string>();
        this.Roles = new List<TypedClaim<string>>();
        this.Expiration = new TypedClaim<DateTime>();
        this.AuthenticationMethod = new TypedClaim<string>();
    }

    [TypedClaim(ClaimTypes.Name, false)]
    public TypedClaim<string> Name { get; private set; }

    [TypedClaim(ClaimTypes.Email, false)]
    public TypedClaim<string> EmailAddress { get; private set; }

    [TypedClaim(ClaimTypes.Role, true)]
    public List<TypedClaim<string>> Roles { get; private set; }

    [TypedClaim(ClaimTypes.Expiration, true)]
    public TypedClaim<DateTime> Expiration { get; private set; }

    [TypedClaim(ClaimTypes.AuthenticationMethod, false)]
    public TypedClaim<string> AuthenticationMethod { get; private set; }

    [TypedClaim(ClaimTypes.GroupSid, false)]
    public TypedClaim<string> GroupSid { get; private set; }
}

Each property must be defined a certain way. Each property must have a particular attribute set: TypedClaimAttribute. This attribute will help the reflection code associate the property with the expected claim. That way the Name property will always be mapped to the ClaimTypes.Name claim type, which is the http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name claim. It also helps by warning the code that it's going to likely have multiple potential values, like the Role claim.

Each property is also of a particular type: TypedClaim<T>. In theory I could have just used simple types like strings but by going this route you can get access to claim metadata like Name.ClaimType or Name.Issuer. TypedClaim<T> is inherited from Claim.

So how does this all work? Well first you need to be able to add the User object into the Identity object. This is done by creating a custom IClaimsIdentity class:

[Serializable]
public class TypedClaimsIdentity : IClaimsIdentity
{
    public TypedClaimsIdentity(IClaimsIdentity identity)
    {
        user = new MyTypedClaimsUser();

        if (identity.Claims != null)
            this.claims = identity.Claims;
        else
            claims = new ClaimCollection(identity);

        this.Actor = identity.Actor;
        this.AuthenticationType = identity.AuthenticationType;

        Update();
    }

    private void Update()
    {
        user.Update(this.claims);
    }

    private MyTypedClaimsUser user;

    public MyTypedClaimsUser User
    {
        get
        {
            Update();
            return user;
        }
    }

    private ClaimCollection claims;

    public ClaimCollection Claims
    {
        get
        {
            Update();
            return claims;
        }
    }

    public IClaimsIdentity Actor { get; set; }

    public SecurityToken BootstrapToken { get; set; }

    public IClaimsIdentity Copy()
    {
        ClaimsIdentity claimsIdentity = new ClaimsIdentity(this.AuthenticationType);

        if (this.Claims != null)
        {
            claimsIdentity.Claims.AddRange(claims.CopyWithSubject(claimsIdentity));
        }

        claimsIdentity.Label = this.Label;
        claimsIdentity.NameClaimType = this.NameClaimType;
        claimsIdentity.RoleClaimType = this.RoleClaimType;
        claimsIdentity.BootstrapToken = this.BootstrapToken;

        return claimsIdentity;
    }

    public string Label { get; set; }

    public string NameClaimType { get; set; }

    public string RoleClaimType { get; set; }

    public string AuthenticationType { get; private set; }

    public bool IsAuthenticated { get { return claims.Count > 0; } }

    public string Name { get { return User.Name.Value; } }
}

There isn't anything spectacularly interesting about this class. The important part is the constructor. It only accepts an IClaimsIdentity object because it's designed as a way to wrap around an already created identity. It then updates the User object through Update().

The User object is updated through reflection. The Update() method calls User.Update(…) which is defined within the base class of MyTypedClaimsUser. This will call into a helper class that looks through the User object and find any properties that contain the TypedClaimAttribute.

EDIT: When it comes to reflection, there is always a better way to do something. My original code was mostly a PoC and didn't make use of existing .NET-isms. I've edited this bit to include the code changes.

The helper class was originally a bit clunky because all it did was look through the properties and if/else if's through their types and parses them:

if (type == typeof(string))
{
    return new TypedClaim<string>(selectedClaims.First()) { Value = selectedClaims.First().Value };
}

This really isn't the smartest way to do it because .NET already contains some pretty strong conversion functions; specifically Convert.ChangeType(value, type).

Going this route requires generating the proper TypedClaim<T> though. Many thanks to Anna Lear because she pointed out the MakeGenericType(…) method, which allows you to take a type and convert it to a generic type with the specified type parameters. That way I could dynamically pass a type into a generic without hardcoding anything. This allows the TypedClaim<T> to be set at runtime without having to code for each particular parameter. So you end up with basic logic along the lines of:

Type constructed = typeof(TypedClaim<>).MakeGenericType(new Type[] { genericParamType });

object val = Convert.ChangeType(claim.Value, genericParamType);

return Activator.CreateInstance(constructed, claim.ClaimType, val);

The Activator.CreateInstance method will construct an instance of the particular type which will eventually be passed into PropertyInfo.Value.SetValue(…).

Finally, it's time to integrate this into your web application. The best location is probably going to be through a custom ClaimsAuthenticationManager. It works like this:

public class TypedClaimsAuthenticationManager : ClaimsAuthenticationManager
{
    public override IClaimsPrincipal Authenticate(string resourceName, IClaimsPrincipal incomingPrincipal)
    {
        if (!incomingPrincipal.Identity.IsAuthenticated)
            return base.Authenticate(resourceName, incomingPrincipal);

        for (int i = 0; i < incomingPrincipal.Identities.Count; i++)
            incomingPrincipal.Identities[i] = new TypedClaimsIdentity(incomingPrincipal.Identities[i]);

        return base.Authenticate(resourceName, incomingPrincipal);
    }
}

Then to tell WIF about this new CAM you need to make a change to the web.config. Within the Microsoft.IdentityModel/Service section, add this:

<claimsAuthenticationManager type="Syfuhs.IdentityModel.TypedClaimsAuthenticationManager, Syfuhs.IdentityModel" />

By dynamically setting the values of the user object, you can create a fairly robust identity model for your application.

You can download the updated code here: typedclaimsv2.zip (6.21 kb)

You can download the original code here: typedclaims.zip (5.61 kb)

Tamper-Evident Configuration Files in ASP.NET

by Steve Syfuhs / September 28, 2011 04:00 PM

A couple weeks ago someone sent a message to one of our internal mailing lists. His message was pretty straightforward: how do you prevent modifications of a configuration file for an application [while the user has administrative rights on the machine]?

There were a couple responses including mine, which was to cryptographically sign the configuration file with an asymmetric key. For a primer on digital signing, take a look here. Asymmetric signing is one possible way of signing a file. By signing it this way the configuration file could be signed by an administrator before deploying the application, and all the application needed to validate the signature was the public key associated with the private key used to sign the file. This separated the private key from the application, preventing the configuration from being re-signed maliciously. It’s similar in theory to how code-signing works.

In the event that validation of the configuration file failed, the application would not load, or would gracefully fail and exit the next time the file was checked (or the application had an exclusive lock on the configuration file so it couldn’t be edited while running).

We are also saved the problem of figuring out the signature format because there is a well-respected XML signature schema: http://www.w3.org/2000/09/xmldsig#. WCF uses this format to sign messages. For a good code-walkthrough see Barry Dorrans’ Beginning ASP.NET Security. More on the code later here though.

Technically, this won’t prevent changes to the file, but it will prevent the application from accepting those changes. It’s kind of like those tamper-evident tags manufacturers stick on the enclosures of their equipment. It doesn’t prevent someone from opening the thing, but they will get caught if someone checks it. You’ll notice I didn’t call them “tamper-resistance” tags.

Given this problem, I went one step further and asked myself: how would I do this with a web application? A well-informed ASP.NET developer might suggest using aspnet_regiis to encrypt the configuration file. Encrypting the configuration does protect against certain things, like being able to read configuration data. However, there are a couple problems with this.

  • If I’m an administrator on that server I can easily decrypt the file by calling aspnet_regiis
  • If I’ve found a way to exploit the site, I can potentially overwrite the contents of the file and make the application behave differently
  • The encryption/decryption keys need to be shared in web farms

Consider our goal. We want to prevent a user with administrative privileges from modifying the configuration. Encryption does not help us in this case.  Signing the configuration will help though (As an aside, for more protection you encrypt the file then sign it, but that’s out of the scope of this) because the web application will stop working if a change is made that invalidates the signature.

Of course, there’s one little problem. You can’t stick the signature in the configuration file, because ASP.NET will b-itch complain about the foreign XML tag. The original application in question was assumed to have a custom XML file for it’s configuration, but in reality it doesn’t, so this problem applies there too.

There are three possible solutions to this:

  • Create a custom ConfigurationSection class for the signature
  • Create a custom configuration file and handler, and intercept all calls to web.config
  • Stick the signature of the configuration file into a different file

The first option isn’t a bad idea, but I really didn’t want to muck about with the configuration classes. The second option is, well, pretty much a bad idea in almost all cases, mainly because I’m not entirely sure you can even intercept all calls to the configuration classes.

I went with option three.

The other file has two important parts: the signature of the web.config file, and a signature for itself. This second signature prevents someone from modifying the signature for the web.config file. Our code becomes a bit more complicated because now we need to validate both signatures.

This makes us ask the question, where is the validation handled? It needs to happen early enough in the request lifecycle, so I decided to stick it into a HTTP Module, for the sake of modularity.

Hold it, you say. If the code is in a HTTP Module, then it needs to be added to the web.config. If you are adding it to the web.config, and protecting the web.config by this module, then removing said module from the web.config will prevent the validation from occurring.

Yep.

There are two ways around this:

  • Add the validation call into Global.asax
  • Hard code the addition of the HTTP Module

It’s very rare that I take the easy approach, so I’ve decided to hard code the addition of the HTTP Module, because sticking the code into a module is cleaner.

In older versions of ASP.NET you had to make some pretty ugly hacks to get the module in because it needs to happen very early in startup of the web application. With ASP.NET 4.0, an assembly attribute was added that allowed you to call code almost immediately after startup:

[assembly: PreApplicationStartMethod(typeof(Syfuhs.Security.Web.Startup), "Go")]

Within the Startup class there is a public static method called Go(). This method calls the Register() within an instance of my HttpModule. This module inherits from an abstract class called DynamicallyLoadedHttpModule, which inherits from IHttpModule. This class looks like:

public abstract class DynamicallyLoadedHttpModule : IHttpModule
{
    public void Register()
    {
        DynamicHttpApplication.RegisterModule(delegate(HttpApplication app) { return this; });
    }

    public abstract void Init(HttpApplication context);

    public abstract void Dispose();
}

The DynamicHttpApplication class inherits from HttpApplication and allows you to load HTTP modules in code. This code was not written by me. It was originally written by Nikhil Kothari:

using HttpModuleFactory = System.Func<System.Web.HttpApplication, System.Web.IHttpModule>;

public abstract class DynamicHttpApplication : HttpApplication
{
    private static readonly Collection<HttpModuleFactory> Factories = new Collection<HttpModuleFactory>();
    private static object _sync = new object();
    private static bool IsInitialized = false;

    private List<IHttpModule> modules;

    public override void Init()
    {
        base.Init();

        if (Factories.Count == 0)
            return;

        List<IHttpModule> dynamicModules = new List<IHttpModule>();

        lock (_sync)
        {
            if (Factories.Count == 0)
                return;

            foreach (HttpModuleFactory factory in Factories)
            {
                IHttpModule m = factory(this);

                if (m != null)
                {
                    m.Init(this);
                    dynamicModules.Add(m);
                }
            }
        }

        if (dynamicModules.Count != 0)
            modules = dynamicModules;

        IsInitialized = true;
    }

    public static void RegisterModule(HttpModuleFactory factory)
    {
        if (IsInitialized)
            throw new InvalidOperationException(Exceptions.CannotRegisterModuleLate);

        if (factory == null)
            throw new ArgumentNullException("factory");

        Factories.Add(factory);
    }

    public override void Dispose()
    {
        if (modules != null)
            modules.ForEach(m => m.Dispose());

        modules = null;
            
        base.Dispose();

        GC.SuppressFinalize(this);
    }
}

Finally, to get this all wired up we modify the Global.asax to inherit from DynamicHttpApplication:

public class Global : DynamicHttpApplication { ... }

Like I said, you could just add the validation code into Global (but where’s the fun in that?)…

So, now that we’ve made it possible to add the HTTP Module, lets actually look at the module:

public sealed class SignedConfigurationHttpModule : DynamicallyLoadedHttpModule
{
    public override void Init(HttpApplication context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        context.BeginRequest += new EventHandler(context_BeginRequest);
        context.Error += new EventHandler(context_Error);
    }

    private void context_BeginRequest(object sender, EventArgs e)
    {
        HttpApplication app = (HttpApplication)sender;

        SignatureValidator validator = new SignatureValidator(app.Request.PhysicalApplicationPath);

        validator.ValidateConfigurationSignatures(CertificateLocator.LocateSigningCertificate());
    }

    private void context_Error(object sender, EventArgs e)
    {
        HttpApplication app = (HttpApplication)sender;

        foreach (var exception in app.Context.AllErrors)
        {
            if (exception is XmlSignatureValidationFailedException)
            {
                // Maybe do something
                // Or don't...
                break;
            }
        }
    }

    public override void Dispose() { }
}

Nothing special here. Just hooking into the context.BeginRequest event so validation occurs on each request. There would be some performance impact as a result.

The core validation is contained within the SignatureValidator class, and there is a public method that we call to validate the signature file, ValidateConfigurationSignatures(…). This method accepts an X509Certificate2 to compare the signature against.

The specification for the schema we are using for the signature will actually encode the public key of the private key into the signature element, however we want to go one step further and make sure it’s signed by a particular certificate. This will prevent someone from modifying the configuration file, and re-signing it with a different private key. Validation of the signature is not enough; we need to make sure it’s signed by someone we trust.

The validator first validates the schema of the signature file. Is the XML well formed? Does the signature file conform to a schema we defined (the schema is defined in a Constants class)? Following that is validates the signature of the file itself. Has the file been tampered with? Following that it validates the signature of the web.config file. Has the web.config file been tampered with?

Before it can do all of this though, it needs to check to see if the signature file exists. The variable passed into the constructor is the physical path of the web application. The validator knows that the signature file should be in the App_Data folder within the root. This file needs to be here because the folder by default will not let you access anything in it, and we don’t want anyone downloading the file. The path is also hardcoded specifically so changes to the configuration cannot bypass the signature file validation.

Here is the validator:

internal sealed class SignatureValidator
{
    public SignatureValidator(string physicalApplicationPath)
    {
        this.physicalApplicationPath = physicalApplicationPath;
        this.signatureFilePath = Path.Combine(this.physicalApplicationPath, "App_Data\\Signature.xml");
    }

    private string physicalApplicationPath;
    private string signatureFilePath;

    public void ValidateConfigurationSignatures(X509Certificate2 cert)
    {
        Permissions.DemandFilePermission(FileIOPermissionAccess.Read, this.signatureFilePath);

        if (cert == null)
            throw new ArgumentNullException("cert");

        if (cert.HasPrivateKey)
            throw new SecurityException(Exceptions.ValidationCertificateHasPrivateKey);

        if (!File.Exists(signatureFilePath))
            throw new SecurityException(Exceptions.CouldNotLoadSignatureFile);

        XmlDocument doc = new XmlDocument() { PreserveWhitespace = true };
        doc.Load(signatureFilePath);

        ValidateXmlSchema(doc);

        CheckForUnsignedConfig(doc);

        if (!X509CertificateCompare.Compare(cert, ValidateSignature(doc)))
            throw new XmlSignatureValidationFailedException(Exceptions.SignatureFileNotSignedByExpectedCertificate);

        List<XmlSignature> signatures = ParseSignatures(doc);

        ValidateSignatures(signatures, cert);
    }

    private void CheckForUnsignedConfig(XmlDocument doc)
    {
        List<string> signedFiles = new List<string>();

        foreach (XmlElement file in doc.GetElementsByTagName("File"))
        {
            string fileName = Path.Combine(this.physicalApplicationPath, file["FileName"].InnerText);

            signedFiles.Add(fileName.ToUpperInvariant());
        }

        CheckConfigFiles(signedFiles);
    }

    private void CheckConfigFiles(List<string> signedFiles)
    {
        foreach (string file in Directory.EnumerateFiles(this.physicalApplicationPath, "*.config", SearchOption.AllDirectories))
        {
            string path = Path.Combine(this.physicalApplicationPath, file);

            if (!signedFiles.Contains(path.ToUpperInvariant()))
                throw new XmlSignatureValidationFailedException(string.Format(CultureInfo.CurrentCulture, Exceptions.ConfigurationFileWithoutSignature, path));
        }
    }

    private void ValidateXmlSchema(XmlDocument doc)
    {
        using (StringReader fileReader = new StringReader(Constants.SignatureFileSchema))
        using (StringReader signatureReader = new StringReader(Constants.SignatureSchema))
        {
            XmlSchema fileSchema = XmlSchema.Read(fileReader, null);
            XmlSchema signatureSchema = XmlSchema.Read(signatureReader, null);

            doc.Schemas.Add(fileSchema);
            doc.Schemas.Add(signatureSchema);

            doc.Validate(Schemas_ValidationEventHandler);
        }
    }

    void Schemas_ValidationEventHandler(object sender, ValidationEventArgs e)
    {
        throw new XmlSignatureValidationFailedException(Exceptions.InvalidSchema, e.Exception);
    }

    public static X509Certificate2 ValidateSignature(XmlDocument xml)
    {
        if (xml == null)
            throw new ArgumentNullException("xml");

        XmlElement signature = ExtractSignature(xml.DocumentElement);

        return ValidateSignature(xml, signature);
    }

    public static X509Certificate2 ValidateSignature(XmlDocument doc, XmlElement signature)
    {
        if (doc == null)
            throw new ArgumentNullException("doc");

        if (signature == null)
            throw new ArgumentNullException("signature");

        X509Certificate2 signingCert = null;

        SignedXml signed = new SignedXml(doc);
        signed.LoadXml(signature);

        foreach (KeyInfoClause clause in signed.KeyInfo)
        {
            KeyInfoX509Data key = clause as KeyInfoX509Data;

            if (key == null || key.Certificates.Count != 1)
                continue;

            signingCert = (X509Certificate2)key.Certificates[0];
        }

        if (signingCert == null)
            throw new CryptographicException(Exceptions.SigningKeyNotFound);

        if (!signed.CheckSignature())
            throw new CryptographicException(Exceptions.SignatureValidationFailed);

        return signingCert;
    }

    private static void ValidateSignatures(List<XmlSignature> signatures, X509Certificate2 cert)
    {
        foreach (XmlSignature signature in signatures)
        {
            X509Certificate2 signingCert = ValidateSignature(signature.Document, signature.Signature);

            if (!X509CertificateCompare.Compare(cert, signingCert))
                throw new XmlSignatureValidationFailedException(
                    string.Format(CultureInfo.CurrentCulture, 
                    Exceptions.SignatureForFileNotSignedByExpectedCertificate, signature.FileName));
        }
    }

    private List<XmlSignature> ParseSignatures(XmlDocument doc)
    {
        List<XmlSignature> signatures = new List<XmlSignature>();

        foreach (XmlElement file in doc.GetElementsByTagName("File"))
        {
            string fileName = Path.Combine(this.physicalApplicationPath, file["FileName"].InnerText);

            Permissions.DemandFilePermission(FileIOPermissionAccess.Read, fileName);

            if (!File.Exists(fileName))
                throw new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, Exceptions.FileNotFound, fileName));

            XmlDocument fileDoc = new XmlDocument() { PreserveWhitespace = true };
            fileDoc.Load(fileName);

            XmlElement sig = file["FileSignature"] as XmlElement;

            signatures.Add(new XmlSignature()
            {
                FileName = fileName,
                Document = fileDoc,
                Signature = ExtractSignature(sig)
            });
        }

        return signatures;
    }

    private static XmlElement ExtractSignature(XmlElement xml)
    {
        XmlNodeList xmlSignatureNode = xml.GetElementsByTagName("Signature");

        if (xmlSignatureNode.Count <= 0)
            throw new CryptographicException(Exceptions.SignatureNotFound);

        return xmlSignatureNode[xmlSignatureNode.Count - 1] as XmlElement;
    }
}

You’ll notice there is a bit of functionality I didn’t mention. Checking that the web.config file hasn’t been modified isn’t enough. We also need to check if any *other* configuration file has been modified. It’s no good if you leave the root configuration file alone, but modify the <authorization> tag within the administration folder to allow anonymous access, right?

So there is code looks through the site for any files that have the “config” extension, and if that file isn’t in the signature file, it throws an exception.

There is also a check done at the very beginning of the validation. If you pass an X509Certificate2 with a private key it will throw an exception. This is absolutely by design. You sign the file with the private key. You validate with the public key. If the private key is present during validation that means you are not separating the keys, and all of this has been a huge waste of time because the private key is not protected. Oops.

Finally, it’s important to know how to sign the files. I’m not a fan of generating XML properly, partially because I’m lazy and partially because it’s a pain to do, so mind the StringBuilder:

public sealed class XmlSigner
{
    public XmlSigner(string appPath)
    {
        this.physicalApplicationPath = appPath;
    }

    string physicalApplicationPath;

    public XmlDocument SignFiles(string[] paths, X509Certificate2 cert)
    {
        if (paths == null || paths.Length == 0)
            throw new ArgumentNullException("paths");

        if (cert == null || !cert.HasPrivateKey)
            throw new ArgumentNullException("cert");

        XmlDocument doc = new XmlDocument() { PreserveWhitespace = true };
        StringBuilder sb = new StringBuilder();

        sb.Append("<Configuration>");
        sb.Append("<Files>");

        foreach (string p in paths)
        {
            sb.Append("<File>");

            sb.AppendFormat("<FileName>{0}</FileName>", p.Replace(this.physicalApplicationPath, ""));
            sb.AppendFormat("<FileSignature><Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">{0}</Signature></FileSignature>", 
            SignFile(p, cert).InnerXml);

            sb.Append("</File>");
        }

        sb.Append("</Files>");
        sb.Append("</Configuration>");

        doc.LoadXml(sb.ToString());

        doc.DocumentElement.AppendChild(doc.ImportNode(SignXmlDocument(doc, cert), true));

        return doc;
    }

    public static XmlElement SignFile(string path, X509Certificate2 cert)
    {
        if (string.IsNullOrWhiteSpace(path))
            throw new ArgumentNullException("path");

        if (cert == null || !cert.HasPrivateKey)
            throw new ArgumentException(Exceptions.CertificateDoesNotContainPrivateKey);

        Permissions.DemandFilePermission(FileIOPermissionAccess.Read, path);

        XmlDocument doc = new XmlDocument();
        doc.PreserveWhitespace = true;
        doc.Load(path);

        return SignXmlDocument(doc, cert);
    }

    public static XmlElement SignXmlDocument(XmlDocument doc, X509Certificate2 cert)
    {
        if (doc == null)
            throw new ArgumentNullException("doc");

        if (cert == null || !cert.HasPrivateKey)
            throw new ArgumentException(Exceptions.CertificateDoesNotContainPrivateKey);

        SignedXml signed = new SignedXml(doc) { SigningKey = cert.PrivateKey };

        Reference reference = new Reference() { Uri = "" };

        XmlDsigC14NTransform transform = new XmlDsigC14NTransform();
        reference.AddTransform(transform);

        XmlDsigEnvelopedSignatureTransform envelope = new XmlDsigEnvelopedSignatureTransform();
        reference.AddTransform(envelope);
        signed.AddReference(reference);

        KeyInfo keyInfo = new KeyInfo();
        keyInfo.AddClause(new KeyInfoX509Data(cert));
        signed.KeyInfo = keyInfo;

        signed.ComputeSignature();

        XmlElement xmlSignature = signed.GetXml();

        return xmlSignature;
    }
}

To write this to a file you can call it like this:

XmlWriter writer = XmlWriter.Create(@"C:\Dev\Projects\Syfuhs.Security.Web\Syfuhs.Security.Web.WebTest\App_Data\Signature.xml");
XmlSigner signer = new XmlSigner(Request.PhysicalApplicationPath);

XmlDocument xml = signer.SignFiles(new string[] { 
@"C:\Dev\Projects\Syfuhs.Security.Web\Syfuhs.Security.Web.WebTest\Web.config",
@"C:\Dev\Projects\Syfuhs.Security.Web\Syfuhs.Security.Web.WebTest\Web.debug.config",
@"C:\Dev\Projects\Syfuhs.Security.Web\Syfuhs.Security.Web.WebTest\Web.release.config",
@"C:\Dev\Projects\Syfuhs.Security.Web\Syfuhs.Security.Web.WebTest\Account\Web.config",
@"C:\Dev\Projects\Syfuhs.Security.Web\Syfuhs.Security.Web.WebTest\test.config"
}, 
new X509Certificate2(@"C:\Dev\Projects\Syfuhs.Security.Web\Syfuhs.Security.Web.WebTest\cert.pfx", "1"));

xml.WriteTo(writer);
writer.Flush();

Now within this code, you have to pass in a X509Certificate2 with a private key, otherwise you can’t sign the files.

These processes should occur on different machines. The private key should never be on the server hosting the site. The basic steps for deployment would go something like:

1. Compile web application.
2. Configure site and configuration files on staging server.
3. Run application that signs the configuration and generates the signature file.
4. Drop the signature.xml file into the App_Data folder.
5. Deploy configured and signed application to production.

There is one final note (I think I’ve made that note a few times by now…) and that is the CertificateLocator class. At the moment it just returns a X509Certificate2 from a particular path on my file system. This isn’t necessarily the best approach because it may be possible to overwrite that file. You should store that certificate in a safe place, and make a secure call to get it. For instance a web service call might make sense. If you have a Hardware Security Module (HSM) to store secret bits in, even better.

Concluding Bits

What have we accomplished by signing our configuration files? We add a degree of trust that our application hasn’t been compromised. In the event that the configuration has been modified, the application stops working. This could be from malicious intent, or careless administrators. This is a great way to prevent one-off changes to configuration files in web farms. It is also a great way to prevent customers from mucking up the configuration file you’ve deployed with your application.

This solution was designed in a way mitigate quite a few attacks. An attacker cannot modify configuration files. An attacker cannot modify the signature file. An attacker cannot view the signature file. An attacker cannot remove the signature file. An attacker cannot remove the HTTP Module that validates the signature without changing the underlying code. An attacker cannot change the underlying code because it’s been compiled before being deployed.

Is it necessary to use on every deployment? No, probably not.

Does it go a little overboard with regard to complexity? Yeah, a little.

Does it protect against a real problem? Absolutely.

Unfortunately it also requires full trust.

Overall it’s a fairly robust solution and shows how you can mitigate certain types of risks seen in the real world.

And of course, it works with both WebForms and MVC.

You can download the full source: https://syfuhs.blob.core.windows.net/files/c6afeabc-36fc-4d41-9aa7-64cc9385280d-configurationsignaturevalidation.zip

Adjusting the Home Realm Discovery page in ADFS to support Email Addresses

by Steve Syfuhs / July 12, 2011 04:00 PM

Over on the Geneva forums a question was asked:

Does anyone have an example of how to change the HomeRealmDiscovery Page in ADFSv2 to accept an e-mail address in a text field and based upon that (actually the domain suffix) select the correct Claims/Identity Provider?

It's pretty easy to modify the HomeRealmDiscovery page, so I thought I'd give it a go.

Based on the question, two things need to be known: the email address and the home realm URI.  Then we need to translate the email address to a home realm URI and pass it on to ADFS.

This could be done a couple ways.  First it could be done by keeping a list of email addresses and their related home realms, or a list of email domains and their related home realms.  For the sake of this being an example, lets do both.

I've created a simple SQL database with three tables:

image

Each entry in the EmailAddress and Domain table have a pointer to the home realm URI (you can find the schema in the zip file below).

Then I created a new ADFS web project and added a new entity model to it:

image

From there I modified the HomeRealmDiscovery page to do the check:

//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------

using System;

using Microsoft.IdentityServer.Web.Configuration;
using Microsoft.IdentityServer.Web.UI;
using AdfsHomeRealm.Data;
using System.Linq;

public partial class HomeRealmDiscovery : Microsoft.IdentityServer.Web.UI.HomeRealmDiscoveryPage
{
    protected void Page_Init(object sender, EventArgs e)
    {
    }

    protected void PassiveSignInButton_Click(object sender, EventArgs e)
    {
        string email = txtEmail.Text;

        if (string.IsNullOrWhiteSpace(email))
        {
            SetError("Please enter an email address");
            return;
        }

        try
        {
            SelectHomeRealm(FindHomeRealmByEmail(email));
        }
        catch (ApplicationException)
        {
            SetError("Cannot find home realm based on email address");
        }
    }

    private string FindHomeRealmByEmail(string email)
    {
        using (AdfsHomeRealmDiscoveryEntities en = new AdfsHomeRealmDiscoveryEntities())
        {
            var emailRealms = from e in en.EmailAddresses where e.EmailAddress1.Equals(email) select e;

            if (emailRealms.Any()) // email address exists
                return emailRealms.First().HomeRealm.HomeRealmUri;

            // email address does not exist
            string domain = ParseDomain(email);

            var domainRealms = from d in en.Domains where d.DomainAddress.Equals(domain) select d;

            if (domainRealms.Any()) // domain exists
                return domainRealms.First().HomeRealm.HomeRealmUri;

            // neither email nor domain exist
            throw new ApplicationException();
        }
    }

    private string ParseDomain(string email)
    {
        if (!email.Contains("@"))
            return email;

        return email.Substring(email.IndexOf("@") + 1);
    }

    private void SetError(string p)
    {
        lblError.Text = p;
    }
}

 

If you compare the original code, there was some changes.  I removed the code that loaded the original home realm drop down list, and removed the code to choose the home realm based on the drop down list's selected value.

You can find my code here: http://www.syfuhs.net/AdfsHomeRealm.zip

Talking about Security Article Series

by Steve Syfuhs / June 02, 2011 04:00 PM

Over on the Canadian Solution Developer's blog I have a series on the basics of writing secure applications.  It's a bit of an introduction to all the things we should know in order to write software that doesn't contain too many vulnerabilities.

Obviously it's not a series on everything you need to know about security, but hopefully it's a starting point.  My goal is to get people to at least start talking about security in their applications.

This is the series:

SAML Protocol Extension CTP for Windows Identity Foundation

by Steve Syfuhs / May 15, 2011 04:00 PM

Earlier this morning the Geneva (WIF/ADFS) Product Team announced a CTP for supporting the SAML protocol within WIF.  WIF has supported SAML tokens since it's inception, however it hasn't supported the SAML protocol until now.  According to the team:

This WIF extension allows .NET developers to easily create claims-based SP-Lite compliant Service Provider applications that use SAML 2.0 conformant identity providers such as AD FS 2.0.

This is the first I've seen this CTP, so I decided to jump into the Quick Start solution to get a feel for what's going on.  Here is the solution hierarchy:

image

There isn't much to it.  We have the sample identity provider that generates a token for us, a relying party application (service provider), and a utilities project to help with some sample-related duties.

In most cases, we really only need to worry about the Service Provider, as the IdP probably already exists.  I think creating an IdP using this framework is for a different post.

If we consider that WIF mostly works via configuration changes to the web.config, it stands to reason that the SAML extensions will too.  Lets take a look at the web.config file.

There are three new things in the web.config that are different from a default-configured WIF application.

First we see a new configSection declaration:

<section name="microsoft.identityModel.saml" type="Microsoft.IdentityModel.Web.Configuration.MicrosoftIdentityModelSamlSection, Microsoft.IdentityModel.Protocols"/>

This creates a new configuration section called microsoft.identityModel.saml.

Interestingly, this doesn't actually contain much.  Just pointers to metadata:

<microsoft.identityModel.saml metadata="bin\App_Data\serviceprovider.xml">
    <identityProviders>
        <metadata file="bin\App_Data\identityprovider.xml"/>
    </identityProviders>
</microsoft.identityModel.saml>

Now this is a step away from WIF-ness.  These metadata documents are consumed by the extension.  They contain certificates and endpoint references:

<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:6010/IdentityProvider/saml/redirect/sso"/>

I can see some extensibility options here.

Finally, an HTTP Module is added to handle the token response:

<add name="Saml2AuthenticationModule" type="Microsoft.IdentityModel.Web.Saml2AuthenticationModule"/>

This module works similarly to the WSFederationAuthenticationModule used by WIF out of the box.

It then uses the SessionAuthenticationModule to handle session creation and management, which is the same module used by WIF.

As you start digging through the rest of the project, there isn't actually anything too surprising to see.  The default.aspx page just grabs a claim from the IClaimsidentity object and adds a control used by the sample to display SAML data.  There is a signout button though which calls the following line of code:

Saml2AuthenticationModule.Current.SignOut( "~/Login.aspx" );

In the Login.aspx page there is a sign in button that calls a similar line of code:

Saml2AuthenticationModule.Current.SignIn( "~/Default.aspx" );

All in all, this SAML protocol extension seems to making federating with a SAML IdP fairly simple and straightforward.

Making the X509Store more Friendly

by Steve Syfuhs / May 12, 2011 04:00 PM

When you need to grab a certificate out of a Windows Certificate Store, you can use a class called X509Store.  It's very simple to use:

X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);

store.Open(OpenFlags.ReadOnly);

X509Certificate2Collection myCerts = store.Certificates.Find(X509FindType.FindByThumbprint, "...", false);

store.Close();

However, I don't like this open/close mechanism.  It reminds me too much of Dispose(), except I can't use a using statement.  There are lots of arguments around whether a using statement is a good way of doing things and I'm in the camp of yes, it is.  When they are used properly they make code a lot more logical.  It creates a scope for an object explicitly.  I want to do something like this:

using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser, OpenFlags.ReadOnly))
{
    X509Certificate2Collection myCerts = store.Certificates.Find(X509FindType.FindByThumbprint, "...", false);
}

The simple solution would be to subclass this, implement IDisposable, and overwrite some of the internals.  The problem though is that someone on the .NET team thought it would be wise to seal the class.  Crap.  Okay, lets create a new class:

public class X509Store2 : IDisposable
{
    private X509Store store;

    public X509Store2(IntPtr storeHandle, OpenFlags flags)
    {
        store = new X509Store(storeHandle);
        store.Open(flags);
    }

    public X509Store2(StoreLocation storeLocation, OpenFlags flags)
    {
        store = new X509Store(storeLocation);
        store.Open(flags);
    }

    public X509Store2(StoreName storeName, OpenFlags flags)
    {
        store = new X509Store(storeName);
        store.Open(flags);
    }

    public X509Store2(string storeName, OpenFlags flags)
    {
        store = new X509Store(storeName);
        store.Open(flags);
    }

    public X509Store2(StoreName storeName, StoreLocation storeLocation, OpenFlags flags)
    {
        store = new X509Store(storeName, storeLocation);
        store.Open(flags);
    }

    public X509Store2(string storeName, StoreLocation storeLocation, OpenFlags flags)
    {
        store = new X509Store(storeName, storeLocation);
        store.Open(flags);
    }

    public X509Certificate2Collection Certificates { get { return store.Certificates; } }

    public StoreLocation Location { get { return store.Location; } }

    public string Name { get { return store.Name; } }

    public IntPtr StoreHandle { get { return store.StoreHandle; } }

    public void Add(X509Certificate2 certificate)
    {
        store.Add(certificate);
    }

    public void AddRange(X509Certificate2Collection certificates)
    {
        store.AddRange(certificates);
    }

    private void Close()
    {
        store.Close();
    }

    private void Open(OpenFlags flags)
    {
        store.Open(flags);
    }

    public void Remove(X509Certificate2 certificate)
    {
        store.Remove(certificate);
    }
    public void RemoveRange(X509Certificate2Collection certificates)
    {
        store.RemoveRange(certificates);
    }

    public void Dispose()
    {
        this.Close();
    }
}

At this point I've copied all the public members of the X509Store class and called their counterparts in the store.  I've also set Open() and Close() to private so they can't be called.  In theory I could just remove them, but I didn't.

Enjoy!

PrairieDevCon Identity and Security Presentations on June 13th and 14th

by Steve Syfuhs / January 25, 2011 04:00 PM

Sometime last week I got confirmation that my sessions were accepted for PrairieDevCon!  The schedule has not yet been announced, but here are the two sessions I will be presenting:

Changing the Identity Game with the Windows Identity Foundation

Identity is a tricky thing to manage. These days every application requires some knowledge of the user, which inevitably requires users to log in and out of the applications to prove they are who they are as well as requiring the application to keep record of the accounts. There is a fundamental shift in the way we manage these users and their accounts in a Claims Based world. The Windows Identity Foundation builds on top of a Claim based architecture and helps solve some real world problems. This session will be a discussion on Claims as well as how WIF fits into the mix.
Track: Microsoft, Security
Style: Lecture
Speaker: Steve Syfuhs

Building a Security Token Service In the Time It Takes to Brew a Pot of Coffee

One of the fundamental pieces of a Claims Based Authentication model is the Security Token Service. Using the Windows Identity Framework it is deceivingly simple to build one, so in this session we will.
Track: Microsoft, Security
Style: Lecture
Speaker: Steve Syfuhs

What is PrairieDevCon?

The Prairie Developer Conference is the conference event for software professionals in the Canadian prairies!

Featuring more than 30 presenters, over 60 sessions, and including session styles such as hands-on coding, panel discussions, and lectures, Prairie Developer Conference is an exceptional learning opportunity!
Register for our June 2011 event today!

Okay, how much $$$?

Register early and take advantage of Early Bird pricing!
Get 50% off the post-conference price when you bundle it with your conference registration!

Conference Conference +
Post-Conf Workshop
Bundle
Until February 28 $299.99 $449.99
Until March 31 $399.99 $549.99
Until April 30 $499.99 $649.99
May and June $599.99 $749.99
Post-Conference Workshop Only $299.99

For more information check out the registration section.

// About

Steve is a renaissance kid when it comes to technology. He spends his time in the security stack.