Apps by default trust the pre-installed system Certificate Authorities (CA), but if someone wants to configure custom CAs in order to use self-signed certificates or certificates issued within a company or to limit the set of CAs or to trust additional CAs not included. This is accomplished by Android’s Network Security Configuration feature, which allows apps to customize their network security settings in an XML file in which developers can set specific domains and apps.
Add a Network Security Configuration file
This feature uses an XML file where you can customize the network security for your app. You must specify this XML in the manifest file under the Application tag.
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application android:networkSecurityConfig="@xml/network_security_config"
... >
...
</application>
</manifest>
Set up trusted Certificate Authorities
Create one XML file under res/xml/ directory for network security configuration. Now consider a scenario where the user wants to connect to a host which uses a self-signed SSL certificate or to a host whose SSL certificate is issued by a non-public CA.
Add the self-signed or non-public CA certificate, in PEM or DER format, to res/raw/self_ca
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">my.domain.com</domain>
<trust-anchors>
<certificates src="@raw/self_ca"/>
</trust-anchors>
</domain-config>
</network-security-config>
In the above snippet, we configured the self-signed CA for a specific domain.
Here the includeSubdomains attribute is for matches this CA for the domain and all the subdomains. If this attribute is false then it will not consider the subdomains.
Now if the user wants to trust all the pre-installed CAs along with a custom CA then we can simply add one more certificate attribute under the trust-anchors tag as below.
<trust-anchors>
<certificates src="@raw/self_ca"/>
<certificates src="system"/>
</trust-anchors>
User-installed certificates will also be included in your default trust anchor, noted as a user.
<trust-anchors>
<certificates src="@raw/self_ca"/>
<certificates src="system"/>
<certificates src="user"/>
</trust-anchors>
We can add multiple certificates under the same domain and can add multiple domains under the same domain configurations. It can also accept a set of CAs provided in a single directory.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">self.domain.com</domain>
<domain includeSubdomains="true">my.domain.com</domain>
<trust-anchors>
<certificates src="@raw/trusted_roots"/>
<certificates src="system"/>
<certificates src="user"/>
</trust-anchors>
</domain-config>
</network-security-config>
The above code snippet will trust pre-installed CA, user installed CA and custom certificates. All these CAs will be matched to both the specified domains.
If the user wants to add any self-signed certificate for localhost, that can be configured as below.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">localhhost</domain>
<trust-anchors>
<certificates src="@raw/local_cert"/>
</trust-anchors>
</domain-config>
</network-security-config>
Implement certificate pinning
Normally, an app trusts all pre-installed CAs. A man-in-the-middle attack could occur if any of these CAs issued a fraudulent certificate. Some apps limit which certificates they accept by either limiting the CAs they trust or by certificate pinning.
Pinning certificates is done by hashing the public key (SubjectPublicKeyInfo of the X.509 certificate) and providing a set of certificates. At least one of the pinned public keys must be included in a certificate chain in order for it to be valid.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">self.domain.com</domain>
<pin-set>
<pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
</pin-set>
</domain-config>
</network-security-config>
This certificate hash can be generated by using the below command.
openssl s_client -servername self.domain.com -connect self.domain.com | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
It is important to always include a backup key when using certificate pinning, so that your app’s connectivity won’t be affected if you need to change keys or CAs (when pinning to a CA certificate). Otherwise, the app must be updated to restore connectivity.
Moreover, pins can have an expiration time after which they are no longer pinned. This prevents connectivity issues in unupdated apps. If pins are set to expire, pinning bypass may be possible.
<pin-set expiration="2023-01-01">
<pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
<!-- backup pin -->
<pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin>
</pin-set>
we can also use the expiration attribute for excluding some of the subdomains from the provided pin set by giving past dates.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">self.domain.com</domain>
<pin-set expiration="2023-01-01">
<pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
<!-- backup pin -->
<pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin>
</pin-set>
<domain-config>
<domain includeSubdomains="true">self.subdomain.com</domain>
<pin-set expiration="2018-01-01">
<pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
</pin-set>
</domain-config>
</domain-config>
</network-security-config>
The above snippet will exclude the self.subdomain.com subdomain from the pinning and it will trust all the CAs.