How to Do SSL Pinning via Self Generated Signed Certificates In Flutter

How to Do SSL Pinning Via Self Generated Signed Certificates In Flutter?

Earlier we have been through various articles based on flutter like Refresh an AlertDialog in flutter. So in this article, we will go through how to Do SSL Pinning Via Self Generated Signed Certificates In Flutter.

Learn how to implement SSL pinning using self-generated signed certificates in your Flutter applications. This comprehensive guide will walk you through the process of generating and installing self-signed certificates, configuring SSL pinning, and ensuring secure connections for your app’s network requests. Enhance the security of your Flutter app and protect against potential security vulnerabilities with SSL pinning. Dive into this step-by-step guide to leverage self-generated signed certificates for SSL pinning in Flutter.

How to Do SSL Pinning via Self Generated Signed Certificates In Flutter?

In order to resolve this issue we need to make some assumption listed below:

  • Your APIs are HTTPS
  • You are talking about validating a server-side self-signed HTTPS certificate
  • You are using package:http as the http client
  • No client-side certificates

package:http uses dart:io HttpClient under the hood and HttpClient has several features to allow for certificate validation.

Since a self-signed server certificate will be untrusted by the client, the client will call the badCertificateCallback allowing you to validate the server certificate yourself, for example:

HttpClient httpClient = new HttpClient()
  ..badCertificateCallback =
  ((X509Certificate cert, String host, int port) {
    // tests that cert is self signed, correct subject and correct date(s) 
    return (cert.issuer == cert.subject &&
        cert.subject == 'MySelfSignedCertCN' &&
        cert.endValidity.millisecondsSinceEpoch == 1234567890);
  });

IOClient ioClient = new IOClient(httpClient);
// use ioClient to perform get/post operations from package:http

// don't forget to call ioClient.close() when done
// note, this also closes the underlying HttpClient

You can also create a self-signed certificate every year and have the clients trust only this year’s and last year’s certificates:

import 'dart:io'
    show
        BytesBuilder,
        File,
        HttpClient,
        HttpClientRequest,
        HttpClientResponse,
        HttpHeaders,
        HttpRequest,
        HttpServer,
        InternetAddress,
        Process,
        stderr,
        stdout,
        SecurityContext;
import 'dart:convert' show utf8;

Future<void> shellCommand(String command) async {
  print('Executing command $command');
  final Process process = await Process.start('sh', ['-c', command]);
  stdout.addStream(process.stdout);
  stderr.addStream(process.stderr);
  final int exitCode = await process.exitCode;
  if (exitCode != 0) {
    throw new Exception('Process exited with status $exitCode');
  }
}

void main() async {
  // Last year's certificate:
  await shellCommand(
      'openssl req -newkey rsa:2048 -passout pass:password -keyout privatekey2018.pem -subj "/CN=localhost" -days 731 -x509 -out certificate2018.pem');
  // This year's certificate:
  await shellCommand(
      'openssl req -newkey rsa:2048 -passout pass:password -keyout privatekey2019.pem -subj "/CN=localhost" -days 731 -x509 -out certificate2019.pem');

  final SecurityContext serverSecurityContext = new SecurityContext();
  serverSecurityContext.useCertificateChainBytes(
      await new File('certificate2019.pem').readAsBytes());
  serverSecurityContext.usePrivateKey('privatekey2019.pem',
      password: 'password');
  final HttpServer httpServer = await HttpServer.bindSecure(
      InternetAddress.loopbackIPv4, 0, serverSecurityContext);
  httpServer.listen((HttpRequest request) {
    request.response.write('body1');
    request.response.close();
  });
  print('Server listening at https://localhost:${httpServer.port}/');

  print('Making request.');
  final SecurityContext clientSecurityContext =
      new SecurityContext(withTrustedRoots: false);
  clientSecurityContext.setTrustedCertificatesBytes(
      await new File('certificate2018.pem').readAsBytes());
  clientSecurityContext.setTrustedCertificatesBytes(
      await new File('certificate2019.pem').readAsBytes());
  final HttpClient httpClient = new HttpClient(context: clientSecurityContext);
  final HttpClientRequest request = await httpClient.getUrl(Uri(
      scheme: 'https', host: 'localhost', port: httpServer.port, path: '/'));
  final HttpClientResponse response = await request.close();
  final List<int> bytes = await response.fold(new BytesBuilder(),
      (BytesBuilder bytesBuilder, List<int> bytes) {
    bytesBuilder.add(bytes);
    return bytesBuilder;
  }).then((BytesBuilder bytesBuilder) => bytesBuilder.takeBytes());
  final String contenType =
      response.headers.value(HttpHeaders.contentTypeHeader) ?? '';
  print('${response.statusCode} ${response.reasonPhrase} '
      'content-type="$contenType" body="${utf8.decode(bytes)}"');

  httpServer.close(force: true);
}

