mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
902 lines
22 KiB
902 lines
22 KiB
#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_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::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<External> stream_obj = args[0].As<External>();
|
|
Local<Object> sc = args[1].As<Object>();
|
|
Kind kind = args[2]->IsTrue() ? SSLWrap<TLSWrap>::kServer :
|
|
SSLWrap<TLSWrap>::kClient;
|
|
|
|
StreamBase* stream = static_cast<StreamBase*>(stream_obj->Value());
|
|
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 node#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 is any data that should be written to the socket,
|
|
// the 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::New(wrap->env(), buf->base, buf->len).ToLocalChecked();
|
|
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(Local<Object> target,
|
|
Local<Value> unused,
|
|
Local<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)
|
|
|