Pergunta

Um módulo eu estou adicionando ao nosso grande aplicação Java tem que conversar com o site protegido por SSL de outra empresa. O problema é que o site usa um certificado auto-assinado. Eu tenho uma cópia do certificado para verificar que eu não estou encontrando um ataque man-in-the-middle, e eu preciso para incorporar este certificado em nosso código de tal forma que a conexão com o servidor será bem sucedido.

Aqui está o código básico:

void sendRequest(String dataPacket) {
  String urlStr = "https://host.example.com/";
  URL url = new URL(urlStr);
  HttpURLConnection conn = (HttpURLConnection)url.openConnection();
  conn.setMethod("POST");
  conn.setRequestProperty("Content-Length", data.length());
  conn.setDoOutput(true);
  OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
  o.write(data);
  o.flush();
}

Sem qualquer manipulação adicional no local para o certificado auto-assinado, este morre aos conn.getOutputStream () com a seguinte exceção:

Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

O ideal é que as minhas necessidades de código para ensinar Java para aceitar este certificado auto-assinado, para este primeiro lugar no aplicativo, e em nenhum outro lugar.

Eu sei que eu posso importar o certificado para arquivo de autoridade de certificação do JRE, e que irá permitir Java para aceitá-lo. Isso não é uma abordagem que eu quero levar se eu puder ajudar; parece muito invasivo para fazer em todas as máquinas de nossos clientes para um módulo que não pode usar; isso afetaria todos os outros aplicativos Java usando o mesmo JRE, e eu não gosto disso, embora as chances de qualquer outra aplicação Java que nunca aceder a este site são nulas. Também não é uma operação trivial:. No UNIX eu tenho para obter os direitos de acesso para modificar o JRE desta maneira

Eu também vi que eu posso criar uma instância TrustManager que faz alguma verificação do costume. Parece que eu poderia até ser capaz de criar um TrustManager que delega para o TrustManager verdadeira em todos os casos, exceto um certificado. Mas parece que TrustManager é instalado globalmente, e presumo que afetaria todas as outras ligações do nosso aplicativo, e que não cheira muito bem para mim, também.

O que é o preferido, padrão ou melhor maneira de configurar um aplicativo Java para aceitar um certificado auto-assinado? Eu posso realizar todas as metas que eu tenho em mente acima, ou eu vou ter que compromisso? Existe uma opção envolvendo arquivos e diretórios e definições de configuração, e pouco ou nenhum código?

Foi útil?

Solução

Criar uma fábrica SSLSocket si mesmo, e colocou-a no HttpsURLConnection antes de ligar.

...
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslFactory);
conn.setMethod("POST");
...

Você vai querer criar um SSLSocketFactory e mantê-lo ao redor. Aqui está um esboço de como inicializar-lo:

/* Load the keyStore that includes self-signed cert as a "trusted" entry. */
KeyStore keyStore = ... 
TrustManagerFactory tmf = 
  TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
sslFactory = ctx.getSocketFactory();

Se você precisar de ajuda para criar o armazenamento de chaves, por favor comentário.


Aqui está um exemplo de carregar o armazenamento de chaves:

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();

Para criar o armazenamento de chaves com um certificado no formato PEM, você pode escrever seu próprio código usando CertificateFactory, ou apenas importá-lo com keytool do JDK (keytool não trabalho para uma "entrada de chave ", mas é apenas bom para uma 'entrada de confiança').

keytool -import -file selfsigned.pem -alias server -keystore server.jks

Outras dicas

Eu li através de lotes de lugares on-line para resolver esta coisa. Este é o código que eu escrevi para torná-lo trabalho:

                ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes());
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream);
                String alias = "alias";//cert.getSubjectX500Principal().getName();

                KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
                trustStore.load(null);
                trustStore.setCertificateEntry(alias, cert);
                KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
                kmf.init(trustStore, null);
                KeyManager[] keyManagers = kmf.getKeyManagers();

                TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
                tmf.init(trustStore);
                TrustManager[] trustManagers = tmf.getTrustManagers();

                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(keyManagers, trustManagers, null);
                URL url = new URL(someURL);
                conn = (HttpsURLConnection) url.openConnection();
                conn.setSSLSocketFactory(sslContext.getSocketFactory());

app.certificateString é uma String que contém o certificado, por exemplo:

            static public String certificateString=
            "-----BEGIN CERTIFICATE-----\n" +
            "MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" +
            "BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" +
            ... a bunch of characters...
            "5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" +
            "-----END CERTIFICATE-----";

Eu testei que você pode colocar quaisquer caracteres na cadeia de certificados, se for auto-assinado, desde que você mantenha a estrutura exata acima. Obtive a string certificado com linha de comando Terminal do meu laptop.

Se a criação de um SSLSocketFactory não é uma opção, basta importar a chave na JVM

  1. Recuperar a chave pública: $openssl s_client -connect dev-server:443, em seguida, criar um arquivo dev-server.pem que se parece com

    -----BEGIN CERTIFICATE----- 
    lklkkkllklklklklllkllklkl
    lklkkkllklklklklllkllklkl
    lklkkkllklk....
    -----END CERTIFICATE-----
    
  2. Importar a chave: #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem. Password: changeit

  3. Restart JVM

Fonte: Como resolver javax.net.ssl. SSLHandshakeException?

Nós copiar truststore do JRE e adicionar nossos certificados personalizados para que truststore, em seguida, dizer o aplicativo para usar o truststore personalizado com uma propriedade do sistema. Desta forma deixamos o truststore JRE padrão sozinho.

A desvantagem é que quando você atualizar o JRE você não fizer a sua nova truststore automaticamente fundiu-se com o seu um personalizado.

Você talvez pudesse lidar com este cenário por ter uma rotina de instalação ou de inicialização que verifica a truststore / jdk e verifica se há uma incompatibilidade ou automaticamente atualiza o truststore. Eu não sei o que acontece se você atualizar o truststore enquanto a aplicação está em execução.

Esta solução não é 100% elegante ou infalível, mas é simples, obras, e não requer nenhum código.

Eu tive que fazer algo assim quando se usa commons-httpclient para acessar um servidor https interno com um certificado auto-assinado. Sim, a nossa solução foi criar um TrustManager personalizado que tudo simplesmente passado (registrando uma mensagem de depuração).

Isso se resume a ter a nossa própria SSLSocketFactory que cria soquetes SSL da nossa SSLContext local, que é configurado para ter apenas a nossa TrustManager locais associada a ele. Você não precisa ir perto de um armazenamento de chaves / certstore em tudo.

Portanto, esta é na nossa LocalSSLSocketFactory:

static {
    try {
        SSL_CONTEXT = SSLContext.getInstance("SSL");
        SSL_CONTEXT.init(null, new TrustManager[] { new LocalSSLTrustManager() }, null);
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Unable to initialise SSL context", e);
    } catch (KeyManagementException e) {
        throw new RuntimeException("Unable to initialise SSL context", e);
    }
}

public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
    LOG.trace("createSocket(host => {}, port => {})", new Object[] { host, new Integer(port) });

    return SSL_CONTEXT.getSocketFactory().createSocket(host, port);
}

Juntamente com outros métodos de aplicação SecureProtocolSocketFactory. LocalSSLTrustManager é a implementação manequim gerenciador de confiança acima mencionado.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top