Android enable TLS1.2 for API 19 or older

 TLS1.1 and TLS1.2 is not enabled in Android by default for API 19 or below. If an app is running on Device running Android API 19 or older and trying to make REST request to a server that requires TLS1.1 or above, it will fail.

To enable TLS1.2 for API 19 or older, all you have to do is to add the try block below in your Application file. It should be the first thing in the onCreate(), make sure the it happens before any Http client initializes, ex: OkHttp. This should work for all https connections, include making request to REST services, streaming videos, and loading pictures.

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        try {
            // Google Play will install latest OpenSSL
            ProviderInstaller.installIfNeeded(applicationContext)
            val sslContext = SSLContext.getInstance("TLSv1.2")
            sslContext.init(null, null, null)
            sslContext.createSSLEngine()
        } catch (e: GooglePlayServicesRepairableException) {
            e.printStackTrace()
        } catch (e: GooglePlayServicesNotAvailableException) {
            e.printStackTrace()
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
        } catch (e: KeyManagementException) {
            e.printStackTrace()
        }
    }
}

The dependency required for the ProviderInstaller

implementation com.google.android.gms:play-services-auth:17.0.0

To have more control with the TLS support, refer to this post, but not recommended.

Note, by setting default SSLSocketFactory on HttpsURLConnection will also enable the TLS versions specified in the custom socket factory, for all https connections made with HttpsURLConnection. Again, it’s not recommend as the ProviderInstaller will cover all types connections, but this only works for HttpsURLConnection.

HttpsURLConnection.setDefaultSSLSocketFactory(TLSSocketFactory())

Here is a custom socket factory taken from this post.

public class TLSSocketFactory extends SSLSocketFactory {

    private SSLSocketFactory internalSSLSocketFactory;

    public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException {
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, null, null);
        internalSSLSocketFactory = context.getSocketFactory();
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return internalSSLSocketFactory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return internalSSLSocketFactory.getSupportedCipherSuites();
    }
    
    @Override
    public Socket createSocket() throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket());
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
    }

    private Socket enableTLSOnSocket(Socket socket) {
        if(socket != null && (socket instanceof SSLSocket)) {
            ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"});
        }
        return socket;
    }
}

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.

Android playing audio with Exoplayer 2 example

 The Exoplayer is initialized with SimpleExoPlayer in the onCreate(), with DefaultRenderersFactory, DefaultTrackSelector, and DefaultLoadControl. The data source (audio) is initialized using ProgressiveMediaSource with DefaultDataSourceFactory and DefaultExtractorsFactory. The Exoplayey is prepared with this ProgressiveMediaSource. In the onResume(), the playWhenReady property of the Exoplayer is set to true, in the onPause(), the playWhenReady property of the Exoplayer is set to false, and in the onDestroy(), the player is released.

class MainActivity : AppCompatActivity(), Player.EventListener {

    lateinit var player: SimpleExoPlayer

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val renderersFactory = DefaultRenderersFactory(this)
        val trackSelectionFactory = AdaptiveTrackSelection.Factory()
        val trackSelectSelector = DefaultTrackSelector(trackSelectionFactory)
        val loadControl = DefaultLoadControl()

        player = ExoPlayerFactory.newSimpleInstance(this, renderersFactory, trackSelectSelector, loadControl)
        player.addListener(this)

        val dataSourceFactory = DefaultDataSourceFactory(this, getString(R.string.app_name))
        val extractorsFactory = DefaultExtractorsFactory()

        val mediaSource = ProgressiveMediaSource
            .Factory(dataSourceFactory, extractorsFactory)
            .createMediaSource(Uri.parse("https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_5MG.mp3"))

        player.prepare(mediaSource)

        video_view.player = player

    }

    override fun onResume() {
        super.onResume()
        player.playWhenReady = true
    }

    override fun onPause() {
        super.onPause()
        player.playWhenReady = false
    }

    override fun onDestroy() {
        super.onDestroy()
        player.release()
    }

    //region Player.EventListener
    override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) {
    }

    override fun onLoadingChanged(isLoading: Boolean) {
    }

    override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
    }

    override fun onPlayerError(error: ExoPlaybackException?) {
    }

    override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters?) {
    }
    //endregion
}

activity_main.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="#000000">

    <com.google.android.exoplayer2.ui.PlayerView
        android:id="@+id/video_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:controller_layout_id="@layout/view_player_controller"/>

</FrameLayout>

view_player_controller.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:layoutDirection="ltr"
    android:background="#CC000000"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:paddingTop="4dp"
        android:orientation="horizontal">

        <ImageButton android:id="@id/exo_play"
            style="@style/ExoMediaButton.Play"/>

        <ImageButton android:id="@id/exo_pause"
            style="@style/ExoMediaButton.Pause"/>

    </LinearLayout>

</LinearLayout>

How to extract filename from Uri?

Now, we can extract filename with and without extension :) You will convert your bitmap to uri and get the real path of your file. Now w...