Skip to content

Commit f8d7e22

Browse files
lundibundiBridgeAR
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 3d47c85 commit f8d7e22

12 files changed

+646
-9
lines changed

doc/api/errors.md

+5
Original file line numberDiff line numberDiff line change
@@ -1857,6 +1857,11 @@ vector for denial-of-service attacks.
18571857
An attempt was made to issue Server Name Indication from a TLS server-side
18581858
socket, which is only valid from a client.
18591859

1860+
<a id="ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED"></a>
1861+
### ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED
1862+
1863+
Failed to set PSK identity hint. Hint may be too long.
1864+
18601865
<a id="ERR_TRACE_EVENTS_CATEGORY_REQUIRED"></a>
18611866
### ERR_TRACE_EVENTS_CATEGORY_REQUIRED
18621867

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
@@ -50,10 +50,12 @@ const { TCP, constants: TCPConstants } = internalBinding('tcp_wrap');
5050
const tls_wrap = internalBinding('tls_wrap');
5151
const { Pipe, constants: PipeConstants } = internalBinding('pipe_wrap');
5252
const { owner_symbol } = require('internal/async_hooks').symbols;
53+
const { isArrayBufferView } = require('internal/util/types');
5354
const { SecureContext: NativeSecureContext } = internalBinding('crypto');
5455
const { connResetException, codes } = require('internal/errors');
5556
const {
5657
ERR_INVALID_ARG_TYPE,
58+
ERR_INVALID_ARG_VALUE,
5759
ERR_INVALID_CALLBACK,
5860
ERR_MULTIPLE_CALLBACK,
5961
ERR_SOCKET_CLOSED,
@@ -65,8 +67,9 @@ const {
6567
ERR_TLS_SESSION_ATTACK,
6668
ERR_TLS_SNI_FROM_SERVER
6769
} = codes;
70+
const { onpskexchange: kOnPskExchange } = internalBinding('symbols');
6871
const { getOptionValue } = require('internal/options');
69-
const { validateString } = require('internal/validators');
72+
const { validateString, validateBuffer } = require('internal/validators');
7073
const traceTls = getOptionValue('--trace-tls');
7174
const tlsKeylog = getOptionValue('--tls-keylog');
7275
const { appendFile } = require('fs');
@@ -77,6 +80,8 @@ const kHandshakeTimeout = Symbol('handshake-timeout');
7780
const kRes = Symbol('res');
7881
const kSNICallback = Symbol('snicallback');
7982
const kEnableTrace = Symbol('enableTrace');
83+
const kPskCallback = Symbol('pskcallback');
84+
const kPskIdentityHint = Symbol('pskidentityhint');
8085

8186
const noop = () => {};
8287

@@ -296,6 +301,67 @@ function onnewsession(sessionId, session) {
296301
done();
297302
}
298303

304+
function onPskServerCallback(identity, maxPskLen) {
305+
const owner = this[owner_symbol];
306+
const ret = owner[kPskCallback](owner, identity);
307+
if (ret == null)
308+
return undefined;
309+
310+
let psk;
311+
if (isArrayBufferView(ret)) {
312+
psk = ret;
313+
} else {
314+
if (typeof ret !== 'object') {
315+
throw new ERR_INVALID_ARG_TYPE(
316+
'ret',
317+
['Object', 'Buffer', 'TypedArray', 'DataView'],
318+
ret
319+
);
320+
}
321+
psk = ret.psk;
322+
validateBuffer(psk, 'psk');
323+
}
324+
325+
if (psk.length > maxPskLen) {
326+
throw new ERR_INVALID_ARG_VALUE(
327+
'psk',
328+
psk,
329+
`Pre-shared key exceeds ${maxPskLen} bytes`
330+
);
331+
}
332+
333+
return psk;
334+
}
335+
336+
function onPskClientCallback(hint, maxPskLen, maxIdentityLen) {
337+
const owner = this[owner_symbol];
338+
const ret = owner[kPskCallback](hint);
339+
if (ret == null)
340+
return undefined;
341+
342+
if (typeof ret !== 'object')
343+
throw new ERR_INVALID_ARG_TYPE('ret', 'Object', ret);
344+
345+
validateBuffer(ret.psk, 'psk');
346+
if (ret.psk.length > maxPskLen) {
347+
throw new ERR_INVALID_ARG_VALUE(
348+
'psk',
349+
ret.psk,
350+
`Pre-shared key exceeds ${maxPskLen} bytes`
351+
);
352+
}
353+
354+
validateString(ret.identity, 'identity');
355+
if (Buffer.byteLength(ret.identity) > maxIdentityLen) {
356+
throw new ERR_INVALID_ARG_VALUE(
357+
'identity',
358+
ret.identity,
359+
`PSK identity exceeds ${maxIdentityLen} bytes`
360+
);
361+
}
362+
363+
return { psk: ret.psk, identity: ret.identity };
364+
}
299365

300366
function onkeylogclient(line) {
301367
debug('client onkeylog');
@@ -694,6 +760,32 @@ TLSSocket.prototype._init = function(socket, wrap) {
694760
ssl.setALPNProtocols(ssl._secureContext.alpnBuffer);
695761
}
696762

763+
if (options.pskCallback && ssl.enablePskCallback) {
764+
if (typeof options.pskCallback !== 'function') {
765+
throw new ERR_INVALID_ARG_TYPE('pskCallback',
766+
'function',
767+
options.pskCallback);
768+
}
769+
770+
ssl[kOnPskExchange] = options.isServer ?
771+
onPskServerCallback : onPskClientCallback;
772+
773+
this[kPskCallback] = options.pskCallback;
774+
ssl.enablePskCallback();
775+
776+
if (options.pskIdentityHint) {
777+
if (typeof options.pskIdentityHint !== 'string') {
778+
throw new ERR_INVALID_ARG_TYPE(
779+
'options.pskIdentityHint',
780+
'string',
781+
options.pskIdentityHint
782+
);
783+
}
784+
ssl.setPskIdentityHint(options.pskIdentityHint);
785+
}
786+
}
787+
788+
697789
if (options.handshakeTimeout > 0)
698790
this.setTimeout(options.handshakeTimeout, this._handleTimeout);
699791

@@ -905,7 +997,7 @@ function makeSocketMethodProxy(name) {
905997
TLSSocket.prototype[method] = makeSocketMethodProxy(method);
906998
});
907999

908-
// TODO: support anonymous (nocert) and PSK
1000+
// TODO: support anonymous (nocert)
9091001

9101002

9111003
function onServerSocketSecure() {
@@ -961,6 +1053,8 @@ function tlsConnectionListener(rawSocket) {
9611053
SNICallback: this[kSNICallback] || SNICallback,
9621054
enableTrace: this[kEnableTrace],
9631055
pauseOnConnect: this.pauseOnConnect,
1056+
pskCallback: this[kPskCallback],
1057+
pskIdentityHint: this[kPskIdentityHint],
9641058
});
9651059

9661060
socket.on('secure', onServerSocketSecure);
@@ -1065,6 +1159,8 @@ function Server(options, listener) {
10651159

10661160
this[kHandshakeTimeout] = options.handshakeTimeout || (120 * 1000);
10671161
this[kSNICallback] = options.SNICallback;
1162+
this[kPskCallback] = options.pskCallback;
1163+
this[kPskIdentityHint] = options.pskIdentityHint;
10681164

10691165
if (typeof this[kHandshakeTimeout] !== 'number') {
10701166
throw new ERR_INVALID_ARG_TYPE(
@@ -1076,6 +1172,18 @@ function Server(options, listener) {
10761172
'options.SNICallback', 'function', options.SNICallback);
10771173
}
10781174

1175+
if (this[kPskCallback] && typeof this[kPskCallback] !== 'function') {
1176+
throw new ERR_INVALID_ARG_TYPE(
1177+
'options.pskCallback', 'function', options.pskCallback);
1178+
}
1179+
if (this[kPskIdentityHint] && typeof this[kPskIdentityHint] !== 'string') {
1180+
throw new ERR_INVALID_ARG_TYPE(
1181+
'options.pskIdentityHint',
1182+
'string',
1183+
options.pskIdentityHint
1184+
);
1185+
}
1186+
10791187
// constructor call
10801188
net.Server.call(this, options, tlsConnectionListener);
10811189

@@ -1272,6 +1380,8 @@ Server.prototype.setOptions = deprecate(function(options) {
12721380
.digest('hex')
12731381
.slice(0, 32);
12741382
}
1383+
if (options.pskCallback) this[kPskCallback] = options.pskCallback;
1384+
if (options.pskIdentityHint) this[kPskIdentityHint] = options.pskIdentityHint;
12751385
}, 'Server.prototype.setOptions() is deprecated', 'DEP0122');
12761386

12771387
// SNI Contexts High-Level API
@@ -1459,7 +1569,8 @@ exports.connect = function connect(...args) {
14591569
session: options.session,
14601570
ALPNProtocols: options.ALPNProtocols,
14611571
requestOCSP: options.requestOCSP,
1462-
enableTrace: options.enableTrace
1572+
enableTrace: options.enableTrace,
1573+
pskCallback: options.pskCallback,
14631574
});
14641575

14651576
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)