From c2ce8d05474c38c503b6ac57e94366421c960762 Mon Sep 17 00:00:00 2001 From: Anton Gerasimov Date: Mon, 5 Aug 2019 12:03:23 +0200 Subject: tls: add option for private keys for OpenSSL engines Add `privateKeyIdentifier` and `privateKeyEngine` options to get private key from an OpenSSL engine in tls.createSecureContext(). PR-URL: https://github.com/nodejs/node/pull/28973 Reviewed-By: Rod Vagg Reviewed-By: James M Snell Reviewed-By: Sam Roberts --- test/addons/openssl-key-engine/binding.gyp | 25 ++++++++ test/addons/openssl-key-engine/test.js | 62 ++++++++++++++++++ test/addons/openssl-key-engine/testkeyengine.cc | 73 ++++++++++++++++++++++ .../test-tls-keyengine-invalid-arg-type.js | 23 +++++++ test/parallel/test-tls-keyengine-unsupported.js | 34 ++++++++++ 5 files changed, 217 insertions(+) create mode 100644 test/addons/openssl-key-engine/binding.gyp create mode 100644 test/addons/openssl-key-engine/test.js create mode 100644 test/addons/openssl-key-engine/testkeyengine.cc create mode 100644 test/parallel/test-tls-keyengine-invalid-arg-type.js create mode 100644 test/parallel/test-tls-keyengine-unsupported.js (limited to 'test') diff --git a/test/addons/openssl-key-engine/binding.gyp b/test/addons/openssl-key-engine/binding.gyp new file mode 100644 index 0000000000..6f9a8c32c1 --- /dev/null +++ b/test/addons/openssl-key-engine/binding.gyp @@ -0,0 +1,25 @@ +{ + 'targets': [ + { + 'target_name': 'testkeyengine', + 'type': 'none', + 'includes': ['../common.gypi'], + 'conditions': [ + ['OS=="mac" and ' + 'node_use_openssl=="true" and ' + 'node_shared=="false" and ' + 'node_shared_openssl=="false"', { + 'type': 'shared_library', + 'sources': [ 'testkeyengine.cc' ], + 'product_extension': 'engine', + 'include_dirs': ['../../../deps/openssl/openssl/include'], + 'link_settings': { + 'libraries': [ + '../../../../out/<(PRODUCT_DIR)/<(openssl_product)' + ] + }, + }], + ] + } + ] +} diff --git a/test/addons/openssl-key-engine/test.js b/test/addons/openssl-key-engine/test.js new file mode 100644 index 0000000000..5c93e62636 --- /dev/null +++ b/test/addons/openssl-key-engine/test.js @@ -0,0 +1,62 @@ +'use strict'; +const common = require('../../common'); +const fixture = require('../../common/fixtures'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const fs = require('fs'); +const path = require('path'); + +const engine = path.join(__dirname, + `/build/${common.buildType}/testkeyengine.engine`); + +if (!fs.existsSync(engine)) + common.skip('no client cert engine'); + +const assert = require('assert'); +const https = require('https'); + +const agentKey = fs.readFileSync(fixture.path('/keys/agent1-key.pem')); +const agentCert = fs.readFileSync(fixture.path('/keys/agent1-cert.pem')); +const agentCa = fs.readFileSync(fixture.path('/keys/ca1-cert.pem')); + +const serverOptions = { + key: agentKey, + cert: agentCert, + ca: agentCa, + requestCert: true, + rejectUnauthorized: true +}; + +const server = https.createServer(serverOptions, common.mustCall((req, res) => { + res.writeHead(200); + res.end('hello world'); +})).listen(0, common.localhostIPv4, common.mustCall(() => { + const clientOptions = { + method: 'GET', + host: common.localhostIPv4, + port: server.address().port, + path: '/test', + privateKeyEngine: engine, + privateKeyIdentifier: 'dummykey', + cert: agentCert, + rejectUnauthorized: false, // Prevent failing on self-signed certificates + headers: {} + }; + + const req = https.request(clientOptions, common.mustCall(function(response) { + let body = ''; + response.setEncoding('utf8'); + response.on('data', function(chunk) { + body += chunk; + }); + + response.on('end', common.mustCall(function() { + assert.strictEqual(body, 'hello world'); + server.close(); + })); + })); + + req.end(); +})); diff --git a/test/addons/openssl-key-engine/testkeyengine.cc b/test/addons/openssl-key-engine/testkeyengine.cc new file mode 100644 index 0000000000..78a4fc49a6 --- /dev/null +++ b/test/addons/openssl-key-engine/testkeyengine.cc @@ -0,0 +1,73 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include + +#ifndef ENGINE_CMD_BASE +# error did not get engine.h +#endif + +#define TEST_ENGINE_ID "testkeyengine" +#define TEST_ENGINE_NAME "dummy test key engine" + +#define PRIVATE_KEY "test/fixtures/keys/agent1-key.pem" + +namespace { + +int EngineInit(ENGINE* engine) { + return 1; +} + +int EngineFinish(ENGINE* engine) { + return 1; +} + +int EngineDestroy(ENGINE* engine) { + return 1; +} + +std::string LoadFile(const char* filename) { + std::ifstream file(filename); + return std::string(std::istreambuf_iterator(file), + std::istreambuf_iterator()); +} + +static EVP_PKEY* EngineLoadPrivkey(ENGINE* engine, const char* name, + UI_METHOD* ui_method, void* callback_data) { + if (strcmp(name, "dummykey") == 0) { + std::string key = LoadFile(PRIVATE_KEY); + BIO* bio = BIO_new_mem_buf(key.data(), key.size()); + EVP_PKEY* ret = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr); + + BIO_vfree(bio); + if (ret != nullptr) { + return ret; + } + } + + return nullptr; +} + +int bind_fn(ENGINE* engine, const char* id) { + ENGINE_set_id(engine, TEST_ENGINE_ID); + ENGINE_set_name(engine, TEST_ENGINE_NAME); + ENGINE_set_init_function(engine, EngineInit); + ENGINE_set_finish_function(engine, EngineFinish); + ENGINE_set_destroy_function(engine, EngineDestroy); + ENGINE_set_load_privkey_function(engine, EngineLoadPrivkey); + + return 1; +} + +extern "C" { + IMPLEMENT_DYNAMIC_CHECK_FN(); + IMPLEMENT_DYNAMIC_BIND_FN(bind_fn); +} + +} // anonymous namespace diff --git a/test/parallel/test-tls-keyengine-invalid-arg-type.js b/test/parallel/test-tls-keyengine-invalid-arg-type.js new file mode 100644 index 0000000000..385841a654 --- /dev/null +++ b/test/parallel/test-tls-keyengine-invalid-arg-type.js @@ -0,0 +1,23 @@ +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const tls = require('tls'); + +common.expectsError( + () => { + tls.createSecureContext({ privateKeyEngine: 0, + privateKeyIdentifier: 'key' }); + }, + { code: 'ERR_INVALID_ARG_TYPE', + message: / Received type number$/ }); + +common.expectsError( + () => { + tls.createSecureContext({ privateKeyEngine: 'engine', + privateKeyIdentifier: 0 }); + }, + { code: 'ERR_INVALID_ARG_TYPE', + message: / Received type number$/ }); diff --git a/test/parallel/test-tls-keyengine-unsupported.js b/test/parallel/test-tls-keyengine-unsupported.js new file mode 100644 index 0000000000..149fc4dc31 --- /dev/null +++ b/test/parallel/test-tls-keyengine-unsupported.js @@ -0,0 +1,34 @@ +// Flags: --expose-internals +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +// Monkey-patch SecureContext +const { internalBinding } = require('internal/test/binding'); +const binding = internalBinding('crypto'); +const NativeSecureContext = binding.SecureContext; + +binding.SecureContext = function() { + const rv = new NativeSecureContext(); + rv.setEngineKey = undefined; + return rv; +}; + +const tls = require('tls'); + +{ + common.expectsError( + () => { + tls.createSecureContext({ + privateKeyEngine: 'engine', + privateKeyIdentifier: 'key' + }); + }, + { + code: 'ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED', + message: 'Custom engines not supported by this OpenSSL' + } + ); +} -- cgit v1.2.3