Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit bfccc00

Browse filesBrowse files
lundibunditargos
authored andcommitted
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: #23188 Reviewed-By: Sam Roberts <vieuxtech@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent 1f0a1d5 commit bfccc00
Copy full SHA for bfccc00

File tree

Expand file treeCollapse file tree

12 files changed

+646
-9
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

12 files changed

+646
-9
lines changed
Open diff view settings
Collapse file

‎doc/api/errors.md‎

Copy file name to clipboardExpand all lines: doc/api/errors.md
+5Lines changed: 5 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1849,6 +1849,11 @@ vector for denial-of-service attacks.
18491849
An attempt was made to issue Server Name Indication from a TLS server-side
18501850
socket, which is only valid from a client.
18511851

1852+
<a id="ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED"></a>
1853+
### ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED
1854+
1855+
Failed to set PSK identity hint. Hint may be too long.
1856+
18521857
<a id="ERR_TRACE_EVENTS_CATEGORY_REQUIRED"></a>
18531858
### `ERR_TRACE_EVENTS_CATEGORY_REQUIRED`
18541859

Collapse file

‎doc/api/tls.md‎

Copy file name to clipboardExpand all lines: doc/api/tls.md
+79-1Lines changed: 79 additions & 1 deletion
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,40 @@ SNI (Server Name Indication) are TLS handshake extensions:
118118
* SNI: Allows the use of one TLS server for multiple hostnames with different
119119
SSL certificates.
120120

121+
### Pre-shared keys
122+
123+
<!-- type=misc -->
124+
125+
TLS-PSK support is available as an alternative to normal certificate-based
126+
authentication. It uses a pre-shared key instead of certificates to
127+
authenticate a TLS connection, providing mutual authentication.
128+
TLS-PSK and public key infrastructure are not mutually exclusive. Clients and
129+
servers can accommodate both, choosing either of them during the normal cipher
130+
negotiation step.
131+
132+
TLS-PSK is only a good choice where means exist to securely share a
133+
key with every connecting machine, so it does not replace PKI
134+
(Public Key Infrastructure) for the majority of TLS uses.
135+
The TLS-PSK implementation in OpenSSL has seen many security flaws in
136+
recent years, mostly because it is used only by a minority of applications.
137+
Please consider all alternative solutions before switching to PSK ciphers.
138+
Upon generating PSK it is of critical importance to use sufficient entropy as
139+
discussed in [RFC 4086][]. Deriving a shared secret from a password or other
140+
low-entropy sources is not secure.
141+
142+
PSK ciphers are disabled by default, and using TLS-PSK thus requires explicitly
143+
specifying a cipher suite with the `ciphers` option. The list of available
144+
ciphers can be retrieved via `openssl ciphers -v 'PSK'`. All TLS 1.3
145+
ciphers are eligible for PSK but currently only those that use SHA256 digest are
146+
supported they can be retrieved via `openssl ciphers -v -s -tls1_3 -psk`.
147+
148+
According to the [RFC 4279][], PSK identities up to 128 bytes in length and
149+
PSKs up to 64 bytes in length must be supported. As of OpenSSL 1.1.0
150+
maximum identity size is 128 bytes, and maximum PSK length is 256 bytes.
151+
152+
The current implementation doesn't support asynchronous PSK callbacks due to the
153+
limitations of the underlying OpenSSL API.
154+
121155
### Client-initiated renegotiation attack mitigation
122156

