El blog del burgués

7 abril 2010

Documentos xml y firmas digitales

Filed under: C# — elburgues @ 7:23 AM

Se puede firmar y comprobar la firma de un documento XML o de parte de un documento XML con una firma digital. Las firmas digitales XML (XMLDSIG) permiten comprobar que no se modificaron los datos después de la firma. .NET Framework incluye clases para la creación y verificación XMLDSIG. Estas clases crean firmas que son también documentos XML en sí mismas. Todas las clases XMLDSIG de .NET se encuentran en el espacio de nombres System.Security.Cryptography.Xml, el cual está incluido en System.Security.dll.

Un ejemplo en C#.NET

El estándar XMLDSIG es complejo, no vamos a ver todas opciones disponibles, tan solo un ejemplo de cómo se firma y se comprueba la firma de un documento XML. En el ejemplo se muestra cómo firmar digitalmente todo un documento XML y adjuntar la firma al documento en un elemento <Signature>. Se crea una clave de firma RSA, se agrega la clave a un contenedor de claves seguro y después se utiliza la clave para firmar digitalmente un documento XML. Luego se muestra cómo comprobar una firma digital XML del documento recién firmado (que se encuentra en un elemento <Signature>). Para ello, se recupera la clave pública RSA del contenedor y, a continuación, se utiliza la clave para comprobar la firma.

Para el ejemplo, se asume la existencia de un documento XML (C:\Libro.xml). Yo he creado uno para hacer la prueba con este contenido:

<libro>
    <titulo>Programando seguridad en .NET</titulo>
    <autor>El Burgués</autor>
    <lineas>151</lineas>
</libro>

Después de la firma, el documento XML quedaría de la siguiente manera:

<libro>
  <titulo>Programando seguridad en .NET</titulo>
  <autor>El Burgués</autor>
  <lineas>151</lineas>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
      <Reference URI="">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
        <DigestValue>xc/892t0w3GfEoo+jmMBktVrdUU=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>jNQR5U/hhzqghuxMl3km9Xnd5K1iZk... bla bla bla...8WYJ3QYTMXge3VJbN3IgYA=</SignatureValue>
  </Signature>
</libro>

El código fuente del ejemplo es el siguiente:

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Xml;

public class SignXML
{
    [STAThread]
    public static void Main(String[] args)
    {
        try
        {
            // Cree un objeto CspParameters y especifique el nombre del contenedor de claves.
            CspParameters cspParams = new CspParameters();
            cspParams.KeyContainerName = "XML_DSIG_RSA_KEY";

            // Genere una clave simétrica mediante la clase RSACryptoServiceProvider.
            // La clave se guarda automáticamente en el contenedor de claves cuando pasa
            // el objeto CspParameters al constructor de la clase RSACryptoServiceProvider.
            // Esta clave se utilizará para firmar el documento XML.
            RSACryptoServiceProvider llave = new RSACryptoServiceProvider(cspParams);

            // Cree un objeto XmlDocument; para ello, cargue un archivo XML de disco.
            // El objeto XmlDocument contiene el elemento XML que se debe cifrar.
            XmlDocument documentoXML = new XmlDocument();
            documentoXML.PreserveWhitespace = true;
            documentoXML.Load(@"C:\Libro.xml");

            // Firme el documento XML.
            FirmarXML(documentoXML, llave);

            Console.WriteLine("Fichero XML firmado.");

            // Ahora verificamos la firma del documento XML.
            Console.WriteLine("Verificando la firma...");
            bool result = VerificarXML(documentoXML, llave);

            // Sacamos por consola el resultado de la verificación.
            if (result)
            {
                Console.WriteLine("La firma XML es válida.");
            }
            else
            {
                Console.WriteLine("La firma XML no es válida.");
            }
            Console.WriteLine("Pulse una tecla para finalizar.");
            Console.ReadKey();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }

    /// <summary>
    /// Firma un documento XML.
    /// </summary>
    /// <param name="xmlDoc">
    /// Documento XML a firmar.
    /// </param>
    /// <param name="llave">
    /// Llave a utilizar para la firma.
    /// </param>
    public static void FirmarXML(XmlDocument xmlDoc, RSA llave)
    {
        // Comprobamos los argumentos.
        if (xmlDoc == null)
            throw new ArgumentException("El argumento Doc no puede ser null.");
        if (llave == null)
            throw new ArgumentException("El argumento Key no puede ser null.");

        // Cree un nuevo objeto SignedXml y pásele el objeto XmlDocument.
        SignedXml xmlFirmado = new SignedXml(xmlDoc);

        // Agregue la clave RSA de firma al objeto SignedXml.
        xmlFirmado.SigningKey = llave;

        // Cree un objeto Reference que describa qué se debe firmar.
        // Para firmar el documento completo, establezca la propiedad Uri como "".
        Reference referencia = new Reference();
        referencia.Uri = "";

        // Agregue un objeto XmlDsigEnvelopedSignatureTransform al objeto Reference.
        // Una transformación permite al comprobador representar los datos XML
        // de idéntico modo que el firmante. Los datos XML se pueden representar de distintas maneras,
        // por lo que este paso es vital para la comprobación.
        XmlDsigEnvelopedSignatureTransform transformacionENV = new XmlDsigEnvelopedSignatureTransform();
        referencia.AddTransform(transformacionENV);

        // Agregue el objeto Reference al objetoSignedXml.
        xmlFirmado.AddReference(referencia);

        // Llame al método ComputeSignature para calcular la firma.
        xmlFirmado.ComputeSignature();

        // Recupere la representación XML de la firma (un elemento <Signature>)
        // y guárdela en un nuevo objeto XmlElement.
        XmlElement firmaDigitalXML = xmlFirmado.GetXml();

        // Anexe el elemento al objeto XmlDocument.
        xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(firmaDigitalXML, true));
    }

