webentwicklung-frage-antwort-db.com.de

Unterstützt OkHttp die Annahme selbst signierter SSL-Zertifikate?

Ich arbeite für einen Kunden, der einen Server mit selbst signiertem SSL-Zertifikat besitzt.

Ich verwende Retrofit + CustomClient mit einem umhüllten OkHttp-Client:

RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint(Config.BASE_URL + Config.API_VERSION)
    .setClient(new CustomClient(new OkClient(), context))
    .build();

Unterstützt OkHttp standardmäßig das Aufrufen eines selbstsignierten SSL-Zertifizierungsservers?

Apropos. Welcher Client verwendet standardmäßig Retrofit? Ich dachte, es sei OkHttp, aber als ich ein wenig mehr recherchierte, wurde mir klar, dass ich OkHttp-Abhängigkeiten importieren musste

57
cesards

Ja tut es.

Mit Retrofit können Sie Ihren benutzerdefinierten HTTP-Client einstellen, der Ihren Anforderungen entspricht.

Für selbst signierte SSL-Zertifikate gibt es eine Diskussion hier . Der Link enthält Codebeispiele, um der DefaultHttpClient von Android eine selbstsignierte SLL hinzuzufügen und diesen Client in Retrofit zu laden.

Wenn Sie OkHttpClient benötigen, um selbst signiertes SSL zu akzeptieren, müssen Sie die benutzerdefinierte javax.net.ssl.SSLSocketFactory-Instanz über die setSslSocketFactory(SSLSocketFactory sslSocketFactory)-Methode übergeben.

Die einfachste Methode, eine Socket-Factory zu erhalten, besteht darin, eine von javax.net.ssl.SSLContext zu erhalten, wie hier hier beschrieben.

Hier ein Beispiel zum Konfigurieren von OkHttpClient:

OkHttpClient client = new OkHttpClient();
KeyStore keyStore = readKeyStore(); //your method to obtain KeyStore
SSLContext sslContext = SSLContext.getInstance("SSL");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "keystore_pass".toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(),trustManagerFactory.getTrustManagers(), new SecureRandom());
client.setSslSocketFactory(sslContext.getSocketFactory());

Aktualisierter Code für okhttp3 (using Builder):

    OkHttpClient client = new OkHttpClient.Builder()
            .sslSocketFactory(sslContext.getSocketFactory())
            .build();

die client hier ist jetzt so konfiguriert, dass Zertifikate von Ihrer KeyStore verwendet werden. Es vertraut jedoch nur den Zertifikaten in Ihrer KeyStore und auch sonst nichts, auch wenn Ihr System ihnen standardmäßig vertraut. (Wenn Sie nur selbst signierte Zertifikate in Ihrer KeyStore haben und versuchen, über HTTPS eine Verbindung zur Google-Hauptseite herzustellen, erhalten Sie SSLHandshakeException).

Sie können die KeyStore-Instanz aus der Datei erhalten, wie in docs : .__ angezeigt.

KeyStore readKeyStore() {
    KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());

    // get user password and file input stream
    char[] password = getPassword();

    Java.io.FileInputStream fis = null;
    try {
        fis = new Java.io.FileInputStream("keyStoreName");
        ks.load(fis, password);
    } finally {
        if (fis != null) {
            fis.close();
        }
    }
    return ks;
}

Wenn Sie mit Android arbeiten, können Sie es im res/raw-Ordner ablegen und mit .__ von einer Context-Instanz herunterladen.

fis = context.getResources().openRawResource(R.raw.your_keystore_filename);

Es gibt mehrere Diskussionen, wie Sie Ihren Schlüsselspeicher erstellen. Zum Beispiel hier

73
Andrey Makarov

Für die Version okhttp3.OkHttpClient com.squareup.okhttp3: okhttp: 3.2.0 müssen Sie den folgenden Code verwenden:

import okhttp3.Call;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;

......

