Mobile Security via Flutter — Ep.1 SSL Pinning

Amorn Apichattanakul
KBTG Life
Published in
6 min readMar 24, 2021

Due to the COVID-19 pandemic, we’re seeing high increase in mobile app’s usage. Developers have to constantly keep up with new features or improve better user experience. With everything going online now, more and more money is pouring into this industry, which naturally bad guys (a.k.a. bad hackers) are looking to take advantage of it. I’m sure we’re all aware of design thinking and how to empathize with the user because how else would you expect users to love our products if we never use it, or worse, like it? It’s time to put yourself in someone else’s shoes. In KBTG, we have a procedure called “dogfooding”. Some of you might have heard the idiom, “eat your own dogfood”. It means you have to use your product, stay with your product, and love your product before others can love it. Some might think what users prefer are cutting-edge features or cool designs but for at KBTG, we don’t stop at just two of these.

Security is another big component that we empathize with users. They trust us by providing us with their valuable private information, so it’s our honor and responsibility to equip our system with complete protections from insiders and outsiders. By insiders, we mean developers like us. For data privacy reasons, we don’t have the ability to look into our database or see user’s information at all. Everything is encrypted and nobody in our team can have access to said key. This procedure shows that how much we care about our user. The hard part for us is when we encounter an issue, it’s tough to investigate since we have no access to raw data to fix it, but we embrace this challenge because we are proud to preserve our user privacy.

In this series, I will share the knowledge that I got from working in the banking industry, which I strongly believe embodies the field of the highest security. My guide is not that hard. I just follow the standard set by OWASP.

I will show you how to implement this in Flutter. We’re not trying to make an app with super security just yet but this will be the guide for basic, must-have security for all mobile applications. The implementation is not too complicated. It might take you around 10 days to develop. Yes, just 10 days! Compared to your feature that usually take up to 80–90 days, it almost sounds like nothing. Let’s begin our first episode of the series with SSL Pinning.

SSL Pinning

SSL Pinning prevents MITM (Man in the Middle Attack), but what is that exactly?

To put simply, when you connect to a public WIFI or hotspot, the IT guy that takes care of the network, either good or bad, can get the traffic from your mobile devices to the server that you connect to. For more details, you can go to look them up on professional websites like one below. The bottom line is don’t use public WIFI or anyone else’s hotspot!!!

Credit https://www.guardsquare.com/en/blog/iOS-SSL-certificate-pinning-bypassing

Start the Implementation

If you’re looking into Stackoverflow about SSL Pinning in Flutter or Dart, you might find a solution about badCertificateCallback .

Basically, you override Flutter by telling it NOT to trust any certificates except the ones that you provide in the mobile app. Here’s how to implement.

HttpClient _client = new HttpClient(context: await globalContext);_client.badCertificateCallback =(X509Certificate cert, String host, int port) => false;var _ioClient = new IOClient(_client);_ioClient.get(url)

Create HttpClient, pass globalContext to it and assign badCertificateCallback as false.

After you get _ioClient, you can use it to call GET, POST, PUT, DELETE as you want. The code above shows that I will trust only two of these certificates, so if other certificates are sent when the mobile app has a request, it will stop working after getting badCertificateCallback .

Here’s how to get GlobalContext .

Future<SecurityContext> get globalContext async {   // Note: Not allowed to load the same certificate   final sslCert1 = await   
rootBundle.load('assets/cert/certificate.pem');
final sslCert2 = await
rootBundle.load('assets/cert/certificate2.pem');
SecurityContext sc = new SecurityContext(withTrustedRoots: false); sc.setTrustedCertificatesBytes(sslCert1.buffer.asInt8List()); sc.setTrustedCertificatesBytes(sslCert2.buffer.asInt8List());return sc;}

To get certificate.pem , I’m using this script to get the public key from the server and run this command in Terminal to get certificate.pem file. Don’t forget to change “your-url.com” to your website without HTTP or HTTPs .

openssl s_client -showcerts -connect your-url.com:443 -servername your-url.com </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > certificate.pem

Once you get certificate.pem , put your certificate into your dart assets and add your asset in pubspec.yaml . I add it in cert folder.

assets:
- assets/cert/

You can set trust certificates as many as you want but I normally set only four certificates. The reason is that the more you have, the easier you can get hacked. But why four? For Firewall, I’m using Akamai to block and filter requests before reaching your server, ridding the system of bad requests, anonymous requests, and DDoS attacks. Therefore two certificates are for Akamai, while the other two are for renewing certificates in the future. I once tried pinning the same certificate or invalid certificates and Dart threw me an error as expected.

Once we’ve done all the above, everything seems to be working. If you change the certificate on the server, the app will stop working. Woohoo! The job’s done, right? That was me at first…

…until I discover one big problem about badCertificateCallback.

It turns out that badCertificateCallback is pinning the intermediate certificate without checking Common Name, which cause a big security issue because bad hackers can create those certificates as well. In my case, for example, I have pinned Let’s Encrypt as an intermediate certificate, so if a hacker created the fake certificate and sent it to our app, it would accept that request!!! because we don’t check for Common Name and assume the certificate come from the same certificate provider.

To resolve this, I have to use another method by checking SHA256 of that certificate as well.

Future<bool> get _isAllowList async {    const myAllowList = "xxxxxxx";
final x509Cert1 = await _readPemCert('assets/cert/certificate.pem');
X509CertificateData data = X509Utils.x509CertificateFromPem(x509Cert1); return data.sha256Thumbprint == myAllowList
}

_readPemCert comes from the below function. It might not be a nice solution since I don’t know how the certificate works, so I just use string manipulation :P

Basically, I just cut other parts of the cert out to get only the last part.

Future<String> _readPemCert(String path) async {   final sslCert = await rootBundle.load(path);   final data = sslCert.buffer.asUint8List();   final pemString = utf8.decode(data);   final pemArray = pemString.split("-----END CERTIFICATE-----");   final cert = [pemArray[0], "-----END CERTIFICATE-----"].join("");   return cert;}

Then, I use libs basic_utils to parse certificates. Add this into your pubspec.yaml . I can’t parse the whole certificate in basic utils, so I have to do it this way.

basic_utils: ^2.7.0-rc.4

Use get X509Utils to get sha256 to compare with the allow list that you save as constant in Dart. Now we can double check our security with 2 methods, badCertCallback and X509Utils, to see if the certificates that we add in allow list and the Sha256 from the server is the same.

I found out a new lib about SSL pinning after I implemented my way.

I haven’t tested this one yet since my solution is accepted in penetration testing for mobile security so I decided to stick with the old solution. It might not look as clean but it works as intended, so I call it a success.

Hope that this article is useful for anyone who looking for a solution for SSL pinning in Flutter. See you in the next article!

Updated: 2022/01/10

Recently, one of my colleagues use a lib that I mentioned above in his Flutter project, it’s passed penetration testing without any problem 👍 so It’s safe to use it. I would love to upgrade to this lib as well, when I have a chance

Want to read more stories like this? Or catch up with the latest trends in the technology world? Be sure to check out our website for more at www.kbtg.tech

--

--

Amorn Apichattanakul
KBTG Life

Google Developer Expert for Flutter & Dart | Senior Flutter/iOS Software Engineer @ KBTG