Certificate pinning is the process of associating a host with their expected X.509 certificate or public key. Once a certificate or public key is known or seen for a host, the certificate or public key is associated or ‘pinned’ to the host.
A host or service’s certificate or public key can be added to an application at development time, or it can be added upon first encountering the certificate or public key. The former, adding at development time, is preferred since preloading the certificate or public key out of band usually means the attacker cannot taint the pin. If the certificate or public key is added upon first encounter, this is known as key continuity. Key continuity can fail if the attacker has a privileged position during the first encounter.
Why should you always pin?
Mobile applications should utilise either certificate or public key pinning in order to ensure that communications are secure. This is usually implemented when the developer of the application needs to validate the remote host’s identity or when operating in a hostile environment. Since one or both of these are almost always true, it is recommended that the majority of applications implement pinning.
How it’s work
Certificate pinning can be implemented in a great many different ways. The pinning strategy should be carefully designed as there are many trade-offs to consider:
What to pin?
Certificate
Public key
Certificate Hash
Where to pin?
The server’s certificate (a.k.a. leaf certificate)
The Certificate Authority’s certificate (a.k.a. root certificate)
An intermediate certificate
The whole certificate chain
These decisions will affect the security but also the longevity of the solution. For example, pinning connections against the whole certificate chain will be the more robust strategy, but if any of the certificates in the chain change (for legitimate reasons) then the application will be unable to establish connections without users updating to a newer/rectified version. As another example, if the application pins connections against the leaf certificate’s public key, connections will remain securely pinned even if the CA is compromised.
Where To Pin
Leaf certificate (server certificate)
Guarantees with close to 100% certainty that this is your certificate
even if Root CA was compromised
If the certificate becomes invalid for some reason (either normal expiration or compromise) the app will be bricked until you can push an update out
Allows self-signed certificates – which can be a good thing from an ease of maintenance perspective
Root certificate
By pinning against the root certificate you are trusting the root certificate authority as well as any intermediaries they trust not to mis-issue certificates
If CA gets compromised it’s game over
Very important to maintain strong certificate validation
Pinning is not an excuse for bad certificate validation!
Intermediate certificate:
By pinning against an intermediate certificate you are trusting that the intermediate certificate authority to not mis-issue a certificate for your server(s)
As long as you stick to the same certificate provider then any changes to your leaf certificates will work without having to update your app
What To Pin
Certificate
Normally the certificate is easiest to pin
At runtime, you retrieve the website or server’s certificate
You compare the retrieved certificate with the certificate embedded within the application
If the site/service rotates its certificate on a regular basis, then your application would need to be updated regularly
Public key
More flexible
A little trickier due to the extra steps necessary to extract the public key from a certificate
Its harder to work with keys since you must extract the key from the certificate – can be somewhat of a pain in Cocoa/CocoaTouch and OpenSSL.
As with a certificate, the program checks the extracted public key with its embedded copy of the public key
Hash
Allows you to anonymize a certificate or public key
this might be important if you application is concerned about leaking information during decompilation and reverse engineering
A digested certificate fingerprint is often available as a native API for many libraries, so its convenient to use
As these examples show, there are different implications that should be considered when implementing certificate pinning.
How to implement certificate pinning
There are three ways to implement certificate pinning on Android:
TrustManager
OkHttp
and CertificatePinner
Network Security Configuration (NSC)
Trust Manager
To use the TrustManager is a low-level and complex approach that requires multiple steps.
Add you certificate file to
/res/raw
Load that certificate into your KeyStore
InputStream inputStream = getResources().openRawResource(R.raw.my_cert)
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(inpuStream, null);
Create a
TrustManagerFactory
that trust that KeyStoreString trustManagerAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(trustManagerAlgorithm);
trustManagerFactory.init(keyStore)
Create an
SSLContext
with that TrustMananger
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
Assign the
SocketFactory
to the URLConnection
URL url = new URL("https://yourdomain.tld/");
HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory()
TrustManager
is also used for self-signed certificates, where the corresponding CA is not trusted by the device.OkHTTP and CertificatePinner
OkHttp is a popular HTTP client for Java and Android. It comes with a class that makes certificate pinning really simple: CertificatePinner. What is actually pinned is not the certificate itself, but the SHA256 hash of the public key of the certificate. This is easier to manage because of its size and it allows to add the fingerprints for backup or renewed certificates without exposing those certificates too early.
String hostname = "yourhost.tld";
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();
OkHttpClient client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
The hostname supports wildcard pattern, e.g. *.yourhost.tld. Please note that the asterisk is only allowed as the left-most label and must be the only character there.
CertificatePinner
cannot be used with self-signed certificates if that certificate is not accepted by the TrustManager
.How to get certificate Hash
There are two ways. First you can open the address in browser in find it with browser certificate viewer. The second way is to use
OpenSSL
or errors. The SHA256 hash is the fingerprint of the public key. There are multiple ways to extract it:
Use a deliberately false fingerprint and extract the correct one from the output of the exception. The code above will fail withCopy and paste the public key hash for the server certificate from the exception into the certificate pinner:
javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
Peer certificate chain:
sha256/IAEQVIalDTaJXtBfrTYjle+QqgRABR2FGQLebGybwtw=: CN=*.ice.ibmcloud.com,O=International Business Machines Corporation,L=Armonk,ST=New York,C=US
sha256/5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=: CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US
sha256/r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=: CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US
Pinned certificates for verify.ice.ibmcloud.com:
sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
String hostname = "*.ice.ibmcloud.com ";
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add(hostname, "sha256/IAEQVIalDTaJXtBfrTYjle+QqgRABR2FGQLebGybwtw=")
.build();x
Extract it from the downloaded certificate: download the certificate in the crt format and execute the following command to extract it:
openssl x509 -in <your_cert_file>.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
Use SSL Labs to run a server test. Part of the report are the SHA256 hashes of the certificates.
Network Security Configuration
Network Security Configuration is supported since Android 7.0. It allows you to declare network security settings in XML files. The following code demonstrates how to use it for certificate pinning:
Declare the usage in the application tag in your manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application android:networkSecurityConfig="@xml/network_security_config" ... >
...
</application>
</manifest>
Create that Network Security Network configuration file
res/xml/network_security_config.xml
Add security settings to the configuration file
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">ice.ibmcloud.com</domain>
<pin-set>
<pin digest="SHA-256">IAEQVIalDTaJXtBfrTYjle+QqgRABR2FGQLebGybwtw=</pin>
<pin digest="SHA-256">r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E =</pin>
</pin-set>
</domain-config>
</network-security-config>
OpenSSL Quick Help
OpenSSL is a widely-used open-source software library that provides cryptographic functions and tools for secure communication over computer networks. It supports various protocols such as SSL/TLS (Secure Sockets Layer/Transport Layer Security), which are used to establish secure connections between clients and servers.
Getting Certificates
We have two format for storing certificates. PEM and DER. The DER format is binary representation and PEM format is hex one.
To getting certificates:
openssl s_client -showcerts -connect ifconfig.io:443
To storing certificate for further investigation just do this:
Retrieving Certificate Fingerprint
openssl x509 -fingerprint -in cer.pem -inform pem
In order to don’t print the certificate you can use
-noout
switch.Getting fingerprint in different digest:
Extracting Certificate Public Key
openssl x509 -in cer.pem -inform pem -pubkey
If you want just public key just add
-noout
switch.To store public key:
openssl x509 -in cer.pem -inform pem -pubkey -noout > pub.key
Extracting Fingerprint of Public Key
First we need to get public key in binary format(DER):
Then:
openssl rsa -in pub.key -pubin -outform der | openssl dgst -sha1
In order to extract public key digest in base64:
There is another problem in network security configuration
The default configuration used by all connections whose destination is not covered by a domain-config. Any values that are not set use the platform default values.
The default configuration for apps targeting Android 9 (API level 28) and higher is as follows:
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
The default configuration for apps targeting Android 7.0 (API level 24) to Android 8.1 (API level 27) is as follows:
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
The default configuration for apps targeting Android 6.0 (API level 23) and lower is as follows:
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
Therefore we have 2 approach. First put our certificate on the system partition. Second use android 6 network configuration on the app.
How to extract burp public key fingerprint
First we need to download burp certificate.
We Extract public key
openssl x509 -in cert.der -inform der -pubkey -noout > pub.key
Calculating public key fingerprint
openssl rsa -in pub.key -pubin -outform der | openssl dgst -sha256