Em diversas aplicações que tratam informações sensíveis, tais como Nota Fiscal Eletrônica, emissão de certidões, entre outras, temos que trabalhar com assinaturas digitais.
Uma assinatura digital é uma marca que prova a autenticidade do arquivo, ou de uma transação, e a identidade do emissor do mesmo.
Para tal autenticidade não ser contestada, é feito o uso de um elemento chamado Certificado Digital, que é emitido por uma Autoridade Certificadora (como VeriSign, CertiSign, Serasa, Equifax, entre outras empresas) através de um processo burocrático.
E existem também diversos tipos de certificados digitais, tais como um arquivo que é instalado no micro e que contém a chave privada do usuário, certificados que são armazenados em cartões inteligentes, como o e-CPF, appliances que armazenam certificados, entre outros.
Um dos padrões de certificados digitais mais utilizado é o padrão X.509, cuja descrição mais detalhada você pode encontrar aqui.
Um tipo de assinatura digital é o formato PKCS, que “empacota” o arquivo em uma mensagem, que além do próprio arquivo contém as informações dos certificados utilizados para assiná-lo.
Certificados, no plural mesmo? Sim, um arquivo pode conter mais de uma assinatura digital :).
E o .NET Framework possui classes que trabalham com o formato PKCS e certificados X.509, nos namespaces System.Security.Cryptography.Pkcs e System.Security.Cryptography.X509Certificates.
O código abaixo serve para realizar a assinatura digital de um arquivo com múltiplas assinaturas, através de certificados X.509, utilizando somente classes nativas do .NET Framework:
public static byte[] SignFile(X509Certificate2Collection certs, byte[] data)
{
try
{
ContentInfo content = new ContentInfo(data);
SignedCms signedCms = new SignedCms(content, false);
if (VerifySign(data))
{
signedCms.Decode(data);
}
foreach (X509Certificate2 cert in certs)
{
CmsSigner signer = new CmsSigner(cert);
signer.IncludeOption = X509IncludeOption.WholeChain;
signedCms.ComputeSignature(signer);
}
return signedCms.Encode();
}
catch(Exception ex)
{
throw new Exception("Erro ao assinar arquivo. A mensagem retornada foi: " + ex.Message);
}
}
Acima somente coloquei o método principal, base de toda a classe de assinatura que será disponibilizada no final do post.
Ele recebe como parâmetros uma coleção de certificados digitais X.509, representados no .NET Framework pela classe X509Certificate2, e um array de byte que corresponde ao arquivo a ser assinado.
A classe ContentInfo contém a mensagem PKCS #7 (o arquivo assinado), representada no nosso programa pela variável content. Inicializamos essa variável com os dados do arquivo a ser assinado.
Mas pera ae… o arquivo ainda não é assinado! Sim, ele ainda não é assinado, e as assinaturas digitais serão gerenciadas pela variável signedCms, da classe SignedCms. É ela que faz o tratamento das assinaturas contidas na mensagem em signedCms, e a classe ContentInfo é exatamente o conteúdo da mensagem (no nosso caso, um arquivo qualquer) a ser assinada ou a ter as assinaturas verificadas.
Após carregar o arquivo nas classes de gerenciamento de assinaturas, verificamos se o mesmo já não é assinado, através do método VerifySign logo abaixo:
public static bool VerifySign(byte[] data)
{
try
{
SignedCms signed = new SignedCms();
signed.Decode(data);
}
catch
{
return false; // Arquivo não assinado
}
return true;
}
Ele simplesmente recebe um array de bytes com a mensagem, instancia uma classe SignedCms e chama o método Decode() com o array de bytes do parâmetro.
Esse método Decode() faz a decodificação da mensagem PKCS #7 e caso o arquivo contenha alguma assinatura digital, sua propriedade ContentInfo (sim, ela mesma, a que instanciamos no outro método) será populada com o arquivo assinado.
Caso o arquivo não possua assinaturas digitais, é disparada uma exceção e o método retornará false.
Voltando ao método SignFile, caso o arquivo possua assinaturas digitais, ele é recarregado na variável signedCms através do método Decode(), que substitui o conteúdo anterior, aquele que colocamos no construtor.
E tem diferença se o arquivo é assinado ou não?
Sim, uma diferença até que sutil: Lembra que eu falei que um arquivo pode ser assinado com múltiplos certificados?
Caso fiquemos apenas com a carga da variável contentCms pelo construtor, somente as informações do último certificado, caso ele fosse assinado, serão gravadas na assinatura. Utilizando o método Decode(), ele manterá os certificados que foram utilizados anteriormente. E se chamássemos ditetamente o método Decode() e o arquivo não contiver certificados, dará erro.
Agora é que vamos assinar efetivamente o arquivo: Dentro da iteração foreach, que varre cada certificado informado na coleção certs, do tipo X509CertificateCollection, criamos uma instância da classe CmsSigner, e informamos em seu construtor o certificado. Incluímos na assinatura toda a cadeia de certificados, conforme a propriedade IncludeOption do nosso objeto signedCms e a opção X509IncludeOption.WholeChain.
Importante: O certificado a ser carregado no objeto CmsSigner deve ter a chave privada.
Após isso, chamamos o método ComputeSignature do nosso objeto signedCms, que inclui as assinaturas digitais com o certificado carregado na variável signer (do tipo CmsSigner) no arquivo que carregamos em signedCms.
Terminada a inclusão de todas as assinaturas, por fim, chamamos o método Encode() da variável signedCms, que retornará um array de bytes com a mensagem PKCS #7 codificada (leia: o arquivo assinado digitalmente).
Mas… como carrego um certificado gravado para um objeto X509Certificate2?
Os certificados podem ser gravados em arquivos no disco (com extensão .cer, .pfx, etc) ou instalados no repositório de certificados do Windows. Para carregar um certificado do disco, vamos estudar esta sobrecarga do nosso método SignFile:
public static byte[] SignFile(string CertFile, string CertPass, byte[] data)
{
FileStream fs = new FileStream(CertFile, FileMode.Open);
byte[] buffer = new byte[fs.Length];
fs.Read(buffer, 0, buffer.Length);
X509Certificate2 cert = new X509Certificate2(buffer, CertPass);
fs.Close();
fs.Dispose();
return SignFile(cert, data);
}
Essa sobrecarga recebe como parâmetros o path do arquivo de certificado e a senha utilizada para exportar a chave privada, além do arquivo a ser assinado.
Abrimos o arquivo utilizando um FileStream, e colocamos o conteúdo do stream em um array de bytes. Até aí, nada de exepcional.
Após isso, instanciamos um objeto da classe X509Certificate2, que recebe no construtor que utilizamos os dados brutos do certificado, que está no array de bytes que carregamos através do FileStream e a senha utilizada para exportar a chave privada.
Em seguida, utilizamos uma outra sobrecarga do método SignFile, que apenas coloca um certificado digital em uma coleção X509Certificate2Collection com o método Add() e por fim executa o método principal.
Para pegar os certificados instalados no Repositório de Certificados do Windows (a instalação deles foge do escopo deste post), utilize o seguinte código:
private static X509Certificate2 FindCertOnStore(int idx)
{
X509Store st = new X509Store(StoreLocation.CurrentUser);
st.Open(OpenFlags.ReadOnly);
X509Certificate2 ret = st.Certificates[idx];
st.Close()
return ret;
}
O processo é bem simples: Crie uma instância da classe X509Store, que representa o repositório de certificados, e indique qual repositório quer abrir. No nosso exemplo, indicamos o repositório “Pessoal” do usuário que está logado na máquina quando a aplicação é executada, e chamamos o método Open() indicando somente leitura.
Daí, os certificados estarão na propriedade Certificates, que por sinal é do tipo X509Certificate2Collection. Você poderá pegar um certificado específico através de um índice ou utilizar a coleção como um todo.
É isso! Assinar arquivos digitalmente no .NET é mais simples do que eu pensava :) Um abraço e Feliz 2010!!!
Baixe agora: Métodos para Assinatura Digital utilizando .NET com C# (13 KiB)
Leia o restante deste post...