Skip to content

Commit bfccc00

Browse 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 <[email protected]> Co-authored-by: stephank <[email protected]> Co-authored-by: Taylor Zane Glaeser <[email protected]> PR-URL: #23188 Reviewed-By: Sam Roberts <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 1f0a1d5 commit bfccc00

12 files changed

+646
-9
lines changed

doc/api/errors.md

+5
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

doc/api/tls.md

+79-1
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

lib/_tls_wrap.js

+114-3
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;

src/env.h

+8-5
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") \

src/node_crypto.cc

+10
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)

src/node_errors.h

+2
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)