123157
<!-- type=misc -->
@@ -1207,6 +1241,9 @@ being issued by trusted CA (`options.ca`).
12071241
<!-- YAML
12081242
added: v0.11.3
12091243
changes:
1244+
- version: REPLACEME
1245+
pr-url: https://github.com/nodejs/node/pull/23188
1246+
description: The `pskCallback` option is now supported.
12101247
- version: v12.9.0
12111248
pr-url: https://github.com/nodejs/node/pull/27836
12121249
description: Support the `allowHalfOpen` option.
@@ -1258,6 +1295,23 @@ changes:
12581295
verified against the list of supplied CAs. An `'error'` event is emitted if
12591296
verification fails; `err.code` contains the OpenSSL error code. **Default:**
12601297
`true`.
1298+
* `pskCallback` {Function}
1299+
* hint: {string} optional message sent from the server to help client
1300+
decide which identity to use during negotiation.
1301+
Always `null` if TLS 1.3 is used.
1302+
* Returns: {Object} in the form
1303+
`{ psk: <Buffer|TypedArray|DataView>, identity: <string> }`
1304+
or `null` to stop the negotiation process. `psk` must be
1305+
compatible with the selected cipher's digest.
1306+
`identity` must use UTF-8 encoding.
1307+
When negotiating TLS-PSK (pre-shared keys), this function is called
1308+
with optional identity `hint` provided by the server or `null`
1309+
in case of TLS 1.3 where `hint` was removed.
1310+
It will be necessary to provide a custom `tls.checkServerIdentity()`
1311+
for the connection as the default one will try to check hostname/IP
1312+
of the server against the certificate but that's not applicable for PSK
1313+
because there won't be a certificate present.
1314+
More information can be found in the [RFC 4279][].
12611315
* `ALPNProtocols`: {string[]|Buffer[]|TypedArray[]|DataView[]|Buffer|
12621316
TypedArray|DataView}
12631317
An array of strings, `Buffer`s or `TypedArray`s or `DataView`s, or a
@@ -1593,8 +1647,30 @@ changes:
15931647
provided the default callback with high-level API will be used (see below).
15941648
* `ticketKeys`: {Buffer} 48-bytes of cryptographically strong pseudo-random
15951649
data. See [Session Resumption][] for more information.
1650+
* `pskCallback` {Function}
1651+
* socket: {tls.TLSSocket} the server [`tls.TLSSocket`][] instance for
1652+
this connection.
1653+
* identity: {string} identity parameter sent from the client.
1654+
* Returns: {Buffer|TypedArray|DataView} pre-shared key that must either be
1655+
a buffer or `null` to stop the negotiation process. Returned PSK must be
1656+
compatible with the selected cipher's digest.
1657+
When negotiating TLS-PSK (pre-shared keys), this function is called
1658+
with the identity provided by the client.
1659+
If the return value is `null` the negotiation process will stop and an
1660+
"unknown_psk_identity" alert message will be sent to the other party.
1661+
If the server wishes to hide the fact that the PSK identity was not known,
1662+
the callback must provide some random data as `psk` to make the connection
1663+
fail with "decrypt_error" before negotiation is finished.
1664+
PSK ciphers are disabled by default, and using TLS-PSK thus
1665+
requires explicitly specifying a cipher suite with the `ciphers` option.
1666+
More information can be found in the [RFC 4279][].
1667+
* `pskIdentityHint` {string} optional hint to send to a client to help
1668+
with selecting the identity during TLS-PSK negotiation. Will be ignored
1669+
in TLS 1.3. Upon failing to set pskIdentityHint `'tlsClientError'` will be
1670+
emitted with `'ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED'` code.
15961671
* ...: Any [`tls.createSecureContext()`][] option can be provided. For
1597-
servers, the identity options (`pfx` or `key`/`cert`) are usually required.
1672+
servers, the identity options (`pfx`, `key`/`cert` or `pskCallback`)
1673+
are usually required.
15981674
* ...: Any [`net.createServer()`][] option can be provided.
15991675
* `secureConnectionListener` {Function}
16001676
* Returns: {tls.Server}
@@ -1870,3 +1946,5 @@ where `secureSocket` has the same API as `pair.cleartext`.
18701946
[cipher list format]: https://www.openssl.org/docs/man1.1.1/man1/ciphers.html#CIPHER-LIST-FORMAT
18711947
[modifying the default cipher suite]: #tls_modifying_the_default_tls_cipher_suite
18721948
[specific attacks affecting larger AES key sizes]: https://www.schneier.com/blog/archives/2009/07/another_new_aes.html
1949+
[RFC 4279]: https://tools.ietf.org/html/rfc4279
1950+
[RFC 4086]: https://tools.ietf.org/html/rfc4086
Collapse file

‎lib/_tls_wrap.js‎