Create self-signed certificates and have the client trust only those certificates, and ignore the hostname in the request.

This is useful when using Terraform to deploy the server to AWS Elastic Beanstalk.

The server binary blob must contain the certificates, yet the server’s hostname is not known until the deployment completes.

import 'dart:io'
    show
        BytesBuilder,
        File,
        HttpClient,
        HttpClientRequest,
        HttpClientResponse,
        HttpHeaders,
        HttpRequest,
        HttpServer,
        InternetAddress,
        Process,
        stderr,
        stdout,
        SecurityContext,
        X509Certificate;
import 'dart:convert' show utf8;

Future<void> shellCommand(String command) async {
  print('Executing command $command');
  final Process process = await Process.start('sh', ['-c', command]);
  stdout.addStream(process.stdout);
  stderr.addStream(process.stderr);
  final int exitCode = await process.exitCode;
  if (exitCode != 0) {
    throw new Exception('Process exited with status $exitCode');
  }
}

void main() async {
  // Last year's certificate:
  await shellCommand(
      'openssl req -newkey rsa:2048 -passout pass:password -keyout privatekey2018.pem -subj "/CN=localhost" -days 731 -x509 -out certificate2018.pem');
  // This year's certificate:
  await shellCommand(
      'openssl req -newkey rsa:2048 -passout pass:password -keyout privatekey2019.pem -subj "/CN=localhost" -days 731 -x509 -out certificate2019.pem');

  final SecurityContext serverSecurityContext = new SecurityContext();
  serverSecurityContext.useCertificateChainBytes(
      await new File('certificate2019.pem').readAsBytes());
  serverSecurityContext.usePrivateKey('privatekey2019.pem',
      password: 'password');
  final HttpServer httpServer = await HttpServer.bindSecure(
      InternetAddress.loopbackIPv4, 0, serverSecurityContext);
  httpServer.listen((HttpRequest request) {
    request.response.write('body1');
    request.response.close();
  });
  print('Server listening at https://localhost:${httpServer.port}/');

  print('Making request.');
  final SecurityContext clientSecurityContext =
      new SecurityContext(withTrustedRoots: false);
  final HttpClient httpClient = new HttpClient(context: clientSecurityContext);
  final List<String> certificatePemStrings = [
    await new File('certificate2018.pem').readAsString(),
    await new File('certificate2019.pem').readAsString()
  ];
  httpClient.badCertificateCallback =
      (X509Certificate cert, String host, int port) => certificatePemStrings
          .any((certificatePemString) => cert.pem == certificatePemString);
  final HttpClientRequest request = await httpClient.getUrl(Uri(
      scheme: 'https', host: 'localhost', port: httpServer.port, path: '/'));
  final HttpClientResponse response = await request.close();
  final List<int> bytes = await response.fold(new BytesBuilder(),
      (BytesBuilder bytesBuilder, List<int> bytes) {
    bytesBuilder.add(bytes);
    return bytesBuilder;
  }).then((BytesBuilder bytesBuilder) => bytesBuilder.takeBytes());
  final String contenType =
      response.headers.value(HttpHeaders.contentTypeHeader) ?? '';
  print('${response.statusCode} ${response.reasonPhrase} '
      'content-type="$contenType" body="${utf8.decode(bytes)}"');

  httpServer.close(force: true);
}

Create certificate authority files on a secure laptop and keep them on a removable drive in a safe.

So whenever you need a new certificate, get the removable drive and generate and sign a new server certificate.

Here’s an example of the openssl commands to run and how to configure Dart to trust only certificates signed by your certificate authority:

await shellCommand(
      'cat certificate2019.pem ca2019.certificate.pem > certificate2019.chain.pem');

  final SecurityContext serverSecurityContext = new SecurityContext();
  serverSecurityContext.useCertificateChainBytes(
      await new File('certificate2019.chain.pem').readAsBytes());
  serverSecurityContext.usePrivateKey('privatekey.pem', password: 'password');
  final HttpServer httpServer = await HttpServer.bindSecure(
      InternetAddress.loopbackIPv4, 0, serverSecurityContext);
  httpServer.listen((HttpRequest request) {
    request.response.write('body1');
    request.response.close();
  });
  print('Server listening at https://localhost:${httpServer.port}/');

  print('Making request.');
  final SecurityContext clientSecurityContext =
      new SecurityContext(withTrustedRoots: false);
  clientSecurityContext.setTrustedCertificatesBytes(
      await new File('ca2018.certificate.pem').readAsBytes());
  clientSecurityContext.setTrustedCertificatesBytes(
      await new File('ca2019.certificate.pem').readAsBytes());
  final HttpClient httpClient = new HttpClient(context: clientSecurityContext);
  final HttpClientRequest request = await httpClient.getUrl(Uri(
      scheme: 'https', host: 'localhost', port: httpServer.port, path: '/'));
  final HttpClientResponse response = await request.close();
  final List<int> bytes = await response.fold(new BytesBuilder(),
      (BytesBuilder bytesBuilder, List<int> bytes) {
    bytesBuilder.add(bytes);
    return bytesBuilder;
  }).then((BytesBuilder bytesBuilder) => bytesBuilder.takeBytes());
  final String contenType =
      response.headers.value(HttpHeaders.contentTypeHeader) ?? '';
  print('${response.statusCode} ${response.reasonPhrase} '
      'content-type="$contenType" body="${utf8.decode(bytes)}"');

  httpServer.close(force: true);
}

So you can use SSL Pinning Plugin to do this. Just put your self-signed certificate fingerprint in the call below:

await SslPinningPlugin.check(serverURL: url, headerHttp : new Map(), allowedSHA1Fingerprint: new List<String>, timeout : 50);

The call returns:

  • On success, return StringCONNECTION_SECURE
  • On error, return StringCONNECTION_INSECURE

Has Interceptor for DIO and Client for HTTP

You can also this lib:

 // Add CertificatePinningInterceptor in dio Client
  Dio getClient(String baseUrl, List<String> allowedSHAFingerprints){
      var dio =  Dio(BaseOptions(baseUrl: baseUrl))
        ..interceptors.add(CertificatePinningInterceptor(allowedSHAFingerprints));
      return dio;
  }
```

Conclusion:

Don’t forget to drop your feedback in the comments below 🙂

So in this article, How to do SSL Pinning Via Self Generated Signed Certificates In Flutter.

Keep Learning !!! Keep Fluttering !!!

FlutterAgency.com is our portal Platform dedicated to Flutter Technology and Flutter Developers. The portal is full of cool resources from Flutter like Flutter Widget Guide, Flutter Projects, Code libs and etc.

FlutterAgency.com is one of the most popular online portals dedicated to Flutter Technology and daily thousands of unique visitors come to this portal to enhance their knowledge of Flutter.

Abhishek Dhanani

Written by Abhishek Dhanani

Abhishek Dhanani, a skilled software developer with 3+ years of experience, masters Dart, JavaScript, TypeScript, and frameworks like Flutter and NodeJS. Proficient in MySQL, Firebase, and cloud platforms AWS and GCP, he delivers innovative digital solutions.

Leave a comment

Your email address will not be published. Required fields are marked *


ready to get started?

Fill out the form below and we will be in touch soon!

"*" indicates required fields

✓ Valid number ✕ Invalid number