Ufanie wszystkim certyfikatom za pomocą okHttp

111

Do celów testowych próbuję dodać fabrykę gniazd do mojego klienta okHttp, który ufa wszystkim, gdy jest ustawiony serwer proxy. Robiono to wiele razy, ale wydaje mi się, że w mojej implementacji ufającej fabryki gniazd brakuje czegoś:

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);

Żadne żądania nie są wysyłane z mojej aplikacji i żadne wyjątki nie są rejestrowane, więc wygląda na to, że po cichu zawodzi w okHttp. Po dalszych badaniach wydaje się, że wyjątek jest pochłaniany w okHttp, Connection.upgradeToTls()gdy wymuszany jest uścisk dłoni. Wyjątek, który mi podano, to:javax.net.ssl.SSLException: SSL handshake terminated: ssl=0x74b522b0: SSL_ERROR_ZERO_RETURN occurred. You should never see this.

Poniższy kod tworzy kod, SSLContextktóry działa jak urok w tworzeniu SSLSocketFactory, który nie zgłasza żadnych wyjątków:

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();
}

Problem polega na tym, że próbuję całkowicie usunąć wszystkie zależności Apache HttpClient z mojej aplikacji. Podstawowy kod z Apache HttpClient do tworzenia SSLContextwydaje się wystarczająco prosty, ale oczywiście brakuje mi czegoś, ponieważ nie mogę skonfigurować mojego, SSLContextaby pasował do tego.

Czy ktoś byłby w stanie stworzyć implementację SSLContext, która robi to, co chciałbym, bez używania Apache HttpClient?

seato
źródło
2
„Do celów testowych próbuję dodać fabrykę gniazd do mojego klienta okHttp, który ufa wszystkim” - co sprawia, że ​​myślisz, że to dobry pomysł?
CommonsWare
1
Jest wywoływany tylko w sieci, której całkowicie ufam.
seato
To powodujejava.net.SocketTimeoutException: Read timed out
Igor Ganapolsky

Odpowiedzi:

250

Na wypadek gdyby ktoś tu wpadł, (jedynym) rozwiązaniem, które zadziałało dla mnie, jest stworzenie OkHttpClientpodobnego wyjaśnionego tutaj .

Oto kod:

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
źródło
15
Dlaczego SSLa nie TLS?
IgorGanapolsky
1
Wielkie dzięki za to! Brakuje dokumentacji dotyczącej modernizacji (używa okhttp), tego rodzaju próbki kodu oszczędzają mi wiele razy.
Warpzit,
8
Zwróć uwagę, że to podejście nie działa już z bieżącymi wersjami OkHttp. W wersji 3.1.1 wydaje się całkowicie zepsuty. Począwszy od 3.1.2, X509TrustManager.getAcceptedIssuers()zamiast wartości musi zwracać pustą tablicę null. Aby uzyskać więcej informacji, zobacz to zatwierdzenie (przewiń w dół i zobacz uwagi w sekcji RealTrustRootIndex.java).
jbxbergdev
12
Próbowałem tego, ale nadal mam Handshake failedwyjątek. Jakieś sugestie?
Esteban
1
przykład z OkHttpClient: github.com/square/okhttp/blob/master/samples/guide/src/main/…
ilyamuromets
13

Następująca metoda jest przestarzała

sslSocketFactory(SSLSocketFactory sslSocketFactory)

Rozważ aktualizację do

sslSocketFactory(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager)
Jakub N
źródło
13

Zaktualizuj OkHttp 3.0, getAcceptedIssuers()funkcja musi zwracać pustą tablicę zamiast null.

Ervin Zhang
źródło
2
@Override public X509Certificate [] getAcceptedIssuers () {return new X509Certificate [] {}; // StackOverflow}
Ervin Zhang
11

SSLSocketFactory nie ujawnia swojego X509TrustManager, które jest polem potrzebnym OkHttp do zbudowania czystego łańcucha certyfikatów. Ta metoda zamiast tego musi używać odbicia w celu wyodrębnienia menedżera zaufania. Aplikacje powinny preferować wywoływanie sslSocketFactory (SSLSocketFactory, X509TrustManager), co pozwala uniknąć takiej refleksji.

Źródło: dokumentacja 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
źródło
To nie działa w przypadku certyfikatów z podpisem własnym. Powoduje błąd 401 nieautoryzowany .
IgorGanapolsky
5
Skąd się sslContextbierze?
Anonsage
3
Jak inicjalizujesz sslContext?
kiltek
10

To rozwiązanie sonxurxo w Kotlinie, jeśli ktoś tego potrzebuje.

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
źródło
7

Zrobiłem funkcję rozszerzenia dla Kotlina. Wklej go w dowolnym miejscu i zaimportuj podczas tworzenia 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
}

użyj tego w ten sposób:

val okHttpClient = OkHttpClient.Builder().apply {
    // ...
    if (BuildConfig.DEBUG) //if it is a debug build ignore ssl errors
        ignoreAllSSLErrors()
    //...
}.build()
George Shalvashvili
źródło
W rzeczywistości możesz go użyć w ten sposób: OkHttpClient.Builder () .ignoreAllSSLErrors () .connectTimeout (api.timeout, TimeUnit.SECONDS) .writeTimeout (api.timeout, TimeUnit.SECONDS) itd., To w końcu wzorzec konstruktora
Emanuel Moecklin
1

To jest rozwiązanie Scala, jeśli ktoś tego potrzebuje

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
źródło
-12

Nigdy nie powinieneś starać się zastąpić walidacji certyfikatu w kodzie! Jeśli musisz przeprowadzić testy, użyj wewnętrznego / testowego CA i zainstaluj certyfikat główny CA na urządzeniu lub emulatorze. Jeśli nie wiesz, jak skonfigurować urząd certyfikacji, możesz użyć BurpSuite lub Charles Proxy.

Tony Trummer
źródło
37
Och przestań. A co, jeśli pracujesz dla firmy i zmuszają Cię do łączenia się z serwerem HTTPS przez VPN. Jak zamierzasz to zasymulować w teście?
IgorGanapolsky
5
Tak, to najczystsze mycie świń. Kompilujemy to tylko w wersjach testowych, więc nie ma niebezpieczeństwa.
Adam