December 2010 Archives

Moving to Sweden

| | Comments (4) | TrackBacks (0)
I never write about personal things on this blog, but I'm making an exception here because I'm on the verge of a major life change that I thought some readers might be interested in knowing about.

After graduating from high school, I lived and studied in England for a couple of years. During that time, I met a young woman from Sweden who I married shortly after. We then moved to the States, where I'm from, to finish our educations and start our life together. More than a decade later, we now have two small babies which has caused us to reconsider our decision about where to live. Now that I'm (more or less) done w/ my master's, we have decided to move to Sweden. Our stuff shipped out in a container three weeks ago and we fly out in a couple days! To keep busy over there, I have accepted a position w/ Ping Identity and will be working for their CTO as a sr. technical architect. We are really excited to go and very grateful to our friends and family in Portland who have enriched our lives while we've lived here.

Next post from Sweden :-)
Here's one that ate up way too much of my time. Imagine this scenario: You're trying to use ADFS to transform a security token from a foreign domain into the one that your app is in. To do this, you have ADFS setup as an active RP-STS and you present it with a SAML assertion that you got from the IP-STS in the other domain. This means you have created a claims provider and relying party trust in ADFS. Now, imagine you can get the assertion from the IP-STS just fine, but you always get this error in ADFS's trace log when you send it over to it:

Source : Microsoft.IdentityModel
EventId : 1
Data :
<TraceRecord xmlns="http://schemas.microsoft.com/2009/10/IdentityModel/TraceRecord" Severity="Warning"><Description>RequestFailed: TrustNamespace=http://docs.oasis-open.org/ws-sx/ws-trust/200512, Action=http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue, Exception=Microsoft.IdentityModel.SecurityTokenService.RequestFailedException: ID4007: The symmetric key inside the requested security token must be encrypted. To fix this, either override the SecurityTokenService.GetScope() method to assign appropriate value to Scope.EncryptingCredentials or set Scope.SymmetricKeyEncryptionRequired to false.
   at Microsoft.IdentityModel.Threading.AsyncResult.End(IAsyncResult result)
   at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustServiceContract.ProcessCoreAsyncResult.End(IAsyncResult ar)
   at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustServiceContract.EndProcessCore(IAsyncResult ar, String requestAction, String responseAction, String trustNamespace)</Description><AppDomain>Microsoft.IdentityServer.ServiceHost.exe</AppDomain></TraceRecord>

ProcessId : 3156
ThreadId : 11
When I read that, I thought it meant that the symmetric key sent in my RST to ADFS wasn't encrypted. I spent hours examining HTTP traces of my requests, pulling out SHA-1 thumbprints from KeyIdentifiers in SubjectConfirmation elements, decoding/encoding them, and confirming that my IP-STS had encrypted the symmetric key w/ ADFS's encryption cert. I almost lost my mind till Yang Yu pointed out to me that the message wasn't about the symmetric key presented to ADFS; it meant that ADFS wouldn't send back a different symmetric key to my app because it couldn't encrypt the payload of the RSTR. The fix was simple: In the relying party trust, add an encryption cert.

OMG, that took like a 100 coffee breaks to figure out :(
Note that this code is only intended for demo and debugging purposes. It doesn't verify the signature of the security token in the RSTR and is not intended to be used in production scenarios.

If you need to convert a RequestSecurityTokenResponse to a ClaimsIdentity, here's one way:

private static ClaimsIdentity GetClaimsIdentity(RequestSecurityTokenResponse rstr)
{
var rstrXml = rstr.RequestedSecurityToken.SecurityTokenXml;
var xnm = new XmlNamespaceManager(rstrXml.OwnerDocument.NameTable);

xnm.AddNamespace(Saml11Constants.Prefix, Saml11Constants.Namespace);

var attributeStatement = rstrXml.SelectSingleNode("saml:AttributeStatement", xnm);
var attributes = attributeStatement.SelectNodes("saml:Attribute", xnm);
var claims = new List<Claim>();

for (var i = 0; attributes != null && i < attributes.Count; i++)
{
var attribute = attributes[i];
var claimType = attribute.Attributes["AttributeNamespace"].Value + attribute.Attributes["AttributeName"].Value;
var value = attribute.SelectSingleNode("saml:AttributeValue/text()", xnm).Value;

claims.Add(new Claim(claimType, value ?? ""));
}

var subject = attributeStatement.SelectSingleNode("saml:Subject/saml:NameIdentifier/text()", xnm).Value;

claims.Add(new Claim(ClaimTypes.Name, subject));

return new ClaimsIdentity(claims);
}

It assumes that the assertion has an AttributeStatement in it, that that has Attribute elements, that the assertion isn't encrypted, etc. If that isn't necessarily true in your case, adjust as needed. (This code, as all code on my Web site, is licensed under the GNU Public License v. 2.)