@@ -43,10 +43,12 @@ const { TCP, constants: TCPConstants } = internalBinding('tcp_wrap'); |
43 | 43 | const tls_wrap = internalBinding('tls_wrap'); |
44 | 44 | const { Pipe, constants: PipeConstants } = internalBinding('pipe_wrap'); |
45 | 45 | const { owner_symbol } = require('internal/async_hooks').symbols; |
| 46 | +const { isArrayBufferView } = require('internal/util/types'); |
46 | 47 | const { SecureContext: NativeSecureContext } = internalBinding('crypto'); |
47 | 48 | const { connResetException, codes } = require('internal/errors'); |
48 | 49 | const { |
49 | 50 | ERR_INVALID_ARG_TYPE, |
| 51 | + ERR_INVALID_ARG_VALUE, |
50 | 52 | ERR_INVALID_CALLBACK, |
51 | 53 | ERR_MULTIPLE_CALLBACK, |
52 | 54 | ERR_SOCKET_CLOSED, |
@@ -58,8 +60,9 @@ const { |
58 | 60 | ERR_TLS_SESSION_ATTACK, |
59 | 61 | ERR_TLS_SNI_FROM_SERVER |
60 | 62 | } = codes; |
| 63 | +const { onpskexchange: kOnPskExchange } = internalBinding('symbols'); |
61 | 64 | const { getOptionValue } = require('internal/options'); |
62 | | -const { validateString } = require('internal/validators'); |
| 65 | +const { validateString, validateBuffer } = require('internal/validators'); |
63 | 66 | const traceTls = getOptionValue('--trace-tls'); |
64 | 67 | const tlsKeylog = getOptionValue('--tls-keylog'); |
65 | 68 | const { appendFile } = require('fs'); |
@@ -70,6 +73,8 @@ const kHandshakeTimeout = Symbol('handshake-timeout'); |
70 | 73 | const kRes = Symbol('res'); |
71 | 74 | const kSNICallback = Symbol('snicallback'); |
72 | 75 | const kEnableTrace = Symbol('enableTrace'); |
| 76 | +const kPskCallback = Symbol('pskcallback'); |
| 77 | +const kPskIdentityHint = Symbol('pskidentityhint'); |
73 | 78 |
|
74 | 79 | const noop = () => {}; |
75 | 80 |
|
@@ -289,6 +294,67 @@ function onnewsession(sessionId, session) { |
289 | 294 | done(); |
290 | 295 | } |
291 | 296 |
|
| 297 | +function onPskServerCallback(identity, maxPskLen) { |
| 298 | + const owner = this[owner_symbol]; |
| 299 | + const ret = owner[kPskCallback](owner, identity); |
| 300 | + if (ret == null) |
| 301 | + return undefined; |
| 302 | + |
| 303 | + let psk; |
| 304 | + if (isArrayBufferView(ret)) { |
| 305 | + psk = ret; |
| 306 | + } else { |
| 307 | + if (typeof ret !== 'object') { |
| 308 | + throw new ERR_INVALID_ARG_TYPE( |
| 309 | + 'ret', |
| 310 | + ['Object', 'Buffer', 'TypedArray', 'DataView'], |
| 311 | + ret |
| 312 | + ); |
| 313 | + } |
| 314 | + psk = ret.psk; |
| 315 | + validateBuffer(psk, 'psk'); |
| 316 | + } |
| 317 | + |
| 318 | + if (psk.length > maxPskLen) { |
| 319 | + throw new ERR_INVALID_ARG_VALUE( |
| 320 | + 'psk', |
| 321 | + psk, |
| 322 | + `Pre-shared key exceeds ${maxPskLen} bytes` |
| 323 | + ); |
| 324 | + } |
| 325 | + |
| 326 | + return psk; |
| 327 | +} |
| 328 | + |
| 329 | +function onPskClientCallback(hint, maxPskLen, maxIdentityLen) { |
| 330 | + const owner = this[owner_symbol]; |
| 331 | + const ret = owner[kPskCallback](hint); |
| 332 | + if (ret == null) |
| 333 | + return undefined; |
| 334 | + |
| 335 | + if (typeof ret !== 'object') |
| 336 | + throw new ERR_INVALID_ARG_TYPE('ret', 'Object', ret); |
| 337 | + |
| 338 | + validateBuffer(ret.psk, 'psk'); |
| 339 | + if (ret.psk.length > maxPskLen) { |
| 340 | + throw new ERR_INVALID_ARG_VALUE( |
| 341 | + 'psk', |
| 342 | + ret.psk, |
| 343 | + `Pre-shared key exceeds ${maxPskLen} bytes` |
| 344 | + ); |
| 345 | + } |
| 346 | + |
| 347 | + validateString(ret.identity, 'identity'); |
| 348 | + if (Buffer.byteLength(ret.identity) > maxIdentityLen) { |
| 349 | + throw new ERR_INVALID_ARG_VALUE( |
| 350 | + 'identity', |
| 351 | + ret.identity, |
| 352 | + `PSK identity exceeds ${maxIdentityLen} bytes` |
| 353 | + ); |
| 354 | + } |
| 355 | + |
| 356 | + return { psk: ret.psk, identity: ret.identity }; |
| 357 | +} |
292 | 358 |
|
293 | 359 | function onkeylogclient(line) { |
294 | 360 | debug('client onkeylog'); |
@@ -687,6 +753,32 @@ TLSSocket.prototype._init = function(socket, wrap) { |
687 | 753 | ssl.setALPNProtocols(ssl._secureContext.alpnBuffer); |
688 | 754 | } |
689 | 755 |
|
| 756 | + if (options.pskCallback && ssl.enablePskCallback) { |
| 757 | + if (typeof options.pskCallback !== 'function') { |
| 758 | + throw new ERR_INVALID_ARG_TYPE('pskCallback', |
| 759 | + 'function', |
| 760 | + options.pskCallback); |
| 761 | + } |
| 762 | + |
| 763 | + ssl[kOnPskExchange] = options.isServer ? |
| 764 | + onPskServerCallback : onPskClientCallback; |
| 765 | + |
| 766 | + this[kPskCallback] = options.pskCallback; |
| 767 | + ssl.enablePskCallback(); |
| 768 | + |
| 769 | + if (options.pskIdentityHint) { |
| 770 | + if (typeof options.pskIdentityHint !== 'string') { |
| 771 | + throw new ERR_INVALID_ARG_TYPE( |
| 772 | + 'options.pskIdentityHint', |
| 773 | + 'string', |
| 774 | + options.pskIdentityHint |
| 775 | + ); |
| 776 | + } |
| 777 | + ssl.setPskIdentityHint(options.pskIdentityHint); |
| 778 | + } |
| 779 | + } |
| 780 | + |
| 781 | + |
690 | 782 | if (options.handshakeTimeout > 0) |
691 | 783 | this.setTimeout(options.handshakeTimeout, this._handleTimeout); |
692 | 784 |
|
@@ -898,7 +990,7 @@ function makeSocketMethodProxy(name) { |
898 | 990 | TLSSocket.prototype[method] = makeSocketMethodProxy(method); |
899 | 991 | }); |
900 | 992 |
|
901 | | -// TODO: support anonymous (nocert) and PSK |
| 993 | +// TODO: support anonymous (nocert) |
902 | 994 |
|
903 | 995 |
|
904 | 996 | function onServerSocketSecure() { |
@@ -954,6 +1046,8 @@ function tlsConnectionListener(rawSocket) { |
954 | 1046 | SNICallback: this[kSNICallback] || SNICallback, |
955 | 1047 | enableTrace: this[kEnableTrace], |
956 | 1048 | pauseOnConnect: this.pauseOnConnect, |
| 1049 | + pskCallback: this[kPskCallback], |
| 1050 | + pskIdentityHint: this[kPskIdentityHint], |
957 | 1051 | }); |
958 | 1052 |
|
959 | 1053 | socket.on('secure', onServerSocketSecure); |
@@ -1058,6 +1152,8 @@ function Server(options, listener) { |
1058 | 1152 |
|
1059 | 1153 | this[kHandshakeTimeout] = options.handshakeTimeout || (120 * 1000); |
1060 | 1154 | this[kSNICallback] = options.SNICallback; |
| 1155 | + this[kPskCallback] = options.pskCallback; |
| 1156 | + this[kPskIdentityHint] = options.pskIdentityHint; |
1061 | 1157 |
|
1062 | 1158 | if (typeof this[kHandshakeTimeout] !== 'number') { |
1063 | 1159 | throw new ERR_INVALID_ARG_TYPE( |
@@ -1069,6 +1165,18 @@ function Server(options, listener) { |
1069 | 1165 | 'options.SNICallback', 'function', options.SNICallback); |
1070 | 1166 | } |
1071 | 1167 |
|
| 1168 | + if (this[kPskCallback] && typeof this[kPskCallback] !== 'function') { |
| 1169 | + throw new ERR_INVALID_ARG_TYPE( |
| 1170 | + 'options.pskCallback', 'function', options.pskCallback); |
| 1171 | + } |
| 1172 | + if (this[kPskIdentityHint] && typeof this[kPskIdentityHint] !== 'string') { |
| 1173 | + throw new ERR_INVALID_ARG_TYPE( |
| 1174 | + 'options.pskIdentityHint', |
| 1175 | + 'string', |
| 1176 | + options.pskIdentityHint |
| 1177 | + ); |
| 1178 | + } |
| 1179 | + |
1072 | 1180 | // constructor call |
1073 | 1181 | net.Server.call(this, options, tlsConnectionListener); |
1074 | 1182 |
|
@@ -1265,6 +1373,8 @@ Server.prototype.setOptions = deprecate(function(options) { |
1265 | 1373 | .digest('hex') |
1266 | 1374 | .slice(0, 32); |
1267 | 1375 | } |
| 1376 | + if (options.pskCallback) this[kPskCallback] = options.pskCallback; |
| 1377 | + if (options.pskIdentityHint) this[kPskIdentityHint] = options.pskIdentityHint; |
1268 | 1378 | }, 'Server.prototype.setOptions() is deprecated', 'DEP0122'); |
1269 | 1379 |
|
1270 | 1380 | // SNI Contexts High-Level API |
@@ -1440,7 +1550,8 @@ exports.connect = function connect(...args) { |
1440 | 1550 | session: options.session, |
1441 | 1551 | ALPNProtocols: options.ALPNProtocols, |
1442 | 1552 | requestOCSP: options.requestOCSP, |
1443 | | - enableTrace: options.enableTrace |
| 1553 | + enableTrace: options.enableTrace, |
| 1554 | + pskCallback: options.pskCallback, |
1444 | 1555 | }); |
1445 | 1556 |
|
1446 | 1557 | tlssock[kConnectOptions] = options; |
|