MSDN Magazine > Inicio > Todos los números > 2007 > March >  Seguridad de .NET: Compatibilidad con certifica...
Seguridad de .NET
Compatibilidad con certificados en las aplicaciones con .NET Framework 2.0
Dominick Baier

En este artículo se analizan los siguientes temas:
  • Almacén de certificados de Windows
  • Clases de certificados en .NET
  • Validación, SSL, servicios web y firma de código
  • Firma y cifrado de datos
En este artículo se utilizan las siguientes tecnologías:
.NET Framework 2.0
Descargar el código de este artículo: Certificates2007_03.exe (160 KB)
Examinar el código en línea
Los certificados se usan en muchas situaciones a través de Microsoft® .NET Framework, desde la comunicación segura hasta la firma de código para directivas de seguridad. .NET Framework 2.0 ha introducido una compatibilidad renovada para certificados y ha agregado un espacio de nombres completamente nuevo para operaciones de cifrado con certificados que cumplen los estándares. En este artículo, trataré el trasfondo de los certificados y el almacén de certificados de Windows®. También le mostraré cómo trabajar con las API de certificados y cómo las usa Framework para implementar características de seguridad.
Un "certificado" es realmente un archivo codificado ASN.1 (Abstract Syntax Notation One) que contiene una clave pública e información adicional acerca de dicha clave y del propietario. Además, un certificado tiene un período de validez y se firma con otra clave (el denominado emisor) que se usa para ofrecer una garantía de autenticidad de esos atributos y, lo que es más importante, la clave pública. Considere ASN.1 como un tipo de XML binario. Al igual que XML, también tiene reglas de codificación, tipos seguros y etiquetas; sin embargo, se tratan de valores binarios que no se suelen corresponder con ningún carácter imprimible.
Para que este tipo de archivo se pueda intercambiar entre sistemas, se requiere un formato estándar. Es decir, X. 509 (en su versión 3 actualmente), que se ha descrito en RFC 3280 (tools.ietf.org/html/rfc3280). X.509 no establece el tipo de clave incrustada del certificado, pero el algoritmo RSA es actualmente el algoritmo cifrado y asimétrico de uso más extendido.
Comenzaré por contar una breve historia. La palabra RSA es el acrónimo correspondiente a los apellidos de los tres inventores de este algoritmo: Ron Rivest, Adi Shamir y Len Adleman. Entre los tres formaron una empresa, RSA Security, que publicó varios documentos estándar denominados Public Key Cryptography Standards (PKCS). Estos documentos describen varios aspectos de criptografía.
Entre estos documentos, uno de los más populares, PKCS #7, define un formato binario para datos firmados y cifrados denominado Sintaxis de cifrado de mensajes (CMS). Actualmente CMS se usa en muchos protocolos conocidos de seguridad, incluidos Capa de sockets seguros (SSL) y Extensiones seguras multipropósito al correo de Internet (S/MIME). Como es un estándar, también es el formato seleccionado cuando las aplicaciones necesitan intercambiar los datos firmados y cifrados entre varias partes. Los documentos PKCS están disponibles en el sitio web de RSA Laboratories en (www.rsasecurity.com/rsalabs/node.asp?id=2124).

