diff options
author | Denys Otrishko <shishugi@gmail.com> | 2018-10-29 11:38:43 +0300 |
---|---|---|
committer | Ruben Bridgewater <ruben@bridgewater.de> | 2019-12-25 14:24:42 +0300 |
commit | f8d7e2216e5821719b5a341c41251d5a860cf5f7 (patch) | |
tree | 8f3a1db4b065a844483ee1a8e388702463683570 /lib | |
parent | 3d47c8592d179991d3bfa4902f12c4fce07ac2d3 (diff) |
tls: add PSK support
Add the `pskCallback` client/server option, which resolves an identity
or identity hint to a pre-shared key.
Add the `pskIdentityHint` server option to set the identity hint for the
ServerKeyExchange message.
Co-authored-by: Chris Osborn <chris.osborn@sitelier.com>
Co-authored-by: stephank <gh@stephank.nl>
Co-authored-by: Taylor Zane Glaeser <tzglaeser@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/23188
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/_tls_wrap.js | 117 |
1 files changed, 114 insertions, 3 deletions
diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index 02a5074b4fe..47a59c02fc5 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -50,10 +50,12 @@ const { TCP, constants: TCPConstants } = internalBinding('tcp_wrap'); const tls_wrap = internalBinding('tls_wrap'); const { Pipe, constants: PipeConstants } = internalBinding('pipe_wrap'); const { owner_symbol } = require('internal/async_hooks').symbols; +const { isArrayBufferView } = require('internal/util/types'); const { SecureContext: NativeSecureContext } = internalBinding('crypto'); const { connResetException, codes } = require('internal/errors'); const { ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, ERR_INVALID_CALLBACK, ERR_MULTIPLE_CALLBACK, ERR_SOCKET_CLOSED, @@ -65,8 +67,9 @@ const { ERR_TLS_SESSION_ATTACK, ERR_TLS_SNI_FROM_SERVER } = codes; +const { onpskexchange: kOnPskExchange } = internalBinding('symbols'); const { getOptionValue } = require('internal/options'); -const { validateString } = require('internal/validators'); +const { validateString, validateBuffer } = require('internal/validators'); const traceTls = getOptionValue('--trace-tls'); const tlsKeylog = getOptionValue('--tls-keylog'); const { appendFile } = require('fs'); @@ -77,6 +80,8 @@ const kHandshakeTimeout = Symbol('handshake-timeout'); const kRes = Symbol('res'); const kSNICallback = Symbol('snicallback'); const kEnableTrace = Symbol('enableTrace'); +const kPskCallback = Symbol('pskcallback'); +const kPskIdentityHint = Symbol('pskidentityhint'); const noop = () => {}; @@ -296,6 +301,67 @@ function onnewsession(sessionId, session) { done(); } +function onPskServerCallback(identity, maxPskLen) { + const owner = this[owner_symbol]; + const ret = owner[kPskCallback](owner, identity); + if (ret == null) + return undefined; + + let psk; + if (isArrayBufferView(ret)) { + psk = ret; + } else { + if (typeof ret !== 'object') { + throw new ERR_INVALID_ARG_TYPE( + 'ret', + ['Object', 'Buffer', 'TypedArray', 'DataView'], + ret + ); + } + psk = ret.psk; + validateBuffer(psk, 'psk'); + } + + if (psk.length > maxPskLen) { + throw new ERR_INVALID_ARG_VALUE( + 'psk', + psk, + `Pre-shared key exceeds ${maxPskLen} bytes` + ); + } + + return psk; +} + +function onPskClientCallback(hint, maxPskLen, maxIdentityLen) { + const owner = this[owner_symbol]; + const ret = owner[kPskCallback](hint); + if (ret == null) + return undefined; + + if (typeof ret !== 'object') + throw new ERR_INVALID_ARG_TYPE('ret', 'Object', ret); + + validateBuffer(ret.psk, 'psk'); + if (ret.psk.length > maxPskLen) { + throw new ERR_INVALID_ARG_VALUE( + 'psk', + ret.psk, + `Pre-shared key exceeds ${maxPskLen} bytes` + ); + } + + validateString(ret.identity, 'identity'); + if (Buffer.byteLength(ret.identity) > maxIdentityLen) { + throw new ERR_INVALID_ARG_VALUE( + 'identity', + ret.identity, + `PSK identity exceeds ${maxIdentityLen} bytes` + ); + } + + return { psk: ret.psk, identity: ret.identity }; +} function onkeylogclient(line) { debug('client onkeylog'); @@ -694,6 +760,32 @@ TLSSocket.prototype._init = function(socket, wrap) { ssl.setALPNProtocols(ssl._secureContext.alpnBuffer); } + if (options.pskCallback && ssl.enablePskCallback) { + if (typeof options.pskCallback !== 'function') { + throw new ERR_INVALID_ARG_TYPE('pskCallback', + 'function', + options.pskCallback); + } + + ssl[kOnPskExchange] = options.isServer ? + onPskServerCallback : onPskClientCallback; + + this[kPskCallback] = options.pskCallback; + ssl.enablePskCallback(); + + if (options.pskIdentityHint) { + if (typeof options.pskIdentityHint !== 'string') { + throw new ERR_INVALID_ARG_TYPE( + 'options.pskIdentityHint', + 'string', + options.pskIdentityHint + ); + } + ssl.setPskIdentityHint(options.pskIdentityHint); + } + } + + if (options.handshakeTimeout > 0) this.setTimeout(options.handshakeTimeout, this._handleTimeout); @@ -905,7 +997,7 @@ function makeSocketMethodProxy(name) { TLSSocket.prototype[method] = makeSocketMethodProxy(method); }); -// TODO: support anonymous (nocert) and PSK +// TODO: support anonymous (nocert) function onServerSocketSecure() { @@ -961,6 +1053,8 @@ function tlsConnectionListener(rawSocket) { SNICallback: this[kSNICallback] || SNICallback, enableTrace: this[kEnableTrace], pauseOnConnect: this.pauseOnConnect, + pskCallback: this[kPskCallback], + pskIdentityHint: this[kPskIdentityHint], }); socket.on('secure', onServerSocketSecure); @@ -1065,6 +1159,8 @@ function Server(options, listener) { this[kHandshakeTimeout] = options.handshakeTimeout || (120 * 1000); this[kSNICallback] = options.SNICallback; + this[kPskCallback] = options.pskCallback; + this[kPskIdentityHint] = options.pskIdentityHint; if (typeof this[kHandshakeTimeout] !== 'number') { throw new ERR_INVALID_ARG_TYPE( @@ -1076,6 +1172,18 @@ function Server(options, listener) { 'options.SNICallback', 'function', options.SNICallback); } + if (this[kPskCallback] && typeof this[kPskCallback] !== 'function') { + throw new ERR_INVALID_ARG_TYPE( + 'options.pskCallback', 'function', options.pskCallback); + } + if (this[kPskIdentityHint] && typeof this[kPskIdentityHint] !== 'string') { + throw new ERR_INVALID_ARG_TYPE( + 'options.pskIdentityHint', + 'string', + options.pskIdentityHint + ); + } + // constructor call net.Server.call(this, options, tlsConnectionListener); @@ -1272,6 +1380,8 @@ Server.prototype.setOptions = deprecate(function(options) { .digest('hex') .slice(0, 32); } + if (options.pskCallback) this[kPskCallback] = options.pskCallback; + if (options.pskIdentityHint) this[kPskIdentityHint] = options.pskIdentityHint; }, 'Server.prototype.setOptions() is deprecated', 'DEP0122'); // SNI Contexts High-Level API @@ -1459,7 +1569,8 @@ exports.connect = function connect(...args) { session: options.session, ALPNProtocols: options.ALPNProtocols, requestOCSP: options.requestOCSP, - enableTrace: options.enableTrace + enableTrace: options.enableTrace, + pskCallback: options.pskCallback, }); tlssock[kConnectOptions] = options; |