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 useclient
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 initializeNewsApiService
that passhttp.Client
(we will change this later with Client that impelement SSLPinning) - It will get data from
NewsApiService
viaFutureBuilder
- 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