skip to Main Content

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.

2

Answers


  1. Chosen as BEST ANSWER

    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


  2. Best solution

    pull the strings to make the owner of the server use a certificate, which is signed by a custom root CA, then pin this certificate using networkSecurityConfig.
    In my opinion it should be a requirement that should prevent a feature from being released.

    Alternative

    I would create 2 http clients:

    1. One with breached trust manager that accepts all certificates (empty imolementation of chechServerTrusted(...)) – only for that specific local network request that expected to fail the SSL handshake.
    2. Normal one for the rest of the requests.

    By doing so, you’re exposing this request for man-in-a-middle attack in case an attacker got into the local network – both attacker and real server certificates are not trusted.
    They can also mimic the certificate metadata so you won’t tell the difference.
    Only you know if your system can afford the risk (and how probable is the risk anyway).

    More insights:

    • networkSecurityConfig is meant for well known certificates so you can hard code public keys, so I don’t believe it can solve your case.
    • You cannot install certificate authority on device without the actual self signed certificate.

    Update

    According to Android Developers, you need the following configuration:

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

    They claim res/raw/my_custom_cas accepts all common formats:

    Add the trusted CAs, in PEM or DER format, to res/raw/trusted_roots. Note that if using PEM format the file must contain only PEM data and no extra text. You can also provide multiple <certificates> elements instead of one.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search