One of the most important things in mobile development is secure communication, especially between the app and its backend server. Currently, the most common architecture of web services is REST based on HTTP. The best protection method for this model of communication is the TLS/SSL standard. It can be combined with the HTTP protocol to create an encrypted variant called HTTPs. HTTPs ensures safe, encrypted communication channels between client apps and the backend server. Moreover, implementing this security feature is very simple on Android. You just need to watch out for some common pitfalls.

The problem

http

https,

It’s very common for developers to implement communication over HTTPs, but not in a proper way. This improper implementation is reduced to replacing the protocol name in the URL fromtoeg. https://www.example.com. Indeed, such an implementation will enable TLS/SSL encryption (if the backend server supports it). However, it will not ensure a good enough security level. The TLS standard is based on X509 certificates and asymmetric encryption. Simply replacing the protocol name will enable encryption, but the app will trust every certificate issued by the server. This means that the attacker can generate their own fake certificates. The certificates will then allow the hacker to intercept encrypted communication. This kind of attack is called Man-In-The-Middle . It is the main reason why you should spend a bit more time and effort to implement HTTPs configuration correctly.

Solution

The old-school way - TrustManager

Add your certificate file to the app resources under /res/raw Load KeyStore with the Certificate file from resources (as InputStream).

val resourceStream = resources.openRawResource(R.raw.demo_cert) val keyStoreType = KeyStore.getDefaultType() val keyStore = KeyStore.getInstance(keyStoreType) keyStore.load(resourceStream, null) Get TrustManagerFactory and init it with KeyStore.

val trustManagerAlgorithm = TrustManagerFactory.getDefaultAlgorithm() val trustManagerFactory = TrustManagerFactory.getInstance(trustManagerAlgorithm) trustManagerFactory.init(keyStore) Get an instance of SSLContext, bind it with TrustManager, and create an sslContext with a URL connection.

val sslContext = SSLContext.getInstance("TLS") sslContext.init(null, trustManagerFactory.trustManagers, null) val url = URL("http://www.example.com/") val urlConnection = url.openConnection() as HttpsURLConnection urlConnection.sslSocketFactory = sslContext.socketFactory

OkHttp and CertificatePinner

To avoid this exploit, developers should implement Certificate Pinning . It’s a method that depends on server certificate verification on the client side. This verification requires the server certificate or its fingerprint to be previously known to the mobile app. When establishing a connection with the server, the app should compare the fingerprint with a certificate from the remote server. If the fingerprints are identical, then the connection is valid and the data transfer can proceed. If the fingerprints are not identical, then the app should reject the connection immediately, as it’s compromised. The following 3 methods are the most popular ways to implement Certificate Pinning in Android apps. TrustManager is a component responsible for deciding whether the app should accept credentials submitted by the peer or not. This mechanism is sourced from the javax.net.ssl package and you can use it to implement Certificate Pinning in Android apps. Keep reading for a step-by-step tutorial on how to implement pinning using this component.As you can see, the solution presented above is quite complex. It also requires handling multiple elements related directly to the framework API. This means that the implementation is conducted on a fairly low level. It can lead to some bugs, but keep calm - there is a simpler way to handle Certificate Pinning. OkHttp by Square is a very popular HTTP client library for Java and Android. The library is used by one of the most popular tools for handling REST communication in Android - Retrofit . OkHttp provides a mechanism that makes implementing Certificate Pinning easy, as it only requires creating an instance ofusing a dedicated builder with its corresponding fingerprints. The fingerprints need to be hard-coded into the app, of course. Personally, I prefer to inject such keys during the build process, using the buildConfigField method. It’s more flexible and safer than keeping the keys in the repository. Then, you need to build an OkHttpClient instance with the CertificatePinner. Here's how to do it:

val certificatePinner = CertificatePinner.Builder() .add( "www.example.com", "sha256/ZC3lTYTDBJQVf1P2V7+fibTqbIsWNR/X7CWNVW+CEEA=" ).build() val okHttpClient = OkHttpClient.Builder() .certificatePinner(certificatePinner) .build()

Something fresh - Network Security Configuration

networkSecurityConfig

Create a network security config file under res/xml/network_security_config.xml Add the android:networkSecurityConfig attribute to the application tag.

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="co.netguru.demoapp"> <application android:networkSecurityConfig="@xml/network_security_config"> ... </application> </manifest Set up the configuration file and add fingerprints.

<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config> <domain includeSubdomains="true">example.com</domain> <pin-set> <pin digest="SHA-256">ZC3lTYTDBJQVf1P2V7+fibTqbIsWNR/X7CWNVW+CEEA=</pin> <pin digest="SHA-256">GUAL5bejH7czkXcAeJ0vCiRxwMnVBsDlBMBsFtfLF8A=</pin> </pin-set> </domain-config> </network-security-config>

Summary

You can add multiple fingerprints for different domains. Multiple fingerprints will also make your app more flexible. You can add all fingerprints from the certification path. You can also add additional certificates if the old ones are going to expire soon. Fingerprints can be retrieved directly from the certificate. You can also import the certificate file to the resources folder, like in TrustManager case. This time you need to manually write a class that will extract the fingerprint from the file. You can also use Peer certificate extractor to do that for you.The Android platform provides a new, easy tool to handle network configuration -(NSC). It has been available since Android 7.0. With NSC, you can declare communication methods, including Certificate Pinning, using XML files. To enable the configuration, you need to bind a configuration file with the Manifest. To bind it, use theattribute in the Application tag. Here is a short snippet showing how to handle it:As you can see, this method is extremely easy to implement. However, keep in mind that it’s only available for API level 24 or higher. For lower levels, you can use a backported version of NSC I’ve presented three ways of implementing Certificate Pinning. Personally, I think that the most flexible option is to use. This method is both short and universal - it works on all Android API levels out of the box. You must use OkHttp as the HTTP client, but the library is very easy to use and pretty much a standard on Android at this point, so it’s recommended anyway.However, it does not matter which implementation method you’ll use, but always remember that Certificate Pinning is. It is the only way to provide truly secure networking, which is why OWASP Mobile recommends certificate pinning as the most effective protection method for MiTM attacks.

Photo by Rubén Bagüés on Unsplash