Cómo obtener un certificado
Existen varias formas de adquirir un certificado. Cuando se intercambian archivos, los certificados suelen aparecer en uno de los dos formatos. Los archivos con la extensión .cer se firman como archivos ASN.1 en el formato X.509v3. Contienen una clave pública y la información adicional que he mencionado anteriormente. Esto es lo que ofrece a sus socios o amigos para que puedan usar la clave pública para cifrarle los datos.
También puede encontrar los archivos con la extensión .pfx (Intercambio de información personal). Un archivo .pfx contiene un certificado y la clave privada correspondiente (el formato se describe en el estándar PKCS #12). Dichos archivos son considerablemente confidenciales y se suelen usar para importar pares de claves a un servidor o para realizar copias de seguridad. Al exportar los pares de claves, Windows ofrece la posibilidad de cifrar el archivo .pfx mediante una contraseña; deberá volver a proporcionar dicha contraseña al importar el par de claves.
También puede generar certificados propios. La forma de generarlos depende del uso que vayan a tener. Para situaciones normales de Internet, donde no sabe quién está en el mismo nivel, normalmente solicita un certificado desde una entidad de certificación (CA) comercial. Este enfoque tiene la ventaja de que estas conocidas CA ya tienen la confianza de Windows y de otros sistemas operativos (y exploradores) que son compatibles con certificados y SSL. Como resultado, no deberá hacer un intercambio de claves CA.
Para B2B y escenarios de intranet, puede usar una CA interna. Los servicios de Certificate Server se incluyen en Windows 2000 y Windows Server® 2003. Junto con Active Directory®, esta funcionalidad le permite distribuir certificados fácilmente a través de una organización. En breve, le voy a mostrar cómo solicitar certificados a una CA privada.
En ocasiones, durante la fase de desarrollo se puede encontrar en situaciones en las que los enfoques descritos no funcionen. Por ejemplo, si necesita rápidamente un certificado para realizar pruebas, puede usar makecert.exe. Esta herramienta, incluida en el SDK de .NET Framework, genera los pares de certificados y claves. Existe una herramienta similar, denominada selfssl.exe, en el kit de recursos de IIS. Especializada en crear pares de claves SSL, sirve también para configurar IIS en un solo paso mediante dicho par de claves.

Almacén de certificados de Windows
Los certificados y las claves privadas correspondientes se pueden almacenar en distintos dispositivos, como discos duros, tarjetas inteligentes y testigos de USB. Windows ofrece una capa de abstracción, denominada almacén de certificados, para unificar el acceso a los certificados independientemente de dónde se almacenen. Siempre que el dispositivo de hardware tenga un proveedor de servicios criptográficos (CSP) compatible con Windows, puede tener acceso a los datos almacenados en el mismo mediante la API del almacén de certificados.
El almacén de certificados se encuentra en el perfil de usuario. Esto permite usar ACL en las claves de una cuenta específica. Cada almacén se divide en contenedores. Por ejemplo, hay un contenedor denominado Personal, donde se almacenan los certificados propios (los que tienen una clave privada asociada). El contenedor Entidades emisoras raíz de confianza, conserva los certificados de todas las CA en las que confía. El contenedor Otras personas conserva los certificados de las personas con las que establece comunicaciones seguras. Y así sucesivamente. La forma más sencilla de obtener un almacén de certificados es mediante la ejecución de certmgr.msc.
También hay un almacén de equipo, que usa las cuentas de equipo de Windows (RED, SERVICIO LOCAL y SISTEMA LOCAL), o bien si desea compartir certificados o claves en las cuentas. Las aplicaciones de ASP.NET usan siempre el almacén de equipo; para aplicaciones de escritorio, normalmente debe instalar certificados en el almacén de usuario.
Sólo los administradores pueden administrar los almacenes de cuenta de servicio y de equipo. Para ello, debe iniciar Microsoft Management Console (mmc.exe) y agregar el complemento Certificados. Ahí podrá seleccionar el almacén que va a administrar. La figura 1 muestra una captura del complemento de MMC.
Figura 1 Complemento MMC Certificados (Hacer clic en la imagen para ampliarla)
Además de permitirle importar, exportar y buscar certificados, el complemento también le permite solicitar certificados de una CA interna de la empresa. Sólo debe hacer clic con el botón secundario en el contenedor personal y seleccionar Todas las tareas | Solicitar certificado. A continuación, el equipo local genera un par de claves de RSA y envía la parte de la clave pública que se debe firmar a la CA. Windows agrega el certificado firmado al almacén de certificados y la clave privada correspondiente a un contenedor de claves. El certificado se vincula al contenedor de claves mediante un atributo de almacenamiento.
Los contenedores de claves privadas se insertan en la ACL de forma segura para la cuenta correspondiente o SISTEMA LOCAL. Esto constituye un problema cuando desea obtener acceso a las claves almacenadas en el perfil de equipo desde ASP.NET o desde otras cuentas de usuario. He escrito una herramienta que puede usar para modificar las ACL del archivo contenedor (está disponible en www.leastprivilege.com/HowToGetToThePrivateKeyFileFromACertificate.aspx).
Las CA comerciales y de Windows también presentan interfaces web para solicitar certificados. Para estas situaciones, un control de ActiveX® en Internet Explorer® suele generar las claves y las importa al almacén del usuario actual. Por regla general, cuando desea que un usuario o servicio tengan acceso a un certificado, dispone de dos opciones: importarlo al almacén correspondiente o solicitarlo al iniciar sesión como dicho usuario.

Trabajar con certificados
Los certificados se usan en varias situaciones en .NET Framework; en algunas ocasiones, toda esta funcionalidad depende de la clase X509Certificate procedente del espacio de nombres System.Security.X509Certificates. Si lo analiza más detenidamente, descubrirá también una clase de certificado que termina en 2. Esto se debe a que .NET Framework 1.x tenía una representación de los certificados X.509 denominada X509Certificate. Esta clase tenía una funcionalidad limitada y compatibilidad nula para operaciones de cifrado. En la versión 2.0, se agregó una clase nueva denominada X509Certificate2. Ésta se deriva de X509Certificate y agrega muchas capacidades. Puede convertir en todos los sentidos según sea necesario; sin embargo, en la medida de lo posible, debe usar la última versión.

Obtención de acceso a certificados
Puede recuperar directamente certificados del sistema de archivos. Sin embargo, es preferible recuperarlos del almacén de certificados. Para crear una instancia de X509Certificate2 a partir de un archivo .cer, sólo debe pasar el nombre de archivo al constructor:
X509Certificate2 cert1 = new X509Certificate2("alice.cer");
También puede cargar certificados desde los archivos .pfx. Sin embargo, como he mencionado anteriormente, los archivos .pfx se pueden proteger mediante contraseñas, contraseña que debe proporcionar como SecureString. SecureString cifra la contraseña internamente e intenta minimizar la exposición de la misma en la memoria, los archivos de paginación y en los archivos de bloqueo. Por este motivo, sólo puede agregar un único carácter (tipo de valor) a la cadena cada vez. El código de la figura 2, que deshabilita el eco de consola y devuelve SecureString, es útil si desea preguntar a sus usuarios una contraseña desde la consola.
private SecureString GetSecureStringFromConsole()
{
    SecureString password = new SecureString();

    Console.Write("Enter Password: ");
    while (true)
    {
        ConsoleKeyInfo cki = Console.ReadKey(true);

        if (cki.Key == ConsoleKey.Enter) break;
        else if (cki.Key == ConsoleKey.Escape) 
        {
            password.Dispose();
            return null;
        }
        else if (cki.Key == ConsoleKey.Backspace)
        {
            if (password.Length != 0)
                password.RemoveAt(password.Length - 1);
        }
        else password.AppendChar(cki.KeyChar);
    }

    return password;
}

En el artículo "Administración de credenciales con .NET Framework 2.0" (puede estar en inglés) (disponible en msdn.microsoft.com/library/en-us/dnnetsec/html/credmgmt.asp), Kenny Kerr incluyó código para convertir el resultado del cuadro de diálogo de credenciales habitual de Windows en SecureString. Independientemente de cómo lo obtenga, SecureString se podrá pasar al constructor X509Certificate2 para cargar el archivo .pfx, como se muestra a continuación:
X509Certificate2 cert2 = new X509Certificate2("alice.pfx", password);
Para obtener acceso al almacén de certificados de Windows, use la clase X509Store. En el constructor, proporcione la ubicación del almacén (usuario o equipo actual) y el nombre del mismo. Puede usar una cadena o la enumeración StoreName para especificar el contenedor que desea abrir. Tenga en cuenta que los nombres internos no coinciden siempre con los que contiene el complemento MMC. El contenedor Personal responde al nombre Mi, mientras que Otras personas se convierte en la libreta de direcciones.
Una vez que tenga una instancia válida de X509Store, puede buscar, recuperar, eliminar y agregar certificados. Salvo para situaciones de implementación, probablemente empleará más a menudo la funcionalidad de búsqueda. Puede buscar certificados según distintos criterios, incluidos el nombre de asunto, el número de serie, la huella digital, el emisor y el período de validez. Si recupera certificados mediante programación en las aplicaciones desde el almacén, debe usar una sola propiedad, por ejemplo, el identificador de clave de asunto. La huella digital también es única, pero tenga en cuenta que es un valor hash SHA-1 del certificado y cambiará si, por ejemplo, el certificado se renueva. El código de la figura 3 muestra una forma genérica para buscar certificados.
static void Main(string[] args)
{
    // search for the subject key id
    X509Certificate2 cert = FindCertificate(
      StoreLocation.CurrentUser, StoreName.My, 
      X509FindType.FindBySubjectKeyIdentifier, 
      "21f2bf447298e83056a69eb02ebe9085ed97f10a");
}

static X509Certificate2 FindCertificate(
    StoreLocation location, StoreName name,
    X509FindType findType, string findValue)
{
    X509Store store = new X509Store(name, location);
    try
    {
        // create and open store for read-only access
        store.Open(OpenFlags.ReadOnly);

        // search store
        X509Certificate2Collection col = store.Certificates.Find(
          findType, findValue, true);

        // return first certificate found
        return col[0];
    }
        // always close the store
    finally { store.Close(); }
}

Una vez que tenga una instancia de X509 Certificate2, puede inspeccionar las diferentes propiedades del certificado (como el nombre de asunto, las fechas de caducidad, el emisor y el nombre descriptivo). La propiedad HasPrivateKey indica si existe una clave privada asociada. Las propiedades PrivateKey y PublicKey devuelven la clave correspondiente como instancia de RSACryptoServiceProvider.
Para importar un certificado, llame al método Add en la instancia de X509Store. Cuando especifique un nombre de almacén que no existe en el constructor del almacén, se creará un contenedor nuevo. A continuación, le muestro la forma de importar un certificado de un archivo alice.cer a un contenedor nuevo denominado Test:
static void ImportCert()
{
    X509Certificate2 cert = new X509Certificate2("alice.cer");
    X509Store store = new X509Store("Test", StoreLocation.CurrentUser);
    try
    {
        store.Open(OpenFlags.ReadWrite);
        store.Add(cert);
    } 
    finally { store.Close(); }
}

Visualización de detalles de certificado y del selector de certificados
Windows muestra dos cuadros de diálogo estándar para trabajar con certificados: uno para mostrar detalles de certificado (propiedades y ruta de acceso de certificación) y otro para permitir a los usuarios seleccionar un certificado de una lista. Puede obtener acceso a estos diálogos mediante los dos métodos estáticos de la clase X509Certificate2UI: SelectFromCollection y DisplayCertificate.
Para mostrar una lista de certificados, debe rellenar la clase X509Certificate2Collection y pasarla a SelectFromCollection. Es muy habitual permitir a un usuario seleccionar uno de los certificados personales en el almacén. Para ello, sólo debe pasar la propiedad Certificates de una instancia de X509Store abierta. También puede controlar el título del cuadro de diálogo, un mensaje y si se permiten varias selecciones. El método DisplayCertificate muestra el mismo cuadro de diálogo que se muestra al hacer doble clic en un archivo .cer del Explorador de Windows. La figura 4 muestra el cuadro de diálogo usado para seleccionar un certificado y la figura 5 proporciona el código correspondiente.
private static X509Certificate2 PickCertificate(
  StoreLocation location, StoreName name)
{
    X509Store store = new X509Store(name, location);
    try
    {
        store.Open(OpenFlags.ReadOnly);
        
        // pick a certificate from the store
        X509Certificate2 cert =  
            X509Certificate2UI.SelectFromCollection(
                store.Certificates, "Caption", 
                "Message", X509SelectionFlag.SingleSelection)[0];

        // show certificate details dialog
        X509Certificate2UI.DisplayCertificate(cert);
        return cert;
    }
    finally { store.Close(); }
}

Figura 4 Cuadro de diálogo para selección de certificados (Hacer clic en la imagen para ampliarla)

Validación de certificados
Hay algunos criterios a tener en cuenta al validar un certificado, en especial, la parte emisora (por norma general, sólo se confía en certificados publicados por una CA de la lista de confianza de CA) y su validez actual (los certificados pueden dejar de ser válidos, por ejemplo, cuando caducan o la CA emisora los revoca). Puede usar la clase X509Chain para comprobar estas propiedades. Mediante esta clase, puede especificar una directiva para comprobar la validez; por ejemplo, puede solicitar una CA raíz de confianza o especificar si desea comprobar las listas de revocación en línea o locales. Si necesita comprobar certificados que se han usado para firmar datos, es importante comprobar si el certificado era válido cuando se calculó la firma; para ello, X509Chain le permite cambiar el tiempo de verificación.
Después de construir una directiva, llame al método Build para obtener información acerca del resultado de la validación en la propiedad ChainStatus. Si hay varios errores de validación, puede procesar una iteración sobre la colección ChainElement para obtener más detalles. La figura 6 muestra cómo realizar una validación estricta de un certificado y su emisor en función de listas de revocación sin conexión y en línea.
static void ValidateCert(X509Certificate2 cert)
{
    X509Chain chain = new X509Chain();
    
    // check entire chain for revocation
    chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;

    // check online and offline revocation lists
    chain.ChainPolicy.RevocationMode = 
        X509RevocationMode.Online | X509RevocationMode.Offline;

    // timeout for online revocation list
    chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 30);

    // no exceptions, check all properties
    chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;

    // modify time of verification
    //chain.ChainPolicy.VerificationTime = new DateTime(1999, 1, 1);

    chain.Build(cert);

    if (chain.ChainStatus.Length != 0)
        Console.WriteLine(chain.ChainStatus[0].Status);
}


