Decrypting a Security Token
CardSpace provides users with the ability to manage their digital identities, and with Internet Explorer 7.0 (or other browsers supporting Information Cards), Web sites can request a digital identity from the user. This identity is transported in the form of an encrypted security token. Decrypting the token to use the claims contained within the token can be done with the Token
sample class. This topic examines the token and how it works.
Web Server Setup
To work through the exercises, Web site configuration is necessary. The configuration is done using the following installation batch file provided in the sample folder:
Setup.bat
For more information about installation of the Web site, and some troubleshooting tips, see Installing CardSpace Sample Certificates.
Getting the token
The code from the sample How to Use Windows CardSpace with Internet Explorer 7.0 demonstrates the HTML code required to display the Identity Selector.
Files used:
Sample.htm
The Identity Selector is displayed by using either an <object>
element or a binary behavior object. This example uses the <object>
element and some JavaScript to request the token from the user and display it in a <textarea>
before submitting it to the Web site.
The Sample.htm file:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Sample 2</title>
<object type="application/x-informationcard" name="_xmlToken">
<param name="tokenType" value="urn:oasis:names:tc:SAML:1.0:assertion" />
<param name="issuer" value="https://schemas.xmlsoap.org/ws/2005/05/identity/issuer/self" />
<param name="requiredClaims"
value="https://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
https://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname
https://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
https://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier" />
</object>
<script language="javascript">
function GetIdentity(){
var xmltkn=document.getElementById("_xmltoken");
var thetextarea = document.getElementById("xmltoken");
thetextarea.value = xmltkn.value ;
}
</script>
</head>
<body>
<form id="form1" method="post" action="login.aspx">
<button name="go" id="go" onclick="javascript:GetIdentity();">Click here to get the token.</button>
<button type="submit">Click here to send the card to the server</button>
<textarea cols=100 rows=20 id="xmltoken" name="xmlToken" ></textarea>
</form>
</body>
</html>
Once the user clicks the Submit button, the token is posted in a form to the server, where it must be decrypted and verified before the claims can be used.
The login.aspx
page that accepts the posted token in C#.
<%@ Page Language="C#" Debug="true" ValidateRequest="false" %>
<%@ Import Namespace="Microsoft.IdentityModel.Samples" %>
<%@ Import Namespace="Microsoft.IdentityModel.TokenProcessor" %>
<script runat="server">
protected void ShowError(string text) {
fields.Visible = false;
errors.Visible = true;
errtext.Text = text;
}
protected void Page_Load(object sender, EventArgs e) {
string xmlToken;
xmlToken = Request.Params["xmlToken"];
if (xmlToken == null || xmlToken.Equals(""))
ShowError("Token presented was null");
else
{
Token token = new Token (xmlToken);
givenname.Text = token.Claims[SelfIssued.GivenName];
surname.Text = token.Claims[SelfIssued.Surname];
email.Text = token.Claims[SelfIssued.EmailAddress];
uid.Text = token.UniqueID;
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Login Page</title>
</head>
<body>
<form id="form1" runat="server">
<div runat="server" id="fields">
Given Name:<asp:Label ID="givenname" runat="server" Text=""></asp:Label><br/>
Surname:<asp:Label ID="surname" runat="server" Text=""></asp:Label><br/>
Email Address:<asp:Label ID="email" runat="server" Text=""></asp:Label><br/>
Unique ID:<asp:Label ID="uid" runat="server" Text=""></asp:Label><br/>
</div>
<div runat="server" id="errors" visible="false">
Error:<asp:Label ID="errtext" runat="server" Text=""></asp:Label><br/>
</div>
</form>
</body>
</html>
The login.aspx
page that accepts the posted token in VB.NET:
<%@ Page Language="VB" Debug="true" ValidateRequest="false" %>
<%@ Import Namespace="Microsoft.IdentityModel.Samples" %>
<%@ Import Namespace="Microsoft.IdentityModel.TokenProcessor" %>
<script runat="server">
Protected Sub ShowError(ByVal text As String)
fields.Visible = False
errors.Visible = True
errtext.Text = text
End Sub
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
Dim xmlToken As String
xmlToken = Request.Params("xmlToken")
If xmlToken = Nothing Or xmlToken.Equals("") Then
ShowError("Token presented was null")
Else
Dim token As New Token(xmlToken)
givenname.Text = token.Claims(ClaimTypes.GivenName)
surname.Text = token.Claims(ClaimTypes.Surname)
email.Text = token.Claims(ClaimTypes.Email)
uid.Text = token.UniqueID
End If
End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Login Page</title>
</head>
<body>
<form id="form1" runat="server">
<div runat="server" id="fields">
Given Name:<asp:Label ID="givenname" runat="server" Text=""></asp:Label><br/>
Surname:<asp:Label ID="surname" runat="server" Text=""></asp:Label><br/>
Email Address:<asp:Label ID="email" runat="server" Text=""></asp:Label><br/>
Unique ID:<asp:Label ID="uid" runat="server" Text=""></asp:Label><br/>
</div>
<div runat="server" id="errors" visible="false">
Error:<asp:Label ID="errtext" runat="server" Text=""></asp:Label><br/>
</div>
</form>
</body>
</html>
The Token
class handles the decryption, verification and claim extraction of the encrypted XML token.
Processing the token: Examining the XML Encryption format
The token posted from the browser is encrypted using a W3C XML Encryption. The document that describes the schema is XML Encryption Syntax and Processing. The schemas are Core Schema and XML Encryption. The following example is of a typical encrypted token.
<enc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
xmlns:enc="http://www.w3.org/2001/04/xmlenc#">
<enc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" />
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<e:EncryptedKey xmlns:e="http://www.w3.org/2001/04/xmlenc#">
<e:EncryptionMethod
Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p">
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
</e:EncryptionMethod>
<KeyInfo>
<o:SecurityTokenReference
xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-
1.0.xsd">
<o:KeyIdentifier
ValueType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-
1.1#ThumbprintSHA1"
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-
message-security-1.0#Base64Binary">
1H3mV/pJAlVZAst/Dt0rqbBd67g=
</o:KeyIdentifier>
</o:SecurityTokenReference>
</KeyInfo>
<e:CipherData>
<e:CipherValue>
YJHp...==</e:CipherValue>
</e:CipherData>
</e:EncryptedKey>
</KeyInfo>
<enc:CipherData>
<enc:CipherValue>
ctct...9A==</enc:CipherValue>
</enc:CipherData>
</enc:EncryptedData>
Looking at the constituent parts of the posted XML, the root element is the <enc:EncryptedData>
. This contains the three things required to get access to the token.
<enc:EncryptedData>
<enc:EncryptionMethod />
<KeyInfo />
<enc:CipherData />
</enc:EncryptedData>
First, the <enc:EncryptionMethod>
contains the method used to encrypt the token.
<enc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" />
In this case, it is AES-256-cbc—a symmetric key encryption algorithm—in which both parties use the same key for encryption and decryption. The symmetric key is randomly generated by the originator and is called a transient key. This is encrypted and stored in the <e:EncryptedKey>
element, which is wrapped by a <KeyInfo>
element.
<KeyInfo >
<e:EncryptedKey />
<e:EncryptionMethod / >
<KeyInfo />
<e:CipherData />
</e:EncryptedKey>
</KeyInfo>
It is preferable to encrypt the token with a symmetric algorithm and send its key encrypted with the token, because asymmetric encryption is slower than symmetric encryption and encrypting data larger than the size of the key with an asymmetric key is inadvisable. Encrypting only the key with the asymmetric algorithm substantially lowers the amount of processing required at the server.
The transient key is encrypted with the relying party’s public key (from their certificate). The transient key’s encryption method is found in the <e:EncryptionMethod>
element.
<e:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p">
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
</e:EncryptionMethod>
In this case, the RSA-OAEP-MGF1P algorithm is used for encryption. The key for this operation is found in the following <KeyInfo>
element.
<KeyInfo>
<o:SecurityTokenReference xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-
wssecurity-secext-1.0.xsd">
<o:KeyIdentifier
ValueType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-
1.1#ThumbprintSHA1"
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-
security-1.0#Base64Binary">
1H3mV/pJAlVZAst/Dt0rqbBd67g=
</o:KeyIdentifier>
</o:SecurityTokenReference>
</KeyInfo>
The <o:KeyIdentifier>
allows the sender to inform the relying party which certificate’s key was used to encrypt the symmetric key, by putting its thumbprint (base64 encoded) into the element.
The relying party then uses its private key to decrypt the data in the <e:CipherData>
/<e:CypherValue>
element.
<e:CipherData>
<e:CipherValue>
YJHp...==</e:CipherValue>
</e:CipherData>
After retrieving the symmetric key, the token itself is decrypted using the symmetric key.
<enc:CipherData>
<enc:CipherValue>
ctct...9A==</enc:CipherValue>
</enc:CipherData>
Highlights of the Token Processor Code
The following highlights of the decryption process should be noted to understand the decryption process. To view the full process, consult the source code of the Token
class.
The Token
class constructor uses the decryptToken
method to perform the decryption of the encrypted XML data passed into the constructor.
private static byte[] decryptToken(string xmlToken)
To iterate through the XML data, an XmlReader
is used.
XmlReader reader = new XmlTextReader(new StringReader(xmlToken));
When searching for the XML elements, very little flexibility is permitted, to fail quickly (using an ArgumentException
) in the event of an invalid token. To begin, it is necessary to find the <EncryptionMethod>
element, where the Algorithm
element is accessed to retrieve the encryption method of the token.
if (!reader.ReadToDescendant(XmlEncryptionStrings.EncryptionMethod,
XmlEncryptionStrings.Namespace))
throw new ArgumentException("Cannot find token EncryptedMethod.");
encryptionAlgorithm = reader.GetAttribute(XmlEncryptionStrings.Algorithm).GetHashCode();
Next, find the <EncryptionMethod>
element for the transient key, again getting the Algorithm
, which is stored as its HashCode
for faster lookup.
if (!reader.ReadToFollowing(XmlEncryptionStrings.EncryptionMethod,
XmlEncryptionStrings.Namespace))
throw new ArgumentException("Cannot find key EncryptedMethod.");
m_keyEncryptionAlgorithm = reader.GetAttribute(XmlEncryptionStrings.Algorithm).GetHashCode();
The next element is the <KeyIdentifier>
, which contains the thumbprint of the certificate (This is required to decrypt the symmetric key), and is extracted from the base64 string.
if (!reader.ReadToFollowing(WSSecurityStrings.KeyIdentifier, WSSecurityStrings.Namespace))
throw new ArgumentException("Cannot find Key Identifier.");
reader.Read();
thumbprint = Convert.FromBase64String(reader.ReadContentAsString());
The <CypherValue>
element contains the symmetric key (base64 encoded), in its encrypted form.
if (!reader.ReadToFollowing(XmlEncryptionStrings.CipherValue, XmlEncryptionStrings.Namespace))
throw new ArgumentException("Cannot find symmetric key.");
reader.Read();
symmetricKeyData = Convert.FromBase64String(reader.ReadContentAsString());
The <CypherValue>
that contains the actual encrypted token.
if (!reader.ReadToFollowing(XmlEncryptionStrings.CipherValue, XmlEncryptionStrings.Namespace))
throw new ArgumentException("Cannot find encrypted security token.");
reader.Read();
securityTokenData = Convert.FromBase64String(reader.ReadContentAsString());
Ensure that the reader is closed to free up resources.
reader.Close();
Encryption of the security token is supported by one of two symmetric algorithms (AES and Triple DES). Use the encryption algorithm URI as a lookup.
SymmetricAlgorithm alg = null;
X509Certificate2 certificate = FindCertificate(thumbprint );
foreach( int i in Aes )
if (encryptionAlgorithm == i)
{
alg= new RijndaelManaged();
break;
}
if ( null == alg )
foreach (int i in TripleDes)
if (encryptionAlgorithm == i)
{
alg = new TripleDESCryptoServiceProvider();
break;
}
if (null == alg)
throw new ArgumentException("Could not determine Symmetric Algorithm");
To get the symmetric key, decrypt it with the private key.
alg.Key=(certificate.PrivateKey as RSACryptoServiceProvider).Decrypt(symmetricKeyData,true);
Using the algorithm that was discovered, decrypt the token, with the symmetric algorithm.
int ivSize = alg.BlockSize / 8;
byte[] iv = new byte[ivSize];
Buffer.BlockCopy(securityTokenData, 0, iv, 0, iv.Length);
alg.Padding = PaddingMode.ISO10126;
alg.Mode = CipherMode.CBC;
ICryptoTransform decrTransform = alg.CreateDecryptor(alg.Key, iv);
byte[] plainText = decrTransform.TransformFinalBlock(securityTokenData, iv.Length,
securityTokenData.Length iv.Length);
decrTransform.Dispose();
return plainText;
}
Deserializing and Authenticating the Token
To use the embedded token, once decrypted, .NET Framework 3.0 deserializes (through the WSSecurityTokenSerializer) and authenticates it using the SamlSecurityTokenAuthenticator. Currently the Token
class supports SAML tokens. In the event that other token types are required, the developer must provide an Authenticator to support it. Once the authenticator has validated it, the Token
class extracts the claims into a usable form.
public Token(String xmlToken)
{
byte[] decryptedData = decryptToken(xmlToken);
XmlReader reader = new XmlTextReader(
new StreamReader(new MemoryStream(decryptedData), Encoding.UTF8));
m_token = (SamlSecurityToken)WSSecurityTokenSerializer.DefaultInstance.ReadToken(
reader, null);
SamlSecurityTokenAuthenticator authenticator =
new SamlSecurityTokenAuthenticator(new List<SecurityTokenAuthenticator>(
new SecurityTokenAuthenticator[]{
new RsaSecurityTokenAuthenticator(),
new X509SecurityTokenAuthenticator() }), MaximumTokenSkew);
if (authenticator.CanValidateToken(m_token))
{
ReadOnlyCollection<IAuthorizationPolicy> policies = authenticator.ValidateToken(m_token);
m_authorizationContext = AuthorizationContext.CreateDefaultAuthorizationContext(policies);
FindIdentityClaims();
}
else
{
throw new Exception("Unable to validate the token.");
}
}
Usage of the Token Class
The following table describes the properties that are exposed by the Token
class that extract the claims from the Security Token.
Name | Description |
---|---|
|
A ClaimSet of the Identity Claims in the token. |
|
A AuthorizationContext generated from the token. |
|
Gets the UniqueID (IdentityClaim) of the token. By default, this uses the PPID and the Issuer's Public Key and hashes them together to generate a UniqueID. To use a different field, add the following line of code.
Replace the value with the URI for your unique claim. |
|
A read-only string collection of the claims in the token. Provides support for an indexed claims accessor.
|
|
Returns the issuer's Identity Claim (typically, the public key of the issuer). |
© 2007 Microsoft Corporation. All rights reserved.