14 de julho de 2009

Acessando o Certificate Store do Windows com CAPICOM

Desde que eu publiquei aqui posts sobre Certificados Digitais usando o framework .Net, várias pessoas me contataram dizendo que não desenvolvem com .Net e perguntando como acessar o Certificate Store do Windows e como assinar documentos digitalmente sem usar o Framework. Já havia falado em outra ocasião mas repito aqui: a solução é usar a biblioteca CAPICOM (Cryptographic Application Programming Interface) da Microsoft, cujo download é gratuito.

Vou mostrar nesse post como utilizar as classes do CAPICOM em Delphi para acessar o Certificate Store do Windows já que esse passo precede a assinatura de documentos - é preciso recuperar um Certificado Digital para realizar a assinatura. No post sobre como trabalhar com o Certificate Store com .NET há mais informações e terminologia sobre o assunto.

Como o CAPICOM é uma biblioteca COM, o primeiro passo é extrair as interfaces e colocá-las na forma de fontes do Delphi. Se ainda não tem os fontes, veja aqui como fazer essa extração e gerá-los. Para usar as classes exportadas, o seu programa deve incluir a unit gerada:
uses CAPICOM_TLB;
Da mesma forma que no .NET, há três classes básicas no CAPICOM para acessar o Store:
TStore que fornece os métodos para acessar o Certificate Store. Há um método Open nessa classe onde se estipula qual parte do Store se quer acessar: store pessoal ("My"), as autoridades certificadoras ("CA"), etc.
TCertificates é uma lista com todos os certificados que atendem os parâmetros especificados ao abrir o Store ou quando se usa a função de seleção Select.
TCertificate representa um certificado e suas propriedades, isto é, se ele está válido, qual é a chave pública, se há uma chave privada disponível e qual é esta chave.

O trecho de código Delphi abaixo abre o Certificate Store "My" da máquina local com privilégio apenas para leitura, recupera todos os certificados encontrados e percorre a lista pesquisando os certificados que são válidos.
var i : integer;
store : TStore;
cert : TCertificate;
certs : TCertificates;
ov : OleVariant;
begin
store := TStore.Create (self);

store.Open (CAPICOM_LOCAL_MACHINE_STORE, 'My', CAPICOM_STORE_OPEN_READ_ONLY);

certs := TCertificates.Create(self);
certs.ConnectTo(store.Certificates as ICertificates2);

cert := TCertificate.Create(self);
for i := 1 to certs.Count do begin
ov := (certs.Item [i]);
cert.ConnectTo (IDispatch (ov) as ICertificate2);

if cert.HasPrivateKey And
(cert.ValidFromDate <= Now) And
(cert.ValidToDate >= Now) then begin
{ Use o certificado aqui para, por exemplo, assinar um documento. }
end;
end;

store.Close;

cert.Free;
certs.Free;
store.Free;
end;

Algumas observações a respeito desse código:
As classes do CAPICOM foram instanciadas através de seus respectivos construtores Create. Por questão de compatibilidade, a Microsoft mantem na assinatura das funções da interface os tipos de estrutura usadas na primeira versão do CAPICOM. Como estou usando o CAPICOM 2.x, o TCertificates aqui é uma extensão da interface original ICertificates. Para poder usar todos os recursos da nova estrutura, crio na mão uma instância do TCertificates e conecto-a à referência de interface retornada pelo TStore. O mesmo vale para o ICertificate
O TCertificates é uma lista e a propriedade Count indica quantos certificados há nesta lista.
Para acessar um certificado na lista TCertificates use a propriedade Item. Valores válidos para indexar essa propriedade vão de 1 até Count
O mapeamento gerado pelo Delphi para a propriedade Item retorna um valor do tipo OleVariant. Na verdade, ela encapsula uma referência à interface ICertificate2 mas como não é permitido fazer um cast direto para esse tipo, transformo antes o valor retornado pelo Item em um IDispatch. Como o IDispatch é o ancestral de todas as interfaces (em qualquer COM), posso então fazer o cast para o tipo correto, conectá-lo à classe TCertificate e usar normalmente as propriedades do ICertificate2 para verificar se o certificado é válido e tem chave privada que possa ser usada num assinatura digital.
No exemplo acima, acesso apenas os certificados My armazenados na área da máquina local. Se quiser acessar outros certificados (CA ou root, por exemplo) em outras áreas (específicos do usuário, por exemplo) veja a documentação do Open para saber que valores passar.
A função Close do Store tem que ser chamada quando não for mais usar o Store ou se for necessário reabrí-lo para acessar outro conjunto de certificados.

