Certificate Pinning In Xamarin.Forms

Securing communications between applications and services is extremely important, and mobile apps are no exception. Even if you use an encrypted channel based on HTTPS, you should never completely trust the identity of the target. For example, an attacker could easily discover the URL your application is pointing to, and put a fake certificate in the middle of the communication between an application and the server, thus intercepting the communication. This is extremely dangerous especially if the application handles sensitive data. In order to avoid this, a technique called certificate pinning can be used to dramatically reduce the risk of this kind of man-in-the-middle attack. This article describes how to implement certificate pinning in Xamarin.Forms, making your mobile apps more secure.

When Do You Need Certificate Pinning?

Generally speaking, you should implement certificate pinning every time you build apps that handle sensitive data and that call HTTPS URLs. Additionally, many enterprises make strict security checks before validating and distributing an app, even if for internal use only, including penetration tests. Penetration tests search for security holes in an application, and simulating a man-in-the-middle attack with a fake certificate is a common test scenario. By implementing certificate pinning, you avoid the risk of certificate replacement and this penetration test will pass.

What Do You Need To Implement Certificate Pinning?

In order to implement certificate pinning, you will need the valid certificate’s public key. This can be provided by your system administrator. For demonstration or development purposes, you can also create a self-signed certificate and retrieve the public key on your own. I won’t cover this scenario here, since the documentation from Microsoft has an excellent coverage here.

How It Works

In Xamarin.Forms, you typically use the System.Net.Http.HttpClient class to send requests over the network, using methods such as GetAsync, PostAsync, PutAsync, and DeleteAsync. Under the hoods, HttpClient relies on the System.Net.HttpWebRequest class. The behavior of the latter can be influenced working with the System.Net.ServicePointManager class, which can be instructed to check what kind of security protocol is being used and to validate the certificate at every Web request. For a better understanding, create a new Xamarin.Forms project in Visual Studio 2017. The attached sample solution is based on .NET Standard as the code sharing strategy. When ready, add the following class,

  1. public class EndpointConfiguration  
  2. {  
  3.     // Replace with the public key of your company's certificate  
  4.     public const string PUBKEY = "Y O U R  V A L I D  K E Y  G O E S  H E R E";  
  5.   
  6.     // Replace with a fake key you want to use for testing   
  7.     public const string PUBKEYFAKE = "Y O U R  F A K E  K E Y  G O E S  H E R E";  
  8. }  

In this class, you can store both the valid and fake public keys. In the real world, you might want to encrypt the real public key or you might consider other options to store it. The next step is setting up the ServicePointManager class. In App.xaml.cs, add the following method (which requires a using System.Net directive):

  1. public static void SetupCertificatePinningCheck()  
  2. {  
  3.      ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;  
  4.      ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate;  
  5. }  

This code assigns the ServicePointManager.SecurityProtocol with the type of protocol you want to check, while ServerCertificateValidationCallback represents the action that must be executed to check if the certificate is valid. The following code demonstrates this:

  1. // Requires the following using directives:  
  2. // using System.Net.Security;  
  3. // using System.Security.Cryptography.X509Certificates;  
  4. private static bool ValidateServerCertificate(object sender, X509Certificate certificate,   
  5.             X509Chain chain, SslPolicyErrors sslPolicyErrors)  
  6. {  
  7.      return EndpointConfiguration.PUBKEY.Replace(" "null)  
  8.             .ToUpper() == certificate?.GetPublicKeyString();  
  9. }  

The certificate is represented by an instance of the X509Certificate class, and is inferred by the underlying HttpWebRequest instance that is making the network call. Now you can simply invoke SetupCertificatePinningCheck somewhere in your code, for example in the App class’ constructor, and every time you use the HttpClient class’ methods, the ValidateServerCertificate method will be called and will return true if the certificate’s public key is the key you are expecting. For example, the following code will cause the method above to be executed before GetAsync can actually access the resource,

  1. var client = new HttpClient();  
  2. client.BaseAddress = new Uri("https://www.mycompanywebsite.com", UriKind.Absolute);  
  3.   
  4. // The following call will cause ValidateServerCertificate to be executed   
  5. // before accessing the resource  
  6. var response = await client.GetAsync("/mydata");  

At this point, ValidateServerCertificate returns false because the certificate check failed and causes a HttpRequestException to be thrown. However, this specific exception can happen for many reasons, so it is not enough to understand if it was thrown because of the certificate validation failure or because of other network issues. Luckily enough, if its InnerException is a WebException and the value of its Status property is TrustFailure, it means that a server certificate could not be validated, therefore it is perfect for our purposes. Rewriting the code as follows allows understanding if a network call failed because of the certificate validation failure or for other reasons:

  1. var client = new HttpClient();  
  2. client.BaseAddress = new Uri("https://www.mycompanywebsite.com", UriKind.Absolute);  
  3.   
  4. try  
  5. {  
  6.         // The following call will cause ValidateServerCertificate to be executed   
  7.         // before accessing the resource  
  8.         var response = await client.GetAsync("/mydata");  
  9. }  
  10. catch (HttpRequestException ex)  
  11. {  
  12.     if (ex.InnerException is WebException e && e.Status ==   
  13.         WebExceptionStatus.TrustFailure)  
  14.     {  
  15.         // The server certificate validation failed: potential attack  
  16.     }  
  17.     else  
  18.     {  
  19.         // Other network issues  
  20.     }  
  21. }  
  22. catch (Exception)  
  23. {  
  24.     // Other exceptions  
  25. }  

If you have a view model that makes network calls, you can then send a message using MessagingCenter. Send one to let views know that the operation failed.

Conclusions

Implementing certificate pinning in Xamarin.Forms is not difficult, and increases the level of security in your mobile apps. Don’t forget to use this technique whenever your app handles sensitive data over HTTPS.


Similar Articles