Assinaturas Digitais XML

Desde a sua normalização pelo W3C, a linguagem XML tem vindo a ser adoptada por um número crescente de produtores de software como formato base para os documentos utilizados pelas aplicações que desenvolvem. O crescendo de utilização de documentos neste formato revelou o interesse em definir mecanismos que lhes permitissem aportar as características de segurança (origem,não-repúdio e integridade) adequadas a cenários de utilização mais exigentes. Para colmatar essa lacuna, o W3C definiu posteriormente a norma XMLDSIG.
Neste artigo vamos-nos debruçar essencialmente no desenvolvimento em C# de uma pequena API para assinar digitalmente documentos XML.

Introdução e Enquadramento

De acordo com o RFC2828 uma assinatura digital define-se como sendo um valor calculado com um algoritmo criptográfico e anexado ao objecto de dados de tal forma que se possa usar essa assinatura para verificar a autenticidade e integridade dos dados.

A forma de funcionamento de uma assinatura digital XML é extremamente fiável. Respeitando escrupulosamente a terceira regra fundamental da criptografia, a integridade, a assinatura não só assegura que a pessoa que assinou o documento é de facto quem se espera, como também que o mesmo se manteve inalterado em todo o seu percurso digital até ao momento em que chegou ao destinatário. Relembrando noções básicas de criptografia assimétrica, "uma mensagem cifrada com uma chave pública apenas pode ser decifrada com a correspondente chave privada".

Percebendo o W3C XML Digital Signature

A especificação XML é responsável pela definição da informação que é usada na verificação de certificados digitais. Assinaturas digitais em XML são representadas pelo elemento XML Signature que contém uma estrutura com as seguintes regras:

  • * representa zero ou mais ocorrências de "algo".
  • + representa uma ou mais ocorrências de "algo".
  • ? representa zero ou uma ocorrência de "algo".

O seguinte XML descreve uma assinatura segundo a especificação W3C, de acordo com as regras descritas.

<Signature ID?>
  <SignedInfo>
   <CanonicalizationMethod/>
    <SignatureMethod/>
     (<Reference URI?>
       (<Transforms>)?
       <DigestMethod>
       <DigestValue>
      </Reference>)+
  </SignedInfo>
  <SignatureValue>
   (<KeyInfo>)?
   (Object ID?)*
</Signature>
</code>

Dissecando a especificação XMLDSIG

O elemento Signature é o construtor primário da assinatura, de acordo com a especificação W3C. A assinatura pode ser ''envelop'' ou ''enveloped'' pela informação que está a ser assinada, ou por outro lado pode referenciar informação que não consta do ficheiro XML, e neste caso será uma assinatura ''detached''. As assinaturas ''detached'' são muito úteis para assinar ficheiros externos ao XML, relembro que não existe qualquer limitação sobre o que se está a assinar.

Etapas de uma assinatura

SignedInfo

O elemento SignedInfo representa a informação que está actualmente assinada. Esta informação é processada sequencialmente por várias etapas no processo de assinatura.

Canonicalization

O elemento CanonicalizationMethod contém o algoritmo que foi usado para canonizar/estruturar a informação, numa forma comum "reconhecida" por todos os intervenientes no processo. Este processo é extremamente importante. A canonização pode definir regras como "a definição de um ''standard'' para o EOF, remoção de comentários" ou outra qualquer manipulação de um documento assinado que possamos necessitar.

Reference/Transform

O elemento Reference identifica o recurso que vais ser assinado e os algoritmos para processar a informação. Estes algoritmos estão enunciados no elemento Transform e podem incluir operações de Canonização, encoding/decoding,
compressão, ou até XPATH ou transformações XSLT.

O elemento Reference pode conter vários elementos Transform.
Nota: O elemento Reference contém um atributo opcional URI. A inclusão de um URI numa assinatura é opcional se a assinatura possuir apenas um elemento Reference. (www.w3.org/TR/2002/REC-xmldsig-core-20020212).

DigestMethod/DigestValue

O elemento DigestMethod representa o algoritmo aplicado à informação após passar pelo processo de transformação, gerando um valor único representado pelo elemento DigestValue. O DigestValue é uma aplicação ao resultado dos processos de Canonização e transformação, sem qualquer referência directa à informação a ser assinada.

KeyInfo

O elemento KeyInfo é opcional e pode conter a assinatura pública do autor para permitir a verificação da autenticidade do documento "automática".