Compatibilidad con SSL
El protocolo de autenticación SSL depende de certificados. La compatibilidad con SSL en .NET Framework consta de dos partes. La clase HttpWebRequest (que últimamente se usa para proxies de cliente de servicio web) implementa el caso especial, pero muy usado, de SSL sobre HTTP. Para habilitar SSL, sólo debe especificar una dirección URL que use el protocolo https:.
Al establecer la conexión con un extremo SSL protegido, el certificado de servidor se valida en el cliente. Si no se produce la validación, la conexión se cierra inmediatamente de forma predeterminada. Puede invalidar este comportamiento mediante una devolución de llamada a una clase denominada ServicePointManager. Siempre que la pila de cliente HTTP realiza la validación de certificado, primero comprueba si se ofrece una devolución de llamada; en caso afirmativo, ejecuta el código. Para enlazar la devolución de llamada, debe ofrecer un delegado de tipo RemoteCertificateValidationCallback:
// override default certificate policy 
// (for example, for testing purposes) 
ServicePointManager.ServerCertificateValidationCallback =  
    new RemoteCertificateValidationCallback(VerifyServerCertificate);
En la devolución de llamada, obtendrá el certificado de servidor, un código de error y un objeto de cadena válido. A continuación, podrá realizar su propia comprobación y devolver true o false. Puede ser útil desactivar una de estas comprobaciones si, por ejemplo, el certificado ha caducado durante las fases de desarrollo o prueba. Por otro lado, esto también permite implementar directivas de validación más estrictas que las establecidas de forma predeterminada. La figura 7 ofrece una devolución de llamada de validación de muestra.
private bool VerifyServerCertificate(
    object sender, X509Certificate certificate, 
    X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    if (sslPolicyErrors == SslPolicyErrors.None) return true;

    foreach (X509ChainStatus s in chain.ChainStatus)
    {
        // allows expired certificates
        if (string.Equals(s.Status.ToString(), "NotTimeValid", 
            StringComparison.OrdinalIgnoreCase))
                return true;
    }

    return false;
}

