tunnelfetch 1.0.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/LICENSE +28 -0
- package/README.md +617 -0
- package/README.zh-CN.md +470 -0
- package/package.json +74 -0
- package/src/client/cookies.js +429 -0
- package/src/client/decode.js +346 -0
- package/src/client/redirect.js +249 -0
- package/src/client.js +704 -0
- package/src/errors.js +181 -0
- package/src/http1/chunked.js +289 -0
- package/src/http1/index.js +10 -0
- package/src/http1/request.js +143 -0
- package/src/http1/response.js +493 -0
- package/src/http2/connection.js +1170 -0
- package/src/http2/constants.js +129 -0
- package/src/http2/frames.js +291 -0
- package/src/http2/hpack.js +420 -0
- package/src/http2/huffman.js +203 -0
- package/src/http2/index.js +21 -0
- package/src/index.js +46 -0
- package/src/pool.js +256 -0
- package/src/proxy/direct.js +62 -0
- package/src/proxy/http-connect.js +206 -0
- package/src/proxy/index.js +197 -0
- package/src/proxy/socks5.js +344 -0
- package/src/tls/aead.js +263 -0
- package/src/tls/connect.js +407 -0
- package/src/tls/constants.js +334 -0
- package/src/tls/extensions.js +376 -0
- package/src/tls/handshake-messages.js +901 -0
- package/src/tls/handshake.js +568 -0
- package/src/tls/handshake12.js +507 -0
- package/src/tls/index.js +44 -0
- package/src/tls/keyschedule.js +473 -0
- package/src/tls/record.js +872 -0
- package/src/tls/tickets.js +145 -0
- package/src/tls/transcript.js +101 -0
- package/src/tls/wire.js +224 -0
- package/src/transport.js +296 -0
- package/src/trust/der.js +551 -0
- package/src/trust/index.js +375 -0
- package/src/trust/name.js +235 -0
- package/src/trust/ocsp.js +759 -0
- package/src/trust/path.js +595 -0
- package/src/trust/roots.js +454 -0
- package/src/trust/x509.js +902 -0
- package/src/util/bytes.js +470 -0
- package/src/util/deadline.js +266 -0
- package/src/warmup-fixture.js +85 -0
- package/src/warmup.js +243 -0
- package/types/client/cookies.d.ts +159 -0
- package/types/client/decode.d.ts +54 -0
- package/types/client/redirect.d.ts +96 -0
- package/types/client.d.ts +323 -0
- package/types/errors.d.ts +141 -0
- package/types/http1/chunked.d.ts +48 -0
- package/types/http1/index.d.ts +3 -0
- package/types/http1/request.d.ts +44 -0
- package/types/http1/response.d.ts +183 -0
- package/types/http2/connection.d.ts +282 -0
- package/types/http2/constants.d.ts +95 -0
- package/types/http2/frames.d.ts +116 -0
- package/types/http2/hpack.d.ts +99 -0
- package/types/http2/huffman.d.ts +21 -0
- package/types/http2/index.d.ts +5 -0
- package/types/index.d.ts +17 -0
- package/types/pool.d.ts +135 -0
- package/types/proxy/direct.d.ts +26 -0
- package/types/proxy/http-connect.d.ts +37 -0
- package/types/proxy/index.d.ts +62 -0
- package/types/proxy/socks5.d.ts +47 -0
- package/types/tls/aead.d.ts +67 -0
- package/types/tls/connect.d.ts +280 -0
- package/types/tls/constants.d.ts +275 -0
- package/types/tls/extensions.d.ts +195 -0
- package/types/tls/handshake-messages.d.ts +430 -0
- package/types/tls/handshake.d.ts +90 -0
- package/types/tls/handshake12.d.ts +35 -0
- package/types/tls/index.d.ts +9 -0
- package/types/tls/keyschedule.d.ts +272 -0
- package/types/tls/record.d.ts +361 -0
- package/types/tls/tickets.d.ts +66 -0
- package/types/tls/transcript.d.ts +52 -0
- package/types/tls/wire.d.ts +106 -0
- package/types/transport.d.ts +222 -0
- package/types/trust/der.d.ts +239 -0
- package/types/trust/index.d.ts +194 -0
- package/types/trust/name.d.ts +33 -0
- package/types/trust/ocsp.d.ts +138 -0
- package/types/trust/path.d.ts +139 -0
- package/types/trust/roots.d.ts +36 -0
- package/types/trust/x509.d.ts +401 -0
- package/types/util/bytes.d.ts +183 -0
- package/types/util/deadline.d.ts +133 -0
- package/types/warmup-fixture.d.ts +11 -0
- package/types/warmup.d.ts +45 -0
package/src/trust/der.js
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
// ASN.1 DER reader.
|
|
2
|
+
//
|
|
3
|
+
// This feeds signature verification, so exactness is the whole point: every element carries the
|
|
4
|
+
// byte range of its full encoding AND of its content, and callers verify signatures over slices
|
|
5
|
+
// of the ORIGINAL buffer. Nothing here re-encodes, ever — a re-encoder that "fixes" anything is a
|
|
6
|
+
// forgery laundromat.
|
|
7
|
+
//
|
|
8
|
+
// DER, not BER. BER's flexibilities (indefinite lengths, non-minimal lengths, constructed
|
|
9
|
+
// strings) are exactly the degrees of freedom historical certificate forgeries lived in, so each
|
|
10
|
+
// one is rejected by name rather than tolerated. Every rejection says where and why, because a
|
|
11
|
+
// parse failure in a certificate is either a broken CA or an attack, and both deserve a precise
|
|
12
|
+
// log line.
|
|
13
|
+
|
|
14
|
+
import { CertificateError, codes } from '../errors.js';
|
|
15
|
+
|
|
16
|
+
/** Universal tag numbers we name in errors and match on. */
|
|
17
|
+
export const TAG = {
|
|
18
|
+
BOOLEAN: 1,
|
|
19
|
+
INTEGER: 2,
|
|
20
|
+
BIT_STRING: 3,
|
|
21
|
+
OCTET_STRING: 4,
|
|
22
|
+
NULL: 5,
|
|
23
|
+
OID: 6,
|
|
24
|
+
ENUMERATED: 10,
|
|
25
|
+
UTF8_STRING: 12,
|
|
26
|
+
SEQUENCE: 16,
|
|
27
|
+
SET: 17,
|
|
28
|
+
NUMERIC_STRING: 18,
|
|
29
|
+
PRINTABLE_STRING: 19,
|
|
30
|
+
TELETEX_STRING: 20,
|
|
31
|
+
IA5_STRING: 22,
|
|
32
|
+
UTC_TIME: 23,
|
|
33
|
+
GENERALIZED_TIME: 24,
|
|
34
|
+
VISIBLE_STRING: 26,
|
|
35
|
+
BMP_STRING: 30,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const CLS = { UNIVERSAL: 0, APPLICATION: 1, CONTEXT: 2, PRIVATE: 3 };
|
|
39
|
+
|
|
40
|
+
const TAG_NAME = Object.fromEntries(Object.entries(TAG).map(([k, v]) => [v, k]));
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* One decoded tag-length-value element, as byte ranges into the ORIGINAL buffer. Every reader
|
|
44
|
+
* in the trust layer passes these around instead of slices precisely so that signature checks
|
|
45
|
+
* always run over the peer's own bytes.
|
|
46
|
+
* @typedef {object} Tlv
|
|
47
|
+
* @property {number} cls tag class, per {@link CLS}
|
|
48
|
+
* @property {boolean} constructed
|
|
49
|
+
* @property {number} tag tag number, high-tag-number form already decoded
|
|
50
|
+
* @property {number} start offset of the first header byte
|
|
51
|
+
* @property {number} headerLen tag + length octets
|
|
52
|
+
* @property {number} contentStart
|
|
53
|
+
* @property {number} contentEnd
|
|
54
|
+
* @property {number} end one past the element; equals contentEnd for every legal DER element
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Describe a tag for error messages: "SEQUENCE", "[0]", "APPLICATION 3".
|
|
59
|
+
* @param {number} cls
|
|
60
|
+
* @param {number} tag
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
export function tagName(cls, tag) {
|
|
64
|
+
if (cls === CLS.UNIVERSAL) return TAG_NAME[tag] ?? `UNIVERSAL ${tag}`;
|
|
65
|
+
if (cls === CLS.CONTEXT) return `[${tag}]`;
|
|
66
|
+
return `${cls === CLS.APPLICATION ? 'APPLICATION' : 'PRIVATE'} ${tag}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {number} offset
|
|
71
|
+
* @param {string} message
|
|
72
|
+
* @returns {CertificateError}
|
|
73
|
+
*/
|
|
74
|
+
export function parseError(offset, message) {
|
|
75
|
+
return new CertificateError(codes.CERT_PARSE, `DER at offset ${offset}: ${message}`, { offset });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read one TLV starting at `offset`. Returns byte ranges only — content is always a subarray of
|
|
80
|
+
* the caller's original buffer, never a copy, so signatures can be verified over the same bytes
|
|
81
|
+
* the peer sent.
|
|
82
|
+
*
|
|
83
|
+
* @param {Uint8Array} bytes
|
|
84
|
+
* @param {number} offset
|
|
85
|
+
* @returns {Tlv}
|
|
86
|
+
*/
|
|
87
|
+
export function readTlv(bytes, offset) {
|
|
88
|
+
const len = bytes.byteLength;
|
|
89
|
+
if (offset >= len) throw parseError(offset, `element expected but buffer ends at ${len}`);
|
|
90
|
+
const first = bytes[offset];
|
|
91
|
+
const cls = first >> 6;
|
|
92
|
+
const constructed = (first & 0x20) !== 0;
|
|
93
|
+
let tag = first & 0x1f;
|
|
94
|
+
let p = offset + 1;
|
|
95
|
+
if (tag === 0x1f) {
|
|
96
|
+
// High tag number form: base-128, minimal, terminated by a byte without the high bit.
|
|
97
|
+
tag = 0;
|
|
98
|
+
if (p < len && bytes[p] === 0x80) {
|
|
99
|
+
throw parseError(p, 'non-minimal multi-byte tag number (leading 0x80)');
|
|
100
|
+
}
|
|
101
|
+
for (let i = 0; ; i++) {
|
|
102
|
+
if (p >= len) throw parseError(p, 'truncated multi-byte tag number');
|
|
103
|
+
if (i >= 4) throw parseError(p, 'tag number wider than 28 bits, refusing');
|
|
104
|
+
const b = bytes[p++];
|
|
105
|
+
tag = tag * 128 + (b & 0x7f);
|
|
106
|
+
if ((b & 0x80) === 0) break;
|
|
107
|
+
}
|
|
108
|
+
if (tag < 0x1f) throw parseError(offset, `tag ${tag} used high-tag-number form unnecessarily`);
|
|
109
|
+
}
|
|
110
|
+
if (p >= len) throw parseError(p, `length byte expected but buffer ends at ${len}`);
|
|
111
|
+
const l0 = bytes[p++];
|
|
112
|
+
let contentLen;
|
|
113
|
+
if (l0 < 0x80) {
|
|
114
|
+
contentLen = l0;
|
|
115
|
+
} else if (l0 === 0x80) {
|
|
116
|
+
// Indefinite length is BER-only. In DER it is illegal, and accepting it would let an attacker
|
|
117
|
+
// move element boundaries around — the classic constructed-string forgery.
|
|
118
|
+
throw parseError(p - 1, 'indefinite length is not allowed in DER');
|
|
119
|
+
} else if (l0 === 0xff) {
|
|
120
|
+
throw parseError(p - 1, 'reserved length byte 0xff');
|
|
121
|
+
} else {
|
|
122
|
+
const n = l0 & 0x7f;
|
|
123
|
+
if (n > 4) throw parseError(p - 1, `length of length ${n} bytes, refusing (> 4)`);
|
|
124
|
+
if (p + n > len) throw parseError(p, 'truncated long-form length');
|
|
125
|
+
if (bytes[p] === 0x00) {
|
|
126
|
+
throw parseError(p, 'non-minimal length encoding (leading zero length byte)');
|
|
127
|
+
}
|
|
128
|
+
contentLen = 0;
|
|
129
|
+
for (let i = 0; i < n; i++) contentLen = contentLen * 256 + bytes[p + i];
|
|
130
|
+
p += n;
|
|
131
|
+
if (contentLen < 0x80) {
|
|
132
|
+
throw parseError(offset, `non-minimal length encoding (long form for length ${contentLen})`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const contentStart = p;
|
|
136
|
+
const contentEnd = contentStart + contentLen;
|
|
137
|
+
if (contentEnd > len) {
|
|
138
|
+
throw parseError(
|
|
139
|
+
offset,
|
|
140
|
+
`${tagName(cls, tag)} content of ${contentLen} bytes runs past end of buffer ` +
|
|
141
|
+
`(${contentEnd} > ${len})`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
cls,
|
|
146
|
+
constructed,
|
|
147
|
+
tag,
|
|
148
|
+
start: offset,
|
|
149
|
+
headerLen: contentStart - offset,
|
|
150
|
+
contentStart,
|
|
151
|
+
contentEnd,
|
|
152
|
+
end: contentEnd,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The content bytes of a TLV, as a subarray of the original buffer. */
|
|
157
|
+
/** @type {(bytes: Uint8Array, tlv: Tlv) => Uint8Array} */
|
|
158
|
+
export const content = (bytes, tlv) => bytes.subarray(tlv.contentStart, tlv.contentEnd);
|
|
159
|
+
/** The full element (tag + length + content), as a subarray of the original buffer. */
|
|
160
|
+
/** @type {(bytes: Uint8Array, tlv: Tlv) => Uint8Array} */
|
|
161
|
+
export const element = (bytes, tlv) => bytes.subarray(tlv.start, tlv.end);
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Assert a TLV has the given shape, with an error naming expected vs got at the offset.
|
|
165
|
+
* @param {Tlv} tlv
|
|
166
|
+
* @param {{ cls?: number, tag: number, constructed?: boolean }} shape omitted `constructed`
|
|
167
|
+
* accepts either form
|
|
168
|
+
* @param {string} what
|
|
169
|
+
* @returns {Tlv} the same tlv, for chaining into a reader
|
|
170
|
+
*/
|
|
171
|
+
export function expectTlv(tlv, { cls = CLS.UNIVERSAL, tag, constructed }, what) {
|
|
172
|
+
const wrongShape = constructed !== undefined && tlv.constructed !== constructed;
|
|
173
|
+
if (tlv.cls !== cls || tlv.tag !== tag || wrongShape) {
|
|
174
|
+
const wanted = constructed === undefined ? '' : constructed ? ' (constructed)' : ' (primitive)';
|
|
175
|
+
throw parseError(
|
|
176
|
+
tlv.start,
|
|
177
|
+
`expected ${tagName(cls, tag)}${wanted} for ${what}, ` +
|
|
178
|
+
`got ${tagName(tlv.cls, tlv.tag)} (${tlv.constructed ? 'constructed' : 'primitive'})`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return tlv;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Read a TLV and require it to be a constructed SEQUENCE.
|
|
186
|
+
* @param {Uint8Array} bytes
|
|
187
|
+
* @param {number} offset
|
|
188
|
+
* @param {string} [what]
|
|
189
|
+
* @returns {Tlv}
|
|
190
|
+
*/
|
|
191
|
+
export function readSequence(bytes, offset, what = 'SEQUENCE') {
|
|
192
|
+
return expectTlv(readTlv(bytes, offset), { tag: TAG.SEQUENCE, constructed: true }, what);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Parse the content of a constructed element into its immediate children, requiring them to fill
|
|
197
|
+
* the content exactly. Trailing bytes inside a container are as suspicious as trailing bytes
|
|
198
|
+
* after the certificate.
|
|
199
|
+
* @param {Uint8Array} bytes
|
|
200
|
+
* @param {Tlv} tlv
|
|
201
|
+
* @param {string} [what]
|
|
202
|
+
* @returns {Tlv[]}
|
|
203
|
+
*/
|
|
204
|
+
export function children(bytes, tlv, what = tagName(tlv.cls, tlv.tag)) {
|
|
205
|
+
if (!tlv.constructed) {
|
|
206
|
+
throw parseError(tlv.start, `${what} must be constructed to have children`);
|
|
207
|
+
}
|
|
208
|
+
const out = [];
|
|
209
|
+
let p = tlv.contentStart;
|
|
210
|
+
while (p < tlv.contentEnd) {
|
|
211
|
+
const child = readTlv(bytes, p);
|
|
212
|
+
if (child.end > tlv.contentEnd) {
|
|
213
|
+
throw parseError(child.start,
|
|
214
|
+
`child overruns enclosing ${what} (ends ${child.end} > ${tlv.contentEnd})`);
|
|
215
|
+
}
|
|
216
|
+
out.push(child);
|
|
217
|
+
p = child.end;
|
|
218
|
+
}
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Read the single top-level element and reject trailing bytes after it.
|
|
224
|
+
* @param {Uint8Array} bytes
|
|
225
|
+
* @param {string} [what]
|
|
226
|
+
* @returns {Tlv}
|
|
227
|
+
*/
|
|
228
|
+
export function readAll(bytes, what = 'top-level element') {
|
|
229
|
+
const tlv = readTlv(bytes, 0);
|
|
230
|
+
if (tlv.end !== bytes.byteLength) {
|
|
231
|
+
throw parseError(tlv.end, `${bytes.byteLength - tlv.end} trailing bytes after ${what}`);
|
|
232
|
+
}
|
|
233
|
+
return tlv;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* INTEGER: returns the raw big-endian content and, when it fits a safe JS number, the value.
|
|
238
|
+
* DER minimality: the first nine bits must not be all-zero or all-one, else a shorter encoding
|
|
239
|
+
* exists. Laxness here would let two different byte strings claim the same serial number.
|
|
240
|
+
* @param {Uint8Array} bytes
|
|
241
|
+
* @param {Tlv} tlv
|
|
242
|
+
* @param {string} [what]
|
|
243
|
+
* @returns {{ bytes: Uint8Array, value: number | null, negative: boolean }} `value` is null
|
|
244
|
+
* when the magnitude does not fit a safe number (serials routinely do not)
|
|
245
|
+
*/
|
|
246
|
+
export function readInteger(bytes, tlv, what = 'INTEGER') {
|
|
247
|
+
expectTlv(tlv, { tag: TAG.INTEGER, constructed: false }, what);
|
|
248
|
+
const c = content(bytes, tlv);
|
|
249
|
+
if (c.byteLength === 0) throw parseError(tlv.start, `empty ${what}`);
|
|
250
|
+
if (c.byteLength > 1) {
|
|
251
|
+
if (c[0] === 0x00 && c[1] < 0x80) {
|
|
252
|
+
throw parseError(tlv.start, `non-minimal ${what} encoding (leading 0x00)`);
|
|
253
|
+
}
|
|
254
|
+
if (c[0] === 0xff && c[1] >= 0x80) {
|
|
255
|
+
throw parseError(tlv.start, `non-minimal ${what} encoding (leading 0xff)`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const negative = (c[0] & 0x80) !== 0;
|
|
259
|
+
let value = null;
|
|
260
|
+
if (!negative && (c.byteLength < 7 || (c.byteLength === 7 && c[0] < 0x20))) {
|
|
261
|
+
value = 0;
|
|
262
|
+
for (const b of c) value = value * 256 + b;
|
|
263
|
+
}
|
|
264
|
+
return { bytes: c, value, negative };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* OBJECT IDENTIFIER to dotted string. Sub-identifiers are base-128 and must be minimal: a leading
|
|
269
|
+
* 0x80 continuation byte is a second spelling of the same OID, and two spellings of one identity
|
|
270
|
+
* is how "unknown critical extension" checks get bypassed.
|
|
271
|
+
* @param {Uint8Array} bytes
|
|
272
|
+
* @param {Tlv} tlv
|
|
273
|
+
* @param {string} [what]
|
|
274
|
+
* @returns {string} dotted form, e.g. '2.5.29.15'
|
|
275
|
+
*/
|
|
276
|
+
export function readOid(bytes, tlv, what = 'OBJECT IDENTIFIER') {
|
|
277
|
+
expectTlv(tlv, { tag: TAG.OID, constructed: false }, what);
|
|
278
|
+
const c = content(bytes, tlv);
|
|
279
|
+
if (c.byteLength === 0) throw parseError(tlv.start, `empty ${what}`);
|
|
280
|
+
const arcs = [];
|
|
281
|
+
let value = 0;
|
|
282
|
+
let inSub = false;
|
|
283
|
+
for (let i = 0; i < c.byteLength; i++) {
|
|
284
|
+
const b = c[i];
|
|
285
|
+
if (!inSub && b === 0x80) {
|
|
286
|
+
throw parseError(tlv.contentStart + i, `non-minimal sub-identifier in ${what} (leading 0x80)`);
|
|
287
|
+
}
|
|
288
|
+
inSub = true;
|
|
289
|
+
if (value > 0x3ffffffffffff) {
|
|
290
|
+
throw parseError(tlv.contentStart + i, `sub-identifier in ${what} too large, refusing`);
|
|
291
|
+
}
|
|
292
|
+
value = value * 128 + (b & 0x7f);
|
|
293
|
+
if ((b & 0x80) === 0) {
|
|
294
|
+
arcs.push(value);
|
|
295
|
+
value = 0;
|
|
296
|
+
inSub = false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (inSub) throw parseError(tlv.end, `truncated sub-identifier in ${what}`);
|
|
300
|
+
const first = arcs[0];
|
|
301
|
+
// The first two arcs share one sub-identifier: 40*X+Y, with X capped at 2.
|
|
302
|
+
const x = first < 40 ? 0 : first < 80 ? 1 : 2;
|
|
303
|
+
const y = first - x * 40;
|
|
304
|
+
return [x, y, ...arcs.slice(1)].join('.');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* BIT STRING: unused-bit count + payload. DER additionally requires the unused bits themselves to
|
|
309
|
+
* be zero — a nonzero padding bit is another two-spellings ambiguity.
|
|
310
|
+
* @param {Uint8Array} bytes
|
|
311
|
+
* @param {Tlv} tlv
|
|
312
|
+
* @param {string} [what]
|
|
313
|
+
* @returns {{ unusedBits: number, bytes: Uint8Array }}
|
|
314
|
+
*/
|
|
315
|
+
export function readBitString(bytes, tlv, what = 'BIT STRING') {
|
|
316
|
+
expectTlv(tlv, { tag: TAG.BIT_STRING, constructed: false }, what);
|
|
317
|
+
const c = content(bytes, tlv);
|
|
318
|
+
if (c.byteLength === 0) throw parseError(tlv.start, `empty ${what} (missing unused-bits byte)`);
|
|
319
|
+
const unusedBits = c[0];
|
|
320
|
+
if (unusedBits > 7) {
|
|
321
|
+
throw parseError(tlv.contentStart, `${what} unused-bits ${unusedBits} > 7`);
|
|
322
|
+
}
|
|
323
|
+
if (c.byteLength === 1 && unusedBits !== 0) {
|
|
324
|
+
throw parseError(tlv.contentStart, `${what} with no payload must have 0 unused bits`);
|
|
325
|
+
}
|
|
326
|
+
if (unusedBits > 0 && (c[c.byteLength - 1] & ((1 << unusedBits) - 1)) !== 0) {
|
|
327
|
+
throw parseError(tlv.end - 1, `${what} padding bits are not zero`);
|
|
328
|
+
}
|
|
329
|
+
return { unusedBits, bytes: c.subarray(1) };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* BOOLEAN. DER: content is exactly one byte, 0x00 or 0xff.
|
|
334
|
+
* @param {Uint8Array} bytes
|
|
335
|
+
* @param {Tlv} tlv
|
|
336
|
+
* @param {string} [what]
|
|
337
|
+
* @returns {boolean}
|
|
338
|
+
*/
|
|
339
|
+
export function readBoolean(bytes, tlv, what = 'BOOLEAN') {
|
|
340
|
+
expectTlv(tlv, { tag: TAG.BOOLEAN, constructed: false }, what);
|
|
341
|
+
const c = content(bytes, tlv);
|
|
342
|
+
if (c.byteLength !== 1) throw parseError(tlv.start, `${what} must be 1 byte, got ${c.byteLength}`);
|
|
343
|
+
if (c[0] !== 0x00 && c[0] !== 0xff) {
|
|
344
|
+
throw parseError(tlv.contentStart, `${what} must be 0x00 or 0xff in DER, got 0x${c[0].toString(16)}`);
|
|
345
|
+
}
|
|
346
|
+
return c[0] === 0xff;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const isDigit = (b) => b >= 0x30 && b <= 0x39;
|
|
350
|
+
|
|
351
|
+
function timeError(tlv, kind, text) {
|
|
352
|
+
return parseError(tlv.start, `malformed ${kind} "${text}"`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Days per month, with February resolved against the Gregorian leap rule. */
|
|
356
|
+
function daysInMonth(y, m) {
|
|
357
|
+
if (m === 2) return y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0) ? 29 : 28;
|
|
358
|
+
return [31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][m - 1];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function toEpochMs(tlv, kind, text, y, mo, d, h, mi, s) {
|
|
362
|
+
if (mo < 1 || mo > 12) throw timeError(tlv, kind, text);
|
|
363
|
+
if (d < 1 || d > daysInMonth(y, mo)) throw timeError(tlv, kind, text);
|
|
364
|
+
// 60 would be a leap second; Date.UTC silently rolls it into the next minute, which is a
|
|
365
|
+
// different instant than encoded. RFC 5280 profiles do not emit leap seconds, so reject.
|
|
366
|
+
if (h > 23 || mi > 59 || s > 59) throw timeError(tlv, kind, text);
|
|
367
|
+
return Date.UTC(y, mo - 1, d, h, mi, s);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* UTCTime, RFC 5280 profile: exactly YYMMDDHHMMSSZ. Seconds are mandatory, the zone is mandatory
|
|
372
|
+
* and must be Z, and fractional seconds do not exist in this profile. The two-digit year pivots
|
|
373
|
+
* at 50: 50..99 map to 19xx, 00..49 to 20xx (RFC 5280 s4.1.2.5.1).
|
|
374
|
+
* @param {Uint8Array} bytes
|
|
375
|
+
* @param {Tlv} tlv
|
|
376
|
+
* @param {string} [what]
|
|
377
|
+
* @returns {number} epoch ms UTC
|
|
378
|
+
*/
|
|
379
|
+
export function readUtcTime(bytes, tlv, what = 'UTCTime') {
|
|
380
|
+
expectTlv(tlv, { tag: TAG.UTC_TIME, constructed: false }, what);
|
|
381
|
+
const c = content(bytes, tlv);
|
|
382
|
+
let text = '';
|
|
383
|
+
for (const b of c) text += String.fromCharCode(b);
|
|
384
|
+
if (c.byteLength !== 13 || c[12] !== 0x5a /* Z */) throw timeError(tlv, what, text);
|
|
385
|
+
for (let i = 0; i < 12; i++) if (!isDigit(c[i])) throw timeError(tlv, what, text);
|
|
386
|
+
const n = (i) => (c[i] - 0x30) * 10 + (c[i + 1] - 0x30);
|
|
387
|
+
const yy = n(0);
|
|
388
|
+
const year = yy >= 50 ? 1900 + yy : 2000 + yy;
|
|
389
|
+
return toEpochMs(tlv, what, text, year, n(2), n(4), n(6), n(8), n(10));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* GeneralizedTime, RFC 5280 profile: exactly YYYYMMDDHHMMSSZ. Fractional seconds are explicitly
|
|
394
|
+
* forbidden by RFC 5280 s4.1.2.5.2, and allowing them would give one instant many encodings.
|
|
395
|
+
* @param {Uint8Array} bytes
|
|
396
|
+
* @param {Tlv} tlv
|
|
397
|
+
* @param {string} [what]
|
|
398
|
+
* @returns {number} epoch ms UTC
|
|
399
|
+
*/
|
|
400
|
+
export function readGeneralizedTime(bytes, tlv, what = 'GeneralizedTime') {
|
|
401
|
+
expectTlv(tlv, { tag: TAG.GENERALIZED_TIME, constructed: false }, what);
|
|
402
|
+
const c = content(bytes, tlv);
|
|
403
|
+
let text = '';
|
|
404
|
+
for (const b of c) text += String.fromCharCode(b);
|
|
405
|
+
if (c.byteLength !== 15 || c[14] !== 0x5a /* Z */) throw timeError(tlv, what, text);
|
|
406
|
+
for (let i = 0; i < 14; i++) if (!isDigit(c[i])) throw timeError(tlv, what, text);
|
|
407
|
+
const n = (i) => (c[i] - 0x30) * 10 + (c[i + 1] - 0x30);
|
|
408
|
+
const year = n(0) * 100 + n(2);
|
|
409
|
+
return toEpochMs(tlv, what, text, year, n(4), n(6), n(8), n(10), n(12));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Either time type, as used by Validity and by name-constraint-free consumers.
|
|
414
|
+
* @param {Uint8Array} bytes
|
|
415
|
+
* @param {Tlv} tlv
|
|
416
|
+
* @param {string} [what]
|
|
417
|
+
* @returns {number} epoch ms UTC
|
|
418
|
+
*/
|
|
419
|
+
export function readTime(bytes, tlv, what = 'Time') {
|
|
420
|
+
if (tlv.tag === TAG.UTC_TIME) return readUtcTime(bytes, tlv, what);
|
|
421
|
+
if (tlv.tag === TAG.GENERALIZED_TIME) return readGeneralizedTime(bytes, tlv, what);
|
|
422
|
+
throw parseError(tlv.start,
|
|
423
|
+
`expected UTCTime or GeneralizedTime for ${what}, got ${tagName(tlv.cls, tlv.tag)}`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const utf8Decoder = new TextDecoder('utf-8', { fatal: true });
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Directory string types. Each type's alphabet is enforced — a PrintableString smuggling bytes
|
|
430
|
+
* outside its charset is two parsers disagreeing about one name.
|
|
431
|
+
*
|
|
432
|
+
* TeletexString is decoded as Latin-1: its real charset (T.61) is a negotiation-dependent mess
|
|
433
|
+
* that no CA has honoured in decades, and Latin-1 is the universal de-facto reading.
|
|
434
|
+
* @param {Uint8Array} bytes
|
|
435
|
+
* @param {Tlv} tlv
|
|
436
|
+
* @param {string} [what]
|
|
437
|
+
* @returns {string}
|
|
438
|
+
*/
|
|
439
|
+
export function readString(bytes, tlv, what = 'string') {
|
|
440
|
+
const c = content(bytes, tlv);
|
|
441
|
+
const at = (i) => tlv.contentStart + i;
|
|
442
|
+
switch (tlv.tag) {
|
|
443
|
+
case TAG.UTF8_STRING: {
|
|
444
|
+
try {
|
|
445
|
+
return utf8Decoder.decode(c);
|
|
446
|
+
} catch {
|
|
447
|
+
throw parseError(tlv.start, `invalid UTF-8 in ${what}`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
case TAG.PRINTABLE_STRING: {
|
|
451
|
+
let s = '';
|
|
452
|
+
for (let i = 0; i < c.byteLength; i++) {
|
|
453
|
+
const b = c[i];
|
|
454
|
+
const ok =
|
|
455
|
+
(b >= 0x41 && b <= 0x5a) || (b >= 0x61 && b <= 0x7a) || (b >= 0x30 && b <= 0x39) ||
|
|
456
|
+
b === 0x20 || (b >= 0x27 && b <= 0x2f && b !== 0x2a) || b === 0x3a || b === 0x3d || b === 0x3f;
|
|
457
|
+
if (!ok) {
|
|
458
|
+
throw parseError(at(i), `byte 0x${b.toString(16)} outside PrintableString alphabet in ${what}`);
|
|
459
|
+
}
|
|
460
|
+
s += String.fromCharCode(b);
|
|
461
|
+
}
|
|
462
|
+
return s;
|
|
463
|
+
}
|
|
464
|
+
case TAG.IA5_STRING:
|
|
465
|
+
case TAG.VISIBLE_STRING:
|
|
466
|
+
case TAG.NUMERIC_STRING: {
|
|
467
|
+
let s = '';
|
|
468
|
+
for (let i = 0; i < c.byteLength; i++) {
|
|
469
|
+
const b = c[i];
|
|
470
|
+
if (b > 0x7f) throw parseError(at(i), `non-ASCII byte 0x${b.toString(16)} in ${what}`);
|
|
471
|
+
if (tlv.tag === TAG.NUMERIC_STRING && !(isDigit(b) || b === 0x20)) {
|
|
472
|
+
throw parseError(at(i), `byte 0x${b.toString(16)} outside NumericString alphabet in ${what}`);
|
|
473
|
+
}
|
|
474
|
+
s += String.fromCharCode(b);
|
|
475
|
+
}
|
|
476
|
+
return s;
|
|
477
|
+
}
|
|
478
|
+
case TAG.TELETEX_STRING: {
|
|
479
|
+
let s = '';
|
|
480
|
+
for (const b of c) s += String.fromCharCode(b);
|
|
481
|
+
return s;
|
|
482
|
+
}
|
|
483
|
+
case TAG.BMP_STRING: {
|
|
484
|
+
if (c.byteLength % 2 !== 0) throw parseError(tlv.start, `odd-length BMPString in ${what}`);
|
|
485
|
+
let s = '';
|
|
486
|
+
for (let i = 0; i < c.byteLength; i += 2) {
|
|
487
|
+
const unit = (c[i] << 8) | c[i + 1];
|
|
488
|
+
// BMPString is UCS-2: surrogate code units have no meaning and are rejected rather than
|
|
489
|
+
// passed through where they could re-pair into unexpected characters downstream.
|
|
490
|
+
if (unit >= 0xd800 && unit <= 0xdfff) {
|
|
491
|
+
throw parseError(at(i), `surrogate code unit in BMPString ${what}`);
|
|
492
|
+
}
|
|
493
|
+
s += String.fromCharCode(unit);
|
|
494
|
+
}
|
|
495
|
+
return s;
|
|
496
|
+
}
|
|
497
|
+
default:
|
|
498
|
+
throw parseError(tlv.start, `unsupported string type ${tagName(tlv.cls, tlv.tag)} for ${what}`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* ECDSA-Sig-Value (SEQUENCE of two INTEGERs) to the fixed-width r||s form WebCrypto verifies.
|
|
504
|
+
*
|
|
505
|
+
* This lives here, once, because both users of it are checking signatures: the certificate path
|
|
506
|
+
* builder and the TLS CertificateVerify. TLS transmits ECDSA signatures in DER while WebCrypto
|
|
507
|
+
* accepts only the P1363 concatenation, and a round-trip test that signs and verifies with
|
|
508
|
+
* WebCrypto agrees with itself while failing against every real server — so this conversion is
|
|
509
|
+
* exactly the sort of thing that must have one implementation and not two.
|
|
510
|
+
*
|
|
511
|
+
* Any malformation is an invalid signature; there is no "close enough" for signature bytes.
|
|
512
|
+
*
|
|
513
|
+
* @param {Uint8Array} sig DER ECDSA-Sig-Value
|
|
514
|
+
* @param {number} orderLen byte width of the curve order (32 / 48 / 66)
|
|
515
|
+
* @param {(why: string) => Error} onInvalid builds the caller's own error type
|
|
516
|
+
* @returns {Uint8Array} r||s, each half left-padded to orderLen
|
|
517
|
+
*/
|
|
518
|
+
export function ecdsaDerToRaw(sig, orderLen, onInvalid) {
|
|
519
|
+
// Every failure below, DER-level or structural, is reported through the caller's error factory.
|
|
520
|
+
// Letting a CERT_PARSE escape from here would put a certificate-shaped error in front of a
|
|
521
|
+
// caller checking a TLS handshake signature — the wrong taxonomy for the wrong layer.
|
|
522
|
+
let kids;
|
|
523
|
+
try {
|
|
524
|
+
const seq = readAll(sig, 'ECDSA-Sig-Value');
|
|
525
|
+
expectTlv(seq, { tag: TAG.SEQUENCE, constructed: true }, 'ECDSA-Sig-Value');
|
|
526
|
+
kids = children(sig, seq, 'ECDSA-Sig-Value');
|
|
527
|
+
} catch (e) {
|
|
528
|
+
throw onInvalid(e?.message ?? String(e));
|
|
529
|
+
}
|
|
530
|
+
if (kids.length !== 2) throw onInvalid(`expected { r, s }, got ${kids.length} fields`);
|
|
531
|
+
|
|
532
|
+
let r;
|
|
533
|
+
let s;
|
|
534
|
+
try {
|
|
535
|
+
r = readInteger(sig, kids[0], 'r');
|
|
536
|
+
s = readInteger(sig, kids[1], 's');
|
|
537
|
+
} catch (e) {
|
|
538
|
+
throw onInvalid(e?.message ?? String(e));
|
|
539
|
+
}
|
|
540
|
+
const out = new Uint8Array(orderLen * 2);
|
|
541
|
+
for (const [i, part] of [r, s].entries()) {
|
|
542
|
+
const label = i === 0 ? 'r' : 's';
|
|
543
|
+
if (part.negative) throw onInvalid(`${label} is negative`);
|
|
544
|
+
// readInteger guarantees minimal form, so at most one leading zero (the sign byte) remains.
|
|
545
|
+
let b = part.bytes;
|
|
546
|
+
if (b.length > 1 && b[0] === 0x00) b = b.subarray(1);
|
|
547
|
+
if (b.length > orderLen) throw onInvalid(`${label} is wider than the curve order`);
|
|
548
|
+
out.set(b, orderLen * (i + 1) - b.length);
|
|
549
|
+
}
|
|
550
|
+
return out;
|
|
551
|
+
}
|