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