From 6c32e155d321e806f21ee1140e94e414f4580183 Mon Sep 17 00:00:00 2001 From: Greg Hughes Date: Thu, 27 Jan 2011 00:46:51 +0000 Subject: [PATCH] Add ext_key_usage to getPeerCertificate --- src/node_crypto.cc | 16 +++ test/fixtures/keys/Makefile | 32 +++++- test/fixtures/keys/agent4-cert.pem | 15 +++ test/fixtures/keys/agent4-csr.pem | 10 ++ test/fixtures/keys/agent4-key.pem | 9 ++ test/fixtures/keys/agent4.cnf | 21 ++++ test/fixtures/keys/ca2-cert.srl | 2 +- test/simple/test-tls-ext-key-usage.js | 153 ++++++++++++++++++++++++++ 8 files changed, 254 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/keys/agent4-cert.pem create mode 100644 test/fixtures/keys/agent4-csr.pem create mode 100644 test/fixtures/keys/agent4-key.pem create mode 100644 test/fixtures/keys/agent4.cnf create mode 100644 test/simple/test-tls-ext-key-usage.js diff --git a/src/node_crypto.cc b/src/node_crypto.cc index ae9e32d2c8..54ccdabf14 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -30,6 +30,7 @@ static Persistent valid_to_symbol; static Persistent fingerprint_symbol; static Persistent name_symbol; static Persistent version_symbol; +static Persistent ext_key_usage_symbol; void SecureContext::Initialize(Handle target) { @@ -695,6 +696,20 @@ Handle Connection::GetPeerCertificate(const Arguments& args) { info->Set(fingerprint_symbol, String::New(fingerprint)); } + + STACK_OF(ASN1_OBJECT) *eku = (STACK_OF(ASN1_OBJECT) *)X509_get_ext_d2i(peer_cert, NID_ext_key_usage, NULL, NULL); + if (eku != NULL) { + Local ext_key_usage = Array::New(); + + for (int i = 0; i < sk_ASN1_OBJECT_num(eku); i++) { + memset(buf, 0, sizeof(buf)); + OBJ_obj2txt(buf, sizeof(buf) - 1, sk_ASN1_OBJECT_value(eku, i), 1); + ext_key_usage->Set(Integer::New(i), String::New(buf)); + } + + sk_ASN1_OBJECT_pop_free(eku, ASN1_OBJECT_free); + info->Set(ext_key_usage_symbol, ext_key_usage); + } X509_free(peer_cert); } @@ -2683,6 +2698,7 @@ void InitCrypto(Handle target) { fingerprint_symbol = NODE_PSYMBOL("fingerprint"); name_symbol = NODE_PSYMBOL("name"); version_symbol = NODE_PSYMBOL("version"); + ext_key_usage_symbol = NODE_PSYMBOL("ext_key_usage"); } } // namespace crypto diff --git a/test/fixtures/keys/Makefile b/test/fixtures/keys/Makefile index 3e0298003c..85b6670bf6 100644 --- a/test/fixtures/keys/Makefile +++ b/test/fixtures/keys/Makefile @@ -1,4 +1,4 @@ -all: agent1-cert.pem agent2-cert.pem agent3-cert.pem +all: agent1-cert.pem agent2-cert.pem agent3-cert.pem agent4-cert.pem # @@ -82,13 +82,39 @@ agent3-cert.pem: agent3-csr.pem ca2-cert.pem ca2-key.pem agent3-verify: agent3-cert.pem ca2-cert.pem openssl verify -CAfile ca2-cert.pem agent3-cert.pem + +# +# agent4 is signed by ca2 (client cert) +# + +agent4-key.pem: + openssl genrsa -out agent4-key.pem + +agent4-csr.pem: agent4.cnf agent4-key.pem + openssl req -new -config agent4.cnf -key agent4-key.pem -out agent4-csr.pem + +agent4-cert.pem: agent4-csr.pem ca2-cert.pem ca2-key.pem + openssl x509 -req \ + -passin "pass:password" \ + -in agent4-csr.pem \ + -CA ca2-cert.pem \ + -CAkey ca2-key.pem \ + -CAcreateserial \ + -extfile agent4.cnf \ + -extensions ext_key_usage \ + -out agent4-cert.pem + +agent4-verify: agent4-cert.pem ca2-cert.pem + openssl verify -CAfile ca2-cert.pem agent4-cert.pem + + # TODO: agent on CRL clean: rm -f *.pem *.srl -test: agent1-verify agent2-verify agent3-verify +test: agent1-verify agent2-verify agent3-verify agent4-verify -.PHONY: all clean test agent1-verify agent2-verify agent3-verify +.PHONY: all clean test agent1-verify agent2-verify agent3-verify agent4-verify diff --git a/test/fixtures/keys/agent4-cert.pem b/test/fixtures/keys/agent4-cert.pem new file mode 100644 index 0000000000..2cca36644e --- /dev/null +++ b/test/fixtures/keys/agent4-cert.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICSDCCAbGgAwIBAgIJAND4S4oV8e77MA0GCSqGSIb3DQEBBQUAMHoxCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJDQTELMAkGA1UEBxMCU0YxDzANBgNVBAoTBkpveWVu +dDEQMA4GA1UECxMHTm9kZS5qczEMMAoGA1UEAxMDY2EyMSAwHgYJKoZIhvcNAQkB +FhFyeUB0aW55Y2xvdWRzLm9yZzAeFw0xMTAxMjYyMzMzMjZaFw0xMTAyMjUyMzMz +MjZaMH0xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTELMAkGA1UEBxMCU0YxDzAN +BgNVBAoTBkpveWVudDEQMA4GA1UECxMHTm9kZS5qczEPMA0GA1UEAxMGYWdlbnQ0 +MSAwHgYJKoZIhvcNAQkBFhFyeUB0aW55Y2xvdWRzLm9yZzBcMA0GCSqGSIb3DQEB +AQUAA0sAMEgCQQDAtcMgUqWCoCMI7ACMVbykoMbXvLwNHhB1/cApRFbXUd3SgDEz +RGKrqZkcT8I1b5IlUwWVQOzN7G8LHijrb05hAgMBAAGjFzAVMBMGA1UdJQQMMAoG +CCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4GBAAjwjr91RV7xLD4j+xB4Ab0iMRx3 +fIb/vizhnWOHMXHp/CuUZcm0k2/lZqlGpLIUbhuUuglol/GyMYpL0l+4usUU5ayQ +r5vOdRI5fo6WnwAlDvpLTJxN6exB3TxRqPu5WGI5t6NIDThJChpXXTuG9Auw+Lk+ +p+Q6Te22clo/XeUj +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/agent4-csr.pem b/test/fixtures/keys/agent4-csr.pem new file mode 100644 index 0000000000..da317e3458 --- /dev/null +++ b/test/fixtures/keys/agent4-csr.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBXTCCAQcCAQAwfTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQswCQYDVQQH +EwJTRjEPMA0GA1UEChMGSm95ZW50MRAwDgYDVQQLEwdOb2RlLmpzMQ8wDQYDVQQD +EwZhZ2VudDQxIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMFwwDQYJ +KoZIhvcNAQEBBQADSwAwSAJBAMC1wyBSpYKgIwjsAIxVvKSgxte8vA0eEHX9wClE +VtdR3dKAMTNEYqupmRxPwjVvkiVTBZVA7M3sbwseKOtvTmECAwEAAaAlMCMGCSqG +SIb3DQEJBzEWExRBIGNoYWxsZW5nZSBwYXNzd29yZDANBgkqhkiG9w0BAQUFAANB +AB8lvAXSHFf+ABRubFGuTuJse8omIJ1vRXuhY345qiObEDPkSVOj4LYUjBlE6S3V +1TVdfQLBqcLJPY8zG66fjKI= +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/agent4-key.pem b/test/fixtures/keys/agent4-key.pem new file mode 100644 index 0000000000..e418a26285 --- /dev/null +++ b/test/fixtures/keys/agent4-key.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOQIBAAJBAMC1wyBSpYKgIwjsAIxVvKSgxte8vA0eEHX9wClEVtdR3dKAMTNE +YqupmRxPwjVvkiVTBZVA7M3sbwseKOtvTmECAwEAAQI/EVBDN6Q1OoconqSVaAZL +7H6FXtyWCJeq4u7pVMvPAYkxe4MQOqAYmHCQlozJBOjwfpi/09KccZ7Ssi80Tc2d +AiEA3tOQX52YHptUdW5gSm4/y8dlhfita//SPkqexECYDF8CIQDdZmQtguBMvHS/ +Mjk5ypRo0mU4G8ZGL7ML1q0GMFKdPwIgP/+VvNCfq1LDrEK6Z0ZJDndDonntHVLJ +iNiXxxgiU5MCIFwrKxszN9NaRTPvYZlod14n8JFqJqHDa8NK7J798PabAiEAlwke +T6UdRvxUZPDW5XRUVftcDygFvF05Hfrr8ziVc88= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/agent4.cnf b/test/fixtures/keys/agent4.cnf new file mode 100644 index 0000000000..5e8bbe0b4f --- /dev/null +++ b/test/fixtures/keys/agent4.cnf @@ -0,0 +1,21 @@ +[ req ] +default_bits = 1024 +days = 36500 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = Joyent +OU = Node.js +CN = agent4 +emailAddress = ry@tinyclouds.org + +[ req_attributes ] +challengePassword = A challenge password + +[ ext_key_usage ] +extendedKeyUsage = clientAuth \ No newline at end of file diff --git a/test/fixtures/keys/ca2-cert.srl b/test/fixtures/keys/ca2-cert.srl index 673c8bb737..3d7b674b3d 100644 --- a/test/fixtures/keys/ca2-cert.srl +++ b/test/fixtures/keys/ca2-cert.srl @@ -1 +1 @@ -D0F84B8A15F1EEF9 +D0F84B8A15F1EEFB diff --git a/test/simple/test-tls-ext-key-usage.js b/test/simple/test-tls-ext-key-usage.js new file mode 100644 index 0000000000..da78868c22 --- /dev/null +++ b/test/simple/test-tls-ext-key-usage.js @@ -0,0 +1,153 @@ +// There is a bug with 'openssl s_server' which makes it not flush certain +// important events to stdout when done over a pipe. Therefore we skip this +// test for all openssl versions less than 1.0.0. +if (!process.versions.openssl || + parseInt(process.versions.openssl[0]) < 1) { + console.error("Skipping due to old OpenSSL version."); + process.exit(0); +} + + +var common = require('../common'); +var join = require('path').join; +var net = require('net'); +var assert = require('assert'); +var fs = require('fs'); +var crypto = require('crypto'); +var tls = require('tls'); +var spawn = require('child_process').spawn; + +// FIXME: Avoid the common PORT as this test currently hits a C-level +// assertion error with node_g. The program aborts without HUPing +// the openssl s_server thus causing many tests to fail with +// EADDRINUSE. +var PORT = common.PORT + 5; + +var connections = 0; + +var keyfn = join(common.fixturesDir, 'keys', 'agent4-key.pem'); +var key = fs.readFileSync(keyfn).toString(); + +var certfn = join(common.fixturesDir, 'keys', 'agent4-cert.pem'); +var cert = fs.readFileSync(certfn).toString(); + +var server = spawn('openssl', ['s_server', + '-accept', PORT, + '-cert', certfn, + '-key', keyfn]); +server.stdout.pipe(process.stdout); +server.stderr.pipe(process.stdout); + + +var state = 'WAIT-ACCEPT'; + +var serverStdoutBuffer = ''; +server.stdout.setEncoding('utf8'); +server.stdout.on('data', function(s) { + serverStdoutBuffer += s; + console.error(state); + switch (state) { + case 'WAIT-ACCEPT': + if (/ACCEPT/g.test(serverStdoutBuffer)) { + // Give s_server half a second to start up. + setTimeout(startClient, 500); + state = 'WAIT-HELLO'; + } + break; + + case 'WAIT-HELLO': + if (/hello/g.test(serverStdoutBuffer)) { + + // End the current SSL connection and exit. + // See s_server(1ssl). + server.stdin.write('Q'); + + state = 'WAIT-SERVER-CLOSE'; + } + break; + + default: + break; + } +}); + + +var timeout = setTimeout(function () { + server.kill(); + process.exit(1); +}, 5000); + +var gotWriteCallback = false; +var serverExitCode = -1; + +server.on('exit', function(code) { + serverExitCode = code; + clearTimeout(timeout); +}); + + +function startClient() { + var s = new net.Stream(); + + var sslcontext = crypto.createCredentials({key: key, cert: cert}); + sslcontext.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA'); + + var pair = tls.createSecurePair(sslcontext, false); + + assert.ok(pair.encrypted.writable); + assert.ok(pair.cleartext.writable); + + pair.encrypted.pipe(s); + s.pipe(pair.encrypted); + + s.connect(PORT); + + s.on('connect', function() { + console.log('client connected'); + }); + + pair.on('secure', function() { + console.log('client: connected+secure!'); + console.log('client pair.cleartext.getPeerCertificate(): %j', + pair.cleartext.getPeerCertificate()); + + // "TLS Web Client Authentication" + assert.equal(pair.cleartext.getPeerCertificate().ext_key_usage.length, 1) + assert.equal(pair.cleartext.getPeerCertificate().ext_key_usage[0], '1.3.6.1.5.5.7.3.2') + + console.log('client pair.cleartext.getCipher(): %j', + pair.cleartext.getCipher()); + setTimeout(function() { + pair.cleartext.write('hello\r\n', function () { + gotWriteCallback = true; + }); + }, 500); + }); + + pair.cleartext.on('data', function(d) { + console.log('cleartext: %s', d.toString()); + }); + + s.on('close', function() { + console.log('client close'); + }); + + pair.encrypted.on('error', function(err) { + console.log('encrypted error: ' + err); + }); + + s.on('error', function(err) { + console.log('socket error: ' + err); + }); + + pair.on('error', function(err) { + console.log('secure error: ' + err); + }); +} + + +process.on('exit', function() { + assert.equal(0, serverExitCode); + assert.equal('WAIT-SERVER-CLOSE', state); + assert.ok(gotWriteCallback); +});