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.
@@ -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 QUICO for both TCP and QUIC), and refuse what cannot
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
- var GROUPS = {
73
- 'X25519': 0x001d,
74
- 'P-256': 0x0017, 'PRIME256V1': 0x0017, 'SECP256R1': 0x0017,
75
- 'P-384': 0x0018, 'SECP384R1': 0x0018,
76
- 'X25519MLKEM768': 0x11ec,
77
- 'SECP256R1MLKEM768': 0x11eb, 'SECP384R1MLKEM1024': 0x11ed,
78
- 'MLKEM512': 0x0200, 'MLKEM768': 0x0201, 'MLKEM1024': 0x0202,
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
- /** 'TLSv1.2' / 'TLSv1.3' → 0x0303 / 0x0304, as node:tls names them. */
131
- function parseTlsVersion(v, name) {
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 === 'TLSv1.3') return 0x0304;
134
- if (v === 'TLSv1.2') return 0x0303;
135
- throw new Error(name + ': "' + v + '" is not supported (TLSv1.2 or TLSv1.3)');
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 };