Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: in-source comments and minor TLS cleanups #25713

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -1003,8 +1003,6 @@ Server.prototype.setSecureContext = function(options) {
if (options.ticketKeys) {
this.ticketKeys = options.ticketKeys;
this.setTicketKeys(this.ticketKeys);
} else {
this.setTicketKeys(this.getTicketKeys());
}
};

Expand All @@ -1021,8 +1019,8 @@ Server.prototype._setServerData = function(data) {
};


Server.prototype.getTicketKeys = function getTicketKeys(keys) {
return this._sharedCreds.context.getTicketKeys(keys);
Server.prototype.getTicketKeys = function getTicketKeys() {
return this._sharedCreds.context.getTicketKeys();
};


Expand Down
5 changes: 3 additions & 2 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1532,7 +1532,7 @@ int SSLWrap<Base>::NewSessionCallback(SSL* s, SSL_SESSION* sess) {
reinterpret_cast<const char*>(session_id_data),
session_id_length).ToLocalChecked();
Local<Value> argv[] = { session_id, session };
w->new_session_wait_ = true;
w->awaiting_new_session_ = true;
w->MakeCallback(env->onnewsession_string(), arraysize(argv), argv);

return 0;
Expand Down Expand Up @@ -2128,6 +2128,7 @@ void SSLWrap<Base>::Renegotiate(const FunctionCallbackInfo<Value>& args) {

ClearErrorOnReturn clear_error_on_return;

// XXX(sam) Return/throw an error, don't discard the SSL error reason.
bool yes = SSL_renegotiate(w->ssl_.get()) == 1;
args.GetReturnValue().Set(yes);
}
Expand Down Expand Up @@ -2161,7 +2162,7 @@ template <class Base>
void SSLWrap<Base>::NewSessionDone(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
w->new_session_wait_ = false;
w->awaiting_new_session_ = false;
w->NewSessionDoneCb();
}

Expand Down
6 changes: 3 additions & 3 deletions src/node_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ class SSLWrap {
kind_(kind),
next_sess_(nullptr),
session_callbacks_(false),
new_session_wait_(false),
awaiting_new_session_(false),
cert_cb_(nullptr),
cert_cb_arg_(nullptr),
cert_cb_running_(false) {
Expand All @@ -234,7 +234,7 @@ class SSLWrap {
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_waiting_new_session() const { return new_session_wait_; }
inline bool is_awaiting_new_session() const { return awaiting_new_session_; }
inline bool is_waiting_cert_cb() const { return cert_cb_ != nullptr; }

protected:
Expand Down Expand Up @@ -325,7 +325,7 @@ class SSLWrap {
SSLSessionPointer next_sess_;
SSLPointer ssl_;
bool session_callbacks_;
bool new_session_wait_;
bool awaiting_new_session_;

// SSL_set_cert_cb
CertCb cert_cb_;
Expand Down
11 changes: 6 additions & 5 deletions src/node_crypto_bio.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ namespace node {
namespace crypto {

// This class represents buffers for OpenSSL I/O, implemented as a singly-linked
// list of chunks. It can be used both for writing data from Node to OpenSSL
// and back, but only one direction per instance.
// list of chunks. It can be used either for writing data from Node to OpenSSL,
// or for reading data back, but not both.
// The structure is only accessed, and owned by, the OpenSSL BIOPointer
// (a.k.a. std::unique_ptr<BIO>).
class NodeBIO : public MemoryRetainer {
Expand Down Expand Up @@ -80,11 +80,12 @@ class NodeBIO : public MemoryRetainer {
// Put `len` bytes from `data` into buffer
void Write(const char* data, size_t size);

// Return pointer to internal data and amount of
// contiguous data available for future writes
// Return pointer to contiguous block of reserved data and the size available
// for future writes. Call Commit() once the write is complete.
char* PeekWritable(size_t* size);

// Commit reserved data
// Specify how much data was written into the block returned by
// PeekWritable().
void Commit(size_t size);


Expand Down
3 changes: 3 additions & 0 deletions src/node_crypto_clienthello.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
namespace node {
namespace crypto {

// Parse the client hello so we can do async session resumption. OpenSSL's
// session resumption uses synchronous callbacks, see SSL_CTX_sess_set_get_cb
// and get_session_cb.
class ClientHelloParser {
public:
inline ClientHelloParser();
Expand Down
2 changes: 1 addition & 1 deletion src/stream_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ void LibuvStreamWrap::OnUvRead(ssize_t nread, const uv_buf_t* buf) {
type = uv_pipe_pending_type(reinterpret_cast<uv_pipe_t*>(stream()));
}

// We should not be getting this callback if someone as already called
// We should not be getting this callback if someone has already called
// uv_close() on the handle.
CHECK_EQ(persistent().IsEmpty(), false);

Expand Down
60 changes: 43 additions & 17 deletions src/tls_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ void TLSWrap::InitSSL() {
#endif // SSL_MODE_RELEASE_BUFFERS

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()) {
Expand Down Expand Up @@ -194,6 +199,9 @@ void TLSWrap::Start(const FunctionCallbackInfo<Value>& args) {

// 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();
}
Expand Down Expand Up @@ -248,7 +256,7 @@ void TLSWrap::EncOut() {
return;

// Wait for `newSession` callback to be invoked
if (is_waiting_new_session())
if (is_awaiting_new_session())
return;

// Split-off queue
Expand All @@ -258,7 +266,7 @@ void TLSWrap::EncOut() {
if (ssl_ == nullptr)
return;

// No data to write
// No encrypted output ready to write to the underlying stream.
if (BIO_pending(enc_out_) == 0) {
if (pending_cleartext_input_.empty())
InvokeQueued(0);
Expand Down Expand Up @@ -480,13 +488,13 @@ void TLSWrap::ClearOut() {
}


bool TLSWrap::ClearIn() {
void TLSWrap::ClearIn() {
// Ignore cycling data if ClientHello wasn't yet parsed
if (!hello_parser_.IsEnded())
return false;
return;

if (ssl_ == nullptr)
return false;
return;

std::vector<uv_buf_t> buffers;
buffers.swap(pending_cleartext_input_);
Expand All @@ -506,8 +514,9 @@ bool TLSWrap::ClearIn() {

// All written
if (i == buffers.size()) {
// We wrote all the buffers, so no writes failed (written < 0 on failure).
CHECK_GE(written, 0);
return true;
return;
}

// Error or partial write
Expand All @@ -519,6 +528,8 @@ bool TLSWrap::ClearIn() {
Local<Value> arg = GetSSLError(written, &err, &error_str);
if (!arg.IsEmpty()) {
write_callback_scheduled_ = true;
// XXX(sam) Should forward an error object with .code/.function/.etc, if
// possible.
InvokeQueued(UV_EPROTO, error_str.c_str());
} else {
// Push back the not-yet-written pending buffers into their queue.
Expand All @@ -529,7 +540,7 @@ bool TLSWrap::ClearIn() {
buffers.end());
}

return false;
return;
}


Expand Down Expand Up @@ -585,6 +596,7 @@ void TLSWrap::ClearError() {
}


// Called by StreamBase::Write() to request async write of clear text into SSL.
int TLSWrap::DoWrite(WriteWrap* w,
uv_buf_t* bufs,
size_t count,
Expand All @@ -598,18 +610,26 @@ int TLSWrap::DoWrite(WriteWrap* w,
}

bool empty = true;

// Empty writes should not go through encryption process
size_t i;
for (i = 0; i < count; i++)
for (i = 0; i < count; i++) {
if (bufs[i].len > 0) {
empty = false;
break;
}
}

// 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 (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) {
CHECK_NULL(current_empty_write_);
current_empty_write_ = w;
Expand All @@ -629,7 +649,7 @@ int TLSWrap::DoWrite(WriteWrap* w,
CHECK_NULL(current_write_);
current_write_ = w;

// Write queued data
// Write encrypted data to underlying stream and call Done().
if (empty) {
EncOut();
return 0;
Expand All @@ -648,17 +668,20 @@ int TLSWrap::DoWrite(WriteWrap* w,
if (i != count) {
int err;
Local<Value> arg = GetSSLError(written, &err, &error_);

// If we stopped writing because of an error, it's fatal, discard the data.
if (!arg.IsEmpty()) {
current_write_ = nullptr;
return UV_EPROTO;
}

// Otherwise, save unwritten data so it can be written later by ClearIn().
pending_cleartext_input_.insert(pending_cleartext_input_.end(),
&bufs[i],
&bufs[count]);
}

// Try writing data immediately
// Write any encrypted/handshake output that may be ready.
EncOut();

return 0;
Expand Down Expand Up @@ -690,17 +713,20 @@ void TLSWrap::OnStreamRead(ssize_t nread, const uv_buf_t& buf) {
return;
}

// Only client connections can receive data
if (ssl_ == nullptr) {
EmitRead(UV_EPROTO);
return;
}

// Commit read data
// 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
// 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<uint8_t*>(enc_in->Peek(&avail));
Expand Down
30 changes: 23 additions & 7 deletions src/tls_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ class TLSWrap : public AsyncWrap,
uv_buf_t* bufs,
size_t count,
uv_stream_t* send_handle) override;
// Return error_ string or nullptr if it's empty.
const char* Error() const override;
// Reset error_ string to empty. Not related to "clear text".
void ClearError() override;

void NewSessionDoneCb();
Expand Down Expand Up @@ -105,11 +107,22 @@ class TLSWrap : public AsyncWrap,

static void SSLInfoCallback(const SSL* ssl_, int where, int ret);
void InitSSL();
void EncOut();
bool ClearIn();
void ClearOut();
// SSL has a "clear" text (unencrypted) side (to/from the node API) and
// encrypted ("enc") text side (to/from the underlying socket/stream).
// On each side data flows "in" or "out" of SSL context.
//
// EncIn() doesn't exist. Encrypted data is pushed from underlying stream into
// enc_in_ via the stream listener's OnStreamAlloc()/OnStreamRead() interface.
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.

// Call Done() on outstanding WriteWrap request.
bool 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)
Expand All @@ -118,6 +131,7 @@ class TLSWrap : public AsyncWrap,
for (; cycle_depth_ > 0; cycle_depth_--) {
ClearIn();
ClearOut();
// EncIn() doesn't exist, it happens via stream listener callbacks.
EncOut();
}
}
Expand All @@ -139,16 +153,18 @@ class TLSWrap : public AsyncWrap,
static void SetVerifyMode(const v8::FunctionCallbackInfo<v8::Value>& args);
static void EnableSessionCallbacks(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void EnableCertCb(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void EnableTrace(const v8::FunctionCallbackInfo<v8::Value>& args);
static void EnableCertCb(const v8::FunctionCallbackInfo<v8::Value>& args);
static void DestroySSL(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetServername(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetServername(const v8::FunctionCallbackInfo<v8::Value>& args);
static int SelectSNIContextCallback(SSL* s, int* ad, void* arg);

crypto::SecureContext* sc_;
BIO* enc_in_ = nullptr;
BIO* enc_out_ = nullptr;
// 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().
// Waiting for ClearIn() to pass to SSL_write().
std::vector<uv_buf_t> pending_cleartext_input_;
size_t write_size_ = 0;
WriteWrap* current_write_ = nullptr;
Expand Down