mirror of
https://github.com/nodejs/node.git
synced 2024-12-01 16:10:02 +01:00
9180140231
Queued write requests should be invoked on handle close, otherwise the "consumer" might be already destroyed when the write callbacks of the "consumed" handle will be invoked. Same applies to the shutdown requests. Make sure to "move" away socket from server to not break the `connections` counter in `net.js`. Otherwise it might not call `close` callback, or call it too early. Fix: https://github.com/iojs/io.js/issues/1696 PR-URL: https://github.com/nodejs/io.js/pull/1910 Reviewed-By: Trevor Norris <trev.norris@gmail.com>
908 lines
22 KiB
C++
908 lines
22 KiB
C++
#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 "stream_base.h"
|
|
#include "stream_base-inl.h"
|
|
#include "util.h"
|
|
#include "util-inl.h"
|
|
|
|
namespace node {
|
|
|
|
using crypto::SSLWrap;
|
|
using crypto::SecureContext;
|
|
using v8::Boolean;
|
|
using v8::Context;
|
|
using v8::EscapableHandleScope;
|
|
using v8::Exception;
|
|
using v8::Function;
|
|
using v8::FunctionCallbackInfo;
|
|
using v8::FunctionTemplate;
|
|
using v8::Handle;
|
|
using v8::Integer;
|
|
using v8::Local;
|
|
using v8::Null;
|
|
using v8::Object;
|
|
using v8::String;
|
|
using v8::Value;
|
|
|
|
|
|
TLSWrap::TLSWrap(Environment* env,
|
|
Kind kind,
|
|
StreamBase* stream,
|
|
SecureContext* sc)
|
|
: SSLWrap<TLSWrap>(env, sc, kind),
|
|
StreamBase(env),
|
|
AsyncWrap(env,
|
|
env->tls_wrap_constructor_function()->NewInstance(),
|
|
AsyncWrap::PROVIDER_TLSWRAP),
|
|
sc_(sc),
|
|
stream_(stream),
|
|
enc_in_(nullptr),
|
|
enc_out_(nullptr),
|
|
clear_in_(nullptr),
|
|
write_size_(0),
|
|
started_(false),
|
|
established_(false),
|
|
shutdown_(false),
|
|
error_(nullptr),
|
|
cycle_depth_(0),
|
|
eof_(false) {
|
|
node::Wrap(object(), this);
|
|
MakeWeak(this);
|
|
|
|
// We've our own session callbacks
|
|
SSL_CTX_sess_set_get_cb(sc_->ctx_, SSLWrap<TLSWrap>::GetSessionCallback);
|
|
SSL_CTX_sess_set_new_cb(sc_->ctx_, SSLWrap<TLSWrap>::NewSessionCallback);
|
|
|
|
stream_->Consume();
|
|
stream_->set_after_write_cb(OnAfterWriteImpl, this);
|
|
stream_->set_alloc_cb(OnAllocImpl, this);
|
|
stream_->set_read_cb(OnReadImpl, this);
|
|
|
|
set_alloc_cb(OnAllocSelf, this);
|
|
set_read_cb(OnReadSelf, this);
|
|
|
|
InitSSL();
|
|
}
|
|
|
|
|
|
TLSWrap::~TLSWrap() {
|
|
enc_in_ = nullptr;
|
|
enc_out_ = nullptr;
|
|
delete clear_in_;
|
|
clear_in_ = nullptr;
|
|
|
|
sc_ = nullptr;
|
|
|
|
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
sni_context_.Reset();
|
|
#endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
|
|
ClearError();
|
|
}
|
|
|
|
|
|
void TLSWrap::MakePending() {
|
|
write_item_queue_.MoveBack(&pending_write_items_);
|
|
}
|
|
|
|
|
|
bool TLSWrap::InvokeQueued(int status) {
|
|
if (pending_write_items_.IsEmpty())
|
|
return false;
|
|
|
|
// Process old queue
|
|
WriteItemList queue;
|
|
pending_write_items_.MoveBack(&queue);
|
|
while (WriteItem* wi = queue.PopFront()) {
|
|
wi->w_->Done(status);
|
|
delete wi;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void TLSWrap::NewSessionDoneCb() {
|
|
Cycle();
|
|
}
|
|
|
|
|
|
void TLSWrap::InitSSL() {
|
|
// Initialize SSL
|
|
enc_in_ = NodeBIO::New();
|
|
enc_out_ = NodeBIO::New();
|
|
NodeBIO::FromBIO(enc_in_)->AssignEnvironment(env());
|
|
NodeBIO::FromBIO(enc_out_)->AssignEnvironment(env());
|
|
|
|
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);
|
|
}
|
|
#endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
|
|
InitNPN(sc_);
|
|
|
|
SSL_set_cert_cb(ssl_, SSLWrap<TLSWrap>::SSLCertCallback, this);
|
|
|
|
if (is_server()) {
|
|
SSL_set_accept_state(ssl_);
|
|
} else if (is_client()) {
|
|
// Enough space for server response (hello, cert)
|
|
NodeBIO::FromBIO(enc_in_)->set_initial(kInitialClientBufferLength);
|
|
SSL_set_connect_state(ssl_);
|
|
} else {
|
|
// Unexpected
|
|
abort();
|
|
}
|
|
|
|
// Initialize ring for queud clear data
|
|
clear_in_ = new NodeBIO();
|
|
clear_in_->AssignEnvironment(env());
|
|
}
|
|
|
|
|
|
void TLSWrap::Wrap(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
if (args.Length() < 1 || !args[0]->IsObject()) {
|
|
return env->ThrowTypeError(
|
|
"First argument should be a StreamWrap instance");
|
|
}
|
|
if (args.Length() < 2 || !args[1]->IsObject()) {
|
|
return env->ThrowTypeError(
|
|
"Second argument should be a SecureContext instance");
|
|
}
|
|
if (args.Length() < 3 || !args[2]->IsBoolean())
|
|
return env->ThrowTypeError("Third argument should be boolean");
|
|
|
|
Local<Object> stream_obj = args[0].As<Object>();
|
|
Local<Object> sc = args[1].As<Object>();
|
|
Kind kind = args[2]->IsTrue() ? SSLWrap<TLSWrap>::kServer :
|
|
SSLWrap<TLSWrap>::kClient;
|
|
|
|
StreamBase* stream = nullptr;
|
|
WITH_GENERIC_STREAM(env, stream_obj, {
|
|
stream = wrap;
|
|
});
|
|
CHECK_NE(stream, nullptr);
|
|
|
|
TLSWrap* res = new TLSWrap(env, kind, stream, Unwrap<SecureContext>(sc));
|
|
|
|
args.GetReturnValue().Set(res->object());
|
|
}
|
|
|
|
|
|
void TLSWrap::Receive(const FunctionCallbackInfo<Value>& args) {
|
|
TLSWrap* wrap = Unwrap<TLSWrap>(args.Holder());
|
|
|
|
CHECK(Buffer::HasInstance(args[0]));
|
|
char* data = Buffer::Data(args[0]);
|
|
size_t len = Buffer::Length(args[0]);
|
|
|
|
uv_buf_t buf;
|
|
|
|
// Copy given buffer entirely or partiall if handle becomes closed
|
|
while (len > 0 && wrap->IsAlive() && !wrap->IsClosing()) {
|
|
wrap->stream_->OnAlloc(len, &buf);
|
|
size_t copy = buf.len > len ? len : buf.len;
|
|
memcpy(buf.base, data, copy);
|
|
buf.len = copy;
|
|
wrap->stream_->OnRead(buf.len, &buf);
|
|
|
|
data += copy;
|
|
len -= copy;
|
|
}
|
|
}
|
|
|
|
|
|
void TLSWrap::Start(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
TLSWrap* wrap = Unwrap<TLSWrap>(args.Holder());
|
|
|
|
if (wrap->started_)
|
|
return env->ThrowError("Already started.");
|
|
wrap->started_ = true;
|
|
|
|
// Send ClientHello handshake
|
|
CHECK(wrap->is_client());
|
|
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;
|
|
|
|
// 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*>(ssl_);
|
|
TLSWrap* c = static_cast<TLSWrap*>(SSL_get_app_data(ssl));
|
|
Environment* env = c->env();
|
|
Local<Object> object = c->object();
|
|
|
|
if (where & SSL_CB_HANDSHAKE_START) {
|
|
Local<Value> callback = object->Get(env->onhandshakestart_string());
|
|
if (callback->IsFunction()) {
|
|
c->MakeCallback(callback.As<Function>(), 0, nullptr);
|
|
}
|
|
}
|
|
|
|
if (where & SSL_CB_HANDSHAKE_DONE) {
|
|
c->established_ = true;
|
|
Local<Value> callback = object->Get(env->onhandshakedone_string());
|
|
if (callback->IsFunction()) {
|
|
c->MakeCallback(callback.As<Function>(), 0, nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void TLSWrap::EncOut() {
|
|
// Ignore cycling data if ClientHello wasn't yet parsed
|
|
if (!hello_parser_.IsEnded())
|
|
return;
|
|
|
|
// Write in progress
|
|
if (write_size_ != 0)
|
|
return;
|
|
|
|
// Wait for `newSession` callback to be invoked
|
|
if (is_waiting_new_session())
|
|
return;
|
|
|
|
// Split-off queue
|
|
if (established_ && !write_item_queue_.IsEmpty())
|
|
MakePending();
|
|
|
|
if (ssl_ == nullptr)
|
|
return;
|
|
|
|
// 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);
|
|
CHECK(write_size_ != 0 && count != 0);
|
|
|
|
Local<Object> req_wrap_obj =
|
|
env()->write_wrap_constructor_function()->NewInstance();
|
|
WriteWrap* write_req = WriteWrap::New(env(),
|
|
req_wrap_obj,
|
|
this,
|
|
EncOutCb);
|
|
|
|
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 err = stream_->DoWrite(write_req, buf, count, nullptr);
|
|
|
|
// Ignore errors, this should be already handled in js
|
|
if (err) {
|
|
write_req->Dispose();
|
|
InvokeQueued(err);
|
|
} else {
|
|
NODE_COUNT_NET_BYTES_SENT(write_size_);
|
|
}
|
|
}
|
|
|
|
|
|
void TLSWrap::EncOutCb(WriteWrap* req_wrap, int status) {
|
|
TLSWrap* wrap = req_wrap->wrap()->Cast<TLSWrap>();
|
|
req_wrap->Dispose();
|
|
|
|
// We should not be getting here after `DestroySSL`, because all queued writes
|
|
// must be invoked with UV_ECANCELED
|
|
CHECK_NE(wrap->ssl_, nullptr);
|
|
|
|
// Handle error
|
|
if (status) {
|
|
// Ignore errors after shutdown
|
|
if (wrap->shutdown_)
|
|
return;
|
|
|
|
// Notify about error
|
|
wrap->InvokeQueued(status);
|
|
return;
|
|
}
|
|
|
|
// Commit
|
|
NodeBIO::FromBIO(wrap->enc_out_)->Read(nullptr, wrap->write_size_);
|
|
|
|
// Ensure that the progress will be made and `InvokeQueued` will be called.
|
|
wrap->ClearIn();
|
|
|
|
// Try writing more data
|
|
wrap->write_size_ = 0;
|
|
wrap->EncOut();
|
|
}
|
|
|
|
|
|
Local<Value> TLSWrap::GetSSLError(int status, int* err, const char** msg) {
|
|
EscapableHandleScope scope(env()->isolate());
|
|
|
|
// ssl_ is already destroyed in reading EOF by close notify alert.
|
|
if (ssl_ == nullptr)
|
|
return Local<Value>();
|
|
|
|
*err = SSL_get_error(ssl_, status);
|
|
switch (*err) {
|
|
case SSL_ERROR_NONE:
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_WRITE:
|
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
|
break;
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
return scope.Escape(env()->zero_return_string());
|
|
break;
|
|
default:
|
|
{
|
|
CHECK(*err == SSL_ERROR_SSL || *err == SSL_ERROR_SYSCALL);
|
|
|
|
BIO* bio = BIO_new(BIO_s_mem());
|
|
ERR_print_errors(bio);
|
|
|
|
BUF_MEM* mem;
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
Local<String> message =
|
|
OneByteString(env()->isolate(), mem->data, mem->length);
|
|
Local<Value> exception = Exception::Error(message);
|
|
|
|
if (msg != nullptr) {
|
|
CHECK_EQ(*msg, nullptr);
|
|
char* const buf = new char[mem->length + 1];
|
|
memcpy(buf, mem->data, mem->length);
|
|
buf[mem->length] = '\0';
|
|
*msg = buf;
|
|
}
|
|
BIO_free_all(bio);
|
|
|
|
return scope.Escape(exception);
|
|
}
|
|
}
|
|
return Local<Value>();
|
|
}
|
|
|
|
|
|
void TLSWrap::ClearOut() {
|
|
// Ignore cycling data if ClientHello wasn't yet parsed
|
|
if (!hello_parser_.IsEnded())
|
|
return;
|
|
|
|
// No reads after EOF
|
|
if (eof_)
|
|
return;
|
|
|
|
if (ssl_ == nullptr)
|
|
return;
|
|
|
|
char out[kClearOutChunkSize];
|
|
int read;
|
|
for (;;) {
|
|
read = SSL_read(ssl_, out, sizeof(out));
|
|
|
|
if (read <= 0)
|
|
break;
|
|
|
|
while (read > 0) {
|
|
int avail = read;
|
|
|
|
uv_buf_t buf;
|
|
OnAlloc(avail, &buf);
|
|
if (static_cast<int>(buf.len) < avail)
|
|
avail = buf.len;
|
|
memcpy(buf.base, out, avail);
|
|
OnRead(avail, &buf);
|
|
|
|
read -= avail;
|
|
}
|
|
}
|
|
|
|
int flags = SSL_get_shutdown(ssl_);
|
|
if (!eof_ && flags & SSL_RECEIVED_SHUTDOWN) {
|
|
eof_ = true;
|
|
OnRead(UV_EOF, nullptr);
|
|
}
|
|
|
|
// We need to check whether an error occurred or the connection was
|
|
// shutdown cleanly (SSL_ERROR_ZERO_RETURN) even when read == 0.
|
|
// See iojs#1642 and SSL_read(3SSL) for details.
|
|
if (read <= 0) {
|
|
int err;
|
|
Local<Value> 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()) {
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool TLSWrap::ClearIn() {
|
|
// Ignore cycling data if ClientHello wasn't yet parsed
|
|
if (!hello_parser_.IsEnded())
|
|
return false;
|
|
|
|
if (ssl_ == nullptr)
|
|
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);
|
|
CHECK(written == -1 || written == static_cast<int>(avail));
|
|
if (written == -1)
|
|
break;
|
|
clear_in_->Read(nullptr, avail);
|
|
}
|
|
|
|
// All written
|
|
if (clear_in_->Length() == 0) {
|
|
CHECK_GE(written, 0);
|
|
return true;
|
|
}
|
|
|
|
// Error or partial write
|
|
int err;
|
|
Local<Value> arg = GetSSLError(written, &err, &error_);
|
|
if (!arg.IsEmpty()) {
|
|
MakePending();
|
|
if (!InvokeQueued(UV_EPROTO))
|
|
ClearError();
|
|
clear_in_->Reset();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void* TLSWrap::Cast() {
|
|
return reinterpret_cast<void*>(this);
|
|
}
|
|
|
|
|
|
AsyncWrap* TLSWrap::GetAsyncWrap() {
|
|
return static_cast<AsyncWrap*>(this);
|
|
}
|
|
|
|
|
|
bool TLSWrap::IsIPCPipe() {
|
|
return stream_->IsIPCPipe();
|
|
}
|
|
|
|
|
|
int TLSWrap::GetFD() {
|
|
return stream_->GetFD();
|
|
}
|
|
|
|
|
|
bool TLSWrap::IsAlive() {
|
|
return ssl_ != nullptr && stream_->IsAlive();
|
|
}
|
|
|
|
|
|
bool TLSWrap::IsClosing() {
|
|
return stream_->IsClosing();
|
|
}
|
|
|
|
|
|
int TLSWrap::ReadStart() {
|
|
return stream_->ReadStart();
|
|
}
|
|
|
|
|
|
int TLSWrap::ReadStop() {
|
|
return stream_->ReadStop();
|
|
}
|
|
|
|
|
|
const char* TLSWrap::Error() const {
|
|
return error_;
|
|
}
|
|
|
|
|
|
void TLSWrap::ClearError() {
|
|
delete[] error_;
|
|
error_ = nullptr;
|
|
}
|
|
|
|
|
|
int TLSWrap::DoWrite(WriteWrap* w,
|
|
uv_buf_t* bufs,
|
|
size_t count,
|
|
uv_stream_t* send_handle) {
|
|
CHECK_EQ(send_handle, nullptr);
|
|
CHECK_NE(ssl_, nullptr);
|
|
|
|
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 stream_->DoWrite(w, bufs, count, send_handle);
|
|
}
|
|
|
|
// Queue callback to execute it on next tick
|
|
write_item_queue_.PushBack(new WriteItem(w));
|
|
w->Dispatched();
|
|
|
|
// 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;
|
|
}
|
|
|
|
if (ssl_ == nullptr)
|
|
return UV_EPROTO;
|
|
|
|
int written = 0;
|
|
for (i = 0; i < count; i++) {
|
|
written = SSL_write(ssl_, bufs[i].base, bufs[i].len);
|
|
CHECK(written == -1 || written == static_cast<int>(bufs[i].len));
|
|
if (written == -1)
|
|
break;
|
|
}
|
|
|
|
if (i != count) {
|
|
int err;
|
|
Local<Value> 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 TLSWrap::OnAfterWriteImpl(WriteWrap* w, void* ctx) {
|
|
// Intentionally empty
|
|
}
|
|
|
|
|
|
void TLSWrap::OnAllocImpl(size_t suggested_size, uv_buf_t* buf, void* ctx) {
|
|
TLSWrap* wrap = static_cast<TLSWrap*>(ctx);
|
|
|
|
if (wrap->ssl_ == nullptr) {
|
|
*buf = uv_buf_init(nullptr, 0);
|
|
return;
|
|
}
|
|
|
|
size_t size = 0;
|
|
buf->base = NodeBIO::FromBIO(wrap->enc_in_)->PeekWritable(&size);
|
|
buf->len = size;
|
|
}
|
|
|
|
|
|
void TLSWrap::OnReadImpl(ssize_t nread,
|
|
const uv_buf_t* buf,
|
|
uv_handle_type pending,
|
|
void* ctx) {
|
|
TLSWrap* wrap = static_cast<TLSWrap*>(ctx);
|
|
wrap->DoRead(nread, buf, pending);
|
|
}
|
|
|
|
|
|
void TLSWrap::OnAllocSelf(size_t suggested_size, uv_buf_t* buf, void* ctx) {
|
|
buf->base = static_cast<char*>(malloc(suggested_size));
|
|
CHECK_NE(buf->base, nullptr);
|
|
buf->len = suggested_size;
|
|
}
|
|
|
|
|
|
void TLSWrap::OnReadSelf(ssize_t nread,
|
|
const uv_buf_t* buf,
|
|
uv_handle_type pending,
|
|
void* ctx) {
|
|
TLSWrap* wrap = static_cast<TLSWrap*>(ctx);
|
|
Local<Object> buf_obj;
|
|
if (buf != nullptr)
|
|
buf_obj = Buffer::Use(wrap->env(), buf->base, buf->len);
|
|
wrap->EmitData(nread, buf_obj, Local<Object>());
|
|
}
|
|
|
|
|
|
void TLSWrap::DoRead(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;
|
|
}
|
|
|
|
OnRead(nread, nullptr);
|
|
return;
|
|
}
|
|
|
|
// Only client connections can receive data
|
|
if (ssl_ == nullptr) {
|
|
OnRead(UV_EPROTO, nullptr);
|
|
return;
|
|
}
|
|
|
|
// 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<uint8_t*>(enc_in->Peek(&avail));
|
|
CHECK(avail == 0 || data != nullptr);
|
|
return hello_parser_.Parse(data, avail);
|
|
}
|
|
|
|
// Cycle OpenSSL's state
|
|
Cycle();
|
|
}
|
|
|
|
|
|
int TLSWrap::DoShutdown(ShutdownWrap* req_wrap) {
|
|
if (ssl_ != nullptr && SSL_shutdown(ssl_) == 0)
|
|
SSL_shutdown(ssl_);
|
|
shutdown_ = true;
|
|
EncOut();
|
|
return stream_->DoShutdown(req_wrap);
|
|
}
|
|
|
|
|
|
void TLSWrap::SetVerifyMode(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
TLSWrap* wrap = Unwrap<TLSWrap>(args.Holder());
|
|
|
|
if (args.Length() < 2 || !args[0]->IsBoolean() || !args[1]->IsBoolean())
|
|
return env->ThrowTypeError("Bad arguments, expected two booleans");
|
|
|
|
if (wrap->ssl_ == nullptr)
|
|
return env->ThrowTypeError("SetVerifyMode after destroySSL");
|
|
|
|
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 TLSWrap::EnableSessionCallbacks(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
TLSWrap* wrap = Unwrap<TLSWrap>(args.Holder());
|
|
if (wrap->ssl_ == nullptr) {
|
|
return wrap->env()->ThrowTypeError(
|
|
"EnableSessionCallbacks after destroySSL");
|
|
}
|
|
wrap->enable_session_callbacks();
|
|
NodeBIO::FromBIO(wrap->enc_in_)->set_initial(kMaxHelloLength);
|
|
wrap->hello_parser_.Start(SSLWrap<TLSWrap>::OnClientHello,
|
|
OnClientHelloParseEnd,
|
|
wrap);
|
|
}
|
|
|
|
|
|
void TLSWrap::DestroySSL(const FunctionCallbackInfo<Value>& args) {
|
|
TLSWrap* wrap = Unwrap<TLSWrap>(args.Holder());
|
|
|
|
// Move all writes to pending
|
|
wrap->MakePending();
|
|
|
|
// And destroy
|
|
wrap->InvokeQueued(UV_ECANCELED);
|
|
|
|
// Destroy the SSL structure and friends
|
|
wrap->SSLWrap<TLSWrap>::DestroySSL();
|
|
|
|
delete wrap->clear_in_;
|
|
wrap->clear_in_ = nullptr;
|
|
}
|
|
|
|
|
|
void TLSWrap::EnableCertCb(const FunctionCallbackInfo<Value>& args) {
|
|
TLSWrap* wrap = Unwrap<TLSWrap>(args.Holder());
|
|
wrap->WaitForCertCb(OnClientHelloParseEnd, wrap);
|
|
}
|
|
|
|
|
|
void TLSWrap::OnClientHelloParseEnd(void* arg) {
|
|
TLSWrap* c = static_cast<TLSWrap*>(arg);
|
|
c->Cycle();
|
|
}
|
|
|
|
|
|
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
void TLSWrap::GetServername(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
TLSWrap* wrap = Unwrap<TLSWrap>(args.Holder());
|
|
|
|
CHECK_NE(wrap->ssl_, nullptr);
|
|
|
|
const char* servername = SSL_get_servername(wrap->ssl_,
|
|
TLSEXT_NAMETYPE_host_name);
|
|
if (servername != nullptr) {
|
|
args.GetReturnValue().Set(OneByteString(env->isolate(), servername));
|
|
} else {
|
|
args.GetReturnValue().Set(false);
|
|
}
|
|
}
|
|
|
|
|
|
void TLSWrap::SetServername(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
TLSWrap* wrap = Unwrap<TLSWrap>(args.Holder());
|
|
|
|
if (args.Length() < 1 || !args[0]->IsString())
|
|
return env->ThrowTypeError("First argument should be a string");
|
|
|
|
if (wrap->started_)
|
|
return env->ThrowError("Already started.");
|
|
|
|
if (!wrap->is_client())
|
|
return;
|
|
|
|
CHECK_NE(wrap->ssl_, nullptr);
|
|
|
|
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
node::Utf8Value servername(env->isolate(), args[0].As<String>());
|
|
SSL_set_tlsext_host_name(wrap->ssl_, *servername);
|
|
#endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
}
|
|
|
|
|
|
int TLSWrap::SelectSNIContextCallback(SSL* s, int* ad, void* arg) {
|
|
TLSWrap* p = static_cast<TLSWrap*>(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;
|
|
|
|
// Call the SNI callback and use its return value as context
|
|
Local<Object> object = p->object();
|
|
Local<Value> ctx = object->Get(env->sni_context_string());
|
|
|
|
// Not an object, probably undefined or null
|
|
if (!ctx->IsObject())
|
|
return SSL_TLSEXT_ERR_NOACK;
|
|
|
|
Local<FunctionTemplate> cons = env->secure_context_constructor_template();
|
|
if (!cons->HasInstance(ctx)) {
|
|
// Failure: incorrect SNI context object
|
|
Local<Value> err = Exception::TypeError(env->sni_context_err_string());
|
|
p->MakeCallback(env->onerror_string(), 1, &err);
|
|
return SSL_TLSEXT_ERR_NOACK;
|
|
}
|
|
|
|
p->sni_context_.Reset();
|
|
p->sni_context_.Reset(env->isolate(), ctx);
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(ctx.As<Object>());
|
|
InitNPN(sc);
|
|
SSL_set_SSL_CTX(s, sc->ctx_);
|
|
return SSL_TLSEXT_ERR_OK;
|
|
}
|
|
#endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
|
|
|
|
void TLSWrap::Initialize(Handle<Object> target,
|
|
Handle<Value> unused,
|
|
Handle<Context> context) {
|
|
Environment* env = Environment::GetCurrent(context);
|
|
|
|
env->SetMethod(target, "wrap", TLSWrap::Wrap);
|
|
|
|
Local<FunctionTemplate> t = FunctionTemplate::New(env->isolate());
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
t->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "TLSWrap"));
|
|
|
|
env->SetProtoMethod(t, "receive", Receive);
|
|
env->SetProtoMethod(t, "start", Start);
|
|
env->SetProtoMethod(t, "setVerifyMode", SetVerifyMode);
|
|
env->SetProtoMethod(t, "enableSessionCallbacks", EnableSessionCallbacks);
|
|
env->SetProtoMethod(t, "destroySSL", DestroySSL);
|
|
env->SetProtoMethod(t, "enableCertCb", EnableCertCb);
|
|
|
|
StreamBase::AddMethods<TLSWrap>(env, t, StreamBase::kFlagHasWritev);
|
|
SSLWrap<TLSWrap>::AddMethods(env, t);
|
|
|
|
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
env->SetProtoMethod(t, "getServername", GetServername);
|
|
env->SetProtoMethod(t, "setServername", SetServername);
|
|
#endif // SSL_CRT_SET_TLSEXT_SERVERNAME_CB
|
|
|
|
env->set_tls_wrap_constructor_template(t);
|
|
env->set_tls_wrap_constructor_function(t->GetFunction());
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "TLSWrap"),
|
|
t->GetFunction());
|
|
}
|
|
|
|
} // namespace node
|
|
|
|
NODE_MODULE_CONTEXT_AWARE_BUILTIN(tls_wrap, node::TLSWrap::Initialize)
|