'Does OkHttp support accepting self-signed SSL certs?

I'm working for a customer who has a server with self-signed SSL cert.

I'm using Retrofit + CustomClient using wrapped OkHttp client:

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

Does OkHttp support calling Self-Signed SSL cert server by default?

By the way. Which client is using Retrofit by default? I thought it was OkHttp but when I researched a bit more I realized I needed to import OkHttp dependencies



Solution 1:[1]

Yes, It does.

Retrofit allows you to set your custom HTTP client, that is configured to your needs.

As for self-signed SSL certs there is a discussion here. The link contains code samples to add self-signed SSL to Android's DefaultHttpClient and to load this client to Retrofit.

If you need OkHttpClient to accept self signed SSL, you need to pass it custom javax.net.ssl.SSLSocketFactory instance via setSslSocketFactory(SSLSocketFactory sslSocketFactory) method.

The easiest method to get a socket factory is to get one from javax.net.ssl.SSLContext as discussed here.

Here is a sample for configuring 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());

Updated code for okhttp3 (using builder):

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

the client here is now configured to use certificates from your KeyStore. However it will only trust the certificates in your KeyStore and will not trust anything else, even if your system trust them by default. (If you have only self signed certs in your KeyStore and try to connect to Google main page via HTTPS you will get SSLHandshakeException).

You can obtain KeyStore instance from file as seen in 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;
}

If you are on android you can put it in res/raw folder and get it from a Context instance using

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

There are several discussions on how to create your keystore. For example here

Solution 2:[2]

Another thing to note, if you pre-install the CA on the device, you can make regular https calls with OKHttp, and no special ssl hoops. The key is to add the network security configs to your manifest.

The key for me to know to do this was that I was getting the following exception.

"Trust anchor for certification path not found."

Here is a good article from Google about how to configure it. https://developer.android.com/training/articles/security-config

Here is an example of my network_security_config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<network-security-config>
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="user"/>
            <certificates src="system"/>
        </trust-anchors>
    </base-config>
</network-security-config>

Solution 3:[3]

For okhttp3.OkHttpClient Version com.squareup.okhttp3:okhttp:3.2.0 you have to use the code below :

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

Solution 4:[4]

Two methods from our app to get OkHttpClient 3.0 instance that recognizes your self-signed certificates from your keystore (uses prepared pkcs12 certificate file in your Android project "raw" resources folder):

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

Solution 5:[5]

I had the same problem and I fixed it with the okhttp client as follow:

1.) Add the certificate file to src/main/res/raw/, which includes this content:

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

2.) Instanciate the okHttpClient:

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

3.) Here is the used getSslContext(Context context) method:

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("my-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;
}

If there is the need to add multiple certificates to the SslContext, here is the solution.

Solution 6:[6]

Against Retrofit 1.9 I was able to accept any certificate with the following strategy: use at your own risk! Accepting any certificate is dangerous and you should understand the consequences. Some relevant parts come from org.apache.http.ssl, so you may require some imports here.

// ...

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

Solution 7:[7]

I know that this post is quite old, bui i want to share the solution that worked for me with the latest update of OkHttp, the 3.12.1 version in the time i'm writing.

First of all you need to obtain the KeyStore object that will be then added to the 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;
}

Now you can get the builded OkHttpClient with the self-signed certificate in your keystore:

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

Remember that it is highly discouraged to return always true in the hostnameVerifier to avoid risk of man in the middle attacks.

Solution 8:[8]

I find answer from :

https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java

It uses HandshakeCertificates to add certificates.

 HandshakeCertificates certificates = new HandshakeCertificates.Builder()
        .addTrustedCertificate(letsEncryptCertificateAuthority)
        .addTrustedCertificate(entrustRootCertificateAuthority)
        .addTrustedCertificate(comodoRsaCertificationAuthority)
        // Uncomment if standard certificates are also required.
        //.addPlatformTrustedCertificates()
        .build();

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

If you have trust certificates in store, you can use it as below:

.......
List<X509Certificate> certificates = getCertificatesFromTrustStore();
        
Builder certificateBuilder =  new HandshakeCertificates.Builder();        
for (X509Certificate x509Certificate : certificates) {
    certificateBuilder.addTrustedCertificate(x509Certificate);
}
HandshakeCertificates handshakeCertificates =  certificateBuilder.build();
.......
//To get certificates from a keystore
private List<X509Certificate> getCertificatesFromTrustStore() throws Exception {
        KeyStore truststore = KeyStore.getInstance("JKS");
        truststore.load(new FileInputStream("d:\certs.jsk"), "mypassword".toCharArray());
        
        PKIXParameters params = new PKIXParameters(truststore);
        Set<TrustAnchor> trustAnchors = params.getTrustAnchors();
        LOG.debug("{} certificates found in {} which will be used", trustAnchors.size(), trustStorePath);
        
        List<X509Certificate> certificates = trustAnchors.stream()
                  .map(TrustAnchor::getTrustedCert)
                  .collect(Collectors.toList());
        return certificates;
    }

Solution 9:[9]

If you need to provide your own certificate, you can pass it like this:

Manifest:

<application android:networkSecurityConfig="@xml/network_security_config"
                    ... >

res/xml/network_security_config.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
    <trust-anchors>
        <certificates src="@raw/your_PEM_formatted_cert" />
        <certificates src="user" />
        <certificates src="system" />
    </trust-anchors>
</base-config>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 selectAll
Solution 2 GR Envoy
Solution 3 Gugelhupf
Solution 4 Sky Kelsey
Solution 5
Solution 6 jocull
Solution 7
Solution 8 Alireza Fattahi
Solution 9 Oleksandr Nos