Security in Xamarin: certificate pinning

8 minute read

We all know security is important, but implementing security measures properly is often a difficult or obscure task. I’m trying to create awareness for this topic, for myself to gain a deeper understanding and for others to benefit from my findings along the way. And the best way to learn is by teaching, right? First up in this app security series is certificate pinning. Why would you need it and how would you implement it in a Xamarin Forms app?

A little bit of history

Just a quick note: SSL is the predecessor of TLS, but they are often used interchangeably. I’ll use TLS in this article from now on, although you can also read this as SSL if you want.

TLS for the web has become fairly familiar now. We check if there is a “Secure” or padlock icon in our address bar before entering credit card data or username and password. This padlock ensures us that we can trust the website we’re on. It looks something like this in Chrome:

SSL padlock

In fact to be really sure you would actually need to check the Certificate Authority (CA) that provided the certificate, but that’s way too complex for regular use cases and normal users. Remember the history of the DigiNotar CA and Google wildcard certificates? The padlock was still there and browsers wouldn’t show a warning, but it wasn’t using Google’s CA anymore.

Within an app, a user does not have this padlock to check if the connection is secure. There is no address bar or other OS functionality that ensures them of a proper TLS connection. The review process won’t catch this, because developers can have valid reasons to use regular HTTP connections. Checking app reviews is also not sufficient, because wrong TLS handling can get introduced in every new version. Users ultimately have to put their trust in you (yes, you,  the developer of the app) to implement this correctly.

Insecure communication is on the OWASP Mobile Top 10 for a very good reason. The OWASP website provides tips on how to prevent this, but these are guidelines. They also provide some basic examples and an extensive reasoning behind certificate pinning. Well worth the read if you want details!

What happens if I do security wrong?

Let’s first set the scene… Say you are developing an app that uses a service on a staging environment. That staging environment uses a self signed certificate issued by your companies internal CA.

Your app will give a connection error by default in this case, since it doesn’t trust the CA that is used to create the self signed certificate. You have a few options here. The option I found most often on blogs and StackOverflow is probably the easiest: disable TLS checks altogether. Most of the time this suggestion lacks a warning that this is the least secure approach to take, and should never be implemented in production code.

ServicePointManager.ServerCertificateValidationCallback += 
  (sender, certificate, chain, sslPolicyErrors) => {
  return false; // this effectively disables all certificate checks, never use this approach in production code!
}

So let’s say you did go for this approach and disabled TLS for your staging environment. Then there is a deadline coming up and some unexpected events happen just before that deadline. What could happen is that these kind of workarounds are forgotten about and stay in your code. Result: the workaround for your staging environment ends up in your production app and you now have an insecure app.

This is the time where an attacker can route your users through his own network/proxy (with Fiddler or ARP spoofing for example) and read and even manipulate all traffic that is sent to your API, even though it is sent over HTTPS. Your users won’t know this is happening, because they can’t see that your app is insecure. What happens is that this attacker can inject his own CA in the middle, and your app would just accept that. This is an example of a “man in the middle” attack. Be aware that the attacker can route all traffic through his computer, even if he does not own the router/network. This is where ARP spoofing is often used.

Let’s do security the right way

Always use valid certificates and don’t disable the TLS checks that are provided by default. Also don’t override Apple’s ATS (App Transport Security) policy unless strictly required, it’s enabled by default for a reason.

In corporate environments you sometimes just have to deal with self signed certificates on non-production environments. In this case, if possible, you could opt to install/trust the CA on your device to work around the warning that the HttpClient gives you by default. Make sure your testers do the same thing.

If you can, just use a valid certificate, it will make your life a lot easier. These certificates can now even be acquired for free with services like Let’s Encrypt. So there is absolutely no reason for not using certificates in production! Your users will thank you for your effort.

Whether or not you should do certificate pinning boils down to a simple trust issue. How much trust do you put in your CA? What happens if the CA gets compromised? If you don’t have an issue with that, you could just rely on the normal certificate chain, and don’t pin to anything. OWASP tells you to always pin because “the internet is broken”, which is a must-read article if you want more detail.

Certificate pinning

Certificate pinning allows you to make sure you are talking to the exact server you meant to talk to. If you are developing a banking app, or something that contains very sensitive data, you should never put your trust in CA’s. If you don’t do certificate pinning, someone can easily see his own traffic of the app through a proxy, as long as he installs the proxy’s CA certificate on the device. Technically the attacker could also manipulate the app code (by decompiling, editing and recompiling) to circumvent your certificate pinning solution, but it makes the attackers job a lot harder. In most cases it’s not really an issue if someone is able to see the traffic for his own device.

