8

I have the following Javascript code which works fine when I run it with nodejs. However, I would like to write something similar that works with Dart. I've gone through the Dart documentation and cannot find any examples. I would be very grateful if someone could show me how to rewrite the following using Google Dart please. Many thanks in advance!

var https = require('https');
var fs = require('fs');
var url = require('url');

var uri = "https://identitysso-api.betfair.com:443/api/certlogin";
var data = 'username=xxxxxxxx&password=xxxxxxxx';
var appKey = 'xxxxxxxxxxxxxx'

var options = url.parse(uri);
options.method = 'POST';
options.headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
    'X-Application': appKey
};
options.key = fs.readFileSync('client-2048.key');
options.cert = fs.readFileSync('client-2048.crt');
options.agent = new https.Agent(options);

var req = https.request(options, function(res) {
    console.log("statusCode:", res.statusCode);
    var responseData = "";
    res.on('data', function(d) {
        responseData += d;
    });
    res.on('end', function() {
        var response = JSON.parse(responseData);
        console.log("sessionToken:", response.sessionToken.replace(/\d/g, ''));
    });
    res.on('error', function(e) {
        console.error(e);
    });
});

req.end(data);

I've got as far as the following:-

import 'dart:io';

void main() {
    var uri = "https://identitysso-api.betfair.com:443/api/certlogin";
    var data = 'username=xxxxxxxx&password=xxxxxxxx';
    var appKey = 'xxxxxxxxxxxx';
    var method = 'POST';

    HttpClient client = new HttpClient();
    client.openUrl(method,Uri.parse(uri))
    .then((HttpClientRequest request) {
        request.headers.set(HttpHeaders.CONTENT_TYPE, 'application/x-www-form-urlencoded');
        request.headers.set('X-Application', appKey);
        request.write(data);
        return request.close();
     })
     .then((HttpClientResponse response) {
    // Process the response.
     });
}

But can not find anything in the docs where it tells you how to add a certificate to HttpClient or HttpRequest?? Any help gratefully received many thanks in advance.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Pystol Pete
  • 81
  • 1
  • 1
  • 3
  • Welcome to StackOverflow. 'Give me some code' questions are discouraged on StackOverflow. Your question should demonstrate what you have tried and in what problem you run into. I am aware that using certificates is not documented well, but there are plenty of questions/answers on StackOverflow about sending HTTP requests. What's a bit tricky here is that HTTP requests work a bit differently on client and server therefore examples for the client wont work for you (http://stackoverflow.com/questions/24183716, http://stackoverflow.com/questions/22460194) – Günter Zöchbauer Aug 19 '14 at 16:54
  • http://stackoverflow.com/questions/21618416, https://code.google.com/p/dart/issues/detail?id=7541#c7, http://stackoverflow.com/questions/13964492 – Günter Zöchbauer Aug 19 '14 at 16:54
  • I understand will do shortly! Many thanks for the links. – Pystol Pete Aug 19 '14 at 17:12

2 Answers2

9

Sending a client certificate in your HTTPS request can now be done in Dart as easily as in node.js, starting with version 1.13.0. The client certificate and key, in PEM format, can be added to the default SecurityContext object, before you make your HTTPS request.

Dart has now switched to using BoringSSL, a fork of OpenSSL maintained by Google. BoringSSL uses X509 certificates (the certificates used by SSL and TLS) stored in files in PEM format. The older versions of Dart used NSS, which had its own database of certificates and keys, that was maintained with command-line tools. Dart has changed some parameters of SecureSocket methods, and added a SecurityContext class.

SecurityContext.defaultContext is an object that contains the built-in trusted roots of well-known certificate authorities, taken from Mozilla's database that they use for Firefox and NSS. So your client should use this object, and add the client certificate and private key to it, so they will be used to authenticate with the server that requests them:

Future postWithClientCertificate() async {
  var context = SecurityContext.defaultContext;
  context.useCertificateChain('client-2048.crt');
  context.usePrivateKey('client-2048.key',
                        password:'keyfile_password');
  HttpClient client = new HttpClient(context: context);

  // The rest of this code comes from your question.
  var uri = "https://identitysso-api.betfair.com:443/api/certlogin";
  var data = 'username=xxxxxxxx&password=xxxxxxxx';
  var appKey = 'xxxxxxxxxxxx';
  var method = 'POST';

  var request = await client.openUrl(method,Uri.parse(uri))
  request.headers.set(HttpHeaders.CONTENT_TYPE,
                      'application/x-www-form-urlencoded');
  request.headers.set('X-Application', appKey);
  request.write(data);
  var response = await request.close();
  // Process the response.
}

The functions SecurityContext.useCertificateChain and SecurityContext.usePrivateKey are the same ones that are used to set the server certificate and key, for a SecureServerSocket, but when the context is used for a client connection, they specify the client certificate to be sent upon request.

