O NeoMatrix Tech está de casa nova!

Você deverá ser redirecionado em 6 segundos. Se não, visite:
http://www.leonelfraga.com/neomatrixtech
e atualize seus favoritos.

Aviso IMPORTANTÍSSIMO!

Aviso aos navegantes:

O NeoMatrix Tech mudou de casa!!!

A partir de agora, acessem pelo novo endereço:

http://www.leonelfraga.com/neomatrixtech

Ué... mas é só o domínio mudou de lugar?

R: Na verdade, não é bem assim hehe. Este domínio que você acessa agora aponta para um blog hospedado no Blogger, enquanto no novo, aponta para um blog na plataforma Wordpress, hospedagem própria, muito mais rápida e com um layout mais agradável de ler ;)

Não vou fechar este domínio igual ao que eu fiz com o NM Light (que já está 100% na nova plataforma). Talvez beeeeeeem depois eu faça isso.

Todos os posts daqui se encontram lá, e novos posts serão colocados somente no novo endereço.
A única coisa que não consegui importar foram os comentários. Mas em breve vai ter um post contando sobre a epopéia que foi migrar o NeoMatrix Tech!

Somente vou fechar a área de comentários daqui. Caso queiram comentar, favor ver o post correspondente no "Novo NeoMatrix Tech" e comentem por lá. É bem melhor! (pena que os permalinks "amigáveis para SEO" não funcionam lá, dá erro 404 e não consigo fazer a configuração funcionar. E olha que eu já vi vários artigos falando desse assunto :( ).

Quem assina o feed, já está lendo o conteúdo do novo NeoMatrix Tech!

sábado, 12 de dezembro de 2009

Assinatura Digital com C# (.NET)

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!!!

download1[1] Baixe agora: Métodos para Assinatura Digital utilizando .NET com C# (13 KiB)

4 comentários:

Anônimo,  18 de jan de 2010 15:24:00  

Muito bom o Post. Me surguiu uma dúvida. como eu valido se a assinatura é válida?

Leonel Fraga de Oliveira 21 de jan de 2010 22:07:00  

@Anônimo:
Verdade... ficou faltando essa parte de verificação da validade da assinatura e de obter os certificados utilizados para assinar um arquivo (dica: é com o objeto SignedCms!). Vou dar uma pesquisada sobre o assunto, e em breve continuação deste post, inclusive com uma micro modificação para fazer esse código funcionar com certificados do tipo A3 ;)

Wagner 3 de fev de 2010 17:25:00  

Ótimo post! Exatamente da forma que estou fazendo. Agora eu tenho uma dúvida: Como posso fazer uma assinatura remota? Exemplo: O pdf está no servidor e o token (A3) no client, existe uma forma de passar essas informaçoes para o servidor para efetuar a assinatura? Estou usando ASP.NET

Abraço

Leonel Fraga de Oliveira 4 de fev de 2010 23:07:00  

@Wagner:

Valeu!

Quanto a assinar via cliente, com uma aplicação ASP.NET, vc provavelmente vai ter que construir um componente ActiveX ou um Applet em Java, para poder ler os dados do certificado e assinar o arquivo.

Abraços!


Postar um comentário

Para tornar este artigo ainda mais interessante, escreva suas críticas (desde que construtivas e sem ofenças), elogios, sugestões, complementos, dúvidas, etc, etc, etc!!!

Sobre o NeoMatrix Tech

Meu blog para assuntos profissionais, ligado com tecnologia.
Dicas de programação (grande parte de C# e ASP.NET, mas não limitado a essa plataforma :-) ), dicas de utilitários, análises de equipamentos e serviços, resenhas sobre sites que eu visito, relacionados com tecnologia, opinião sobre mercado de trabalho, metodologias de desenvolvimento, comportamento no mundo tecnológico...

NeoMatrix Light

  © Blogger templates ProBlogger Template by Ourblogtemplates.com 2008 - Editado e configurado por Leonel F.

Voltar ao TOPO