Déchiffrement d'un jeton de sécurité

Download sample

CardSpace donne aux utilisateurs la possibilité de gérer leurs identités numériques et, avec Internet Explorer 7.0 (ou d'autres navigateurs qui prennent en charge les Cartes d'informations), les sites Web peuvent réclamer une identité numérique à l'utilisateur. Cette identité est transportée sous la forme d'un jeton de sécurité chiffré. Déchiffrer le jeton pour utiliser les revendications qu'il contient peut se faire à l'aide de la classe d'exemple Token. Cette rubrique examine le jeton et son fonctionnement

Installation du serveur Web

Il est nécessaire de configurer un site Web pour effectuer les exercices. La configuration est exécutée à l'aide du fichier batch d'installation suivant, fourni dans le dossier exemple :

Setup.bat

Pour plus d'informations sur l'installation du site Web, et quelques conseils en matière de dépannage, consultez Installation de certificats pour les exemples CardSpace.

Obtention du jeton

Le code de l'exemple Comment utiliser Windows CardSpace avec Internet Explorer 7.0 montre le code HTML requis pour afficher le sélecteur d'identité.

Fichiers utilisés :

Sample.htm

Le sélecteur d'identité est affiché à l'aide d'un élément <object> ou d'un objet de comportement binaire. Cet exemple utilise l'élément <object> et du code JavaScript pour demander le jeton à l'utilisateur et l'afficher dans un élément <textarea> avant de le soumettre au site Web.

Le fichier sample.htm :

<!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>

Lorsque l'utilisateur clique sur le bouton Envoyer, le jeton est publié dans un formulaire sur le serveur, où il doit être déchiffré et vérifié avant que les revendications puissent être utilisées.

La page login.aspx qui accepte le jeton publié en 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>

La page login.aspx qui accepte le jeton publié en 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>

La classe Token gère le déchiffrement, la vérification et l'extraction de revendication du jeton XML chiffré.

Traitement du jeton : Examen du format de chiffrement XML

Le jeton publié depuis le navigateur est chiffré à l'aide du mode de chiffrement W3C XML. Le document qui décrit le schéma est XML Encryption Syntax and Processing. Les schémas sont Core Schema et XML Encryption. (pages pouvant être en anglais) L'exemple suivant illustre un jeton chiffré typique.

<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>

Si l'on regarde les parties qui composent le XML publié, l'élément racine est <enc:EncryptedData>. Il contient les trois choses requises pour accéder au jeton.

<enc:EncryptedData>
  <enc:EncryptionMethod />
  <KeyInfo /> 
  <enc:CipherData />
</enc:EncryptedData>

En premier lieu, <enc:EncryptionMethod> contient la méthode utilisée pour chiffrer le jeton.

  <enc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" />

Dans le cas présent, il s'agit d'AES-256-cbc, un algorithme de chiffrement de clé symétrique, dans lequel les deux parties utilisent la même clé pour le chiffrement et le déchiffrement. La clé symétrique est générée de manière aléatoire par l'expéditeur et est appelée clé transitoire. Elle est chiffrée et stockée dans l'élément <e:EncryptedKey>, qui est lui-même encapsulé par un élément <KeyInfo>.

<KeyInfo >
  <e:EncryptedKey />
    <e:EncryptionMethod / >
    <KeyInfo />
    <e:CipherData />
  </e:EncryptedKey>
</KeyInfo>

Il est préférable de chiffrer le jeton avec un algorithme symétrique et d'envoyer sa clé chiffrée avec le jeton, car le chiffrement asymétrique est plus lent que le chiffrement symétrique. Par ailleurs, il n'est pas conseillé de chiffrer des données plus volumineuses que la taille de la clé avec une clé asymétrique. Chiffrer uniquement la clé avec l'algorithme asymétrique réduit substantiellement la quantité de traitement requise au niveau du serveur.

La clé transitoire est chiffrée avec la clé publique de la partie de confiance (provenant de leur certificat). La méthode de chiffrement de la clé transitoire se trouve dans l'élément <e:EncryptionMethod>.

<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>

Dans le cas présent, l'algorithme RSA-OAEP-MGF1P est utilisé pour le chiffrement. La clé utilisée pour cette opération se trouve dans l'élément <KeyInfo> suivant.

<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>

<o:KeyIdentifier> permet à l'expéditeur d'indiquer à la partie de confiance la clé de certificat qui a été utilisée pour chiffrer la clé symétrique, en appliquant son empreinte numérique (codée en base 64) dans l'élément.

La partie de confiance utilise ensuite sa clé privée pour déchiffrer les données dans l'élément <e:CipherData>/<e:CypherValue>.

<e:CipherData>
    <e:CipherValue>
    YJHp...==</e:CipherValue>
</e:CipherData>

Après avoir récupéré la clé symétrique, le jeton lui-même est déchiffré à l'aide de la clé symétrique.

<enc:CipherData>
    <enc:CipherValue>
    ctct...9A==</enc:CipherValue>
</enc:CipherData>

Points importants du code de traitement du jeton

