Na empresa onde trabalho utilizamos o httpclient para realizar algumas operações de monitoramento em páginas web.
Nos casos onde a aplicação responde via https, precisamos importar (utilizando o Keytool) o certificado (arquivo .cer obtido clicando no cadeado do browser) ao KeyStore (arquivo cacerts), para que o httpclient consiga realizar as requisições.
Na maioria do certificados funciona tudo bem. Mas tem alguns certificados que acontece de não conseguirmos importar, pois ocorre uma exceção. Vejam abaixo:
keytool error: java.security.SignatureException: Signature does not match.
[/quote]
Só lembro de ter visto este erro quando o certificado estava “corrompido” (a CA mandou um certificado errado para o cliente e a gente ficou batendo cabeça para descobrir que o problema era do certificado).
Pelo o que eu entendi que você escreveu, não é o seu caso. Você está importando um certificado de um site externo (não é um certificado gerado para o seu site). Tente verficar se o certificado está OK (um double-click em um arquivo .cer no Windows abre uma aplicação que mostra detalhes do certificado).
Primeiramente obrigado pela resposta.
É isso mesmo. Estou importando o certificado de um site externo.
Bom... Abrindo o arquivo .cer, o que o windows fala sobre o certificado é apenas que não pode garantir a integridade do mesmo. Mas ele fala isso para os outros certificados também, mesmo os que consegui importar com o Keytool.
Obrigado pela resposta.
Mas acredito que não seja isto. O arquivo .cer foi gerado em formato binário mesmo. Selecionando a opção "DER encoded binary X.509 (.CER)" no Internet Explorer. É esta a opção de exportação que você se referiu, correto?
Criei uma classe de teste. Ela carrega o certificado (arquivo .cer) e utiliza o método java.security.cert.Certificate.verify(PublicKey arg0) (que verifica se a assinatura foi feita utilizando a chave privada que corresponde à chave pública passada como parâmetro).
CertificadoSSLLoader
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import javax.security.cert.CertificateException;
import javax.security.cert.X509Certificate;
public class CertificadoSSLLoader {
public static X509Certificate load(String cerFileName) throws CertificateException {
File file = new File(cerFileName);
FileInputStream fis;
try {
fis = new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new IllegalArgumentException("Arquivo " + cerFileName + " não existe.");
}
X509Certificate certificado = X509Certificate.getInstance(fis);
return certificado;
}
}
A saída deste programa, como esperado, é a mesma do keytool:
Assinatura inválida.
java.security.SignatureException: Signature does not match.
at sun.security.x509.X509CertImpl.verify(Unknown Source)
at sun.security.x509.X509CertImpl.verify(Unknown Source)
at com.sun.security.cert.internal.x509.X509V1CertImpl.verify(Unknown Source)
at TestaValidadeCertificadoSSL.main(TestaValidadeCertificadoSSL.java:16)
O que mais me chamou atenção até aqui foi o comentário javadoc do método.
abstract void verify(PublicKey key)
Verifies that this certificate was signed using the private key that corresponds to the specified public key.
Isto me leva a crer que que há grandes chances de ser algum problema ocorrido no momento da geração do certificado no servidor do site.
Clareou para alguém?
Pelo teste que você fez o certificado que você está tentando importar está “inválido”. Isto pode acontecer se o certificado diz que está assinado por um “certificado root” que você tem no seu cacerts e isto não é verdade (assinatura não bate). Ou o “certificado root” que você tem no seu cacerts não é mais válido ou é “fake” ou o certificado que voce está tentando importar é “fake”. Tente ver a cadeia de certificados que assina o certificado que voce está tentando importar e veja se não tem algo no seu cacerts que invalide-o.
Thingol
Só para te dar um toque: antigamente eu também já tive problemas de importar certificados que estavam em formato binário (só aceitava base-64). mas fiz uns testes agora e realmente ele aceita certificados em formato binário. Não tenho uma JDK 1.3 aqui na minha máquina para fazer o teste, mas eu lembro pelo menos que em JDK 1.3.1 não dava para fazer o import de formato binário. Com JDK 1.4.2 eu consegui aqui.
Legal, não sabia disso. (É que muitas das ferramentas que uso precisam especificar explicitamente o formato; não são espertas o suficiente para fazer a auto-detecção. Seria relativamente simples, já que o primeiro byte é sempre 0x30 (‘0’) que quer dizer SEQUENCE em ASN.1 codificação DER ou BER, se o formato for binário. )
Me diz uma coisa... Como identifico a cadeia de certificados que assina o certificado que estou está tentando importar?
É o CN (Common Name, Emitido Por)?
[quote=pierobom]Me diz uma coisa… Como identifico a cadeia de certificados que assina o certificado que estou está tentando importar?
É o CN (Common Name, Emitido Por)?
[/quote]
Para identificar quem assinou o seu certificado, verifique o Issuer (acho que em portugues é o “emitido por”).
O método getIssuerDN() da classe X509Certificate retorna o DN (Distinguished Name) do Issuer (“assinador”) do certificado. Procure um certificado no seu cacerts que tenha o mesmo DN em Subject. DN é todo o campo, e não só o campo CN.
Obrigado pelas respostas.
Não. Não existre outro certificado com o mesmo DN no cacerts. Inclusive eu tentei importar o certificado em outra máquina (com um cacerts vazio, sem certificados) e ocorreu o mesmo problema.
Acho mais provável a sua outra sugestão, a de que o certificado que estou tentando importar é "fake". Agora, como posso me certificar disso?
Quando voce diz “cacerts vaziou”, voce “zerou” o arquivo $JAVA_HOME/jre/lib/security/cacerts? Pois é com base nos “certificados root” deste arquivo que a JVM verifica a assinatura de um certificado.
Com qual JDK/JRE voce está fazendo o import do certificado?
Está dando a mensagem “Trust this certificate? [no]” quando voce está fazendo o import?
Já devia ter pedido isto antes, mas posta o arquivo com o certificado para podemos dar uma olhada.
Acho que me expressei mal. Não é um cacerts vazio. O que eu pretendi dizer é que testei também com um cacerts de um jdk recém-instalado, ou seja, um cacerts para o qual não foi importado nenhum certificado ainda.
E o keytool não chega a fazer a pergunta "Trust this certificate? [no]". Ao executar o comando keytool - import já ocorre a exceção.
Dá uma olhada no certificado (arquivo .cer). Segue anexo.
[quote=pierobom]Acho que me expressei mal. Não é um cacerts vazio. O que eu pretendi dizer é que testei também com um cacerts de um jdk recém-instalado, ou seja, um cacerts para o qual não foi importado nenhum certificado ainda.
E o keytool não chega a fazer a pergunta "Trust this certificate? [no]". Ao executar o comando keytool - import já ocorre a exceção.
Dá uma olhada no certificado (arquivo .cer). Segue anexo.[/quote]
Jean
Olhei o arquivo. Realmente, o “meliante” que gerou este certificado fez alguma besteira. Ele é um certificado “auto-assinado” (não foi assinado por uma CA), ou seja, não dá para saber se é confiável. Não sei porque os browsers (testei no IE e no Firefox) não detectam o problema da assinatura do certificado (eu desconfio que por ser “auto-assinado” , os browsers nem verificam a assinatura).
O jeito seria pedir para o responsável pelo site gerar um certificado correto (será que o governo do Paraná não tem convenho com o Serpro para gerar certificados “autenticos”?).
Voltando ao seu problema original: você importava o certificado dos sites para poder acessá-los via httpclient. Se você não importar o certificado provavelmente dá algum erro. Voce poderia tentar gerar um cacerts (que nada mais é que uma keystore só com certificados) criando um programa que não validade a assinatura no import. Faça um programa que leia um certificado e gere uma keystore. Vai ser uma gambiarra…