Confiar em todos os certificados com okHttp

111

Para fins de teste, estou tentando adicionar uma fábrica de soquetes ao meu cliente okHttp que confie em tudo enquanto um proxy é definido. Isso foi feito muitas vezes, mas minha implementação de uma fábrica de soquetes confiáveis ​​parece estar faltando alguma coisa:

class TrustEveryoneManager implements X509TrustManager {
    @Override
    public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { }

    @Override
    public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { }

    @Override
    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
        return null;
    }
}
OkHttpClient client = new OkHttpClient();

final InetAddress ipAddress = InetAddress.getByName("XX.XXX.XXX.XXX"); // some IP
client.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(ipAddress, 8888)));

SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManager[] trustManagers = new TrustManager[]{new TrustEveryoneManager()};
sslContext.init(null, trustManagers, null);
client.setSslSocketFactory(sslContext.getSocketFactory);

Nenhuma solicitação está sendo enviada para fora do meu aplicativo e nenhuma exceção está sendo registrada, então parece que ele está falhando silenciosamente em okHttp. Após uma investigação mais aprofundada, parece que há uma exceção sendo engolida em okHttp Connection.upgradeToTls()quando o aperto de mão está sendo forçado. A exceção que estou recebendo é:javax.net.ssl.SSLException: SSL handshake terminated: ssl=0x74b522b0: SSL_ERROR_ZERO_RETURN occurred. You should never see this.

O código a seguir produz um SSLContextque funciona perfeitamente na criação de um SSLSocketFactory que não lança nenhuma exceção:

protected SSLContext getTrustingSslContext() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
    final SSLContextBuilder trustingSSLContextBuilder = SSLContexts.custom()
            .loadTrustMaterial(null, new TrustStrategy() {
                @Override
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true; // Accepts any ssl cert whether valid or not.
                }
            });
    return trustingSSLContextBuilder.build();
}

O problema é que estou tentando remover todas as dependências do Apache HttpClient do meu aplicativo completamente. O código subjacente com Apache HttpClient para produzir o SSLContextparece bastante direto, mas obviamente estou faltando algo porque não posso configurar meu SSLContextpara corresponder a isso.

Alguém seria capaz de produzir uma implementação SSLContext que faz o que eu gostaria sem usar o Apache HttpClient?

Seato
fonte
2
"Para fins de teste, estou tentando adicionar uma fábrica de soquetes ao meu cliente okHttp que confie em tudo" - o que o faz pensar que esta é uma boa ideia em primeiro lugar?
CommonsWare
1
Ele só é chamado em uma rede em que confio inteiramente.
mar para
Isso causa umjava.net.SocketTimeoutException: Read timed out
IgorGanapolsky

Respostas:

249

Caso alguém caia aqui, a (única) solução que funcionou para mim é criar o OkHttpClientsemelhante explicado aqui .

Aqui está o código:

private static OkHttpClient getUnsafeOkHttpClient() {
  try {
    // Create a trust manager that does not validate certificate chains
    final TrustManager[] trustAllCerts = new TrustManager[] {
        new X509TrustManager() {
          @Override
          public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
          }

          @Override
          public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
          }

          @Override
          public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return new java.security.cert.X509Certificate[]{};
          }
        }
    };

    // Install the all-trusting trust manager
    final SSLContext sslContext = SSLContext.getInstance("SSL");
    sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
    // Create an ssl socket factory with our all-trusting manager
    final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    builder.sslSocketFactory(sslSocketFactory, (X509TrustManager)trustAllCerts[0]);
    builder.hostnameVerifier(new HostnameVerifier() {
      @Override
      public boolean verify(String hostname, SSLSession session) {
        return true;
      }
    });

    OkHttpClient okHttpClient = builder.build();
    return okHttpClient;
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}
sonxurxo
fonte
15
Por que SSLe não TLS?
IgorGanapolsky
1
Muito obrigado por isso! A documentação sobre retrofit (usa okhttp) está faltando, este tipo de amostra de código me economiza muito tempo novamente e novamente.
Warpzit
8
Observe que essa abordagem não funciona mais com as versões atuais do OkHttp. Com 3.1.1 parece completamente quebrado. De 3.1.2 em diante, X509TrustManager.getAcceptedIssuers()deve retornar um array vazio em vez de null. Para obter mais informações, consulte este commit (role para baixo e veja as notas em RealTrustRootIndex.java).
jbxbergdev
12
Eu tentei isso, mas ainda recebo uma Handshake failedexceção. Alguma sugestão?
Esteban de
1
exemplo de OkHttpClient: github.com/square/okhttp/blob/master/samples/guide/src/main/…
ilyamuromets
13

O método a seguir está obsoleto

sslSocketFactory(SSLSocketFactory sslSocketFactory)

Considere atualizá-lo para

sslSocketFactory(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager)
Jakub N
fonte
13