35 comentários :

Anônimo disse...

Muito bom!
Mas tenho uma duvida, o procedimento acima é capaz de assinar arquivos executaveis!?

Gustavo Fabbro disse...

Vamos por partes:
1) O trecho de código mostrado neste post apenas localiza os certificados, ainda não faz a assinatura em si - será assunto para um outro post.
2) Para assinar um executável, planeje antes e tome alguns cuidados pois inserir nele uma assinatura poderá danificá-lo.

Se o quê você quer é garantir que o conteúdo do executável não foi modificado, pode gerar um DigestValue externo e distribuí-lo junto com o programa (e a chave pública para validação).

A Toca do Panda disse...

Salvou minha vida, vou recomendar o seu blog para muitos amigos meus, parabéns.

Anônimo disse...

cara, fiz do jeito que vc descreveu e no meu d7 nao funcionou, ou seja, ele trouxe count de zero e na verdade tenho 3 certificados instalados na maquina (um deles tem data de validade até 2011).
sabe o que poderia ser?
vlw
danilo.

Luís Gustavo Fabbro disse...

O exemplo recupera os certificados da área 'My' do Certificate Store. Verifique se os certificados que você tem estão instalados nessa área. Se não for, mude o nome para a área correta.

Anônimo disse...

luíz desculpe minha ignorância, mas onde consigo ver essa área? Atualmente eu vou em "opções da internet", aba conteúdo, botao certificados, e os certificados que tenho na maquina aparecem na aba Pessoal,seria isso?
vlw
Danilo

Luís Gustavo Fabbro disse...

Danilo

A forma que vc citou para ver seus certificados é valida - outra seria através do Iniciar -> Executar no windows e digitar mmc \windows\system32\certmgr.msc. A aba Personal é realmente mapeada para o local 'My'.

Outras coisas que você pode verificar: se o certificado está instalado só pra seu usuário no windows ou se todos no computador têm acesso a eles. Veja a documentação da função Open do objeto store para ver quais valores são válidos. O exemplo do post extrai apenas o que estiver instalado para todos os usuários (valor LOCAL_MACHINE); tente outros valores.

Certifique-se também que há uma chave privada em seu certificado pois o código está validando isto.

Anônimo disse...

ai luiz, olhei a guia de documentação e comparei com o codigo que eu ja tinha em c# e descobri que era mesmo configuração, ou seja, no meu funcionou assim...
store.Open (CAPICOM_CURRENT_USER_STORE, 'MY', CAPICOM_STORE_OPEN_READ_WRITE);
brigadão pela dica, vlw.

Anônimo disse...

Luís,
Qual seria o nome do repositório para pegar o certificado do usuário. Pois a NFSe de salvador exige assinatura e segundo instruções da dll utilizada por ele (Sefaz Salvador), tenho que copiar o certificado da conta usuário para conta local. Mesmo colocando o repositório "My" para que a dll busque do usuário. Sabe algo sobre a prefeitura? ou qual nome de repositório devo por para que a dll busque do lugar certo.

Luís Gustavo Fabbro disse...

Há 2 parâmetros que determinam o local onde um certificado está instalado: a localização (que pode ser do usuário ou do computador) e o nome do repositório (que pode ser MY, CA, ROOT, etc.)

Você diz que colocou o seu no repositório MY mas não diz em qual localização. Pelo jeito, no seu caso o certificado tem que estar no MY do Local Computer não no do Current User.

Veja a documentação do Store.Open para mais detalhes.

Anônimo disse...