SSL también es compatible con la autenticación de cliente mediante un certificado. Si el sitio web o el servicio a los que desea obtener acceso exigen un certificado de cliente, tanto el proxy de cliente de servicio web como HttpWebRequest proporcionarán una propiedad ClientCertificates de tipo X509Certicate:
  proxy.Url = 
    "https://server/app/service.asmx";
  proxy.ClientCertificates.Add(
    PickCertificate(...));
Además, .NET Framework 2.0 introduce una clase nueva denominada SslStream. Esto le permite colocar SSL encima de todas las secuencias, no sólo sobre HTTP, con lo que es posible que SSL habilite un protocolo basado en el socket personalizado. SslStream usa la compatibilidad estándar de los certificados de .NET de varias formas; por ejemplo, mediante el uso del mecanismo de devolución de llamada de validación que he explicado:
public SslStream(Stream innerStream, bool leaveInnerStreamOpen, 
    RemoteCertificateValidationCallback ValidationCallback) {...}
Para iniciar una autenticación SSL con SslStream, debe pasar una clase X509Certificate al método AuthenticateAsServer:
ssl.AuthenticateAsServer(PickCertificate(...));

Seguridad de servicio web
El estándar WS-Security especifica la autenticación de cliente y servidor y protege la comunicación mediante certificados. Los kits de herramientas como Web Services Enhancements (WSE) para .NET Framework y tecnologías como Windows Communication Foundation son totalmente compatibles con lo anterior. De nuevo, esto se reduce al suministro de un certificado en el código o mediante configuración. El fragmento de código siguiente muestra cómo agregar un certificado de cliente a un proxy de servicio web mediante WSE3:
X509SecurityToken token = new X509SecurityToken(PickCertificate(...));
proxy.RequestSoapContext.Security.Tokens.Add(token);
Con Windows Communication Foundation normalmente debe ofrecer una referencia a un almacén de certificados en un archivo de configuración (consulte la figura 8). Como puede comprobar, todos los atributos de configuración se asignan directamente a las enumeraciones usadas anteriormente en el código.
<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior name="ServiceBehavior">
        <serviceCredentials>
          <serviceCertificate storeLocation="LocalMachine"
            storeName="My" x509FindType="FindBySubjectKeyIdentifier"
            findValue="1a7b..." />
        </serviceCredentials>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>