When checking certificates manually, you have a few options available. Safe bets are to check for the public key or the thumbprint of the certificate. You could also validate the full certificate chain, like the OWASP best practices mention.

In Xamarin Forms apps, you can use the ServerCertificateValidationCallback property on the ServicePointManager class to handle this globally, like this:

ServicePointManager.ServerCertificateValidationCallback += 
  (sender, certificate, chain, sslPolicyErrors) => {
  // ...
}

In other Xamarin apps that don’t use Forms, you are mostly better off by implementing the platform specific alternatives. These are not handled in this article.

So which certificate do you pin to?

You also have a few options here. Certificates are acquired through a Certificate Authority, so you get a chain of certificates you could potentially pin to. The flavours can be either certificate pinning or CA pinning. We’ll use the public key of the certificates in the following examples.

The leaf certificate (certificate pinning)

The leaf is the actual certificate you are using on your API and is usually valid for a relative short amount of time (from a couple of months to 1 or 2 years). For this website, this is the leaf certificate at the time of writing:

Leaf certificate

Note that this certificate isn’t valid for a long period of time. Let’s Encrypt certificates are usually valid for a period of 90 days. Other CA’s have different, mostly longer, validity periods.

If you pin to this certificate, you have to make sure to update your app regularly so it validates new certificates. This means you also need to make sure your users update their app often or force them to update. Otherwise they will not be able to communicate with your API anymore, which would probably break your app. You can also set up some sort of grace period, where multiple public keys can be used simultaneously. You then have some overlap in validity periods while the certificates are renewed.

Never force an update on your users unless it’s absolutely necessary! If you discover a severe security issue, this is a valid reason to force an update in my opinion.

So how do you pin to the leaf certificate? This example shows pinning to the public key:

// validkeys should be a list of strings containing the trusted public keys
ServicePointManager.ServerCertificateValidationCallback += 
  (sender, certificate, chain, sslPolicyErrors) => {
  return validkeys.Contains(certificate?.GetPublicKeyString());
}

You could also use the thumbprint instead of the public key, both are equally safe.

The intermediate certificate (CA pinning)

The CA or Intermediate certificate is usually valid for a lot longer period. For this website, it’s valid until nearly 4 years in the future.

Intermediate certificate

This means you don’t have to update your app as often, or perhaps never at all to update certificate checks. However this isn’t as secure as pinning to the leaf certificate, since all certificates that are issued by the CA are trusted. OWASP does not recommend this approach, since you have now put your trust in the CA again. So in fact this doesn’t help you a lot. It would only prevent an attacker from intercepting traffic, even if he installs his own proxy CA on the device.

ServicePointManager.ServerCertificateValidationCallback += 
  (sender, certificate, chain, sslPolicyErrors) => {
  if (certificate == null)
  {
    return false;
  }

  foreach (var cert in chain.ChainPolicy.ExtraStore)
  {
    if (cert.Subject == certificate?.Issuer)
    {
      return validkeys.Contains(cert.GetPublicKeyString());
    }
  }
  
  return false;
}

In this example I check if the issuer name of the certificate matches the subject name of one of the certificates in the certificate chain (which seems to be in the ExtraStore). There are plenty of other ways you could implement this, so adjust this example to your needs.

The root certificate (CA pinning)

This gives you almost the same pro’s and con’s as pinning to the intermediate certificate. The validity period is usually longer than the intermediate certificate. In this case it’s only a few months, but it could also be a few years depending on your CA.

Root certificate

Conclusion

Always use valid certificates for your API’s, no matter what. Using certificates is free since Let’s Encrypt was introduced!

Certificate pinning is mostly solving a trust issue. How much trust do you put in the CA’s? If you want to do certificate pinning, you have to make a decision between update intervals. If you release your app often, and security is of the utmost importance, then go for leaf certificate pinning. Otherwise, if you just want an extra layer of (moderate) security, go for intermediate certificate pinning. This prevents you from the hassle of frequently updating the public keys or thumbprints, but puts your trust in CA’s once again. Remember, it’s all about trust!

If you happen to know a better way of checking certificates or how to handle certificate renewals, please let me know in the comments below.

Leave a comment