Delen via


Decrypting a Security Token

Download sample

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

IdentityClaims

A ClaimSet of the Identity Claims in the token.

AuthorizationContext

A AuthorizationContext generated from the token.

UniqueID

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.

<add name="IdentityClaimType"  
     value="https://schemas.xmlsoap.org/ws/2005/05/identity/
     claims/privatepersonalidentifier" />

Replace the value with the URI for your unique claim.

Claims

A read-only string collection of the claims in the token. Provides support for an indexed claims accessor.

securityToken.Claims[ClaimsTypes.PPID]

IssuerIdentityClaim

Returns the issuer's Identity Claim (typically, the public key of the issuer).

Footer image

© 2007 Microsoft Corporation. All rights reserved.