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
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:
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
namedraw
. Inside this directory, copy the contents of the .crt file that the command above produced.Step 3 Create an android resource directory under
res
namedxml
. In there, create a new xml file which in my case I callednetwork_security_config.xml
. In there, I inserted the following: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.xmlBest 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:
chechServerTrusted(...)
) – only for that specific local network request that expected to fail the SSL handshake.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.Update
According to Android Developers, you need the following configuration:
They claim
res/raw/my_custom_cas
accepts all common formats: