From 7bf46ba4cedefe8d3b6548a314ad64fc0cfd89f1 Mon Sep 17 00:00:00 2001 From: Jason Gerfen Date: Thu, 10 Oct 2013 13:24:53 -0700 Subject: [PATCH] crypto: add SPKAC support Implements new class 'Certificate' within crypto object for working with SPKAC's (signed public key & challenge) natively. --- lib/crypto.js | 24 +++++ src/node_crypto.cc | 185 +++++++++++++++++++++++++++++++++ src/node_crypto.h | 20 ++++ test/fixtures/spkac.fail | 1 + test/fixtures/spkac.pem | 6 ++ test/fixtures/spkac.valid | 1 + test/simple/test-crypto-certificate.js | 54 ++++++++++ 7 files changed, 291 insertions(+) create mode 100644 test/fixtures/spkac.fail create mode 100644 test/fixtures/spkac.pem create mode 100644 test/fixtures/spkac.valid create mode 100644 test/simple/test-crypto-certificate.js diff --git a/lib/crypto.js b/lib/crypto.js index 9cfc09e..4f4e7e1 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -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; diff --git a/src/node_crypto.cc b/src/node_crypto.cc index aa08fd8..43a184b 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -3581,6 +3581,190 @@ void GetHashes(const FunctionCallbackInfo& args) { } +void Certificate::Initialize(Handle target) { + HandleScope scope(node_isolate); + + Local 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& args) { + new Certificate(args.GetIsolate(), args.This()); +} + + +bool Certificate::VerifySpkac(const char* data, unsigned int len) { + bool i = 0; + 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; + + i = NETSCAPE_SPKI_verify(spki, pkey) > 0; + + exit: + if (pkey != NULL) + EVP_PKEY_free(pkey); + + if (spki != NULL) + NETSCAPE_SPKI_free(spki); + + return i; +} + + +void Certificate::VerifySpkac(const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); + + Certificate* certificate = WeakObject::Unwrap(args.This()); + bool i = 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(i); + + char* data = Buffer::Data(args[0]); + assert(data != NULL); + + i = certificate->VerifySpkac(data, length) > 0; + + args.GetReturnValue().Set(i); +} + + +const char* Certificate::ExportPublicKey(const char* data, 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; + + BIO_write(bio, "\0", 1); + 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& args) { + HandleScope scope(node_isolate); + + Certificate* certificate = WeakObject::Unwrap(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 out = Encode(pkey, strlen(pkey), BUFFER); + + delete[] pkey; + + args.GetReturnValue().Set(out); +} + + +const char* Certificate::ExportChallenge(const char* data, int len) { + NETSCAPE_SPKI* sp = NULL; + + sp = NETSCAPE_SPKI_b64_decode(data, len); + if (sp == NULL) + return NULL; + + const char* buf = NULL; + buf = reinterpret_cast(ASN1_STRING_data(sp->spkac->challenge)); + + return buf; +} + + +void Certificate::ExportChallenge(const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); + + Certificate* crt = WeakObject::Unwrap(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 outString = Encode(cert, strlen(cert), BUFFER); + + delete[] cert; + + args.GetReturnValue().Set(outString); +} + + void InitCryptoOnce() { SSL_library_init(); OpenSSL_add_all_algorithms(); @@ -3621,6 +3805,7 @@ void InitCrypto(Handle 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); diff --git a/src/node_crypto.h b/src/node_crypto.h index 9d8ade3..772db95 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -533,6 +533,26 @@ class DiffieHellman : public WeakObject { DH* dh; }; +class Certificate : public WeakObject { + public: + static void Initialize(v8::Handle target); + + v8::Handle CertificateInit(const char* sign_type); + bool VerifySpkac(const char* data, unsigned int len); + const char* ExportPublicKey(const char* data, int len); + const char* ExportChallenge(const char* data, int len); + + protected: + static void New(const v8::FunctionCallbackInfo& args); + static void VerifySpkac(const v8::FunctionCallbackInfo& args); + static void ExportPublicKey(const v8::FunctionCallbackInfo& args); + static void ExportChallenge(const v8::FunctionCallbackInfo& args); + + Certificate(v8::Isolate* isolate, v8::Local wrap) + : WeakObject(isolate, wrap) { + } +}; + bool EntropySource(unsigned char* buffer, size_t length); void InitCrypto(v8::Handle target); diff --git a/test/fixtures/spkac.fail b/test/fixtures/spkac.fail new file mode 100644 index 0000000..4dc12f8 --- /dev/null +++ b/test/fixtures/spkac.fail @@ -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 index 0000000..4ad1cdd --- /dev/null +++ b/test/fixtures/spkac.pem @@ -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 index 0000000..6bfe5bb --- /dev/null +++ b/test/fixtures/spkac.valid @@ -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 index 0000000..60fedee --- /dev/null +++ b/test/simple/test-crypto-certificate.js @@ -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, ''); +} -- 2.7.4