Directiva de seguridad y firma de código
Los certificados también se usan en la firma de código de Authenticode®. Mediante la firma de un código binario, puede agregar información acerca del publicador y asegurarse de que el archivo firmado se puede validar de forma segura después de firmarlo. Puede usar la herramienta signtool.exe de .NET Framework SDK para firmar archivos .exe y dll. A continuación, podrá comprobar la firma y ver el certificado que usa el cuadro de diálogo de propiedades en el Explorador de Windows. Tenga en cuenta que si se usa Authenticode y las firmas de nombre seguro, se debe aplicar primero la firma de nombre seguro. Además, los ensamblados que Authenticode ha firmado pueden sufrir retraso en el momento de la carga, lo que se traduce en un mayor tiempo de inicio de la aplicación, en caso de haberse firmado el archivo ejecutable de punto de entrada.
Los archivos firmados también se pueden usar para directivas de seguridad. Mediante directivas de restricción de software, puede limitar la ejecución de archivos ejecutables sin administrar en función de firmas o la ausencia de las mismas (consulte microsoft.com/technet/prodtechnol/winxppro/maintain/rstrplcy.mspx). La directiva de seguridad de acceso a código (CAS) de .NET Framework es compatible con los grupos de código basados en un certificado de emisor.
Para crear una directiva de CAS, use mscorcfg.msc para crear un grupo de código nuevo basado en una condición de pertenencia de emisor. A continuación, podrá asignar un conjunto de permisos a todas las aplicaciones que ha firmado dicho emisor (consulte la figura 9).
Figura 9 Asignación de permisos a un emisor (Hacer clic en la imagen para ampliarla)

