lemon-tls 0.6.0 → 0.9.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 +253 -20
- package/index.d.ts +94 -6
- package/index.js +126 -0
- package/package.json +2 -7
- package/src/compat.js +316 -45
- package/src/crypto.js +33 -3
- package/src/dtls_session.js +477 -41
- package/src/dtls_socket.js +32 -0
- package/src/node_options.js +138 -0
- package/src/record.js +210 -27
- package/src/secure_context.js +49 -4
- package/src/session/cert_compression.js +157 -0
- package/src/session/ecdh.js +237 -10
- package/src/session/ech.js +715 -0
- package/src/session/hpke.js +414 -0
- package/src/session/message.js +44 -5
- package/src/session/signing.js +56 -3
- package/src/session/ticket.js +44 -28
- package/src/tls_session.js +3003 -217
- package/src/tls_socket.js +839 -149
- package/src/utils.js +75 -2
- package/src/wire.js +220 -13
package/src/utils.js
CHANGED
|
@@ -54,7 +54,13 @@ function arraysEqual(a, b) {
|
|
|
54
54
|
return false;
|
|
55
55
|
}
|
|
56
56
|
}else{
|
|
57
|
-
|
|
57
|
+
// Either element may be null/undefined here: the branch above requires
|
|
58
|
+
// BOTH to be non-null typed arrays, so a null slips through to this
|
|
59
|
+
// side and the `.constructor` reads below threw a TypeError. Settle
|
|
60
|
+
// those cases by identity before anything is dereferenced.
|
|
61
|
+
if(a[i]==null || b[i]==null){
|
|
62
|
+
if(a[i]!==b[i]) return false;
|
|
63
|
+
}else if(typeof a[i]=='string' && typeof b[i]=='string'){
|
|
58
64
|
if (a[i] !== b[i]){
|
|
59
65
|
return false;
|
|
60
66
|
}
|
|
@@ -119,10 +125,77 @@ function timingSafeEqualU8(a, b) {
|
|
|
119
125
|
}
|
|
120
126
|
|
|
121
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Parse an X509Certificate DN string into the object shape Node's certificate
|
|
130
|
+
* objects use.
|
|
131
|
+
*
|
|
132
|
+
* crypto.X509Certificate gives `subject`/`issuer` as newline-separated
|
|
133
|
+
* "KEY=value" text, but Node documents them on a certificate object as objects
|
|
134
|
+
* keyed by RDN type: { C, ST, L, O, OU, CN }. Passing the raw string through
|
|
135
|
+
* meant tls.checkServerIdentity() — which reads cert.subject.CN — could never
|
|
136
|
+
* find a Common Name, so any certificate without a SAN was rejected outright
|
|
137
|
+
* even when its CN matched. The exported identity check did not work against
|
|
138
|
+
* this library's own getPeerCertificate() output, which is exactly the pairing
|
|
139
|
+
* Node documents.
|
|
140
|
+
*
|
|
141
|
+
* A type may legitimately repeat (OU is the common case); Node represents a
|
|
142
|
+
* repeat as an array and a single occurrence as a plain string.
|
|
143
|
+
*
|
|
144
|
+
* Idempotent: an already-parsed object is returned unchanged, so callers can
|
|
145
|
+
* pass either form.
|
|
146
|
+
*/
|
|
147
|
+
function parseDN(dn) {
|
|
148
|
+
if (dn == null) return {};
|
|
149
|
+
if (typeof dn === 'object') return dn;
|
|
150
|
+
let out = {};
|
|
151
|
+
let lines = String(dn).split('\n');
|
|
152
|
+
for (let i = 0; i < lines.length; i++) {
|
|
153
|
+
let line = lines[i];
|
|
154
|
+
if (!line) continue;
|
|
155
|
+
let eq = line.indexOf('=');
|
|
156
|
+
if (eq < 1) continue;
|
|
157
|
+
let k = line.slice(0, eq).trim();
|
|
158
|
+
let v = line.slice(eq + 1);
|
|
159
|
+
if (!Object.prototype.hasOwnProperty.call(out, k)) out[k] = v;
|
|
160
|
+
else if (Array.isArray(out[k])) out[k].push(v);
|
|
161
|
+
else out[k] = [out[k], v];
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* A certificate (DER) as node:tls's getPeerCertificate() describes it:
|
|
168
|
+
* subject and issuer as RDN objects, the SAN string, the validity dates,
|
|
169
|
+
* fingerprints, serial, and the raw DER. One place for TLSSocket, the
|
|
170
|
+
* session's identity check and anything above (QUIC) — they built it
|
|
171
|
+
* apiece. Unparseable: just { raw }.
|
|
172
|
+
*/
|
|
173
|
+
function certificateObject(der) {
|
|
174
|
+
if (!der) return null;
|
|
175
|
+
try {
|
|
176
|
+
let x = new crypto.X509Certificate(der);
|
|
177
|
+
return {
|
|
178
|
+
subject: parseDN(x.subject),
|
|
179
|
+
issuer: parseDN(x.issuer),
|
|
180
|
+
subjectaltname: x.subjectAltName,
|
|
181
|
+
valid_from: x.validFrom,
|
|
182
|
+
valid_to: x.validTo,
|
|
183
|
+
fingerprint: x.fingerprint,
|
|
184
|
+
fingerprint256: x.fingerprint256,
|
|
185
|
+
serialNumber: x.serialNumber,
|
|
186
|
+
raw: der
|
|
187
|
+
};
|
|
188
|
+
} catch (e) {
|
|
189
|
+
return { raw: der };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
122
193
|
export {
|
|
194
|
+
certificateObject,
|
|
123
195
|
concatUint8Arrays,
|
|
124
196
|
arraybufferEqual,
|
|
125
197
|
arraysEqual,
|
|
126
198
|
uint8Equal,
|
|
127
|
-
timingSafeEqualU8
|
|
199
|
+
timingSafeEqualU8,
|
|
200
|
+
parseDN
|
|
128
201
|
};
|
package/src/wire.js
CHANGED
|
@@ -17,6 +17,36 @@ const DTLS_VERSION = {
|
|
|
17
17
|
DTLS1_3: 0xFEFC
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Does `v` denote the TLS 1.3 *message* format?
|
|
22
|
+
*
|
|
23
|
+
* SINGLE SOURCE OF TRUTH for every "is this 1.3?" branch in this file.
|
|
24
|
+
* The reason it has to exist: DTLS 1.3 is TLS 1.3 at the message layer, but
|
|
25
|
+
* its version constant is 0xFEFC, not 0x0304. Every `v === TLS_VERSION.TLS1_3`
|
|
26
|
+
* comparison is therefore silently FALSE for DTLS 1.3, and silently selects
|
|
27
|
+
* the legacy TLS 1.2 encoding of whatever message is being built.
|
|
28
|
+
*
|
|
29
|
+
* That is not hypothetical: build_certificate below did exactly this, so a
|
|
30
|
+
* DTLS 1.3 server emitted a TLS 1.2 Certificate (no certificate_request_context,
|
|
31
|
+
* no per-entry extensions vector). The peer parsed byte 0 as the context length
|
|
32
|
+
* — which happened to be 0x00 and so passed — then read the next three bytes as
|
|
33
|
+
* certificate_list length and got a value far past the end of the message.
|
|
34
|
+
* BoringSSL answers that with decode_error(50).
|
|
35
|
+
*
|
|
36
|
+
* It only ever bit the server role, because the client-side Certificate call
|
|
37
|
+
* sites hardcode TLS_VERSION.TLS1_3 instead of passing the negotiated version.
|
|
38
|
+
* Two copies of this library also agree with each other, because
|
|
39
|
+
* parse_certificate sniffs both encodings — so only a conforming peer objects.
|
|
40
|
+
*/
|
|
41
|
+
function is_tls13_wire(v) {
|
|
42
|
+
return v === TLS_VERSION.TLS1_3 || v === DTLS_VERSION.DTLS1_3;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Companion to is_tls13_wire for the 1.2 message format. */
|
|
46
|
+
function is_tls12_wire(v) {
|
|
47
|
+
return v === TLS_VERSION.TLS1_2 || v === DTLS_VERSION.DTLS1_2;
|
|
48
|
+
}
|
|
49
|
+
|
|
20
50
|
const TLS_CONTENT_TYPE = {
|
|
21
51
|
CHANGE_CIPHER_SPEC: 20,
|
|
22
52
|
ALERT: 21,
|
|
@@ -24,6 +54,14 @@ const TLS_CONTENT_TYPE = {
|
|
|
24
54
|
APPLICATION_DATA: 23
|
|
25
55
|
};
|
|
26
56
|
|
|
57
|
+
// The largest one handshake message may be, as its header declares it, or a
|
|
58
|
+
// CompressedCertificate decompressed to. OpenSSL allows a ClientHello of
|
|
59
|
+
// 128 KB and certificate chains up to 100 KB (max_cert_list); QUIC's CRYPTO
|
|
60
|
+
// buffer in QUICO is 64 KB. The 24-bit length would allow 16 MB — memory a
|
|
61
|
+
// peer could make us hold, or inflate a few hundred bytes into, before it
|
|
62
|
+
// has proven anything. A maxHandshakeSize below it lowers it.
|
|
63
|
+
const HANDSHAKE_MESSAGE_MAX = 128 * 1024;
|
|
64
|
+
|
|
27
65
|
const TLS_ALERT_LEVEL = {
|
|
28
66
|
WARNING: 1,
|
|
29
67
|
FATAL: 2
|
|
@@ -45,12 +83,14 @@ const TLS_ALERT = {
|
|
|
45
83
|
PROTOCOL_VERSION: 70,
|
|
46
84
|
INSUFFICIENT_SECURITY: 71,
|
|
47
85
|
INTERNAL_ERROR: 80,
|
|
86
|
+
INAPPROPRIATE_FALLBACK: 86, // RFC 7507 (TLS_FALLBACK_SCSV)
|
|
48
87
|
USER_CANCELED: 90,
|
|
49
88
|
MISSING_EXTENSION: 109,
|
|
50
89
|
UNSUPPORTED_EXTENSION: 110,
|
|
51
90
|
UNRECOGNIZED_NAME: 112,
|
|
52
91
|
CERTIFICATE_REQUIRED: 116,
|
|
53
|
-
NO_APPLICATION_PROTOCOL: 120
|
|
92
|
+
NO_APPLICATION_PROTOCOL: 120,
|
|
93
|
+
ECH_REQUIRED: 121
|
|
54
94
|
};
|
|
55
95
|
|
|
56
96
|
const TLS_MESSAGE_TYPE = {
|
|
@@ -67,6 +107,8 @@ const TLS_MESSAGE_TYPE = {
|
|
|
67
107
|
CLIENT_KEY_EXCHANGE: 16,
|
|
68
108
|
FINISHED: 20,
|
|
69
109
|
KEY_UPDATE: 24,
|
|
110
|
+
CERTIFICATE_STATUS: 22, // RFC 6066 §8 (TLS 1.2: the stapled OCSP response)
|
|
111
|
+
COMPRESSED_CERTIFICATE: 25, // RFC 8879
|
|
70
112
|
MESSAGE_HASH: 254 // HRR flow marker
|
|
71
113
|
};
|
|
72
114
|
|
|
@@ -83,6 +125,7 @@ const TLS_EXT = {
|
|
|
83
125
|
MAX_FRAGMENT_LENGTH: 1,
|
|
84
126
|
STATUS_REQUEST: 5,
|
|
85
127
|
SUPPORTED_GROUPS: 10,
|
|
128
|
+
EC_POINT_FORMATS: 11,
|
|
86
129
|
SIGNATURE_ALGORITHMS: 13,
|
|
87
130
|
USE_SRTP: 14,
|
|
88
131
|
HEARTBEAT: 15,
|
|
@@ -92,6 +135,7 @@ const TLS_EXT = {
|
|
|
92
135
|
SERVER_CERT_TYPE: 20,
|
|
93
136
|
PADDING: 21,
|
|
94
137
|
EXTENDED_MASTER_SECRET: 23,
|
|
138
|
+
COMPRESS_CERTIFICATE: 27, // RFC 8879
|
|
95
139
|
SESSION_TICKET: 35,
|
|
96
140
|
PRE_SHARED_KEY: 41,
|
|
97
141
|
EARLY_DATA: 42,
|
|
@@ -146,6 +190,25 @@ function w_u48(buf, off, v) {
|
|
|
146
190
|
return off;
|
|
147
191
|
}
|
|
148
192
|
|
|
193
|
+
/**
|
|
194
|
+
* 64-bit big-endian write. Split into two 32-bit halves rather than using
|
|
195
|
+
* BigInt: DTLS epochs and sequence numbers are both far below 2^53, so Number
|
|
196
|
+
* arithmetic is exact here and roughly an order of magnitude faster.
|
|
197
|
+
*/
|
|
198
|
+
function w_u64(buf, off, v) {
|
|
199
|
+
let hi = Math.floor(v / 0x100000000);
|
|
200
|
+
let lo = v >>> 0;
|
|
201
|
+
buf[off++] = (hi >>> 24) & 0xFF;
|
|
202
|
+
buf[off++] = (hi >>> 16) & 0xFF;
|
|
203
|
+
buf[off++] = (hi >>> 8) & 0xFF;
|
|
204
|
+
buf[off++] = (hi ) & 0xFF;
|
|
205
|
+
buf[off++] = (lo >>> 24) & 0xFF;
|
|
206
|
+
buf[off++] = (lo >>> 16) & 0xFF;
|
|
207
|
+
buf[off++] = (lo >>> 8) & 0xFF;
|
|
208
|
+
buf[off++] = (lo ) & 0xFF;
|
|
209
|
+
return off;
|
|
210
|
+
}
|
|
211
|
+
|
|
149
212
|
function w_bytes(buf, off, b) {
|
|
150
213
|
buf.set(b, off);
|
|
151
214
|
return off + b.length;
|
|
@@ -255,6 +318,7 @@ let exts = {};
|
|
|
255
318
|
exts.SERVER_NAME = { encode: null, decode: null };
|
|
256
319
|
exts.SUPPORTED_VERSIONS = { encode: null, decode: null };
|
|
257
320
|
exts.SUPPORTED_GROUPS = { encode: null, decode: null };
|
|
321
|
+
exts.EC_POINT_FORMATS = { encode: null, decode: null };
|
|
258
322
|
exts.SIGNATURE_ALGORITHMS = { encode: null, decode: null };
|
|
259
323
|
exts.PSK_KEY_EXCHANGE_MODES = { encode: null, decode: null };
|
|
260
324
|
exts.KEY_SHARE = { encode: null, decode: null };
|
|
@@ -264,6 +328,47 @@ exts.RENEGOTIATION_INFO = { encode: null, decode: null };
|
|
|
264
328
|
exts.SESSION_TICKET = { encode: null, decode: null };
|
|
265
329
|
exts.EXTENDED_MASTER_SECRET = { encode: null, decode: null };
|
|
266
330
|
exts.CERTIFICATE_AUTHORITIES = { encode: null, decode: null };
|
|
331
|
+
exts.COMPRESS_CERTIFICATE = { encode: null, decode: null };
|
|
332
|
+
exts.STATUS_REQUEST = { encode: null, decode: null };
|
|
333
|
+
|
|
334
|
+
/* ---------------------- STATUS_REQUEST (5) ---------------------- */
|
|
335
|
+
// RFC 6066 §8. Three places, three bodies:
|
|
336
|
+
// ClientHello: CertificateStatusRequest — status_type ocsp(1),
|
|
337
|
+
// responder_id_list<0..2^16-1>, request_extensions<0..2^16-1>
|
|
338
|
+
// ServerHello (1.2): empty — "a CertificateStatus message follows"
|
|
339
|
+
// CertificateEntry (1.3, RFC 8446 §4.4.2.1): CertificateStatus itself
|
|
340
|
+
// Encoded from { ocsp: true } (the request with empty lists, as every
|
|
341
|
+
// browser sends it) or a raw body; decoded to { status_type } or {} when
|
|
342
|
+
// empty. Unknown status types are not an error: the server just does not
|
|
343
|
+
// answer them.
|
|
344
|
+
exts.STATUS_REQUEST.encode = function (value) {
|
|
345
|
+
if (value instanceof Uint8Array) return value;
|
|
346
|
+
if (value && value.ocsp) return new Uint8Array([1, 0, 0, 0, 0]);
|
|
347
|
+
return new Uint8Array(0);
|
|
348
|
+
};
|
|
349
|
+
exts.STATUS_REQUEST.decode = function (data) {
|
|
350
|
+
if (data.length === 0) return {};
|
|
351
|
+
return { status_type: data[0] };
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/* ---------------------- COMPRESS_CERTIFICATE (27) ---------------------- */
|
|
355
|
+
// RFC 8879 §3: CertificateCompressionAlgorithm algorithms<2..2^8-2> — a
|
|
356
|
+
// one-byte length, then 16-bit codes.
|
|
357
|
+
exts.COMPRESS_CERTIFICATE.encode = function (value) {
|
|
358
|
+
let list = Array.isArray(value) ? value : [];
|
|
359
|
+
let out = new Uint8Array(1 + list.length * 2);
|
|
360
|
+
out[0] = list.length * 2;
|
|
361
|
+
for (let i = 0; i < list.length; i++) w_u16(out, 1 + i * 2, list[i]);
|
|
362
|
+
return out;
|
|
363
|
+
};
|
|
364
|
+
exts.COMPRESS_CERTIFICATE.decode = function (data) {
|
|
365
|
+
if (data.length < 1) throw parseError('compress_certificate: empty');
|
|
366
|
+
let n = data[0];
|
|
367
|
+
if (n < 2 || n % 2 !== 0 || 1 + n !== data.length) throw parseError('compress_certificate: bad length');
|
|
368
|
+
let out = [];
|
|
369
|
+
for (let i = 1; i < 1 + n; i += 2) out.push((data[i] << 8) | data[i + 1]);
|
|
370
|
+
return out;
|
|
371
|
+
};
|
|
267
372
|
|
|
268
373
|
/* ---------------------- CERTIFICATE_AUTHORITIES (47) ---------------------- */
|
|
269
374
|
// RFC 8446 §4.2.4: DistinguishedName authorities<3..2^16-1> — the list is
|
|
@@ -478,6 +583,24 @@ exts.SUPPORTED_GROUPS.decode = function (data) {
|
|
|
478
583
|
return out; // array of named groups
|
|
479
584
|
};
|
|
480
585
|
|
|
586
|
+
/* ---------------------------- EC_POINT_FORMATS (11) ---------------------------- */
|
|
587
|
+
// RFC 8422 §5.1.2: ECPointFormat ec_point_format_list<1..2^8-1>, one byte per
|
|
588
|
+
// format. 0 is uncompressed — the only format TLS 1.2 still uses, and the one
|
|
589
|
+
// every list must contain.
|
|
590
|
+
exts.EC_POINT_FORMATS.encode = function (value) {
|
|
591
|
+
let formats = Array.isArray(value) && value.length > 0 ? value : [0];
|
|
592
|
+
return veclen(1, new Uint8Array(formats));
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
exts.EC_POINT_FORMATS.decode = function (data) {
|
|
596
|
+
let off = 0;
|
|
597
|
+
let list;
|
|
598
|
+
[list, off] = readVec(data, off, 1);
|
|
599
|
+
if (off !== data.length) throw parseError('trailing data after ec_point_formats');
|
|
600
|
+
if (list.length === 0) throw parseError('empty ec_point_formats list');
|
|
601
|
+
return Array.from(list); // array of point formats
|
|
602
|
+
};
|
|
603
|
+
|
|
481
604
|
/* -------------------------- SIGNATURE_ALGORITHMS (13) -------------------------- */
|
|
482
605
|
exts.SIGNATURE_ALGORITHMS.encode = function (value) {
|
|
483
606
|
// A codec encodes what it is handed; the set of signature schemes is policy
|
|
@@ -674,7 +797,16 @@ exts.KEY_SHARE.encode = function (value) {
|
|
|
674
797
|
return veclen(2, concatUint8Arrays(parts));
|
|
675
798
|
};
|
|
676
799
|
|
|
677
|
-
|
|
800
|
+
// `kind` — the message the extension is in ('client_hello', 'server_hello';
|
|
801
|
+
// parse_hello passes it) — decides the form; the three forms cannot be told
|
|
802
|
+
// apart by length. An empty client_shares, which RFC 8446 §4.2.8 allows ("in
|
|
803
|
+
// order to request group selection from the server, at the cost of an
|
|
804
|
+
// additional round trip"), is two bytes [00 00]: read as a HelloRetryRequest's
|
|
805
|
+
// NamedGroup it became a share for group 0x0000, refused illegal_parameter
|
|
806
|
+
// (not in supported_groups) — where the client asked for a HelloRetryRequest.
|
|
807
|
+
// Scanners enumerate a server's groups exactly so; every probe failed.
|
|
808
|
+
exts.KEY_SHARE.decode = function (data, kind) {
|
|
809
|
+
if (kind === 'client_hello') return decode_client_shares(data);
|
|
678
810
|
// HelloRetryRequest form: just NamedGroup (2 bytes, no key_exchange)
|
|
679
811
|
if (data.length === 2) {
|
|
680
812
|
let g, off = 0;
|
|
@@ -698,12 +830,17 @@ exts.KEY_SHARE.decode = function (data) {
|
|
|
698
830
|
}
|
|
699
831
|
}
|
|
700
832
|
|
|
701
|
-
|
|
833
|
+
return decode_client_shares(data);
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
// ClientHello form: vector<2> of KeyShareEntry, exactly filling the extension.
|
|
837
|
+
function decode_client_shares(data) {
|
|
702
838
|
let off2 = 0;
|
|
703
839
|
let listBytes;
|
|
704
840
|
[listBytes, off2] = r_u16(data, off2);
|
|
705
841
|
|
|
706
842
|
let end = off2 + listBytes;
|
|
843
|
+
if (end !== data.length) throw parseError('key_share: client_shares length does not match the extension');
|
|
707
844
|
let out = [];
|
|
708
845
|
|
|
709
846
|
while (off2 < end) {
|
|
@@ -718,9 +855,10 @@ exts.KEY_SHARE.decode = function (data) {
|
|
|
718
855
|
|
|
719
856
|
out.push({ group: g2, key_exchange: ke2 });
|
|
720
857
|
}
|
|
858
|
+
if (off2 !== end) throw parseError('key_share: an entry overruns client_shares');
|
|
721
859
|
|
|
722
860
|
return out; // array of entries
|
|
723
|
-
}
|
|
861
|
+
}
|
|
724
862
|
|
|
725
863
|
/* ------------------------------------ ALPN (16) ----------------------------------- */
|
|
726
864
|
exts.ALPN.encode = function (value) {
|
|
@@ -900,7 +1038,7 @@ function build_extensions(list) {
|
|
|
900
1038
|
* CertificateRequest) → use parse_extension_list().
|
|
901
1039
|
* Both share this core so the duplicate/overflow rules apply identically.
|
|
902
1040
|
*/
|
|
903
|
-
function parse_extension_list(buf) {
|
|
1041
|
+
function parse_extension_list(buf, kind) {
|
|
904
1042
|
let off = 0;
|
|
905
1043
|
let out = [];
|
|
906
1044
|
let seen = {}; // RFC 8446 §4.2: "There MUST NOT be more than one extension of the same type"
|
|
@@ -937,7 +1075,7 @@ function parse_extension_list(buf) {
|
|
|
937
1075
|
|
|
938
1076
|
let name = ext_name_by_code(t);
|
|
939
1077
|
let dec = exts[name] && exts[name].decode;
|
|
940
|
-
let val = dec ? dec(d) : null;
|
|
1078
|
+
let val = dec ? dec(d, kind) : null;
|
|
941
1079
|
|
|
942
1080
|
out.push({ type: t, name: name, data: d, value: val });
|
|
943
1081
|
}
|
|
@@ -949,7 +1087,7 @@ function parse_extension_list(buf) {
|
|
|
949
1087
|
* Parse a length-prefixed extensions vector that ENDS the enclosing structure.
|
|
950
1088
|
* `buf` must START with the 2-byte vector length and contain nothing after it.
|
|
951
1089
|
*/
|
|
952
|
-
function parse_extensions(buf) {
|
|
1090
|
+
function parse_extensions(buf, kind) {
|
|
953
1091
|
let off = 0;
|
|
954
1092
|
let n;
|
|
955
1093
|
[n, off] = r_u16(buf, off);
|
|
@@ -961,7 +1099,7 @@ function parse_extensions(buf) {
|
|
|
961
1099
|
if (end > buf.length) throw parseError('extensions vector length exceeds body');
|
|
962
1100
|
if (end < buf.length) throw parseError('trailing data after extensions vector');
|
|
963
1101
|
|
|
964
|
-
return parse_extension_list(buf.subarray(off, end));
|
|
1102
|
+
return parse_extension_list(buf.subarray(off, end), kind);
|
|
965
1103
|
}
|
|
966
1104
|
|
|
967
1105
|
|
|
@@ -1109,7 +1247,7 @@ function parse_hello(params) {
|
|
|
1109
1247
|
let extensions = [];
|
|
1110
1248
|
if (body.length > off) {
|
|
1111
1249
|
let extRaw = body.subarray(off);
|
|
1112
|
-
extensions = parse_extensions(extRaw);
|
|
1250
|
+
extensions = parse_extensions(extRaw, type);
|
|
1113
1251
|
}
|
|
1114
1252
|
|
|
1115
1253
|
// --- version תמיד ערך יחיד ---
|
|
@@ -1151,6 +1289,14 @@ function build_certificate(params) {
|
|
|
1151
1289
|
// TLS 1.3 → מחזיר: request_context (vec<1>) || certificate_list (vec<3> של [ cert(vec<3>) || extensions(vec<2>) ]*)
|
|
1152
1290
|
// TLS 1.2 → מחזיר: certificate_list (vec<3> של cert(vec<3>)*), מתעלם מ-extensions/request_context
|
|
1153
1291
|
|
|
1292
|
+
// NOTE the asymmetry with build_certificate_request, which defaults to
|
|
1293
|
+
// TLS1_3. Both defaults are kept as they were, because callers outside this
|
|
1294
|
+
// file may rely on them; every call site inside the library passes `version`
|
|
1295
|
+
// explicitly, so the default is never actually exercised here. What matters
|
|
1296
|
+
// is that the branch below asks is_tls13_wire(v), not v === TLS1_3 — the
|
|
1297
|
+
// mismatched defaults are what made the two messages in the SAME server
|
|
1298
|
+
// flight come out in two different encodings, which is why this stayed
|
|
1299
|
+
// hidden for so long.
|
|
1154
1300
|
let v = params.version || TLS_VERSION.TLS1_2;
|
|
1155
1301
|
|
|
1156
1302
|
// Normalize to entries[] always
|
|
@@ -1161,8 +1307,14 @@ function build_certificate(params) {
|
|
|
1161
1307
|
}
|
|
1162
1308
|
if (!entries) entries = [];
|
|
1163
1309
|
|
|
1164
|
-
if (v
|
|
1165
|
-
|
|
1310
|
+
if (is_tls13_wire(v)) {
|
|
1311
|
+
// Accept either spelling of the context field. build_tls_message callers
|
|
1312
|
+
// pass `certificate_request_context` (the RFC 8446 field name); this
|
|
1313
|
+
// builder originally read only `request_context`, so the value was
|
|
1314
|
+
// dropped without a word. Harmless during the initial handshake, where
|
|
1315
|
+
// the context is always empty — but post-handshake client authentication
|
|
1316
|
+
// is identified BY that context, so it would have failed silently.
|
|
1317
|
+
let ctx = toU8(params.request_context || params.certificate_request_context || new Uint8Array(0));
|
|
1166
1318
|
|
|
1167
1319
|
let entryParts = [];
|
|
1168
1320
|
for (let i = 0; i < entries.length; i++) {
|
|
@@ -1199,6 +1351,51 @@ function build_certificate(params) {
|
|
|
1199
1351
|
}
|
|
1200
1352
|
}
|
|
1201
1353
|
|
|
1354
|
+
/* ------------------------ CompressedCertificate ------------------------ */
|
|
1355
|
+
// RFC 8879 §4: algorithm (u16), uncompressed_length (u24),
|
|
1356
|
+
// compressed_certificate_message<1..2^24-1> (u24-prefixed).
|
|
1357
|
+
/**
|
|
1358
|
+
* CertificateStatus (RFC 6066 §8): status_type ocsp(1), then the DER OCSP
|
|
1359
|
+
* response as an opaque<1..2^24-1>. The body of the TLS 1.2 handshake
|
|
1360
|
+
* message, and of the status_request extension in a TLS 1.3 CertificateEntry.
|
|
1361
|
+
*/
|
|
1362
|
+
function build_certificate_status(response) {
|
|
1363
|
+
let r = toU8(response);
|
|
1364
|
+
let out = new Uint8Array(4 + r.length);
|
|
1365
|
+
out[0] = 1;
|
|
1366
|
+
out[1] = (r.length >>> 16) & 0xff; out[2] = (r.length >>> 8) & 0xff; out[3] = r.length & 0xff;
|
|
1367
|
+
out.set(r, 4);
|
|
1368
|
+
return out;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
/** CertificateStatus → { status_type, response }; decode_error on a bad length. */
|
|
1372
|
+
function parse_certificate_status(body) {
|
|
1373
|
+
if (body.length < 4) throw parseError('CertificateStatus: too short');
|
|
1374
|
+
let len = (body[1] << 16) | (body[2] << 8) | body[3];
|
|
1375
|
+
if (len === 0 || 4 + len !== body.length) throw parseError('CertificateStatus: bad length');
|
|
1376
|
+
return { status_type: body[0], response: body.slice(4) };
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
function build_compressed_certificate(params) {
|
|
1380
|
+
let data = params.compressed;
|
|
1381
|
+
let out = new Uint8Array(2 + 3 + 3 + data.length);
|
|
1382
|
+
let o = 0;
|
|
1383
|
+
o = w_u16(out, o, params.algorithm);
|
|
1384
|
+
out[o++] = (params.uncompressed_length >>> 16) & 0xff; out[o++] = (params.uncompressed_length >>> 8) & 0xff; out[o++] = params.uncompressed_length & 0xff;
|
|
1385
|
+
out[o++] = (data.length >>> 16) & 0xff; out[o++] = (data.length >>> 8) & 0xff; out[o++] = data.length & 0xff;
|
|
1386
|
+
out.set(data, o);
|
|
1387
|
+
return out;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
function parse_compressed_certificate(body) {
|
|
1391
|
+
if (body.length < 8) throw parseError('CompressedCertificate: truncated');
|
|
1392
|
+
let algorithm = (body[0] << 8) | body[1];
|
|
1393
|
+
let uncompressed_length = (body[2] << 16) | (body[3] << 8) | body[4];
|
|
1394
|
+
let n = (body[5] << 16) | (body[6] << 8) | body[7];
|
|
1395
|
+
if (n < 1 || 8 + n !== body.length) throw parseError('CompressedCertificate: bad length');
|
|
1396
|
+
return { algorithm: algorithm, uncompressed_length: uncompressed_length, compressed: body.subarray(8) };
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1202
1399
|
function parse_certificate(body) {
|
|
1203
1400
|
// always returns:
|
|
1204
1401
|
// {
|
|
@@ -1400,8 +1597,10 @@ function parse_new_session_ticket(body) {
|
|
|
1400
1597
|
function build_certificate_request(params) {
|
|
1401
1598
|
let v = params && params.version || TLS_VERSION.TLS1_3;
|
|
1402
1599
|
|
|
1403
|
-
if (v
|
|
1404
|
-
|
|
1600
|
+
if (is_tls13_wire(v)) {
|
|
1601
|
+
// Same dual-spelling accommodation as build_certificate above.
|
|
1602
|
+
let ctx = toU8((params && (params.request_context || params.certificate_request_context)) ||
|
|
1603
|
+
new Uint8Array(0));
|
|
1405
1604
|
|
|
1406
1605
|
// RFC 8446 §4.3.2: "The 'signature_algorithms' extension MUST be specified"
|
|
1407
1606
|
// in a TLS 1.3 CertificateRequest.
|
|
@@ -1867,8 +2066,11 @@ function parse_hello_verify_request(body) {
|
|
|
1867
2066
|
|
|
1868
2067
|
/* ================================ Exports ================================= */
|
|
1869
2068
|
export {
|
|
2069
|
+
HANDSHAKE_MESSAGE_MAX,
|
|
1870
2070
|
TLS_VERSION,
|
|
1871
2071
|
DTLS_VERSION,
|
|
2072
|
+
is_tls13_wire,
|
|
2073
|
+
is_tls12_wire,
|
|
1872
2074
|
TLS_CONTENT_TYPE,
|
|
1873
2075
|
TLS_ALERT_LEVEL,
|
|
1874
2076
|
TLS_ALERT,
|
|
@@ -1879,6 +2081,7 @@ export {
|
|
|
1879
2081
|
w_u16,
|
|
1880
2082
|
w_u24,
|
|
1881
2083
|
w_u48,
|
|
2084
|
+
w_u64,
|
|
1882
2085
|
w_bytes,
|
|
1883
2086
|
r_u8,
|
|
1884
2087
|
r_u16,
|
|
@@ -1899,6 +2102,10 @@ export {
|
|
|
1899
2102
|
|
|
1900
2103
|
build_certificate,
|
|
1901
2104
|
parse_certificate,
|
|
2105
|
+
build_compressed_certificate,
|
|
2106
|
+
parse_compressed_certificate,
|
|
2107
|
+
build_certificate_status,
|
|
2108
|
+
parse_certificate_status,
|
|
1902
2109
|
|
|
1903
2110
|
build_certificate_verify,
|
|
1904
2111
|
parse_certificate_verify,
|