4

I need to use self signed https certificate with flutter app. Unfortunately I can't solve a problem. What ever I try, I always get this error "[VERBOSE-2:ui_dart_state.cc(199)] Unhandled Exception: HandshakeException: Handshake error in client (OS Error: CERTIFICATE_VERIFY_FAILED: application verification failure(handshake.cc:359))"

I tried self signed certificate, tried with self signed with CA - no success. Tried to downgrade server version to tls1.2 (I found some old issues about it) - does not help. This is the code:

   ByteData bytes = await rootBundle.load('ssl/certificate2019.pem');
    SecurityContext clientContext = SecurityContext();
    clientContext.setTrustedCertificatesBytes((bytes.buffer.asInt8List()));
    var httpClient = new HttpClient(context: clientContext);
    httpClient.badCertificateCallback = (X509Certificate cert, String host, int port){
       print("!!!!Bad certificate host $host $port");
       return false;
    };
   
    var uri = Uri.parse('https://localhost:443');
    final result = await httpClient.getUrl(uri).timeout(Duration(milliseconds: 3000));
    result.close();

It seems that setTrustedCertificatesBytes totaly do not make any difference with empty context, the error is same application verification failure(handshake.cc:359)

The last thing I tried is the examples from here https://github.com/dart-lang/sdk/issues/35981#issuecomment-536134040 from @M.Leonhard i suppose, which exclude mobile(flutter),because it is dart and exclude my server (server inside the example script) script but the result is the same. This is result of the dart script from example from the link above enter image description here

The example source code:

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

I can successfully do requests over https with postman with added .pem file to postman. enter image description here

Also I can successfully do a request with curl

enter image description here

I cant figure out what I made wrong... Why setTrustedCertificatesBytes does not affect? I also tried with facebook certificate (download it and convert into pem) and SecurityContext() which by default exclude all systems certificates, (withTrustedRoots is false by default) and it works well. It seems that it checks certificate inside, and can verify, but cant verify self signed certificates! But I cant understand why this examples (like above) works? I suppose they should work if it is publushed .. so I do somthing wrong but can't understand what exactly.. Will wait any help! Thanks for readind till the end)

UPDATE ----- I tried do all steps again. openssl commands I used

  1. Root Private Key

openssl genrsa -out rootCA.key 2048

  1. Root CA certificate

openssl req -x509 -new -key rootCA.key -sha256 -days 365 -out rootCA.pem

  1. Server Private key

openssl genrsa -out server.key 2048

  1. Sign request

openssl req -new -key server.key -out server.csr

  1. Sign server Cert with CA

openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -days 365 -sha256 -out server.pem

  1. Concatenate into chain

cat server.pem rootCA.pem > chain.pem

I also entered company, in some places it is mentioned as important. Dart server code (in same directory with certificates)

    void main() async {
      
      final SecurityContext serverSecurityContext = new SecurityContext();
      serverSecurityContext.useCertificateChainBytes(await new File('chain.pem').readAsBytes());
      serverSecurityContext.usePrivateKey('server.key');
      
      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}/');
    }

Request script code (in the same directory)

void main() async {
  print('Making request.');

  final SecurityContext clientSecurityContext = new SecurityContext();
  clientSecurityContext.setTrustedCertificatesBytes(await new File('rootCA.pem').readAsBytes());

  final HttpClient httpClient = new HttpClient(context: clientSecurityContext);
  final HttpClientRequest request = await httpClient.getUrl(Uri(scheme: 'https', host: 'localhost', port: 443, 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)}"');
}

Result of openssl s_client -connect localhost:55132 -CAfile rootCA.pem enter image description here

Result of request from Dart script enter image description here I got the same error( I also tried another server not Dart - the same error on client. I still sycsefully can connect with postman with rootCA.pem.
Maybe I do somthing wrong with certificates whan generate.. And I cant find this concrete error at the internet I check time it is correct.. Maybe any suggestions what else I can do?

Parafin
  • 89
  • 3
  • 7
  • When you call `setTrustedCertificateBytes` you need to pass the *signing* certificate's (i.e. CA) certificate. From the screenshot that appears to be `ca2019.certificate.pem`. You should not be passing in the server's certificate. Check the output of `openssl s_client` too. – Richard Heap Sep 01 '21 at 20:11
  • Thanks @Richard! Can ypu give some more details please? – Parafin Sep 01 '21 at 20:15
  • Change to this: `ByteData bytes = await rootBundle.load('ssl/ca2019.certificate.pem');` And, of course, include that in the assets. – Richard Heap Sep 01 '21 at 20:16
  • Thanks for you answer @Richard! It seems I wrote incomprehensibly.I tried CA but without any success, maybe miss somthing will try again.. But one thing confused me, the example above as I understand uses ca2019.certificate.pem for setTrustedCertificateBytes. I run the example without any modification but got same error.. The first screenshot is the result of examle but the third example, not first or second and as I understand that examole uses CA certs for setTrustedCertificateBytes.. but do not work for me. So I need to try server cert signed by self CA cert, correct? – Parafin Sep 01 '21 at 20:41
  • @RichardHeap I edited question and added source code of example I used – Parafin Sep 01 '21 at 20:47
  • @RichardHeap I updated question with new attempt. I generated all certs and keys again, set chain and rootCA.pem into setTrustedCertificateBytes. I still get same error, added all code and commands ans openssl s_client output. I new to all this things dart and certificates so cant figure out what I am doing wrong. Will be very graceful for help.. – Parafin Sep 02 '21 at 10:18

0 Answers0