Boa noite meu amigo, teria mais alguma coisa específica, porque já utilizei todas as opções que você mencionou mas mesmo assim não consigo acessar os meus certificados

Luís Gustavo Fabbro disse...

Realmente não há nada além do exposto no post ou nos comentários. Encontrar um certificado que tenha sido importado para o Certificate Store é uma questão de abrir a localização (normalmente CAPICOM_CURRENT_USER_STORE ou CAPICOM_LOCAL_MACHINE_STORE) e a pasta correta dentro desse local (My, por exemplo).

Você consegue enxergar seu certificado pelo gerenciador de certificados do Windows ?

Você consegue listar algum certificado via programação ? Dá mensagem de erro ou a lista simplesmente vem vazia ?

Thiago Araújo disse...

Não consegui utilizar no C++ Builder XE. Conseguir fazer a instalação normalmente mas não consigo instanciar o objeto (new). Alguem já utilizou no C++ Builder?

Grato

Luís Gustavo Fabbro disse...

Thiago

O C++ Builder encapsula de maneira diferente as classes quando você importa um OCX como o do CAPICOM. Você pode usar essas estruturas - chamadas Co Classes - sem necessidade de dar new :

CoStore s;
ICertificates2Ptr certs;

store = s.Create();
store->Open(CAPICOM_CURRENT_USER_STORE,
WideString ("My"),
CAPICOM_STORE_OPEN_READ_ONLY);
certs = store->Certificates;
{ ... }

As coclasses trabalham com ponteiros inteligentes; com isso, também não é preciso se preocupar em destruí-los. A memória associada a eles é liberada automaticamente quando a variável sai de escopo.

Valter Junior disse...

Luís,

Estou desenvolvendo uma aplicação para enviar CT-e para validação no site da receita, bem semelhante ao processo da NF-e. Estou pesquisando muito no seu blog mas fiquei com uma dúvida no procedimento acima: Neste código você exemplifica como "ler" todos os certificados existentes na máquina local no repositório "My". Mas como eu vou saber quais destes é o certificado que eu necessito para assinar meu XML?

Desde já agradeço e parabéns pelo blog, realmente muito útil.

Valter

Luís Gustavo Fabbro disse...

Valter

Sua aplicação terá que ser configurável para que o usuário tenha a chance de indicar ao menos uma vez o certificado que deve ser usado.

Na solução da ABC71, optamos por salvar em banco de dados o número serial do certificado. Com isso, acrescentamos às comparações mostradas no post uma outra pra ver se o cert.SerialNumber corresponde ao certificado indicado pelo usuário.

[]s

Valter Junior disse...

Luis,

Obrigado mais uma vez pela atenção. Tenho só mais uma dúvida: Como o usuário pode ver esse número de série do certificado? porque eu acessei os certificados da minha máquina, por exemplo, e em todos o número de série vem apenas "00". (Note que ainda estou estudando o assunto "Certificados digitais")

Grato.

Luís Gustavo Fabbro disse...

Valter

Se consultar pelo aplicativo de certificados do Windows, o Serial aparecerá como uma sequência de bytes mas o SubjectName, que é uma descrição mais fácil de ser compreendida, estará no mesmo formato retornado pelo CAPICOM. Essa variável, no entanto, pode não ser única. Então, exiba para seu usuário o SubjectName mas armazene o Serial retornado pelo CAPICOM e o use em seu programa localizar o certificado correto.

[]s

Anônimo disse...

Luiz, boa tarde.

Estou precisando pegar o certificado do token USB e assinar um documento .DOCX pelo Browser. Até conseguir fazer, mas quando publiquei no servidor não funcionou porque o browser não consegue acessar a máquina pra pegar o certificado.
Quando rodo a aplicação local funciona.

Você tem alguma sugestão de como fazer isso via browser????

Luís Gustavo Fabbro disse...

Imagino que vc esteja usando uma tecnologia Web pra gerar as páginas HTML de sua solução - algo como o ASP.Net. Nesse caso, dê uma olhada na propriedade ClientCertificate. Ela é membro da classe HttpRequest, que representa no programa ASP as requisições enviadas pelo browser.

