lemon-tls 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +168 -41
- package/index.cjs +9 -7
- package/index.d.ts +626 -30
- package/package.json +45 -7
- package/src/ca_store.js +31 -0
- package/src/compat.js +76 -116
- package/src/crypto.js +21 -114
- package/src/dtls_session.js +1284 -390
- package/src/dtls_socket.js +318 -96
- package/src/node_options.js +154 -17
- package/src/record.js +355 -191
- package/src/secure_context.js +97 -39
- package/src/session/ecdh.js +140 -122
- package/src/session/ech.js +92 -32
- package/src/session/hpke.js +12 -44
- package/src/session/message.js +29 -9
- package/src/session/signing.js +80 -22
- package/src/session/ticket.js +5 -3
- package/src/session/x509.js +385 -0
- package/src/tls_session.js +2221 -902
- package/src/tls_socket.js +451 -452
- package/src/wire.js +271 -158
- package/types/crypto.d.ts +40 -0
- package/types/record.d.ts +40 -0
- package/types/session.d.ts +3 -0
- package/types/wire.d.ts +62 -0
package/src/node_options.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// groups, signatureAlgorithms). compat.js promised Node's API and never read
|
|
8
8
|
// the three — a server given `ciphers` or `ecdhCurve` ran with the defaults
|
|
9
9
|
// and said nothing. These turn them into the lists, once, where an option is
|
|
10
|
-
// taken (compat.js, and
|
|
10
|
+
// taken (compat.js, and a layer above for its own transports), and refuse what cannot
|
|
11
11
|
// be honoured, as Node does ("no cipher match", "Failed to set ECDH curve").
|
|
12
12
|
//
|
|
13
13
|
// What is understood:
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
// ed25519, …) or OpenSSL's SIG+HASH form (ECDSA+SHA256,
|
|
27
27
|
// RSA-PSS+SHA256, RSA+SHA256).
|
|
28
28
|
|
|
29
|
-
import { TLS_CIPHER_SUITES, DEFAULT_CIPHER_SUITES_TLS13, DEFAULT_CIPHER_SUITES_TLS12 } from './crypto.js';
|
|
30
|
-
import { SERVER_DEFAULT_GROUPS } from './session/ecdh.js';
|
|
29
|
+
import { TLS_CIPHER_SUITES, DEFAULT_CIPHER_SUITES_TLS13, DEFAULT_CIPHER_SUITES_TLS12, is_usable_cipher_suite, default_cipher_suites } from './crypto.js';
|
|
30
|
+
import { SERVER_DEFAULT_GROUPS, GROUP_NAMES } from './session/ecdh.js';
|
|
31
31
|
import { MLDSA_AVAILABLE } from './session/signing.js';
|
|
32
32
|
|
|
33
33
|
function splitList(s) {
|
|
@@ -69,14 +69,56 @@ function parseCiphers(str) {
|
|
|
69
69
|
return out;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
72
|
+
/**
|
|
73
|
+
* A list of cipher-suite code points (allowedCipherSuites; DTLS cipherSuites)
|
|
74
|
+
* → the suites of it this library carries, in the order given; null when not
|
|
75
|
+
* given. Throws when none is, as `ciphers` does ("no cipher match"): a list of
|
|
76
|
+
* only suites it does not carry (RSA key transport, DHE, CBC — removed) left
|
|
77
|
+
* the server with nothing to choose and no alert to send, and the handshake
|
|
78
|
+
* stalled until the client gave up.
|
|
79
|
+
*/
|
|
80
|
+
function checkCipherSuites(list, name) {
|
|
81
|
+
if (list === undefined || list === null) return null;
|
|
82
|
+
if (!Array.isArray(list)) throw new TypeError(name + ' must be an array of cipher-suite code points');
|
|
83
|
+
var out = [];
|
|
84
|
+
for (var i = 0; i < list.length; i++) {
|
|
85
|
+
var c = list[i];
|
|
86
|
+
if (Number.isInteger(c) && is_usable_cipher_suite(c) && out.indexOf(c) < 0) out.push(c);
|
|
87
|
+
}
|
|
88
|
+
if (out.length === 0) {
|
|
89
|
+
throw new Error(name + ': no cipher match in [' + list.map(function (c) { return '0x' + (Number(c) >>> 0).toString(16).padStart(4, '0'); }).join(', ') +
|
|
90
|
+
'] — this library carries TLS 1.3 suites and TLS 1.2 ECDHE with AES-GCM or ChaCha20-Poly1305');
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* signatureAlgorithms (code points) → the list, or null when not given.
|
|
97
|
+
* SHA-1 is neither offered nor used (RFC 9155; session/signing.js), so a list
|
|
98
|
+
* naming it is refused where it is given — as `sigalgs` refuses a name it
|
|
99
|
+
* does not know — rather than offered and then never used.
|
|
100
|
+
*/
|
|
101
|
+
function checkSignatureAlgorithms(list) {
|
|
102
|
+
if (list === undefined || list === null) return null;
|
|
103
|
+
if (!Array.isArray(list)) throw new TypeError('signatureAlgorithms must be an array of signature scheme code points');
|
|
104
|
+
for (var i = 0; i < list.length; i++) {
|
|
105
|
+
if (list[i] === 0x0201 || list[i] === 0x0203) {
|
|
106
|
+
throw new Error('signatureAlgorithms: 0x0' + list[i].toString(16) + ' (' + (list[i] === 0x0201 ? 'rsa_pkcs1_sha1' : 'ecdsa_sha1') +
|
|
107
|
+
') is not supported: SHA-1 handshake signatures are refused (RFC 9155)');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return list;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// The groups by name: the registry's (session/ecdh.js, GROUP_NAMES) and
|
|
114
|
+
// OpenSSL's curve names for the NIST ones.
|
|
115
|
+
var GROUPS = (function () {
|
|
116
|
+
var out = {};
|
|
117
|
+
for (var code in GROUP_NAMES) out[GROUP_NAMES[code].toUpperCase()] = Number(code);
|
|
118
|
+
var aliases = { 'PRIME256V1': 0x0017, 'SECP256R1': 0x0017, 'SECP384R1': 0x0018, 'SECP521R1': 0x0019 };
|
|
119
|
+
for (var a in aliases) out[a] = aliases[a];
|
|
120
|
+
return out;
|
|
121
|
+
})();
|
|
80
122
|
|
|
81
123
|
/** `ecdhCurve` → [group, …]; 'auto' → null, the role's own default list
|
|
82
124
|
* (a server's has the hybrid first, a client's does not offer it unasked).
|
|
@@ -127,12 +169,107 @@ function parseSigalgs(str) {
|
|
|
127
169
|
return out;
|
|
128
170
|
}
|
|
129
171
|
|
|
130
|
-
|
|
131
|
-
|
|
172
|
+
// ---- versions ----
|
|
173
|
+
//
|
|
174
|
+
// minVersion / maxVersion → the versions offered (or taken), newest first:
|
|
175
|
+
// one reading for TLSSocket (client and server) and DTLSSession, which each
|
|
176
|
+
// had their own — a lenient parser on one side, inline string compares on
|
|
177
|
+
// the other that took an unknown name silently. Names as node:tls spells
|
|
178
|
+
// them (TLSv1.2, TLSv1.3) and their DTLS twins (DTLSv1.2, DTLSv1.3; a TLS
|
|
179
|
+
// name is taken for its DTLS twin), or a version's code point. A minimum
|
|
180
|
+
// below 1.2 (node:tls's 'TLSv1', 'TLSv1.1') starts at 1.2, the oldest here;
|
|
181
|
+
// anything else unknown is an error, as node:tls throws for it.
|
|
182
|
+
var VERSION_RANKS = { 'TLSV1': 1, 'TLSV1.1': 2, 'TLSV1.2': 3, 'TLSV1.3': 4, 'DTLSV1': 2, 'DTLSV1.2': 3, 'DTLSV1.3': 4 };
|
|
183
|
+
function versionRank(v, name) {
|
|
132
184
|
if (v === undefined || v === null) return null;
|
|
133
|
-
if (v === '
|
|
134
|
-
|
|
135
|
-
|
|
185
|
+
if (typeof v === 'number') {
|
|
186
|
+
if (v >= 0x0301 && v <= 0x0304) return v - 0x0300;
|
|
187
|
+
if (v === 0xfeff) return 2;
|
|
188
|
+
if (v === 0xfefd) return 3;
|
|
189
|
+
if (v === 0xfefc) return 4;
|
|
190
|
+
} else if (typeof v === 'string' && VERSION_RANKS[v.toUpperCase()] !== undefined) {
|
|
191
|
+
return VERSION_RANKS[v.toUpperCase()];
|
|
192
|
+
}
|
|
193
|
+
throw new Error(name + ': "' + v + '" is not a TLS version (TLSv1.2, TLSv1.3, DTLSv1.2, DTLSv1.3)');
|
|
194
|
+
}
|
|
195
|
+
/** [code, …] of (D)TLS 1.3 and 1.2 within [min, max], newest first; throws when none is. */
|
|
196
|
+
function versionsInRange(min, max, dtls) {
|
|
197
|
+
var lo = Math.max(3, versionRank(min, 'minVersion') || 3), hi = versionRank(max, 'maxVersion') || 4;
|
|
198
|
+
var out = [];
|
|
199
|
+
if (lo <= 4 && hi >= 4) out.push(dtls ? 0xfefc : 0x0304);
|
|
200
|
+
if (lo <= 3 && hi >= 3) out.push(dtls ? 0xfefd : 0x0303);
|
|
201
|
+
if (out.length === 0) throw new Error('minVersion ' + min + ', maxVersion ' + max + ': no version this library has (1.2, 1.3) in between');
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
/** 'TLSv1.2' / 'TLSv1.3' → 0x0303 / 0x0304 (null when not given). */
|
|
205
|
+
function parseTlsVersion(v, name) {
|
|
206
|
+
var r = versionRank(v, name || 'version');
|
|
207
|
+
if (r === null) return null;
|
|
208
|
+
if (r < 3) throw new Error((name || 'version') + ': "' + v + '" is not supported (TLSv1.2 or TLSv1.3)');
|
|
209
|
+
return 0x0300 + r;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ---- the lists a session negotiates with, from the options ----
|
|
213
|
+
//
|
|
214
|
+
// One function for TLS over TCP (TLSSocket, both roles) and DTLS
|
|
215
|
+
// (DTLSSession), which each built them — and differently: a DTLS server
|
|
216
|
+
// took no signatureAlgorithms, no prioritizeChaCha, no allowedCipherSuites.
|
|
217
|
+
// Only what the options say: what they leave out, TLSSession fills by its
|
|
218
|
+
// role (fill_default_lists). The names are TLSSocket's; DTLS's older ones —
|
|
219
|
+
// cipherSuites, alpnProtocols — taken as the same.
|
|
220
|
+
// The suites to negotiate with, in preference order: those of `allowed`
|
|
221
|
+
// (allowedCipherSuites, cipherSuites, or `ciphers` parsed) that the versions
|
|
222
|
+
// in range can use, IN THE ORDER GIVEN — the order is the preference, as
|
|
223
|
+
// node:tls takes `ciphers`; or, without a list, `defaults`. Then
|
|
224
|
+
// prioritizeChaCha moves the ChaCha20-Poly1305 suites to the front.
|
|
225
|
+
//
|
|
226
|
+
// The list only filtered the defaults, and kept their order: a server could
|
|
227
|
+
// not prefer AES-256 to AES-128 (honorCipherOrder took our order, and ours
|
|
228
|
+
// was always the default one — BoGo TLS13-Server-StrictPreference), and a
|
|
229
|
+
// client offered its suites in the default order, whatever it listed.
|
|
230
|
+
function chooseCipherSuites(defaults, prioritizeChaCha, allowed) {
|
|
231
|
+
var ciphers = allowed ? allowed.filter(function (c) { return defaults.indexOf(c) >= 0; }) : defaults.slice();
|
|
232
|
+
if (prioritizeChaCha) {
|
|
233
|
+
var isChaCha = function (c) { return !!TLS_CIPHER_SUITES[c] && TLS_CIPHER_SUITES[c].cipher === 'CHACHA20_POLY1305'; };
|
|
234
|
+
ciphers = ciphers.filter(isChaCha).concat(ciphers.filter(function (c) { return !isChaCha(c); }));
|
|
235
|
+
}
|
|
236
|
+
return ciphers;
|
|
237
|
+
}
|
|
238
|
+
function localLists(options, versions) {
|
|
239
|
+
var out = { local_supported_versions: versions };
|
|
240
|
+
var allowed = checkCipherSuites(options.allowedCipherSuites != null ? options.allowedCipherSuites : options.cipherSuites,
|
|
241
|
+
options.allowedCipherSuites != null ? 'allowedCipherSuites' : 'cipherSuites');
|
|
242
|
+
if (allowed || options.prioritizeChaCha) {
|
|
243
|
+
var has13 = versions.some(function (v) { return v === 0x0304 || v === 0xfefc; });
|
|
244
|
+
var has12 = versions.some(function (v) { return v === 0x0303 || v === 0xfefd; });
|
|
245
|
+
out.local_supported_cipher_suites = chooseCipherSuites(default_cipher_suites(has13, has12), options.prioritizeChaCha, allowed);
|
|
246
|
+
// Narrowed to nothing for these versions (1.3 suites only, a 1.2-only
|
|
247
|
+
// profile): as the list with nothing in it at all.
|
|
248
|
+
if (out.local_supported_cipher_suites.length === 0) throw new Error('allowedCipherSuites: no cipher match for the versions in range');
|
|
249
|
+
}
|
|
250
|
+
if (options.groups) out.local_supported_groups = options.groups.slice();
|
|
251
|
+
if (options.signatureAlgorithms) out.local_supported_signature_algorithms = checkSignatureAlgorithms(options.signatureAlgorithms).slice();
|
|
252
|
+
var alpn = options.ALPNProtocols || options.alpnProtocols;
|
|
253
|
+
if (Array.isArray(alpn) && alpn.length > 0) {
|
|
254
|
+
out.local_supported_alpns = alpn.map(function (p) { return Buffer.isBuffer(p) ? p.toString('latin1') : String(p); });
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// External PSK at the socket layers (TLSSocket, DTLSSocket), as node:tls
|
|
260
|
+
// takes it, to what the session takes (TLSSession, DTLSSession). Client:
|
|
261
|
+
// pskCallback(hint) → { psk, identity } or null, asked once — TLS 1.3 has no
|
|
262
|
+
// identity hint, so hint is null, as in Node — or the psk option itself.
|
|
263
|
+
// Server: pskCallback(socket, identity) → the key (or { psk, hash }), or
|
|
264
|
+
// null; the identity as a string, as Node gives it. The sessions take the
|
|
265
|
+
// identity as bytes: pskCallback(identity).
|
|
266
|
+
function pskOptions(options, socket) {
|
|
267
|
+
if (typeof options.pskCallback !== 'function') return { psk: options.isServer ? null : (options.psk || null), pskCallback: null };
|
|
268
|
+
if (options.isServer) {
|
|
269
|
+
return { psk: null, pskCallback: function (identity) { return options.pskCallback(socket, Buffer.from(identity).toString('utf8')); } };
|
|
270
|
+
}
|
|
271
|
+
var r = options.pskCallback(null);
|
|
272
|
+
return { psk: r ? { identity: r.identity, psk: r.psk, hash: r.hash } : null, pskCallback: null };
|
|
136
273
|
}
|
|
137
274
|
|
|
138
|
-
export { parseCiphers, parseEcdhCurve, parseSigalgs, parseTlsVersion };
|
|
275
|
+
export { parseCiphers, checkCipherSuites, checkSignatureAlgorithms, parseEcdhCurve, parseSigalgs, parseTlsVersion, versionsInRange, localLists, pskOptions };
|