web-dev-qa-db-fra.com

OkHttp prend-il en charge l’acceptation des certificats SSL auto-signés?

Je travaille pour un client qui a un serveur avec un certificat SSL auto-signé.

J'utilise Retrofit + CustomClient à l'aide du client OkHttp intégré:

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

OkHttp prend-il en charge l’appel par défaut du serveur de cert SSL auto-signé?

Au fait. Quel client utilise Retrofit par défaut? Je pensais que c'était OkHttp mais quand j'ai cherché un peu plus, j'ai réalisé que je devais importer des dépendances OkHttp

57
cesards

Oui.

Retrofit vous permet de définir votre client HTTP personnalisé, configuré selon vos besoins.

En ce qui concerne les certificats SSL auto-signés, il y a une discussion ici . Le lien contient des exemples de code permettant d’ajouter une SLL auto-signée à la DefaultHttpClient d’Android et de charger ce client dans Retrofit.

Si vous avez besoin de OkHttpClient pour accepter le protocole SSL auto-signé, vous devez lui transmettre l'instance javax.net.ssl.SSLSocketFactory personnalisée via la méthode setSslSocketFactory(SSLSocketFactory sslSocketFactory).

La méthode la plus simple pour obtenir une fabrique de sockets consiste à en obtenir un à partir de javax.net.ssl.SSLContext, comme indiqué ici .

Voici un exemple pour configurer 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());

Code mis à jour pour okhttp3 (à l'aide du générateur):

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

la client ici est maintenant configurée pour utiliser les certificats de votre KeyStore. Cependant, il ne fera confiance qu'aux certificats de votre KeyStore et ne fera confiance à rien d'autre, même si votre système leur fait confiance par défaut. (Si vous n'avez que des certificats auto-signés dans votre KeyStore et tentez de vous connecter à la page principale de Google via HTTPS, vous obtiendrez SSLHandshakeException).

Vous pouvez obtenir une instance KeyStore à partir d'un fichier, comme indiqué dans docs :

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

Si vous êtes sur Android, vous pouvez le placer dans le dossier res/raw et l'obtenir à partir d'une instance Context à l'aide de

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

Il y a plusieurs discussions sur la façon de créer votre magasin de clés. Par exemple ici

73
Andrey Makarov

Pour okhttp3.OkHttpClient Version com.squareup.okhttp3: okhttp: 3.2.0, vous devez utiliser le code ci-dessous:

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

Deux méthodes de notre application pour obtenir OkHttpClient 3.0 instance qui reconnaît vos certificats auto-signés à partir de votre magasin de clés (utilise le fichier de certificat préparé pkcs12 dans le dossier de ressources de votre projet Android "raw"):

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

Contre Retrofit 1.9 J'ai pu accepter n'importe quel certificat avec la stratégie suivante: utilisez-le à vos risques et périls! Accepter un certificat est dangereux et vous devez en comprendre les conséquences. Certaines pièces pertinentes proviennent de org.Apache.http.ssl; vous pouvez donc avoir besoin d'importer certaines données ici.

// ...

    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

Je sais que ce post est assez ancien, bui je veux partager la solution qui a fonctionné pour moi avec la dernière mise à jour de OkHttp, la version 3.12.1 dans le temps que je suis en train d'écrire.

Tout d’abord, vous devez obtenir l’objet KeyStore qui sera ensuite ajouté à TrustManager:

/**
 *  @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;
}

Vous pouvez maintenant obtenir la OkHttpClient construite avec le certificat auto-signé dans votre magasin de clés:

/**
 * @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;
}

Rappelez-vous que il est fortement déconseillé de revenir toujours vrai dans la variable hostnameVerifier afin d’éviter le risque d’attaques humaines au milieu.

0
Domenico

J'ai eu le même problème et je l'ai corrigé avec le okhttp client comme suit:

1.) Ajoutez le fichier certificate à src/main/res/raw/, qui comprend le contenu suivant:

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

2.) Instancez le okHttpClient:

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

3.) Voici la méthode getSslContext(Context context) utilisée:

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

S'il est nécessaire d'ajouter plusieurs certificats à SslContext, ici est la solution.

0