crypto: add SPKAC support
authorJason Gerfen <jason.gerfen@gmail.com>
Thu, 10 Oct 2013 20:24:53 +0000 (13:24 -0700)
committerBen Noordhuis <info@bnoordhuis.nl>
Sun, 13 Oct 2013 08:31:20 +0000 (10:31 +0200)
Implements new class 'Certificate' within crypto object for working
with SPKAC's (signed public key & challenge) natively.

lib/crypto.js
src/node_crypto.cc
src/node_crypto.h
test/fixtures/spkac.fail [new file with mode: 0644]
test/fixtures/spkac.pem [new file with mode: 0644]
test/fixtures/spkac.valid [new file with mode: 0644]
test/simple/test-crypto-certificate.js [new file with mode: 0644]

index 9cfc09e..4f4e7e1 100644 (file)
@@ -573,6 +573,30 @@ function pbkdf2(password, salt, iterations, keylen, callback) {
 }
 
 
+exports.Certificate = Certificate;
+
+function Certificate() {
+  if (!(this instanceof Certificate))
+    return new Certificate();
+
+  this._binding = new binding.Certificate();
+}
+
+
+Certificate.prototype.verifySpkac = function(object) {
+  return this._binding.verifySpkac(object);
+};
+
+
+Certificate.prototype.exportPublicKey = function(object, encoding) {
+  return this._binding.exportPublicKey(toBuf(object, encoding));
+};
+
+
+Certificate.prototype.exportChallenge = function(object, encoding) {
+  return this._binding.exportChallenge(toBuf(object, encoding));
+};
+
 
 exports.randomBytes = randomBytes;
 exports.pseudoRandomBytes = pseudoRandomBytes;
index aa08fd8..0ebcd31 100644 (file)
@@ -3581,6 +3581,186 @@ void GetHashes(const FunctionCallbackInfo<Value>& args) {
 }
 
 