    /// <summary>
    /// Verifica la firma de un documento XML.
    /// </summary>
    /// <param name="Doc">
    /// Documento XML a verificar.
    /// </param>
    /// <param name="Key">
    /// Llave con la que fue firmado el documento XML.
    /// </param>
    /// <returns>
    /// Valor booleano que indica éxito o error
    /// </returns>
    public static Boolean VerificarXML(XmlDocument xmlDoc, RSA llave)
    {
        // Comprobamos los argumentos.
        if (xmlDoc == null)
            throw new ArgumentException("El argumento Doc no puede ser null.");
        if (llave == null)
            throw new ArgumentException("El argumento Key no puede ser null.");

        // Cree un nuevo objeto SignedXml y pásele el objeto XmlDocument.
        SignedXml xmlFirmado = new SignedXml(xmlDoc);

        // Busque el elemento <signature> y cree un nuevo objeto XmlNodeList.
        XmlNodeList listaNodos = xmlDoc.GetElementsByTagName("Signature");

        // Se lanza una excepción si no se ha encontrado una firma.
        if (listaNodos.Count <= 0)
        {
            throw new CryptographicException("La verificación de la firma ha fallado: No se ha encontrado una firma en el documento.");
        }

        // Este ejemplo solo suporta una firma para el documento xml entero.
        // Se lanza una excepción si se encuentra más de una firma.
        if (listaNodos.Count >= 2)
        {
            throw new CryptographicException("La verificación de la firma ha fallado: Se ha encontrado más de una firma en el documento.");
        }

        // Cargue el XML del primer elemento <signature> en el objeto SignedXml.
        xmlFirmado.LoadXml((XmlElement)listaNodos[0]);

        // Compruebe la firma mediante el método CheckSignature y la clave pública RSA.
        // Este método devuelve un valor booleano que indica éxito o error.
        return xmlFirmado.CheckSignature(llave);
    }
}
Anuncios

4 comentarios »

  1. Bien, bien. Ya se quien va a hacer la parte de seguridad de los TCFs, jejejeje

    Comentario por Oscar — 18 abril 2010 @ 2:31 AM | Responder

  2. he intentado encontrar informaicona acerca de firma digital pero no entiendo si se refiere a firma digitalmente con un lector externo de firmas????

    Comentario por karina — 26 octubre 2010 @ 7:39 PM | Responder

  3. Buen artículo de inicio a la firma XMLDSIG en Csharp!

    Por si acaso necesitáis ir un poquito más allá y utilizar firma digital en .NET, estoy en un equipo que estamos construyendo una librería de firma para .NET.

    La librería soportará XAdES y XMLDSIG, y se utilizará a través de un DSL diseñado para que sea simple su empleo.

    Podéis encontrar el proyecto en http://xadesnet.codeplex.com

    En fin, que buen trabajo y espero que esto ayude a alguien.

    Un saludo,
    Luis

    Comentario por Luis M. Villa — 25 noviembre 2010 @ 2:05 PM | Responder


RSS feed for comments on this post.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Blog de WordPress.com.

A %d blogueros les gusta esto: