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

8 min read
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.

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.

Leave a Reply