+void Certificate::Initialize(Handle<Object> target) {
+  HandleScope scope(node_isolate);
+
+  Local<FunctionTemplate> t = FunctionTemplate::New(New);
+
+  t->InstanceTemplate()->SetInternalFieldCount(1);
+
+  NODE_SET_PROTOTYPE_METHOD(t, "verifySpkac", VerifySpkac);
+  NODE_SET_PROTOTYPE_METHOD(t, "exportPublicKey", ExportPublicKey);
+  NODE_SET_PROTOTYPE_METHOD(t, "exportChallenge", ExportChallenge);
+
+  target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Certificate"),
+              t->GetFunction());
+}
+
+
+void Certificate::New(const FunctionCallbackInfo<Value>& args) {
+  new Certificate(args.GetIsolate(), args.This());
+}
+
+
+bool Certificate::VerifySpkac(const char* data, unsigned int len) {
+  bool rc = false;
+  EVP_PKEY* pkey = NULL;
+  NETSCAPE_SPKI* spki = NULL;
+
+  spki = NETSCAPE_SPKI_b64_decode(data, len);
+  if (spki == NULL)
+    goto exit;
+
+  pkey = X509_PUBKEY_get(spki->spkac->pubkey);
+  if (pkey == NULL)
+    goto exit;
+
+  rc = NETSCAPE_SPKI_verify(spki, pkey) > 0;
+
+ exit:
+  if (pkey != NULL)
+    EVP_PKEY_free(pkey);
+
+  if (spki != NULL)
+    NETSCAPE_SPKI_free(spki);
+
+  return rc;
+}
+
+
+void Certificate::VerifySpkac(const FunctionCallbackInfo<Value>& args) {
+  HandleScope scope(node_isolate);
+
+  Certificate* certificate = WeakObject::Unwrap<Certificate>(args.This());
+  bool rc = false;
+
+  if (args.Length() < 1)
+    return ThrowTypeError("Missing argument");
+
+  ASSERT_IS_BUFFER(args[0]);
+
+  size_t length = Buffer::Length(args[0]);
+  if (length == 0)
+    return args.GetReturnValue().Set(false);
+
+  const char* data = Buffer::Data(args[0]);
+  assert(data != NULL);
+
+  rc = certificate->VerifySpkac(data, length) > 0;
+
+  args.GetReturnValue().Set(rc);
+}
+
+
+const char* Certificate::ExportPublicKey(const char* data, unsigned int len) {
+  char* buf = NULL;
+  EVP_PKEY* pkey = NULL;
+  NETSCAPE_SPKI* spki = NULL;
+
+  BIO* bio = BIO_new(BIO_s_mem());
+  if (bio == NULL)
+    goto exit;
+
+  spki = NETSCAPE_SPKI_b64_decode(data, len);
+  if (spki == NULL)
+    goto exit;
+
+  pkey = NETSCAPE_SPKI_get_pubkey(spki);
+  if (pkey == NULL)
+    goto exit;
+
+  if (PEM_write_bio_PUBKEY(bio, pkey) <= 0)
+    goto exit;
+
+  BUF_MEM* ptr;
+  BIO_get_mem_ptr(bio, &ptr);
+
+  buf = new char[ptr->length];
+  memcpy(buf, ptr->data, ptr->length);
+
+ exit:
+  if (pkey != NULL)
+    EVP_PKEY_free(pkey);
+
+  if (spki != NULL)
+    NETSCAPE_SPKI_free(spki);
+
+  if (bio != NULL)
+    BIO_free_all(bio);
+
+  return buf;
+}
+
+
+void Certificate::ExportPublicKey(const FunctionCallbackInfo<Value>& args) {
+  HandleScope scope(node_isolate);
+
+  Certificate* certificate = WeakObject::Unwrap<Certificate>(args.This());
+
+  if (args.Length() < 1)
+    return ThrowTypeError("Missing argument");
+
+  ASSERT_IS_BUFFER(args[0]);
+
+  size_t length = Buffer::Length(args[0]);
+  if (length == 0)
+    return args.GetReturnValue().SetEmptyString();
+
+  char* data = Buffer::Data(args[0]);
+  assert(data != NULL);
+
+  const char* pkey = certificate->ExportPublicKey(data, length);
+  if (pkey == NULL)
+    return args.GetReturnValue().SetEmptyString();
+
+  Local<Value> out = Encode(pkey, strlen(pkey), BUFFER);
+
+  delete[] pkey;
+
+  args.GetReturnValue().Set(out);
+}
+
+
+const char* Certificate::ExportChallenge(const char* data, unsigned int len) {
+  NETSCAPE_SPKI* sp = NULL;
+
+  sp = NETSCAPE_SPKI_b64_decode(data, len);
+  if (sp == NULL)
+    return NULL;
+
+  return reinterpret_cast<const char*>(ASN1_STRING_data(sp->spkac->challenge));
+}
+
+
+void Certificate::ExportChallenge(const FunctionCallbackInfo<Value>& args) {
+  HandleScope scope(node_isolate);
+
+  Certificate* crt = WeakObject::Unwrap<Certificate>(args.This());
+
+  if (args.Length() < 1)
+    return ThrowTypeError("Missing argument");
+
+  ASSERT_IS_BUFFER(args[0]);
+
+  size_t len = Buffer::Length(args[0]);
+  if (len == 0)
+    return args.GetReturnValue().SetEmptyString();
+
+  char* data = Buffer::Data(args[0]);
+  assert(data != NULL);
+
+  const char* cert = crt->ExportChallenge(data, len);
+  if (cert == NULL)
+    return args.GetReturnValue().SetEmptyString();
+
+  Local<Value> outString = Encode(cert, strlen(cert), BUFFER);
+
+  delete[] cert;
+
+  args.GetReturnValue().Set(outString);
+}
+
+
 void InitCryptoOnce() {
   SSL_library_init();
   OpenSSL_add_all_algorithms();
@@ -3621,6 +3801,7 @@ void InitCrypto(Handle<Object> target,
   Hash::Initialize(env, target);
   Sign::Initialize(env, target);
   Verify::Initialize(env, target);
+  Certificate::Initialize(target);
 
   NODE_SET_METHOD(target, "PBKDF2", PBKDF2);
   NODE_SET_METHOD(target, "randomBytes", RandomBytes<false>);
index 9d8ade3..ff60a88 100644 (file)
@@ -533,6 +533,26 @@ class DiffieHellman : public WeakObject {
   DH* dh;
 };
 
+class Certificate : public WeakObject {
+ public:
+  static void Initialize(v8::Handle<v8::Object> target);
+
+  v8::Handle<v8::Value> CertificateInit(const char* sign_type);
+  bool VerifySpkac(const char* data, unsigned int len);
+  const char* ExportPublicKey(const char* data, unsigned int len);
+  const char* ExportChallenge(const char* data, unsigned int len);
+
+ protected:
+  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
+  static void VerifySpkac(const v8::FunctionCallbackInfo<v8::Value>& args);
+  static void ExportPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
+  static void ExportChallenge(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+  Certificate(v8::Isolate* isolate, v8::Local<v8::Object> wrap)
+    : WeakObject(isolate, wrap) {
+  }
+};
+
 bool EntropySource(unsigned char* buffer, size_t length);
 void InitCrypto(v8::Handle<v8::Object> target);
 
diff --git a/test/fixtures/spkac.fail b/test/fixtures/spkac.fail
new file mode 100644 (file)
index 0000000..4dc12f8
--- /dev/null
@@ -0,0 +1 @@
+MIIB/FAILDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA3L0IfUijj7+A8CPC8EmhcdNoe5fUAog7OrBdhn7EkxFButUp40P7+LiYiygYG1TmoI/a5EgsLU3s9twEz3hmgY9mYIqb/rb+SF8qlD/K6KVyUORC7Wlz1Df4L8O3DuRGzx6/+3jIW6cPBpfgH1sVuYS1vDBsP/gMMIxwTsKJ4P0CAwEAARYkZmI5YWI4MTQtNjY3Ny00MmE0LWE2MGMtZjkwNWQxYTY5MjRkMA0GCSqGSIb3DQEBBQUAA4GBADu1U9t3eY9O3WOofp1RHX2rkh0TPs1CeS+sNdWUSDmdV5ifaGdeXpDikEnh4QIUIeZehxwgy2EjiZqMjMJHF++KPTzfAnHuuEtpDmIGzBnodZa1qt322iGZwgREvacv78GxJBJvPP3KLe+EDDsERG1aWLJoSZRusadacdzNmdV4
diff --git a/test/fixtures/spkac.pem b/test/fixtures/spkac.pem
new file mode 100644 (file)
index 0000000..4ad1cdd
--- /dev/null
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcvQh9SKOPv4DwI8LwSaFx02h7
+l9QCiDs6sF2GfsSTEUG61SnjQ/v4uJiLKBgbVOagj9rkSCwtTez23ATPeGaBj2Zg
+ipv+tv5IXyqUP8ropXJQ5ELtaXPUN/gvw7cO5EbPHr/7eMhbpw8Gl+AfWxW5hLW8
+MGw/+AwwjHBOwong/QIDAQAB
+-----END PUBLIC KEY-----
diff --git a/test/fixtures/spkac.valid b/test/fixtures/spkac.valid
new file mode 100644 (file)
index 0000000..6bfe5bb
--- /dev/null
@@ -0,0 +1 @@
+MIIBXjCByDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA3L0IfUijj7+A8CPC8EmhcdNoe5fUAog7OrBdhn7EkxFButUp40P7+LiYiygYG1TmoI/a5EgsLU3s9twEz3hmgY9mYIqb/rb+SF8qlD/K6KVyUORC7Wlz1Df4L8O3DuRGzx6/+3jIW6cPBpfgH1sVuYS1vDBsP/gMMIxwTsKJ4P0CAwEAARYkZmI5YWI4MTQtNjY3Ny00MmE0LWE2MGMtZjkwNWQxYTY5MjRkMA0GCSqGSIb3DQEBBQUAA4GBADu1U9t3eY9O3WOofp1RHX2rkh0TPs1CeS+sNdWUSDmdV5ifaGdeXpDikEnh4QIUIeZehxwgy2EjiZqMjMJHF++KPTzfAnHuuEtpDmIGzBnodZa1qt322iGZwgREvacv78GxJBJvPP3KLe+EDDsERG1aWLJoSZRusadacdzNmdV4
diff --git a/test/simple/test-crypto-certificate.js b/test/simple/test-crypto-certificate.js
new file mode 100644 (file)
index 0000000..60fedee
--- /dev/null
@@ -0,0 +1,54 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var common = require('../common');
+var assert = require('assert');
+
+var crypto = require('crypto');
+
+crypto.DEFAULT_ENCODING = 'buffer';
+
+var fs = require('fs');
+var path = require('path');
+
+// Test Certificates
+var spkacValid = fs.readFileSync(common.fixturesDir + '/spkac.valid');
+var spkacFail = fs.readFileSync(common.fixturesDir + '/spkac.fail');
+var spkacPem = fs.readFileSync(common.fixturesDir + '/spkac.pem');
+
+var certificate = new crypto.Certificate();
+
+assert.equal(certificate.verifySpkac(spkacValid), true);
+assert.equal(certificate.verifySpkac(spkacFail), false);
+
+assert.equal(stripLineEndings(certificate.exportPublicKey(spkacValid)
+                                                         .toString('utf8')),
+                        stripLineEndings(spkacPem.toString('utf8')));
+assert.equal(certificate.exportPublicKey(spkacFail), '');
+
+assert.equal(certificate.exportChallenge(spkacValid)
+                        .toString('utf8'),
+                        'fb9ab814-6677-42a4-a60c-f905d1a6924d');
+assert.equal(certificate.exportChallenge(spkacFail), '');
+
+function stripLineEndings(obj) {
+  return obj.replace(/\n/g, '');
+}