Atualize o OkHttp 3.0, a getAcceptedIssuers()função deve retornar uma matriz vazia em vez de null.

Ervin Zhang
fonte
2
@Override public X509Certificate [] getAcceptedIssuers () {return new X509Certificate [] {}; // StackOverflow}
Ervin Zhang
11

SSLSocketFactory não expõe seu X509TrustManager, que é um campo que OkHttp precisa para construir uma cadeia de certificados limpa. Em vez disso, esse método deve usar reflexão para extrair o gerenciador de confiança. Os aplicativos devem preferir chamar sslSocketFactory (SSLSocketFactory, X509TrustManager), o que evita tal reflexão.

Fonte: documentação OkHttp

OkHttpClient.Builder builder = new OkHttpClient.Builder();

builder.sslSocketFactory(sslContext.getSocketFactory(),
    new X509TrustManager() {
        @Override
        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return new java.security.cert.X509Certificate[]{};
        }
    });
Tharindu Bandara
fonte
Isso não funciona para certificados autoassinados. Causa um erro 401 não autorizado .
IgorGanapolsky
5
De onde sslContextvem isso?
Anonsage
3
Como você inicializa sslContext?
kiltek
9

Esta é a solução da sonxurxo em Kotlin, se alguém precisar.

private fun getUnsafeOkHttpClient(): OkHttpClient {
    // Create a trust manager that does not validate certificate chains
    val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
        override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
        }

        override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
        }

        override fun getAcceptedIssuers() = arrayOf<X509Certificate>()
    })

    // Install the all-trusting trust manager
    val sslContext = SSLContext.getInstance("SSL")
    sslContext.init(null, trustAllCerts, java.security.SecureRandom())
    // Create an ssl socket factory with our all-trusting manager
    val sslSocketFactory = sslContext.socketFactory

    return OkHttpClient.Builder()
        .sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
        .hostnameVerifier { _, _ -> true }.build()
}
doodla
fonte
7

Fiz uma função de extensão para Kotlin. Cole-o onde quiser e importe-o enquanto cria OkHttpClient.

fun OkHttpClient.Builder.ignoreAllSSLErrors(): OkHttpClient.Builder {
    val naiveTrustManager = object : X509TrustManager {
        override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
        override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) = Unit
        override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) = Unit
    }

    val insecureSocketFactory = SSLContext.getInstance("TLSv1.2").apply {
        val trustAllCerts = arrayOf<TrustManager>(naiveTrustManager)
        init(null, trustAllCerts, SecureRandom())
    }.socketFactory

    sslSocketFactory(insecureSocketFactory, naiveTrustManager)
    hostnameVerifier(HostnameVerifier { _, _ -> true })
    return this
}

use-o assim:

val okHttpClient = OkHttpClient.Builder().apply {
    // ...
    if (BuildConfig.DEBUG) //if it is a debug build ignore ssl errors
        ignoreAllSSLErrors()
    //...
}.build()
George Shalvashvili
fonte
Na verdade, você pode usá-lo assim: OkHttpClient.Builder () .ignoreAllSSLErrors () .connectTimeout (api.timeout, TimeUnit.SECONDS) .writeTimeout (api.timeout, TimeUnit.SECONDS) etc., é um padrão de construtor afinal
Emanuel Moecklin
1

Esta é a solução Scala se alguém precisar

def anUnsafeOkHttpClient(): OkHttpClient = {
val manager: TrustManager =
  new X509TrustManager() {
    override def checkClientTrusted(x509Certificates: Array[X509Certificate], s: String) = {}

    override def checkServerTrusted(x509Certificates: Array[X509Certificate], s: String) = {}

    override def getAcceptedIssuers = Seq.empty[X509Certificate].toArray
  }
val trustAllCertificates =  Seq(manager).toArray

val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCertificates, new java.security.SecureRandom())
val sslSocketFactory = sslContext.getSocketFactory()
val okBuilder = new OkHttpClient.Builder()
okBuilder.sslSocketFactory(sslSocketFactory, trustAllCertificates(0).asInstanceOf[X509TrustManager])
okBuilder.hostnameVerifier(new NoopHostnameVerifier)
okBuilder.build()

}

netta
fonte
-12

Você nunca deve procurar substituir a validação do certificado no código! Se você precisar fazer testes, use uma CA interna / de teste e instale o certificado raiz da CA no dispositivo ou emulador. Você pode usar BurpSuite ou Charles Proxy se não souber como configurar uma CA.

Tony Trummer
fonte
37
Oh vamos lá. E se você estiver trabalhando para uma empresa e eles o forçarem a se conectar ao servidor HTTPS via VPN. Como você vai simular isso em um teste?
IgorGanapolsky
5
Sim, esta é a mais pura lavagem de porco. Nós apenas compilamos isso em builds de teste, então não há perigo.
Adam