Tipos de assinaturas XMLDSIG

As assinaturas XMLDSIG podem ser aplicadas em três formas básicas:

  • ''Detached'': O documento XML a ser assinado e a assinatura estão em dois ficheiros distintos, sendo que esta tem uma referência (URI) ao documento que se propõe a assinar.
<signature> ... </signature>
  • ''Enveloped'': O documento XML e a assinatura surgem no mesmo ficheiro de uma forma sequencial.
<document>
   <signature>...</signature>
</document>
  • ''Enveloping'': O documento e a assinatura estão contidos num envelope XML.
<signature>
   <document>...</document>
</signature>

Relembrar Conceitos

Antes de focar a fase de implementação, é necessário rever alguns aspectos importantes da criptografia, bem como certos aspectos organizacionais presentes no sistema operativo Windows. Comecemos pela Criptografia …

Certificados Digitais

Um certificado digital é um arquivo de computador que contém um conjunto de informações referentes à entidade para o qual o certificado foi emitido (seja uma empresa, pessoa física ou computador), mais a chave pública referente e a chave privada que acredita-se ser de posse unicamente da entidade especificada no certificado.

A anatomia de um certificado X.509

Um certificado padrão X.509 contém os seguintes campos:

  • Versão - Contem a versão do certificado X.509, actualmente versão 3
  • Número serie- Todo certificado possui um, não é globalmente único, mas único no âmbito de uma AC, ac LCRs usam o número de serie para apontar quais certificados se encontram revogados.
  • Tipo de algoritmo - Contem um identificador do algoritmo criptográfico usado pela AC para assinar o certificado juntamente com o tipo de função de hash criptográfica usada no certificado
  • Nome do titular - Nome da entidade para o qual o certificado foi emitido
  • Nome do emitente - Autoridade Certificadora que emitiu/assinou o certificado
  • Período de validade - Mostra o período de validade do certificado no formato "Não antes" e "Não depois" (Ex. "Não antes de 05/03/2006 - 14:35:02" "Não depois de 05/03/2007 - 14:03:20")
  • Informações de chave pública da entidade
  • Algoritmo de chave pública
  • Chave pública
    • Assinatura da AC - A garantia que a AC provê sobre a veracidade das informações contidas neste certificado de acordo com as políticas
  • Identificador da chave do titular - É uma extensão do X.509 que possui um identificador numérico para a chave pública contida neste certificado, especialmente útil para que programas de computador possam se referir a ela
  • Identificador da chave do emitente - A mesma ideia mencionada anteriormente, só que se referindo a chave pública da AC que emitiu o certificado
  • Atributos ou extensões - A vasta maioria dos certificados X.509 possui campos chamados extensões (OID) que provêem algumas
  • informações extras, como registos adicionais do titular e do emitente, especificações de propósito do certificado, etc.

Criptografia de chave pública

A criptografia de chave pública ou criptografia assimétrica é um método de criptografia que utiliza um par de chaves: uma chave pública e uma chave privada. A chave pública é distribuída livremente para todos os correspondentes via e-mail ou outras formas, enquanto a chave privada deve ser conhecida apenas pelo seu dono.

Num algoritmo de criptografia assimétrica, uma mensagem cifrada com a chave pública pode somente ser decifrada pela sua chave privada correspondente. Do mesmo modo, uma mensagem cifrada com a chave privada pode somente ser decifrada pela sua chave pública correspondente.

Os algoritmos de chave pública podem ser utilizados para autenticidade e confidencialidade. Para confidencialidade, a chave pública é usada para cifrar mensagens, com isso apenas o dono da chave privada pode decifrá-la. Para autenticidade, a chave privada é usada para cifrar mensagens, com isso garante-se que apenas o dono da chave privada poderia ter cifrado a mensagem que foi decifrada com a 'chave pública'.

RSA

RSA é um algoritmo de cifra de dados, que deve o seu nome a três professores do Instituto MIT (fundadores da actual empresa RSA Data Security, Inc.), Ron Rivest, Adi Shamir e Len Adleman, que inventaram este algoritmo — até à data (2005), a mais bem-sucedida implementação de sistemas de chaves assimétricas, e fundamenta-se em Teorias Clássicas dos Números. É considerado dos mais seguros. Foi também o primeiro algoritmo a possibilitar cifra e assinatura digital, é uma das grandes inovações em criptografia de chave pública.

