diff --git a/doc/api/errors.md b/doc/api/errors.md
index 0475cfb1e0954a..3455d03e68793e 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -631,6 +631,12 @@ Used when `Console` is instantiated without `stdout` stream or when `stdout` or
Used when the native call from `process.cpuUsage` cannot be processed properly.
+
+### ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED
+
+Used when a client certificate engine is requested that is not supported by the
+version of OpenSSL being used.
+
### ERR_CRYPTO_ECDH_INVALID_FORMAT
diff --git a/lib/_tls_common.js b/lib/_tls_common.js
index 75eb6a2ec53449..fcb6de81669e23 100644
--- a/lib/_tls_common.js
+++ b/lib/_tls_common.js
@@ -208,6 +208,18 @@ exports.createSecureContext = function createSecureContext(options, context) {
c.context.setFreeListLength(0);
}
+ if (typeof options.clientCertEngine === 'string') {
+ if (c.context.setClientCertEngine)
+ c.context.setClientCertEngine(options.clientCertEngine);
+ else
+ throw new errors.Error('ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED');
+ } else if (options.clientCertEngine != null) {
+ throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
+ 'options.clientCertEngine',
+ ['string', 'null', 'undefined'],
+ options.clientCertEngine);
+ }
+
return c;
};
diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js
index 7a7371ae9eb8f5..3e6f2b06423f44 100644
--- a/lib/_tls_wrap.js
+++ b/lib/_tls_wrap.js
@@ -816,6 +816,7 @@ function tlsConnectionListener(rawSocket) {
// - rejectUnauthorized. Boolean, default to true.
// - key. string.
// - cert: string.
+// - clientCertEngine: string.
// - ca: string or array of strings.
// - sessionTimeout: integer.
//
@@ -859,6 +860,7 @@ function Server(options, listener) {
key: this.key,
passphrase: this.passphrase,
cert: this.cert,
+ clientCertEngine: this.clientCertEngine,
ca: this.ca,
ciphers: this.ciphers,
ecdhCurve: this.ecdhCurve,
@@ -931,6 +933,8 @@ Server.prototype.setOptions = function(options) {
if (options.key) this.key = options.key;
if (options.passphrase) this.passphrase = options.passphrase;
if (options.cert) this.cert = options.cert;
+ if (options.clientCertEngine)
+ this.clientCertEngine = options.clientCertEngine;
if (options.ca) this.ca = options.ca;
if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
if (options.crl) this.crl = options.crl;
diff --git a/lib/https.js b/lib/https.js
index f6e5a533b95084..5013791fe2de25 100644
--- a/lib/https.js
+++ b/lib/https.js
@@ -160,6 +160,10 @@ Agent.prototype.getName = function getName(options) {
if (options.cert)
name += options.cert;
+ name += ':';
+ if (options.clientCertEngine)
+ name += options.clientCertEngine;
+
name += ':';
if (options.ciphers)
name += options.ciphers;
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index b898c9fdce82d8..8da4e39f19dce6 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -155,6 +155,8 @@ E('ERR_CHILD_CLOSED_BEFORE_REPLY', 'Child closed before reply received');
E('ERR_CONSOLE_WRITABLE_STREAM',
'Console expects a writable stream instance for %s');
E('ERR_CPU_USAGE', 'Unable to obtain cpu usage %s');
+E('ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED',
+ 'Custom engines not supported by this OpenSSL');
E('ERR_CRYPTO_ECDH_INVALID_FORMAT', 'Invalid ECDH format: %s');
E('ERR_CRYPTO_ENGINE_UNKNOWN', 'Engine "%s" was not found');
E('ERR_CRYPTO_FIPS_FORCED',
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
index 2a641ad38017fb..ed2782e78978a1 100644
--- a/src/node_crypto.cc
+++ b/src/node_crypto.cc
@@ -361,6 +361,41 @@ static int PasswordCallback(char *buf, int size, int rwflag, void *u) {
return 0;
}
+// Loads OpenSSL engine by engine id and returns it. The loaded engine
+// gets a reference so remember the corresponding call to ENGINE_free.
+// In case of error the appropriate js exception is scheduled
+// and nullptr is returned.
+#ifndef OPENSSL_NO_ENGINE
+static ENGINE* LoadEngineById(const char* engine_id, char (*errmsg)[1024]) {
+ MarkPopErrorOnReturn mark_pop_error_on_return;
+
+ ENGINE* engine = ENGINE_by_id(engine_id);
+
+ if (engine == nullptr) {
+ // Engine not found, try loading dynamically.
+ engine = ENGINE_by_id("dynamic");
+ if (engine != nullptr) {
+ if (!ENGINE_ctrl_cmd_string(engine, "SO_PATH", engine_id, 0) ||
+ !ENGINE_ctrl_cmd_string(engine, "LOAD", nullptr, 0)) {
+ ENGINE_free(engine);
+ engine = nullptr;
+ }
+ }
+ }
+
+ if (engine == nullptr) {
+ int err = ERR_get_error();
+ if (err != 0) {
+ ERR_error_string_n(err, *errmsg, sizeof(*errmsg));
+ } else {
+ snprintf(*errmsg, sizeof(*errmsg),
+ "Engine \"%s\" was not found", engine_id);
+ }
+ }
+
+ return engine;
+}
+#endif // !OPENSSL_NO_ENGINE
// This callback is used to avoid the default passphrase callback in OpenSSL
// which will typically prompt for the passphrase. The prompting is designed
@@ -505,6 +540,10 @@ void SecureContext::Initialize(Environment* env, Local