[]s

Alcimar Bonomi disse...

Muito Obrigado pela Resposta. Estou usando c# + MVC. Quando estou usando a aplicação em Localhost aparece uma janela para escolher o certificado. Aí escolho o certificado do token e continuo na aplicação. Agora quando publiquei no servidor a janela para escolher o certificado não aparece e aparece a mensagem "A Sessão atual não é interativa". Obrigado

Luís Gustavo Fabbro disse...

Alcimar

Você terá que configurar seu site no IIS para que ele exija o uso de um certificado. Assim, todas as requisições feitas ao servidor enviarão junto os dados do certificado escolhido e o seu programa poderá recuperá-lo via Request.ClientCertificate.Certificate.

Há uma discussão sobre esse assunto nesse fórum.

Não sei como vc implementou para que dê o erro reportado mas, como sua aplicação roda no servidor, ela não poderá exibir diretamente a tela para o usuário selecionar um certificado. Isto é, não faz sentido usar a função de UI da classe de certificados no servidor.

[]s

Alcimar Bonomi disse...

Luiz, agradeço a sua resposta. Ja li a discussão nesse fórum. Mas la também ninguém conseguiu fazer isso. O que me chama a´atenção é que o site da receita federal é em aspx e pede o certificado sem usar nenhum applet, só acessando o link ja pede o certificado. Veja o link https://cav.receita.fazenda.gov.br/eCAC/publico/Login/Certificado.aspx

Luís Gustavo Fabbro disse...

Alcimar

Exigir que o usuário forneça um certificado para acessar um site é uma configuração do servidor. Veja o roteiro para isso no artigo Specify Whether to Use Client Certificates (IIS 7) do Technet.

Não sei se há algum código HTML ou javascript que possa ser inserido na página para exibir já de início a lista dos certicados instalados no Client para seleção.

[]s

Marcos Fernando Barbosa disse...

Gostaria de saber se tem como pegar a chave publica que esta dentro do certificado?

Luís Gustavo Fabbro disse...

Marcos

O TCertificate implementa a interface ICertificate2, que possui a propriedade PublicKey para representar a informação que você quer. Veja a documentação dessa interface no endereço http://msdn.microsoft.com/en-us/library/aa376092%28v=vs.85%29.aspx

[]s

Marcos Fernando Barbosa disse...

Gostaria de saber tb se tem jeito de pegar a o valor da assinatura contido no certicado digital. Se tiver como mandar algum fonte feito em delphi agradeço. Colo abaixo aqui o meu :
OE_Util:=createoleobject('CAPICOM.Utilities.1');
store := TStore.Create (self);
store.Open( CAPICOM_SMART_CARD_USER_STORE, 'Root', CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED);
certs := TCertificates.Create(self);
certs.ConnectTo(store.Certificates as ICertificates2);

cert := TCertificate.Create(self);
for i := 1 to certs.Count do
begin
ov := (certs.Item [i]);
cert.ConnectTo (IDispatch (ov) as ICertificate2);

Memo1.Lines.Add('Versão do certificado--->' + IntToStr(cert.Version));
Memo1.Lines.Add('Nº serial do certificado--->' + cert.SerialNumber);
Memo1.Lines.Add('Autoridade certificadora--->' + cert.SubjectName);
Memo1.Lines.Add('Nº remoto--->' + cert.RemoteMachineName);
Memo1.Lines.Add('ThumbPrint--->' + cert.Thumbprint);
Memo1.Lines.Add('IssuerName--->' + cert.IssuerName);
Memo1.Lines.Add('Informação1--->' + cert.GetInfo(CAPICOM_CERT_INFO_SUBJECT_SIMPLE_NAME));
Memo1.Lines.Add('Informação2--->' + cert.GetInfo(CAPICOM_CERT_INFO_SUBJECT_EMAIL_NAME));
Memo1.Lines.Add('Informação3--->' + cert.GetInfo(CAPICOM_CERT_INFO_ISSUER_DNS_NAME));
Memo1.Lines.Add('HASH CODE---->' +IntToStr(cert.GetHashCode));
Memo1.Lines.Add('Tamanho da chave publica--->' + IntToStr(cert.PublicKey.Length));
Memo1.Lines.Add('Algoritimo de validação--->' + cert.PublicKey.Algorithm.Value);