KeyStore

Uma KeyStore é uma base de dados de chaves. As chaves privadas numa KeyStore possuem uma cadeia de certificados associada, que possibilita a autenticação à correspondente chave pública. Uma KeyStore também possui certificados de entidades confiáveis.

O Sistema operativo Windows possui uma KeyStore própria, onde são armazenados todos os certificados instalados. Existem também uma API embebida no Sistema Operativo que permite fazer a ponte entre SmarCards com certificados digitais, desde que o hardware possua um leitor de smartcards.

Programing, Programing, Program…

++Requisitos

Para Prosseguir é necessário termos instalado no nosso computador uma licença do Windows XP ou Superior, com a Framework .NET v2.0.

Ajuda possuir-mos também uma licença do Visual Studio 2005, se bem que não é obrigatório pois existem alternativas OpenSource bastante completas na WWW.

Geração de um Certificado Digital

Vamos gerar um ficheiro PKCS#12. Um ficheiro PKCS12 é um formato que permite o armazenamento de chaves privadas juntamente com o certificado de chave pública, protegidos por uma palavra-chave. Este ficheiro servirá de base para os testes que vamos produzir ao longo do desenvolvimento.
Não é obrigatória a geração deste ficheiro. Podemos criar em RunTime um certificado e exportar as respectivas chaves públicas e privadas. Este passo é apenas um exercício didáctico.

Criação das chaves

makecert.exe -sv MyKey.pvk -n "CN=P@P" MyKey.cer

ser-vos-á pedida uma palavra-chave para a criação da chave privada.

Criação do PKCS#12

pvk2pfx.exe -pvk MyKey.pvk -spc MyKey.cer -pfx cert.pfx -po pwd

Este aplicativo permite criar o ficheiro cert.pfx , protegido pela password pwd

Classe KeyManager.cs

A classe KeyManager.cs servirá de ponte para a geração/carregamento de chaves publicas/privadas/Certificados, usando RSA. É bastante simples alterar a classe para podermos usar também o DSA, mas deixo isso à vossa curiosidade e "investigação".

É também possível interligar esta classe com a KeyStore do Windows ou simplesmente gerar uma chave pública/privada e exporta-las para XML.

using System;
using System.Collections.Generic;
using System.Text;
 
#region Crypt
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;
#endregion
 
namespace AsynXmlCrypt
{
    public sealed class KeyManager
    {
        public static int KeySize = 1024;  // Tamanho da chave
 
        #region Private Properties
        private RSACryptoServiceProvider m_rsa = null;  // Provider RSA 
        #endregion
 
        #region Public Properties
 
        public RSACryptoServiceProvider KeyProvider     // Propriedade que exporta o provider
        {
            get { return m_rsa; }
        }
 
        #endregion
 
        #region Constructors
 
        // Inicializa o Provider com uma chave de tamanho igual à KeySize
        public KeyManager(){
            m_rsa = new RSACryptoServiceProvider(KeySize);
        }
 
        // Inicializa o Provider com um ficheiro
        public KeyManager(string filename)
        {
            m_rsa = new RSACryptoServiceProvider(KeySize);
            _loadKey(m_rsa, filename);            
        }
 
        // Inicializa o Provider com um ficheiro XML de chave public e um ficheiro XML de chave privada
        public KeyManager(string publicKeyFilename, string privateKeyFilename)
        {
            m_rsa = new RSACryptoServiceProvider(KeySize);
            LoadPublicKeyFromXmlFile(publicKeyFilename);
            LoadPrivateKeyFromXmlFile(privateKeyFilename);
        }
 
        // Inicializa o Provider com um certificado 
        public KeyManager(X509Certificate2 certificate)
        {
            if (certificate == null)
                throw new Exceptions.KeyManagerException("certificate not initialized");
 
            if (certificate.HasPrivateKey)                            
                m_rsa = certificate.PrivateKey as RSACryptoServiceProvider;
            else
                throw new Exceptions.KeyManagerException("certificate does not contains the PrivateKey ");
        }
 
        #endregion               
 
        #region SAVE
        // Exporta a chave privada para um ficheiro XML
        public void SavePrivateKeyToXmlFile( string filename, bool overwrite)
        {
            _saveKey(m_rsa, filename,overwrite , true);
        }
 