Copy file name to clipboardExpand all lines: lib/_tls_wrap.js
+114-3Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@ const { TCP, constants: TCPConstants } = internalBinding('tcp_wrap');
4343
const tls_wrap = internalBinding('tls_wrap');
4444
const { Pipe, constants: PipeConstants } = internalBinding('pipe_wrap');
4545
const { owner_symbol } = require('internal/async_hooks').symbols;
46+
const { isArrayBufferView } = require('internal/util/types');
4647
const { SecureContext: NativeSecureContext } = internalBinding('crypto');
4748
const { connResetException, codes } = require('internal/errors');
4849
const {
4950
ERR_INVALID_ARG_TYPE,
51+
ERR_INVALID_ARG_VALUE,
5052
ERR_INVALID_CALLBACK,
5153
ERR_MULTIPLE_CALLBACK,
5254
ERR_SOCKET_CLOSED,
@@ -58,8 +60,9 @@ const {
5860
ERR_TLS_SESSION_ATTACK,
5961
ERR_TLS_SNI_FROM_SERVER
6062
} = codes;
63+
const { onpskexchange: kOnPskExchange } = internalBinding('symbols');
6164
const { getOptionValue } = require('internal/options');
62-
const { validateString } = require('internal/validators');
65+
const { validateString, validateBuffer } = require('internal/validators');
6366
const traceTls = getOptionValue('--trace-tls');
6467
const tlsKeylog = getOptionValue('--tls-keylog');
6568
const { appendFile } = require('fs');
@@ -70,6 +73,8 @@ const kHandshakeTimeout = Symbol('handshake-timeout');
7073
const kRes = Symbol('res');
7174
const kSNICallback = Symbol('snicallback');
7275
const kEnableTrace = Symbol('enableTrace');
76+
const kPskCallback = Symbol('pskcallback');
77+
const kPskIdentityHint = Symbol('pskidentityhint');
7378

7479
const noop = () => {};
7580

@@ -289,6 +294,67 @@ function onnewsession(sessionId, session) {
289294
done();
290295
}
291296

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+
}
292358

293359
function onkeylogclient(line) {
294360
debug('client onkeylog');
@@ -687,6 +753,32 @@ TLSSocket.prototype._init = function(socket, wrap) {
687753
ssl.setALPNProtocols(ssl._secureContext.alpnBuffer);
688754
}
689755

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+
690782
if (options.handshakeTimeout > 0)
691783
this.setTimeout(options.handshakeTimeout, this._handleTimeout);
692784

@@ -898,7 +990,7 @@ function makeSocketMethodProxy(name) {
898990
TLSSocket.prototype[method] = makeSocketMethodProxy(method);
899991
});
900992

901-
// TODO: support anonymous (nocert) and PSK
993+
// TODO: support anonymous (nocert)
902994

903995

904996
function onServerSocketSecure() {
@@ -954,6 +1046,8 @@ function tlsConnectionListener(rawSocket) {
9541046
SNICallback: this[kSNICallback] || SNICallback,
9551047
enableTrace: this[kEnableTrace],
9561048
pauseOnConnect: this.pauseOnConnect,
1049+
pskCallback: this[kPskCallback],
1050+
pskIdentityHint: this[kPskIdentityHint],
9571051
});
9581052

9591053
socket.on('secure', onServerSocketSecure);
@@ -1058,6 +1152,8 @@ function Server(options, listener) {
10581152

10591153
this[kHandshakeTimeout] = options.handshakeTimeout || (120 * 1000);
10601154
this[kSNICallback] = options.SNICallback;
1155+
this[kPskCallback] = options.pskCallback;
1156+
this[kPskIdentityHint] = options.pskIdentityHint;
10611157

10621158
if (typeof this[kHandshakeTimeout] !== 'number') {
10631159
throw new ERR_INVALID_ARG_TYPE(
@@ -1069,6 +1165,18 @@ function Server(options, listener) {
10691165
'options.SNICallback', 'function', options.SNICallback);
10701166
}
10711167

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+
10721180
// constructor call
10731181
net.Server.call(this, options, tlsConnectionListener);
10741182

@@ -1265,6 +1373,8 @@ Server.prototype.setOptions = deprecate(function(options) {
12651373
.digest('hex')
12661374
.slice(0, 32);
12671375
}
1376+
if (options.pskCallback) this[kPskCallback] = options.pskCallback;
1377+
if (options.pskIdentityHint) this[kPskIdentityHint] = options.pskIdentityHint;
12681378
}, 'Server.prototype.setOptions() is deprecated', 'DEP0122');
12691379

12701380
// SNI Contexts High-Level API
@@ -1440,7 +1550,8 @@ exports.connect = function connect(...args) {
14401550
session: options.session,
14411551
ALPNProtocols: options.ALPNProtocols,
14421552
requestOCSP: options.requestOCSP,
1443-
enableTrace: options.enableTrace
1553+
enableTrace: options.enableTrace,
1554+
pskCallback: options.pskCallback,
14441555
});
14451556

