blob: bee38e390a54b7c01d40e994f7514098d18348fe [file] [log] [blame]
#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::EscapableHandleScope;
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;
TLSCallbacks::TLSCallbacks(Environment* env,
Kind kind,
Handle<Object> sc,
StreamWrapCallbacks* old)
: SSLWrap<TLSCallbacks>(env, Unwrap<SecureContext>(sc), kind),
StreamWrapCallbacks(old),
AsyncWrap(env,
env->tls_wrap_constructor_function()->NewInstance(),
AsyncWrap::PROVIDER_TLSWRAP),
sc_(Unwrap<SecureContext>(sc)),
sc_handle_(env->isolate(), sc),
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);
// 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<TLSCallbacks>::GetSessionCallback);
SSL_CTX_sess_set_new_cb(sc_->ctx_, SSLWrap<TLSCallbacks>::NewSessionCallback);
InitSSL();
}
TLSCallbacks::~TLSCallbacks() {
enc_in_ = nullptr;
enc_out_ = nullptr;
delete clear_in_;
clear_in_ = nullptr;
sc_ = nullptr;
sc_handle_.Reset();
persistent().Reset();
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
sni_context_.Reset();
#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 = ContainerOf(&WriteItem::member_, q);
delete wi;
}
ClearError();
}
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
QUEUE queue;
QUEUE* q = QUEUE_HEAD(&pending_write_items_);
QUEUE_SPLIT(&pending_write_items_, q, &queue);
while (QUEUE_EMPTY(&queue) == false) {
q = QUEUE_HEAD(&queue);
QUEUE_REMOVE(q);
WriteItem* wi = ContainerOf(&WriteItem::member_, q);
wi->cb_(&wi->w_->req_, status);
delete wi;
}
return true;
}
void TLSCallbacks::NewSessionDoneCb() {
Cycle();
}
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);
}
#endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
InitNPN(sc_);
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();
}
void TLSCallbacks::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 = args[0].As<Object>();
Local<Object> sc = args[1].As<Object>();
Kind kind = args[2]->IsTrue() ? SSLWrap<TLSCallbacks>::kServer :
SSLWrap<TLSCallbacks>::kClient;
TLSCallbacks* callbacks = nullptr;
WITH_GENERIC_STREAM(env, stream, {
callbacks = new TLSCallbacks(env, kind, sc, wrap->callbacks());
wrap->OverrideCallbacks(callbacks, true);
});
if (callbacks == nullptr) {
return args.GetReturnValue().SetNull();
}
args.GetReturnValue().Set(callbacks->persistent());
}
void TLSCallbacks::Receive(const FunctionCallbackInfo<Value>& args) {
TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.Holder());
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<uv_handle_t*>(stream))) {
wrap->DoAlloc(reinterpret_cast<uv_handle_t*>(stream), len, &buf);
size_t copy = buf.len > len ? len : buf.len;
memcpy(buf.base, data, copy);
buf.len = copy;
wrap->DoRead(stream, buf.len, &buf, UV_UNKNOWN_HANDLE);
data += copy;
len -= copy;
}
}
void TLSCallbacks::Start(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
TLSCallbacks* wrap = Unwrap<TLSCallbacks>(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 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*>(ssl_);
TLSCallbacks* c = static_cast<TLSCallbacks*>(SSL_get_app_data(ssl));
Environment* env = c->env();
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
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 TLSCallbacks::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_ && !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);
CHECK(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<TLSCallbacks*>(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(nullptr, callbacks->write_size_);
// Try writing more data
callbacks->write_size_ = 0;
callbacks->EncOut();
}
Local<Value> TLSCallbacks::GetSSLError(int status, int* err, const char** msg) {
EscapableHandleScope scope(env()->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.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;
}
static_cast<void>(BIO_reset(bio));
return scope.Escape(exception);
}
}
return Local<Value>();
}
void TLSCallbacks::ClearOut() {
// Ignore cycling data if ClientHello wasn't yet parsed
if (!hello_parser_.IsEnded())
return;
// No reads after EOF
if (eof_)
return;
HandleScope handle_scope(env()->isolate());
Context::Scope context_scope(env()->context());
CHECK_NE(ssl_, nullptr);
char out[kClearOutChunkSize];
int read;
do {
read = SSL_read(ssl_, out, sizeof(out));
if (read > 0) {
Local<Value> argv[] = {
Integer::New(env()->isolate(), read),
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<Value> arg = Integer::New(env()->isolate(), UV_EOF);
wrap()->MakeCallback(env()->onread_string(), 1, &arg);
}
if (read == -1) {
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 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);
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;
}
HandleScope handle_scope(env()->isolate());
Context::Scope context_scope(env()->context());
// 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;
}
const char* TLSCallbacks::Error() const {
return error_;
}
void TLSCallbacks::ClearError() {
delete[] error_;
error_ = nullptr;
}
int TLSCallbacks::TryWrite(uv_buf_t** bufs, size_t* count) {
// TODO(indutny): Support it
return 0;
}
int TLSCallbacks::DoWrite(WriteWrap* w,
uv_buf_t* bufs,
size_t count,
uv_stream_t* send_handle,
uv_write_cb cb) {
CHECK_EQ(send_handle, 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 uv_write(&w->req_, wrap()->stream(), bufs, count, cb);
}
// Queue callback to execute it on next tick
WriteItem* wi = new WriteItem(w, 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);
CHECK(written == -1 || written == static_cast<int>(bufs[i].len));
if (written == -1)
break;
}
if (i != count) {
int err;
HandleScope handle_scope(env()->isolate());
Context::Scope context_scope(env()->context());
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 TLSCallbacks::AfterWrite(WriteWrap* w) {
// Intentionally empty
}
void TLSCallbacks::DoAlloc(uv_handle_t* handle,
size_t suggested_size,
uv_buf_t* buf) {
size_t size = 0;
buf->base = NodeBIO::FromBIO(enc_in_)->PeekWritable(&size);
buf->len = 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<Value> arg = Integer::New(env()->isolate(), nread);
wrap()->MakeCallback(env()->onread_string(), 1, &arg);
return;
}
// Only client connections can receive data
CHECK_NE(ssl_, nullptr);
// 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 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<Value>& args) {
Environment* env = Environment::GetCurrent(args);
TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.Holder());
if (args.Length() < 2 || !args[0]->IsBoolean() || !args[1]->IsBoolean())
return env->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<Value>& args) {
TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.Holder());
wrap->enable_session_callbacks();
EnableHelloParser(args);
}
void TLSCallbacks::EnableHelloParser(const FunctionCallbackInfo<Value>& args) {
TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.Holder());
NodeBIO::FromBIO(wrap->enc_in_)->set_initial(kMaxHelloLength);
wrap->hello_parser_.Start(SSLWrap<TLSCallbacks>::OnClientHello,
OnClientHelloParseEnd,
wrap);
}
void TLSCallbacks::OnClientHelloParseEnd(void* arg) {
TLSCallbacks* c = static_cast<TLSCallbacks*>(arg);
c->Cycle();
}
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
void TLSCallbacks::GetServername(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.Holder());
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 TLSCallbacks::SetServername(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
TLSCallbacks* wrap = Unwrap<TLSCallbacks>(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;
#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 TLSCallbacks::SelectSNIContextCallback(SSL* s, int* ad, void* arg) {
TLSCallbacks* p = static_cast<TLSCallbacks*>(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 scope(env->isolate());
// 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 TLSCallbacks::Initialize(Handle<Object> target,
Handle<Value> unused,
Handle<Context> context) {
Environment* env = Environment::GetCurrent(context);
env->SetMethod(target, "wrap", TLSCallbacks::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, "enableHelloParser", EnableHelloParser);
SSLWrap<TLSCallbacks>::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_function(t->GetFunction());
}
} // namespace node
NODE_MODULE_CONTEXT_AWARE_BUILTIN(tls_wrap, node::TLSCallbacks::Initialize)