Categories
Android

Use Retrofit with a self-signed or unknown SSL certificate in Android

In this post I will highlight how to use Retrofit with a self-signed or unknown SSL certificate in Android

First of all, this is hell in a cell. took me 8 hours to accomplish this from the point of a working Service/API Manager that uses the Retrofit Rest Client for android.

 

retrofit-and-android
If you can avoid it, please don’t try this!

Android is Implicitly Secure

As opposed to iOS development, android does not work with self-signed SSL certificates and certificates where the Certificate Authority cannot be verified. Surprisingly, using an insecure SSL certificate works with iOS development (emulator and phone dev).

When using retrofit (verbose logging enabled) with the following code sample:


public static final RestAdapter REST_ADAPTER = new RestAdapter.Builder()
            .setEndpoint("https://my-unsecure-endpoint.co.za")
            .setClient(new OkClient(SplashActivity.trustTestClient))
            .setLogLevel(RestAdapter.LogLevel.FULL)
            .setLog(new AndroidLog("RETROFIT"))
            .build();

We get the following error in Logcat:


java.security.cert.certificateexception: java.security.cert.certpathvalidatorexception: trust anchor for certification path not found.

Searching for this error and avoiding non-authentic sources we get:

Android Developer Docs Security with HTTPS and SSL – An excellent article explaining what is going on. Try the code yourself before moving on.

who-knows-maybe-you-can-break-the-bank-whoops
Go ahead use the Android Developer docs. Who knows maybe you can break the bank…whoops!

First of all this line of code…error prone for me. Do you need to change permissions or what?

InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));

Also who in their right mind will use the standard AndroidHttpClient,DefaultHttpClient or the HttpURLConnection. U WOT MATE? If you aren’t using Retrofit as your REST Client for Android Development either you haven’t found it or you are an idiot.

not using Retrofit REST Client
Not using Retrofit?

Let us get this self-signed or untrusted certificate

Lets get the certificate, we need the .pem.

A .pem is a container format that may include just the public certificate (such as with Apache installs, and CA certificate files /etc/ssl/certs), or may include an entire certificate chain including public key, private key, and root certificates. The name is from Privacy Enhanced Email, a failed method for secure email but the container format it used lives on, and is a base64 translation of the x509 ASN.1 keys.

Replace ${MY_SERVER} with the host name of the server you are using. Code below to acquire the SSL certificate PEM was provided by Crazy Bob.

echo | openssl s_client -connect ${MY_SERVER}:443 2>&1 | \sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > mycert.pem

Now we need to create a keystore (BKS) for the self-signed/untrusted certificate

Info on Keytool.

First of all we need to use Bouncy Castle, because android supports it. Poignantly the only version of the keystore creator jar that works is version 146. Download the required Jar.

Generate the keystore (BKS), use the above .pem as the -file:

keytool -import -v -trustcacerts -alias <myalias> -file mycert.pem -keystore mykeystore.bks -storetype BKS -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath /home/odin/Downloads/bcprov-jdk15on-146.jar -storepass secret

Adding the Keystore to your Android Project

Copy your file to your android project directory in:src/main/res/raw/.

Load the Self Signed Certificate

Now you have a choice here, if you are nuts you can use the DefaultHttpClient highlighted on Codeproject or use the OkHttpClient by Square, the same creators and maintainers of Retrofit. To help you make your decision take a look at this post on github.

Below is the code for implementing the OkHttpClient Solution MySSLTrust.java class:


public class MySSLTrust {
    public static OkHttpClient trustcert(Context context){
        OkHttpClient okHttpClient = new OkHttpClient();
        try {
            KeyStore ksTrust = KeyStore.getInstance("BKS");
            InputStream instream = context.getResources().openRawResource(R.raw.mykeystore);
            ksTrust.load(instream, "secret".toCharArray());

            // TrustManager decides which certificate authorities to use.
            TrustManagerFactory tmf = TrustManagerFactory
                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(ksTrust);
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, tmf.getTrustManagers(), null);

            okHttpClient.setSslSocketFactory(sslContext.getSocketFactory());
        } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | KeyManagementException e) {
            e.printStackTrace();
        }
        return okHttpClient;
    }
}

Now we to create this Client and make sure that retrofit uses it. But where to create this Client. To be honest I am not sure, but in my project I have a Starting Activity and I have made sure that the variable is public static

Linking the newly created certificate authority entrenched OkHttpClient with Retrofit

In StartingActivity.java:


//Class Atributes
public static OkHttpClient trustTestClient;

protected void onCreate(Bundle savedInstanceState) {
...
trustTestClient = MySSLTrust.trustcert(this);

Now we need to parse it to our ApiManager.java class which allows us to re-use the same Retrofit REST client throughout the application:

In short:


public static final RestAdapter REST_ADAPTER = new RestAdapter.Builder()
.setRequestInterceptor(intercept)
.setEndpoint("https://my-unsecure-endpoint.co.za")
.setClient(new OkClient(StartingActivity.trustTestClient))
.setLogLevel(RestAdapter.LogLevel.FULL)
.setLog(new AndroidLog("RETROFIT"))
.build();

The full ApiManager.java can be found at the end of this post.

So there we have it, quite a headache, I hope this saves you some time. Now we can use retrofit with a self-signed or unknown SSL certificate in Android.
Any comments or ways to improve the code always welcome, please comment below.

android-https-ssl-certificate
I struggled with this one, but at the end of the day…

ApiManager.java:


public class ApiManager {

    public interface MyAPI {
        @GET("/path")
        public void voyager(@Query("var1") String var1, @Query("var2") String var2,
                            Callback response);
    }

    private static final String ENDPOINT = "https://my-unsecure-endpoint.co.za";

    //Member of Retrofit Library - creates adapter object
    //TODO: Remove Debugging during QA phase
    public static final RestAdapter REST_ADAPTER = new RestAdapter.Builder()
            .setEndpoint(ENDPOINT)
            .setClient(new OkClient(StartingActivity.trustTestClient))
            .setLogLevel(RestAdapter.LogLevel.FULL)
            .setLog(new AndroidLog("RETROFIT"))
            .build();

    private static final MyAPI MYAPI = REST_ADAPTER.create(MyAPI.class);

    public static MyAPI getService() {
        return MYAPI;
    }
}