strstring:= OE_Util.BinaryToHex(Cert.PublicKey.EncodedKey.Value[CAPICOM_ENCODE_BINARY]);
Memo1.Lines.Add('Valor da Chave Privada em string---->' + strstring);
Memo1.Lines.Add('-------------------------------------');

Memo1.Lines.Add('CHAVE PUBLICA BASE 64---->' + cert.PublicKey.EncodedKey.Value[CAPICOM_ENCODE_BASE64]);
Memo1.Lines.Add('-------------------------------------');

// Memo1.Lines.Add('conteudo do cerificado');
// Memo1.Lines.Add(cert.Export(CAPICOM_ENCODE_ANY));

if cert.HasPrivateKey then
begin
Memo1.Lines.add('CERTIFICADO COM CHAVE PRIVADA');
if (cert.IsValid.Result) then
begin
Memo1.Lines.Add('CERTIFICADO VALIDO');
//validar no web service
end
else
Memo1.Lines.Add('CERTIFICADO INVÁLIDO')
end
else
begin
Memo1.Lines.add('CERTIFICADO SEM CHAVE PRIVADA');
if (cert.IsValid.Result) then
Memo1.Lines.Add('CERTIFICADO VALIDO')
else
Memo1.Lines.Add('CERTIFICADO INVÁLIDO');
end;

Memo1.Lines.Add('-------------------------------------------------------');

Luís Gustavo Fabbro disse...

Marcos

O que vc chama de "pegar a assinatura" do certificado? Se vc quer usar o certificado para assinar um documento, dê uma olhada no link http://balaiotecnologico.blogspot.com.br/2009/07/assinando-documentos-com-capicom.html

Todos os posts do blog envolvendo CAPICOM estão agrupados no endereço http://balaiotecnologico.blogspot.com.br/search/label/CAPICOM.

[]s

drgarcia1986 disse...

Eu gerei a CAPICOM_TLB.pas, importei em um projeto de teste e copiei o código para recuperar o certificado digital, porem na CAPICOM_TLB não tem essas classes (TStore, TCertificate e TCertificates) sendo assim, não consegui compilar o projeto, estou usando o Delphi XE2, tem algo que estou fazendo de errado?

Luís Gustavo Fabbro disse...

Diego

Provavelmente você não marcou a caixa "Generate Component Wrappers" quando pediu pra gerar o CAPICOM_TLB.

Você pode gerar de novo com essa caixa marcada ou usar as classes CoXXXXXXX geradas no fim da unit (Ex: CoStore) em conjunto com as interfaces (Ex: IStore3). O efeito é o mesmo de se usar o TStore.

[]s

drgarcia1986 disse...

Muito obrigado Luís, refiz os processo marcando a opção "Generate Component Wrappers" e funcionou perfeito, agora só preciso estudar um pouco as possibilidades da capicom. Abraços.

Talles disse...

estou com problema na parte cert.ConnectTo(IDispatch (ov) as ICertificate2);
ele nao retorna nada. Nao aparece erro nenhum e simplismente sai do evento. Se puder me ajudar agradeço.

Luís Gustavo Fabbro disse...

Talles

Experimente envolver seu código num bloco try / Except para tentar capturar a exceção que possivelmente está sendo levantada. Este tipo de comportamento normalmente ocorre quando alguma variável não está corretamente atribuída (alocada). O tratamento de exceção pode ajudar a descobrir a causa.

Att.

OHO Sistemas disse...

Como poderia capturar as informações de em certificado específico, sem passar pela seleção.

Luís Gustavo Fabbro disse...

Você pode carregar as informações a partir do arquivo do certicado com a função Load ou importar um texto representando o certificado codificado usando a função Import.

Ambas são expostas no objeto TCertificate.

[]s

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.