William Hesse
  • 151
  • 1
  • 2
3

It is possible to use a client certificates with dart:io HTTPS requests. However it is a bit complicated due to the way SSL/TLS is implemented in Dart.

Dart uses the Mozilla NSS library for SSL/TLS. NSS stores all its keys and certificates in a database. The SecureSocket.initialize call is used to select the NSS database to use. If no database is specified the builtin database will be used. The builtin database contains only trusted root certificates.

To work with client certificates you need to create a NSS database with your certificates and keys. The database is manipulated using the NSS certutil tool, which is documented here:

https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Reference/NSS_tools_:_certutil

This example creates a database with three certificates, a self signed CA, a server certificate for localhost and a client certificate. Use testtest as the password.

$ mkdir certdb
$ certutil -N -d sql:certdb
$ certutil -S -s "CN=Example CA" -n my-ca-cert -x -t "TCu,u,u" -5 sslCA -m 1234 -d sql:certdb

Answer 9 and N

$ certutil -S -s "CN=localhost" -n my-server-cert -c "my-ca-cert" -t "u,u,u" -m 730 -d sql:certdb
$ certutil -S -s "CN=sgjesse" -n my-client-cert -c "my-ca-cert" -t "Pu,u,u" -m 731 -d sql:certdb

Then you can run this server:

import "dart:async";
import "dart:io";

void serve() {
  HttpServer.bindSecure('localhost',
                        8080,
                        certificateName: 'my-server-cert',
                        requestClientCertificate: true).then((server) {
    server.listen((HttpRequest request) {
      if (request.certificate != null) {
        print('Client certificate ${request.certificate.subject}');
      } else {
        print('No client certificate');
      }
      request.response.write("Hello");
      request.response.close();
    });
  });
}

void InitializeSSL() {
  var testPkcertDatabase = Platform.script.resolve('certdb').toFilePath();
  SecureSocket.initialize(database: testPkcertDatabase,
                          password: 'testtest');
}

void main() {
  InitializeSSL();
  serve();
}

And this client:

import "dart:async";
import "dart:io";

void request() {
  HttpClient client = new HttpClient();
  client.getUrl(Uri.parse("https://localhost:8080/"))
      .then((request) => request.close())
      .then((response) {
        print('Subject: ${response.certificate.subject}');
        print('Issuer: ${response.certificate.issuer}');
        return response.transform(UTF8.decoder).join('');
      })
     .then(print);
}

void InitializeSSL() {
  var testPkcertDatabase = Platform.script.resolve('certdb').toFilePath();
  SecureSocket.initialize(database: testPkcertDatabase,
                          password: 'testtest');
}

void main() {
  InitializeSSL();
  request();
}

And hopefully the client will present the client certificate. However this relies on NSS picking the client certificate, and I am not sure of the rules for this. If you use a SecureSocket you can chose the client certificate explicitly, but that is currently not possible for HTTPS, see https://code.google.com/p/dart/issues/detail?id=8872.

Here is a client which uses SecureSocket:

import "dart:async";
import "dart:io";

void request() {
  SecureSocket.connect('localhost',
                       8080,
                       sendClientCertificate: true,
                       certificateName: 'my-client-cert').then((socket) {
    socket.write("GET / HTTP/1.0\r\n\r\n");
    socket.listen(print);
  });
}

void InitializeSSL() {
  var testPkcertDatabase = Platform.script.resolve('certdb').toFilePath();
  SecureSocket.initialize(database: testPkcertDatabase,
                          password: 'testtest');
}

void main() {
  InitializeSSL();
  request();
}

Hope that helps.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
sgjesse
  • 3,793
  • 14
  • 17
  • I need to choose the client certificate explicitly and send over https as described here, https://api.developer.betfair.com/services/webapps/docs/display/1smk3cen4v3lu3yomq5qye0ni/Non-Interactive+%28bot%29+login However, this is a one-off initialisation task. The rest of the calls to the Betfair API I can make using my appkey and the session token that is returned from the login. As a workaround for the login I can write a curl cmd according to the Betfair example and then call it via the Dart Process.run command and capture the output. That will work nicely! Many thanks for all your help! – Pystol Pete Aug 21 '14 at 21:03
  • How would this work with an actual purchased SSL certificate? I can't figure out how to configure certutil to use the 4 files I got from the SSL provider. – Steven Roose Nov 27 '14 at 00:11
  • I added a follow-up question for that here: https://stackoverflow.com/questions/27161403/how-to-setup-dart-to-use-a-ca-ssl-certificate – Steven Roose Nov 27 '14 at 00:59
  • 1
    This answer is now obsolete, with the switch to BoringSSL in Dart 1.13. The newer answer, with different code, should be used instead. – William Hesse Oct 22 '15 at 13:19