Securing Your Flutter App By Adding SSL Pinning

Introduction

When using HTTPS, the server automatically creates a certificate and sends it to the app. However, the app will accept any certificate it receives, making it vulnerable to a man-in-the-middle attack (MITM) where a hacker intercepts the client-server connection and adds some bad certificates that can lead to data breaching and leakage of private user information. This can be a security concern.

SSL Pinning will create trustable SSL certificate connection between the server and the client. This way, an additional validation is in place to check whether the certificate stored in the application is the same as the one used by the server. If the certificate does not match, the application can reject it.

Limitation

An SSL certificate has an expiration date and must be renewed regularly. So we have to update the app with every new certificate, even if there is no change in the our application.

If you want to learn more about TLS/SSL Pinning, i recommend you to watch this video

Setting up a Flutter project

For this tutorial we will create an simple application that fetch data from https://newsapi.org/

Create an simple http request to fetch news from web service

Step 1 Create Api Service Class

This class will have a method that will fetch article from https://newsapi.org/ and return the result as Article object

You can see full source code in my repository that i put in Summary section

import 'dart:convert';

import 'package:medium_flutter_unit_testing/constant.dart';

import 'package:medium_flutter_unit_testing/model/article.dart';
import 'package:http/http.dart' as http;
import 'package:medium_flutter_unit_testing/model/news_response.dart';

class NewsApiService {

final http.Client client;

NewsApiService(this.client);

Future<List<Article>> fetchArticle() async {

final uri = Uri.parse(
'$baseUrl/everything?q=flutter&apiKey=788576fa85e0490eacac2d580771d924');
final response = await client.get(uri);
if (response.statusCode == 200) {
return NewsResponse.fromJson(json.decode(response.body)).articles;
} else {
throw Error(); } }

}

Explanation

  • NewsApiService will have property client that will be injected later
    it will make it easier to change the implementation of our http Client when we want to use client that implement SSL Pinning.
  • FetchArticle method will try to call web service, if status code 200 (success) it will return model that converted from json body, if not it will throw Error

Step 2 Create a Home Page

We need to create home page for our flutter application that will consume NewsApiService and showing the article information

void main() {
final client = http.Client();
final apiService = NewsApiService(client);

runApp(MyApp(apiService: apiService));

}

class MyApp extends StatelessWidget {

final NewsApiService apiService;
const MyApp({super.key, required this.apiService});

Widget build(BuildContext context) {

return MaterialApp(
title: 'News App SSL Pinning',
home: Scaffold(
appBar: AppBar(
title: const Text('News App SSL Pinning'), ),

body: FutureBuilder<List<Article>>(

future: apiService.fetchArticle(), builder:

(BuildContext context, AsyncSnapshot<List<Article>> snapshot) {

if (snapshot.data != null) {
return ListView.builder( itemBuilder: (context, index) {

final article = snapshot.data![index];

return Padding(
padding: const EdgeInsets.all(8),
child: Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, children: [

Text(

article.title,

style: const TextStyle(fontWeight: FontWeight.w700),

),

Text(article.description),

], ), ), ); },

itemCount: snapshot.data?.length,

);

} else {

return Center(
child: Column( children: const [

CircularProgressIndicator(),

Text("Load data, please wait...") ], ), ); } }, ), ), ); }

}

Explanation

  • In main() we initialize NewsApiService that pass http.Client (we will change this later with Client that impelement SSLPinning)
  • It will get data from NewsApiService via FutureBuilder
  • If data exists it will show ListView with news information otherwise it will show loading indicator

Import Certificate from Website

We need to download the SSL certificate used by the server. There are two way to get SLL Certificate used by the server

  • Command Line
  • Export from Browser

Command Line

Open your terminal and type command below

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

It will get certificate from newsapi.org and save it into your desired directory & file name. For the command above the file will be stored in that directory with the name certificate.pem

Export from Browser

To download the SSL certificate from Broweser, open the endpoint or base url of the API that we call from the application and then open it in the browser, Click padlock icon -> Connection is Secure -> Certificate is Valid

After a new Ceritifc Viewer window appears, we can click details and click export button and save the certificate into our project

In this case, we will save our certificate into assets directory, so our project structure will look like below

asset - certificate.pemlibtest

pubspec.yaml

Adding SSL Pinning to Project

We already have certificate.pem the next step is we will use that certificate into our project. To do that we need to define certificate.pem into our pubspec.yaml so we can load that file and use it

Step 1 Add **certificate.pem** into **pubspec.yaml**

Add assets/certificate.pem into into flutter section in pubspec.yaml after that run flutter pub get

Step 2 Create Future to Load Certificate

Create a future method that will be used to load certificate.pem from our asset and set the certificate into SecurityContext. By setting SecurityContext with our certificate, SecurityContext will not trust the OS’s certificate, instead if will load our certificate file and trust the our certificate.

Future<SecurityContext> get globalContext async {
final sslCert = await rootBundle.load('assets/certificate.pem');
SecurityContext securityContext = SecurityContext(withTrustedRoots: false); securityContext.setTrustedCertificatesBytes(sslCert.buffer.asInt8List());

return securityContext;

}

Create a method that return http.Client in this method we will initialize HttpClient instance that will contain our ceritifcate.pem information

Future<http.Client> getSSLPinningClient() async {
HttpClient client = HttpClient(context: await globalContext);
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => false;
IOClient ioClient = IOClient(client);
return ioClient;
}

Refactor our main() method that will use getSLLPinningClient instead of http.client

Future<SecurityContext> get globalContext async {
final sslCert = await rootBundle.load('assets/certificate.pem');
SecurityContext securityContext = SecurityContext(withTrustedRoots: false); securityContext.setTrustedCertificatesBytes(sslCert.buffer.asInt8List());

return securityContext;

}

Future<http.Client> getSSLPinningClient() async {

HttpClient client = HttpClient(context: await globalContext);
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => false;
IOClient ioClient = IOClient(client);
return ioClient;}

void main() async {

WidgetsFlutterBinding.ensureInitialized();

final client = await getSSLPinningClient();

final apiService = NewsApiService(client);

runApp(MyApp(apiService: apiService));

}

Testing SSL Pinning Implementation

Use Valid Certificate

We already implement the SSL Pinning into our project, So, let’s try this out. When we are hitting newsapi.orgi with the certificate we pinned our application will be able to fetch news data and show it to our home page

Use Invalid Certificate

If we use invalid certificate or the certificate is expired we will not able to connect into our server, it wil throw the exception like below

To fix that issue we need to renew our certificate.pem file and our application will run smoothly.

Summary

SSL pinning is one of the ways to secure communication between the mobile device and the server. It explains that while HTTPS creates a certificate and sends it to the app, apps can accept any certificate they receive, making them vulnerable to man-in-the-middle attacks.

SSL pinning creates a trustable SSL certificate connection between the server and the client, adding an additional validation to check if the certificate stored in the application is the same as the one used by the server.

If you want to know more about full project you can download source code in my github repository below

- 위키
Copyright © 2011-2024 iteam. Current version is 2.137.1. UTC+08:00, 2024-11-09 00:11
浙ICP备14020137号-1 $방문자$