        // Exporta a chave publica para um ficheiro XML
        public void SavePublicKeyToXmlFile(string filename, bool overwrite)
        {
            _saveKey(m_rsa, filename,overwrite , false);
        }
 
        private void _saveKey(RSACryptoServiceProvider rsa, string filename, bool overwrite, bool privateKey){
            if (rsa == null)
                    throw new Exceptions.ServiceProviderException("Service Provider NULL Exception");
 
            if (System.IO.File.Exists(filename) && !overwrite)
                return;
 
            string Key = rsa.ToXmlString(privateKey);
            using (System.IO.StreamWriter sw = new System.IO.StreamWriter(filename, false, Encoding.UTF8))
            {
                sw.WriteLine(Key);
                sw.Close();
            }
        }
 
        #endregion
 
        #region LOAD
 
       // Carrega uma chave privada de um ficheiro XML
        public void LoadPrivateKeyFromXmlFile(string filename)
        {
            _loadKey(m_rsa, filename);
        }
 
       // Carrega uma chave publica de um ficheiro XML
        public void LoadPublicKeyFromXmlFile(string filename)
        {
            _loadKey(m_rsa, filename);
        }
 
        private void _loadKey(RSACryptoServiceProvider rsa, string filename)
        {
            if (rsa == null)
                throw new Exceptions.ServiceProviderException("Service Provider NULL Exception");
 
            if (!System.IO.File.Exists(filename))
                throw new System.IO.FileNotFoundException(filename);
 
            using (System.IO.StreamReader sr = new System.IO.StreamReader(filename, Encoding.UTF8))
            {
                string Key = sr.ReadToEnd();
                sr.Close();
                rsa.FromXmlString(Key);
            }
        }
 
        #endregion
 
    }
}

Utilizações da classe KeyManager.cs

Gerando um par de chaves

O seguinte exemplo ilustra a forma de gerar um par de chaves (1024 bits) para posteriormente assinar documentos.
As Chaves são posteriormente guardadas em ficheiros XML.

Normalmente não é muito usual guardar as chaves em ficheiros XML, mas nada impede a um utilizador, guardar e distribuir a sua chave pública, para assinatura e verificação.

using System;
using System.Collections.Generic;
using System.Text;
 
#region Crypt
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;
using AsynXmlCrypt;
 
#endregion
 
static class Program{
        [STAThread]
        static void Main()
        {
 
            AsynXmlCrypt.KeyManager.KeySize = 1024;
            AsynXmlCrypt.KeyManager km = new AsynXmlCrypt.KeyManager();
            km.SavePublicKeyToXmlFile(@"c:/Publica.xml",true);
            km.SavePrivateKeyToXmlFile(@"c:/Privada.xml", true);
        }
}

Como se pode verificar pela análise do código fonte, auxiliados pela classe que construímos, a geração de um par de chaves passa a ser de codificação simples/trivial .

Lendo um certificado PKCS#12

O seguinte exemplo ilustra a forma de carregar um certificado PKCS#12 para posteriormente assinar documentos.
Podemos tentar carregar o certifica
do gerado previamente no ponto Geração de um Certificado Digital

using System;
using System.Collections.Generic;
using System.Text;
 
#region Crypt
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;
using AsynXmlCrypt;
 
#endregion
 
static class Program{
        [STAThread]
        static void Main()
        {
            AsynXmlCrypt.KeyManager km = new AsynXmlCrypt.KeyManager(
                                  new X509Certificate2 @"c:/cert.pfx", "pwd")
            );        
        }
}

Lendo um certificado da KeyStore do Windows

O seguinte exemplo ilustra a forma de carregar um certificado da KeyStore do Windows para posteriormente assinar documentos.
O Código é genérico e num caso real será necessário filtrar o certificado a extrair da KeyStore.

using System;
using System.Collections.Generic;
using System.Text;
 
#region Crypt
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;
using AsynXmlCrypt;
 
#endregion
 
static class Program{
        [STAThread]
        static void Main()
        {
 
            // Store Current User
            X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
            X509Certificate2 cc = null;
            foreach (X509Certificate2 c in store.Certificates){
                // Vai buscar o primeiro
                cc = c;      
                break;
            }           
            store.Close();
 
            //Carrega o par de chaves do certificado.
            AsynXmlCrypt.KeyManager km = new AsynXmlCrypt.KeyManager(cc);      
        }
}

