DEV Community

Shahar Kedar
Shahar Kedar

Posted on

Using self-signed certificates when developing Android applications

I recently started learning how to develop mobile applications on Android. I've been doing mostly backend work before I joined RiseUp, but in the past 2 years I've learned to love web development, and mobile development seemed like a natural next step.
 
The first three weeks were awesome! Google created an amazing set of hands-on tutorials you can do to get started. But then I wanted to test my skills with real-world scenarios, and decided to implement our login flow in Android. The flow itself is not that interesting so I won't go into details there. What's important is that we use Nginx in our local (dev) environment to funnel all requests to our backend services. Nginx is responsible not only for routing API calls to the right service, but also for SSL termination. This allows our backend services to avoid meddling with SSL termination on their own.
 
Our local Nginx listens to port 9090, so I thought something like this should work pretty well*:

val loginApiService = Retrofit.Builder()
.baseUrl("https://localhost:9090")
.create(LoginApiService::class.java)                                

val token = loginApiService.authenticate(..)
Enter fullscreen mode Exit fullscreen mode

*I simplified the code to make it more concise. 

But when I ran the application, I got the following error:

java.net.ConnectException: Failed to connect to localhost/127.0.0.1:9090
Enter fullscreen mode Exit fullscreen mode

Say what??? Well… apparently localhost on mobile devices is the device itself. That actually makes sense.

Problem no.1 - forwarding calls to localhost to my laptop

What I really wanted is that any calls to localhost should go to my laptop and not the device. adb to the rescue!

Android Debug Bridge (adb) is a CLI tool that helps you communicate with your mobile devices (virtual or physical). You can easily install in using brew:

brew install android-platform-tools
Enter fullscreen mode Exit fullscreen mode

It has a cool command called reverse that allows you to forward any calls from any device to your laptop. This is how it's done:

adb reverse tcp:9090 tcp:9090
Enter fullscreen mode Exit fullscreen mode

This basically tells the device that every TCP call on port 9090 should be forwarded to port 9090 on your laptop.

Problem solved! So I ran my application again and this time I got this:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
Enter fullscreen mode Exit fullscreen mode

Problem no.2 - telling your device to trust a self-signed certificate

Our local Nginx uses a self-signed SSL certificate. It was created by an openssl command. This worked great for web development and we didn't have any real issues with it so far.

With Android (and iOS) devices, you need to tell your device to trust that certificate. There are two steps here:

Step 1 - Install a CA certificate on the device

First you need to tell your device to trust the root CA of your self signed certificate. Unfortunately, when using openssl to generate a certificate, you don't really have a root CA. So first I needed to change that.

I searched the Internet and found a great tool called mkcert. mkcert is a simple tool for making locally-trusted development certificates (I shamelessly copied from their README. Sue me).

When using mkcert you also get a PEM encoded CA Certificate which is exactly what our Android device is expecting. To find that certificate you can run:

 echo "$(mkcert -CAROOT)/rootCA.pem"
Enter fullscreen mode Exit fullscreen mode

Copy the certificate to your device. Now on your device open the _CA Certificate _ settings screen and follow the instructions to install the certificate you copied.

Step 2 - Tell your application to trust user added CAs

By default, Android applications will not trust user added CAs. To make it trust your certificate you need to create a new file under res/xml called network_security_config with the following content:

<network-security-config>
    <debug-overrides>
        <trust-anchors>
            <!-- Trust preinstalled CAs -->
            <certificates src="system" />
            <!-- Additionally trust user added CAs -->
            <certificates src="user" />
        </trust-anchors>
    </debug-overrides>
</network-security-config>
Enter fullscreen mode Exit fullscreen mode

Notice that we're trusting user added certificates only in debug mode. Released applications should work against properly signed SSL certificates.

Now add the configuration file to your AndroidManifest file:

<application   
...
android:networkSecurityConfig="@xml/network_security_config"
...
/>
Enter fullscreen mode Exit fullscreen mode

That's it! 🎉

P.S. This works well for both physical and virtual devices.

To summarize, in order to enable your Android application to connect with an HTTPs server on your laptop with a self-signed certificate, you should:

  1. Use adb to forward all calls to localhost
  2. Generate a root CA certificate and install in on your device
  3. Tell your application to trust user added CAs

Top comments (2)

Collapse
 
kimhj profile image
Kim Jensen

Would you have to generate a new CA certificate for the Android device every time you renew your SSL certificate?
I don't have a self signed, but one from Symantec that I renew every year.

Collapse
 
shaharke profile image
Shahar Kedar

I’m pretty sure that if you’re using a real certificate this whole manual is redundant.