Utilizar certificados digitales desde .NETViernes, 21 Ago, 2009 @ 09:20 | Por Gustavo Cantero (The Wolf) | .NET, Seguridad |
Luego de mis artículos sobre certificados digitales, firma digital y hash sólo me queda escribir el último de esta serie en donde me gustaría mostrar cómo buscar, leer y utilizar estos certificados X.509 desde .NET.
Consultar los repositorios de certificados
Como primer paso vamos a recorrer los certificados que tenemos instalados en nuestra máquina. Para esto debemos utilizar la clase X509Store, la cual nos da la posibilidad de consultar un repositorio de certificados, por ejemplo, el repositorio raíz (Root), el repositorio donde están los certificados de las autoridades certificantes (CertificateAuthority), o el repostorio personal (My). También tenemos que elegir la ubicación del certificado, es decir, si vamos a querer consultar los certificados que están a nivel de máquina o de usuario. Tengan en cuenta que si van a utilizar la clase X509Store desde ASP.NET deben leer los certificados que están a nivel de máquina, ya que el usuario ASP.NET rara vez va a tener algún certificado instalado.
Luego de elegido el repositorio que queremos abrir debemos utilizar el método Open para que consulte al mismo.
Una vez abierto el repositorio, la propiedad Certificates contendrá una colección (basada en la clase X509Certificate2Collection) de los certificados almacenados.
Después de utilizar el repositorio no hay que olvidar de cerrar el mismo con método Close.
A continuación muestro cómo abrir el repositorio personal y escribir en la consola de debug los nombres y firmas digitales (hash) de cada certificado:
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
...
X509Store objStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
objStore.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 objCert in objStore.Certificates)
Debug.Print(objCert.SubjectName.Name + ": " + objCert.Thumbprint);
objStore.Close();
Consultar la información del certificado y sus extensiones
Cada certificado posee una serie de datos (los cuales se describen en el artículo Conceptos de Certificado Digital y Firma Digital y creamos en el artículo Crear certificados de prueba para servidor y cliente). Además de estos datos los certificados poseen extensiones, que definen distintos posibles usos dependiendo de las mismas, y según la extensión poseen distintos datos almacenados. Por ejemplo, en la extensión del tipo “2.5.29.37” (también llamada “Enhanced Key Usage”) se guarda cual va a ser el destino del certificado, por ejemplo para autenticación del cliente.
Estas extensiones están en la colección Extensions del certificado, y para obtener la información almacenada debe castearse según el tipo del mismo. Para saber qué tipo de extensión es la almacenada se puede leer la propiedad Oid, la cual devuelve la clase homónima que representa un identificador de un objeto criptográfico (cryptographic object identifier) y luego, de este objeto, podemos leer la propiedad FriendlyName para obtener el nombre de la extensión.
A continuación hay un código de ejemplo donde se muestra la información de un certificado:
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado
StringBuilder objSB = new StringBuilder("Detalle del certificado: \n\n");
//Detalle
objSB.AppendLine("Persona = " + objCert.Subject);
objSB.AppendLine("Emisor = " + objCert.Issuer);
objSB.AppendLine("Válido desde = " + objCert.NotBefore.ToString());
objSB.AppendLine("Válido hasta = " + objCert.NotAfter.ToString());
objSB.AppendLine("Tamaño de la clave = " + objCert.PublicKey.Key.KeySize.ToString());
objSB.AppendLine("Número de serie = " + objCert.SerialNumber);
objSB.AppendLine("Hash = " + objCert.Thumbprint);
//Extensiones
objSB.AppendLine("\nExtensiones:\n");
foreach (X509Extension objExt in objCert.Extensions)
{
objSB.AppendLine(objExt.Oid.FriendlyName + " (" + objExt.Oid.Value + ')');
if (objExt.Oid.FriendlyName == "Key Usage")
{
X509KeyUsageExtension ext = (X509KeyUsageExtension)objExt;
objSB.AppendLine(" " + ext.KeyUsages);
}
if (objExt.Oid.FriendlyName == "Basic Constraints")
{
X509BasicConstraintsExtension ext = (X509BasicConstraintsExtension)objExt;
objSB.AppendLine(" " + ext.CertificateAuthority);
objSB.AppendLine(" " + ext.HasPathLengthConstraint);
objSB.AppendLine(" " + ext.PathLengthConstraint);
}
if (objExt.Oid.FriendlyName == "Subject Key Identifier")
{
X509SubjectKeyIdentifierExtension ext = (X509SubjectKeyIdentifierExtension)objExt;
objSB.AppendLine(" " + ext.SubjectKeyIdentifier);
}
if (objExt.Oid.FriendlyName == "Enhanced Key Usage") //2.5.29.37
{
X509EnhancedKeyUsageExtension ext = (X509EnhancedKeyUsageExtension)objExt;
OidCollection objOids = ext.EnhancedKeyUsages;
foreach (Oid oid in objOids)
objSB.AppendLine(" " + oid.FriendlyName + " (" + oid.Value + ')');
}
}
Debug.Print(objSB.ToString());
Validar certificados
Una de las tareas comunes que se realizan cuando estamos trabajando con certificados es la validación de los mismos. Esta tarea incluye verificar las fechas de validez, chequear que el certificado no haya sido revocado, etc. Esta validación debe hacerse en todos y cada uno de los certificados de la cadena hasta el certificado raíz.
Para validar la cadena de certificados se puede utilizar la clase X509Chain. Esta clase posee varias propiedades para especificar la forma en la que se quiere hacer la validación, por ejemplo, en la propiedad RevocationFlag se puede establecer si queremos validar sólo el certificado final (X509RevocationFlag.EndCertificateOnly), toda la cadena excepto el raíz (X509RevocationFlag.ExcludeRoot) o la cadena completa (X509RevocationFlag.EntireChain). También puede establecerse la forma en la que se quiere validar las revocaciones a través de la propiedad RevocationMode, pudiendo seleccionar en línea (X509RevocationMode.Online), fuera de línea (X509RevocationMode.Offline) o no chequear las listas de revocados (X509RevocationMode.NoCheck).
Para iniciar la validación, en la instancia de la clase X509Chain, se debe utilizar el método Build pasándole como parámetro el certificado. Luego de esto, en la propiedad ChainStatus, vamos a encontrar la lista de errores en las validaciones o, en caso de estar vacío este vector, significa que el certificado es válido.
Acá les dejo un código de ejemplo donde validamos un certificado y toda la cadena:
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado
X509Chain objChain = new X509Chain();
//Verifico toda la cadena de revocación
objChain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
objChain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
//Timeout para las listas de revocación
objChain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 30);
//Verificar todo
objChain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
//Se puede cambiar la fecha de verificación
//objChain.ChainPolicy.VerificationTime = new DateTime(1999, 1, 1);
objChain.Build(objCert);
if (objChain.ChainStatus.Length != 0)
foreach (X509ChainStatus objChainStatus in objChain.ChainStatus)
Debug.Print(objChainStatus.Status.ToString() + " - " + objChainStatus.StatusInformation);
else
Debug.Print("Ok");
Firma de datos
Una de las tareas más comunes para las cuales se utilizan los certificados es para firmar documentos. Para esto casi siempre se utiliza el estándar de sintaxis para mensajes criptográficos PKCS #7, el cual está definido en el RFC 2315.
En .NET, para crear firmas digitales utilizando PKCS #7, se pueden utilizar las clases que están en el namespace System.Security.Cryptography.Pkcs.
Pienso que lo mejor para explicar cómo hacerte esto es a través de un ejemplo, por lo tanto, a continuación muestro un ejemplo donde creo un documento con su firma digital a partir de un texto:
using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado
//Creamos el ContentInfo
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));
//Creamos el objeto que representa los datos firmados
SignedCms objSignedData = new SignedCms(objContent);
//Creamos el "firmante"
CmsSigner objSigner = new CmsSigner(objCert);
//Firmamos los datos
objSignedData.ComputeSignature(objSigner);
//Obtenemos el resultado
byte[] bytSigned = objSignedData.Encode();
Debug.Print("Documento con firma: " + Convert.ToBase64String(bytSigned);
Otra opción, dependiendo de la forma en la que se quiere utilizar el resultado, es la de crear la firma solamente, sin el documento:
using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado
//Creamos el ContentInfo
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));
//Creamos el objeto que representa los datos firmados
SignedCms objSignedData = new SignedCms(objContent, true);
//Creamos el "firmante"
CmsSigner objSigner = new CmsSigner(objCert);
//Firmamos los datos
objSignedData.ComputeSignature(objSigner);
//Obtenemos el resultado
byte[] bytSigned = objSignedData.Encode();
Debug.Print("Firma digital: " + Convert.ToBase64String(bytSigned);
Verificar firma
Obviamente si creamos un documento con una firma digital es porque, tarde o temprano, vamos a querer verificar esta firma. Para realizar esto utilizamos nuevamente la clase SignedCms, la cual posee el método CheckSignature, y en caso de ser un documento firmado (no la firma “suelta”), en la propiedad ContentInfo.Content va a estar el documento sin firma.
A continuación les dejo los dos ejemplos, la verificación de la firma digital:
using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
byte[] bytFirma = ... //Acá tenemos que poner la firma digital calculada en el ejemplo anterior
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));
SignedCms objDatos = new SignedCms(objContent, true);
//Deserealizamos la firma
objDatos.Decode(bytFirma);
try
{
//Verificamos si la firma concuerda con los datos
objDatos.CheckSignature(true);
Debug.Print("Ok - La firma concuerda con los datos");
}
catch
{
Debug.Print("Error - La firma no concuerda con los datos");
}
Y el ejemplo de la verificación de la firma y obtención del documento:
using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
byte[] bytDocFirmado = ... //Acá tenemos que poner el documento firmado obtenido en el ejemplo anterior
SignedCms objDatos = new SignedCms();
//Deserializo los bytes PKCS#7
objDatos.Decode(bytDocFirmado);
//Verifico la firma y obtengo el documento
try
{
objDatos.CheckSignature(true);
Debug.Print("Ok - La firma concuerda con los datos");
Debug.Print(Encoding.ASCII.GetString(objDatos.ContentInfo.Content));
}
catch
{
Debug.Print("Error - La firma no concuerda con los datos");
}
Encriptación y desencriptación utilizando un certificado
Por último voy a mostrar como encriptar y desencriptar documentos utilizando certificados digitales. Para esto, al igual con la firma de documentos, voy a mostrar un ejemplo, que creo es la mejor manera de entenderlo:
using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
X509Certificate2 objCert = ... //Acá tenemos que poner el certificado
//Creamos el ContentInfo
ContentInfo objContent = new ContentInfo(Encoding.ASCII.GetBytes("Scientia Soluciones Informáticas, la mejor consultora de desarrollo"));
//Creamos el objeto que representa los datos firmados
EnvelopedCms objEncryptedData = new EnvelopedCms(objContent);
//Creamos el destino
CmsRecipient objRecipient = new CmsRecipient(objCert);
//Firmamos los datos
objEncryptedData.Encrypt(objRecipient);
//Datos encriptados
byte[] bytResult = objEncryptedData.Encode();
Debug.Pring("Datos encriptados: " + Convert.ToBase64String(bytResult));
Y ahora voy a mostrar como desencriptar los datos obtenidos en el paso anterior. Para esto previamente tenemos que tener instalado el certificado con el que se encriptaron estos datos, junto con su clave privada, en un repositorio sobre el cual el usuario tenga permisos:
using System.Security.Cryptography.Pkcs;
using System.Diagnostics;
using System.Text;
...
byte[] bytDatos = … //Datos encriptados
EnvelopedCms objEncryptedData = new EnvelopedCms();
//Leemos los datos encriptados
objEncryptedData.Decode(bytDatos);
//Desencriptamos los datos
objEncryptedData.Decrypt();
//Documento original
byte[] bytDoc = objEncryptedData.ContentInfo.Content;
//Mostramos el resultado
Debug.Print("Datos desencriptados: " + Encoding.ASCII.GetString(bytDoc));
Espero que este resumen pueda servirle a cualquier que necesite trabajar con certificados digitales desde .NET.
Artículos relacionados
- Conceptos de Certificado Digital y Firma Digital
- Crear certificados de prueba para servidor y cliente
- Como calcular el Hash de un vector de bytes o un string
Reciente