Numa aplicação "real", variantes deste código serão talvez aquelas que farão mais sentido de ser usadas já que normalmente pretendemos usar certificados que estejam instalados no nosso Sistema Operativo.

Assinar um documento XML

Vamos agora assinar um documento XML usando o certificado Cert.pxf, e recorrendo à classe KeyManager.cs

using System;
using System.Collections.Generic;
using System.Text;
 
#region Crypt
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;
using AsynXmlCrypt;
 
#endregion
 
static class Program{
        [STAThread]
        static void Main()
        {
 
            // Leitura do certificado :)
            AsynXmlCrypt.KeyManager km = new AsynXmlCrypt.KeyManager(
                                  new X509Certificate2 @"c:/cert.pfx", "pwd")
            );
 
            // Leitura de um documento XML    (Test.XML)
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.PreserveWhitespace = true;
            xmlDoc.Load("./test.xml");
 
            // Ok, vamos assinar o documento
            SignXml(xmlDoc, km);
 
            // Gravar o documento.
            xmlDoc.Save("./SignedTest.xml");
        }
 
       public static void SignXml(XmlDocument Doc, AsynXmlCrypt.KeyManager Key)
        {
 
            if (Doc == null)
                throw new ArgumentException("Empty XML Document Object ?");
            if (Key == null)
                throw new ArgumentException("Empty KeyManager Provider ?");
 
            SignedXml signedXml = new SignedXml(Doc);
 
            // Passamos a chave :)
            signedXml.SigningKey = Key.KeyProvider;
 
            // Criamos uma referencia para a assinatura
            Reference reference = new Reference();
            reference.Uri = "";
 
            // Adicionamos uma transformação enveloped à referencia.
            XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
            reference.AddTransform(env);
 
            // Adicionamos a referência ao objecto SignedXml.
            signedXml.AddReference(reference);
 
            // Assinamos.
            signedXml.ComputeSignature();
 
            // Extraimos a representação da assinatura em XML
            XmlElement xmlDigitalSignature = signedXml.GetXml();
 
            // Juntamos a assinatura XML ao documento.
            Doc.DocumentElement.AppendChild(Doc.ImportNode(xmlDigitalSignature, true));
 
            //Et Voilá :)
 
        }
}

Com este pedaço de código, criamos um documento SignedTest.xml com uma assinatura do tipo Enveloped.
O resultado do ficheiro assinado será "algo" do género …

<?xml version="1.0" encoding="UTF-8"?>
 
...
 
<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>9H/rQr2Axe9hYTV2n/tCp+3UIQQ=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>Mx4psIy9/UY+u8QBJRDrwQWKRaCGz0WOVftyDzAe6WHAFSjMNr7qb2ojq9kdipT8
Oub5q2OQ7mzdSLiiejkrO1VeqM/90yEIGI4En6KEB6ArEzw+iq4N1wm6EptcyxXx
M9StAOOa9ilWYqR9Tfx3SW1urUIuKYgUitxsONiUHBVaW6HeX51bsXoTF++4ZI+D
jiPBjN4HHmr0cbJ6BXk91S27ffZIfp1Qj5nL9onFLUGbR6EFgu2luiRzQbPuM2tP
XxyI7GZ8AfHnRJK28ARvBC9oi+O1ej20S79CIV7gdBxbLbFprozBHAwOEC57YgJc
x+YEjSjcO7SBIR1FiUA7pw==</SignatureValue>
</Signature>

A próxima etapa será a verificação/validação de um documento XML assinado.

Validar um documento assinado

A verificação de um documento previamente assinado é uma tarefa relativamente simples.
O código fonte inicial (leitura do ficheiro, e chaves) é de alguma forma idêntico ao do ponto anterior.

using System;
using System.Collections.Generic;
using System.Text;
 
#region Crypt
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;
using AsynXmlCrypt;
 
#endregion
 
static class Program{
        [STAThread]
        static void Main()
        {
 
            // Leitura do certificado :)
            AsynXmlCrypt.KeyManager km = new AsynXmlCrypt.KeyManager(
                                  new X509Certificate2 @"c:/cert.pfx", "pwd")
            );
 
            // Leitura de um documento XML coma respectiva assinatura    (SignedTest.XML)
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.PreserveWhitespace = true;
            xmlDoc.Load("./SignedTest.xml");
 
