[Edit: attached the sample app files]
In short: I show a simple class that checks the signature of self issued tokens sent on a normal HTTP connection (as opposed to HTTPS); the same class takes care of generating a UniqueID and giving access to claims. It basically covers for the NoSSL case the core functions that TokenHelper offers for the SSL case.
Today for few hours I found myself living in the early 90s. I agreed with Mario to meet at Victor's, the only place where coffee meets the bar of the Italian community here in Redmond, but he wasn't there. I did the obvious thing, I called his mobile: instead of connecting with him, I talk with his wife: she tells me that he forgot the phone at home, and he was already out. That happened all the time before everybody had a cell (for my circle of friends in Italy, that means '98), but now? Luckily I had my UMPC in the borsello, so I pulled it out and fired up Visual Studio. Few days ago we were chatting about the fact that we have no samples that work without HTTPS: the TokenHelper assumes that the incoming token is encrypted, which is not the case in the NoSSL scenario. It seemed engaging enough to fill the wait... so I wrote a little proof of concept that shows how an RP could handle a token sent in clear.
Remember the long post I made in September about the same topic? There I was making the point that while the content of the token may now be visible (at least in the selfissued case, the one I will consider in this post), the way of authenticating the caller is unchanged: "Ultimately what the RP verifies is the capability of the subject of signing the token with a certain key. As long as there's a signature on the token this check can still be performed, HTTPS or not". So the first thing we need to do is
writing a class that will tell us if the signature is good.
The second thing we need to do is verifying that the signature has been performed by somebody I recognize: without repeating too many details here (check out this post), basically
I need to be able to generate a suitable UniqueID.
That concludes the authentication functionality, if what I need is just credentials (see this, and the part II when I'll post it) I could stop here. But that would not be that claim oriented, would it. Hence I want to do a last thing, and that would be
giving easy access to the claims received with the incoming token.
Alright, let's see what we can do. Create a new website in visual studio, and create a new application in IIS which points to the phisical path of the website project. Note that, of course, you don't need to configure HTTPS 🙂
Add a simple HTML file, with a form for invoking CardSpace & POSTing to the page where we will run our token processing logic. Code of Default.htm:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
value="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier" />
The page where we will host the token processing logic, Default.aspx, is very straightforward too; anyway there's at least one interesting point:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs"
In the Page directive there's a validateRequest = false. Without that directive, ASP.NET blocked the incoming request because it was unhappy with all that XML it was receiving in the token. I am sure that disabling that filter is bad in term of security, here I do it just for going further with the example. I shall read more about it and get back to you if I discover something.
All right! Let's fake the fact that we already have our token processing helper class: how would we use it from the Default.aspx code behind (pardon, beside)? As follows:
1: using System;
2: using System.Configuration;
3: using System.Data;
4: using System.Linq;
5: using System.Web;
6: using System.Web.Security;
7: using System.Web.UI;
8: using System.Web.UI.HtmlControls;
9: using System.Web.UI.WebControls;
10: using System.Web.UI.WebControls.WebParts;
11: using System.Xml.Linq;
12: using System.IdentityModel.Claims;
13: using System.IO;
15: public partial class _Default : System.Web.UI.Page
17: protected void Page_Load(object sender, EventArgs e)
19: string xmlToken;
20: xmlToken = Request.Params["xmlToken"];
21: NoSSLTokenHelper tokenHelper = new NoSSLTokenHelper(xmlToken);
23: Response.Write("Welcome " + tokenHelper[ClaimTypes.GivenName] +
24: "! <br>The token you sent was correctly signed, thank you.<br>");
25: Response.Write("Your PPID with us is" + tokenHelper[ClaimTypes.PPID] +
26: ", while the UniqueID (thumbprint of the hash of PPID and signing key) is "
28: Response.Write("<br><br> here there's the raw token:<br><br>");
If you came to my session at TechEd Barcelona (and TR6 for colleagues) or you read my posts about what's coming (part 1, 2 &3) you know that the future looks good and way less verbose than shown here: anyway for now we have to write some code. Turns out that in the NoSSL case it's not much anyway, actually very little :). Some comments:
Line 19-21: if you ever used the SSL RP samples, you'll see that here we are doing exactly what we were used to do. We retrieve the incoming token from a request variable, and we use it for instantiating our helper class. For being consistent with the classic TokenHelper, if we go past line 21 this means that the signature of the token is intact. We'll have to take that into account when we will write the class, we need to put the signature check in the constructor.
Line 23 and 24: here we show easy access to the claim values. It's somewhat simpler that the old tokenhelper, and uses strings instead of Claims. The reason is that this is a quick&dirty sample, however IMHO the simpler we make things for ASP.NET developers, the happier they will be.
Line 27: we need an easy way of obtaining the UniqueID. In this example we just show it in the response, normally we'd use it for verifying that this token comes from a known customer. Again, here it's a simple string as opposed to the EndpointIdentity thing that the classic tokenhelper offers.
OK, this is what will use our new class, now it's time to write it. As mentioned before, here I want to concentrate on the selfissued case: that actually simplifies a lot. The tokens obtained by the use of personal cards are SAML assertions, and the framework offers a nice class that represents just that. What's more, the constructor of the class itself takes care of verifying the integrity of the signature (if present). That's great, in few lines of code we solve the first problem!
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.IdentityModel.Tokens;
6: using System.IdentityModel.Claims;
7: using System.IO;
8: using System.Xml;
9: using System.ServiceModel.Security;
10: using System.ServiceModel.Security.Tokens;
11: using System.Security.Cryptography;
13: public class NoSSLTokenHelper
15: SamlAssertion theSamlAssertion;
17: public NoSSLTokenHelper(string xmlTokenString)
19: SamlAssertion samlassertion = new SamlAssertion();
20: SamlSerializer serializer = new SamlSerializer();
21: WSSecurityTokenSerializer wssserializer = new WSSecurityTokenSerializer();
22: SecurityContextSecurityTokenResolver scstr = new SecurityContextSecurityTokenResolver(4096, true);
24: byte tokenbytes = ASCIIEncoding.ASCII.GetBytes(xmlTokenString.ToCharArray());
25: StreamReader sreader = new StreamReader(new MemoryStream(tokenbytes));
26: XmlDictionaryReader dreader = XmlDictionaryReader.CreateDictionaryReader(new XmlTextReader(sreader));
28: theSamlAssertion = serializer.LoadAssertion(dreader, wssserializer, scstr);
No need to make many comments here, it's just a matter of finding out the various steps necessary for deserializing the string into a SamlAssertion. Being Saturday afternoon, I couldn't bug Govind or HongMei like I often do in those cases... but intellisense and MSDN worked their magic and I managed on my own.
Note that if the signature is compromised line 28 will raise an exception, otherwise we are good: hence we fulfilled the requirement of verifying the signature in the constructor of our helper class.
Now: the access to claim values. The SamlAssertion class does not have claimsets & claims, but the SamlAttribute is a close kin:
31: public string this[string index]
35: string name = index.Substring(index.LastIndexOf('/') + 1);
36: string nspace = index.Substring(0, index.LastIndexOf('/'));
37: foreach (SamlAttribute sa in ((SamlAttributeStatement)theSamlAssertion.Statements).Attributes)
39: if ((sa.Name == name) && (sa.Namespace == nspace))
40: return sa.AttributeValues;
42: throw new IndexOutOfRangeException("The token does not contain the claim " + index);
Note that here I don't do any error check, and I assume that the attributevalues are always "scalar". Given the fact that I am restricting this to the selfissued scenario, it's a pretty reasonable assumption (but no excuse for the lack of error checking, of course).
Finally, we have to extract the UniqueID. Here we enter in the arcane of the RSA implementation, and I can't get away with a simple "the UniqueID is a function of the signing key and the PPID"; I actually need to implement this function.
46: public string GetUniqueID()
48: string uniqueID;
49: byte issuerKey = ((RSACryptoServiceProvider)((RsaSecurityToken)theSamlAssertion.SigningToken).Rsa).ExportCspBlob(false);
50: byte PPIDbytes = Encoding.UTF8.GetBytes(this[ClaimTypes.PPID]);
51: byte thumbprintData = new byte[PPIDbytes.Length + issuerKey.Length];
52: issuerKey.CopyTo(thumbprintData, 0);
53: PPIDbytes.CopyTo(thumbprintData, issuerKey.Length);
55: using (SHA256 sha = new SHA256Managed())
57: uniqueID = Convert.ToBase64String(sha.ComputeHash(thumbprintData));
60: return uniqueID;
Luckily the code for generating the UniqueID in the original tokenhelper is pretty clear, and I was able to transport it without too many bad words (I admit I spent some immediatewindow time before figuring out line 49). Note that here I assume the presence of a PPID claim, whereas the old tokenhelper gives a choice via configuration.It's easy to modify, if you don't like the current generation procedure. Also note that we don't do anything of the fancy EndpointIdentity gimmick that tokenhelper does, we just give back the string of the hash.
Believe it or not, we don't need anything else: let's give to the website a test spin. This is how the first page looks like:
Note the address bar, classic HTTP. Clicking on the big button we get to the familiar CardSpace UI, with a twist.
The twist is the big warning we get about the fact that what we send is in clear. Let's send a card and..
Tadahhh. We did it. We processed an unencrypted token: deserialized, checked the signature, extracted the uniqueID and the claims. Hurray!
Anyway, are we really sure that our signature check is sound? There is a simple way to test it: we can write a small app that uses the same NoSSLTokenHelper class, and feed it with purposefully tampered SAML.
I'll spare you the code of the app, it's a simple winform with a textbox for pasting the SAML and a button for starting the check. Let's copy the token XML text from the page in the former figure and let's paste it in our little tester. The signature check succeeds:
Now let's add an innocent modification, like deleting one of the "t" from "Vittorio" (people make that mistake all the time anyway :-)):
Well, judging from the enraged exception message we get it seems that our signature check performs as expected.
Ahh, after all the recent philosophy posts some code was in order. Of course please double check everything before using the sample: that should always be the casee, but especially this time since this was mainly developed in a noisy cafe' (with the main purpose of killing some time, rather than doing anything super cool) and without much researching.
Ah, and kudos for my little Fujitsu U810: not only I used it for developing the samples, it is the PC from which I am writing this blog post (via live writer) and on which I did all the screen capturing & processing. It has 1Gb of RAM, a 4200RPM disk, and a loooow powered processor... running Vista Ultimate. The next time you read on forums that vista needs monstrous hardware for working, you'll know that here there's a counterexample 🙂