Lunes, 24 Agosto, 2009 a las 20:08
Excelente serie. De mucha utilidad
Lunes, 31 Agosto, 2009 a las 13:45
Hola Gustavo:
Nuevamente felicitarte por la serie. Probando el codigo fuente en lo referente a firmar datos obtengo CryptographicException “El conjunto de claves no existe” cuando se invoca al metodo objSignedData.ComputeSignature(objSigner);
Estoy usando certificados de prueba tal cual lo describiste en el inicio de serie.
Puedes indicarme cual es el problema? Muchas gracias.
Lunes, 31 Agosto, 2009 a las 14:36
Santiago: Muchas gracias por las felicitaciones.
Sobre el error, parecería ser porque no encuentra el certificado digital. ¿Guardaste un certificado en la variable objCert? De ser así: ¿El usuario tiene permisos para leer la clave privada del mismo? Ten en cuenta que si lo estás usando desde ASP.NET, el usuario “ASP.NET” debe tener permisos para leer el certificado.
Si sigue fin funcionar te invito a nuestro foro en http://foro.scientia.com.ar para dejar tus consultas, las cuales trataremos de responder a la brevedad.
Espero tus comentarios.
Suerte!
Martes, 01 Septiembre, 2009 a las 14:50
Gracias por responder. El problema estaba en los permisos.
Saludos
Martes, 01 Septiembre, 2009 a las 15:35
Me alegro. Cualquier otra cosa no dudes en consultarnos.
Saludos.
Miércoles, 23 Septiembre, 2009 a las 17:41
Primera que nada felicitaciones un muy buen aporte.
Estoy haciendo una aplicacion vb net, para firmar un documento xml o parte de este, y lo unico que me entregan es un archivo en formato .PEM, y no he encontrado como hacerlo..seria una gran ayuda si me das un empujoncito.
Saludos, Jonathan
Jueves, 24 Septiembre, 2009 a las 15:05
Hola..felicitaciones me ha ayudado bastante.
Necesito ayuda, estoy haciendo una aplicacion en vb .net y necesito firma un documento xml o parte de este, para ello tengo una string con la rsa key private (ese texto que esta en base 64 entre —begin rsa private key y end rsa private key), mi duda es como firmo este documento teniendo esta llave privada..Un abrazo y ojala me ayudes porfa.
Lunes, 19 Octubre, 2009 a las 20:16
hola felicitaciones por el articulo y el código son bastante token.
estoy realizando la validación del certificado, pero después de correr el codigo me muestra el siguiente mensaje:
“RevocationStatusUnknown – La función de revocación no puede comprobar la revocación para el certificado”
que puede estar pasando… entre los parametros se encuentra objChain.ChainPolicy.RevocationMode = X509RevocationMode.Online; que tiene que ver con la CRL pero en donde le puedo decir la URL en la cual debe buscar la CRL o como se hace esta parte.
muchas gracias de antemano.
saludos…
Lunes, 19 Octubre, 2009 a las 23:39
Leonardo:
Antes que nada, gracias por los elogios.
Sobre tu consulta: la URL de la lista de revocación está dentro del certificado. A simple vista parece ser que no puede obtener la lista, y creo que las posibles causas debe ser alguna de las siguientes:
objChain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 3, 0);Cualquier otra consulta no dudes en escribirnos.
Suerte!
Martes, 20 Octubre, 2009 a las 17:23
Hola Gustavo
he verificado los puntos 1 y 2 accediendo directamente a la URL de la CRL que trae el certificado el cual descarga un archivo .crl, y le modifiqué el tiempo de espera, pero aun asi me sigue presentando el mismo mensaje “RevocationStatusUnknown – La función de revocación no puede comprobar la revocación para el certificado.”
Por otro lado tambien estoy intentando firmar con el ejemplo tuyo, pero todos los certificados que posee mi maquina requieren password, no encontre en el ejemplo como le paso el password, le agradezco si me puede por favor indicar como hacerlo.
saludos
Miércoles, 28 Octubre, 2009 a las 14:50
Al tratar de desencriptar los datos, nos un error de “Clave Inválida”. el error da en la siguiente linea de código:
objEncryptedData.Decrypt();
Puedo leer certificados, encriptar documentos sin ningun problema.
Por favor si me pueden ayudar,
Saludos,
Jonathan.
Viernes, 06 Noviembre, 2009 a las 18:59
Hola Jonathan,
Alguna vez nos pasó algo similar y el problema estaba relacionado con la longitud de los datos que estabamos intentando encriptar, ahora no recuerdo los límites (creo que eran 117 bytes) pero cuando probamos con datos más cortos el problema se resolvió, claro que no es la idea acortar los datos por que esa opción no es posible en casi ninguna ocasión.
Quizás una posible solución (al menos a nosotros nos sirvió) sea utilizar algorítmos asimétricos para encriptar las claves y algorítmos simétricos para encriptar la información, ya que estos no poseen esta restricción.
Espero que esta respuesta te sirva.
Lunes, 16 Noviembre, 2009 a las 16:13
Leonardo, te pido mil disculpas por la demora en la respuesta.
Sobre tu problema, imagino que el problema no está en el certificado que necesitas validar sino en alguno de la cadena de certificación, por ejemplo, el que se utiliza como entidad certificadora de éste.
Espero que puedas resolver el inconveniente, sino te invito a nuestro foro http://foro.scientia.com.ar donde intentaremos ayudarte lo más rápido posible.
Saludos y suerte.
Viernes, 27 Noviembre, 2009 a las 19:55
Buenas Tardes
Bueno la solución para que el código pida la clave del certificado digital almacenado en el store es simplemente colocar un false
signedCms.ComputeSignature(cmsSigner, false);
pero ahora tengo otro problema, necesito firmar un archivo extremadamente grande y el código que tenemos hasta el momento solo firma hasta 80 MB con archivos por ejemplo de un archivo de 1 GB no lo puede firmar dado que saca un error de memoria.
si alguien sabe de esto por favor les agradeceria me dieran la solución
cuando tenga el código completo lo pìenso subir… por ahora mi correo es jlqr@hotmail.com por si necesitan algo.. no se mucho pero igual ya tengo funcionando un pedazo de código.. y todo gracias a lo que he visto en esta página y en algunas otras…
Lunes, 30 Noviembre, 2009 a las 11:11
Hola, queria saber como levantar una clave privada que se encuentra en un archivo.p12 desde .net? desde ya muchas gracias
Martes, 22 Diciembre, 2009 a las 13:30
Hola, Juan! Te pido disculpas por la tardanza, pero estuvimos muy ocupados las últimas semanas.
Sobre el tema del archivo .p12, en este artículo del MSDN http://msdn.microsoft.com/en-us/library/ms867088.aspx habla sobre cómo utilizar los archivos pfx/p12 desde .NET.
Espero que te sea de utilidad.
Saludos.