14461557
tlssock[kConnectOptions] = options;
Collapse file

‎src/env.h‎

Copy file name to clipboardExpand all lines: src/env.h
+8-5Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,12 @@ constexpr size_t kFsStatsBufferLength =
160160

161161
// Symbols are per-isolate primitives but Environment proxies them
162162
// for the sake of convenience.
163-
#define PER_ISOLATE_SYMBOL_PROPERTIES(V) \
164-
V(handle_onclose_symbol, "handle_onclose") \
165-
V(no_message_symbol, "no_message_symbol") \
166-
V(oninit_symbol, "oninit") \
167-
V(owner_symbol, "owner") \
163+
#define PER_ISOLATE_SYMBOL_PROPERTIES(V) \
164+
V(handle_onclose_symbol, "handle_onclose") \
165+
V(no_message_symbol, "no_message_symbol") \
166+
V(oninit_symbol, "oninit") \
167+
V(owner_symbol, "owner") \
168+
V(onpskexchange_symbol, "onpskexchange") \
168169

169170
// Strings are per-isolate primitives but Environment proxies them
170171
// for the sake of convenience. Strings should be ASCII-only.
@@ -254,6 +255,7 @@ constexpr size_t kFsStatsBufferLength =
254255
V(host_string, "host") \
255256
V(hostmaster_string, "hostmaster") \
256257
V(http_1_1_string, "http/1.1") \
258+
V(identity_string, "identity") \
257259
V(ignore_string, "ignore") \
258260
V(import_string, "import") \
259261
V(infoaccess_string, "infoAccess") \
@@ -325,6 +327,7 @@ constexpr size_t kFsStatsBufferLength =
325327
V(priority_string, "priority") \
326328
V(process_string, "process") \
327329
V(promise_string, "promise") \
330+
V(psk_string, "psk") \
328331
V(pubkey_string, "pubkey") \
329332
V(query_string, "query") \
330333
V(raw_string, "raw") \
Collapse file

‎src/node_crypto.cc‎

Copy file name to clipboardExpand all lines: src/node_crypto.cc
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2620,6 +2620,16 @@ void SSLWrap<Base>::VerifyError(const FunctionCallbackInfo<Value>& args) {
26202620
if (X509* peer_cert = SSL_get_peer_certificate(w->ssl_.get())) {
26212621
X509_free(peer_cert);
26222622
x509_verify_error = SSL_get_verify_result(w->ssl_.get());
2623+
} else {
2624+
const SSL_CIPHER* curr_cipher = SSL_get_current_cipher(w->ssl_.get());
2625+
const SSL_SESSION* sess = SSL_get_session(w->ssl_.get());
2626+
// Allow no-cert for PSK authentication in TLS1.2 and lower.
2627+
// In TLS1.3 check that session was reused because TLS1.3 PSK
2628+
// looks like session resumption. Is there a better way?
2629+
if (SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk ||
2630+
(SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION &&
2631+
SSL_session_reused(w->ssl_.get())))
2632+
return args.GetReturnValue().SetNull();
26232633
}
26242634

26252635
if (x509_verify_error == X509_V_OK)
Collapse file

‎src/node_errors.h‎

Copy file name to clipboardExpand all lines: src/node_errors.h
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ void PrintErrorString(const char* format, ...);
5858
V(ERR_STRING_TOO_LONG, Error) \
5959
V(ERR_TLS_INVALID_PROTOCOL_METHOD, TypeError) \
6060
V(ERR_TRANSFERRING_EXTERNALIZED_SHAREDARRAYBUFFER, TypeError) \
61+
V(ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED, Error) \
6162

6263
#define V(code, type) \
6364
inline v8::Local<v8::Value> code(v8::Isolate* isolate, \
@@ -101,6 +102,7 @@ void PrintErrorString(const char* format, ...);
101102
"Script execution was interrupted by `SIGINT`") \
102103
V(ERR_TRANSFERRING_EXTERNALIZED_SHAREDARRAYBUFFER, \
103104
"Cannot serialize externalized SharedArrayBuffer") \
105+
V(ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED, "Failed to set PSK identity hint") \
104106

105107
#define V(code, message) \
106108
inline v8::Local<v8::Value> code(v8::Isolate* isolate) { \

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.