diff options
author | Denys Otrishko <shishugi@gmail.com> | 2018-10-29 11:38:43 +0300 |
---|---|---|
committer | Ruben Bridgewater <ruben@bridgewater.de> | 2019-12-25 14:24:42 +0300 |
commit | f8d7e2216e5821719b5a341c41251d5a860cf5f7 (patch) | |
tree | 8f3a1db4b065a844483ee1a8e388702463683570 /src | |
parent | 3d47c8592d179991d3bfa4902f12c4fce07ac2d3 (diff) |
tls: add PSK support
Add the `pskCallback` client/server option, which resolves an identity
or identity hint to a pre-shared key.
Add the `pskIdentityHint` server option to set the identity hint for the
ServerKeyExchange message.
Co-authored-by: Chris Osborn <chris.osborn@sitelier.com>
Co-authored-by: stephank <gh@stephank.nl>
Co-authored-by: Taylor Zane Glaeser <tzglaeser@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/23188
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Diffstat (limited to 'src')
-rw-r--r-- | src/env.h | 13 | ||||
-rw-r--r-- | src/node_crypto.cc | 10 | ||||
-rw-r--r-- | src/node_errors.h | 2 | ||||
-rw-r--r-- | src/tls_wrap.cc | 134 | ||||
-rw-r--r-- | src/tls_wrap.h | 17 |
5 files changed, 171 insertions, 5 deletions
diff --git a/src/env.h b/src/env.h index c86f0f7de7b..150cdf0fb1e 100644 --- a/src/env.h +++ b/src/env.h @@ -160,11 +160,12 @@ constexpr size_t kFsStatsBufferLength = // Symbols are per-isolate primitives but Environment proxies them // for the sake of convenience. -#define PER_ISOLATE_SYMBOL_PROPERTIES(V) \ - V(handle_onclose_symbol, "handle_onclose") \ - V(no_message_symbol, "no_message_symbol") \ - V(oninit_symbol, "oninit") \ - V(owner_symbol, "owner") \ +#define PER_ISOLATE_SYMBOL_PROPERTIES(V) \ + V(handle_onclose_symbol, "handle_onclose") \ + V(no_message_symbol, "no_message_symbol") \ + V(oninit_symbol, "oninit") \ + V(owner_symbol, "owner") \ + V(onpskexchange_symbol, "onpskexchange") \ // Strings are per-isolate primitives but Environment proxies them // for the sake of convenience. Strings should be ASCII-only. @@ -254,6 +255,7 @@ constexpr size_t kFsStatsBufferLength = V(host_string, "host") \ V(hostmaster_string, "hostmaster") \ V(http_1_1_string, "http/1.1") \ + V(identity_string, "identity") \ V(ignore_string, "ignore") \ V(import_string, "import") \ V(infoaccess_string, "infoAccess") \ @@ -325,6 +327,7 @@ constexpr size_t kFsStatsBufferLength = V(priority_string, "priority") \ V(process_string, "process") \ V(promise_string, "promise") \ + V(psk_string, "psk") \ V(pubkey_string, "pubkey") \ V(query_string, "query") \ V(raw_string, "raw") \ diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 87a3e5525d1..9165e9171e6 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -2620,6 +2620,16 @@ void SSLWrap<Base>::VerifyError(const FunctionCallbackInfo<Value>& args) { if (X509* peer_cert = SSL_get_peer_certificate(w->ssl_.get())) { X509_free(peer_cert); x509_verify_error = SSL_get_verify_result(w->ssl_.get()); + } else { + const SSL_CIPHER* curr_cipher = SSL_get_current_cipher(w->ssl_.get()); + const SSL_SESSION* sess = SSL_get_session(w->ssl_.get()); + // Allow no-cert for PSK authentication in TLS1.2 and lower. + // In TLS1.3 check that session was reused because TLS1.3 PSK + // looks like session resumption. Is there a better way? + if (SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk || + (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION && + SSL_session_reused(w->ssl_.get()))) + return args.GetReturnValue().SetNull(); } if (x509_verify_error == X509_V_OK) diff --git a/src/node_errors.h b/src/node_errors.h index 6080aa93dba..74413e7456a 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -58,6 +58,7 @@ void PrintErrorString(const char* format, ...); V(ERR_STRING_TOO_LONG, Error) \ V(ERR_TLS_INVALID_PROTOCOL_METHOD, TypeError) \ V(ERR_TRANSFERRING_EXTERNALIZED_SHAREDARRAYBUFFER, TypeError) \ + V(ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED, Error) \ #define V(code, type) \ inline v8::Local<v8::Value> code(v8::Isolate* isolate, \ @@ -101,6 +102,7 @@ void PrintErrorString(const char* format, ...); "Script execution was interrupted by `SIGINT`") \ V(ERR_TRANSFERRING_EXTERNALIZED_SHAREDARRAYBUFFER, \ "Cannot serialize externalized SharedArrayBuffer") \ + V(ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED, "Failed to set PSK identity hint") \ #define V(code, message) \ inline v8::Local<v8::Value> code(v8::Isolate* isolate) { \ diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index cd7a5d59ebd..10ebc4ccd9a 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -28,6 +28,7 @@ #include "node_crypto_bio.h" // NodeBIO // ClientHelloParser #include "node_crypto_clienthello-inl.h" +#include "node_errors.h" #include "stream_base-inl.h" #include "util-inl.h" @@ -42,8 +43,11 @@ using v8::Exception; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; +using v8::Integer; using v8::Isolate; using v8::Local; +using v8::Maybe; +using v8::MaybeLocal; using v8::Object; using v8::ReadOnly; using v8::Signature; @@ -1076,6 +1080,131 @@ int TLSWrap::SelectSNIContextCallback(SSL* s, int* ad, void* arg) { return SSL_TLSEXT_ERR_OK; } +#ifndef OPENSSL_NO_PSK + +void TLSWrap::SetPskIdentityHint(const FunctionCallbackInfo<Value>& args) { + TLSWrap* p; + ASSIGN_OR_RETURN_UNWRAP(&p, args.Holder()); + CHECK_NOT_NULL(p->ssl_); + + Environment* env = p->env(); + Isolate* isolate = env->isolate(); + + CHECK(args[0]->IsString()); + node::Utf8Value hint(isolate, args[0].As<String>()); + + if (!SSL_use_psk_identity_hint(p->ssl_.get(), *hint)) { + Local<Value> err = node::ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED(isolate); + p->MakeCallback(env->onerror_string(), 1, &err); + } +} + +void TLSWrap::EnablePskCallback(const FunctionCallbackInfo<Value>& args) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + CHECK_NOT_NULL(wrap->ssl_); + + SSL_set_psk_server_callback(wrap->ssl_.get(), PskServerCallback); + SSL_set_psk_client_callback(wrap->ssl_.get(), PskClientCallback); +} + +unsigned int TLSWrap::PskServerCallback(SSL* s, + const char* identity, + unsigned char* psk, + unsigned int max_psk_len) { + TLSWrap* p = static_cast<TLSWrap*>(SSL_get_app_data(s)); + + Environment* env = p->env(); + Isolate* isolate = env->isolate(); + HandleScope scope(isolate); + + MaybeLocal<String> maybe_identity_str = + v8::String::NewFromUtf8(isolate, identity, v8::NewStringType::kNormal); + + v8::Local<v8::String> identity_str; + if (!maybe_identity_str.ToLocal(&identity_str)) return 0; + + // Make sure there are no utf8 replacement symbols. + v8::String::Utf8Value identity_utf8(isolate, identity_str); + if (strcmp(*identity_utf8, identity) != 0) return 0; + + Local<Value> argv[] = {identity_str, + Integer::NewFromUnsigned(isolate, max_psk_len)}; + + MaybeLocal<Value> maybe_psk_val = + p->MakeCallback(env->onpskexchange_symbol(), arraysize(argv), argv); + Local<Value> psk_val; + if (!maybe_psk_val.ToLocal(&psk_val) || !psk_val->IsArrayBufferView()) + return 0; + + char* psk_buf = Buffer::Data(psk_val); + size_t psk_buflen = Buffer::Length(psk_val); + + if (psk_buflen > max_psk_len) return 0; + + memcpy(psk, psk_buf, psk_buflen); + return psk_buflen; +} + +unsigned int TLSWrap::PskClientCallback(SSL* s, + const char* hint, + char* identity, + unsigned int max_identity_len, + unsigned char* psk, + unsigned int max_psk_len) { + TLSWrap* p = static_cast<TLSWrap*>(SSL_get_app_data(s)); + + Environment* env = p->env(); + Isolate* isolate = env->isolate(); + HandleScope scope(isolate); + + Local<Value> argv[] = {Null(isolate), + Integer::NewFromUnsigned(isolate, max_psk_len), + Integer::NewFromUnsigned(isolate, max_identity_len)}; + if (hint != nullptr) { + MaybeLocal<String> maybe_hint = String::NewFromUtf8(isolate, hint); + + Local<String> local_hint; + if (!maybe_hint.ToLocal(&local_hint)) return 0; + + argv[0] = local_hint; + } + MaybeLocal<Value> maybe_ret = + p->MakeCallback(env->onpskexchange_symbol(), arraysize(argv), argv); + Local<Value> ret; + if (!maybe_ret.ToLocal(&ret) || !ret->IsObject()) return 0; + Local<Object> obj = ret.As<Object>(); + + MaybeLocal<Value> maybe_psk_val = obj->Get(env->context(), env->psk_string()); + + Local<Value> psk_val; + if (!maybe_psk_val.ToLocal(&psk_val) || !psk_val->IsArrayBufferView()) + return 0; + + char* psk_buf = Buffer::Data(psk_val); + size_t psk_buflen = Buffer::Length(psk_val); + + if (psk_buflen > max_psk_len) return 0; + + MaybeLocal<Value> maybe_identity_val = + obj->Get(env->context(), env->identity_string()); + Local<Value> identity_val; + if (!maybe_identity_val.ToLocal(&identity_val) || !identity_val->IsString()) + return 0; + Local<String> identity_str = identity_val.As<String>(); + + String::Utf8Value identity_buf(isolate, identity_str); + size_t identity_len = identity_buf.length(); + + if (identity_len > max_identity_len) return 0; + + memcpy(identity, *identity_buf, identity_len); + memcpy(psk, psk_buf, psk_buflen); + + return psk_buflen; +} + +#endif void TLSWrap::GetWriteQueueSize(const FunctionCallbackInfo<Value>& info) { TLSWrap* wrap; @@ -1142,6 +1271,11 @@ void TLSWrap::Initialize(Local<Object> target, env->SetProtoMethod(t, "destroySSL", DestroySSL); env->SetProtoMethod(t, "enableCertCb", EnableCertCb); +#ifndef OPENSSL_NO_PSK + env->SetProtoMethod(t, "setPskIdentityHint", SetPskIdentityHint); + env->SetProtoMethod(t, "enablePskCallback", EnablePskCallback); +#endif + StreamBase::AddMethods(env, t); SSLWrap<TLSWrap>::AddMethods(env, t); diff --git a/src/tls_wrap.h b/src/tls_wrap.h index 14b7327e7d8..7bb33b4a3cb 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -169,6 +169,23 @@ class TLSWrap : public AsyncWrap, static void SetServername(const v8::FunctionCallbackInfo<v8::Value>& args); static int SelectSNIContextCallback(SSL* s, int* ad, void* arg); +#ifndef OPENSSL_NO_PSK + static void SetPskIdentityHint( + const v8::FunctionCallbackInfo<v8::Value>& args); + static void EnablePskCallback( + const v8::FunctionCallbackInfo<v8::Value>& args); + static unsigned int PskServerCallback(SSL* s, + const char* identity, + unsigned char* psk, + unsigned int max_psk_len); + static unsigned int PskClientCallback(SSL* s, + const char* hint, + char* identity, + unsigned int max_identity_len, + unsigned char* psk, + unsigned int max_psk_len); +#endif + crypto::SecureContext* sc_; // BIO buffers hold encrypted data. BIO* enc_in_ = nullptr; // StreamListener fills this for SSL_read(). |