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

tls_wrap: migrate synchronous errors #18125

Closed
wants to merge 3 commits 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: 6 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -1530,6 +1530,12 @@ a hostname in the first parameter.
An excessive amount of TLS renegotiations is detected, which is a potential
vector for denial-of-service attacks.

<a id="ERR_TLS_SNI_FROM_SERVER"></a>
### ERR_TLS_SNI_FROM_SERVER

An attempt was made to issue Server Name Indication from a TLS server-side
socket, which is only valid from a client.

<a id="ERR_TLS_RENEGOTIATION_DISABLED"></a>
### ERR_TLS_RENEGOTIATION_DISABLED

Expand Down
22 changes: 19 additions & 3 deletions lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const { Timer } = process.binding('timer_wrap');
const tls_wrap = process.binding('tls_wrap');
const { TCP, constants: TCPConstants } = process.binding('tcp_wrap');
const { Pipe, constants: PipeConstants } = process.binding('pipe_wrap');
const {
SecureContext: NativeSecureContext
} = process.binding('crypto');
const errors = require('internal/errors');
const kConnectOptions = Symbol('connect-options');
const kDisableRenegotiation = Symbol('disable-renegotiation');
Expand Down Expand Up @@ -407,7 +410,12 @@ TLSSocket.prototype._wrapHandle = function(wrap) {
const context = options.secureContext ||
options.credentials ||
tls.createSecureContext(options);
const res = tls_wrap.wrap(handle._externalStream,
const externalStream = handle._externalStream;
assert(typeof externalStream === 'object',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be throw errors.TypeErrors instead of asserts?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maclover7 I tried that before and the test looked really dubious. These are not user-facing errors, whoever hits then either is using the underscored API or patching internals incorrectly, or is hitting a bug. Either way assertions seem to be more appropriate.

'handle must be a LibuvStreamWrap');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just needs to be a StreamBase, even if LibuvStreamWrap is the common case here.

Copy link
Member Author

@joyeecheung joyeecheung Jan 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@addaleax I thought about checking the type of handle here but there doesn't seem to be a way of checking those in JS land since there isn't a proper inheritance of StreamBase.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joyeecheung Yeah, externalStream doesn’t really tell you what kind of thing it is. :/

Ideally, what we could do is making the StreamBase inheritance hierarchy reflected in the actual JS handle classes; that requires more work than this PR, though…

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@addaleax Yeah that should probably better be done in a dedicated PR IMO

assert(context.context instanceof NativeSecureContext,
'context.context must be a NativeSecureContext');
const res = tls_wrap.wrap(externalStream,
context.context,
!!options.isServer);
res._parent = handle;
Expand Down Expand Up @@ -548,8 +556,8 @@ TLSSocket.prototype.renegotiate = function(options, callback) {
if (this.destroyed)
return;

let requestCert = this._requestCert;
let rejectUnauthorized = this._rejectUnauthorized;
let requestCert = !!this._requestCert;
let rejectUnauthorized = !!this._rejectUnauthorized;

if (options.requestCert !== undefined)
requestCert = !!options.requestCert;
Expand Down Expand Up @@ -649,6 +657,14 @@ TLSSocket.prototype._start = function() {
};

TLSSocket.prototype.setServername = function(name) {
if (typeof name !== 'string') {
throw new errors.Error('ERR_INVALID_ARG_TYPE', 'name', 'string');
}

if (this._tlsOptions.isServer) {
throw new errors.Error('ERR_TLS_SNI_FROM_SERVER');
}

this._handle.setServername(name);
};

Expand Down
2 changes: 2 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,8 @@ E('ERR_TLS_RENEGOTIATION_FAILED', 'Failed to renegotiate');
E('ERR_TLS_REQUIRED_SERVER_NAME',
'"servername" is required parameter for Server.addContext');
E('ERR_TLS_SESSION_ATTACK', 'TLS session renegotiation attack detected');
E('ERR_TLS_SNI_FROM_SERVER',
'Cannot issue SNI from a TLS server-side socket');
E('ERR_TRANSFORM_ALREADY_TRANSFORMING',
'Calling transform done when still transforming');
E('ERR_TRANSFORM_WITH_LENGTH_0',
Expand Down
48 changes: 15 additions & 33 deletions src/tls_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -181,16 +181,10 @@ void TLSWrap::InitSSL() {
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 LibuvStreamWrap 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");
CHECK_EQ(args.Length(), 3);
CHECK(args[0]->IsObject());
CHECK(args[1]->IsObject());
CHECK(args[2]->IsBoolean());

Local<External> stream_obj = args[0].As<External>();
Local<Object> sc = args[1].As<Object>();
Expand Down Expand Up @@ -231,13 +225,11 @@ void TLSWrap::Receive(const FunctionCallbackInfo<Value>& args) {


void TLSWrap::Start(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

TLSWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());

if (wrap->started_)
return env->ThrowError("Already started.");
CHECK(!wrap->started_);

wrap->started_ = true;

// Send ClientHello handshake
Expand Down Expand Up @@ -753,16 +745,13 @@ int TLSWrap::DoShutdown(ShutdownWrap* req_wrap) {


void TLSWrap::SetVerifyMode(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

TLSWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, 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");
CHECK_EQ(args.Length(), 2);
CHECK(args[0]->IsBoolean());
CHECK(args[1]->IsBoolean());
CHECK_NE(wrap->ssl_, nullptr);

int verify_mode;
if (wrap->is_server()) {
Expand Down Expand Up @@ -790,10 +779,7 @@ void TLSWrap::EnableSessionCallbacks(
const FunctionCallbackInfo<Value>& args) {
TLSWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
if (wrap->ssl_ == nullptr) {
return wrap->env()->ThrowTypeError(
"EnableSessionCallbacks after destroySSL");
}
CHECK_NE(wrap->ssl_, nullptr);
wrap->enable_session_callbacks();
crypto::NodeBIO::FromBIO(wrap->enc_in_)->set_initial(kMaxHelloLength);
wrap->hello_parser_.Start(SSLWrap<TLSWrap>::OnClientHello,
Expand Down Expand Up @@ -855,14 +841,10 @@ void TLSWrap::SetServername(const FunctionCallbackInfo<Value>& args) {
TLSWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, 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_EQ(args.Length(), 1);
CHECK(args[0]->IsString());
CHECK(!wrap->started_);
CHECK(wrap->is_client());

CHECK_NE(wrap->ssl_, nullptr);

Expand Down
9 changes: 7 additions & 2 deletions test/parallel/test-tls-basic-validations.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,13 @@ assert.throws(() => tls.createServer({ ticketKeys: 'abcd' }),
assert.throws(() => tls.createServer({ ticketKeys: Buffer.alloc(0) }),
/TypeError: Ticket keys length must be 48 bytes/);

assert.throws(() => tls.createSecurePair({}),
/TypeError: Second argument should be a SecureContext instance/);
common.expectsError(
() => tls.createSecurePair({}),
{
code: 'ERR_ASSERTION',
message: 'context.context must be a NativeSecureContext'
}
);

{
const buffer = Buffer.from('abcd');
Expand Down
46 changes: 46 additions & 0 deletions test/parallel/test-tls-error-servername.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';

// This tests the errors thrown from TLSSocket.prototype.setServername

const common = require('../common');
const fixtures = require('../common/fixtures');

if (!common.hasCrypto)
common.skip('missing crypto');

const { connect, TLSSocket } = require('tls');
const makeDuplexPair = require('../common/duplexpair');
const { clientSide, serverSide } = makeDuplexPair();

const key = fixtures.readKey('agent1-key.pem');
const cert = fixtures.readKey('agent1-cert.pem');
const ca = fixtures.readKey('ca1-cert.pem');

const client = connect({
socket: clientSide,
ca,
host: 'agent1' // Hostname from certificate
});

[undefined, null, 1, true, {}].forEach((value) => {
common.expectsError(() => {
client.setServername(value);
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: 'The "name" argument must be of type string'
});
});

const server = new TLSSocket(serverSide, {
isServer: true,
key,
cert,
ca
});

common.expectsError(() => {
server.setServername('localhost');
}, {
code: 'ERR_TLS_SNI_FROM_SERVER',
message: 'Cannot issue SNI from a TLS server-side socket'
});