Les points importants suivants du processus de déchiffrement doivent être pris en compte pour comprendre le processus de déchiffrement. Pour consulter le processus complet, consultez le code source de la classe Token.

Le constructeur de classe Token utilise la méthode decryptToken pour effectuer le déchiffrement des données XML chiffrées passées au constructeur.

private static byte[] decryptToken(string xmlToken)

Un élément XmlReader est utilisé pour parcourir les données XML.

XmlReader reader = new XmlTextReader(new StringReader(xmlToken));

Très peu de souplesse est autorisée lors de la recherche des éléments XML, afin d'échouer rapidement (avec un élément ArgumentException) en cas de jeton non valide. Pour commencer, il faut trouver l'élément <EncryptionMethod>, où l'on accède à l'élément Algorithm pour récupérer la méthode de chiffrement du jeton.

if (!reader.ReadToDescendant(XmlEncryptionStrings.EncryptionMethod,  
                             XmlEncryptionStrings.Namespace))
  throw new ArgumentException("Cannot find token EncryptedMethod.");
encryptionAlgorithm = reader.GetAttribute(XmlEncryptionStrings.Algorithm).GetHashCode();

Recherchez ensuite l'élément <EncryptionMethod> pour la clé transitoire, en récupérant à nouveau l'élément Algorithm, stocké comme son HashCode pour faciliter la consultation.

if (!reader.ReadToFollowing(XmlEncryptionStrings.EncryptionMethod, 
                            XmlEncryptionStrings.Namespace))
   throw new ArgumentException("Cannot find key EncryptedMethod.");
m_keyEncryptionAlgorithm = reader.GetAttribute(XmlEncryptionStrings.Algorithm).GetHashCode();

L'élément suivant est <KeyIdentifier>, qui contient l'empreinte numérique du certificat (requis pour déchiffrer la clé symétrique), et est extrait de la chaîne en base 64.

if (!reader.ReadToFollowing(WSSecurityStrings.KeyIdentifier, WSSecurityStrings.Namespace))
  throw new ArgumentException("Cannot find Key Identifier.");
reader.Read();
thumbprint = Convert.FromBase64String(reader.ReadContentAsString());

L'élément <CypherValue> contient la clé symétrique (codée en base 64), sous sa forme chiffrée.

if (!reader.ReadToFollowing(XmlEncryptionStrings.CipherValue, XmlEncryptionStrings.Namespace))
    throw new ArgumentException("Cannot find symmetric key.");
reader.Read();
symmetricKeyData = Convert.FromBase64String(reader.ReadContentAsString());

L'élément <CypherValue> qui contient le jeton chiffré.

if (!reader.ReadToFollowing(XmlEncryptionStrings.CipherValue, XmlEncryptionStrings.Namespace))
  throw new ArgumentException("Cannot find encrypted security token.");
reader.Read();
securityTokenData = Convert.FromBase64String(reader.ReadContentAsString());

Veillez à ce que le lecteur soit fermé pour libérer des ressources.

reader.Close();

Le chiffrement du jeton de sécurité est pris en charge par un algorithme symétrique sur deux possibles (AES et Triple DES). Utilisez l'URI de l'algorithme de chiffrement comme élément de consultation.

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");

Pour obtenir la clé symétrique, déchiffrez-la avec la clé privée.

alg.Key=(certificate.PrivateKey as RSACryptoServiceProvider).Decrypt(symmetricKeyData,true);

À l'aide de l'algorithme découvert, déchiffrez le jeton, avec l'algorithme symétrique.

  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;
}

Désérialisation et authentification du jeton

Pour utiliser le jeton incorporé, une fois déchiffré, .NET Framework 3.0 le désérialise (via le WSSecurityTokenSerializer) et l'authentifie à l'aide de l'SamlSecurityTokenAuthenticator. Actuellement, la classe Token prend en charge les jetons SAML. Dans le cas où d'autres types de jeton sont requis, le développeur doit fournir un authentificateur pour le prendre en charge. Une fois validée par l'authentificateur, la classe Token extrait les revendications dans un formulaire utilisable.

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.");
  }
}

Utilisation de la classe de jeton

La table suivante décrit les propriétés exposées par la classe Token qui extrait les revendications du jeton de sécurité.

Nom Description

IdentityClaims

Un ClaimSet des revendications d'identité extraites du jeton.

AuthorizationContext

Un AuthorizationContext généré depuis le jeton.

UniqueID

Récupère le UniqueID (IdentityClaim) du jeton.

Par défaut, il utilise le PPID et la clé publique de l'émetteur et les hache ensemble pour générer un UniqueID.

Pour utiliser un autre champ, ajoutez la ligne de code suivante :

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

Remplacez la valeur par l'URI de votre revendication unique.

Claims

Une collection de chaînes en lecture seule des revendications du jeton. Assure la prise en charge d'un accesseur de revendications indexé.

securityToken.Claims[ClaimsTypes.PPID]

IssuerIdentityClaim

Retourne la revendication d'identité de l'émetteur (en général, sa clé publique).

Footer image

Envoyer des commentaires sur cette rubrique à Microsoft.

Copyright ©2007 par Microsoft Corporation. Tous droits réservés.