CHAPTER 6
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. To avoid this, you can use a technique called certificate pinning to dramatically reduce the risk of this kind of man-in-the-middle attack. This chapter describes how to implement certificate pinning in Xamarin.Forms, making your mobile apps more secure.
You should implement certificate pinning every time you build apps that handle sensitive data and 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; a common test scenario is simulating a man-in-the-middle attack with a fake certificate.
By implementing certificate pinning, you avoid the risk of certificate replacement, and this penetration test will pass. 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. This scenario will not be covered in this chapter since the documentation from Microsoft already provides excellent coverage.
As you saw in Chapter 5, 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 hood, HttpClient relies on the System.Net.HttpWebRequest class. The behavior of the latter can be influenced by 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 2019. When you’re ready, add a new file called EndpointConfiguration.cs. Code Listing 19 shows the content for the new class.
Code Listing 19
namespace CertificatePinning { public class EndpointConfiguration { // Replace with the public key of your company's certificate public const string PUBKEY = "Y O U R V A L I D K E Y G O E S H E R E"; // Replace with a fake key you want to use for testing public const string PUBKEYFAKE = "Y O U R F A K E K E Y G O E S H E R E"; } } |
In this class, you can store both the valid and fake public keys. You can also consider encrypting the real public key for further security, or you might consider other options for storing it, such as the secure storage.
The next step is setting up the ServicePointManager class. In App.xaml.cs, add the following method (which requires a using System.Net directive):
public static void SetupCertificatePinningCheck()
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback =
ValidateServerCertificate;
}
This code assigns the ServicePointManager.SecurityProtocol with the type of protocol you want to check (Transport Layer Security v1.2 in the previous code), while ServerCertificateValidationCallback represents the action that must be executed to check if the certificate is valid. The following code demonstrates this:
// Requires the following using directives:
// using System.Net.Security;
// using System.Security.Cryptography.X509Certificates;
private static bool ValidateServerCertificate(object sender,
X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return EndpointConfiguration.PUBKEY.Replace(" ", null)
.ToUpper() == certificate?.GetPublicKeyString();
}
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. The GetPublicKeyString method returns the public key of the certificate, which you must compare to the key that has been stored inside the EndpointConfiguration class.
The next step is invoking the SetupCertificatePinningCheck method. An appropriate place for the invocation is the App class constructor so that the configuration is applied at startup. Every time you use methods from the HttpClient class, the ValidateServerCertificate method will be also called, and it will return true if the certificate’s public key is the key you are expecting.
For example, the following code will cause the previous method to be executed before GetAsync can actually access the resource:
var client = new HttpClient();
client.BaseAddress =
new Uri("https://www.mycompanywebsite.com", UriKind.Absolute);
// The following call will cause ValidateServerCertificate to be executed
// before accessing the resource
var response = await client.GetAsync("/mydata");
At this point, the ValidateServerCertificate method returns false because the certificate check failed and caused an 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, you can check its InnerException; if this is of type WebException and the value of its Status property is TrustFailure, it means that a server certificate could not be validated; therefore, it can perfectly fit in the current implementation.
Code Listing 20 demonstrates how to write code that performs a network call and intercepts different types of exceptions, including a trust failure.
Code Listing 20
var client = new HttpClient(); client.BaseAddress = new Uri("https://www.mycompanywebsite.com", UriKind.Absolute);
try { // The following call causes ValidateServerCertificate to be executed // before accessing the resource var response = await client.GetAsync("/mydata"); } catch (HttpRequestException ex) { if (ex.InnerException is WebException e && e.Status == WebExceptionStatus.TrustFailure) { // The server certificate validation failed: potential attack } else { // Other network issues } } catch (Exception) { // Other exceptions } |
In my projects, I have a service class that contains static methods to make invocations to web services and APIs. My view models call these static methods and, in case of trust failure, they send messages to the user interface through the MessagingCenter class. When notified, the user interface will show appropriate error handling options.
You might want to avoid checks for certificate pinning while debugging, at least once you have made sure and tested that it works with your app. There are several reasons for doing this. For example, your development and production environments have different certificates, and it’s not useful to check for the development certificate every time, or you want to avoid overhead while debugging other features.
I disable these checks while debugging with a simple #if preprocessor directive that is added to the ValidateServerCertificate method as follows:
private static bool ValidateServerCertificate(object sender,
X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
#if DEBUG
return true;
#endif
return EndpointConfiguration.PUBKEY.Replace(" ", null)
.ToUpper() == certificate?.GetPublicKeyString();
}
This simple addition will simplify the way you do debugging, especially on simulators, which might not support all the protocols.
When your app works with sensitive data, security is even more important. Implementing checks for certificate pinning is as easy as it is important. In this chapter, you have seen how to leverage the X509Certificate class and its methods to make this check, and you have also seen how to avoid checks while debugging to avoid overhead.
Security is an important topic for everyone in the team—certainly for developers, but also for the businesspeople that need to promote or sell apps. But security is not the only important topic, especially for the business. Another important topic is driving decisions and investments based on app usage. This is the topic of the next chapter.