Manifiestos de ClickOnce
Otra tecnología que usa certificados para la información de publicador es ClickOnce. Al publicar una aplicación de ClickOnce, debe firmar el manifiesto de aplicación e implementación. Esto vuelve a agregar información de emisor a la aplicación y garantiza que la información confidencial de los manifiestos (como las dependencias de aplicación y de directiva de seguridad) no se puede modificar sin invalidar la firma. ClickOnce muestra la información de emisor a los clientes durante la instalación para que puedan tomar decisiones inteligentes sobre la confiabilidad de la aplicación. En función del certificado (y del resultado de la validación), el instalador de ClickOnce también usa indicaciones visuales diferentes. La figura 10 muestra el cuadro de diálogo de firma del manifiesto de Visual Studio®.
Figura 10 Cuadro de diálogo de firma del manifiesto de Visual Studio (Hacer clic en la imagen para ampliarla)

Firma y cifrado de datos
Hasta ahora me he ocupado de las API básicas relacionadas con certificados y de cómo otras tecnologías las utilizan. Ahora me gustaría tratar las operaciones de cifrado, como cifrar y firmar datos con certificados, y la implementación nueva de PKCS #7 para .NET Framework 2.0.
La protección de datos es siempre un proceso de dos pasos. En primer lugar, debe firmar los datos para que no se puedan alterar. A continuación, debe cifrar los datos para evitar su divulgación. Sin embargo, para poder realizar alguna operación de cifrado con las clases de PKCS #7, primero debe ajustar los datos en un objeto ContentInfo, con lo que representará una estructura de datos CMS. Después, podrá transformar los datos en datos firmados o cifrados, representados por las clases SignedCms y EnvelopedCms, respectivamente.
Técnicamente, una firma digital es el resultado hash de los datos que, a continuación, se cifran mediante la clave privada. Esto significa que necesita un certificado con una clave privada asociada o un archivo .pfx. A partir de dicho certificado, puede crear un objeto CmsSigner, que representa al firmante de los datos. A su vez, la clase SignedCms calcula la firma y produce un resultado de PKCS #7, matriz de bytes compatible con CMS. La figura 11 muestra el código correspondiente. La matriz de bytes codificada contiene los datos, la firma y el certificado usado para firmar los datos.
byte[] Sign(byte[] data, X509Certificate2 signingCert)
{
    // create ContentInfo
    ContentInfo content = new ContentInfo(data);

    // SignedCms represents signed data
    SignedCms signedMessage = new SignedCms(content);

    // create a signer
    CmsSigner signer = new CmsSigner(signingCert);

    // sign the data
    signedMessage.ComputeSignature(signer);

    // create PKCS #7 byte array
    byte[] signedBytes = signedMessage.Encode();

    // return signed data
    return signedBytes;
}

