-
Notifications
You must be signed in to change notification settings - Fork 30.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Openssl clientcertengine support2 #6569
Changes from all commits
498e189
0c82a37
f2aab17
dbf47ae
9fc1a9a
7b27ab3
efe5aca
2faddc4
9fea66a
7ce6187
ddeb024
ad9139f
a52d405
e95d204
922e5da
249a46b
e72b12b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -209,6 +209,43 @@ static int CryptoPemCallback(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(Environment* env, const char* engine_id) { | ||
ENGINE* engine = ENGINE_by_id(engine_id); | ||
|
||
MarkPopErrorOnReturn mark_pop_error_on_return; | ||
|
||
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) { | ||
char errmsg[1024]; | ||
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); | ||
} | ||
env->ThrowError(errmsg); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still think moving the ThrowError out of this function is aesthetically nicer but I'll let it rest. |
||
} | ||
|
||
return engine; | ||
} | ||
#endif // !OPENSSL_NO_ENGINE | ||
|
||
void ThrowCryptoError(Environment* env, | ||
unsigned long err, // NOLINT(runtime/int) | ||
|
@@ -289,6 +326,10 @@ void SecureContext::Initialize(Environment* env, Local<Object> target) { | |
SecureContext::SetSessionTimeout); | ||
env->SetProtoMethod(t, "close", SecureContext::Close); | ||
env->SetProtoMethod(t, "loadPKCS12", SecureContext::LoadPKCS12); | ||
#ifndef OPENSSL_NO_ENGINE | ||
env->SetProtoMethod(t, "setClientCertEngine", | ||
SecureContext::SetClientCertEngine); | ||
#endif // !OPENSSL_NO_ENGINE | ||
env->SetProtoMethod(t, "getTicketKeys", SecureContext::GetTicketKeys); | ||
env->SetProtoMethod(t, "setTicketKeys", SecureContext::SetTicketKeys); | ||
env->SetProtoMethod(t, "setFreeListLength", SecureContext::SetFreeListLength); | ||
|
@@ -1024,6 +1065,46 @@ void SecureContext::LoadPKCS12(const FunctionCallbackInfo<Value>& args) { | |
} | ||
|
||
|
||
#ifndef OPENSSL_NO_ENGINE | ||
void SecureContext::SetClientCertEngine( | ||
const FunctionCallbackInfo<Value>& args) { | ||
Environment* env = Environment::GetCurrent(args); | ||
CHECK_EQ(args.Length(), 1); | ||
CHECK(args[0]->IsString()); | ||
|
||
SecureContext* sc = Unwrap<SecureContext>(args.This()); | ||
|
||
MarkPopErrorOnReturn mark_pop_error_on_return; | ||
|
||
// SSL_CTX_set_client_cert_engine does not itself 'support' multiple | ||
// calls by cleaning up before overwriting the client_cert_engine | ||
// internal context variable. | ||
// Instead of trying to fix up this problem we in turn also do not | ||
// support multiple calls to SetClientCertEngine. As this may surprise | ||
// a developer we throw him an exception. | ||
if (sc->ctx_->client_cert_engine != nullptr) { | ||
return env->ThrowError( | ||
"Multiple calls to SetClientCertEngine are not allowed"); | ||
} | ||
|
||
const node::Utf8Value engine_id(env->isolate(), args[0]); | ||
ENGINE* engine = LoadEngineById(env, *engine_id); | ||
|
||
if (engine == nullptr) { | ||
// Load failed... appropriate js exception has been scheduled. | ||
return; | ||
} | ||
|
||
int r = SSL_CTX_set_client_cert_engine(sc->ctx_, engine); | ||
// Free reference (SSL_CTX_set_client_cert_engine took it via ENGINE_init). | ||
ENGINE_free(engine); | ||
if (r == 0) { | ||
return ThrowCryptoError(env, ERR_get_error()); | ||
} | ||
} | ||
#endif // !OPENSSL_NO_ENGINE | ||
|
||
|
||
void SecureContext::GetTicketKeys(const FunctionCallbackInfo<Value>& args) { | ||
#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_get_tlsext_ticket_keys) | ||
|
||
|
@@ -5815,32 +5896,14 @@ void SetEngine(const FunctionCallbackInfo<Value>& args) { | |
unsigned int flags = args[1]->Uint32Value(); | ||
|
||
ClearErrorOnReturn clear_error_on_return; | ||
(void) &clear_error_on_return; // Silence compiler warning. | ||
|
||
// Load engine | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Style: please punctuate comments. I'll stop pointing that out from now on. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is still unfixed. It's a bit on the superfluous side as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still unfixed. :-) |
||
const node::Utf8Value engine_id(env->isolate(), args[0]); | ||
ENGINE* engine = ENGINE_by_id(*engine_id); | ||
|
||
// Engine not found, try loading dynamically | ||
if (engine == nullptr) { | ||
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; | ||
} | ||
} | ||
} | ||
ENGINE* engine = LoadEngineById(env, *engine_id); | ||
|
||
if (engine == nullptr) { | ||
int err = ERR_get_error(); | ||
if (err == 0) { | ||
char tmp[1024]; | ||
snprintf(tmp, sizeof(tmp), "Engine \"%s\" was not found", *engine_id); | ||
return env->ThrowError(tmp); | ||
} else { | ||
return ThrowCryptoError(env, err); | ||
} | ||
// Load failed... appropriate js exception has been scheduled | ||
return; | ||
} | ||
|
||
int r = ENGINE_set_default(engine, flags); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
'targets': [ | ||
{ | ||
'target_name': 'testengine', | ||
'type': 'shared_library', | ||
'product_extension': 'engine', | ||
'sources': [ 'testengine.cc' ], | ||
'include_dirs': [ '../../../deps/openssl/openssl/include' ] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this perhaps cater support for shared openssl permutations? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think for the purposes of testing it should be good to do it right now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this mean? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bnoordhuis did the windows build get fixed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jbergstroem is referring to Apropos the Windows build, no, I'm still working on that. EDIT 20160819: For posterity, support for linking against the bundled openssl on windows landed a few weeks ago. |
||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
'use strict'; | ||
const common = require('../../common'); | ||
const assert = require('assert'); | ||
const https = require('https'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
|
||
const engine = path.join(__dirname, '/build/Release/testengine.engine'); | ||
|
||
const agentKey = fs.readFileSync(path.join(common.fixturesDir, | ||
'/keys/agent1-key.pem')); | ||
const agentCert = fs.readFileSync(path.join(common.fixturesDir, | ||
'/keys/agent1-cert.pem')); | ||
const agentCa = fs.readFileSync(path.join(common.fixturesDir, | ||
'/keys/ca1-cert.pem')); | ||
|
||
const port = common.PORT; | ||
|
||
const serverOptions = { | ||
key: agentKey, | ||
cert: agentCert, | ||
ca: agentCa, | ||
requestCert: true, | ||
rejectUnauthorized: true | ||
}; | ||
|
||
var server = https.createServer(serverOptions, (req, res) => { | ||
res.writeHead(200); | ||
res.end('hello world'); | ||
}).listen(port, common.localhostIPv4, () => { | ||
var clientOptions = { | ||
method: 'GET', | ||
host: common.localhostIPv4, | ||
port: port, | ||
path: '/test', | ||
clientCertEngine: engine, // engine will provide key+cert | ||
rejectUnauthorized: false, // prevent failing on self-signed certificates | ||
headers: {} | ||
}; | ||
|
||
var req = https.request(clientOptions, common.mustCall(function(response) { | ||
var body = ''; | ||
response.setEncoding('utf8'); | ||
response.on('data', common.mustCall(function(chunk) { | ||
body += chunk; | ||
})); | ||
|
||
response.on('end', common.mustCall(function() { | ||
assert.strictEqual(body, 'hello world'); | ||
server.close(); | ||
})); | ||
})); | ||
|
||
req.end(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency and greppability, I'd use the same description here; it's slightly different from the other two.