OkHttpClient.Builder clientBuilder = client.newBuilder().readTimeout(LOGIN_TIMEOUT_SEC, TimeUnit.SECONDS);

            boolean allowUntrusted = true;

            if (  allowUntrusted) {
                Log.w(TAG,"**** Allow untrusted SSL connection ****");
                final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        X509Certificate[] cArrr = new X509Certificate[0];
                        return cArrr;
                    }

                    @Override
                    public void checkServerTrusted(final X509Certificate[] chain,
                                                   final String authType) throws CertificateException {
                    }

                    @Override
                    public void checkClientTrusted(final X509Certificate[] chain,
                                                   final String authType) throws CertificateException {
                    }
                }};

                SSLContext sslContext = SSLContext.getInstance("SSL");

                sslContext.init(null, trustAllCerts, new Java.security.SecureRandom());
                clientBuilder.sslSocketFactory(sslContext.getSocketFactory());

                HostnameVerifier hostnameVerifier = new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        Log.d(TAG, "Trust Host :" + hostname);
                        return true;
                    }
                };
                clientBuilder.hostnameVerifier( hostnameVerifier);
            }

            final Call call = clientBuilder.build().newCall(request);
12
Gugelhupf

Zwei Methoden aus unserer App zum Abrufen einer OkHttpClient 3.0 -Instanz, die Ihre selbstsignierten Zertifikate von Ihrem Keystore erkennt (verwendet die vorbereitete pkcs12-Zertifikatdatei in Ihrem Android-Projekt "raw" resources-Ordner):

private static OkHttpClient getSSLClient(Context context) throws
                              NoSuchAlgorithmException,
                              KeyStoreException,
                              KeyManagementException,
                              CertificateException,
                              IOException {

  OkHttpClient client;
  SSLContext sslContext;
  SSLSocketFactory sslSocketFactory;
  TrustManager[] trustManagers;
  TrustManagerFactory trustManagerFactory;
  X509TrustManager trustManager;

  trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
  trustManagerFactory.init(readKeyStore(context));
  trustManagers = trustManagerFactory.getTrustManagers();

  if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
    throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
  }

  trustManager = (X509TrustManager) trustManagers[0];

  sslContext = SSLContext.getInstance("TLS");

  sslContext.init(null, new TrustManager[]{trustManager}, null);

  sslSocketFactory = sslContext.getSocketFactory();

  client = new OkHttpClient.Builder()
      .sslSocketFactory(sslSocketFactory, trustManager)
      .build();
  return client;
}

/**
 * Get keys store. Key file should be encrypted with pkcs12 standard. It    can be done with standalone encrypting Java applications like "keytool". File password is also required.
 *
 * @param context Activity or some other context.
 * @return Keys store.
 * @throws KeyStoreException
 * @throws CertificateException
 * @throws NoSuchAlgorithmException
 * @throws IOException
*/
private static KeyStore readKeyStore(Context context) throws
                          KeyStoreException,
                          CertificateException,
                          NoSuchAlgorithmException,
                          IOException {
  KeyStore keyStore;
  char[] PASSWORD = "12345678".toCharArray();
  ArrayList<InputStream> certificates;
  int certificateIndex;
  InputStream certificate;

  certificates = new ArrayList<>();
  certificates.add(context.getResources().openRawResource(R.raw.ssl_pkcs12));

keyStore = KeyStore.getInstance("pkcs12");

for (Certificate certificate : certificates) {
    try {
      keyStore.load(certificate, PASSWORD);
    } finally {
      if (certificate != null) {
        certificate.close();
      }
    }
  }
  return keyStore;
}
4
Zon

Gegen Retrofit 1.9 konnte ich jedes Zertifikat mit der folgenden Strategie akzeptieren: Verwendung auf eigenes Risiko! Das Akzeptieren eines Zertifikats ist gefährlich und Sie sollten die Konsequenzen verstehen. Einige relevante Teile stammen aus org.Apache.http.ssl, daher müssen Sie hier möglicherweise importieren.

// ...

    Client httpClient = getHttpClient();

    RestAdapter adapter = new RestAdapter.Builder()
        .setClient(httpClient)
        // ... the rest of your builder setup
        .build();

// ...

