// 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 "async-wrap.h" #include "async-wrap-inl.h" #include "node_buffer.h" // Buffer #include "node_crypto.h" // SecureContext #include "node_crypto_bio.h" // NodeBIO #include "node_crypto_clienthello.h" // ClientHelloParser #include "node_crypto_clienthello-inl.h" #include "node_wrap.h" // WithGenericStream #include "node_counters.h" #include "node_internals.h" #include "util.h" #include "util-inl.h" namespace node { using crypto::SSLWrap; using crypto::SecureContext; using v8::Boolean; using v8::Context; using v8::Exception; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::Handle; using v8::HandleScope; using v8::Integer; using v8::Local; using v8::Null; using v8::Object; using v8::String; using v8::Value; static const int X509_NAME_FLAGS = ASN1_STRFLGS_ESC_CTRL | ASN1_STRFLGS_ESC_MSB | XN_FLAG_SEP_MULTILINE | XN_FLAG_FN_SN; size_t TLSCallbacks::error_off_; char TLSCallbacks::error_buf_[1024]; TLSCallbacks::TLSCallbacks(Environment* env, Kind kind, Handle sc, StreamWrapCallbacks* old) : SSLWrap(env, Unwrap(sc), kind), StreamWrapCallbacks(old), AsyncWrap(env, env->tls_wrap_constructor_function()->NewInstance(), AsyncWrap::PROVIDER_TLSWRAP), sc_(Unwrap(sc)), sc_handle_(env->isolate(), sc), enc_in_(NULL), enc_out_(NULL), clear_in_(NULL), write_size_(0), started_(false), established_(false), shutdown_(false), error_(NULL), eof_(false) { node::Wrap(object(), this); // Initialize queue for clearIn writes QUEUE_INIT(&write_item_queue_); QUEUE_INIT(&pending_write_items_); // We've our own session callbacks SSL_CTX_sess_set_get_cb(sc_->ctx_, SSLWrap::GetSessionCallback); SSL_CTX_sess_set_new_cb(sc_->ctx_, SSLWrap::NewSessionCallback); InitSSL(); } TLSCallbacks::~TLSCallbacks() { enc_in_ = NULL; enc_out_ = NULL; delete clear_in_; clear_in_ = NULL; sc_ = NULL; sc_handle_.Dispose(); persistent().Dispose(); #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB sni_context_.Dispose(); #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB // Move all writes to pending MakePending(); // And destroy while (!QUEUE_EMPTY(&pending_write_items_)) { QUEUE* q = QUEUE_HEAD(&pending_write_items_); QUEUE_REMOVE(q); WriteItem* wi = QUEUE_DATA(q, WriteItem, member_); delete wi; } } void TLSCallbacks::MakePending() { // Aliases QUEUE* from = &write_item_queue_; QUEUE* to = &pending_write_items_; if (QUEUE_EMPTY(from)) return; // Add items to pending QUEUE_ADD(to, from); // Empty original queue QUEUE_INIT(from); } bool TLSCallbacks::InvokeQueued(int status) { if (QUEUE_EMPTY(&pending_write_items_)) return false; // Process old queue while (!QUEUE_EMPTY(&pending_write_items_)) { QUEUE* q = QUEUE_HEAD(&pending_write_items_); QUEUE_REMOVE(q); WriteItem* wi = QUEUE_DATA(q, WriteItem, member_); wi->cb_(&wi->w_->req_, status); delete wi; } return true; } void TLSCallbacks::InitSSL() { // Initialize SSL enc_in_ = NodeBIO::New(); enc_out_ = NodeBIO::New(); SSL_set_bio(ssl_, enc_in_, enc_out_); // NOTE: This could be overriden in SetVerifyMode SSL_set_verify(ssl_, SSL_VERIFY_NONE, crypto::VerifyCallback); #ifdef SSL_MODE_RELEASE_BUFFERS long mode = SSL_get_mode(ssl_); SSL_set_mode(ssl_, mode | SSL_MODE_RELEASE_BUFFERS); #endif // SSL_MODE_RELEASE_BUFFERS SSL_set_app_data(ssl_, this); SSL_set_info_callback(ssl_, SSLInfoCallback); #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB if (is_server()) { SSL_CTX_set_tlsext_servername_callback(sc_->ctx_, SelectSNIContextCallback); SSL_CTX_set_tlsext_servername_arg(sc_->ctx_, this); } #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB InitNPN(sc_, this); if (is_server()) { SSL_set_accept_state(ssl_); } else if (is_client()) { SSL_set_connect_state(ssl_); } else { // Unexpected abort(); } // Initialize ring for queud clear data clear_in_ = new NodeBIO(); } void TLSCallbacks::Wrap(const FunctionCallbackInfo& args) { HandleScope handle_scope(args.GetIsolate()); Environment* env = Environment::GetCurrent(args.GetIsolate()); if (args.Length() < 1 || !args[0]->IsObject()) return ThrowTypeError("First argument should be a StreamWrap instance"); if (args.Length() < 2 || !args[1]->IsObject()) return ThrowTypeError("Second argument should be a SecureContext instance"); if (args.Length() < 3 || !args[2]->IsBoolean()) return ThrowTypeError("Third argument should be boolean"); Local stream = args[0].As(); Local sc = args[1].As(); Kind kind = args[2]->IsTrue() ? SSLWrap::kServer : SSLWrap::kClient; TLSCallbacks* callbacks = NULL; WITH_GENERIC_STREAM(env, stream, { callbacks = new TLSCallbacks(env, kind, sc, wrap->callbacks()); wrap->OverrideCallbacks(callbacks); }); if (callbacks == NULL) { return args.GetReturnValue().SetNull(); } args.GetReturnValue().Set(callbacks->persistent()); } void TLSCallbacks::Receive(const FunctionCallbackInfo& args) { HandleScope handle_scope(args.GetIsolate()); TLSCallbacks* wrap = Unwrap(args.This()); CHECK(Buffer::HasInstance(args[0])); char* data = Buffer::Data(args[0]); size_t len = Buffer::Length(args[0]); uv_buf_t buf; uv_stream_t* stream = wrap->wrap()->stream(); // Copy given buffer entirely or partiall if handle becomes closed while (len > 0 && !uv_is_closing(reinterpret_cast(stream))) { wrap->DoAlloc(reinterpret_cast(stream), len, &buf); size_t copy = buf.len > len ? len : buf.len; memcpy(buf.base, data, copy); wrap->DoRead(stream, buf.len, &buf, UV_UNKNOWN_HANDLE); data += copy; len -= copy; } } void TLSCallbacks::Start(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); TLSCallbacks* wrap = Unwrap(args.This()); if (wrap->started_) return ThrowError("Already started."); wrap->started_ = true; // Send ClientHello handshake assert(wrap->is_client()); wrap->ClearOut(); wrap->EncOut(); } void TLSCallbacks::SSLInfoCallback(const SSL* ssl_, int where, int ret) { if (!(where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE))) return; // Be compatible with older versions of OpenSSL. SSL_get_app_data() wants // a non-const SSL* in OpenSSL <= 0.9.7e. SSL* ssl = const_cast(ssl_); TLSCallbacks* 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) { Local callback = object->Get(env->onhandshakestart_string()); if (callback->IsFunction()) { c->MakeCallback(callback.As(), 0, NULL); } } if (where & SSL_CB_HANDSHAKE_DONE) { c->established_ = true; Local callback = object->Get(env->onhandshakedone_string()); if (callback->IsFunction()) { c->MakeCallback(callback.As(), 0, NULL); } } } void TLSCallbacks::EncOut() { // Ignore cycling data if ClientHello wasn't yet parsed if (!hello_parser_.IsEnded()) return; // Write in progress if (write_size_ != 0) return; // Split-off queue if (established_ && !QUEUE_EMPTY(&write_item_queue_)) MakePending(); // No data to write if (BIO_pending(enc_out_) == 0) { if (clear_in_->Length() == 0) InvokeQueued(0); return; } char* data[kSimultaneousBufferCount]; size_t size[ARRAY_SIZE(data)]; size_t count = ARRAY_SIZE(data); write_size_ = NodeBIO::FromBIO(enc_out_)->PeekMultiple(data, size, &count); assert(write_size_ != 0 && count != 0); write_req_.data = this; uv_buf_t buf[ARRAY_SIZE(data)]; for (size_t i = 0; i < count; i++) buf[i] = uv_buf_init(data[i], size[i]); int r = uv_write(&write_req_, wrap()->stream(), buf, count, EncOutCb); // Ignore errors, this should be already handled in js if (!r) { if (wrap()->is_tcp()) { NODE_COUNT_NET_BYTES_SENT(write_size_); } else if (wrap()->is_named_pipe()) { NODE_COUNT_PIPE_BYTES_SENT(write_size_); } } } void TLSCallbacks::EncOutCb(uv_write_t* req, int status) { TLSCallbacks* callbacks = static_cast(req->data); // Handle error if (status) { // Ignore errors after shutdown if (callbacks->shutdown_) return; // Notify about error callbacks->InvokeQueued(status); return; } // Commit NodeBIO::FromBIO(callbacks->enc_out_)->Read(NULL, callbacks->write_size_); // Try writing more data callbacks->write_size_ = 0; callbacks->EncOut(); } int TLSCallbacks::PrintErrorsCb(const char* str, size_t len, void* arg) { size_t to_copy = error_off_; size_t avail = sizeof(error_buf_) - error_off_ - 1; if (avail > to_copy) to_copy = avail; memcpy(error_buf_, str, avail); error_off_ += avail; assert(error_off_ < sizeof(error_buf_)); // Zero-terminate error_buf_[error_off_] = '\0'; return 0; } const char* TLSCallbacks::PrintErrors() { error_off_ = 0; ERR_print_errors_cb(PrintErrorsCb, this); return error_buf_; } Local TLSCallbacks::GetSSLError(int status, int* err, const char** msg) { HandleScope scope(node_isolate); *err = SSL_get_error(ssl_, status); switch (*err) { case SSL_ERROR_NONE: case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: break; case SSL_ERROR_ZERO_RETURN: return scope.Close(FIXED_ONE_BYTE_STRING(node_isolate, "ZERO_RETURN")); break; default: { assert(*err == SSL_ERROR_SSL || *err == SSL_ERROR_SYSCALL); const char* buf = PrintErrors(); Local message = OneByteString(node_isolate, buf, strlen(buf)); Local exception = Exception::Error(message); if (msg != NULL) { assert(*msg == NULL); *msg = buf; } return scope.Close(exception); } } return Local(); } void TLSCallbacks::ClearOut() { // Ignore cycling data if ClientHello wasn't yet parsed if (!hello_parser_.IsEnded()) return; HandleScope handle_scope(env()->isolate()); Context::Scope context_scope(env()->context()); assert(ssl_ != NULL); char out[kClearOutChunkSize]; int read; do { read = SSL_read(ssl_, out, sizeof(out)); if (read > 0) { Local argv[] = { Integer::New(read, node_isolate), Buffer::New(env(), out, read) }; wrap()->MakeCallback(env()->onread_string(), ARRAY_SIZE(argv), argv); } } while (read > 0); int flags = SSL_get_shutdown(ssl_); if (!eof_ && flags & SSL_RECEIVED_SHUTDOWN) { eof_ = true; Local arg = Integer::New(UV_EOF, node_isolate); wrap()->MakeCallback(env()->onread_string(), 1, &arg); } if (read == -1) { int err; Local arg = GetSSLError(read, &err, NULL); if (!arg.IsEmpty()) { MakeCallback(env()->onerror_string(), 1, &arg); } } } bool TLSCallbacks::ClearIn() { // Ignore cycling data if ClientHello wasn't yet parsed if (!hello_parser_.IsEnded()) return false; int written = 0; while (clear_in_->Length() > 0) { size_t avail = 0; char* data = clear_in_->Peek(&avail); written = SSL_write(ssl_, data, avail); assert(written == -1 || written == static_cast(avail)); if (written == -1) break; clear_in_->Read(NULL, avail); } // All written if (clear_in_->Length() == 0) { assert(written >= 0); return true; } HandleScope handle_scope(env()->isolate()); Context::Scope context_scope(env()->context()); // Error or partial write int err; Local arg = GetSSLError(written, &err, &error_); if (!arg.IsEmpty()) { MakePending(); if (!InvokeQueued(UV_EPROTO)) error_ = NULL; clear_in_->Reset(); } return false; } const char* TLSCallbacks::Error() { const char* ret = error_; error_ = NULL; return ret; } int TLSCallbacks::TryWrite(uv_buf_t** bufs, size_t* count) { // TODO(indutny): Support it return -1; } int TLSCallbacks::DoWrite(WriteWrap* w, uv_buf_t* bufs, size_t count, uv_stream_t* send_handle, uv_write_cb cb) { assert(send_handle == NULL); // Queue callback to execute it on next tick WriteItem* wi = new WriteItem(w, cb); bool empty = true; // Empty writes should not go through encryption process size_t i; for (i = 0; i < count; i++) if (bufs[i].len > 0) { empty = false; break; } if (empty) { ClearOut(); // However if there any data that should be written to socket, // callback should not be invoked immediately if (BIO_pending(enc_out_) == 0) return uv_write(&w->req_, wrap()->stream(), bufs, count, cb); } QUEUE_INSERT_TAIL(&write_item_queue_, &wi->member_); // Write queued data if (empty) { EncOut(); return 0; } // Process enqueued data first if (!ClearIn()) { // If there're still data to process - enqueue current one for (i = 0; i < count; i++) clear_in_->Write(bufs[i].base, bufs[i].len); return 0; } int written = 0; for (i = 0; i < count; i++) { written = SSL_write(ssl_, bufs[i].base, bufs[i].len); assert(written == -1 || written == static_cast(bufs[i].len)); if (written == -1) break; } if (i != count) { int err; HandleScope handle_scope(env()->isolate()); Context::Scope context_scope(env()->context()); Local arg = GetSSLError(written, &err, &error_); if (!arg.IsEmpty()) return UV_EPROTO; // No errors, queue rest for (; i < count; i++) clear_in_->Write(bufs[i].base, bufs[i].len); } // Try writing data immediately EncOut(); return 0; } void TLSCallbacks::AfterWrite(WriteWrap* w) { // Intentionally empty } void TLSCallbacks::DoAlloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { buf->base = NodeBIO::FromBIO(enc_in_)->PeekWritable(&suggested_size); buf->len = suggested_size; } void TLSCallbacks::DoRead(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf, uv_handle_type pending) { 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; } HandleScope handle_scope(env()->isolate()); Context::Scope context_scope(env()->context()); Local arg = Integer::New(nread, node_isolate); wrap()->MakeCallback(env()->onread_string(), 1, &arg); return; } // Only client connections can receive data assert(ssl_ != NULL); // Commit read data NodeBIO* enc_in = NodeBIO::FromBIO(enc_in_); enc_in->Commit(nread); // Parse ClientHello first if (!hello_parser_.IsEnded()) { size_t avail = 0; uint8_t* data = reinterpret_cast(enc_in->Peek(&avail)); assert(avail == 0 || data != NULL); return hello_parser_.Parse(data, avail); } // Cycle OpenSSL's state Cycle(); } int TLSCallbacks::DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb) { if (SSL_shutdown(ssl_) == 0) SSL_shutdown(ssl_); shutdown_ = true; EncOut(); return StreamWrapCallbacks::DoShutdown(req_wrap, cb); } void TLSCallbacks::SetVerifyMode(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); TLSCallbacks* wrap = Unwrap(args.This()); if (args.Length() < 2 || !args[0]->IsBoolean() || !args[1]->IsBoolean()) return ThrowTypeError("Bad arguments, expected two booleans"); int verify_mode; if (wrap->is_server()) { bool request_cert = args[0]->IsTrue(); if (!request_cert) { // Note reject_unauthorized ignored. 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 { // Note request_cert and reject_unauthorized are ignored for clients. verify_mode = SSL_VERIFY_NONE; } // Always allow a connection. We'll reject in javascript. SSL_set_verify(wrap->ssl_, verify_mode, crypto::VerifyCallback); } void TLSCallbacks::EnableSessionCallbacks( const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); TLSCallbacks* wrap = Unwrap(args.This()); wrap->enable_session_callbacks(); EnableHelloParser(args); } void TLSCallbacks::EnableHelloParser(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); TLSCallbacks* wrap = Unwrap(args.This()); wrap->hello_parser_.Start(SSLWrap::OnClientHello, OnClientHelloParseEnd, wrap); } void TLSCallbacks::OnClientHelloParseEnd(void* arg) { TLSCallbacks* c = static_cast(arg); c->Cycle(); } #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB void TLSCallbacks::GetServername(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); TLSCallbacks* wrap = Unwrap(args.This()); const char* servername = SSL_get_servername(wrap->ssl_, TLSEXT_NAMETYPE_host_name); if (servername != NULL) { args.GetReturnValue().Set(OneByteString(node_isolate, servername)); } else { args.GetReturnValue().Set(false); } } void TLSCallbacks::SetServername(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); TLSCallbacks* wrap = Unwrap(args.This()); if (args.Length() < 1 || !args[0]->IsString()) return ThrowTypeError("First argument should be a string"); if (wrap->started_) return ThrowError("Already started."); if (!wrap->is_client()) return; #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB String::Utf8Value servername(args[0].As()); SSL_set_tlsext_host_name(wrap->ssl_, *servername); #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB } int TLSCallbacks::SelectSNIContextCallback(SSL* s, int* ad, void* arg) { TLSCallbacks* p = static_cast(arg); Environment* env = p->env(); const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); if (servername == NULL) return SSL_TLSEXT_ERR_OK; HandleScope scope(env->isolate()); // Call the SNI callback and use its return value as context Local object = p->object(); Local ctx = object->Get(env->sni_context_string()); // 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; } p->sni_context_.Dispose(); p->sni_context_.Reset(node_isolate, ctx); SecureContext* sc = Unwrap(ctx.As()); InitNPN(sc, p); SSL_set_SSL_CTX(s, sc->ctx_); return SSL_TLSEXT_ERR_OK; } #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB void TLSCallbacks::Initialize(Handle target, Handle unused, Handle context) { Environment* env = Environment::GetCurrent(context); NODE_SET_METHOD(target, "wrap", TLSCallbacks::Wrap); Local t = FunctionTemplate::New(); t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(FIXED_ONE_BYTE_STRING(node_isolate, "TLSWrap")); NODE_SET_PROTOTYPE_METHOD(t, "receive", Receive); NODE_SET_PROTOTYPE_METHOD(t, "start", Start); NODE_SET_PROTOTYPE_METHOD(t, "setVerifyMode", SetVerifyMode); NODE_SET_PROTOTYPE_METHOD(t, "enableSessionCallbacks", EnableSessionCallbacks); NODE_SET_PROTOTYPE_METHOD(t, "enableHelloParser", EnableHelloParser); SSLWrap::AddMethods(t); #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB NODE_SET_PROTOTYPE_METHOD(t, "getServername", GetServername); NODE_SET_PROTOTYPE_METHOD(t, "setServername", SetServername); #endif // SSL_CRT_SET_TLSEXT_SERVERNAME_CB env->set_tls_wrap_constructor_function(t->GetFunction()); } } // namespace node NODE_MODULE_CONTEXT_AWARE_BUILTIN(tls_wrap, node::TLSCallbacks::Initialize)