HTTPS is on the rise. This is a good thing. HTTPS ensures your network communication is encrypted in both directions, protecting your app’s users. If you write the code
you are basically doing the same as a user entering the URL into the browser’s address bar would do. The user agent does a handshake with the host you specified, retrieves its certificate and checks the validity of that certificate according to a few criteria, e.g., host name, expiration date, revokation, and trust chain. Let’s have a look at a certificate, in this case the one for
You can see details such as the expiration date or the host names the certificate is valid for. You can also see the certificate chain. This certificate was issued by the certificate authority “Microsoft IT TLS CA 4”. And its certificate was issued by “Baltimore CyberTrust Root”. Baltimore CyberTrust Root is in the list of trusted root certificates of most operating systems and that is why I can connect to
https://www.microsoft.com/ without getting an error message.
This root certificate list is unfortunately the weakest link in the entire HTTPS story. Most users don’t check which root certificates are installed on their machine. There have been cases where computer vendors added their own certificate to the root certificates list. And there are also numerous companies that install the company’s root certificate onto each employee’s device so they can monitor all traffic by doing a man-in-the-middle attack.
For the browser story, this is currently the only way to make sure your browser can connect to all HTTPS servers on the internet, even those it has never contacted. But for a connection from a mobile app to its backend, you usually know exactly which server you’re connecting to. From a security perspective, it is a good idea to take advantage of this.
Check which server you’re connecting to
The most effective thing you can do is to get the root certificate list out of the picture. If you control the server and the certificate on the server, you can check if the certificate is the exact certificate you are expecting. You can do this by comparing if certificate’s public key is the one you are expecting. Here’s how to do that with .NET:
This code replaces the default certificate validation with custom code. This code compares the public key of the server’s certificate with the expected pubic key of the server. This public key corresponds with the private key that is installed on the server. Since it is not possible to derive the private key from a public key, this means that we can guarantee we’re talking to that one server and not any server our operating system happens to trust.
This code has a couple of limitations you should be aware of:
- It only works for this one server. If you have multiple servers you’re connecting to with different public keys, you’ll have to differentiate her.
- This code does not account for changes to the server certificate. In the case of connecting to a server you don’t control yourself, the certificate could be changed at any point which we require an update of the app for it to work again.
- You could check other criteria, e.g., the certificate’s expiration date.
An alternative you can think about is rolling your own certificate authority and pinning not the certificate itself but the certificate authority. Even though a browser would not trust such a certificate from an unknown certificate authority, a mobile app would work just fine.
Only allow up-to-date TLS
What is commonly referred to as SSL (Secure Sockets Layer) was actually renamed to TLS (Transport Layer Security) in 1999. TLS 1.2 is the current version and TLS 1.3 is expected to be finalized soon. All older version, including SSL 2, SSL3, TLS 1.0, and TLS 1.1 should not be considered sufficiently secure anymore at this point. At this time, TLS 1.2 should be used for all communication.
When using default settings, both the client and server will allow downgrading the connection to an older standard as part of the initial handshake. This is to allow new clients to connect to old servers and to allow old clients connect to new servers. But since we’re controlling both client and server, there is no reason to allow downgrades to the not sufficiently secure TLS version 1.1 or lower. The solution is to force both the client and the server to only allow TLS 1.2 connections. With
HttpClient in .NET, this is done like this:
If you’re using Azure Websites as your backend, here’s an article on how to disable older TLS versions.
Xamarin developers will have to be aware that there are different implementations of
HttpClient available and not all support TLS 1.2 or other security features. For help on picking the right
HttpClient, I wrote a blog post entitled The many flavors of HttpClient.
Pinning both the certificate and the TLS version will make it much harder to intercept the communication between your app and its backend. It should be employed whenever making calls to a backend within the control of the app developer.
Other post in this series
- Part 1: How to handle API keys
- Part 2: Why you should be using certificate pinning
- Part 3: Inspecting your app’s traffic with Fiddler