Esto puede no ser grave si firma grandes cantidades de datos, pero si se trata de un número reducido de datos, supone una sobrecarga. Por ejemplo, al firmar una matriz de 10 bytes con una clave pública de 2 KB, se obtiene como resultado una matriz de 2.400 bytes aproximadamente. Recuérdelo si desea almacenar los datos firmados en una base de datos, por ejemplo. Un enfoque alternativo es usar la denominada firma separada. Esto le permite eliminar los datos de la firma y almacenarla aparte. Por ejemplo, puede combinar primero varios conjuntos pequeños de datos y firmarlos en conjunto. Para crear una firma separada, debe pasar un valor true adicional al constructor de SignedCms, como se muestra en la figura 12.
byte[] SignDetached(byte[] data, X509Certificate2 signingCert)
{
    // create ContentInfo
    ContentInfo content = new ContentInfo(data);

    // pass true to the constructor to indicate
    // we want to sign detached
    SignedCms signedMessage = new SignedCms(content, true);

    // these steps are the same 
    CmsSigner signer = new CmsSigner(signingCert);
    signedMessage.ComputeSignature(signer);
    byte[] signedBytes = signedMessage.Encode();

    // return only the signature (not the data)
    return signedBytes;
}

Una vez firmados los datos, puede cifrarlos. Necesitará las claves públicas de los destinatarios que puedan descifrar los datos. Por norma general, los obtiene del almacén Otras personas (o de un archivo .cer, si no desea usar el almacén de certificados). Esta vez, la clase EnvelopedCms realizará todo el trabajo pesado. Puede especificar las claves públicas usadas para el cifrado mediante CmsRecipientCollection, que debe pasar al método Encrypt. Al igual que con SignedCms, el método Encode crea PKCS #7, matriz de bytes que cumple con CMS (consulte la figura 13).
byte[] Encrypt(byte[] data, X509Certificate2 encryptingCert)
{
    // create ContentInfo
    ContentInfo plainContent = new ContentInfo(data);

    // EnvelopedCms represents encrypted data
    EnvelopedCms encryptedData = new EnvelopedCms(plainContent);

    // add a recipient
    CmsRecipient recipient = new CmsRecipient(encryptingCert);

    // encrypt data with public key of recipient
    encryptedData.Encrypt(recipient);

    // create PKCS #7 byte array
    byte[] encryptedBytes = encryptedMessage.Encode();

    // return encrypted data
    return encryptedBytes;
}

Internamente, EnvelopedCms genera una clave de sesión aleatoria con la que los datos se cifran de forma simétrica. A continuación, la clave de sesión se cifra con la clave pública de cada destinatario. De este modo, no necesitará una versión cifrada separada de los datos para cada uno de los destinatarios. Además, se presenta incrustada alguna información adicional, lo que permite al destinatario encontrar la clave privada correspondiente para el descifrado del almacén de certificados.