private Client getHttpClient() {
    try {
        // Allow self-signed (and actually any) SSL certificate to be trusted in this context
        TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;

        SSLContext sslContext = org.Apache.http.ssl.SSLContexts.custom()
            .loadTrustMaterial(null, acceptingTrustStrategy)
            .build();

        sslContext.getSocketFactory();

        SSLSocketFactory sf = sslContext.getSocketFactory();

        OkHttpClient client = new OkHttpClient();
        client.setSslSocketFactory(sf);

        return new OkClient(client);
    } catch (Exception e) {
        throw new RuntimeException("Failed to create new HTTP client", e);
    }
}
0
jocull

Ich weiß, dass dieser Beitrag ziemlich alt ist, aber ich möchte die Lösung teilen, die für mich mit dem neuesten Update von OkHttp, der 3.12.1-Version in der Zeit, in der ich schreibe, funktionierte.

Zunächst müssen Sie das KeyStore-Objekt abrufen, das dann zum TrustManager hinzugefügt wird:

/**
 *  @param context The Android context to be used for retrieving the keystore from raw resource
 * @return the KeyStore read or null on error
 */
private static KeyStore readKeyStore(Context context) {

    char[] password = "keystore_password".toCharArray();

    // for non-Android usage:
    // try(FileInputStream is = new FileInputStream(keystoreName)) {

    try(InputStream is = context.getResources().openRawResource(R.raw.keystore)) {
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(is, password);
        return ks;
    } catch (CertificateException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    }

    return null;
}

Jetzt können Sie die erstellte OkHttpClient mit dem selbstsignierten Zertifikat in Ihrem Schlüsselspeicher abrufen:

/**
 * @param context The Android context used to obtain the KeyStore
 * @return the builded OkHttpClient or null on error
 */
public static OkHttpClient getOkHttpClient(Context context) {

    try {
        TrustManagerFactory trustManagerFactory = TrustManagerFactory
                .getInstance(TrustManagerFactory.getDefaultAlgorithm());

        trustManagerFactory.init(readKeyStore(context));

        X509TrustManager trustManager = (X509TrustManager) trustManagerFactory.getTrustManagers()[0];
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, new TrustManager[]{trustManager}, null);

        return new OkHttpClient.Builder()
                .hostnameVerifier((hostname, session) -> {
                    HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
                    /* Never return true without verifying the hostname, otherwise you will be vulnerable
                    to man in the middle attacks. */
                    return  hv.verify("your_hostname_here", session);
                })
                .sslSocketFactory(sslContext.getSocketFactory(), trustManager)
                .build();

    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    }

    return null;
}

Denken Sie daran, dass es wird dringend davon abgeraten in der hostnameVerifier immer true zurückzukehren, um das Risiko eines Mannes in den mittleren Angriffen zu vermeiden.

0
Domenico

Ich hatte das gleiche Problem und habe es mit dem okhttp client wie folgt behoben:

1.) Fügen Sie die Datei certificate zu src/main/res/raw/ hinzu, die den folgenden Inhalt enthält:

-----BEGIN CERTIFICATE-----
...=
-----END CERTIFICATE-----

2.) Instanziiere den okHttpClient:

OkHttpClient client = new OkHttpClient.Builder()
                .sslSocketFactory(getSslContext(context).getSocketFactory())
                .build();

3.) Hier ist die verwendete getSslContext(Context context) Methode:

SSLContext getSslContext(Context context) throws Exception {
    KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); // "BKS"
    ks.load(null, null);

    InputStream is = context.getResources().openRawResource(R.raw.certificate);
    String certificate = Converter.convertStreamToString(is);

    // generate input stream for certificate factory
    InputStream stream = IOUtils.toInputStream(certificate);

    // CertificateFactory
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    // certificate
    Certificate ca;
    try {
        ca = cf.generateCertificate(stream);
    } finally {
        is.close();
    }

    ks.setCertificateEntry("av-ca", ca);

    // TrustManagerFactory
    String algorithm = TrustManagerFactory.getDefaultAlgorithm();
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
    // Create a TrustManager that trusts the CAs in our KeyStore
    tmf.init(ks);

    // Create a SSLContext with the certificate that uses tmf (TrustManager)
    sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());

    return sslContext;
}

Wenn dem SslContext mehrere Zertifikate hinzugefügt werden müssen, ist hier die Lösung.