Add ext_key_usage to getPeerCertificate
authorGreg Hughes <greg@ghughes.com>
Thu, 27 Jan 2011 00:46:51 +0000 (00:46 +0000)
committerRyan Dahl <ry@tinyclouds.org>
Thu, 27 Jan 2011 22:06:40 +0000 (14:06 -0800)
src/node_crypto.cc
test/fixtures/keys/Makefile
test/fixtures/keys/agent4-cert.pem [new file with mode: 0644]
test/fixtures/keys/agent4-csr.pem [new file with mode: 0644]
test/fixtures/keys/agent4-key.pem [new file with mode: 0644]
test/fixtures/keys/agent4.cnf [new file with mode: 0644]
test/fixtures/keys/ca2-cert.srl
test/simple/test-tls-ext-key-usage.js [new file with mode: 0644]

index ae9e32d2c832e13514a8ec3f4f94e7c9bd7da436..54ccdabf1442bdd287a2f118f72f09463459a02e 100644 (file)
@@ -30,6 +30,7 @@ static Persistent<String> valid_to_symbol;
 static Persistent<String> fingerprint_symbol;
 static Persistent<String> name_symbol;
 static Persistent<String> version_symbol;
+static Persistent<String> ext_key_usage_symbol;
 
 
 void SecureContext::Initialize(Handle<Object> target) {
@@ -695,6 +696,20 @@ Handle<Value> 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<Array> 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<Object> 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
index 3e0298003c57899d507eb3a9254f79396750ef3d..85b6670bf69dc5aeedc846b98eea2b4a416a2db1 100644 (file)
@@ -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 (file)
index 0000000..2cca366
--- /dev/null
@@ -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 (file)
index 0000000..da317e3
--- /dev/null
@@ -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 (file)
index 0000000..e418a26
--- /dev/null
@@ -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 (file)
index 0000000..5e8bbe0
--- /dev/null
@@ -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
index 673c8bb737c64b7fbb87c37914207ad04f833a7f..3d7b674b3d86d9cd69eabf4fc5651b31f184f2ff 100644 (file)
@@ -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 (file)
index 0000000..da78868
--- /dev/null
@@ -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);
+});