Descifrado de un token de seguridad

Download sample

CardSpace proporciona a los usuarios la capacidad de administrar sus identidades digitales y con Internet Explorer 7.0 (u otros exploradores que admiten tarjetas de información), los sitios Web pueden solicitar una identidad digital al usuario. Esta identidad se transporta en forma de un testigo de seguridad cifrado. El descifrado del token para utilizar las notificaciones contenidas dentro del token se puede hacer con la clase de ejemplo Token. En este tema se examina el token y cómo funciona.

Configuración del servidor Web

Para realizar los ejercicios, es necesario configurar el sitio Web. La configuración se hace utilizando el archivo por lotes de instalación siguiente proporcionado en la carpeta de ejemplo:

Setup.bat

Para obtener más información sobre la instalación del sitio Web y la solución a algunos problemas, vea Instalación de certificados de ejemplo de CardSpace.

Obtener el token

El código del ejemplo Cómo usar Windows CardSpace con Internet Explorer 7.0 muestra el código HTML requerido para mostrar el selector de identidad.

Archivos utilizados:

Sample.htm

El selector de identidad se muestra utilizando un elemento <object> o un objeto de comportamiento binario. Este ejemplo utiliza el elemento <object> y JavaScript para solicitar el token al usuario y mostrarlo en <textarea> antes de enviarlo al sitio Web.

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

Cuando el usuario hace clic en el botón Enviar, el token se expone en un formulario al servidor, donde se debe descifrar y comprobar antes de que se puedan utilizar las notificaciones.

La página login.aspx que acepta el token expuesto 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 página login.aspx que acepta el token expuesto 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 clase Token administra el descifrado, comprobación y extracción de notificación del token XML cifrado.

Procesar el token: examinar el formato de cifrado de XML

El token expuesto del explorador se cifra utilizando un cifrado W3C XML. El documento que describe el esquema es Sintaxis y procesamiento del cifrado XML. Los esquemas son Esquema principal y Cifrado XML. El ejemplo siguiente es de un token cifrado típico.

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

Examinando las partes constituyentes del XML expuesto, el elemento raíz es <enc:EncryptedData>. Esto contiene las tres cosas requeridas para obtener acceso al token.

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

Primero, <enc:EncryptionMethod> contiene el método utilizado para cifrar el token.

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

En este caso, es AES-256-cbc –un algoritmo de cifrado de clave simétrica– donde ambas partes utilizan la misma clave para el cifrado y descifrado. La clave simétrica es generada de forma aleatoria por el autor y denomina una clave transitoria. Se cifra y almacena en el elemento <e:EncryptedKey>, que es ajustado por un elemento <KeyInfo>.

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

Es preferible cifrar el token con un algoritmo simétrico y enviar su clave cifrada con el token, porque el cifrado asimétrico es más lento que el cifrado simétrico y los datos de cifrado mayores que el tamaño de la clave con una clave asimétrica son desaconsejables. Al cifrar sólo la clave con el algoritmo asimétrico, se reduce sustancialmente la cantidad de procesamiento requerida en el servidor.

La clave transitoria se cifra con la clave pública del usuario de confianza (de su certificado). El método de cifrado de la clave transitoria se encuentra en el elemento <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>

En este caso, el algoritmo RSA OAEP MGF1P se utiliza para el cifrado. La clave para esta operación se encuentra en el elemento <KeyInfo> siguiente.

<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> permite al remitente informar el usuario de confianza qué clave de certificado se utilizó para cifrar la clave simétrica, colocando su huella digital (codificada con base64) en el elemento.

El usuario de confianza utiliza a continuación su clave privada para descifrar los datos en el elemento <e:CipherData>/<e:CypherValue>.

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

Después de recuperar la clave simétrica, el propio token se descifra utilizando la clave simétrica.

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

Puntos destacados del código de procesador de token

Los puntos destacados siguientes del proceso de descifrado se deberían tener en cuenta para entender el proceso de descifrado. Para ver el proceso completo, consulte el código fuente de la clase Token.

El Token constructor de clase utiliza el método decryptToken para realizar el descifrado de los datos XML cifrados pasados al constructor.

private static byte[] decryptToken(string xmlToken)

Para ejecutar una iteración en los datos XML, se utiliza un XmlReader.

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

Al buscar los elementos XML, se permite una flexibilidad muy reducida, para que se produzca rápidamente un error (utilizando una ArgumentException) en el caso de un token no válido. Para comenzar, es necesario encontrar el elemento <EncryptionMethod>, donde se tiene acceso al elemento Algorithm para recuperar el método de cifrado del token.

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

A continuación, busque el elemento <EncryptionMethod> para la clave transitoria, obteniendo de nuevo Algorithm, que está almacenado como HashCode para la búsqueda más rápida.

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

El elemento siguiente es <KeyIdentifier>, que contiene la huella digital del certificado (necesaria para descifrar la clave simétrica), y que se extrae de la cadena base64.

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

El elemento <CypherValue> contiene la clave simétrica (codificada en base64), en su forma cifrada.

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

<CypherValue> que contiene el token cifrado actual.

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

Asegúrese de que el lector esté cerrado para liberar recursos.

reader.Close();

Uno de dos algoritmos simétricos (AES y Triple DES) admite el cifrado del testigo de seguridad. Utilice el URI del algoritmo de cifrado a modo de búsqueda.

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

Para obtener la clave simétrica, descífrela con la clave privada.

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

Con el algoritmo detectado, descifre el token, con el algoritmo simétrico.

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

Deserializar y autenticar el token

Para utilizar el token incrustado, una vez descifrado, .NET Framework 3.0 lo deserializa (a través de WSSecurityTokenSerializer) y lo autentica utilizando SamlSecurityTokenAuthenticator. Actualmente la clase Token admite los tokens de SAML. En el caso de que se requieran otros tipos de token, el programador debe proporcionar un autenticador para admitirlos. Cuando el autenticador ha realizado la validación, la clase Token extrae las notificaciones en una forma utilizable.

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

Uso de la clase del token

La tabla siguiente describe las propiedades que son expuestas por la clase Token que extrae las notificaciones del token de seguridad.

Nombre Descripción

IdentityClaims

ClaimSet de las notificaciones de identidad en el token.

AuthorizationContext

AuthorizationContext generado por el token.

UniqueID

Obtiene el UniqueID (IdentityClaim) del token.

De forma predeterminada, utiliza el PPID y la clave pública del emisor y aplica conjuntamente un algoritmo hash para generar un UniqueID.

Para utilizar un campo diferente, agregue la siguiente línea de código.

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

Reemplace el valor con el URI para su notificación única.

Claims

Una colección de cadenas de sólo lectura de las notificaciones en el token. Proporciona la compatibilidad para un descriptor de acceso de notificaciones indizado.

securityToken.Claims[ClaimsTypes.PPID]

IssuerIdentityClaim

Devuelve la notificación de identidad del emisor (normalmente, la clave pública del emisor).

Footer image

Enviar comentarios sobre este tema a Microsoft.

Copyright © 2007 Microsoft Corporation. Reservados todos los derechos.