When I first started implementing security token services the documentation was minimal (ok, so that hasn’t changed much). The client wanted to log where their cards were being used, and allow specific claim access based on the identity of the relying party.
When you create a managed card you can add the wsp:AppliesTo element to it which instructions the identity selector to send relying party information when requesting a token. A well behaved selector will warn users that the card provider is receiving this information(the screen shot to the left shows CardSpace and the warning it gives users).
The AppliesTo information is sent as part of the RST in the form of a WS Policy statement which looks something like this;
<wsp2004:AppliesTo xmlns:wsp2004="http://schemas.xmlsoap.org/ws/2004/09/policy">
<EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
<Address>https://www.fabrikam.com/Demos/Reading/signin2.html</Address>
<Identity xmlns="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity">
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Data>
<X509Certificate>MII...Ed</X509Certificate>
<X509Certificate>MII...==</X509Certificate>
</X509Data>
</KeyInfo>
</Identity>
</EndpointReference>
</wsp2004:AppliesTo>
However as soon as I added the requirement for the endpoint information encryption of the returned token stopped. What was going on? During my trip to the mothership last December I got a chance to sit down with the Information Card team and this was the first question I asked. In order to get at the answer we need to look at the behaviour of the identity selector. Grab the selector interop documentation and read section 8.3. This documents how a selector encrypts tokens for self issued cards.
Now consider; if we don’t require the endpoint information the token is encrypted before being sent to the relying party. How does this happen, when the STS doesn’t know what to encrypt against? The answer is the identity selector does it. The STS sends its response unencrypted to the selector which then strips out the token, encrypts it against the relying party’s SSL certificate (assuming it has one of course, remember you don’t have to have one) and forwards it on. It is the selector that is responsible for encryption. However as soon as a request gets the endpoint information then a selector will assume that the STS copes with encryption. Why? Well you don’t have to use the SSL certificate at all if you are a managed STS. You could encrypt with a custom client certificate, PGP, ROT13 (not really), anything you like, so long as the relying party code knows how to decrypt it. Obviously in order to support a wide client base you should mirror the process that the identity selector would go through if you hadn’t asked for the endpoint details; you should carefully consider how you will support relying parties with custom decryption code should you stray from the standardised path.
Encrypting in the standard way involves XML encryption; The XML encryption profile that must be used for encrypting the key and the token is as follows;
- Uses the RSA-OAEP key wrap method identified by the algorithm identifier “http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p” for encrypting the encryption key.
- Uses the AES256 with CBC encryption method identified by the algorithm “http://www.w3.org/2001/04/xmlenc#aes256-cbc” for encrypting the token. The padding method used is as per the PKCS-7 standard in which the number of octets remaining in the last block is used as the padding octet value.
- The ds:KeyInfo element is present in the encrypted key specifying the encryption key information in the form of a security token reference.
Basically you need to create a symmetric signing key, encrypt it against the public key of the X509 certificate sent with the endpoint information and create a reference to it. You also need to sign the encrypted token.
.NET 3.0 provides methods to construct an EndpointAddress object from a WS Policy endpoint reference; via the ReadFrom method. From there we can retrieve the SSL certificate of the endpoint as follows
if (null != _rst.RelyingPartyEndpoint && _rst.RelyingPartyEndpoint.Uri.Scheme == "https")
{
X509CertificateEndpointIdentity x509Identity;
x509Identity = (X509CertificateEndpointIdentity) _rst.RelyingPartyEndpoint.Identity;
X509Certificate2 endpointCert = x509Identity.Certificates[0];}
Now we have everything we need to encrypt out token before it is sent.
If all this seems like too much hard work then wait a couple of weeks and there will be a nice surprise on CodePlex ... (I know, that’s a tease.)