Securing Your Flutter App By Adding SSL Pinning

Ensuring Secure Communication in Flutter Applications Using SSL Pinning Techniques

  • Introduction
  • Setting up a Flutter project
  • Import Certificate from Website
  • Adding SSL Pinning to Project
  • Testing SSL Pinning Implementation
  • Summary

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.

Setting up a Flutter project

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

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();
}
}
}
  • 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
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});

// This widget is the root of your application.
@override
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...")
],
),
);
}
},
),
),
);
}
}
  • 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
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
asset
- certificate.pem
lib
test
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

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;
}
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();
// use client that contain certificate
final client = await getSSLPinningClient();

final apiService = NewsApiService(client);

runApp(MyApp(apiService: apiService));
}

Testing SSL Pinning Implementation

Use Valid Certificate

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.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store