From bfc906906f8fdbed93681ac7079644f859d12e11 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 6 Oct 2020 15:28:14 -0700 Subject: [PATCH] src: combine TLSWrap/SSLWrap SSLWrap was needlessly defined as a template class, splitting the TLS implementation over multiple locations. The original idea, I surmise, was to make it possible to reuse SSLWrap for some other purpose that never manifest. This squashes them down into a single TLSWrap class and moves tls_wrap.h/cc into src/crypto. Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/35552 Reviewed-By: Alba Mendez --- node.gyp | 8 +- src/crypto/crypto_common.cc | 2 + src/crypto/crypto_context.cc | 20 + src/crypto/crypto_context.h | 14 + src/crypto/crypto_ssl.cc | 860 ---------- src/crypto/crypto_ssl.h | 146 -- src/crypto/crypto_tls.cc | 2087 +++++++++++++++++++++++ src/{tls_wrap.h => crypto/crypto_tls.h} | 194 ++- src/node_crypto.h | 2 +- src/tls_wrap.cc | 1326 -------------- 10 files changed, 2257 insertions(+), 2402 deletions(-) delete mode 100644 src/crypto/crypto_ssl.cc delete mode 100644 src/crypto/crypto_ssl.h create mode 100644 src/crypto/crypto_tls.cc rename src/{tls_wrap.h => crypto/crypto_tls.h} (59%) delete mode 100644 src/tls_wrap.cc diff --git a/node.gyp b/node.gyp index a96162d10790e0..9d4ac04ebf9acc 100644 --- a/node.gyp +++ b/node.gyp @@ -934,7 +934,7 @@ 'src/crypto/crypto_keys.cc', 'src/crypto/crypto_keygen.cc', 'src/crypto/crypto_scrypt.cc', - 'src/crypto/crypto_ssl.cc', + 'src/crypto/crypto_tls.cc', 'src/crypto/crypto_aes.cc', 'src/crypto/crypto_bio.h', 'src/crypto/crypto_clienthello-inl.h', @@ -951,7 +951,7 @@ 'src/crypto/crypto_keys.h', 'src/crypto/crypto_keygen.h', 'src/crypto/crypto_scrypt.h', - 'src/crypto/crypto_ssl.h', + 'src/crypto/crypto_tls.h', 'src/crypto/crypto_clienthello.h', 'src/crypto/crypto_context.h', 'src/crypto/crypto_ecdh.h', @@ -961,9 +961,7 @@ 'src/crypto/crypto_random.h', 'src/crypto/crypto_timing.h', 'src/node_crypto.cc', - 'src/node_crypto.h', - 'src/tls_wrap.cc', - 'src/tls_wrap.h' + 'src/node_crypto.h' ], }], [ 'OS in "linux freebsd mac" and ' diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index 327b9d7e0ff909..ada0340a974261 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -799,6 +799,8 @@ MaybeLocal GetClientHelloCiphers( MaybeLocal GetCipherInfo(Environment* env, const SSLPointer& ssl) { + if (SSL_get_current_cipher(ssl.get()) == nullptr) + return MaybeLocal(); EscapableHandleScope scope(env->isolate()); Local info = Object::New(env->isolate()); diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index 946dfc78ef2e4a..d0210079166fb4 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -472,6 +472,26 @@ void SecureContext::Init(const FunctionCallbackInfo& args) { SSL_CTX_set_tlsext_ticket_key_cb(sc->ctx_.get(), TicketCompatibilityCallback); } +SSLPointer SecureContext::CreateSSL() { + return SSLPointer(SSL_new(ctx_.get())); +} + +void SecureContext::SetNewSessionCallback(NewSessionCb cb) { + SSL_CTX_sess_set_new_cb(ctx_.get(), cb); +} + +void SecureContext::SetGetSessionCallback(GetSessionCb cb) { + SSL_CTX_sess_set_get_cb(ctx_.get(), cb); +} + +void SecureContext::SetSelectSNIContextCallback(SelectSNIContextCb cb) { + SSL_CTX_set_tlsext_servername_callback(ctx_.get(), cb); +} + +void SecureContext::SetKeylogCallback(KeylogCb cb) { + SSL_CTX_set_keylog_callback(ctx_.get(), cb); +} + void SecureContext::SetKey(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); diff --git a/src/crypto/crypto_context.h b/src/crypto/crypto_context.h index f6c911f1fad03f..e36a76c5a8812a 100644 --- a/src/crypto/crypto_context.h +++ b/src/crypto/crypto_context.h @@ -23,12 +23,26 @@ void IsExtraRootCertsFileLoaded( class SecureContext final : public BaseObject { public: + using GetSessionCb = SSL_SESSION* (*)(SSL*, const unsigned char*, int, int*); + using KeylogCb = void (*)(const SSL*, const char*); + using NewSessionCb = int (*)(SSL*, SSL_SESSION*); + using SelectSNIContextCb = int (*)(SSL*, int*, void*); + ~SecureContext() override; static void Initialize(Environment* env, v8::Local target); SSL_CTX* operator*() const { return ctx_.get(); } + SSL_CTX* ssl_ctx() const { return ctx_.get(); } + + SSLPointer CreateSSL(); + + void SetGetSessionCallback(GetSessionCb cb); + void SetKeylogCallback(KeylogCb cb); + void SetNewSessionCallback(NewSessionCb cb); + void SetSelectSNIContextCallback(SelectSNIContextCb cb); + // TODO(joyeecheung): track the memory used by OpenSSL types SET_NO_MEMORY_INFO() SET_MEMORY_INFO_NAME(SecureContext) diff --git a/src/crypto/crypto_ssl.cc b/src/crypto/crypto_ssl.cc deleted file mode 100644 index 8e361dd373a95c..00000000000000 --- a/src/crypto/crypto_ssl.cc +++ /dev/null @@ -1,860 +0,0 @@ -#include "crypto/crypto_ssl.h" -#include "crypto/crypto_clienthello-inl.h" -#include "crypto/crypto_common.h" -#include "crypto/crypto_util.h" -#include "allocated_buffer-inl.h" -#include "async_wrap-inl.h" -#include "env-inl.h" -#include "memory_tracker-inl.h" -#include "node_buffer.h" -#include "tls_wrap.h" -#include "v8.h" - -namespace node { - -using v8::Array; -using v8::ArrayBufferView; -using v8::Boolean; -using v8::Context; -using v8::Exception; -using v8::FunctionCallbackInfo; -using v8::FunctionTemplate; -using v8::HandleScope; -using v8::Isolate; -using v8::Local; -using v8::MaybeLocal; -using v8::Object; -using v8::String; -using v8::Uint32; -using v8::Value; - -namespace crypto { -// Just to generate static methods -template void SSLWrap::AddMethods(Environment* env, - Local t); -template void SSLWrap::ConfigureSecureContext(SecureContext* sc); -template int SSLWrap::SetCACerts(SecureContext* sc); -template void SSLWrap::MemoryInfo(MemoryTracker* tracker) const; -template SSL_SESSION* SSLWrap::GetSessionCallback( - SSL* s, - const unsigned char* key, - int len, - int* copy); -template int SSLWrap::NewSessionCallback(SSL* s, - SSL_SESSION* sess); -template void SSLWrap::KeylogCallback(const SSL* s, - const char* line); -template void SSLWrap::OnClientHello( - void* arg, - const ClientHelloParser::ClientHello& hello); -template int SSLWrap::TLSExtStatusCallback(SSL* s, void* arg); -template void SSLWrap::DestroySSL(); -template int SSLWrap::SSLCertCallback(SSL* s, void* arg); -template void SSLWrap::WaitForCertCb(CertCb cb, void* arg); -template int SSLWrap::SelectALPNCallback( - SSL* s, - const unsigned char** out, - unsigned char* outlen, - const unsigned char* in, - unsigned int inlen, - void* arg); - -template -void SSLWrap::AddMethods(Environment* env, Local t) { - HandleScope scope(env->isolate()); - - env->SetProtoMethodNoSideEffect(t, "getPeerCertificate", GetPeerCertificate); - env->SetProtoMethodNoSideEffect(t, "getCertificate", GetCertificate); - env->SetProtoMethodNoSideEffect(t, "getFinished", GetFinished); - env->SetProtoMethodNoSideEffect(t, "getPeerFinished", GetPeerFinished); - env->SetProtoMethodNoSideEffect(t, "getSession", GetSession); - env->SetProtoMethod(t, "setSession", SetSession); - env->SetProtoMethod(t, "loadSession", LoadSession); - env->SetProtoMethodNoSideEffect(t, "isSessionReused", IsSessionReused); - env->SetProtoMethodNoSideEffect(t, "verifyError", VerifyError); - env->SetProtoMethodNoSideEffect(t, "getCipher", GetCipher); - env->SetProtoMethodNoSideEffect(t, "getSharedSigalgs", GetSharedSigalgs); - env->SetProtoMethodNoSideEffect( - t, "exportKeyingMaterial", ExportKeyingMaterial); - env->SetProtoMethod(t, "endParser", EndParser); - env->SetProtoMethod(t, "certCbDone", CertCbDone); - env->SetProtoMethod(t, "renegotiate", Renegotiate); - env->SetProtoMethodNoSideEffect(t, "getTLSTicket", GetTLSTicket); - env->SetProtoMethod(t, "newSessionDone", NewSessionDone); - env->SetProtoMethod(t, "setOCSPResponse", SetOCSPResponse); - env->SetProtoMethod(t, "requestOCSP", RequestOCSP); - env->SetProtoMethodNoSideEffect(t, "getEphemeralKeyInfo", - GetEphemeralKeyInfo); - env->SetProtoMethodNoSideEffect(t, "getProtocol", GetProtocol); - - env->SetProtoMethod(t, "setMaxSendFragment", SetMaxSendFragment); - - env->SetProtoMethodNoSideEffect(t, "getALPNNegotiatedProtocol", - GetALPNNegotiatedProto); - env->SetProtoMethod(t, "setALPNProtocols", SetALPNProtocols); -} - - -template -void SSLWrap::ConfigureSecureContext(SecureContext* sc) { - // OCSP stapling - SSL_CTX_set_tlsext_status_cb(sc->ctx_.get(), TLSExtStatusCallback); - SSL_CTX_set_tlsext_status_arg(sc->ctx_.get(), nullptr); -} - - -template -SSL_SESSION* SSLWrap::GetSessionCallback(SSL* s, - const unsigned char* key, - int len, - int* copy) { - Base* w = static_cast(SSL_get_app_data(s)); - - *copy = 0; - return w->next_sess_.release(); -} - -template -int SSLWrap::NewSessionCallback(SSL* s, SSL_SESSION* sess) { - Base* w = static_cast(SSL_get_app_data(s)); - Environment* env = w->ssl_env(); - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); - - if (!w->session_callbacks_) - return 0; - - // Check if session is small enough to be stored - int size = i2d_SSL_SESSION(sess, nullptr); - if (size > SecureContext::kMaxSessionSize) - return 0; - - // Serialize session - Local session = Buffer::New(env, size).ToLocalChecked(); - unsigned char* session_data = reinterpret_cast( - Buffer::Data(session)); - memset(session_data, 0, size); - i2d_SSL_SESSION(sess, &session_data); - - unsigned int session_id_length; - const unsigned char* session_id_data = SSL_SESSION_get_id(sess, - &session_id_length); - Local session_id = Buffer::Copy( - env, - reinterpret_cast(session_id_data), - session_id_length).ToLocalChecked(); - Local argv[] = { session_id, session }; - // On servers, we pause the handshake until callback of 'newSession', which - // calls NewSessionDoneCb(). On clients, there is no callback to wait for. - if (w->is_server()) - w->awaiting_new_session_ = true; - w->MakeCallback(env->onnewsession_string(), arraysize(argv), argv); - - return 0; -} - -template -void SSLWrap::KeylogCallback(const SSL* s, const char* line) { - Base* w = static_cast(SSL_get_app_data(s)); - Environment* env = w->ssl_env(); - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); - - const size_t size = strlen(line); - Local line_bf = Buffer::Copy(env, line, 1 + size).ToLocalChecked(); - char* data = Buffer::Data(line_bf); - data[size] = '\n'; - w->MakeCallback(env->onkeylog_string(), 1, &line_bf); -} - -template -void SSLWrap::OnClientHello(void* arg, - const ClientHelloParser::ClientHello& hello) { - Base* w = static_cast(arg); - Environment* env = w->ssl_env(); - HandleScope handle_scope(env->isolate()); - Local context = env->context(); - Context::Scope context_scope(context); - - Local hello_obj = Object::New(env->isolate()); - Local buff = Buffer::Copy( - env, - reinterpret_cast(hello.session_id()), - hello.session_size()).ToLocalChecked(); - hello_obj->Set(context, env->session_id_string(), buff).Check(); - if (hello.servername() == nullptr) { - hello_obj->Set(context, - env->servername_string(), - String::Empty(env->isolate())).Check(); - } else { - Local servername = OneByteString(env->isolate(), - hello.servername(), - hello.servername_size()); - hello_obj->Set(context, env->servername_string(), servername).Check(); - } - hello_obj->Set(context, - env->tls_ticket_string(), - Boolean::New(env->isolate(), hello.has_ticket())).Check(); - - Local argv[] = { hello_obj }; - w->MakeCallback(env->onclienthello_string(), arraysize(argv), argv); -} - -template -void SSLWrap::GetPeerCertificate( - const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - Environment* env = w->ssl_env(); - - bool abbreviated = args.Length() < 1 || !args[0]->IsTrue(); - - Local ret; - if (GetPeerCert(env, w->ssl_, abbreviated, w->is_server()).ToLocal(&ret)) - args.GetReturnValue().Set(ret); -} - -template -void SSLWrap::GetCertificate( - const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - Environment* env = w->ssl_env(); - - Local ret; - if (GetCert(env, w->ssl_).ToLocal(&ret)) - args.GetReturnValue().Set(ret); -} - -template -void SSLWrap::GetFinished(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - - // We cannot just pass nullptr to SSL_get_finished() - // because it would further be propagated to memcpy(), - // where the standard requirements as described in ISO/IEC 9899:2011 - // sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated. - // Thus, we use a dummy byte. - char dummy[1]; - size_t len = SSL_get_finished(w->ssl_.get(), dummy, sizeof dummy); - if (len == 0) - return; - - AllocatedBuffer buf = AllocatedBuffer::AllocateManaged(env, len); - CHECK_EQ(len, SSL_get_finished(w->ssl_.get(), buf.data(), len)); - args.GetReturnValue().Set(buf.ToBuffer().ToLocalChecked()); -} - -template -void SSLWrap::GetPeerFinished(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - - // We cannot just pass nullptr to SSL_get_peer_finished() - // because it would further be propagated to memcpy(), - // where the standard requirements as described in ISO/IEC 9899:2011 - // sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated. - // Thus, we use a dummy byte. - char dummy[1]; - size_t len = SSL_get_peer_finished(w->ssl_.get(), dummy, sizeof dummy); - if (len == 0) - return; - - AllocatedBuffer buf = AllocatedBuffer::AllocateManaged(env, len); - CHECK_EQ(len, SSL_get_peer_finished(w->ssl_.get(), buf.data(), len)); - args.GetReturnValue().Set(buf.ToBuffer().ToLocalChecked()); -} - -template -void SSLWrap::GetSession(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - - SSL_SESSION* sess = SSL_get_session(w->ssl_.get()); - if (sess == nullptr) - return; - - int slen = i2d_SSL_SESSION(sess, nullptr); - if (slen <= 0) - return; // Invalid or malformed session. - - AllocatedBuffer sbuf = AllocatedBuffer::AllocateManaged(env, slen); - unsigned char* p = reinterpret_cast(sbuf.data()); - CHECK_LT(0, i2d_SSL_SESSION(sess, &p)); - args.GetReturnValue().Set(sbuf.ToBuffer().ToLocalChecked()); -} - -template -void SSLWrap::SetSession(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - - if (args.Length() < 1) - return THROW_ERR_MISSING_ARGS(env, "Session argument is mandatory"); - - THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "Session"); - - SSLSessionPointer sess = GetTLSSession(args[0]); - if (sess == nullptr) - return; - - if (!SetTLSSession(w->ssl_, sess)) - return env->ThrowError("SSL_set_session error"); -} - -template -void SSLWrap::LoadSession(const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - - // TODO(@sam-github) check arg length and types in js, and CHECK in c++ - if (args.Length() >= 1 && Buffer::HasInstance(args[0])) { - ArrayBufferViewContents sbuf(args[0]); - - const unsigned char* p = sbuf.data(); - SSL_SESSION* sess = d2i_SSL_SESSION(nullptr, &p, sbuf.length()); - - // Setup next session and move hello to the BIO buffer - w->next_sess_.reset(sess); - } -} - -template -void SSLWrap::IsSessionReused(const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - bool yes = SSL_session_reused(w->ssl_.get()); - args.GetReturnValue().Set(yes); -} - -template -void SSLWrap::EndParser(const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - w->hello_parser_.End(); -} - -template -void SSLWrap::Renegotiate(const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - - ClearErrorOnReturn clear_error_on_return; - - if (SSL_renegotiate(w->ssl_.get()) != 1) { - return ThrowCryptoError(w->ssl_env(), ERR_get_error()); - } -} - -template -void SSLWrap::GetTLSTicket(const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - Environment* env = w->ssl_env(); - - SSL_SESSION* sess = SSL_get_session(w->ssl_.get()); - if (sess == nullptr) - return; - - const unsigned char* ticket; - size_t length; - SSL_SESSION_get0_ticket(sess, &ticket, &length); - - if (ticket == nullptr) - return; - - Local buff = Buffer::Copy( - env, reinterpret_cast(ticket), length).ToLocalChecked(); - - args.GetReturnValue().Set(buff); -} - -template -void SSLWrap::NewSessionDone(const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - w->awaiting_new_session_ = false; - w->NewSessionDoneCb(); -} - -template -void SSLWrap::SetOCSPResponse(const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - Environment* env = w->env(); - - if (args.Length() < 1) - return THROW_ERR_MISSING_ARGS(env, "OCSP response argument is mandatory"); - - THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "OCSP response"); - - w->ocsp_response_.Reset(args.GetIsolate(), args[0].As()); -} - -template -void SSLWrap::RequestOCSP(const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - - SSL_set_tlsext_status_type(w->ssl_.get(), TLSEXT_STATUSTYPE_ocsp); -} - -template -void SSLWrap::GetEphemeralKeyInfo( - const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - Environment* env = Environment::GetCurrent(args); - - CHECK(w->ssl_); - - // tmp key is available on only client - if (w->is_server()) - return args.GetReturnValue().SetNull(); - - Local ret; - if (GetEphemeralKey(env, w->ssl_).ToLocal(&ret)) - args.GetReturnValue().Set(ret); - - // TODO(@sam-github) semver-major: else return ThrowCryptoError(env, - // ERR_get_error()) -} - -template -void SSLWrap::SetMaxSendFragment( - const FunctionCallbackInfo& args) { - CHECK(args.Length() >= 1 && args[0]->IsNumber()); - - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - - int rv = SSL_set_max_send_fragment( - w->ssl_.get(), - args[0]->Int32Value(w->ssl_env()->context()).FromJust()); - args.GetReturnValue().Set(rv); -} - -template -void SSLWrap::VerifyError(const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - - // XXX(bnoordhuis) The UNABLE_TO_GET_ISSUER_CERT error when there is no - // peer certificate is questionable but it's compatible with what was - // here before. - long x509_verify_error = // NOLINT(runtime/int) - VerifyPeerCertificate(w->ssl_, X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT); - - if (x509_verify_error == X509_V_OK) - return args.GetReturnValue().SetNull(); - - const char* reason = X509_verify_cert_error_string(x509_verify_error); - const char* code = reason; - code = X509ErrorCode(x509_verify_error); - - Isolate* isolate = args.GetIsolate(); - Local reason_string = OneByteString(isolate, reason); - Local exception_value = Exception::Error(reason_string); - Local exception_object = - exception_value->ToObject(isolate->GetCurrentContext()).ToLocalChecked(); - exception_object->Set(w->env()->context(), w->env()->code_string(), - OneByteString(isolate, code)).Check(); - args.GetReturnValue().Set(exception_object); -} - -template -void SSLWrap::GetCipher(const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - Environment* env = w->ssl_env(); - - const SSL_CIPHER* c = SSL_get_current_cipher(w->ssl_.get()); - if (c == nullptr) - return; - - Local ret; - if (GetCipherInfo(env, w->ssl_).ToLocal(&ret)) - args.GetReturnValue().Set(ret); -} - -template -void SSLWrap::GetSharedSigalgs(const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - Environment* env = w->ssl_env(); - - SSL* ssl = w->ssl_.get(); - int nsig = SSL_get_shared_sigalgs(ssl, 0, nullptr, nullptr, nullptr, nullptr, - nullptr); - MaybeStackBuffer, 16> ret_arr(nsig); - - for (int i = 0; i < nsig; i++) { - int hash_nid; - int sign_nid; - std::string sig_with_md; - - SSL_get_shared_sigalgs(ssl, i, &sign_nid, &hash_nid, nullptr, nullptr, - nullptr); - - switch (sign_nid) { - case EVP_PKEY_RSA: - sig_with_md = "RSA+"; - break; - - case EVP_PKEY_RSA_PSS: - sig_with_md = "RSA-PSS+"; - break; - - case EVP_PKEY_DSA: - sig_with_md = "DSA+"; - break; - - case EVP_PKEY_EC: - sig_with_md = "ECDSA+"; - break; - - case NID_ED25519: - sig_with_md = "Ed25519+"; - break; - - case NID_ED448: - sig_with_md = "Ed448+"; - break; -#ifndef OPENSSL_NO_GOST - case NID_id_GostR3410_2001: - sig_with_md = "gost2001+"; - break; - - case NID_id_GostR3410_2012_256: - sig_with_md = "gost2012_256+"; - break; - - case NID_id_GostR3410_2012_512: - sig_with_md = "gost2012_512+"; - break; -#endif // !OPENSSL_NO_GOST - default: - const char* sn = OBJ_nid2sn(sign_nid); - - if (sn != nullptr) { - sig_with_md = std::string(sn) + "+"; - } else { - sig_with_md = "UNDEF+"; - } - break; - } - - const char* sn_hash = OBJ_nid2sn(hash_nid); - if (sn_hash != nullptr) { - sig_with_md += std::string(sn_hash); - } else { - sig_with_md += "UNDEF"; - } - ret_arr[i] = OneByteString(env->isolate(), sig_with_md.c_str()); - } - - args.GetReturnValue().Set( - Array::New(env->isolate(), ret_arr.out(), ret_arr.length())); -} - -template -void SSLWrap::ExportKeyingMaterial( - const FunctionCallbackInfo& args) { - CHECK(args[0]->IsInt32()); - CHECK(args[1]->IsString()); - - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - Environment* env = w->ssl_env(); - - uint32_t olen = args[0].As()->Value(); - node::Utf8Value label(env->isolate(), args[1]); - - AllocatedBuffer out = AllocatedBuffer::AllocateManaged(env, olen); - - ByteSource context; - bool use_context = !args[2]->IsUndefined(); - if (use_context) - context = ByteSource::FromBuffer(args[2]); - - if (SSL_export_keying_material(w->ssl_.get(), - reinterpret_cast(out.data()), - olen, - *label, - label.length(), - reinterpret_cast( - context.get()), - context.size(), - use_context) != 1) { - return ThrowCryptoError(env, ERR_get_error(), "SSL_export_keying_material"); - } - - args.GetReturnValue().Set(out.ToBuffer().ToLocalChecked()); -} - -template -void SSLWrap::GetProtocol(const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - - const char* tls_version = SSL_get_version(w->ssl_.get()); - args.GetReturnValue().Set(OneByteString(args.GetIsolate(), tls_version)); -} - -template -int SSLWrap::SelectALPNCallback(SSL* s, - const unsigned char** out, - unsigned char* outlen, - const unsigned char* in, - unsigned int inlen, - void* arg) { - Base* w = static_cast(SSL_get_app_data(s)); - Environment* env = w->env(); - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); - - Local alpn_buffer = - w->object()->GetPrivate( - env->context(), - env->alpn_buffer_private_symbol()).ToLocalChecked(); - ArrayBufferViewContents alpn_protos(alpn_buffer); - int status = SSL_select_next_proto(const_cast(out), outlen, - alpn_protos.data(), alpn_protos.length(), - in, inlen); - // According to 3.2. Protocol Selection of RFC7301, fatal - // no_application_protocol alert shall be sent but OpenSSL 1.0.2 does not - // support it yet. See - // https://rt.openssl.org/Ticket/Display.html?id=3463&user=guest&pass=guest - return status == OPENSSL_NPN_NEGOTIATED ? SSL_TLSEXT_ERR_OK - : SSL_TLSEXT_ERR_NOACK; -} - -template -void SSLWrap::GetALPNNegotiatedProto( - const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - - const unsigned char* alpn_proto; - unsigned int alpn_proto_len; - - SSL_get0_alpn_selected(w->ssl_.get(), &alpn_proto, &alpn_proto_len); - - Local result; - if (alpn_proto_len == 0) { - result = False(args.GetIsolate()); - } else if (alpn_proto_len == sizeof("h2") - 1 && - 0 == memcmp(alpn_proto, "h2", sizeof("h2") - 1)) { - result = w->env()->h2_string(); - } else if (alpn_proto_len == sizeof("http/1.1") - 1 && - 0 == memcmp(alpn_proto, "http/1.1", sizeof("http/1.1") - 1)) { - result = w->env()->http_1_1_string(); - } else { - result = OneByteString(args.GetIsolate(), alpn_proto, alpn_proto_len); - } - - args.GetReturnValue().Set(result); -} - -template -void SSLWrap::SetALPNProtocols(const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - Environment* env = w->env(); - if (args.Length() < 1 || !Buffer::HasInstance(args[0])) - return env->ThrowTypeError("Must give a Buffer as first argument"); - - if (w->is_client()) { - CHECK(SetALPN(w->ssl_, args[0])); - } else { - CHECK( - w->object()->SetPrivate( - env->context(), - env->alpn_buffer_private_symbol(), - args[0]).FromJust()); - // Server should select ALPN protocol from list of advertised by client - SSL_CTX_set_alpn_select_cb(SSL_get_SSL_CTX(w->ssl_.get()), - SelectALPNCallback, - nullptr); - } -} - -template -int SSLWrap::TLSExtStatusCallback(SSL* s, void* arg) { - Base* w = static_cast(SSL_get_app_data(s)); - Environment* env = w->env(); - HandleScope handle_scope(env->isolate()); - - if (w->is_client()) { - // Incoming response - Local arg; - MaybeLocal ret = GetSSLOCSPResponse(env, s, Null(env->isolate())); - if (ret.ToLocal(&arg)) - w->MakeCallback(env->onocspresponse_string(), 1, &arg); - - // No async acceptance is possible, so always return 1 to accept the - // response. The listener for 'OCSPResponse' event has no control over - // return value, but it can .destroy() the connection if the response is not - // acceptable. - return 1; - } else { - // Outgoing response - if (w->ocsp_response_.IsEmpty()) - return SSL_TLSEXT_ERR_NOACK; - - Local obj = PersistentToLocal::Default(env->isolate(), - w->ocsp_response_); - size_t len = obj->ByteLength(); - - // OpenSSL takes control of the pointer after accepting it - unsigned char* data = MallocOpenSSL(len); - obj->CopyContents(data, len); - - if (!SSL_set_tlsext_status_ocsp_resp(s, data, len)) - OPENSSL_free(data); - w->ocsp_response_.Reset(); - - return SSL_TLSEXT_ERR_OK; - } -} - -template -void SSLWrap::WaitForCertCb(CertCb cb, void* arg) { - cert_cb_ = cb; - cert_cb_arg_ = arg; -} - -template -int SSLWrap::SSLCertCallback(SSL* s, void* arg) { - Base* w = static_cast(SSL_get_app_data(s)); - - if (!w->is_server()) - return 1; - - if (!w->is_waiting_cert_cb()) - return 1; - - if (w->cert_cb_running_) - // Not an error. Suspend handshake with SSL_ERROR_WANT_X509_LOOKUP, and - // handshake will continue after certcb is done. - return -1; - - Environment* env = w->env(); - Local context = env->context(); - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(context); - w->cert_cb_running_ = true; - - Local info = Object::New(env->isolate()); - - const char* servername = GetServerName(s); - if (servername == nullptr) { - info->Set(context, - env->servername_string(), - String::Empty(env->isolate())).Check(); - } else { - Local str = OneByteString(env->isolate(), servername, - strlen(servername)); - info->Set(context, env->servername_string(), str).Check(); - } - - const bool ocsp = (SSL_get_tlsext_status_type(s) == TLSEXT_STATUSTYPE_ocsp); - info->Set(context, env->ocsp_request_string(), - Boolean::New(env->isolate(), ocsp)).Check(); - - Local argv[] = { info }; - w->MakeCallback(env->oncertcb_string(), arraysize(argv), argv); - - if (!w->cert_cb_running_) - return 1; - - // Performing async action, wait... - return -1; -} - -template -void SSLWrap::CertCbDone(const FunctionCallbackInfo& args) { - Base* w; - ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); - Environment* env = w->env(); - - CHECK(w->is_waiting_cert_cb() && w->cert_cb_running_); - - Local object = w->object(); - Local ctx = object->Get(env->context(), - env->sni_context_string()).ToLocalChecked(); - Local cons = env->secure_context_constructor_template(); - - if (cons->HasInstance(ctx)) { - SecureContext* sc = Unwrap(ctx.As()); - CHECK_NOT_NULL(sc); - // Store the SNI context for later use. - w->sni_context_ = BaseObjectPtr(sc); - - if (UseSNIContext(w->ssl_, w->sni_context_) && !w->SetCACerts(sc)) { - // Not clear why sometimes we throw error, and sometimes we call - // onerror(). Both cause .destroy(), but onerror does a bit more. - unsigned long err = ERR_get_error(); // NOLINT(runtime/int) - return ThrowCryptoError(env, err, "CertCbDone"); - } - } else if (ctx->IsObject()) { - // Failure: incorrect SNI context object - Local err = Exception::TypeError(env->sni_context_err_string()); - w->MakeCallback(env->onerror_string(), 1, &err); - return; - } - - CertCb cb; - void* arg; - - cb = w->cert_cb_; - arg = w->cert_cb_arg_; - - w->cert_cb_running_ = false; - w->cert_cb_ = nullptr; - w->cert_cb_arg_ = nullptr; - - cb(arg); -} - -template -void SSLWrap::DestroySSL() { - if (!ssl_) - return; - - env_->isolate()->AdjustAmountOfExternalAllocatedMemory(-kExternalSize); - ssl_.reset(); -} - -template -int SSLWrap::SetCACerts(SecureContext* sc) { - int err = SSL_set1_verify_cert_store(ssl_.get(), - SSL_CTX_get_cert_store(sc->ctx_.get())); - if (err != 1) - return err; - - STACK_OF(X509_NAME)* list = SSL_dup_CA_list( - SSL_CTX_get_client_CA_list(sc->ctx_.get())); - - // NOTE: `SSL_set_client_CA_list` takes the ownership of `list` - SSL_set_client_CA_list(ssl_.get(), list); - return 1; -} - -template -void SSLWrap::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackField("ocsp_response", ocsp_response_); - tracker->TrackField("sni_context", sni_context_); -} - -} // namespace crypto -} // namespace node diff --git a/src/crypto/crypto_ssl.h b/src/crypto/crypto_ssl.h deleted file mode 100644 index a35b46f3381909..00000000000000 --- a/src/crypto/crypto_ssl.h +++ /dev/null @@ -1,146 +0,0 @@ -#ifndef SRC_CRYPTO_CRYPTO_SSL_H_ -#define SRC_CRYPTO_CRYPTO_SSL_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "crypto/crypto_context.h" -#include "crypto/crypto_clienthello.h" -#include "crypto/crypto_util.h" -#include "env.h" -#include "v8.h" - -namespace node { -namespace crypto { -// SSLWrap implicitly depends on the inheriting class' handle having an -// internal pointer to the Base class. -template -class SSLWrap { - public: - enum Kind { - kClient, - kServer - }; - - SSLWrap(Environment* env, SecureContext* sc, Kind kind) - : env_(env), - kind_(kind), - next_sess_(nullptr), - session_callbacks_(false), - awaiting_new_session_(false), - cert_cb_(nullptr), - cert_cb_arg_(nullptr), - cert_cb_running_(false) { - ssl_.reset(SSL_new(sc->ctx_.get())); - CHECK(ssl_); - env_->isolate()->AdjustAmountOfExternalAllocatedMemory(kExternalSize); - } - - virtual ~SSLWrap() { - DestroySSL(); - } - - inline void enable_session_callbacks() { session_callbacks_ = true; } - inline bool is_server() const { return kind_ == kServer; } - inline bool is_client() const { return kind_ == kClient; } - inline bool is_awaiting_new_session() const { return awaiting_new_session_; } - inline bool is_waiting_cert_cb() const { return cert_cb_ != nullptr; } - - void MemoryInfo(MemoryTracker* tracker) const; - - protected: - typedef void (*CertCb)(void* arg); - - // OpenSSL structures are opaque. Estimate SSL memory size for OpenSSL 1.1.1b: - // SSL: 6224 - // SSL->SSL3_STATE: 1040 - // ...some buffers: 42 * 1024 - // NOTE: Actually it is much more than this - static const int64_t kExternalSize = 6224 + 1040 + 42 * 1024; - - static void ConfigureSecureContext(SecureContext* sc); - static void AddMethods(Environment* env, v8::Local t); - - static SSL_SESSION* GetSessionCallback(SSL* s, - const unsigned char* key, - int len, - int* copy); - static int NewSessionCallback(SSL* s, SSL_SESSION* sess); - static void KeylogCallback(const SSL* s, const char* line); - static void OnClientHello(void* arg, - const ClientHelloParser::ClientHello& hello); - - static void GetPeerCertificate( - const v8::FunctionCallbackInfo& args); - static void GetCertificate(const v8::FunctionCallbackInfo& args); - static void GetFinished(const v8::FunctionCallbackInfo& args); - static void GetPeerFinished(const v8::FunctionCallbackInfo& args); - static void GetSession(const v8::FunctionCallbackInfo& args); - static void SetSession(const v8::FunctionCallbackInfo& args); - static void LoadSession(const v8::FunctionCallbackInfo& args); - static void IsSessionReused(const v8::FunctionCallbackInfo& args); - static void VerifyError(const v8::FunctionCallbackInfo& args); - static void GetCipher(const v8::FunctionCallbackInfo& args); - static void GetSharedSigalgs(const v8::FunctionCallbackInfo& args); - static void ExportKeyingMaterial( - const v8::FunctionCallbackInfo& args); - static void EndParser(const v8::FunctionCallbackInfo& args); - static void CertCbDone(const v8::FunctionCallbackInfo& args); - static void Renegotiate(const v8::FunctionCallbackInfo& args); - static void GetTLSTicket(const v8::FunctionCallbackInfo& args); - static void NewSessionDone(const v8::FunctionCallbackInfo& args); - static void SetOCSPResponse(const v8::FunctionCallbackInfo& args); - static void RequestOCSP(const v8::FunctionCallbackInfo& args); - static void GetEphemeralKeyInfo( - const v8::FunctionCallbackInfo& args); - static void GetProtocol(const v8::FunctionCallbackInfo& args); - -#ifdef SSL_set_max_send_fragment - static void SetMaxSendFragment( - const v8::FunctionCallbackInfo& args); -#endif // SSL_set_max_send_fragment - - static void GetALPNNegotiatedProto( - const v8::FunctionCallbackInfo& args); - static void SetALPNProtocols(const v8::FunctionCallbackInfo& args); - static int SelectALPNCallback(SSL* s, - const unsigned char** out, - unsigned char* outlen, - const unsigned char* in, - unsigned int inlen, - void* arg); - static int TLSExtStatusCallback(SSL* s, void* arg); - static int SSLCertCallback(SSL* s, void* arg); - - void DestroySSL(); - void WaitForCertCb(CertCb cb, void* arg); - int SetCACerts(SecureContext* sc); - - inline Environment* ssl_env() const { - return env_; - } - - Environment* const env_; - Kind kind_; - SSLSessionPointer next_sess_; - SSLPointer ssl_; - bool session_callbacks_; - bool awaiting_new_session_; - - // SSL_set_cert_cb - CertCb cert_cb_; - void* cert_cb_arg_; - bool cert_cb_running_; - - ClientHelloParser hello_parser_; - - v8::Global ocsp_response_; - BaseObjectPtr sni_context_; - - friend class SecureContext; -}; - -} // namespace crypto -} // namespace node - -#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#endif // SRC_CRYPTO_CRYPTO_SSL_H_ diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc new file mode 100644 index 00000000000000..f4850e425f46b3 --- /dev/null +++ b/src/crypto/crypto_tls.cc @@ -0,0 +1,2087 @@ +// 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. + +#include "crypto/crypto_tls.h" +#include "crypto/crypto_context.h" +#include "crypto/crypto_common.h" +#include "crypto/crypto_util.h" +#include "crypto/crypto_bio.h" +#include "crypto/crypto_clienthello-inl.h" +#include "allocated_buffer-inl.h" +#include "async_wrap-inl.h" +#include "debug_utils-inl.h" +#include "memory_tracker-inl.h" +#include "node_buffer.h" +#include "node_errors.h" +#include "stream_base-inl.h" +#include "util-inl.h" + +namespace node { + +using v8::Array; +using v8::ArrayBufferView; +using v8::Context; +using v8::DontDelete; +using v8::EscapableHandleScope; +using v8::Exception; +using v8::False; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::HandleScope; +using v8::Integer; +using v8::Isolate; +using v8::Local; +using v8::MaybeLocal; +using v8::Null; +using v8::Object; +using v8::PropertyAttribute; +using v8::ReadOnly; +using v8::Signature; +using v8::String; +using v8::True; +using v8::Uint32; +using v8::Value; + +namespace crypto { + +namespace { +SSL_SESSION* GetSessionCallback( + SSL* s, + const unsigned char* key, + int len, + int* copy) { + TLSWrap* w = static_cast(SSL_get_app_data(s)); + *copy = 0; + return w->ReleaseSession(); +} + +void OnClientHello( + void* arg, + const ClientHelloParser::ClientHello& hello) { + TLSWrap* w = static_cast(arg); + Environment* env = w->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local hello_obj = Object::New(env->isolate()); + Local servername = (hello.servername() == nullptr) + ? String::Empty(env->isolate()) + : OneByteString(env->isolate(), + hello.servername(), + hello.servername_size()); + Local buf = + Buffer::Copy( + env, + reinterpret_cast(hello.session_id()), + hello.session_size()).FromMaybe(Local()); + + if ((buf.IsEmpty() || + hello_obj->Set(env->context(), env->session_id_string(), buf) + .IsNothing()) || + hello_obj->Set(env->context(), env->servername_string(), servername) + .IsNothing() || + hello_obj->Set( + env->context(), + env->tls_ticket_string(), + hello.has_ticket() + ? True(env->isolate()) + : False(env->isolate())).IsNothing()) { + return; + } + + Local argv[] = { hello_obj }; + w->MakeCallback(env->onclienthello_string(), arraysize(argv), argv); +} + +void KeylogCallback(const SSL* s, const char* line) { + TLSWrap* w = static_cast(SSL_get_app_data(s)); + Environment* env = w->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + const size_t size = strlen(line); + Local line_bf = Buffer::Copy(env, line, 1 + size) + .FromMaybe(Local()); + if (UNLIKELY(line_bf.IsEmpty())) + return; + + char* data = Buffer::Data(line_bf); + data[size] = '\n'; + w->MakeCallback(env->onkeylog_string(), 1, &line_bf); +} + +int NewSessionCallback(SSL* s, SSL_SESSION* sess) { + TLSWrap* w = static_cast(SSL_get_app_data(s)); + Environment* env = w->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + if (!w->has_session_callbacks()) + return 0; + + // Check if session is small enough to be stored + int size = i2d_SSL_SESSION(sess, nullptr); + if (UNLIKELY(size > SecureContext::kMaxSessionSize)) + return 0; + + // Serialize session + // TODO(@jasnell): An AllocatedBuffer or BackingStore would be better + // here to start eliminating unnecessary uses of Buffer where an ordinary + // Uint8Array would do just fine. + Local session = Buffer::New(env, size).FromMaybe(Local()); + if (UNLIKELY(session.IsEmpty())) + return 0; + + unsigned char* session_data = + reinterpret_cast(Buffer::Data(session)); + + memset(session_data, 0, size); + i2d_SSL_SESSION(sess, &session_data); + + unsigned int session_id_length; + const unsigned char* session_id_data = + SSL_SESSION_get_id(sess, &session_id_length); + + // TODO(@jasnell): An AllocatedBuffer or BackingStore would be better + // here to start eliminating unnecessary uses of Buffer where an ordinary + // Uint8Array would do just fine + Local session_id = Buffer::Copy( + env, + reinterpret_cast(session_id_data), + session_id_length).FromMaybe(Local()); + if (UNLIKELY(session_id.IsEmpty())) + return 0; + + Local argv[] = { + session_id, + session + }; + + // On servers, we pause the handshake until callback of 'newSession', which + // calls NewSessionDoneCb(). On clients, there is no callback to wait for. + if (w->is_server()) + w->set_awaiting_new_session(true); + + w->MakeCallback(env->onnewsession_string(), arraysize(argv), argv); + + return 0; +} + +int SSLCertCallback(SSL* s, void* arg) { + TLSWrap* w = static_cast(SSL_get_app_data(s)); + + if (!w->is_server() || !w->is_waiting_cert_cb()) + return 1; + + if (w->is_cert_cb_running()) + // Not an error. Suspend handshake with SSL_ERROR_WANT_X509_LOOKUP, and + // handshake will continue after certcb is done. + return -1; + + Environment* env = w->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + w->set_cert_cb_running(); + + Local info = Object::New(env->isolate()); + + const char* servername = GetServerName(s); + Local servername_str = (servername == nullptr) + ? String::Empty(env->isolate()) + : OneByteString(env->isolate(), servername, strlen(servername)); + + Local ocsp = (SSL_get_tlsext_status_type(s) == TLSEXT_STATUSTYPE_ocsp) + ? True(env->isolate()) + : False(env->isolate()); + + if (info->Set(env->context(), env->servername_string(), servername_str) + .IsNothing() || + info->Set(env->context(), env->ocsp_request_string(), ocsp).IsNothing()) { + return 1; + } + + Local argv[] = { info }; + w->MakeCallback(env->oncertcb_string(), arraysize(argv), argv); + + return w->is_cert_cb_running() ? -1 : 1; +} + +int SelectALPNCallback( + SSL* s, + const unsigned char** out, + unsigned char* outlen, + const unsigned char* in, + unsigned int inlen, + void* arg) { + TLSWrap* w = static_cast(SSL_get_app_data(s)); + Environment* env = w->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local alpn_buffer = + w->object()->GetPrivate( + env->context(), + env->alpn_buffer_private_symbol()).FromMaybe(Local()); + if (UNLIKELY(alpn_buffer.IsEmpty()) || !alpn_buffer->IsArrayBufferView()) + return SSL_TLSEXT_ERR_NOACK; + + ArrayBufferViewContents alpn_protos(alpn_buffer); + int status = SSL_select_next_proto( + const_cast(out), + outlen, + alpn_protos.data(), + alpn_protos.length(), + in, + inlen); + + // According to 3.2. Protocol Selection of RFC7301, fatal + // no_application_protocol alert shall be sent but OpenSSL 1.0.2 does not + // support it yet. See + // https://rt.openssl.org/Ticket/Display.html?id=3463&user=guest&pass=guest + return status == OPENSSL_NPN_NEGOTIATED + ? SSL_TLSEXT_ERR_OK + : SSL_TLSEXT_ERR_NOACK; +} + +int TLSExtStatusCallback(SSL* s, void* arg) { + TLSWrap* w = static_cast(SSL_get_app_data(s)); + Environment* env = w->env(); + HandleScope handle_scope(env->isolate()); + + if (w->is_client()) { + // Incoming response + Local arg; + if (GetSSLOCSPResponse(env, s, Null(env->isolate())).ToLocal(&arg)) + w->MakeCallback(env->onocspresponse_string(), 1, &arg); + + // No async acceptance is possible, so always return 1 to accept the + // response. The listener for 'OCSPResponse' event has no control over + // return value, but it can .destroy() the connection if the response is not + // acceptable. + return 1; + } + + // Outgoing response + Local obj = + w->ocsp_response().FromMaybe(Local()); + if (UNLIKELY(obj.IsEmpty())) + return SSL_TLSEXT_ERR_NOACK; + + size_t len = obj->ByteLength(); + + // OpenSSL takes control of the pointer after accepting it + unsigned char* data = MallocOpenSSL(len); + obj->CopyContents(data, len); + + if (!SSL_set_tlsext_status_ocsp_resp(s, data, len)) + OPENSSL_free(data); + + w->ClearOcspResponse(); + + return SSL_TLSEXT_ERR_OK; +} + +void ConfigureSecureContext(SecureContext* sc) { + // OCSP stapling + SSL_CTX_set_tlsext_status_cb(sc->ctx_.get(), TLSExtStatusCallback); + SSL_CTX_set_tlsext_status_arg(sc->ctx_.get(), nullptr); +} + +inline bool Set( + Environment* env, + Local target, + Local name, + const char* value, + bool ignore_null = true) { + if (value == nullptr) + return ignore_null; + return !target->Set( + env->context(), + name, + OneByteString(env->isolate(), value)) + .IsNothing(); +} +} // namespace + +TLSWrap::TLSWrap(Environment* env, + Local obj, + Kind kind, + StreamBase* stream, + SecureContext* sc) + : AsyncWrap(env, obj, AsyncWrap::PROVIDER_TLSWRAP), + StreamBase(env), + env_(env), + kind_(kind), + sc_(sc) { + MakeWeak(); + CHECK(sc_); + ssl_ = sc_->CreateSSL(); + CHECK(ssl_); + + sc_->SetGetSessionCallback(GetSessionCallback); + sc_->SetNewSessionCallback(NewSessionCallback); + + StreamBase::AttachToObject(GetObject()); + stream->PushStreamListener(this); + + env_->isolate()->AdjustAmountOfExternalAllocatedMemory(kExternalSize); + + InitSSL(); + Debug(this, "Created new TLSWrap"); +} + +TLSWrap::~TLSWrap() { + Destroy(); +} + +MaybeLocal TLSWrap::ocsp_response() const { + if (ocsp_response_.IsEmpty()) + return MaybeLocal(); + return PersistentToLocal::Default(env()->isolate(), ocsp_response_); +} + +void TLSWrap::ClearOcspResponse() { + ocsp_response_.Reset(); +} + +SSL_SESSION* TLSWrap::ReleaseSession() { + return next_sess_.release(); +} + +void TLSWrap::InvokeQueued(int status, const char* error_str) { + Debug(this, "Invoking queued write callbacks (%d, %s)", status, error_str); + if (!write_callback_scheduled_) + return; + + if (current_write_) { + BaseObjectPtr current_write = std::move(current_write_); + current_write_.reset(); + WriteWrap* w = WriteWrap::FromObject(current_write); + w->Done(status, error_str); + } +} + +void TLSWrap::NewSessionDoneCb() { + Debug(this, "New session callback done"); + Cycle(); +} + +void TLSWrap::InitSSL() { + // Initialize SSL – OpenSSL takes ownership of these. + enc_in_ = NodeBIO::New(env()).release(); + enc_out_ = NodeBIO::New(env()).release(); + + SSL_set_bio(ssl_.get(), enc_in_, enc_out_); + + // NOTE: This could be overridden in SetVerifyMode + SSL_set_verify(ssl_.get(), SSL_VERIFY_NONE, VerifyCallback); + +#ifdef SSL_MODE_RELEASE_BUFFERS + SSL_set_mode(ssl_.get(), SSL_MODE_RELEASE_BUFFERS); +#endif // SSL_MODE_RELEASE_BUFFERS + + // This is default in 1.1.1, but set it anyway, Cycle() doesn't currently + // re-call ClearIn() if SSL_read() returns SSL_ERROR_WANT_READ, so data can be + // left sitting in the incoming enc_in_ and never get processed. + // - https://wiki.openssl.org/index.php/TLS1.3#Non-application_data_records + SSL_set_mode(ssl_.get(), SSL_MODE_AUTO_RETRY); + +#ifdef OPENSSL_IS_BORINGSSL + // OpenSSL allows renegotiation by default, but BoringSSL disables it. + // Configure BoringSSL to match OpenSSL's behavior. + SSL_set_renegotiate_mode(ssl_.get(), ssl_renegotiate_freely); +#endif + + SSL_set_app_data(ssl_.get(), this); + // Using InfoCallback isn't how we are supposed to check handshake progress: + // https://github.com/openssl/openssl/issues/7199#issuecomment-420915993 + // + // Note on when this gets called on various openssl versions: + // https://github.com/openssl/openssl/issues/7199#issuecomment-420670544 + SSL_set_info_callback(ssl_.get(), SSLInfoCallback); + + if (is_server()) + sc_->SetSelectSNIContextCallback(SelectSNIContextCallback); + + ConfigureSecureContext(sc_.get()); + + SSL_set_cert_cb(ssl_.get(), SSLCertCallback, this); + + if (is_server()) { + SSL_set_accept_state(ssl_.get()); + } else if (is_client()) { + // Enough space for server response (hello, cert) + NodeBIO::FromBIO(enc_in_)->set_initial(kInitialClientBufferLength); + SSL_set_connect_state(ssl_.get()); + } else { + // Unexpected + ABORT(); + } +} + +void TLSWrap::Wrap(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK_EQ(args.Length(), 3); + CHECK(args[0]->IsObject()); + CHECK(args[1]->IsObject()); + CHECK(args[2]->IsBoolean()); + + Local sc = args[1].As(); + Kind kind = args[2]->IsTrue() ? Kind::kServer : Kind::kClient; + + StreamBase* stream = StreamBase::FromObject(args[0].As()); + CHECK_NOT_NULL(stream); + + Local obj; + if (!env->tls_wrap_constructor_function() + ->NewInstance(env->context()) + .ToLocal(&obj)) { + return; + } + + TLSWrap* res = new TLSWrap(env, obj, kind, stream, Unwrap(sc)); + + args.GetReturnValue().Set(res->object()); +} + +void TLSWrap::Receive(const FunctionCallbackInfo& args) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + ArrayBufferViewContents buffer(args[0]); + const char* data = buffer.data(); + size_t len = buffer.length(); + Debug(wrap, "Receiving %zu bytes injected from JS", len); + + // Copy given buffer entirely or partiall if handle becomes closed + while (len > 0 && wrap->IsAlive() && !wrap->IsClosing()) { + uv_buf_t buf = wrap->OnStreamAlloc(len); + size_t copy = buf.len > len ? len : buf.len; + memcpy(buf.base, data, copy); + buf.len = copy; + wrap->OnStreamRead(copy, buf); + + data += copy; + len -= copy; + } +} + +void TLSWrap::Start(const FunctionCallbackInfo& args) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + CHECK(!wrap->started_); + wrap->started_ = true; + + // Send ClientHello handshake + CHECK(wrap->is_client()); + // Seems odd to read when when we want to send, but SSL_read() triggers a + // handshake if a session isn't established, and handshake will cause + // encrypted data to become available for output. + wrap->ClearOut(); + wrap->EncOut(); +} + +void TLSWrap::SSLInfoCallback(const SSL* ssl_, int where, int ret) { + if (!(where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE))) + return; + + // SSL_renegotiate_pending() should take `const SSL*`, but it does not. + SSL* ssl = const_cast(ssl_); + TLSWrap* c = static_cast(SSL_get_app_data(ssl_)); + Environment* env = c->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + Local object = c->object(); + + if (where & SSL_CB_HANDSHAKE_START) { + Debug(c, "SSLInfoCallback(SSL_CB_HANDSHAKE_START);"); + // Start is tracked to limit number and frequency of renegotiation attempts, + // since excessive renegotiation may be an attack. + Local callback; + + if (object->Get(env->context(), env->onhandshakestart_string()) + .ToLocal(&callback) && callback->IsFunction()) { + Local argv[] = { env->GetNow() }; + c->MakeCallback(callback.As(), arraysize(argv), argv); + } + } + + // SSL_CB_HANDSHAKE_START and SSL_CB_HANDSHAKE_DONE are called + // sending HelloRequest in OpenSSL-1.1.1. + // We need to check whether this is in a renegotiation state or not. + if (where & SSL_CB_HANDSHAKE_DONE && !SSL_renegotiate_pending(ssl)) { + Debug(c, "SSLInfoCallback(SSL_CB_HANDSHAKE_DONE);"); + CHECK(!SSL_renegotiate_pending(ssl)); + Local callback; + + c->established_ = true; + + if (object->Get(env->context(), env->onhandshakedone_string()) + .ToLocal(&callback) && callback->IsFunction()) { + c->MakeCallback(callback.As(), 0, nullptr); + } + } +} + +void TLSWrap::EncOut() { + Debug(this, "Trying to write encrypted output"); + + // Ignore cycling data if ClientHello wasn't yet parsed + if (!hello_parser_.IsEnded()) { + Debug(this, "Returning from EncOut(), hello_parser_ active"); + return; + } + + // Write in progress + if (write_size_ != 0) { + Debug(this, "Returning from EncOut(), write currently in progress"); + return; + } + + // Wait for `newSession` callback to be invoked + if (is_awaiting_new_session()) { + Debug(this, "Returning from EncOut(), awaiting new session"); + return; + } + + // Split-off queue + if (established_ && current_write_) { + Debug(this, "EncOut() write is scheduled"); + write_callback_scheduled_ = true; + } + + if (ssl_ == nullptr) { + Debug(this, "Returning from EncOut(), ssl_ == nullptr"); + return; + } + + // No encrypted output ready to write to the underlying stream. + if (BIO_pending(enc_out_) == 0) { + Debug(this, "No pending encrypted output"); + if (pending_cleartext_input_.size() == 0) { + if (!in_dowrite_) { + Debug(this, "No pending cleartext input, not inside DoWrite()"); + InvokeQueued(0); + } else { + Debug(this, "No pending cleartext input, inside DoWrite()"); + // TODO(@sam-github, @addaleax) If in_dowrite_ is true, appdata was + // passed to SSL_write(). If we are here, the data was not encrypted to + // enc_out_ yet. Calling Done() "works", but since the write is not + // flushed, its too soon. Just returning and letting the next EncOut() + // call Done() passes the test suite, but without more careful analysis, + // its not clear if it is always correct. Not calling Done() could block + // data flow, so for now continue to call Done(), just do it in the next + // tick. + BaseObjectPtr strong_ref{this}; + env()->SetImmediate([this, strong_ref](Environment* env) { + InvokeQueued(0); + }); + } + } + return; + } + + char* data[kSimultaneousBufferCount]; + size_t size[arraysize(data)]; + size_t count = arraysize(data); + write_size_ = NodeBIO::FromBIO(enc_out_)->PeekMultiple(data, size, &count); + CHECK(write_size_ != 0 && count != 0); + + uv_buf_t buf[arraysize(data)]; + uv_buf_t* bufs = buf; + for (size_t i = 0; i < count; i++) + buf[i] = uv_buf_init(data[i], size[i]); + + Debug(this, "Writing %zu buffers to the underlying stream", count); + StreamWriteResult res = underlying_stream()->Write(bufs, count); + if (res.err != 0) { + InvokeQueued(res.err); + return; + } + + if (!res.async) { + Debug(this, "Write finished synchronously"); + HandleScope handle_scope(env()->isolate()); + + // Simulate asynchronous finishing, TLS cannot handle this at the moment. + BaseObjectPtr strong_ref{this}; + env()->SetImmediate([this, strong_ref](Environment* env) { + OnStreamAfterWrite(nullptr, 0); + }); + } +} + +void TLSWrap::OnStreamAfterWrite(WriteWrap* req_wrap, int status) { + Debug(this, "OnStreamAfterWrite(status = %d)", status); + if (current_empty_write_) { + Debug(this, "Had empty write"); + BaseObjectPtr current_empty_write = + std::move(current_empty_write_); + current_empty_write_.reset(); + WriteWrap* finishing = WriteWrap::FromObject(current_empty_write); + finishing->Done(status); + return; + } + + if (ssl_ == nullptr) { + Debug(this, "ssl_ == nullptr, marking as cancelled"); + status = UV_ECANCELED; + } + + // Handle error + if (status) { + if (shutdown_) { + Debug(this, "Ignoring error after shutdown"); + return; + } + + // Notify about error + InvokeQueued(status); + return; + } + + // Commit + NodeBIO::FromBIO(enc_out_)->Read(nullptr, write_size_); + + // Ensure that the progress will be made and `InvokeQueued` will be called. + ClearIn(); + + // Try writing more data + write_size_ = 0; + EncOut(); +} + +MaybeLocal TLSWrap::GetSSLError(int status, int* err, std::string* msg) { + EscapableHandleScope scope(env()->isolate()); + + // ssl_ is already destroyed in reading EOF by close notify alert. + if (ssl_ == nullptr) + return MaybeLocal(); + + *err = SSL_get_error(ssl_.get(), status); + switch (*err) { + case SSL_ERROR_NONE: + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_X509_LOOKUP: + return MaybeLocal(); + + case SSL_ERROR_ZERO_RETURN: + return scope.Escape(env()->zero_return_string()); + + case SSL_ERROR_SSL: + case SSL_ERROR_SYSCALL: + { + unsigned long ssl_err = ERR_peek_error(); // NOLINT(runtime/int) + BIO* bio = BIO_new(BIO_s_mem()); + ERR_print_errors(bio); + + BUF_MEM* mem; + BIO_get_mem_ptr(bio, &mem); + + Isolate* isolate = env()->isolate(); + Local context = isolate->GetCurrentContext(); + + Local message = OneByteString(isolate, mem->data, mem->length); + Local exception = Exception::Error(message); + Local obj = + exception->ToObject(context).FromMaybe(Local()); + if (UNLIKELY(obj.IsEmpty())) + return MaybeLocal(); + + const char* ls = ERR_lib_error_string(ssl_err); + const char* fs = ERR_func_error_string(ssl_err); + const char* rs = ERR_reason_error_string(ssl_err); + + if (!Set(env(), obj, env()->library_string(), ls) || + !Set(env(), obj, env()->function_string(), fs)) { + return MaybeLocal(); + } + + if (rs != nullptr) { + if (!Set(env(), obj, env()->reason_string(), rs)) + return MaybeLocal(); + + // SSL has no API to recover the error name from the number, so we + // transform reason strings like "this error" to "ERR_SSL_THIS_ERROR", + // which ends up being close to the original error macro name. + std::string code(rs); + + for (auto& c : code) + c = (c == ' ') ? '_' : ToUpper(c); + + if (!Set(env(), obj, + env()->code_string(), + ("ERR_SSL_" + code).c_str())) { + return MaybeLocal(); + } + } + + if (msg != nullptr) + msg->assign(mem->data, mem->data + mem->length); + + BIO_free_all(bio); + + return scope.Escape(exception); + } + + default: + UNREACHABLE(); + } + UNREACHABLE(); +} + +void TLSWrap::ClearOut() { + Debug(this, "Trying to read cleartext output"); + // Ignore cycling data if ClientHello wasn't yet parsed + if (!hello_parser_.IsEnded()) { + Debug(this, "Returning from ClearOut(), hello_parser_ active"); + return; + } + + // No reads after EOF + if (eof_) { + Debug(this, "Returning from ClearOut(), EOF reached"); + return; + } + + if (ssl_ == nullptr) { + Debug(this, "Returning from ClearOut(), ssl_ == nullptr"); + return; + } + + MarkPopErrorOnReturn mark_pop_error_on_return; + + char out[kClearOutChunkSize]; + int read; + for (;;) { + read = SSL_read(ssl_.get(), out, sizeof(out)); + Debug(this, "Read %d bytes of cleartext output", read); + + if (read <= 0) + break; + + char* current = out; + while (read > 0) { + int avail = read; + + uv_buf_t buf = EmitAlloc(avail); + if (static_cast(buf.len) < avail) + avail = buf.len; + memcpy(buf.base, current, avail); + EmitRead(avail, buf); + + // Caveat emptor: OnRead() calls into JS land which can result in + // the SSL context object being destroyed. We have to carefully + // check that ssl_ != nullptr afterwards. + if (ssl_ == nullptr) { + Debug(this, "Returning from read loop, ssl_ == nullptr"); + return; + } + + read -= avail; + current += avail; + } + } + + int flags = SSL_get_shutdown(ssl_.get()); + if (!eof_ && flags & SSL_RECEIVED_SHUTDOWN) { + eof_ = true; + EmitRead(UV_EOF); + } + + // We need to check whether an error occurred or the connection was + // shutdown cleanly (SSL_ERROR_ZERO_RETURN) even when read == 0. + // See node#1642 and SSL_read(3SSL) for details. + if (read <= 0) { + HandleScope handle_scope(env()->isolate()); + int err; + + Local arg = GetSSLError(read, &err, nullptr) + .FromMaybe(Local()); + + // Ignore ZERO_RETURN after EOF, it is basically not a error + if (err == SSL_ERROR_ZERO_RETURN && eof_) + return; + + if (LIKELY(!arg.IsEmpty())) { + Debug(this, "Got SSL error (%d), calling onerror", err); + // When TLS Alert are stored in wbio, + // it should be flushed to socket before destroyed. + if (BIO_pending(enc_out_) != 0) + EncOut(); + + MakeCallback(env()->onerror_string(), 1, &arg); + } + } +} + +void TLSWrap::ClearIn() { + Debug(this, "Trying to write cleartext input"); + // Ignore cycling data if ClientHello wasn't yet parsed + if (!hello_parser_.IsEnded()) { + Debug(this, "Returning from ClearIn(), hello_parser_ active"); + return; + } + + if (ssl_ == nullptr) { + Debug(this, "Returning from ClearIn(), ssl_ == nullptr"); + return; + } + + if (pending_cleartext_input_.size() == 0) { + Debug(this, "Returning from ClearIn(), no pending data"); + return; + } + + AllocatedBuffer data = std::move(pending_cleartext_input_); + MarkPopErrorOnReturn mark_pop_error_on_return; + + NodeBIO::FromBIO(enc_out_)->set_allocate_tls_hint(data.size()); + int written = SSL_write(ssl_.get(), data.data(), data.size()); + Debug(this, "Writing %zu bytes, written = %d", data.size(), written); + CHECK(written == -1 || written == static_cast(data.size())); + + // All written + if (written != -1) { + Debug(this, "Successfully wrote all data to SSL"); + return; + } + + // Error or partial write + HandleScope handle_scope(env()->isolate()); + Context::Scope context_scope(env()->context()); + + int err; + std::string error_str; + MaybeLocal arg = GetSSLError(written, &err, &error_str); + if (!arg.IsEmpty()) { + Debug(this, "Got SSL error (%d)", err); + write_callback_scheduled_ = true; + // TODO(@sam-github) Should forward an error object with + // .code/.function/.etc, if possible. + return InvokeQueued(UV_EPROTO, error_str.c_str()); + } + + Debug(this, "Pushing data back"); + // Push back the not-yet-written data. This can be skipped in the error + // case because no further writes would succeed anyway. + pending_cleartext_input_ = std::move(data); +} + +std::string TLSWrap::diagnostic_name() const { + std::string name = "TLSWrap "; + name += is_server() ? "server (" : "client ("; + name += std::to_string(static_cast(get_async_id())) + ")"; + return name; +} + +AsyncWrap* TLSWrap::GetAsyncWrap() { + return static_cast(this); +} + +bool TLSWrap::IsIPCPipe() { + return underlying_stream()->IsIPCPipe(); +} + +int TLSWrap::GetFD() { + return underlying_stream()->GetFD(); +} + +bool TLSWrap::IsAlive() { + return ssl_ && + underlying_stream() != nullptr && + underlying_stream()->IsAlive(); +} + +bool TLSWrap::IsClosing() { + return underlying_stream()->IsClosing(); +} + +int TLSWrap::ReadStart() { + Debug(this, "ReadStart()"); + if (underlying_stream() != nullptr) + return underlying_stream()->ReadStart(); + return 0; +} + +int TLSWrap::ReadStop() { + Debug(this, "ReadStop()"); + return underlying_stream() != nullptr ? underlying_stream()->ReadStop() : 0; +} + +const char* TLSWrap::Error() const { + return error_.empty() ? nullptr : error_.c_str(); +} + +void TLSWrap::ClearError() { + error_.clear(); +} + +// Called by StreamBase::Write() to request async write of clear text into SSL. +// TODO(@sam-github) Should there be a TLSWrap::DoTryWrite()? +int TLSWrap::DoWrite(WriteWrap* w, + uv_buf_t* bufs, + size_t count, + uv_stream_t* send_handle) { + CHECK_NULL(send_handle); + Debug(this, "DoWrite()"); + + if (ssl_ == nullptr) { + ClearError(); + error_ = "Write after DestroySSL"; + return UV_EPROTO; + } + + size_t length = 0; + size_t i; + size_t nonempty_i = 0; + size_t nonempty_count = 0; + for (i = 0; i < count; i++) { + length += bufs[i].len; + if (bufs[i].len > 0) { + nonempty_i = i; + nonempty_count += 1; + } + } + + // We want to trigger a Write() on the underlying stream to drive the stream + // system, but don't want to encrypt empty buffers into a TLS frame, so see + // if we can find something to Write(). + // First, call ClearOut(). It does an SSL_read(), which might cause handshake + // or other internal messages to be encrypted. If it does, write them later + // with EncOut(). + // If there is still no encrypted output, call Write(bufs) on the underlying + // stream. Since the bufs are empty, it won't actually write non-TLS data + // onto the socket, we just want the side-effects. After, make sure the + // WriteWrap was accepted by the stream, or that we call Done() on it. + if (length == 0) { + Debug(this, "Empty write"); + ClearOut(); + if (BIO_pending(enc_out_) == 0) { + Debug(this, "No pending encrypted output, writing to underlying stream"); + CHECK(!current_empty_write_); + current_empty_write_.reset(w->GetAsyncWrap()); + StreamWriteResult res = + underlying_stream()->Write(bufs, count, send_handle); + if (!res.async) { + BaseObjectPtr strong_ref{this}; + env()->SetImmediate([this, strong_ref](Environment* env) { + OnStreamAfterWrite(WriteWrap::FromObject(current_empty_write_), 0); + }); + } + return 0; + } + } + + // Store the current write wrap + CHECK(!current_write_); + current_write_.reset(w->GetAsyncWrap()); + + // Write encrypted data to underlying stream and call Done(). + if (length == 0) { + EncOut(); + return 0; + } + + AllocatedBuffer data; + MarkPopErrorOnReturn mark_pop_error_on_return; + + int written = 0; + + // It is common for zero length buffers to be written, + // don't copy data if there there is one buffer with data + // and one or more zero length buffers. + // _http_outgoing.js writes a zero length buffer in + // in OutgoingMessage.prototype.end. If there was a large amount + // of data supplied to end() there is no sense allocating + // and copying it when it could just be used. + + if (nonempty_count != 1) { + data = AllocatedBuffer::AllocateManaged(env(), length); + size_t offset = 0; + for (i = 0; i < count; i++) { + memcpy(data.data() + offset, bufs[i].base, bufs[i].len); + offset += bufs[i].len; + } + + NodeBIO::FromBIO(enc_out_)->set_allocate_tls_hint(length); + written = SSL_write(ssl_.get(), data.data(), length); + } else { + // Only one buffer: try to write directly, only store if it fails + uv_buf_t* buf = &bufs[nonempty_i]; + NodeBIO::FromBIO(enc_out_)->set_allocate_tls_hint(buf->len); + written = SSL_write(ssl_.get(), buf->base, buf->len); + + if (written == -1) { + data = AllocatedBuffer::AllocateManaged(env(), length); + memcpy(data.data(), buf->base, buf->len); + } + } + + CHECK(written == -1 || written == static_cast(length)); + Debug(this, "Writing %zu bytes, written = %d", length, written); + + if (written == -1) { + int err; + MaybeLocal arg = GetSSLError(written, &err, &error_); + + // If we stopped writing because of an error, it's fatal, discard the data. + if (!arg.IsEmpty()) { + // TODO(@jasnell): What are we doing with the error? + Debug(this, "Got SSL error (%d), returning UV_EPROTO", err); + current_write_.reset(); + return UV_EPROTO; + } + + Debug(this, "Saving data for later write"); + // Otherwise, save unwritten data so it can be written later by ClearIn(). + CHECK_EQ(pending_cleartext_input_.size(), 0); + pending_cleartext_input_ = std::move(data); + } + + // Write any encrypted/handshake output that may be ready. + // Guard against sync call of current_write_->Done(), its unsupported. + in_dowrite_ = true; + EncOut(); + in_dowrite_ = false; + + return 0; +} + +uv_buf_t TLSWrap::OnStreamAlloc(size_t suggested_size) { + CHECK_NOT_NULL(ssl_); + + size_t size = suggested_size; + char* base = NodeBIO::FromBIO(enc_in_)->PeekWritable(&size); + return uv_buf_init(base, size); +} + +void TLSWrap::OnStreamRead(ssize_t nread, const uv_buf_t& buf) { + Debug(this, "Read %zd bytes from underlying stream", nread); + if (nread < 0) { + // Error should be emitted only after all data was read + ClearOut(); + + // Ignore EOF if received close_notify + if (nread == UV_EOF) { + if (eof_) + return; + eof_ = true; + } + + EmitRead(nread); + return; + } + + // DestroySSL() is the only thing that un-sets ssl_, but that also removes + // this TLSWrap as a stream listener, so we should not receive OnStreamRead() + // calls anymore. + CHECK(ssl_); + + // Commit the amount of data actually read into the peeked/allocated buffer + // from the underlying stream. + NodeBIO* enc_in = NodeBIO::FromBIO(enc_in_); + enc_in->Commit(nread); + + // Parse ClientHello first, if we need to. It's only parsed if session event + // listeners are used on the server side. "ended" is the initial state, so + // can mean parsing was never started, or that parsing is finished. Either + // way, ended means we can give the buffered data to SSL. + if (!hello_parser_.IsEnded()) { + size_t avail = 0; + uint8_t* data = reinterpret_cast(enc_in->Peek(&avail)); + CHECK_IMPLIES(data == nullptr, avail == 0); + Debug(this, "Passing %zu bytes to the hello parser", avail); + return hello_parser_.Parse(data, avail); + } + + // Cycle OpenSSL's state + Cycle(); +} + +ShutdownWrap* TLSWrap::CreateShutdownWrap(Local req_wrap_object) { + return underlying_stream()->CreateShutdownWrap(req_wrap_object); +} + +int TLSWrap::DoShutdown(ShutdownWrap* req_wrap) { + Debug(this, "DoShutdown()"); + MarkPopErrorOnReturn mark_pop_error_on_return; + + if (ssl_ && SSL_shutdown(ssl_.get()) == 0) + SSL_shutdown(ssl_.get()); + + shutdown_ = true; + EncOut(); + return underlying_stream()->DoShutdown(req_wrap); +} + +void TLSWrap::SetVerifyMode(const FunctionCallbackInfo& args) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + CHECK_EQ(args.Length(), 2); + CHECK(args[0]->IsBoolean()); + CHECK(args[1]->IsBoolean()); + CHECK_NOT_NULL(wrap->ssl_); + + int verify_mode; + if (wrap->is_server()) { + bool request_cert = args[0]->IsTrue(); + if (!request_cert) { + // If no cert is requested, there will be none to reject as unauthorized. + verify_mode = SSL_VERIFY_NONE; + } else { + bool reject_unauthorized = args[1]->IsTrue(); + verify_mode = SSL_VERIFY_PEER; + if (reject_unauthorized) + verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + } + } else { + // Servers always send a cert if the cipher is not anonymous (anon is + // disabled by default), so use VERIFY_NONE and check the cert after the + // handshake has completed. + verify_mode = SSL_VERIFY_NONE; + } + + // Always allow a connection. We'll reject in javascript. + SSL_set_verify(wrap->ssl_.get(), verify_mode, VerifyCallback); +} + +void TLSWrap::EnableSessionCallbacks(const FunctionCallbackInfo& args) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + CHECK_NOT_NULL(wrap->ssl_); + wrap->enable_session_callbacks(); + + // Clients don't use the HelloParser. + if (wrap->is_client()) + return; + + NodeBIO::FromBIO(wrap->enc_in_)->set_initial(kMaxHelloLength); + wrap->hello_parser_.Start(OnClientHello, + OnClientHelloParseEnd, + wrap); +} + +void TLSWrap::EnableKeylogCallback(const FunctionCallbackInfo& args) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + CHECK(wrap->sc_); + wrap->sc_->SetKeylogCallback(KeylogCallback); +} + +// Check required capabilities were not excluded from the OpenSSL build: +// - OPENSSL_NO_SSL_TRACE excludes SSL_trace() +// - OPENSSL_NO_STDIO excludes BIO_new_fp() +// HAVE_SSL_TRACE is available on the internal tcp_wrap binding for the tests. +#if defined(OPENSSL_NO_SSL_TRACE) || defined(OPENSSL_NO_STDIO) +# define HAVE_SSL_TRACE 0 +#else +# define HAVE_SSL_TRACE 1 +#endif + +void TLSWrap::EnableTrace(const FunctionCallbackInfo& args) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + +#if HAVE_SSL_TRACE + if (wrap->ssl_) { + wrap->bio_trace_.reset(BIO_new_fp(stderr, BIO_NOCLOSE | BIO_FP_TEXT)); + SSL_set_msg_callback(wrap->ssl_.get(), [](int write_p, int version, int + content_type, const void* buf, size_t len, SSL* ssl, void* arg) + -> void { + // BIO_write(), etc., called by SSL_trace, may error. The error should + // be ignored, trace is a "best effort", and its usually because stderr + // is a non-blocking pipe, and its buffer has overflowed. Leaving errors + // on the stack that can get picked up by later SSL_ calls causes + // unwanted failures in SSL_ calls, so keep the error stack unchanged. + MarkPopErrorOnReturn mark_pop_error_on_return; + SSL_trace(write_p, version, content_type, buf, len, ssl, arg); + }); + SSL_set_msg_callback_arg(wrap->ssl_.get(), wrap->bio_trace_.get()); + } +#endif +} + +void TLSWrap::DestroySSL(const FunctionCallbackInfo& args) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + wrap->Destroy(); + Debug(wrap, "DestroySSL() finished"); +} + +void TLSWrap::Destroy() { + if (!ssl_) + return; + + // If there is a write happening, mark it as finished. + write_callback_scheduled_ = true; + + // And destroy + InvokeQueued(UV_ECANCELED, "Canceled because of SSL destruction"); + + env()->isolate()->AdjustAmountOfExternalAllocatedMemory(-kExternalSize); + ssl_.reset(); + + enc_in_ = nullptr; + enc_out_ = nullptr; + + if (underlying_stream() != nullptr) + underlying_stream()->RemoveStreamListener(this); + + sc_.reset(); +} + +void TLSWrap::EnableCertCb(const FunctionCallbackInfo& args) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + wrap->WaitForCertCb(OnClientHelloParseEnd, wrap); +} + +void TLSWrap::WaitForCertCb(CertCb cb, void* arg) { + cert_cb_ = cb; + cert_cb_arg_ = arg; +} + +void TLSWrap::OnClientHelloParseEnd(void* arg) { + TLSWrap* c = static_cast(arg); + Debug(c, "OnClientHelloParseEnd()"); + c->Cycle(); +} + +void TLSWrap::GetServername(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + CHECK_NOT_NULL(wrap->ssl_); + + const char* servername = SSL_get_servername(wrap->ssl_.get(), + TLSEXT_NAMETYPE_host_name); + if (servername != nullptr) { + args.GetReturnValue().Set(OneByteString(env->isolate(), servername)); + } else { + args.GetReturnValue().Set(false); + } +} + +void TLSWrap::SetServername(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsString()); + CHECK(!wrap->started_); + CHECK(wrap->is_client()); + + CHECK(wrap->ssl_); + + Utf8Value servername(env->isolate(), args[0].As()); + SSL_set_tlsext_host_name(wrap->ssl_.get(), *servername); +} + +int TLSWrap::SelectSNIContextCallback(SSL* s, int* ad, void* arg) { + TLSWrap* p = static_cast(SSL_get_app_data(s)); + Environment* env = p->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); + if (!Set(env, p->GetOwner(), env->servername_string(), servername)) + return SSL_TLSEXT_ERR_NOACK; + + Local ctx = p->object()->Get(env->context(), env->sni_context_string()) + .FromMaybe(Local()); + + if (UNLIKELY(ctx.IsEmpty()) || !ctx->IsObject()) + return SSL_TLSEXT_ERR_NOACK; + + if (!env->secure_context_constructor_template()->HasInstance(ctx)) { + // Failure: incorrect SNI context object + Local err = Exception::TypeError(env->sni_context_err_string()); + p->MakeCallback(env->onerror_string(), 1, &err); + return SSL_TLSEXT_ERR_NOACK; + } + + SecureContext* sc = Unwrap(ctx.As()); + CHECK_NOT_NULL(sc); + p->sni_context_ = BaseObjectPtr(sc); + + ConfigureSecureContext(sc); + CHECK_EQ(SSL_set_SSL_CTX(p->ssl_.get(), sc->ctx_.get()), sc->ctx_.get()); + p->SetCACerts(sc); + + return SSL_TLSEXT_ERR_OK; +} + +#ifndef OPENSSL_NO_PSK + +int TLSWrap::SetCACerts(SecureContext* sc) { + int err = SSL_set1_verify_cert_store( + ssl_.get(), SSL_CTX_get_cert_store(sc->ctx_.get())); + if (err != 1) + return err; + + STACK_OF(X509_NAME)* list = + SSL_dup_CA_list(SSL_CTX_get_client_CA_list(sc->ctx_.get())); + + // NOTE: `SSL_set_client_CA_list` takes the ownership of `list` + SSL_set_client_CA_list(ssl_.get(), list); + return 1; +} + +void TLSWrap::SetPskIdentityHint(const FunctionCallbackInfo& 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()); + Utf8Value hint(isolate, args[0].As()); + + if (!SSL_use_psk_identity_hint(p->ssl_.get(), *hint)) { + Local err = node::ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED(isolate); + p->MakeCallback(env->onerror_string(), 1, &err); + } +} + +void TLSWrap::EnablePskCallback(const FunctionCallbackInfo& 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(SSL_get_app_data(s)); + + Environment* env = p->env(); + HandleScope scope(env->isolate()); + + Local identity_str = + String::NewFromUtf8(env->isolate(), identity).FromMaybe(Local()); + if (UNLIKELY(identity_str.IsEmpty())) + return 0; + + // Make sure there are no utf8 replacement symbols. + Utf8Value identity_utf8(env->isolate(), identity_str); + if (strcmp(*identity_utf8, identity) != 0) + return 0; + + Local argv[] = { + identity_str, + Integer::NewFromUnsigned(env->isolate(), max_psk_len) + }; + + Local psk_val = + p->MakeCallback(env->onpskexchange_symbol(), arraysize(argv), argv) + .FromMaybe(Local()); + if (UNLIKELY(psk_val.IsEmpty()) || !psk_val->IsArrayBufferView()) + return 0; + + ArrayBufferViewContents psk_buf(psk_val); + + if (psk_buf.length() > max_psk_len) + return 0; + + memcpy(psk, psk_buf.data(), psk_buf.length()); + return psk_buf.length(); +} + +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(SSL_get_app_data(s)); + + Environment* env = p->env(); + HandleScope scope(env->isolate()); + + Local argv[] = { + Null(env->isolate()), + Integer::NewFromUnsigned(env->isolate(), max_psk_len), + Integer::NewFromUnsigned(env->isolate(), max_identity_len) + }; + + if (hint != nullptr) { + Local local_hint = + String::NewFromUtf8(env->isolate(), hint).FromMaybe(Local()); + if (UNLIKELY(local_hint.IsEmpty())) + return 0; + + argv[0] = local_hint; + } + + Local ret = + p->MakeCallback(env->onpskexchange_symbol(), arraysize(argv), argv) + .FromMaybe(Local()); + if (UNLIKELY(ret.IsEmpty()) || !ret->IsObject()) + return 0; + + Local obj = ret.As(); + + Local psk_val = obj->Get(env->context(), env->psk_string()) + .FromMaybe(Local()); + if (UNLIKELY(psk_val.IsEmpty()) || !psk_val->IsArrayBufferView()) + return 0; + + ArrayBufferViewContents psk_buf(psk_val); + if (psk_buf.length() > max_psk_len) + return 0; + + Local identity_val = obj->Get(env->context(), env->identity_string()) + .FromMaybe(Local()); + if (UNLIKELY(identity_val.IsEmpty()) || !identity_val->IsString()) + return 0; + + Utf8Value identity_buf(env->isolate(), identity_val); + + if (identity_buf.length() > max_identity_len) + return 0; + + memcpy(identity, *identity_buf, identity_buf.length()); + memcpy(psk, psk_buf.data(), psk_buf.length()); + + return psk_buf.length(); +} + +#endif + +void TLSWrap::GetWriteQueueSize(const FunctionCallbackInfo& info) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, info.This()); + + if (!wrap->ssl_) + return info.GetReturnValue().Set(0); + + uint32_t write_queue_size = BIO_pending(wrap->enc_out_); + info.GetReturnValue().Set(write_queue_size); +} + +void TLSWrap::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackField("ocsp_response", ocsp_response_); + tracker->TrackField("sni_context", sni_context_); + tracker->TrackField("error", error_); + tracker->TrackFieldWithSize("pending_cleartext_input", + pending_cleartext_input_.size(), + "AllocatedBuffer"); + if (enc_in_ != nullptr) + tracker->TrackField("enc_in", NodeBIO::FromBIO(enc_in_)); + if (enc_out_ != nullptr) + tracker->TrackField("enc_out", NodeBIO::FromBIO(enc_out_)); +} + +void TLSWrap::CertCbDone(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + CHECK(w->is_waiting_cert_cb() && w->cert_cb_running_); + + Local object = w->object(); + Local ctx = object->Get(env->context(), env->sni_context_string()) + .FromMaybe(Local()); + if (UNLIKELY(ctx.IsEmpty())) + return; + + Local cons = env->secure_context_constructor_template(); + if (cons->HasInstance(ctx)) { + SecureContext* sc = Unwrap(ctx.As()); + CHECK_NOT_NULL(sc); + // Store the SNI context for later use. + w->sni_context_ = BaseObjectPtr(sc); + + if (UseSNIContext(w->ssl_, w->sni_context_) && !w->SetCACerts(sc)) { + // Not clear why sometimes we throw error, and sometimes we call + // onerror(). Both cause .destroy(), but onerror does a bit more. + unsigned long err = ERR_get_error(); // NOLINT(runtime/int) + return ThrowCryptoError(env, err, "CertCbDone"); + } + } else if (ctx->IsObject()) { + // Failure: incorrect SNI context object + Local err = Exception::TypeError(env->sni_context_err_string()); + w->MakeCallback(env->onerror_string(), 1, &err); + return; + } + + CertCb cb; + void* arg; + + cb = w->cert_cb_; + arg = w->cert_cb_arg_; + + w->cert_cb_running_ = false; + w->cert_cb_ = nullptr; + w->cert_cb_arg_ = nullptr; + + cb(arg); +} + +void TLSWrap::SetALPNProtocols(const FunctionCallbackInfo& args) { + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + Environment* env = w->env(); + if (args.Length() < 1 || !Buffer::HasInstance(args[0])) + return env->ThrowTypeError("Must give a Buffer as first argument"); + + if (w->is_client()) { + CHECK(SetALPN(w->ssl_, args[0])); + } else { + CHECK( + w->object()->SetPrivate( + env->context(), + env->alpn_buffer_private_symbol(), + args[0]).FromJust()); + // Server should select ALPN protocol from list of advertised by client + SSL_CTX_set_alpn_select_cb(SSL_get_SSL_CTX(w->ssl_.get()), + SelectALPNCallback, + nullptr); + } +} + +void TLSWrap::GetPeerCertificate(const FunctionCallbackInfo& args) { + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + Environment* env = w->env(); + + bool abbreviated = args.Length() < 1 || !args[0]->IsTrue(); + + Local ret; + if (GetPeerCert( + env, + w->ssl_, + abbreviated, + w->is_server()).ToLocal(&ret)) + args.GetReturnValue().Set(ret); +} + +void TLSWrap::GetCertificate(const FunctionCallbackInfo& args) { + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + Environment* env = w->env(); + + Local ret; + if (GetCert(env, w->ssl_).ToLocal(&ret)) + args.GetReturnValue().Set(ret); +} + +void TLSWrap::GetFinished(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + // We cannot just pass nullptr to SSL_get_finished() + // because it would further be propagated to memcpy(), + // where the standard requirements as described in ISO/IEC 9899:2011 + // sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated. + // Thus, we use a dummy byte. + char dummy[1]; + size_t len = SSL_get_finished(w->ssl_.get(), dummy, sizeof dummy); + if (len == 0) + return; + + AllocatedBuffer buf = AllocatedBuffer::AllocateManaged(env, len); + CHECK_EQ(len, SSL_get_finished(w->ssl_.get(), buf.data(), len)); + args.GetReturnValue().Set(buf.ToBuffer().FromMaybe(Local())); +} + +void TLSWrap::GetPeerFinished(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + // We cannot just pass nullptr to SSL_get_peer_finished() + // because it would further be propagated to memcpy(), + // where the standard requirements as described in ISO/IEC 9899:2011 + // sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated. + // Thus, we use a dummy byte. + char dummy[1]; + size_t len = SSL_get_peer_finished(w->ssl_.get(), dummy, sizeof dummy); + if (len == 0) + return; + + AllocatedBuffer buf = AllocatedBuffer::AllocateManaged(env, len); + CHECK_EQ(len, SSL_get_peer_finished(w->ssl_.get(), buf.data(), len)); + args.GetReturnValue().Set(buf.ToBuffer().FromMaybe(Local())); +} + +void TLSWrap::GetSession(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + SSL_SESSION* sess = SSL_get_session(w->ssl_.get()); + if (sess == nullptr) + return; + + int slen = i2d_SSL_SESSION(sess, nullptr); + if (slen <= 0) + return; // Invalid or malformed session. + + AllocatedBuffer sbuf = AllocatedBuffer::AllocateManaged(env, slen); + unsigned char* p = reinterpret_cast(sbuf.data()); + CHECK_LT(0, i2d_SSL_SESSION(sess, &p)); + args.GetReturnValue().Set(sbuf.ToBuffer().FromMaybe(Local())); +} + +void TLSWrap::SetSession(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + if (args.Length() < 1) + return THROW_ERR_MISSING_ARGS(env, "Session argument is mandatory"); + + THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "Session"); + + SSLSessionPointer sess = GetTLSSession(args[0]); + if (sess == nullptr) + return; + + if (!SetTLSSession(w->ssl_, sess)) + return env->ThrowError("SSL_set_session error"); +} + +void TLSWrap::IsSessionReused(const FunctionCallbackInfo& args) { + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + bool yes = SSL_session_reused(w->ssl_.get()); + args.GetReturnValue().Set(yes); +} + +void TLSWrap::VerifyError(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + // XXX(bnoordhuis) The UNABLE_TO_GET_ISSUER_CERT error when there is no + // peer certificate is questionable but it's compatible with what was + // here before. + long x509_verify_error = // NOLINT(runtime/int) + VerifyPeerCertificate( + w->ssl_, + X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT); + + if (x509_verify_error == X509_V_OK) + return args.GetReturnValue().SetNull(); + + const char* reason = X509_verify_cert_error_string(x509_verify_error); + const char* code = X509ErrorCode(x509_verify_error); + + Local exception = + Exception::Error(OneByteString(env->isolate(), reason)) + ->ToObject(env->isolate()->GetCurrentContext()) + .FromMaybe(Local()); + + if (Set(env, exception, env->code_string(), code)) + args.GetReturnValue().Set(exception); +} + +void TLSWrap::GetCipher(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + args.GetReturnValue().Set( + GetCipherInfo(env, w->ssl_).FromMaybe(Local())); +} + +void TLSWrap::LoadSession(const FunctionCallbackInfo& args) { + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + // TODO(@sam-github) check arg length and types in js, and CHECK in c++ + if (args.Length() >= 1 && Buffer::HasInstance(args[0])) { + ArrayBufferViewContents sbuf(args[0]); + + const unsigned char* p = sbuf.data(); + SSL_SESSION* sess = d2i_SSL_SESSION(nullptr, &p, sbuf.length()); + + // Setup next session and move hello to the BIO buffer + w->next_sess_.reset(sess); + } +} + +void TLSWrap::GetSharedSigalgs(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + SSL* ssl = w->ssl_.get(); + int nsig = SSL_get_shared_sigalgs(ssl, 0, nullptr, nullptr, nullptr, nullptr, + nullptr); + MaybeStackBuffer, 16> ret_arr(nsig); + + for (int i = 0; i < nsig; i++) { + int hash_nid; + int sign_nid; + std::string sig_with_md; + + SSL_get_shared_sigalgs(ssl, i, &sign_nid, &hash_nid, nullptr, nullptr, + nullptr); + + switch (sign_nid) { + case EVP_PKEY_RSA: + sig_with_md = "RSA+"; + break; + + case EVP_PKEY_RSA_PSS: + sig_with_md = "RSA-PSS+"; + break; + + case EVP_PKEY_DSA: + sig_with_md = "DSA+"; + break; + + case EVP_PKEY_EC: + sig_with_md = "ECDSA+"; + break; + + case NID_ED25519: + sig_with_md = "Ed25519+"; + break; + + case NID_ED448: + sig_with_md = "Ed448+"; + break; +#ifndef OPENSSL_NO_GOST + case NID_id_GostR3410_2001: + sig_with_md = "gost2001+"; + break; + + case NID_id_GostR3410_2012_256: + sig_with_md = "gost2012_256+"; + break; + + case NID_id_GostR3410_2012_512: + sig_with_md = "gost2012_512+"; + break; +#endif // !OPENSSL_NO_GOST + default: + const char* sn = OBJ_nid2sn(sign_nid); + + if (sn != nullptr) { + sig_with_md = std::string(sn) + "+"; + } else { + sig_with_md = "UNDEF+"; + } + break; + } + + const char* sn_hash = OBJ_nid2sn(hash_nid); + if (sn_hash != nullptr) { + sig_with_md += std::string(sn_hash); + } else { + sig_with_md += "UNDEF"; + } + ret_arr[i] = OneByteString(env->isolate(), sig_with_md.c_str()); + } + + args.GetReturnValue().Set( + Array::New(env->isolate(), ret_arr.out(), ret_arr.length())); +} + +void TLSWrap::ExportKeyingMaterial(const FunctionCallbackInfo& args) { + CHECK(args[0]->IsInt32()); + CHECK(args[1]->IsString()); + + Environment* env = Environment::GetCurrent(args); + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + uint32_t olen = args[0].As()->Value(); + Utf8Value label(env->isolate(), args[1]); + + AllocatedBuffer out = AllocatedBuffer::AllocateManaged(env, olen); + + ByteSource context; + bool use_context = !args[2]->IsUndefined(); + if (use_context) + context = ByteSource::FromBuffer(args[2]); + + if (SSL_export_keying_material( + w->ssl_.get(), + reinterpret_cast(out.data()), + olen, + *label, + label.length(), + reinterpret_cast(context.get()), + context.size(), + use_context) != 1) { + return ThrowCryptoError( + env, + ERR_get_error(), + "SSL_export_keying_material"); + } + + args.GetReturnValue().Set(out.ToBuffer().FromMaybe(Local())); +} + +void TLSWrap::EndParser(const FunctionCallbackInfo& args) { + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + w->hello_parser_.End(); +} + +void TLSWrap::Renegotiate(const FunctionCallbackInfo& args) { + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + ClearErrorOnReturn clear_error_on_return; + if (SSL_renegotiate(w->ssl_.get()) != 1) + return ThrowCryptoError(w->env(), ERR_get_error()); +} + +void TLSWrap::GetTLSTicket(const FunctionCallbackInfo& args) { + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + Environment* env = w->env(); + + SSL_SESSION* sess = SSL_get_session(w->ssl_.get()); + if (sess == nullptr) + return; + + const unsigned char* ticket; + size_t length; + SSL_SESSION_get0_ticket(sess, &ticket, &length); + + if (ticket != nullptr) { + args.GetReturnValue().Set( + Buffer::Copy(env, reinterpret_cast(ticket), length) + .FromMaybe(Local())); + } +} + +void TLSWrap::NewSessionDone(const FunctionCallbackInfo& args) { + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + w->awaiting_new_session_ = false; + w->NewSessionDoneCb(); +} + +void TLSWrap::SetOCSPResponse(const FunctionCallbackInfo& args) { + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + Environment* env = w->env(); + + if (args.Length() < 1) + return THROW_ERR_MISSING_ARGS(env, "OCSP response argument is mandatory"); + + THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "OCSP response"); + + w->ocsp_response_.Reset(args.GetIsolate(), args[0].As()); +} + +void TLSWrap::RequestOCSP(const FunctionCallbackInfo& args) { + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + SSL_set_tlsext_status_type(w->ssl_.get(), TLSEXT_STATUSTYPE_ocsp); +} + +void TLSWrap::GetEphemeralKeyInfo(const FunctionCallbackInfo& args) { + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + Environment* env = Environment::GetCurrent(args); + + CHECK(w->ssl_); + + // tmp key is available on only client + if (w->is_server()) + return args.GetReturnValue().SetNull(); + + args.GetReturnValue().Set(GetEphemeralKey(env, w->ssl_) + .FromMaybe(Local())); + + // TODO(@sam-github) semver-major: else return ThrowCryptoError(env, + // ERR_get_error()) +} + +void TLSWrap::GetProtocol(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + args.GetReturnValue().Set( + OneByteString(env->isolate(), SSL_get_version(w->ssl_.get()))); +} + +void TLSWrap::GetALPNNegotiatedProto(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + const unsigned char* alpn_proto; + unsigned int alpn_proto_len; + + SSL_get0_alpn_selected(w->ssl_.get(), &alpn_proto, &alpn_proto_len); + + Local result; + if (alpn_proto_len == 0) { + result = False(env->isolate()); + } else if (alpn_proto_len == sizeof("h2") - 1 && + 0 == memcmp(alpn_proto, "h2", sizeof("h2") - 1)) { + result = env->h2_string(); + } else if (alpn_proto_len == sizeof("http/1.1") - 1 && + 0 == memcmp(alpn_proto, "http/1.1", sizeof("http/1.1") - 1)) { + result = env->http_1_1_string(); + } else { + result = OneByteString(env->isolate(), alpn_proto, alpn_proto_len); + } + + args.GetReturnValue().Set(result); +} + +void TLSWrap::Cycle() { + // Prevent recursion + if (++cycle_depth_ > 1) + return; + + for (; cycle_depth_ > 0; cycle_depth_--) { + ClearIn(); + ClearOut(); + // EncIn() doesn't exist, it happens via stream listener callbacks. + EncOut(); + } +} + +#ifdef SSL_set_max_send_fragment +void TLSWrap::SetMaxSendFragment(const FunctionCallbackInfo& args) { + CHECK(args.Length() >= 1 && args[0]->IsNumber()); + Environment* env = Environment::GetCurrent(args); + TLSWrap* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + int rv = SSL_set_max_send_fragment( + w->ssl_.get(), + args[0]->Int32Value(env->context()).FromJust()); + args.GetReturnValue().Set(rv); +} +#endif // SSL_set_max_send_fragment + +void TLSWrap::Initialize( + Local target, + Local unused, + Local context, + void* priv) { + Environment* env = Environment::GetCurrent(context); + + env->SetMethod(target, "wrap", TLSWrap::Wrap); + + NODE_DEFINE_CONSTANT(target, HAVE_SSL_TRACE); + + Local t = BaseObject::MakeLazilyInitializedJSTemplate(env); + Local tlsWrapString = + FIXED_ONE_BYTE_STRING(env->isolate(), "TLSWrap"); + t->SetClassName(tlsWrapString); + t->InstanceTemplate()->SetInternalFieldCount(StreamBase::kInternalFieldCount); + + Local get_write_queue_size = + FunctionTemplate::New(env->isolate(), + GetWriteQueueSize, + Local(), + Signature::New(env->isolate(), t)); + t->PrototypeTemplate()->SetAccessorProperty( + env->write_queue_size_string(), + get_write_queue_size, + Local(), + static_cast(ReadOnly | DontDelete)); + + t->Inherit(AsyncWrap::GetConstructorTemplate(env)); + + env->SetProtoMethod(t, "certCbDone", CertCbDone); + env->SetProtoMethod(t, "destroySSL", DestroySSL); + env->SetProtoMethod(t, "enableCertCb", EnableCertCb); + env->SetProtoMethod(t, "endParser", EndParser); + env->SetProtoMethod(t, "enableKeylogCallback", EnableKeylogCallback); + env->SetProtoMethod(t, "enableSessionCallbacks", EnableSessionCallbacks); + env->SetProtoMethod(t, "enableTrace", EnableTrace); + env->SetProtoMethod(t, "getServername", GetServername); + env->SetProtoMethod(t, "loadSession", LoadSession); + env->SetProtoMethod(t, "newSessionDone", NewSessionDone); + env->SetProtoMethod(t, "receive", Receive); + env->SetProtoMethod(t, "renegotiate", Renegotiate); + env->SetProtoMethod(t, "requestOCSP", RequestOCSP); + env->SetProtoMethod(t, "setALPNProtocols", SetALPNProtocols); + env->SetProtoMethod(t, "setOCSPResponse", SetOCSPResponse); + env->SetProtoMethod(t, "setServername", SetServername); + env->SetProtoMethod(t, "setSession", SetSession); + env->SetProtoMethod(t, "setVerifyMode", SetVerifyMode); + env->SetProtoMethod(t, "start", Start); + + env->SetProtoMethodNoSideEffect(t, "exportKeyingMaterial", + ExportKeyingMaterial); + env->SetProtoMethodNoSideEffect(t, "isSessionReused", IsSessionReused); + env->SetProtoMethodNoSideEffect(t, "getALPNNegotiatedProtocol", + GetALPNNegotiatedProto); + env->SetProtoMethodNoSideEffect(t, "getCertificate", GetCertificate); + env->SetProtoMethodNoSideEffect(t, "getCipher", GetCipher); + env->SetProtoMethodNoSideEffect(t, "getEphemeralKeyInfo", + GetEphemeralKeyInfo); + env->SetProtoMethodNoSideEffect(t, "getFinished", GetFinished); + env->SetProtoMethodNoSideEffect(t, "getPeerCertificate", GetPeerCertificate); + env->SetProtoMethodNoSideEffect(t, "getPeerFinished", GetPeerFinished); + env->SetProtoMethodNoSideEffect(t, "getProtocol", GetProtocol); + env->SetProtoMethodNoSideEffect(t, "getSession", GetSession); + env->SetProtoMethodNoSideEffect(t, "getSharedSigalgs", GetSharedSigalgs); + env->SetProtoMethodNoSideEffect(t, "getTLSTicket", GetTLSTicket); + env->SetProtoMethodNoSideEffect(t, "verifyError", VerifyError); + +#ifdef SSL_set_max_send_fragment + env->SetProtoMethod(t, "setMaxSendFragment", SetMaxSendFragment); +#endif // SSL_set_max_send_fragment + +#ifndef OPENSSL_NO_PSK + env->SetProtoMethod(t, "enablePskCallback", EnablePskCallback); + env->SetProtoMethod(t, "setPskIdentityHint", SetPskIdentityHint); +#endif // !OPENSSL_NO_PSK + + StreamBase::AddMethods(env, t); + + Local fn = t->GetFunction(env->context()).ToLocalChecked(); + + env->set_tls_wrap_constructor_function(fn); + + target->Set(env->context(), tlsWrapString, fn).Check(); +} + +} // namespace crypto +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(tls_wrap, node::crypto::TLSWrap::Initialize) diff --git a/src/tls_wrap.h b/src/crypto/crypto_tls.h similarity index 59% rename from src/tls_wrap.h rename to src/crypto/crypto_tls.h index 3b9a6c85987d8f..6bcd3f411c71fe 100644 --- a/src/tls_wrap.h +++ b/src/crypto/crypto_tls.h @@ -19,13 +19,13 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -#ifndef SRC_TLS_WRAP_H_ -#define SRC_TLS_WRAP_H_ +#ifndef SRC_CRYPTO_CRYPTO_TLS_H_ +#define SRC_CRYPTO_CRYPTO_TLS_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include "node_crypto.h" // SSLWrap -#include "crypto/crypto_ssl.h" +#include "crypto/crypto_context.h" +#include "crypto/crypto_clienthello.h" #include "allocated_buffer.h" #include "async_wrap.h" @@ -37,27 +37,34 @@ #include namespace node { - -// Forward-declarations -class Environment; -class WriteWrap; namespace crypto { -class SecureContext; -class NodeBIO; -} class TLSWrap : public AsyncWrap, - public crypto::SSLWrap, public StreamBase, public StreamListener { public: - ~TLSWrap() override; + enum class Kind { + kClient, + kServer + }; static void Initialize(v8::Local target, v8::Local unused, v8::Local context, void* priv); + ~TLSWrap() override; + + bool is_cert_cb_running() const { return cert_cb_running_; } + bool is_waiting_cert_cb() const { return cert_cb_ != nullptr; } + bool has_session_callbacks() const { return session_callbacks_; } + void set_cert_cb_running(bool on = true) { cert_cb_running_ = on; } + void set_awaiting_new_session(bool on = true) { awaiting_new_session_ = on; } + void enable_session_callbacks() { session_callbacks_ = true; } + bool is_server() const { return kind_ == Kind::kServer; } + bool is_client() const { return kind_ == Kind::kClient; } + bool is_awaiting_new_session() const { return awaiting_new_session_; } + // Implement StreamBase: bool IsAlive() override; bool IsClosing() override; @@ -81,6 +88,9 @@ class TLSWrap : public AsyncWrap, // Reset error_ string to empty. Not related to "clear text". void ClearError() override; + v8::MaybeLocal ocsp_response() const; + void ClearOcspResponse(); + SSL_SESSION* ReleaseSession(); // Called by the done() callback of the 'newSession' event. void NewSessionDoneCb(); @@ -92,29 +102,40 @@ class TLSWrap : public AsyncWrap, std::string diagnostic_name() const override; - protected: - // Alternative to StreamListener::stream(), that returns a StreamBase instead - // of a StreamResource. - inline StreamBase* underlying_stream() { - return static_cast(stream_); - } + private: + // OpenSSL structures are opaque. Estimate SSL memory size for OpenSSL 1.1.1b: + // SSL: 6224 + // SSL->SSL3_STATE: 1040 + // ...some buffers: 42 * 1024 + // NOTE: Actually it is much more than this + static constexpr int64_t kExternalSize = 6224 + 1040 + 42 * 1024; - static const int kClearOutChunkSize = 16384; + static constexpr int kClearOutChunkSize = 16384; // Maximum number of bytes for hello parser - static const int kMaxHelloLength = 16384; + static constexpr int kMaxHelloLength = 16384; // Usual ServerHello + Certificate size - static const int kInitialClientBufferLength = 4096; + static constexpr int kInitialClientBufferLength = 4096; // Maximum number of buffers passed to uv_write() - static const int kSimultaneousBufferCount = 10; + static constexpr int kSimultaneousBufferCount = 10; + + typedef void (*CertCb)(void* arg); + + // Alternative to StreamListener::stream(), that returns a StreamBase instead + // of a StreamResource. + StreamBase* underlying_stream() const { + return static_cast(stream()); + } + + void WaitForCertCb(CertCb cb, void* arg); TLSWrap(Environment* env, v8::Local obj, Kind kind, StreamBase* stream, - crypto::SecureContext* sc); + SecureContext* sc); static void SSLInfoCallback(const SSL* ssl_, int where, int ret); void InitSSL(); @@ -127,25 +148,15 @@ class TLSWrap : public AsyncWrap, void EncOut(); // Write encrypted data from enc_out_ to underlying stream. void ClearIn(); // SSL_write() clear data "in" to SSL. void ClearOut(); // SSL_read() clear text "out" from SSL. + void Destroy(); // Call Done() on outstanding WriteWrap request. - bool InvokeQueued(int status, const char* error_str = nullptr); + void InvokeQueued(int status, const char* error_str = nullptr); // Drive the SSL state machine by attempting to SSL_read() and SSL_write() to // it. Transparent handshakes mean SSL_read() might trigger I/O on the // underlying stream even if there is no clear text to read or write. - inline void Cycle() { - // Prevent recursion - if (++cycle_depth_ > 1) - return; - - for (; cycle_depth_ > 0; cycle_depth_--) { - ClearIn(); - ClearOut(); - // EncIn() doesn't exist, it happens via stream listener callbacks. - EncOut(); - } - } + void Cycle(); // Implement StreamListener: // Returns buf that points into enc_in_. @@ -153,29 +164,66 @@ class TLSWrap : public AsyncWrap, void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override; void OnStreamAfterWrite(WriteWrap* w, int status) override; - v8::Local GetSSLError(int status, int* err, std::string* msg); + int SetCACerts(SecureContext* sc); - static void OnClientHelloParseEnd(void* arg); - static void Wrap(const v8::FunctionCallbackInfo& args); - static void Receive(const v8::FunctionCallbackInfo& args); - static void Start(const v8::FunctionCallbackInfo& args); - static void SetVerifyMode(const v8::FunctionCallbackInfo& args); - static void EnableSessionCallbacks( - const v8::FunctionCallbackInfo& args); + v8::MaybeLocal GetSSLError(int status, int* err, std::string* msg); + + static int SelectSNIContextCallback(SSL* s, int* ad, void* arg); + + static void CertCbDone(const v8::FunctionCallbackInfo& args); + static void DestroySSL(const v8::FunctionCallbackInfo& args); + static void EnableCertCb(const v8::FunctionCallbackInfo& args); static void EnableKeylogCallback( const v8::FunctionCallbackInfo& args); + static void EnableSessionCallbacks( + const v8::FunctionCallbackInfo& args); static void EnableTrace(const v8::FunctionCallbackInfo& args); - static void EnableCertCb(const v8::FunctionCallbackInfo& args); - static void DestroySSL(const v8::FunctionCallbackInfo& args); + static void EndParser(const v8::FunctionCallbackInfo& args); + static void ExportKeyingMaterial( + const v8::FunctionCallbackInfo& args); + static void GetALPNNegotiatedProto( + const v8::FunctionCallbackInfo& args); + static void GetCertificate(const v8::FunctionCallbackInfo& args); + static void GetCipher(const v8::FunctionCallbackInfo& args); + static void GetEphemeralKeyInfo( + const v8::FunctionCallbackInfo& args); + static void GetFinished(const v8::FunctionCallbackInfo& args); + static void GetPeerCertificate( + const v8::FunctionCallbackInfo& args); + static void GetPeerFinished(const v8::FunctionCallbackInfo& args); + static void GetProtocol(const v8::FunctionCallbackInfo& args); static void GetServername(const v8::FunctionCallbackInfo& args); + static void GetSession(const v8::FunctionCallbackInfo& args); + static void GetSharedSigalgs(const v8::FunctionCallbackInfo& args); + static void GetTLSTicket(const v8::FunctionCallbackInfo& args); + static void GetWriteQueueSize( + const v8::FunctionCallbackInfo& info); + static void IsSessionReused(const v8::FunctionCallbackInfo& args); + static void LoadSession(const v8::FunctionCallbackInfo& args); + static void NewSessionDone(const v8::FunctionCallbackInfo& args); + static void OnClientHelloParseEnd(void* arg); + static void Receive(const v8::FunctionCallbackInfo& args); + static void Renegotiate(const v8::FunctionCallbackInfo& args); + static void RequestOCSP(const v8::FunctionCallbackInfo& args); + static void SetALPNProtocols(const v8::FunctionCallbackInfo& args); + static void SetOCSPResponse(const v8::FunctionCallbackInfo& args); static void SetServername(const v8::FunctionCallbackInfo& args); - static int SelectSNIContextCallback(SSL* s, int* ad, void* arg); + static void SetSession(const v8::FunctionCallbackInfo& args); + static void SetVerifyMode(const v8::FunctionCallbackInfo& args); + static void Start(const v8::FunctionCallbackInfo& args); + static void VerifyError(const v8::FunctionCallbackInfo& args); + static void Wrap(const v8::FunctionCallbackInfo& args); -#ifndef OPENSSL_NO_PSK - static void SetPskIdentityHint( +#ifdef SSL_set_max_send_fragment + static void SetMaxSendFragment( const v8::FunctionCallbackInfo& args); +#endif // SSL_set_max_send_fragment + +#ifndef OPENSSL_NO_PSK static void EnablePskCallback( const v8::FunctionCallbackInfo& args); + static void SetPskIdentityHint( + const v8::FunctionCallbackInfo& args); static unsigned int PskServerCallback(SSL* s, const char* identity, unsigned char* psk, @@ -188,7 +236,15 @@ class TLSWrap : public AsyncWrap, unsigned int max_psk_len); #endif - crypto::SecureContext* sc_; + Environment* const env_; + Kind kind_; + SSLSessionPointer next_sess_; + SSLPointer ssl_; + ClientHelloParser hello_parser_; + v8::Global ocsp_response_; + BaseObjectPtr sni_context_; + BaseObjectPtr sc_; + // BIO buffers hold encrypted data. BIO* enc_in_ = nullptr; // StreamListener fills this for SSL_read(). BIO* enc_out_ = nullptr; // SSL_write()/handshake fills this for EncOut(). @@ -196,28 +252,38 @@ class TLSWrap : public AsyncWrap, AllocatedBuffer pending_cleartext_input_; size_t write_size_ = 0; BaseObjectPtr current_write_; - bool in_dowrite_ = false; BaseObjectPtr current_empty_write_; - bool write_callback_scheduled_ = false; - bool started_ = false; - bool established_ = false; - bool shutdown_ = false; std::string error_; - int cycle_depth_ = 0; - // If true - delivered EOF to the js-land, either after `close_notify`, or - // after the `UV_EOF` on socket. + bool session_callbacks_ = false; + bool awaiting_new_session_ = false; + bool in_dowrite_ = false; + bool started_ = false; + bool shutdown_ = false; + bool cert_cb_running_ = false; bool eof_ = false; - private: - static void GetWriteQueueSize( - const v8::FunctionCallbackInfo& info); + // TODO(@jasnell): These state flags should be revisited. + // The established_ flag indicates that the handshake is + // completed. The write_callback_scheduled_ flag is less + // clear -- once it is set to true, it is never set to + // false and it is only set to true after established_ + // is set to true, so it's likely redundant. + bool established_ = false; + bool write_callback_scheduled_ = false; + + int cycle_depth_ = 0; + + // SSL_set_cert_cb + CertCb cert_cb_ = nullptr; + void* cert_cb_arg_ = nullptr; - crypto::BIOPointer bio_trace_; + BIOPointer bio_trace_; }; +} // namespace crypto } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#endif // SRC_TLS_WRAP_H_ +#endif // SRC_CRYPTO_CRYPTO_TLS_H_ diff --git a/src/node_crypto.h b/src/node_crypto.h index a12f353af90b89..2d7837c049ca32 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -47,7 +47,7 @@ #include "crypto/crypto_scrypt.h" #include "crypto/crypto_sig.h" #include "crypto/crypto_spkac.h" -#include "crypto/crypto_ssl.h" +#include "crypto/crypto_tls.h" #include "crypto/crypto_timing.h" #include "crypto/crypto_util.h" diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc deleted file mode 100644 index 908e3899db35be..00000000000000 --- a/src/tls_wrap.cc +++ /dev/null @@ -1,1326 +0,0 @@ -// 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. - -#include "tls_wrap.h" -#include "allocated_buffer-inl.h" -#include "async_wrap-inl.h" -#include "debug_utils-inl.h" -#include "memory_tracker-inl.h" -#include "node_buffer.h" // Buffer -#include "crypto/crypto_context.h" -#include "crypto/crypto_util.h" -#include "crypto/crypto_bio.h" // NodeBIO -#include "crypto/crypto_clienthello-inl.h" // ClientHelloParser -#include "node_errors.h" -#include "stream_base-inl.h" -#include "util-inl.h" - -namespace node { - -using crypto::SecureContext; -using crypto::SSLWrap; -using v8::Context; -using v8::DontDelete; -using v8::EscapableHandleScope; -using v8::Exception; -using v8::Function; -using v8::FunctionCallbackInfo; -using v8::FunctionTemplate; -using v8::HandleScope; -using v8::Integer; -using v8::Isolate; -using v8::Local; -using v8::MaybeLocal; -using v8::Object; -using v8::PropertyAttribute; -using v8::ReadOnly; -using v8::Signature; -using v8::String; -using v8::Value; - -TLSWrap::TLSWrap(Environment* env, - Local obj, - Kind kind, - StreamBase* stream, - SecureContext* sc) - : AsyncWrap(env, obj, AsyncWrap::PROVIDER_TLSWRAP), - SSLWrap(env, sc, kind), - StreamBase(env), - sc_(sc) { - MakeWeak(); - StreamBase::AttachToObject(GetObject()); - - // sc comes from an Unwrap. Make sure it was assigned. - CHECK_NOT_NULL(sc); - - // We've our own session callbacks - SSL_CTX_sess_set_get_cb(sc_->ctx_.get(), - SSLWrap::GetSessionCallback); - SSL_CTX_sess_set_new_cb(sc_->ctx_.get(), - SSLWrap::NewSessionCallback); - - stream->PushStreamListener(this); - - InitSSL(); - Debug(this, "Created new TLSWrap"); -} - - -TLSWrap::~TLSWrap() { - Debug(this, "~TLSWrap()"); - sc_ = nullptr; -} - - -bool TLSWrap::InvokeQueued(int status, const char* error_str) { - Debug(this, "InvokeQueued(%d, %s)", status, error_str); - if (!write_callback_scheduled_) - return false; - - if (current_write_) { - BaseObjectPtr current_write = std::move(current_write_); - current_write_.reset(); - WriteWrap* w = WriteWrap::FromObject(current_write); - w->Done(status, error_str); - } - - return true; -} - - -void TLSWrap::NewSessionDoneCb() { - Debug(this, "NewSessionDoneCb()"); - Cycle(); -} - - -void TLSWrap::InitSSL() { - // Initialize SSL – OpenSSL takes ownership of these. - enc_in_ = crypto::NodeBIO::New(env()).release(); - enc_out_ = crypto::NodeBIO::New(env()).release(); - - SSL_set_bio(ssl_.get(), enc_in_, enc_out_); - - // NOTE: This could be overridden in SetVerifyMode - SSL_set_verify(ssl_.get(), SSL_VERIFY_NONE, crypto::VerifyCallback); - -#ifdef SSL_MODE_RELEASE_BUFFERS - SSL_set_mode(ssl_.get(), SSL_MODE_RELEASE_BUFFERS); -#endif // SSL_MODE_RELEASE_BUFFERS - - // This is default in 1.1.1, but set it anyway, Cycle() doesn't currently - // re-call ClearIn() if SSL_read() returns SSL_ERROR_WANT_READ, so data can be - // left sitting in the incoming enc_in_ and never get processed. - // - https://wiki.openssl.org/index.php/TLS1.3#Non-application_data_records - SSL_set_mode(ssl_.get(), SSL_MODE_AUTO_RETRY); - -#ifdef OPENSSL_IS_BORINGSSL - // OpenSSL allows renegotiation by default, but BoringSSL disables it. - // Configure BoringSSL to match OpenSSL's behavior. - SSL_set_renegotiate_mode(ssl_.get(), ssl_renegotiate_freely); -#endif - - SSL_set_app_data(ssl_.get(), this); - // Using InfoCallback isn't how we are supposed to check handshake progress: - // https://github.com/openssl/openssl/issues/7199#issuecomment-420915993 - // - // Note on when this gets called on various openssl versions: - // https://github.com/openssl/openssl/issues/7199#issuecomment-420670544 - SSL_set_info_callback(ssl_.get(), SSLInfoCallback); - - if (is_server()) { - SSL_CTX_set_tlsext_servername_callback(sc_->ctx_.get(), - SelectSNIContextCallback); - } - - ConfigureSecureContext(sc_); - - SSL_set_cert_cb(ssl_.get(), SSLWrap::SSLCertCallback, this); - - if (is_server()) { - SSL_set_accept_state(ssl_.get()); - } else if (is_client()) { - // Enough space for server response (hello, cert) - crypto::NodeBIO::FromBIO(enc_in_)->set_initial(kInitialClientBufferLength); - SSL_set_connect_state(ssl_.get()); - } else { - // Unexpected - ABORT(); - } -} - - -void TLSWrap::Wrap(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK_EQ(args.Length(), 3); - CHECK(args[0]->IsObject()); - CHECK(args[1]->IsObject()); - CHECK(args[2]->IsBoolean()); - - Local sc = args[1].As(); - Kind kind = args[2]->IsTrue() ? SSLWrap::kServer : - SSLWrap::kClient; - - StreamBase* stream = StreamBase::FromObject(args[0].As()); - CHECK_NOT_NULL(stream); - - Local obj; - if (!env->tls_wrap_constructor_function() - ->NewInstance(env->context()) - .ToLocal(&obj)) { - return; - } - - TLSWrap* res = new TLSWrap(env, obj, kind, stream, Unwrap(sc)); - - args.GetReturnValue().Set(res->object()); -} - - -void TLSWrap::Receive(const FunctionCallbackInfo& args) { - TLSWrap* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); - - ArrayBufferViewContents buffer(args[0]); - const char* data = buffer.data(); - size_t len = buffer.length(); - Debug(wrap, "Receiving %zu bytes injected from JS", len); - - // Copy given buffer entirely or partiall if handle becomes closed - while (len > 0 && wrap->IsAlive() && !wrap->IsClosing()) { - uv_buf_t buf = wrap->OnStreamAlloc(len); - size_t copy = buf.len > len ? len : buf.len; - memcpy(buf.base, data, copy); - buf.len = copy; - wrap->OnStreamRead(copy, buf); - - data += copy; - len -= copy; - } -} - - -void TLSWrap::Start(const FunctionCallbackInfo& args) { - TLSWrap* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); - - CHECK(!wrap->started_); - - wrap->started_ = true; - - // Send ClientHello handshake - CHECK(wrap->is_client()); - // Seems odd to read when when we want to send, but SSL_read() triggers a - // handshake if a session isn't established, and handshake will cause - // encrypted data to become available for output. - wrap->ClearOut(); - wrap->EncOut(); -} - - -void TLSWrap::SSLInfoCallback(const SSL* ssl_, int where, int ret) { - if (!(where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE))) - return; - - // SSL_renegotiate_pending() should take `const SSL*`, but it does not. - SSL* ssl = const_cast(ssl_); - TLSWrap* c = static_cast(SSL_get_app_data(ssl_)); - Environment* env = c->env(); - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); - Local object = c->object(); - - if (where & SSL_CB_HANDSHAKE_START) { - Debug(c, "SSLInfoCallback(SSL_CB_HANDSHAKE_START);"); - // Start is tracked to limit number and frequency of renegotiation attempts, - // since excessive renegotiation may be an attack. - Local callback; - - if (object->Get(env->context(), env->onhandshakestart_string()) - .ToLocal(&callback) && callback->IsFunction()) { - Local argv[] = { env->GetNow() }; - c->MakeCallback(callback.As(), arraysize(argv), argv); - } - } - - // SSL_CB_HANDSHAKE_START and SSL_CB_HANDSHAKE_DONE are called - // sending HelloRequest in OpenSSL-1.1.1. - // We need to check whether this is in a renegotiation state or not. - if (where & SSL_CB_HANDSHAKE_DONE && !SSL_renegotiate_pending(ssl)) { - Debug(c, "SSLInfoCallback(SSL_CB_HANDSHAKE_DONE);"); - CHECK(!SSL_renegotiate_pending(ssl)); - Local callback; - - c->established_ = true; - - if (object->Get(env->context(), env->onhandshakedone_string()) - .ToLocal(&callback) && callback->IsFunction()) { - c->MakeCallback(callback.As(), 0, nullptr); - } - } -} - - -void TLSWrap::EncOut() { - Debug(this, "Trying to write encrypted output"); - - // Ignore cycling data if ClientHello wasn't yet parsed - if (!hello_parser_.IsEnded()) { - Debug(this, "Returning from EncOut(), hello_parser_ active"); - return; - } - - // Write in progress - if (write_size_ != 0) { - Debug(this, "Returning from EncOut(), write currently in progress"); - return; - } - - // Wait for `newSession` callback to be invoked - if (is_awaiting_new_session()) { - Debug(this, "Returning from EncOut(), awaiting new session"); - return; - } - - // Split-off queue - if (established_ && current_write_) { - Debug(this, "EncOut() setting write_callback_scheduled_"); - write_callback_scheduled_ = true; - } - - if (ssl_ == nullptr) { - Debug(this, "Returning from EncOut(), ssl_ == nullptr"); - return; - } - - // No encrypted output ready to write to the underlying stream. - if (BIO_pending(enc_out_) == 0) { - Debug(this, "No pending encrypted output"); - if (pending_cleartext_input_.size() == 0) { - if (!in_dowrite_) { - Debug(this, "No pending cleartext input, not inside DoWrite()"); - InvokeQueued(0); - } else { - Debug(this, "No pending cleartext input, inside DoWrite()"); - // TODO(@sam-github, @addaleax) If in_dowrite_ is true, appdata was - // passed to SSL_write(). If we are here, the data was not encrypted to - // enc_out_ yet. Calling Done() "works", but since the write is not - // flushed, its too soon. Just returning and letting the next EncOut() - // call Done() passes the test suite, but without more careful analysis, - // its not clear if it is always correct. Not calling Done() could block - // data flow, so for now continue to call Done(), just do it in the next - // tick. - BaseObjectPtr strong_ref{this}; - env()->SetImmediate([this, strong_ref](Environment* env) { - InvokeQueued(0); - }); - } - } - return; - } - - char* data[kSimultaneousBufferCount]; - size_t size[arraysize(data)]; - size_t count = arraysize(data); - write_size_ = crypto::NodeBIO::FromBIO(enc_out_)->PeekMultiple(data, - size, - &count); - CHECK(write_size_ != 0 && count != 0); - - uv_buf_t buf[arraysize(data)]; - uv_buf_t* bufs = buf; - for (size_t i = 0; i < count; i++) - buf[i] = uv_buf_init(data[i], size[i]); - - Debug(this, "Writing %zu buffers to the underlying stream", count); - StreamWriteResult res = underlying_stream()->Write(bufs, count); - if (res.err != 0) { - InvokeQueued(res.err); - return; - } - - if (!res.async) { - Debug(this, "Write finished synchronously"); - HandleScope handle_scope(env()->isolate()); - - // Simulate asynchronous finishing, TLS cannot handle this at the moment. - BaseObjectPtr strong_ref{this}; - env()->SetImmediate([this, strong_ref](Environment* env) { - OnStreamAfterWrite(nullptr, 0); - }); - } -} - - -void TLSWrap::OnStreamAfterWrite(WriteWrap* req_wrap, int status) { - Debug(this, "OnStreamAfterWrite(status = %d)", status); - if (current_empty_write_) { - Debug(this, "Had empty write"); - BaseObjectPtr current_empty_write = - std::move(current_empty_write_); - current_empty_write_.reset(); - WriteWrap* finishing = WriteWrap::FromObject(current_empty_write); - finishing->Done(status); - return; - } - - if (ssl_ == nullptr) { - Debug(this, "ssl_ == nullptr, marking as cancelled"); - status = UV_ECANCELED; - } - - // Handle error - if (status) { - if (shutdown_) { - Debug(this, "Ignoring error after shutdown"); - return; - } - - // Notify about error - InvokeQueued(status); - return; - } - - // Commit - crypto::NodeBIO::FromBIO(enc_out_)->Read(nullptr, write_size_); - - // Ensure that the progress will be made and `InvokeQueued` will be called. - ClearIn(); - - // Try writing more data - write_size_ = 0; - EncOut(); -} - - -Local TLSWrap::GetSSLError(int status, int* err, std::string* msg) { - EscapableHandleScope scope(env()->isolate()); - - // ssl_ is already destroyed in reading EOF by close notify alert. - if (ssl_ == nullptr) - return Local(); - - *err = SSL_get_error(ssl_.get(), status); - switch (*err) { - case SSL_ERROR_NONE: - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - case SSL_ERROR_WANT_X509_LOOKUP: - return Local(); - - case SSL_ERROR_ZERO_RETURN: - return scope.Escape(env()->zero_return_string()); - - case SSL_ERROR_SSL: - case SSL_ERROR_SYSCALL: - { - unsigned long ssl_err = ERR_peek_error(); // NOLINT(runtime/int) - BIO* bio = BIO_new(BIO_s_mem()); - ERR_print_errors(bio); - - BUF_MEM* mem; - BIO_get_mem_ptr(bio, &mem); - - Isolate* isolate = env()->isolate(); - Local context = isolate->GetCurrentContext(); - - Local message = - OneByteString(isolate, mem->data, mem->length); - Local exception = Exception::Error(message); - Local obj = exception->ToObject(context).ToLocalChecked(); - - const char* ls = ERR_lib_error_string(ssl_err); - const char* fs = ERR_func_error_string(ssl_err); - const char* rs = ERR_reason_error_string(ssl_err); - - if (ls != nullptr) - obj->Set(context, env()->library_string(), - OneByteString(isolate, ls)).Check(); - if (fs != nullptr) - obj->Set(context, env()->function_string(), - OneByteString(isolate, fs)).Check(); - if (rs != nullptr) { - obj->Set(context, env()->reason_string(), - OneByteString(isolate, rs)).Check(); - - // SSL has no API to recover the error name from the number, so we - // transform reason strings like "this error" to "ERR_SSL_THIS_ERROR", - // which ends up being close to the original error macro name. - std::string code(rs); - - for (auto& c : code) { - if (c == ' ') - c = '_'; - else - c = ToUpper(c); - } - obj->Set(context, env()->code_string(), - OneByteString(isolate, ("ERR_SSL_" + code).c_str())) - .Check(); - } - - if (msg != nullptr) - msg->assign(mem->data, mem->data + mem->length); - - BIO_free_all(bio); - - return scope.Escape(exception); - } - - default: - UNREACHABLE(); - } - UNREACHABLE(); -} - - -void TLSWrap::ClearOut() { - Debug(this, "Trying to read cleartext output"); - // Ignore cycling data if ClientHello wasn't yet parsed - if (!hello_parser_.IsEnded()) { - Debug(this, "Returning from ClearOut(), hello_parser_ active"); - return; - } - - // No reads after EOF - if (eof_) { - Debug(this, "Returning from ClearOut(), EOF reached"); - return; - } - - if (ssl_ == nullptr) { - Debug(this, "Returning from ClearOut(), ssl_ == nullptr"); - return; - } - - crypto::MarkPopErrorOnReturn mark_pop_error_on_return; - - char out[kClearOutChunkSize]; - int read; - for (;;) { - read = SSL_read(ssl_.get(), out, sizeof(out)); - Debug(this, "Read %d bytes of cleartext output", read); - - if (read <= 0) - break; - - char* current = out; - while (read > 0) { - int avail = read; - - uv_buf_t buf = EmitAlloc(avail); - if (static_cast(buf.len) < avail) - avail = buf.len; - memcpy(buf.base, current, avail); - EmitRead(avail, buf); - - // Caveat emptor: OnRead() calls into JS land which can result in - // the SSL context object being destroyed. We have to carefully - // check that ssl_ != nullptr afterwards. - if (ssl_ == nullptr) { - Debug(this, "Returning from read loop, ssl_ == nullptr"); - return; - } - - read -= avail; - current += avail; - } - } - - int flags = SSL_get_shutdown(ssl_.get()); - if (!eof_ && flags & SSL_RECEIVED_SHUTDOWN) { - eof_ = true; - EmitRead(UV_EOF); - } - - // We need to check whether an error occurred or the connection was - // shutdown cleanly (SSL_ERROR_ZERO_RETURN) even when read == 0. - // See node#1642 and SSL_read(3SSL) for details. - if (read <= 0) { - HandleScope handle_scope(env()->isolate()); - int err; - Local arg = GetSSLError(read, &err, nullptr); - - // Ignore ZERO_RETURN after EOF, it is basically not a error - if (err == SSL_ERROR_ZERO_RETURN && eof_) - return; - - if (!arg.IsEmpty()) { - Debug(this, "Got SSL error (%d), calling onerror", err); - // When TLS Alert are stored in wbio, - // it should be flushed to socket before destroyed. - if (BIO_pending(enc_out_) != 0) - EncOut(); - - MakeCallback(env()->onerror_string(), 1, &arg); - } - } -} - - -void TLSWrap::ClearIn() { - Debug(this, "Trying to write cleartext input"); - // Ignore cycling data if ClientHello wasn't yet parsed - if (!hello_parser_.IsEnded()) { - Debug(this, "Returning from ClearIn(), hello_parser_ active"); - return; - } - - if (ssl_ == nullptr) { - Debug(this, "Returning from ClearIn(), ssl_ == nullptr"); - return; - } - - if (pending_cleartext_input_.size() == 0) { - Debug(this, "Returning from ClearIn(), no pending data"); - return; - } - - AllocatedBuffer data = std::move(pending_cleartext_input_); - crypto::MarkPopErrorOnReturn mark_pop_error_on_return; - - crypto::NodeBIO::FromBIO(enc_out_)->set_allocate_tls_hint(data.size()); - int written = SSL_write(ssl_.get(), data.data(), data.size()); - Debug(this, "Writing %zu bytes, written = %d", data.size(), written); - CHECK(written == -1 || written == static_cast(data.size())); - - // All written - if (written != -1) { - Debug(this, "Successfully wrote all data to SSL"); - return; - } - - // Error or partial write - HandleScope handle_scope(env()->isolate()); - Context::Scope context_scope(env()->context()); - - int err; - std::string error_str; - Local arg = GetSSLError(written, &err, &error_str); - if (!arg.IsEmpty()) { - Debug(this, "Got SSL error (%d)", err); - write_callback_scheduled_ = true; - // TODO(@sam-github) Should forward an error object with - // .code/.function/.etc, if possible. - InvokeQueued(UV_EPROTO, error_str.c_str()); - } else { - Debug(this, "Pushing data back"); - // Push back the not-yet-written data. This can be skipped in the error - // case because no further writes would succeed anyway. - pending_cleartext_input_ = std::move(data); - } -} - - -std::string TLSWrap::diagnostic_name() const { - std::string name = "TLSWrap "; - if (is_server()) - name += "server ("; - else - name += "client ("; - name += std::to_string(static_cast(get_async_id())) + ")"; - return name; -} - - -AsyncWrap* TLSWrap::GetAsyncWrap() { - return static_cast(this); -} - - -bool TLSWrap::IsIPCPipe() { - return underlying_stream()->IsIPCPipe(); -} - - -int TLSWrap::GetFD() { - return underlying_stream()->GetFD(); -} - - -bool TLSWrap::IsAlive() { - return ssl_ != nullptr && - stream_ != nullptr && - underlying_stream()->IsAlive(); -} - - -bool TLSWrap::IsClosing() { - return underlying_stream()->IsClosing(); -} - - - -int TLSWrap::ReadStart() { - Debug(this, "ReadStart()"); - if (stream_ != nullptr) - return stream_->ReadStart(); - return 0; -} - - -int TLSWrap::ReadStop() { - Debug(this, "ReadStop()"); - if (stream_ != nullptr) - return stream_->ReadStop(); - return 0; -} - - -const char* TLSWrap::Error() const { - return error_.empty() ? nullptr : error_.c_str(); -} - - -void TLSWrap::ClearError() { - error_.clear(); -} - - -// Called by StreamBase::Write() to request async write of clear text into SSL. -// TODO(@sam-github) Should there be a TLSWrap::DoTryWrite()? -int TLSWrap::DoWrite(WriteWrap* w, - uv_buf_t* bufs, - size_t count, - uv_stream_t* send_handle) { - CHECK_NULL(send_handle); - Debug(this, "DoWrite()"); - - if (ssl_ == nullptr) { - ClearError(); - error_ = "Write after DestroySSL"; - return UV_EPROTO; - } - - size_t length = 0; - size_t i; - size_t nonempty_i = 0; - size_t nonempty_count = 0; - for (i = 0; i < count; i++) { - length += bufs[i].len; - if (bufs[i].len > 0) { - nonempty_i = i; - nonempty_count += 1; - } - } - - // We want to trigger a Write() on the underlying stream to drive the stream - // system, but don't want to encrypt empty buffers into a TLS frame, so see - // if we can find something to Write(). - // First, call ClearOut(). It does an SSL_read(), which might cause handshake - // or other internal messages to be encrypted. If it does, write them later - // with EncOut(). - // If there is still no encrypted output, call Write(bufs) on the underlying - // stream. Since the bufs are empty, it won't actually write non-TLS data - // onto the socket, we just want the side-effects. After, make sure the - // WriteWrap was accepted by the stream, or that we call Done() on it. - if (length == 0) { - Debug(this, "Empty write"); - ClearOut(); - if (BIO_pending(enc_out_) == 0) { - Debug(this, "No pending encrypted output, writing to underlying stream"); - CHECK(!current_empty_write_); - current_empty_write_.reset(w->GetAsyncWrap()); - StreamWriteResult res = - underlying_stream()->Write(bufs, count, send_handle); - if (!res.async) { - BaseObjectPtr strong_ref{this}; - env()->SetImmediate([this, strong_ref](Environment* env) { - OnStreamAfterWrite(WriteWrap::FromObject(current_empty_write_), 0); - }); - } - return 0; - } - } - - // Store the current write wrap - CHECK(!current_write_); - current_write_.reset(w->GetAsyncWrap()); - - // Write encrypted data to underlying stream and call Done(). - if (length == 0) { - EncOut(); - return 0; - } - - AllocatedBuffer data; - crypto::MarkPopErrorOnReturn mark_pop_error_on_return; - - int written = 0; - - // It is common for zero length buffers to be written, - // don't copy data if there there is one buffer with data - // and one or more zero length buffers. - // _http_outgoing.js writes a zero length buffer in - // in OutgoingMessage.prototype.end. If there was a large amount - // of data supplied to end() there is no sense allocating - // and copying it when it could just be used. - - if (nonempty_count != 1) { - data = AllocatedBuffer::AllocateManaged(env(), length); - size_t offset = 0; - for (i = 0; i < count; i++) { - memcpy(data.data() + offset, bufs[i].base, bufs[i].len); - offset += bufs[i].len; - } - - crypto::NodeBIO::FromBIO(enc_out_)->set_allocate_tls_hint(length); - written = SSL_write(ssl_.get(), data.data(), length); - } else { - // Only one buffer: try to write directly, only store if it fails - uv_buf_t* buf = &bufs[nonempty_i]; - crypto::NodeBIO::FromBIO(enc_out_)->set_allocate_tls_hint(buf->len); - written = SSL_write(ssl_.get(), buf->base, buf->len); - - if (written == -1) { - data = AllocatedBuffer::AllocateManaged(env(), length); - memcpy(data.data(), buf->base, buf->len); - } - } - - CHECK(written == -1 || written == static_cast(length)); - Debug(this, "Writing %zu bytes, written = %d", length, written); - - if (written == -1) { - int err; - Local arg = GetSSLError(written, &err, &error_); - - // If we stopped writing because of an error, it's fatal, discard the data. - if (!arg.IsEmpty()) { - Debug(this, "Got SSL error (%d), returning UV_EPROTO", err); - current_write_.reset(); - return UV_EPROTO; - } - - Debug(this, "Saving data for later write"); - // Otherwise, save unwritten data so it can be written later by ClearIn(). - CHECK_EQ(pending_cleartext_input_.size(), 0); - pending_cleartext_input_ = std::move(data); - } - - // Write any encrypted/handshake output that may be ready. - // Guard against sync call of current_write_->Done(), its unsupported. - in_dowrite_ = true; - EncOut(); - in_dowrite_ = false; - - return 0; -} - - -uv_buf_t TLSWrap::OnStreamAlloc(size_t suggested_size) { - CHECK_NOT_NULL(ssl_); - - size_t size = suggested_size; - char* base = crypto::NodeBIO::FromBIO(enc_in_)->PeekWritable(&size); - return uv_buf_init(base, size); -} - - -void TLSWrap::OnStreamRead(ssize_t nread, const uv_buf_t& buf) { - Debug(this, "Read %zd bytes from underlying stream", nread); - if (nread < 0) { - // Error should be emitted only after all data was read - ClearOut(); - - // Ignore EOF if received close_notify - if (nread == UV_EOF) { - if (eof_) - return; - eof_ = true; - } - - EmitRead(nread); - return; - } - - // DestroySSL() is the only thing that un-sets ssl_, but that also removes - // this TLSWrap as a stream listener, so we should not receive OnStreamRead() - // calls anymore. - CHECK(ssl_); - - // Commit the amount of data actually read into the peeked/allocated buffer - // from the underlying stream. - crypto::NodeBIO* enc_in = crypto::NodeBIO::FromBIO(enc_in_); - enc_in->Commit(nread); - - // Parse ClientHello first, if we need to. It's only parsed if session event - // listeners are used on the server side. "ended" is the initial state, so - // can mean parsing was never started, or that parsing is finished. Either - // way, ended means we can give the buffered data to SSL. - if (!hello_parser_.IsEnded()) { - size_t avail = 0; - uint8_t* data = reinterpret_cast(enc_in->Peek(&avail)); - CHECK_IMPLIES(data == nullptr, avail == 0); - Debug(this, "Passing %zu bytes to the hello parser", avail); - return hello_parser_.Parse(data, avail); - } - - // Cycle OpenSSL's state - Cycle(); -} - - -ShutdownWrap* TLSWrap::CreateShutdownWrap(Local req_wrap_object) { - return underlying_stream()->CreateShutdownWrap(req_wrap_object); -} - - -int TLSWrap::DoShutdown(ShutdownWrap* req_wrap) { - Debug(this, "DoShutdown()"); - crypto::MarkPopErrorOnReturn mark_pop_error_on_return; - - if (ssl_ && SSL_shutdown(ssl_.get()) == 0) - SSL_shutdown(ssl_.get()); - - shutdown_ = true; - EncOut(); - return stream_->DoShutdown(req_wrap); -} - - -void TLSWrap::SetVerifyMode(const FunctionCallbackInfo& args) { - TLSWrap* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); - - CHECK_EQ(args.Length(), 2); - CHECK(args[0]->IsBoolean()); - CHECK(args[1]->IsBoolean()); - CHECK_NOT_NULL(wrap->ssl_); - - int verify_mode; - if (wrap->is_server()) { - bool request_cert = args[0]->IsTrue(); - if (!request_cert) { - // If no cert is requested, there will be none to reject as unauthorized. - verify_mode = SSL_VERIFY_NONE; - } else { - bool reject_unauthorized = args[1]->IsTrue(); - verify_mode = SSL_VERIFY_PEER; - if (reject_unauthorized) - verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; - } - } else { - // Servers always send a cert if the cipher is not anonymous (anon is - // disabled by default), so use VERIFY_NONE and check the cert after the - // handshake has completed. - verify_mode = SSL_VERIFY_NONE; - } - - // Always allow a connection. We'll reject in javascript. - SSL_set_verify(wrap->ssl_.get(), verify_mode, crypto::VerifyCallback); -} - - -void TLSWrap::EnableSessionCallbacks( - const FunctionCallbackInfo& args) { - TLSWrap* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); - CHECK_NOT_NULL(wrap->ssl_); - wrap->enable_session_callbacks(); - - // Clients don't use the HelloParser. - if (wrap->is_client()) - return; - - crypto::NodeBIO::FromBIO(wrap->enc_in_)->set_initial(kMaxHelloLength); - wrap->hello_parser_.Start(SSLWrap::OnClientHello, - OnClientHelloParseEnd, - wrap); -} - -void TLSWrap::EnableKeylogCallback( - const FunctionCallbackInfo& args) { - TLSWrap* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); - CHECK_NOT_NULL(wrap->sc_); - SSL_CTX_set_keylog_callback(wrap->sc_->ctx_.get(), - SSLWrap::KeylogCallback); -} - -// Check required capabilities were not excluded from the OpenSSL build: -// - OPENSSL_NO_SSL_TRACE excludes SSL_trace() -// - OPENSSL_NO_STDIO excludes BIO_new_fp() -// HAVE_SSL_TRACE is available on the internal tcp_wrap binding for the tests. -#if defined(OPENSSL_NO_SSL_TRACE) || defined(OPENSSL_NO_STDIO) -# define HAVE_SSL_TRACE 0 -#else -# define HAVE_SSL_TRACE 1 -#endif - -void TLSWrap::EnableTrace( - const FunctionCallbackInfo& args) { - TLSWrap* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); - -#if HAVE_SSL_TRACE - if (wrap->ssl_) { - wrap->bio_trace_.reset(BIO_new_fp(stderr, BIO_NOCLOSE | BIO_FP_TEXT)); - SSL_set_msg_callback(wrap->ssl_.get(), [](int write_p, int version, int - content_type, const void* buf, size_t len, SSL* ssl, void* arg) - -> void { - // BIO_write(), etc., called by SSL_trace, may error. The error should - // be ignored, trace is a "best effort", and its usually because stderr - // is a non-blocking pipe, and its buffer has overflowed. Leaving errors - // on the stack that can get picked up by later SSL_ calls causes - // unwanted failures in SSL_ calls, so keep the error stack unchanged. - crypto::MarkPopErrorOnReturn mark_pop_error_on_return; - SSL_trace(write_p, version, content_type, buf, len, ssl, arg); - }); - SSL_set_msg_callback_arg(wrap->ssl_.get(), wrap->bio_trace_.get()); - } -#endif -} - -void TLSWrap::DestroySSL(const FunctionCallbackInfo& args) { - TLSWrap* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); - Debug(wrap, "DestroySSL()"); - - // If there is a write happening, mark it as finished. - wrap->write_callback_scheduled_ = true; - - // And destroy - wrap->InvokeQueued(UV_ECANCELED, "Canceled because of SSL destruction"); - - // Destroy the SSL structure and friends - wrap->SSLWrap::DestroySSL(); - wrap->enc_in_ = nullptr; - wrap->enc_out_ = nullptr; - - if (wrap->stream_ != nullptr) - wrap->stream_->RemoveStreamListener(wrap); - Debug(wrap, "DestroySSL() finished"); -} - - -void TLSWrap::EnableCertCb(const FunctionCallbackInfo& args) { - TLSWrap* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); - wrap->WaitForCertCb(OnClientHelloParseEnd, wrap); -} - - -void TLSWrap::OnClientHelloParseEnd(void* arg) { - TLSWrap* c = static_cast(arg); - Debug(c, "OnClientHelloParseEnd()"); - c->Cycle(); -} - - -void TLSWrap::GetServername(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - TLSWrap* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); - - CHECK_NOT_NULL(wrap->ssl_); - - const char* servername = SSL_get_servername(wrap->ssl_.get(), - TLSEXT_NAMETYPE_host_name); - if (servername != nullptr) { - args.GetReturnValue().Set(OneByteString(env->isolate(), servername)); - } else { - args.GetReturnValue().Set(false); - } -} - - -void TLSWrap::SetServername(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - TLSWrap* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); - - CHECK_EQ(args.Length(), 1); - CHECK(args[0]->IsString()); - CHECK(!wrap->started_); - CHECK(wrap->is_client()); - - CHECK_NOT_NULL(wrap->ssl_); - - node::Utf8Value servername(env->isolate(), args[0].As()); - SSL_set_tlsext_host_name(wrap->ssl_.get(), *servername); -} - - -int TLSWrap::SelectSNIContextCallback(SSL* s, int* ad, void* arg) { - TLSWrap* p = static_cast(SSL_get_app_data(s)); - Environment* env = p->env(); - - const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); - - if (servername == nullptr) - return SSL_TLSEXT_ERR_OK; - - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); - - // Call the SNI callback and use its return value as context - Local object = p->object(); - Local ctx; - - // Set the servername as early as possible - Local owner = p->GetOwner(); - if (!owner->Set(env->context(), - env->servername_string(), - OneByteString(env->isolate(), servername)).FromMaybe(false)) { - return SSL_TLSEXT_ERR_NOACK; - } - - if (!object->Get(env->context(), env->sni_context_string()).ToLocal(&ctx)) - return SSL_TLSEXT_ERR_NOACK; - - // Not an object, probably undefined or null - if (!ctx->IsObject()) - return SSL_TLSEXT_ERR_NOACK; - - Local cons = env->secure_context_constructor_template(); - if (!cons->HasInstance(ctx)) { - // Failure: incorrect SNI context object - Local err = Exception::TypeError(env->sni_context_err_string()); - p->MakeCallback(env->onerror_string(), 1, &err); - return SSL_TLSEXT_ERR_NOACK; - } - - SecureContext* sc = Unwrap(ctx.As()); - CHECK_NOT_NULL(sc); - p->sni_context_ = BaseObjectPtr(sc); - - p->ConfigureSecureContext(sc); - CHECK_EQ(SSL_set_SSL_CTX(p->ssl_.get(), sc->ctx_.get()), sc->ctx_.get()); - p->SetCACerts(sc); - - return SSL_TLSEXT_ERR_OK; -} - -#ifndef OPENSSL_NO_PSK - -void TLSWrap::SetPskIdentityHint(const FunctionCallbackInfo& 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()); - - if (!SSL_use_psk_identity_hint(p->ssl_.get(), *hint)) { - Local err = node::ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED(isolate); - p->MakeCallback(env->onerror_string(), 1, &err); - } -} - -void TLSWrap::EnablePskCallback(const FunctionCallbackInfo& 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(SSL_get_app_data(s)); - - Environment* env = p->env(); - Isolate* isolate = env->isolate(); - HandleScope scope(isolate); - - MaybeLocal maybe_identity_str = - String::NewFromUtf8(isolate, identity); - - v8::Local 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 argv[] = {identity_str, - Integer::NewFromUnsigned(isolate, max_psk_len)}; - - MaybeLocal maybe_psk_val = - p->MakeCallback(env->onpskexchange_symbol(), arraysize(argv), argv); - Local 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(SSL_get_app_data(s)); - - Environment* env = p->env(); - Isolate* isolate = env->isolate(); - HandleScope scope(isolate); - - Local argv[] = {Null(isolate), - Integer::NewFromUnsigned(isolate, max_psk_len), - Integer::NewFromUnsigned(isolate, max_identity_len)}; - if (hint != nullptr) { - MaybeLocal maybe_hint = String::NewFromUtf8(isolate, hint); - - Local local_hint; - if (!maybe_hint.ToLocal(&local_hint)) return 0; - - argv[0] = local_hint; - } - MaybeLocal maybe_ret = - p->MakeCallback(env->onpskexchange_symbol(), arraysize(argv), argv); - Local ret; - if (!maybe_ret.ToLocal(&ret) || !ret->IsObject()) return 0; - Local obj = ret.As(); - - MaybeLocal maybe_psk_val = obj->Get(env->context(), env->psk_string()); - - Local 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 maybe_identity_val = - obj->Get(env->context(), env->identity_string()); - Local identity_val; - if (!maybe_identity_val.ToLocal(&identity_val) || !identity_val->IsString()) - return 0; - Local identity_str = identity_val.As(); - - 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& info) { - TLSWrap* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, info.This()); - - if (wrap->ssl_ == nullptr) { - info.GetReturnValue().Set(0); - return; - } - - uint32_t write_queue_size = BIO_pending(wrap->enc_out_); - info.GetReturnValue().Set(write_queue_size); -} - - -void TLSWrap::MemoryInfo(MemoryTracker* tracker) const { - SSLWrap::MemoryInfo(tracker); - tracker->TrackField("error", error_); - tracker->TrackFieldWithSize("pending_cleartext_input", - pending_cleartext_input_.size(), - "AllocatedBuffer"); - if (enc_in_ != nullptr) - tracker->TrackField("enc_in", crypto::NodeBIO::FromBIO(enc_in_)); - if (enc_out_ != nullptr) - tracker->TrackField("enc_out", crypto::NodeBIO::FromBIO(enc_out_)); -} - - -void TLSWrap::Initialize(Local target, - Local unused, - Local context, - void* priv) { - Environment* env = Environment::GetCurrent(context); - - env->SetMethod(target, "wrap", TLSWrap::Wrap); - - NODE_DEFINE_CONSTANT(target, HAVE_SSL_TRACE); - - Local t = BaseObject::MakeLazilyInitializedJSTemplate(env); - Local tlsWrapString = - FIXED_ONE_BYTE_STRING(env->isolate(), "TLSWrap"); - t->SetClassName(tlsWrapString); - t->InstanceTemplate()->SetInternalFieldCount(StreamBase::kInternalFieldCount); - - Local get_write_queue_size = - FunctionTemplate::New(env->isolate(), - GetWriteQueueSize, - Local(), - Signature::New(env->isolate(), t)); - t->PrototypeTemplate()->SetAccessorProperty( - env->write_queue_size_string(), - get_write_queue_size, - Local(), - static_cast(ReadOnly | DontDelete)); - - t->Inherit(AsyncWrap::GetConstructorTemplate(env)); - env->SetProtoMethod(t, "receive", Receive); - env->SetProtoMethod(t, "start", Start); - env->SetProtoMethod(t, "setVerifyMode", SetVerifyMode); - env->SetProtoMethod(t, "enableSessionCallbacks", EnableSessionCallbacks); - env->SetProtoMethod(t, "enableKeylogCallback", EnableKeylogCallback); - env->SetProtoMethod(t, "enableTrace", EnableTrace); - 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::AddMethods(env, t); - - env->SetProtoMethod(t, "getServername", GetServername); - env->SetProtoMethod(t, "setServername", SetServername); - - Local fn = t->GetFunction(env->context()).ToLocalChecked(); - - env->set_tls_wrap_constructor_function(fn); - - target->Set(env->context(), tlsWrapString, fn).Check(); -} - -} // namespace node - -NODE_MODULE_CONTEXT_AWARE_INTERNAL(tls_wrap, node::TLSWrap::Initialize)