Search code examples
androidhttpsretrofitself-signed-certificate

Trust my own self-signed certificate in local network using Android Native, Android Studio and retrofit


I am creating a simple android app that will be used in a closed local network. In this local network, a flask server is running which is configured to use a self-signed certificate via nginx proxying. The backend application of the server works fine using the self-signed certificate, I have verified this both using my browser and postman. (Obviously, I had to explicitly ask the browser to trust my certificate).

For days, I have been trying to find some definitive answer online on how to make my android app accept my certificate, but all the things I have tried have led me to a dead end. Sometimes the solutions where deprecated, and other times just too complicated for such a trivial thing.

The http requests are sent using Retrofit; as I understand, I must somehow configure my retrofit instance's http client to accept my certificate.

I have managed to use a client that accepts any certificate, but this is not what I want. Ideally, my certificate would be added to the "set" of certificates that are trusted by default by official CAs, so that the app can possibly send requests to outside resources as well.

So, given that the backend application is running on e.g. 192.168.1.10:443, how would I go about this?

Note: I have read the instructions given here https://developer.android.com/training/articles/security-config.html#TrustingAdditionalCas and have added

android:networkSecurityConfig="@xml/network_security_config"

to my manifest file, but I am getting the following error:

Hostname 192.168.1.10 not verified: certificate sha256/...../..... and continues to list the information of the certificate like common name etc.


Solution

  • Shlomi Katriel's answer was something I had already tried, but it led me to the solution indirectly. Please keep in mind that the only reason I was able to solve this was because I have root access to the server and can do with it as I please.

    This answer was basically the key to solving the whole thing. I will post all the steps in case someone else needs this.

    Step 1 Create your self-signed certificate. In my case, I used the openssl utility. It is very important to include the -addext flag. Here is an example taken directly from the answer I linked above:

    openssl req \
    -newkey rsa:2048 \
    -nodes \
    -x509 \
    -days 36500 -nodes \
    -addext "subjectAltName = IP.1:1.2.3.4" \
    -keyout /etc/ssl/private/nginx-selfsigned2.key \
    -out /etc/ssl/certs/nginx-selfsigned2.crt
    

    Replace 1.2.3.4 with the local ip of your server. This will include the server's local ip in the subjectAltName property of the certificate. Without this, you will keep getting the error "Hostname ... not verified ..."

    Using the certificate in a running nginx instance is a different subject which can be done easily and there are plenty of resources online.

    Step 2 Open android studio and create a new android resource directory (if you don't already have it) under res named raw. Inside this directory, copy the contents of the .crt file that the command above produced.

    Step 3 Create an android resource directory under res named xml. In there, create a new xml file which in my case I called network_security_config.xml. In there, I inserted the following:

    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
        <base-config cleartextTrafficPermitted="false">
            <trust-anchors>
                <certificates src="@raw/certificate"/>
                <certificates src="system"/>
            </trust-anchors>
        </base-config>
    </network-security-config>
    

    Instead of "certificate", use the filename of the certificate file you copied in step 2.

    Step 4 Add android:networkSecurityConfig="@xml/network_security_config" to the application element in your AndroidManifest.xml