emSign Blog

Explicit SSL Trust Implementation in Android using TrustManager Factory

By eMudhra Editorial on December 28, 2023
Explicit SSL Trust Implementation in Android using TrustManager Factory

Introduction

SSL Trust implementation in android helps in supporting root certificates which are not distributed in outdated OS versions, which may not have updates from OEM.

There are several ways in which it can be achieved. Here is a general overview of the complete implement process.

In Android app development, secure communication with servers is crucial to protect sensitive data. SSL/TLS certificates play a vital role in ensuring the security of the communication channel. However, there may be scenarios there is a need to implement custom trust for the certificate issued by Certificate Authorities (CAs) that are not distributed in older Android system.

The code snippet makes use of widely used OkHttp and Retrofit library for the implementation. The code below implements custom trust for the devices running Android 10 and below.

Prerequisites

  1. SSL certificate issued CA (.crt or .cer).

Steps to implement SSL Trust using TrustManager

1. Add our Public Key file into our project as a raw resource.

We can go to our project directory and create it raw folder inside the app/src/main/res and then paste our file.

2. Create a method that returns OkHttp Client with Custom TrustManager.

This method returns the OkHttp client with Custom TrustmManager. The methods check for Android SDK version and calls the addCustomTrustManager method mentioned the Step 3. In our case addCustomTrustManager is called only incase the SDK is less than equal to 10.

public static OkHttpClient getOkHttpClient(Context context) throws KeyStoreException, CertificateException, IOException, 
    NoSuchAlgorithmException, KeyManagementException 

{

    OkHttpClient.Builder builder = new OkHttpClient.Builder()
            .readTimeout(60, TimeUnit.SECONDS)
            .connectTimeout(60, TimeUnit.SECONDS);

    // Add custom trust manager for Android 10 and below.
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q){
        addCustomTrustManager(context, builder);
    }

    return builder.build();
}

3. Create a method that implements the custom SSL Trust.

This method is where we're going to read our certificate and add trust it. First we create the KeyStore instance which helps to store our certificate, than with instantiate TrustManagerFactory factory by passing the keystore object. The method also verifiy the server certificate with the certificate at client side and adds it to Okhttp builder.

public static void addCustomTrustManager(Context context, OkHttpClient.Builder builder) throws KeyStoreException, CertificateException, 
    IOException, NoSuchAlgorithmException, KeyManagementException 

{
        
    InputStream caFileInputStream = context
            .getResources().openRawResource(R.raw.rootcert);

    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    X509Certificate yourCertificate = (X509Certificate) certificateFactory.generateCertificate(caFileInputStream);

    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(null, null);
    keyStore.setCertificateEntry("certificate_name", yourCertificate);

    // Create a TrustManager that trusts the server certificate
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(keyStore);
    TrustManager[] trustManagers = new TrustManager[] {
            new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    // No client verification needed
                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    // Check if the server's certificate matches your trusted certificate
                    for (X509Certificate cert : chain) {
                        if (cert.equals(yourCertificate)) {
                            return; // The certificate is trusted
                        }
                    }
                    throw new CertificateException("Server certificate does not match the expected certificate.");
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            }
    };

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, trustManagers, null);
    SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

    builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustManagers[0]);
}

4. Create a method that returns Retrofit Client for making the API call.

This method returns the Retrofit client with OkHttp Builder configured. You can use the below method to instantiate Retrofit client and make the API calls.

public static Retrofit createRetrofitClient(Context context) throws Exception

{
    OkHttpClient client = getOkHttpClient(context);
    return new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(client)
            .build();
}

Sample Code

package com.emudhra.customtrustmanager;

import android.content.Context;
import android.os.Build;

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;

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.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class APIUtils

{

    public static final String BASE_URL = "<Base URL of API>";

    public static ApiService getApiService(Context context) throws Exception{
        return createRetrofitClient(context).create(ApiService.class);
    }

    /* Below method creates an Retrofit Client for making the API Call using OkHttp Client */
    public static Retrofit createRetrofitClient(Context context) throws Exception

{
        OkHttpClient client = getOkHttpClient(context);
        return new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .client(client)
                .build();
    }

    /* Below method creates an OkHttp Client with basic configuration and also adds CustomTrustManager for Android OS 10 and below.   */
    public static OkHttpClient getOkHttpClient(Context context) throws KeyStoreException, CertificateException, IOException, 
        NoSuchAlgorithmException, KeyManagementException 

{

        OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .readTimeout(60, TimeUnit.SECONDS)
                .connectTimeout(60, TimeUnit.SECONDS);

        // Add custom trust manager for Android 10 and below.
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q){
            addCustomTrustManager(context, builder);
        }
        return builder.build();
    }

    /* Below method create CustomTrustManager by verifing the certificate.   */
    public static void addCustomTrustManager(Context context, OkHttpClient.Builder builder) throws KeyStoreException, CertificateException, 
        IOException, NoSuchAlgorithmException, KeyManagementException 
{
            
        InputStream caFileInputStream = context
                .getResources().openRawResource(R.raw.rootcert);

        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
        X509Certificate yourCertificate = (X509Certificate) certificateFactory.generateCertificate(caFileInputStream);

        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, null);
        keyStore.setCertificateEntry("certificate_name", yourCertificate);

        // Create a TrustManager that trusts the server certificate
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);
        TrustManager[] trustManagers = new TrustManager[] {
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                        // No client verification needed
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                        // Check if the server's certificate matches your trusted certificate
                        for (X509Certificate cert : chain) {
                            if (cert.equals(yourCertificate)) {
                                return; // The certificate is trusted
                            }
                        }
                        throw new CertificateException("Server certificate does not match the expected certificate.");
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                }
        };

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustManagers, null);
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

        builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustManagers[0]);
    }
}

Conclusion

Explicit Trusting of SSL certificates in Android will be essential in certain scenarios like outdated Android version or those devices which may not get OEM updates. By implementing a custom TrustManager with Retrofit and OkHttp, it can ensure secure communication with servers. You can use the Step 3 code with any other networking libraries like Volley etc. or use it with default HttpURLConnection to the same.