androidcertificateokhttp

Use a certificate in an okhttp request with android


The server of the application in which I work uses a certificate to allow requests. I have it installed, for example, in the desktop Chrome browser and it works fine. It´s a usual certificate with the extension .cer

Now I have to make this certificate work also in my android application and, honestly, I have never done it and I'm a bit lost.

To make the requests I am using okhttp2, as you can see in this example:

 public String makeServiceCall(String url, JSONObject data) {
        final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
        OkHttpClient client = new OkHttpClient();
        client.setConnectTimeout(45, TimeUnit.SECONDS);
        client.setReadTimeout(45, TimeUnit.SECONDS);
        client.setProtocols(Arrays.asList(Protocol.HTTP_1_1));

        RequestBody body = RequestBody.create(JSON, data.toString());
        Request request = new Request.Builder()
                .url(url)
                .header("Accept","application/json")
                .post(body)
                .build();
        try {
            Response response = client.newCall(request).execute();
            return response.body().string();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

So far everything works perfectly, but after searching and reading tutorials, examples, etc, (many of them from this page) I have not managed to make it work. Make it work with the certificate.

Having never done this, and being a bit confused already, I would appreciate the following clarifications:

ok, I already have my certificate converted to BKS and hosted in the res / raw folder, but I'm still unable to apply it successfully to the request okhttp2 ..

I have searched for information about doing it using okhttp3 but I have not been able to authorize the requests either.

This article has been useful to me, but I am not using retrofit and adapting it to okhttp2 does not work either.

I would appreciate an explanation of how to do it


Solution

  • Here is an implementation using official okhttp3 sample code. It is possible to create a trusted OkHttpClient using a custom certificate. I've put the .cer certificate in res/raw then read it in using the trustedCertificatesInputStream() method.

    CustomTrust customTrust = new CustomTrust(getApplicationContext());
    OkHttpClient client = customTrust.getClient();
    

    CustomTrust.java

    import android.content.Context;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.security.GeneralSecurityException;
    import java.security.KeyStore;
    import java.security.cert.Certificate;
    import java.security.cert.CertificateFactory;
    import java.util.Arrays;
    import java.util.Collection;
    
    import javax.net.ssl.KeyManagerFactory;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSocketFactory;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.TrustManagerFactory;
    import javax.net.ssl.X509TrustManager;
    
    import okhttp3.CertificatePinner;
    import okhttp3.OkHttpClient;
    
    public final class CustomTrust {
    
        private final OkHttpClient client;
        private final Context context;
    
        public CustomTrust(Context context) {
    
            this.context = context;
            X509TrustManager trustManager;
            SSLSocketFactory sslSocketFactory;
            try {
                trustManager = trustManagerForCertificates(trustedCertificatesInputStream());
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, new TrustManager[]{trustManager}, null);
                sslSocketFactory = sslContext.getSocketFactory();
            } catch (GeneralSecurityException e) {
                throw new RuntimeException(e);
            }
    
            client = new OkHttpClient.Builder()
                    .sslSocketFactory(sslSocketFactory, trustManager)
                    .connectTimeout(45, TimeUnit.SECONDS)
                    .readTimeout(45, TimeUnit.SECONDS)
                    .protocols(Arrays.asList(Protocol.HTTP_1_1))
                    .build();
        }
    
        public OkHttpClient getClient() {
            return client;
        }
    
        /**
         * Returns an input stream containing one or more certificate PEM files. This implementation just
         * embeds the PEM files in Java strings; most applications will instead read this from a resource
         * file that gets bundled with the application.
         */
        private InputStream trustedCertificatesInputStream() {
            return context.getResources().openRawResource(R.raw.certificate);
        }
    
        /**
         * Returns a trust manager that trusts {@code certificates} and none other. HTTPS services whose
         * certificates have not been signed by these certificates will fail with a {@code
         * SSLHandshakeException}.
         *
         * <p>This can be used to replace the host platform's built-in trusted certificates with a custom
         * set. This is useful in development where certificate authority-trusted certificates aren't
         * available. Or in production, to avoid reliance on third-party certificate authorities.
         *
         * <p>See also {@link CertificatePinner}, which can limit trusted certificates while still using
         * the host platform's built-in trust store.
         *
         * <h3>Warning: Customizing Trusted Certificates is Dangerous!</h3>
         *
         * <p>Relying on your own trusted certificates limits your server team's ability to update their
         * TLS certificates. By installing a specific set of trusted certificates, you take on additional
         * operational complexity and limit your ability to migrate between certificate authorities. Do
         * not use custom trusted certificates in production without the blessing of your server's TLS
         * administrator.
         */
        private X509TrustManager trustManagerForCertificates(InputStream in)
                throws GeneralSecurityException {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(in);
            if (certificates.isEmpty()) {
                throw new IllegalArgumentException("expected non-empty set of trusted certificates");
            }
    
            // Put the certificates a key store.
            char[] password = "password".toCharArray(); // Any password will work.
            KeyStore keyStore = newEmptyKeyStore(password);
            int index = 0;
            for (Certificate certificate : certificates) {
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificate);
            }
    
            // Use it to build an X509 trust manager.
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
                    KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, password);
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                    TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new IllegalStateException("Unexpected default trust managers:"
                        + Arrays.toString(trustManagers));
            }
            return (X509TrustManager) trustManagers[0];
        }
    
        private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException {
            try {
                KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                InputStream in = null; // By convention, 'null' creates an empty key store.
                keyStore.load(in, password);
                return keyStore;
            } catch (IOException e) {
                throw new AssertionError(e);
            }
        }
    
    }