AndroidSecurityConfig for local development and useful commands
In one of the projects we have been working on for the last months we’ve been working on both mobile development (native Android) and web development, creating a backend and a front end application. Basically, the mobile app sends messages via POST request to the backend, where they are stored, so they can be displayed in the frontend dashboard we created as well.
Android does not trust unsecure connections (HTTP instead of HTTPS) by default. We were using this kind of connections during the first stages of the project to send these messages, at least until we had a valid certificate, from the Android app to the backend. As a workaround, we enabled HTTP connections temporary the following way:
AndroidManifest.xml
1
2
3
4
5
6
7
8
|
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
<application
...
cleartextTrafficPermitted="true"
... >
</application>
</manifest>
|
This simple configuration allowed us to continue working, as we said, until we had a valid certificate in the backend and could use HTTPS. In the meantime we were still able to test the app in our local environment and in the machines that eventually were going to be used for deployment.
Once we had the certificate we redirected our network traffic to HTTPS and thought that was it. Little did we know that it wouldn’t be so easy. We were still not able to access our backend locally.
- If we used the real certificate in our local machines we would get an error related to the host name (Hostname was not verified). The certificate was emitted for URLs under the domain of our collaborator’s company name (**..com*). Whenever we run our backend locally it would be launched on localhost, but the address we would access from the Android Emulator would be 10.0.2.2, which is just an alias. Either way, none of those addressed (localhost or 10.0.2.2) looked nothing like *..com meaning we would not be able to use the real certificate for development stages. This isn’t recommended anyways, so that was an easy solution for us to discard.
- Using a self signed certificate we would see the following exception in our terminal
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
Wait… what? What’s a trust anchor? Where should it be? And where are you looking for it?
We were trying to figure out what was happening for a whole week, and we are here to tell you how we managed this issue.
The first thing we did was look it up online, of course, and we came across different workarounds for this, such as enabling ALL connections or adding “patches” to the actual code that eventually will be production code, and we weren’t quite happy with any of those. We kept searching online for other solutions until we found this article deep down the Android developers documentation page.
The solution was generating our own certification authority (CA) to then generate a client certificate too. It would be necessary to add the generated CA to the Android application in order to get Android to accept this CA as a trusted CA. Moreover, we would have to add the keystore (that includes the CA certificate internally) to the backend app. Let’s see it in detail:
Importing this CA in the Android application should be done in a configuration file that must be referenced in the application manifest too.
To do this, we only have to add android:networkSecurityConfig="@xml/network_security_config in the application section of the AndroidManifest.xml. In this case, @xml/network_security_config references a configuration file that must be created inside a xml directory and whose content must be the following:
1
2
3
4
5
6
7
8
|
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<debug-overrides>
<trust-anchors>
<certificates src="@raw/ca" /> <!-- ca certificate with .pem extension -->
</trust-anchors>
</debug-overrides>
</network-security-config>
|
In this way, it is not necessary to install the CA certificate in every device we want to execute the application in, since it is part of the project configuration. It is worth mentioning that this configuration is inside the debug-overrides section, meaining that it is only valid when the application is executed in debug mode. This mode is the default one used by Android studio when we are in the development stage and run our applications.
As it was aforementioned, the backend application must use a keystore that contains the CA certificate and the client certificate previously signed by this CA.
To generate and sign the certificates openssl and keytool tools are needed. The next commands could be used to accomplish this:
- The first step consists of the generation of private keys for the root certificate and the CA certificate.
1
2
|
keytool -genkeypair -alias root -dname "cn=Local Development" -validity 10000 -keyalg RSA -keysize 2048 -ext bc:c -keystore root.jks -keypass 123456-storepass 123456
keytool -genkeypair -alias ca -dname "cn=Local Development" -validity 10000 -keyalg RSA -keysize 2048 -ext bc:c -keystore ca.jks -keypass 123456-storepass 123456
|
- Next, the root certificate must be generated using the previously generated key. To do this, the following command could be used:
1
|
keytool -exportcert -rfc -keystore root.jks -alias root -storepass 123456 > root.pem
|
- The following step consists of generating a CA certificate signed by the root one and importing this certificate chain in our keystore.
1
2
3
|
keytool -keystore ca.jks -storepass 123456 -certreq -alias ca| keytool -keystore root.jks -storepass 123456 -gencert -alias root -ext bc=0 -ext san=dns:ca -rfc > ca.pem
keytool -keystore ca.jks -storepass 123456 -importcert -trustcacerts -noprompt -alias root -file root.pem
keytool -keystore ca.jks -storepass 123456 -importcert -alias ca -file ca.pem
|
- The next step requires that a new private key be generated to create the server certificate. This private key will be used to generate a server certificate signed by the CA. In this step, we can add the IPs and DNSs that we need in local development.
1
2
|
keytool -genkeypair -alias server -dname cn=server -validity 10000 -keyalg RSA -keysize 2048 -keystore localDevkeystore.jks -keypass 123456 -storepass 123456
keytool -keystore localDevkeystore.jks -storepass 123456 -certreq -alias server | keytool -keystore ca.jks -storepass 123456 -gencert -alias ca -ext ku:c=dig,keyEnc -ext "san=ip:10.0.2.2" -ext eku=sa,ca -rfc > server.pem
|
- Finally, the certificate chain must be added in the keystore.
1
2
3
|
keytool -keystore localDevkeystore.jks -storepass 123456 -importcert -trustcacerts -noprompt -alias root -file root.pem
keytool -keystore localDevkeystore.jks -storepass 123456 -importcert -alias ca -file ca.pem
keytool -keystore localDevkeystore.jks -storepass 123456 -importcert -alias server -file server.pem
|
Other useful commands:
- Convert keystore from .jks to .p12.
1
|
keytool -importkeystore -srckeystore clientkeystore -srcstoretype JKS -deststoretype PKCS12 -destkeystore keystore.p12
|
- Extract .crt public key from keystore .p12.
1
|
openssl pkcs12 -in certificate.p12 -out certificate.pem -clcerts -nokeys
|
- Extract .key private key from keystore .p12.
1
|
openssl pkcs12 -in certificado.p12 -out certificado_key.pem -nocerts -nodes
|
- Convert from .p12 file to .crt to .key public and private key files.
1
|
openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt
|
- Print keystore information.
1
|
keytool -list -v -keystore keystore.jks
|