            // Ok, vamos verificar a validade do ficheiro XML
            bool result=VerifyXml(xmlDoc, km);
            if (result)
             Console.Writeln("Ficheiro Valido!");
            else
             Console.Writeln("Ficheiro Invalido!");
        }
 
       public static void VerifyXml(XmlDocument Doc, AsynXmlCrypt.KeyManager Key)
        {
 
            if (Doc == null)
                throw new ArgumentException("Empty XML Document Object ?");
            if (Key == null)
                throw new ArgumentException("Empty KeyManager Provider ?");
 
            // Instanciação/Inicialização
            SignedXml signedXml = new SignedXml(Doc);
 
            //Neste ponto vamos verificar quantos elementos (ASSINATURAS) possui o ficheiro XML.
            XmlNodeList nodeList = Doc.GetElementsByTagName("Signature");
 
            //O documento XML não possui nenhuma assinatura!!
            //Decidimos lançar uma excepção .
            if (nodeList.Count <= 0){
              throw new CryptographicException("Failed: No Sig was found in the document.");
            }
 
            //O documento XML possui mais que uma assinatura!!
            //No contexto actual, não possuimos KnowHow suficiente para tratar esta variante, 
            //pois queremos verificar uma relação de 1 para 1, ou seja, Um documento Assinado por uma chave.
            //Decidimos lançar uma excepção .
 
            if (nodeList.Count >= 2){
               throw new CryptographicException("Failed: More that one sig was found for the document.");
            }
 
            //Ok, se chegamos ate aqui, então estamos prontos para verificar a assinatura.
            //Carregamos o Elemento XML que contém a assinatura para o respectivo objecto 
            //que faz a verfificação.
            signedXml.LoadXml((XmlElement)nodeList[0]);
 
            //Retornamos o resultado da verificação.
            return signedXml.CheckSignature(Key.KeyProvider);
        }
}

O código fonte anterior é genérico e serve para verificar a validade de um documento XML, com uma assinatura (Detached, Enveloped ou Enveloping). Verificamos também que é necessário termos na nossa posse a chave pública (neste exemplo temos ambas, já que passamos o certificado), para verificar a validade do documento. Numa situação real, muitas das vezes não temos acesso à chave pública do autor e mesmo assim temos a necessidade de verificar a validade de um documento.

Fazendo um pequeno "upgrade" ao código de assinar, podemos incluir a nossa chave pública na assinatura, libertando o nosso código de verificação para uma versão mais "light" e eficiente.

Upgrade da assinatura

public static void SignXml(XmlDocument Doc, AsynXmlCrypt.KeyManager Key)
        {
 
            if (Doc == null)
                throw new ArgumentException("Empty XML Document Object ?");
            if (Key == null)
                throw new ArgumentException("Empty KeyManager Provider ?");
 
            SignedXml signedXml = new SignedXml(Doc);
 
            // Passamos a chave :)
            signedXml.SigningKey = Key.KeyProvider;
 
            // Criamos uma referencia para a assinatura
            Reference reference = new Reference();
            reference.Uri = "";
 
            // Adicionamos uma transformação enveloped à referencia.
            XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
            reference.AddTransform(env);
 
            // Adicionamos a referência ao objecto SignedXml.
            signedXml.AddReference(reference);
 
            //BEGIN UPGRADE ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
            //Inserimos a nossa chave pública na própria assinatura para posterior verificação.
 
            #region KeyInfo
            KeyInfo keyInfo = new KeyInfo();
            keyInfo.AddClause(new RSAKeyValue((RSA)Key.KeyProvider));
            signedXml.KeyInfo = keyInfo;
            #endregion
 
            //END UPGRADE   ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
            // Assinamos.
            signedXml.ComputeSignature();
 
            // Extraimos a representação da assinatura em XML
            XmlElement xmlDigitalSignature = signedXml.GetXml();
 
            // Juntamos a assinatura XML ao documento.
            Doc.DocumentElement.AppendChild(Doc.ImportNode(xmlDigitalSignature, true));
 
            //Et Voilá :)
 
        }

Upgrade da função de verificação

public static void VerifyXml(XmlDocument Doc)
        {
 
            if (Doc == null)
                throw new ArgumentException("Empty XML Document Object ?");
            if (Key == null)
                throw new ArgumentException("Empty KeyManager Provider ?");
 
            // Instanciação/Inicialização
            SignedXml signedXml = new SignedXml(Doc);
 
            //Neste ponto vamos verificar quantos elementos (ASSINATURAS) possui o ficheiro XML.
            XmlNodeList nodeList = Doc.GetElementsByTagName("Signature");
 
            //O documento XML não possui nenhuma assinatura!!
            //Decidimos lançar uma excepção .
            if (nodeList.Count <= 0){
              throw new CryptographicException("Failed: No Sig was found in the document.");
            }
 
            //O documento XML possui mais que uma assinatura!!
            //No contexto actual, não possuimos KnowHow suficiente para tratar esta variante, 
            //pois queremos verificar uma relação de 1 para 1, ou seja, Um documento Assinado por uma chave.
            //Decidimos lançar uma excepção .
 
            if (nodeList.Count >= 2){
               throw new CryptographicException("Failed: More that one sig was found for the document.");
            }
 
            //Ok, se chegamos ate aqui, então estamos prontos para verificar a assinatura.
            //Carregamos o Elemento XML que contém a assinatura para o respectivo objecto que 
            //faz a verfificação.
            signedXml.LoadXml((XmlElement)nodeList[0]);
 
           //BEGIN UPGRADE :::::::::::::::::::::::::::::::::::::::::::::::::::::
 
            //Tentamos extrair a chave publica da assinatura embebida. 
            //Se não conseguirmos então não podemos atestar a 
            //validade do documento retornando FALSO.
 
            KeyInfo keyInfo = signedXml.KeyInfo;
            if (keyInfo.Count == 0)
                return false;           
 
           //END UPGRADE :::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
            //Retornamos o resultado da verificação com a assinatura embebida.
            return signedXml.CheckSignature();
        }

Assim, e com base neste upgrade, não necessitamos de passar nenhuma chave pública para verificar a validade de um certo documento XML assinado, como podemos verificar no próximo pedaço de código!

static void Main()
        {  
            // Leitura de um documento XML coma respectiva assinatura    (SignedTest.XML)
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.PreserveWhitespace = true;
            xmlDoc.Load("./SignedTest.xml");
 
            // Ok, vamos verificar a validade do ficheiro XML
            bool result=VerifyXml(xmlDoc);
            if (result)
             Console.Writeln("Ficheiro Valido!");
            else
             Console.Writeln("Ficheiro Invalido!");
        }

Nota: Como este documento é um artigo didáctico, não estou a fazer o devido tratamento das excepções que podem ocorrer no funcionamento do software, o código aqui desenvolvido não está pronto para deploy em ambientes de produção como devem todos perceber.

Reflexões …

Tenham em atenção que a assinatura refere-se ao documento XML, assim se alterarmos o conteúdo do documento XML a verificação falhará. Mas se alterarmos o Elemento ''Signature''; Incluirmos dados por exemplo sem alterar o conteúdo XML que assinamos, o processo de verificação e validação do documento funcionará correctamente.

Perguntam os leitores: Mas esta "falha" não invalida de certa forma a autenticidade do documento?
Não, porque assinamos um documento (XML DATA). Além de que, não é uma falha, podemos "corrigir" esta "distracção" adicionando elementos ao objecto ''Signature'' que verifiquem a autenticidade da própria assinatura.

Este artigo, embora exiga conceitos mais avançados de criptografia, é de certa forma introdutório e fornece aos leitores boas bases para investigação e desenvolvimento de novas funcionalidades tais como : Múltiplas Assinaturas num documento, assinaturas com TimeStamp, Assinar apenas certos elementos XML, Cifrar/Decifrar Documentos/elementos XML baseado na nossa chave pública/privada, etc.

Conclusões

O XMLDSIG é de grande utilidade para a indústria, a sua aplicação passa por as mais variadas áreas. Podemos integrar esta tecnologia com as mais diversas áreas, tais como, WebServices para garantir a fiabilidade da transmissão de dados, podemos também usa-la para criar sistemas de protecção de software, aplica-la na troca de dados entre diferentes entidades que exijam garantias de autenticidade e não repudio da informação(O SAFT-PT por exemplo), Facturação electrónica, etc.

As aplicações são inúmeras, e tudo depende da imaginação e criatividade de quem desenvolve, integra e arquitecta sistemas.

"A imaginação é mais importante que o conhecimento. "
( Albert Einstein )

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License