How to do SSL pinning via self generated signed certificates in flutter?

孤街浪徒 提交于 2019-11-26 10:05:07

问题


I am looking for SSL pinning and using self generated certificates to run our apis in flutter.


回答1:


There isn't enough detail in the question, so this answer is based on some assumptions:

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

package:http uses dart:io HttpClient under the hood, and HttpClient has a 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



回答2:


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 String "CONNECTION_SECURE"
  • On error, return String "CONNECTION_INSECURE"



回答3:


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. 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:

import 'dart:io'
    show
        BytesBuilder,
        File,
        HttpClient,
        HttpClientRequest,
        HttpClientResponse,
        HttpHeaders,
        HttpRequest,
        HttpServer,
        InternetAddress,
        Process,
        SecurityContext,
        stderr,
        stdout;
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 certificates:
  await shellCommand(
      'openssl req -newkey rsa:2048 -nodes -keyout ca2018.privatekey.pem -subj "/OU=CA" -days 731 -x509 -out ca2018.certificate.pem');
  // This year's certificates:
  await shellCommand(
      'openssl req -newkey rsa:2048 -nodes -keyout ca2019.privatekey.pem -subj "/OU=CA" -days 731 -x509 -out ca2019.certificate.pem');
  await shellCommand(
      'openssl req -newkey rsa:2048 -passout pass:password -keyout privatekey.pem -subj "/CN=localhost" -days 731 -sha256 -new -out csr2019.pem');
  await shellCommand(
      'openssl x509 -req -in csr2019.pem -CA ca2019.certificate.pem -CAkey ca2019.privatekey.pem -set_serial 1 -days 730 -sha256 -out certificate2019.pem');
  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);
}


来源:https://stackoverflow.com/questions/51323603/how-to-do-ssl-pinning-via-self-generated-signed-certificates-in-flutter

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!