Descifrado de datos y verificación de firmas
En la parte del destinatario, se invierte el proceso entero. Eso significa que, primero, debe descifrar los datos y, a continuación, validar la firma y el certificado de firma. En el código, primero debe llamar a los métodos Decode de las clases SignedCms y EnvelopedCms para deserializar la matriz de bytes de CMS en una representación objetiva. A continuación, puede llamar a Decrypt y CheckSignature, respectivamente.
El proceso busca en el paquete cifrado si la clave de sesión se puede descifrar mediante la búsqueda de la clave privada correspondiente en el almacén de certificados. Después, la clave de sesión descifrada se usa para descifrar los datos reales. También puede ofrecer una lista de certificados adicionales que se deben tener en cuenta durante el descifrado, en caso de que la clave privada no se almacene en el almacén de certificados:
static byte[] Decrypt(byte[] data)
{
    // create EnvelopedCms
    EnvelopedCms encryptedMessage = new EnvelopedCms();

    // deserialize PKCS#7 byte array
    encryptedMessage.Decode(data);

    // decryt data
    encryptedMessage.Decrypt();

    // return plain text data
    return encryptedMessage.ContentInfo.Content;
}
La verificación de datos es siempre un proceso de dos pasos. Asegúrese primero de que la firma es válida, lo que significa que los datos no se han alterado. A continuación, compruebe el certificado de firma. El método CheckSignature de la clase SignedCms permite realizar ambos pasos inmediatamente. En este caso, el certificado se valida en función de la directiva de sistema predeterminada. Si desea más control sobre dicho proceso, puede realizar su propia comprobación mediante un objeto y un código X509Chain, como se muestra en la figura 6. La figura 14 muestra el código usado para comprobar y quitar una firma, mientras que la figura 15 muestra el código usado para validar una firma separada.
static bool VerifyDetached(byte[] data, byte[] signature)
{
    ContentInfo content = new ContentInfo(data);

    // pass true for detached
    SignedCms signedMessage = new SignedCms(content, true);

    // deserialize signature
    signedMessage.Decode(signature);

    try
    {
        // check if signature matches data
        // the certificate is also checked
        signedMessage.CheckSignature(false);
        return true;
    }
    catch { return false; }
}

byte[] VerifyAndRemoveSignature(byte[] data)
{
    // create SignedCms
    SignedCms signedMessage = new SignedCms();

    // deserialize PKCS #7 byte array
    signedMessage.Decode(data);

    // check signature
    // false checks signature and certificate
    // true only checks signature
    signedMessage.CheckSignature(false);

    // access signature certificates (if needed)
    foreach (SignerInfo signer in signedMessage.SignerInfos)
    {
        Console.WriteLine("Subject: {0}", 
          signer.Certificate.Subject);
    }

    // return plain data without signature
    return signedMessage.ContentInfo.Content;
}


Integración
Al trabajar con directivas de seguridad o protocolos de comunicaciones, encontrará certificados en toda clase de situaciones. En este artículo, he explicado las API básicas usadas para recuperar y buscar certificados; asimismo, le he mostrado cómo usarlas para cifrado y firmas digitales. Además, le he ofrecido algunos ejemplos de los servicios de aplicaciones de más alto nivel, para los que es necesario que comprenda cómo funciona el almacén de certificados de Windows y la relación entre las claves públicas y privadas.
El código fuente para este artículo, que se puede descargar del sitio web de MSDN®Magazine, incluye una pequeña aplicación de Windows Forms compatible con archivos de firma y cifrado. Usa muchas de las técnicas que he tratado en este artículo, como seleccionar certificados de almacenes diferentes y proteger/comprobar datos que usan cifrado y firmas.

Dominick Baier es un consultor independiente de seguridad en Alemania. Ayuda a las empresas a obtener un diseño y arquitectura seguros, a desarrollar contenidos, a probar la penetración y a realizar auditorías de código. También dirige el programa de seguridad en DevelopMentor, es MVP de seguridad para programadores y autor de More-Secure Microsoft ASP.NET 2.0 Applications (Microsoft Press, 2006). Puede ver su blog en www.leastprivilege.com.

Page view tracker