Trusting self signed ssl certificate in Android

1. Let’s create a simple nginx server serving media files. Skip this step if you already have a server with self signed ssl certificate.

2. Let’s create a simple video player app using ExoPlayer, which will play a mp4 video file through https from the server created in step 1. Or you can just checkout this repo, update the media_url_mp4 value in the string res with the https url that points to the video on the server that you created in step 1. Run the app, pick the MP4 and click play, it will not play and error out with error message: Trust anchor for certification path not found. But if you remove the s from the https, it will play normally. The http and https url to the video on the server should look like this:

http://123.456.78.9/media/bunny.mp4
https://123.456.78.9/media/bunny.mp4

3. To resolve this certificate untrusted issue, we need to create a pem file from the self signed certificate on the server, and generate a keystore file with the this pem file, then load this into the TrustManager in your Android app. Continue to the following steps to do these.

4. SSH into your server and run this command to create a pem file from the self signed certificate. In this case, mycert.pem.

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

5. Get a copy of this mycert.pem file to your local machine where you develop your Android apps. You can simply do cat mycert.pem on the server shell window, copy the content to the clickboard, create a new file with the same name mycert.pem on your local machine, then paste the content to it.

6. On your local machine, create a temp folder to hold this mycert.pem, cd into that folder, and then run this command to create a keystore file from this mycert.pem, using the bouncy castle provider library, if you don’t have it, download the jar file from their website and place it in the same folder where mycert.pem is. bcprov-jdk15to18-164.jar is the latest version at the time of this post.

export CLASSPATH=bcprov-jdk15to18-164.jar
CERTSTORE=mystore.bks
if [ -a $CERTSTORE ]; then
    rm $CERTSTORE || exit 1
fi
keytool \
      -import \
      -v \
      -trustcacerts \
      -alias 0 \
      -file <(openssl x509 -in mycert.pem) \
      -keystore $CERTSTORE \
      -storetype BKS \
      -provider org.bouncycastle.jce.provider.BouncyCastleProvider \
      -providerpath bcprov-jdk15to18-164.jar \
      -storepass some-password

7. After step 6, a keystore file mystore.bks will be created in the same folder where you run the command. Copy this mystore.bks to res/raw/mystore.bks in your Android project folder, if you don't have the raw folder already in your Android project, create it and place the mystore.bks in it.

8. Create an application class for your Android project if it doesn't already have one, and remember to register it in the Mainifest file. Let's use the simple video player Android project from step 2, create an application class, PlayerApp.kt, see the code in the onCreate, it loads the mystore.pem file and add it to the trust manager, create a SSLContext with it, set default ssl socket factory for the HttpsURLConnection, and lastly skips the hostname verification to avoid the hostname not verified error which will happen when self signed certificate did not include the ip address of the server.

import android.app.Application
import java.security.KeyStore
import javax.net.ssl.*

class PlayerApp : Application() {

    override fun onCreate() {
        super.onCreate()

        val trustStore = KeyStore.getInstance("BKS")
        val input = resources.openRawResource(R.raw.mystore)
        trustStore.load(input, null)

        val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
        tmf.init(trustStore)

        val sslCtx = SSLContext.getInstance("TLS")
        sslCtx.init(null, tmf.trustManagers, java.security.SecureRandom())

        //Set the default ssl socket factory which will trust the specified certificate.
        HttpsURLConnection.setDefaultSSLSocketFactory(sslCtx.socketFactory)

        //Skips the hostname verification by always returning true, otherwise might get
        //hostname not verified error when using a self signed certificate.
        HttpsURLConnection.setDefaultHostnameVerifier(object : HostnameVerifier {
            override fun verify(p0: String?, p1: SSLSession?): Boolean {
                return true
            }
        })
    }
}

9. Run the simple video app again, use the https url for the video and it should now plays the mp4 normally without the error message: Trust anchor for certification path not found.

10. Note, the above should work for any connection using HttpsURLConnection, if you are using other http clients such as OkHttp, the idea is the same, step 1 to 7 will be the same, only for step 8, instead of setting the ssl socket on HttpsURLConnection, you will do it for OkHttpClient instead.

11. Lastly, using a self signed certificate is not recommended for production, the above should only be used for development and testing purposes.

References:
https://stackoverflow.com/questions/6825226/trust-anchor-not-found-for-android-ssl-connection
https://stackoverflow.com/questions/2642777/trusting-all-certificates-using-httpclient-over-https/6378872#6378872
https://number1.co.za/use-retrofit-self-signed-unknown-ssl-certificate-android/

Search within Codexpedia

Custom Search

Search the entire web

Custom Search