nlcurl 0.6.0 → 0.7.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/dist/cache/store.d.ts +89 -0
- package/dist/cache/store.d.ts.map +1 -0
- package/dist/cache/store.js +402 -0
- package/dist/cache/store.js.map +1 -0
- package/dist/cache/types.d.ts +101 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +2 -0
- package/dist/cache/types.js.map +1 -0
- package/dist/cookies/jar.d.ts +11 -0
- package/dist/cookies/jar.d.ts.map +1 -1
- package/dist/cookies/jar.js +50 -4
- package/dist/cookies/jar.js.map +1 -1
- package/dist/cookies/parser.d.ts +2 -0
- package/dist/cookies/parser.d.ts.map +1 -1
- package/dist/cookies/parser.js +14 -0
- package/dist/cookies/parser.js.map +1 -1
- package/dist/cookies/psl-data.d.ts +1 -1
- package/dist/cookies/psl-data.js +1 -1
- package/dist/core/client.js +3 -0
- package/dist/core/client.js.map +1 -1
- package/dist/core/request.d.ts +68 -2
- package/dist/core/request.d.ts.map +1 -1
- package/dist/core/response.d.ts +22 -0
- package/dist/core/response.d.ts.map +1 -1
- package/dist/core/response.js +32 -0
- package/dist/core/response.js.map +1 -1
- package/dist/core/session.d.ts +23 -0
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js +101 -3
- package/dist/core/session.js.map +1 -1
- package/dist/core/validation.d.ts +2 -1
- package/dist/core/validation.d.ts.map +1 -1
- package/dist/core/validation.js +5 -4
- package/dist/core/validation.js.map +1 -1
- package/dist/dns/codec.d.ts +37 -0
- package/dist/dns/codec.d.ts.map +1 -0
- package/dist/dns/codec.js +254 -0
- package/dist/dns/codec.js.map +1 -0
- package/dist/dns/doh-resolver.d.ts +52 -0
- package/dist/dns/doh-resolver.d.ts.map +1 -0
- package/dist/dns/doh-resolver.js +192 -0
- package/dist/dns/doh-resolver.js.map +1 -0
- package/dist/dns/https-rr.d.ts +51 -0
- package/dist/dns/https-rr.d.ts.map +1 -0
- package/dist/dns/https-rr.js +127 -0
- package/dist/dns/https-rr.js.map +1 -0
- package/dist/dns/types.d.ts +110 -0
- package/dist/dns/types.d.ts.map +1 -0
- package/dist/dns/types.js +34 -0
- package/dist/dns/types.js.map +1 -0
- package/dist/hsts/store.d.ts +41 -0
- package/dist/hsts/store.d.ts.map +1 -0
- package/dist/hsts/store.js +171 -0
- package/dist/hsts/store.js.map +1 -0
- package/dist/hsts/types.d.ts +28 -0
- package/dist/hsts/types.d.ts.map +1 -0
- package/dist/hsts/types.js +2 -0
- package/dist/hsts/types.js.map +1 -0
- package/dist/http/alt-svc.d.ts +85 -0
- package/dist/http/alt-svc.d.ts.map +1 -0
- package/dist/http/alt-svc.js +220 -0
- package/dist/http/alt-svc.js.map +1 -0
- package/dist/http/h1/client.d.ts.map +1 -1
- package/dist/http/h1/client.js +16 -0
- package/dist/http/h1/client.js.map +1 -1
- package/dist/http/h3/detection.d.ts +17 -0
- package/dist/http/h3/detection.d.ts.map +1 -0
- package/dist/http/h3/detection.js +59 -0
- package/dist/http/h3/detection.js.map +1 -0
- package/dist/http/negotiator.d.ts +24 -1
- package/dist/http/negotiator.d.ts.map +1 -1
- package/dist/http/negotiator.js +88 -18
- package/dist/http/negotiator.js.map +1 -1
- package/dist/http/pool.d.ts +2 -2
- package/dist/http/pool.d.ts.map +1 -1
- package/dist/http/pool.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/middleware/rate-limiter.d.ts.map +1 -1
- package/dist/middleware/rate-limiter.js +4 -0
- package/dist/middleware/rate-limiter.js.map +1 -1
- package/dist/proxy/env-proxy.d.ts +21 -0
- package/dist/proxy/env-proxy.d.ts.map +1 -0
- package/dist/proxy/env-proxy.js +74 -0
- package/dist/proxy/env-proxy.js.map +1 -0
- package/dist/proxy/http-proxy.d.ts +2 -0
- package/dist/proxy/http-proxy.d.ts.map +1 -1
- package/dist/proxy/http-proxy.js +19 -6
- package/dist/proxy/http-proxy.js.map +1 -1
- package/dist/proxy/socks.js +1 -1
- package/dist/proxy/socks.js.map +1 -1
- package/dist/sse/parser.d.ts +70 -0
- package/dist/sse/parser.d.ts.map +1 -0
- package/dist/sse/parser.js +153 -0
- package/dist/sse/parser.js.map +1 -0
- package/dist/tls/ech.d.ts +147 -0
- package/dist/tls/ech.d.ts.map +1 -0
- package/dist/tls/ech.js +401 -0
- package/dist/tls/ech.js.map +1 -0
- package/dist/tls/node-engine.d.ts +9 -1
- package/dist/tls/node-engine.d.ts.map +1 -1
- package/dist/tls/node-engine.js +39 -1
- package/dist/tls/node-engine.js.map +1 -1
- package/dist/tls/pin-verification.d.ts +9 -0
- package/dist/tls/pin-verification.d.ts.map +1 -0
- package/dist/tls/pin-verification.js +34 -0
- package/dist/tls/pin-verification.js.map +1 -0
- package/dist/tls/session-cache.d.ts +70 -0
- package/dist/tls/session-cache.d.ts.map +1 -0
- package/dist/tls/session-cache.js +80 -0
- package/dist/tls/session-cache.js.map +1 -0
- package/dist/tls/stealth/client-hello.d.ts +21 -0
- package/dist/tls/stealth/client-hello.d.ts.map +1 -1
- package/dist/tls/stealth/client-hello.js +116 -0
- package/dist/tls/stealth/client-hello.js.map +1 -1
- package/dist/tls/stealth/engine.d.ts.map +1 -1
- package/dist/tls/stealth/engine.js +152 -30
- package/dist/tls/stealth/engine.js.map +1 -1
- package/dist/tls/stealth/handshake.d.ts +2 -1
- package/dist/tls/stealth/handshake.d.ts.map +1 -1
- package/dist/tls/stealth/handshake.js +118 -5
- package/dist/tls/stealth/handshake.js.map +1 -1
- package/dist/tls/stealth/tls12-handshake.d.ts +14 -0
- package/dist/tls/stealth/tls12-handshake.d.ts.map +1 -0
- package/dist/tls/stealth/tls12-handshake.js +462 -0
- package/dist/tls/stealth/tls12-handshake.js.map +1 -0
- package/dist/tls/types.d.ts +16 -0
- package/dist/tls/types.d.ts.map +1 -1
- package/dist/utils/encoding.d.ts +8 -6
- package/dist/utils/encoding.d.ts.map +1 -1
- package/dist/utils/encoding.js +92 -24
- package/dist/utils/encoding.js.map +1 -1
- package/dist/utils/happy-eyeballs.d.ts +3 -0
- package/dist/utils/happy-eyeballs.d.ts.map +1 -1
- package/dist/utils/happy-eyeballs.js +42 -2
- package/dist/utils/happy-eyeballs.js.map +1 -1
- package/dist/ws/client.d.ts +3 -0
- package/dist/ws/client.d.ts.map +1 -1
- package/dist/ws/client.js +63 -7
- package/dist/ws/client.js.map +1 -1
- package/dist/ws/frame.d.ts +4 -2
- package/dist/ws/frame.d.ts.map +1 -1
- package/dist/ws/frame.js +8 -5
- package/dist/ws/frame.js.map +1 -1
- package/dist/ws/permessage-deflate.d.ts +58 -0
- package/dist/ws/permessage-deflate.d.ts.map +1 -0
- package/dist/ws/permessage-deflate.js +148 -0
- package/dist/ws/permessage-deflate.js.map +1 -0
- package/package.json +2 -2
package/dist/tls/ech.js
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encrypted Client Hello (ECH) support per RFC 9735.
|
|
3
|
+
*
|
|
4
|
+
* ECH encrypts the SNI (Server Name Indication) and other sensitive
|
|
5
|
+
* fields in the TLS ClientHello to prevent passive observers from
|
|
6
|
+
* learning which domain the client is connecting to.
|
|
7
|
+
*
|
|
8
|
+
* This module provides:
|
|
9
|
+
* 1. ECHConfig parsing from HTTPS-RR DNS records
|
|
10
|
+
* 2. ECH configuration for Node.js TLS engine (Node.js 22+)
|
|
11
|
+
* 3. HPKE (RFC 9180) encryption of the inner ClientHello
|
|
12
|
+
* 4. ECH parameters for the stealth TLS engine's ClientHello builder
|
|
13
|
+
*
|
|
14
|
+
* @see https://datatracker.ietf.org/doc/rfc9735/
|
|
15
|
+
* @see https://datatracker.ietf.org/doc/rfc9180/
|
|
16
|
+
*/
|
|
17
|
+
import { randomBytes, createHmac, createCipheriv, generateKeyPairSync, diffieHellman, createPrivateKey, createPublicKey } from "node:crypto";
|
|
18
|
+
/**
|
|
19
|
+
* Parses an ECHConfigList from raw bytes (as received from HTTPS-RR or config).
|
|
20
|
+
*
|
|
21
|
+
* ECHConfigList format (draft-ietf-tls-esni §4):
|
|
22
|
+
* ```
|
|
23
|
+
* struct {
|
|
24
|
+
* uint16 length;
|
|
25
|
+
* ECHConfig echConfigs<1..2^16-1>;
|
|
26
|
+
* } ECHConfigList;
|
|
27
|
+
*
|
|
28
|
+
* struct {
|
|
29
|
+
* uint16 version;
|
|
30
|
+
* uint16 length;
|
|
31
|
+
* opaque contents<1..2^16-1>;
|
|
32
|
+
* } ECHConfig;
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @param data Raw ECHConfigList bytes.
|
|
36
|
+
* @returns Parsed ECH parameters, or null if invalid.
|
|
37
|
+
*/
|
|
38
|
+
export function parseECHConfigList(data) {
|
|
39
|
+
if (data.length < 4)
|
|
40
|
+
return null;
|
|
41
|
+
const totalLength = data.readUInt16BE(0);
|
|
42
|
+
if (totalLength + 2 > data.length)
|
|
43
|
+
return null;
|
|
44
|
+
const configs = [];
|
|
45
|
+
let offset = 2;
|
|
46
|
+
while (offset + 4 <= 2 + totalLength) {
|
|
47
|
+
const version = data.readUInt16BE(offset);
|
|
48
|
+
offset += 2;
|
|
49
|
+
const configLength = data.readUInt16BE(offset);
|
|
50
|
+
offset += 2;
|
|
51
|
+
if (offset + configLength > 2 + totalLength)
|
|
52
|
+
break;
|
|
53
|
+
const contents = data.subarray(offset, offset + configLength);
|
|
54
|
+
const publicName = extractPublicName(contents);
|
|
55
|
+
configs.push({
|
|
56
|
+
version,
|
|
57
|
+
length: configLength,
|
|
58
|
+
contents,
|
|
59
|
+
publicName,
|
|
60
|
+
});
|
|
61
|
+
offset += configLength;
|
|
62
|
+
}
|
|
63
|
+
if (configs.length === 0)
|
|
64
|
+
return null;
|
|
65
|
+
const outerSNI = configs[0].publicName;
|
|
66
|
+
return {
|
|
67
|
+
echConfigList: data,
|
|
68
|
+
outerSNI,
|
|
69
|
+
configs,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Extracts the public_name from ECHConfig contents.
|
|
74
|
+
*
|
|
75
|
+
* The contents layout (draft-ietf-tls-esni §4):
|
|
76
|
+
* ```
|
|
77
|
+
* struct {
|
|
78
|
+
* HpkeKeyConfig key_config;
|
|
79
|
+
* uint8 maximum_name_length;
|
|
80
|
+
* opaque public_name<1..255>;
|
|
81
|
+
* Extension extensions<0..2^16-1>;
|
|
82
|
+
* } ECHConfigContents;
|
|
83
|
+
*
|
|
84
|
+
* struct {
|
|
85
|
+
* uint8 config_id;
|
|
86
|
+
* uint16 kem_id;
|
|
87
|
+
* opaque public_key<1..2^16-1>;
|
|
88
|
+
* HpkeSymmetricCipherSuite cipher_suites<4..2^16-4>;
|
|
89
|
+
* } HpkeKeyConfig;
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
function extractPublicName(contents) {
|
|
93
|
+
if (contents.length < 7)
|
|
94
|
+
return "";
|
|
95
|
+
let offset = 0;
|
|
96
|
+
offset += 1;
|
|
97
|
+
offset += 2;
|
|
98
|
+
if (offset + 2 > contents.length)
|
|
99
|
+
return "";
|
|
100
|
+
const pkLen = contents.readUInt16BE(offset);
|
|
101
|
+
offset += 2 + pkLen;
|
|
102
|
+
if (offset + 2 > contents.length)
|
|
103
|
+
return "";
|
|
104
|
+
const csLen = contents.readUInt16BE(offset);
|
|
105
|
+
offset += 2 + csLen;
|
|
106
|
+
if (offset >= contents.length)
|
|
107
|
+
return "";
|
|
108
|
+
offset += 1;
|
|
109
|
+
if (offset >= contents.length)
|
|
110
|
+
return "";
|
|
111
|
+
const nameLen = contents[offset];
|
|
112
|
+
offset += 1;
|
|
113
|
+
if (offset + nameLen > contents.length)
|
|
114
|
+
return "";
|
|
115
|
+
return contents.subarray(offset, offset + nameLen).toString("ascii");
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Generates a GREASE ECH extension for the outer ClientHello.
|
|
119
|
+
* This makes ECH-capable clients indistinguishable from non-ECH clients
|
|
120
|
+
* by including a random ECH extension even when no real config is available.
|
|
121
|
+
*
|
|
122
|
+
* @returns A plausible-looking random ECH extension payload.
|
|
123
|
+
*/
|
|
124
|
+
export function generateGreaseECH() {
|
|
125
|
+
const payloadLen = 128 + Math.floor(Math.random() * 64);
|
|
126
|
+
const buf = Buffer.alloc(1 + 4 + 1 + 2 + 32 + 2 + payloadLen);
|
|
127
|
+
let offset = 0;
|
|
128
|
+
buf[offset++] = 0x00;
|
|
129
|
+
buf.writeUInt16BE(0x0001, offset);
|
|
130
|
+
offset += 2;
|
|
131
|
+
buf.writeUInt16BE(0x0001, offset);
|
|
132
|
+
offset += 2;
|
|
133
|
+
buf[offset++] = randomBytes(1)[0];
|
|
134
|
+
const enc = randomBytes(32);
|
|
135
|
+
buf.writeUInt16BE(32, offset);
|
|
136
|
+
offset += 2;
|
|
137
|
+
enc.copy(buf, offset);
|
|
138
|
+
offset += 32;
|
|
139
|
+
const payload = randomBytes(payloadLen);
|
|
140
|
+
buf.writeUInt16BE(payloadLen, offset);
|
|
141
|
+
offset += 2;
|
|
142
|
+
payload.copy(buf, offset);
|
|
143
|
+
return buf;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Parses the HpkeKeyConfig from ECHConfig contents.
|
|
147
|
+
*
|
|
148
|
+
* HpkeKeyConfig layout:
|
|
149
|
+
* ```
|
|
150
|
+
* struct {
|
|
151
|
+
* uint8 config_id;
|
|
152
|
+
* uint16 kem_id;
|
|
153
|
+
* opaque public_key<1..2^16-1>;
|
|
154
|
+
* HpkeSymmetricCipherSuite cipher_suites<4..2^16-4>;
|
|
155
|
+
* } HpkeKeyConfig;
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export function parseHpkeKeyConfig(contents) {
|
|
159
|
+
if (contents.length < 7)
|
|
160
|
+
return null;
|
|
161
|
+
let offset = 0;
|
|
162
|
+
const configId = contents[offset];
|
|
163
|
+
offset += 1;
|
|
164
|
+
const kemId = contents.readUInt16BE(offset);
|
|
165
|
+
offset += 2;
|
|
166
|
+
if (offset + 2 > contents.length)
|
|
167
|
+
return null;
|
|
168
|
+
const pkLen = contents.readUInt16BE(offset);
|
|
169
|
+
offset += 2;
|
|
170
|
+
if (offset + pkLen > contents.length)
|
|
171
|
+
return null;
|
|
172
|
+
const publicKey = Buffer.from(contents.subarray(offset, offset + pkLen));
|
|
173
|
+
offset += pkLen;
|
|
174
|
+
if (offset + 2 > contents.length)
|
|
175
|
+
return null;
|
|
176
|
+
const csLen = contents.readUInt16BE(offset);
|
|
177
|
+
offset += 2;
|
|
178
|
+
const cipherSuites = [];
|
|
179
|
+
const csEnd = offset + csLen;
|
|
180
|
+
while (offset + 4 <= csEnd) {
|
|
181
|
+
const kdfId = contents.readUInt16BE(offset);
|
|
182
|
+
offset += 2;
|
|
183
|
+
const aeadId = contents.readUInt16BE(offset);
|
|
184
|
+
offset += 2;
|
|
185
|
+
cipherSuites.push({ kdfId, aeadId });
|
|
186
|
+
}
|
|
187
|
+
return { configId, kemId, publicKey, cipherSuites };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Extracts the `maximum_name_length` field from ECHConfig contents.
|
|
191
|
+
* This value determines how much padding is added to the inner ClientHello.
|
|
192
|
+
*/
|
|
193
|
+
export function getMaxNameLength(contents) {
|
|
194
|
+
if (contents.length < 7)
|
|
195
|
+
return 0;
|
|
196
|
+
let offset = 0;
|
|
197
|
+
offset += 1;
|
|
198
|
+
offset += 2;
|
|
199
|
+
if (offset + 2 > contents.length)
|
|
200
|
+
return 0;
|
|
201
|
+
const pkLen = contents.readUInt16BE(offset);
|
|
202
|
+
offset += 2 + pkLen;
|
|
203
|
+
if (offset + 2 > contents.length)
|
|
204
|
+
return 0;
|
|
205
|
+
const csLen = contents.readUInt16BE(offset);
|
|
206
|
+
offset += 2 + csLen;
|
|
207
|
+
if (offset >= contents.length)
|
|
208
|
+
return 0;
|
|
209
|
+
return contents[offset];
|
|
210
|
+
}
|
|
211
|
+
function hpkeHkdfExtract(salt, ikm) {
|
|
212
|
+
const s = salt.length === 0 ? Buffer.alloc(32) : salt;
|
|
213
|
+
return Buffer.from(createHmac("sha256", s).update(ikm).digest());
|
|
214
|
+
}
|
|
215
|
+
function hpkeHkdfExpand(prk, info, length) {
|
|
216
|
+
const N = Math.ceil(length / 32);
|
|
217
|
+
const okm = Buffer.alloc(N * 32);
|
|
218
|
+
let T = Buffer.alloc(0);
|
|
219
|
+
for (let i = 1; i <= N; i++) {
|
|
220
|
+
const hmac = createHmac("sha256", prk);
|
|
221
|
+
hmac.update(T);
|
|
222
|
+
hmac.update(info);
|
|
223
|
+
hmac.update(Buffer.from([i]));
|
|
224
|
+
T = Buffer.from(hmac.digest());
|
|
225
|
+
T.copy(okm, (i - 1) * 32);
|
|
226
|
+
}
|
|
227
|
+
return okm.subarray(0, length);
|
|
228
|
+
}
|
|
229
|
+
function labeledExtract(suiteId, salt, label, ikm) {
|
|
230
|
+
const hpkeV1 = Buffer.from("HPKE-v1", "ascii");
|
|
231
|
+
const labelBuf = Buffer.from(label, "ascii");
|
|
232
|
+
const labeledIkm = Buffer.concat([hpkeV1, suiteId, labelBuf, ikm]);
|
|
233
|
+
return hpkeHkdfExtract(salt, labeledIkm);
|
|
234
|
+
}
|
|
235
|
+
function labeledExpand(suiteId, prk, label, info, length) {
|
|
236
|
+
const hpkeV1 = Buffer.from("HPKE-v1", "ascii");
|
|
237
|
+
const labelBuf = Buffer.from(label, "ascii");
|
|
238
|
+
const lenBuf = Buffer.alloc(2);
|
|
239
|
+
lenBuf.writeUInt16BE(length);
|
|
240
|
+
const labeledInfo = Buffer.concat([lenBuf, hpkeV1, suiteId, labelBuf, info]);
|
|
241
|
+
return hpkeHkdfExpand(prk, labeledInfo, length);
|
|
242
|
+
}
|
|
243
|
+
function buildX25519PKCS8(raw) {
|
|
244
|
+
return Buffer.concat([Buffer.from([0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x04, 0x22, 0x04, 0x20]), raw]);
|
|
245
|
+
}
|
|
246
|
+
function buildX25519SPKI(raw) {
|
|
247
|
+
return Buffer.concat([Buffer.from([0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00]), raw]);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* DHKEM(X25519, HKDF-SHA256) Encapsulate (RFC 9180 §4.1).
|
|
251
|
+
* Generates an ephemeral X25519 key pair, performs DH with the recipient's
|
|
252
|
+
* public key, and derives the shared secret.
|
|
253
|
+
*/
|
|
254
|
+
function dhkemX25519Encap(pkR) {
|
|
255
|
+
const kp = generateKeyPairSync("x25519");
|
|
256
|
+
const pkEDer = Buffer.from(kp.publicKey.export({ type: "spki", format: "der" }));
|
|
257
|
+
const skEDer = Buffer.from(kp.privateKey.export({ type: "pkcs8", format: "der" }));
|
|
258
|
+
const pkE = Buffer.from(pkEDer.subarray(pkEDer.length - 32));
|
|
259
|
+
const skE = Buffer.from(skEDer.subarray(skEDer.length - 32));
|
|
260
|
+
const privKey = createPrivateKey({ key: buildX25519PKCS8(skE), format: "der", type: "pkcs8" });
|
|
261
|
+
const pubKey = createPublicKey({ key: buildX25519SPKI(pkR), format: "der", type: "spki" });
|
|
262
|
+
const dh = Buffer.from(diffieHellman({ privateKey: privKey, publicKey: pubKey }));
|
|
263
|
+
const enc = Buffer.from(pkE);
|
|
264
|
+
const kemContext = Buffer.concat([enc, pkR]);
|
|
265
|
+
const kemSuiteId = Buffer.from([0x4b, 0x45, 0x4d, 0x00, 0x20]);
|
|
266
|
+
const prk = labeledExtract(kemSuiteId, Buffer.alloc(0), "shared_secret", dh);
|
|
267
|
+
const sharedSecret = labeledExpand(kemSuiteId, prk, "shared_secret", kemContext, 32);
|
|
268
|
+
return { sharedSecret, enc };
|
|
269
|
+
}
|
|
270
|
+
/** AEAD key/nonce/tag sizes for supported cipher suites. */
|
|
271
|
+
function aeadParams(aeadId) {
|
|
272
|
+
switch (aeadId) {
|
|
273
|
+
case 0x0001:
|
|
274
|
+
return { Nk: 16, Nn: 12 };
|
|
275
|
+
case 0x0002:
|
|
276
|
+
return { Nk: 32, Nn: 12 };
|
|
277
|
+
case 0x0003:
|
|
278
|
+
return { Nk: 32, Nn: 12 };
|
|
279
|
+
default:
|
|
280
|
+
throw new Error(`Unsupported HPKE AEAD: 0x${aeadId.toString(16)}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function aeadAlgorithm(aeadId) {
|
|
284
|
+
switch (aeadId) {
|
|
285
|
+
case 0x0001:
|
|
286
|
+
return "aes-128-gcm";
|
|
287
|
+
case 0x0002:
|
|
288
|
+
return "aes-256-gcm";
|
|
289
|
+
case 0x0003:
|
|
290
|
+
return "chacha20-poly1305";
|
|
291
|
+
default:
|
|
292
|
+
throw new Error(`Unsupported HPKE AEAD: 0x${aeadId.toString(16)}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* HPKE KeyScheduleS in base mode (RFC 9180 §5.1).
|
|
297
|
+
* Derives the encryption key and base nonce from the shared secret and info.
|
|
298
|
+
*/
|
|
299
|
+
function hpkeKeyScheduleS(kemId, kdfId, aeadId, sharedSecret, info) {
|
|
300
|
+
const suiteId = Buffer.alloc(10);
|
|
301
|
+
suiteId.write("HPKE", 0, "ascii");
|
|
302
|
+
suiteId.writeUInt16BE(kemId, 4);
|
|
303
|
+
suiteId.writeUInt16BE(kdfId, 6);
|
|
304
|
+
suiteId.writeUInt16BE(aeadId, 8);
|
|
305
|
+
const { Nk, Nn } = aeadParams(aeadId);
|
|
306
|
+
const pskIdHash = labeledExtract(suiteId, Buffer.alloc(0), "psk_id_hash", Buffer.alloc(0));
|
|
307
|
+
const infoHash = labeledExtract(suiteId, Buffer.alloc(0), "info_hash", info);
|
|
308
|
+
const ksContext = Buffer.concat([Buffer.from([0x00]), pskIdHash, infoHash]);
|
|
309
|
+
const secret = labeledExtract(suiteId, sharedSecret, "secret", Buffer.alloc(0));
|
|
310
|
+
const key = labeledExpand(suiteId, secret, "key", ksContext, Nk);
|
|
311
|
+
const baseNonce = labeledExpand(suiteId, secret, "base_nonce", ksContext, Nn);
|
|
312
|
+
return { key, baseNonce };
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* HPKE single-shot Seal (AEAD encrypt, seq=0). Returns ciphertext || tag.
|
|
316
|
+
*/
|
|
317
|
+
function hpkeSeal(key, baseNonce, aad, plaintext, aeadId) {
|
|
318
|
+
const alg = aeadAlgorithm(aeadId);
|
|
319
|
+
const cipher = createCipheriv(alg, key, baseNonce, { authTagLength: 16 });
|
|
320
|
+
cipher.setAAD(aad);
|
|
321
|
+
const encrypted = cipher.update(plaintext);
|
|
322
|
+
const final = cipher.final();
|
|
323
|
+
const tag = cipher.getAuthTag();
|
|
324
|
+
return Buffer.concat([encrypted, final, tag]);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Builds the ECH outer extension data (type=outer) for the ClientHello.
|
|
328
|
+
*
|
|
329
|
+
* Layout:
|
|
330
|
+
* ```
|
|
331
|
+
* uint8 type = 0x00 (outer)
|
|
332
|
+
* uint16 kdf_id
|
|
333
|
+
* uint16 aead_id
|
|
334
|
+
* uint8 config_id
|
|
335
|
+
* opaque enc<0..2^16-1>
|
|
336
|
+
* opaque payload<1..2^16-1>
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
export function buildECHOuterExtData(kdfId, aeadId, configId, enc, payload) {
|
|
340
|
+
const len = 1 + 2 + 2 + 1 + 2 + enc.length + 2 + payload.length;
|
|
341
|
+
const buf = Buffer.alloc(len);
|
|
342
|
+
let off = 0;
|
|
343
|
+
buf[off++] = 0x00;
|
|
344
|
+
buf.writeUInt16BE(kdfId, off);
|
|
345
|
+
off += 2;
|
|
346
|
+
buf.writeUInt16BE(aeadId, off);
|
|
347
|
+
off += 2;
|
|
348
|
+
buf[off++] = configId;
|
|
349
|
+
buf.writeUInt16BE(enc.length, off);
|
|
350
|
+
off += 2;
|
|
351
|
+
enc.copy(buf, off);
|
|
352
|
+
off += enc.length;
|
|
353
|
+
buf.writeUInt16BE(payload.length, off);
|
|
354
|
+
off += 2;
|
|
355
|
+
payload.copy(buf, off);
|
|
356
|
+
return buf;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Extracts the raw bytes of the first ECHConfig entry from an ECHConfigList.
|
|
360
|
+
* Returns the `version(2) + length(2) + contents(configLength)` bytes.
|
|
361
|
+
*/
|
|
362
|
+
export function extractFirstECHConfigRaw(echConfigList) {
|
|
363
|
+
if (echConfigList.length < 6)
|
|
364
|
+
return null;
|
|
365
|
+
const totalLength = echConfigList.readUInt16BE(0);
|
|
366
|
+
if (totalLength + 2 > echConfigList.length)
|
|
367
|
+
return null;
|
|
368
|
+
const version = echConfigList.readUInt16BE(2);
|
|
369
|
+
const configLength = echConfigList.readUInt16BE(4);
|
|
370
|
+
if (6 + configLength > echConfigList.length)
|
|
371
|
+
return null;
|
|
372
|
+
return Buffer.from(echConfigList.subarray(2, 6 + configLength));
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Core ECH encryption: encrypts an inner ClientHello body using HPKE and
|
|
376
|
+
* returns all values needed to populate the outer ECH extension.
|
|
377
|
+
*
|
|
378
|
+
* @param innerCHBody Serialized inner ClientHello body (after handshake header).
|
|
379
|
+
* @param outerCHAAD Full outer ClientHello handshake message with ECH payload zeroed.
|
|
380
|
+
* @param config The selected ECHConfig entry.
|
|
381
|
+
* @param configRaw Raw bytes of the ECHConfig entry (version + length + contents).
|
|
382
|
+
* @returns ECH outer extension data and HPKE metadata.
|
|
383
|
+
*/
|
|
384
|
+
export function echEncryptInner(innerCHBody, outerCHAAD, config, configRaw) {
|
|
385
|
+
const hpkeConfig = parseHpkeKeyConfig(config.contents);
|
|
386
|
+
if (!hpkeConfig)
|
|
387
|
+
throw new Error("Invalid ECHConfig: cannot parse HPKE key config");
|
|
388
|
+
if (hpkeConfig.kemId !== 0x0020) {
|
|
389
|
+
throw new Error(`Unsupported KEM: 0x${hpkeConfig.kemId.toString(16)} (only DHKEM(X25519, HKDF-SHA256) = 0x0020)`);
|
|
390
|
+
}
|
|
391
|
+
const suite = hpkeConfig.cipherSuites.find((cs) => cs.kdfId === 0x0001 && (cs.aeadId === 0x0001 || cs.aeadId === 0x0003));
|
|
392
|
+
if (!suite)
|
|
393
|
+
throw new Error("No supported HPKE cipher suite in ECHConfig");
|
|
394
|
+
const hpkeInfo = Buffer.concat([Buffer.from("tls ech\x00", "ascii"), configRaw]);
|
|
395
|
+
const { sharedSecret, enc } = dhkemX25519Encap(hpkeConfig.publicKey);
|
|
396
|
+
const { key, baseNonce } = hpkeKeyScheduleS(hpkeConfig.kemId, suite.kdfId, suite.aeadId, sharedSecret, hpkeInfo);
|
|
397
|
+
const payload = hpkeSeal(key, baseNonce, outerCHAAD, innerCHBody, suite.aeadId);
|
|
398
|
+
const extensionData = buildECHOuterExtData(suite.kdfId, suite.aeadId, hpkeConfig.configId, enc, payload);
|
|
399
|
+
return { extensionData, enc, kdfId: suite.kdfId, aeadId: suite.aeadId, configId: hpkeConfig.configId };
|
|
400
|
+
}
|
|
401
|
+
//# sourceMappingURL=ech.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ech.js","sourceRoot":"","sources":["../../src/tls/ech.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ITLSEngine, TLSConnectOptions, TLSSocket } from "./types.js";
|
|
2
2
|
import type { BrowserProfile } from "../fingerprints/types.js";
|
|
3
|
+
import { TLSSessionCache } from "./session-cache.js";
|
|
3
4
|
/**
|
|
4
5
|
* TLS engine that delegates to Node.js’s built-in `tls` module. Provides
|
|
5
6
|
* standard TLS connectivity with optional browser-profile cipher and curve
|
|
@@ -8,10 +9,17 @@ import type { BrowserProfile } from "../fingerprints/types.js";
|
|
|
8
9
|
* fidelity is required.
|
|
9
10
|
*/
|
|
10
11
|
export declare class NodeTLSEngine implements ITLSEngine {
|
|
12
|
+
private readonly sessionCache;
|
|
13
|
+
constructor(sessionCache?: TLSSessionCache);
|
|
11
14
|
/**
|
|
12
|
-
*
|
|
15
|
+
* Returns the session ticket cache used by this engine.
|
|
16
|
+
*/
|
|
17
|
+
getSessionCache(): TLSSessionCache;
|
|
18
|
+
/**
|
|
19
|
+
* Establishes a TLS connection to the given host and port using Node.js's
|
|
13
20
|
* native `tls.connect()`. When a `profile` is supplied the cipher list,
|
|
14
21
|
* ECDH curves, and signature algorithms are overridden to match that profile.
|
|
22
|
+
* Reuses cached session tickets for session resumption when available.
|
|
15
23
|
*
|
|
16
24
|
* @param {TLSConnectOptions} options - Connection parameters.
|
|
17
25
|
* @param {BrowserProfile} [profile] - Optional browser profile to apply cipher/curve overrides.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-engine.d.ts","sourceRoot":"","sources":["../../src/tls/node-engine.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAqB,SAAS,EAAE,MAAM,YAAY,CAAC;AAC9F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"node-engine.d.ts","sourceRoot":"","sources":["../../src/tls/node-engine.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAqB,SAAS,EAAE,MAAM,YAAY,CAAC;AAC9F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG/D,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAkFrD;;;;;;GAMG;AACH,qBAAa,aAAc,YAAW,UAAU;IAC9C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkB;gBAEnC,YAAY,CAAC,EAAE,eAAe;IAI1C;;OAEG;IACH,eAAe,IAAI,eAAe;IAIlC;;;;;;;;;;OAUG;IACG,OAAO,CAAC,OAAO,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,SAAS,CAAC;CA6IxF"}
|
package/dist/tls/node-engine.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as tls from "node:tls";
|
|
2
2
|
import { CipherSuite, NamedGroup, SignatureScheme } from "./constants.js";
|
|
3
3
|
import { TLSError } from "../core/errors.js";
|
|
4
|
+
import { TLSSessionCache } from "./session-cache.js";
|
|
5
|
+
import { verifyPinnedPublicKey } from "./pin-verification.js";
|
|
4
6
|
const CIPHER_NAME = new Map([
|
|
5
7
|
[CipherSuite.TLS_AES_128_GCM_SHA256, "TLS_AES_128_GCM_SHA256"],
|
|
6
8
|
[CipherSuite.TLS_AES_256_GCM_SHA384, "TLS_AES_256_GCM_SHA384"],
|
|
@@ -84,10 +86,21 @@ function buildSigalgs(algs) {
|
|
|
84
86
|
* fidelity is required.
|
|
85
87
|
*/
|
|
86
88
|
export class NodeTLSEngine {
|
|
89
|
+
sessionCache;
|
|
90
|
+
constructor(sessionCache) {
|
|
91
|
+
this.sessionCache = sessionCache ?? new TLSSessionCache();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Returns the session ticket cache used by this engine.
|
|
95
|
+
*/
|
|
96
|
+
getSessionCache() {
|
|
97
|
+
return this.sessionCache;
|
|
98
|
+
}
|
|
87
99
|
/**
|
|
88
|
-
* Establishes a TLS connection to the given host and port using Node.js
|
|
100
|
+
* Establishes a TLS connection to the given host and port using Node.js's
|
|
89
101
|
* native `tls.connect()`. When a `profile` is supplied the cipher list,
|
|
90
102
|
* ECDH curves, and signature algorithms are overridden to match that profile.
|
|
103
|
+
* Reuses cached session tickets for session resumption when available.
|
|
91
104
|
*
|
|
92
105
|
* @param {TLSConnectOptions} options - Connection parameters.
|
|
93
106
|
* @param {BrowserProfile} [profile] - Optional browser profile to apply cipher/curve overrides.
|
|
@@ -123,6 +136,9 @@ export class NodeTLSEngine {
|
|
|
123
136
|
if (options.ca) {
|
|
124
137
|
tlsOpts.ca = options.ca;
|
|
125
138
|
}
|
|
139
|
+
if (options.echConfigList) {
|
|
140
|
+
tlsOpts["encryptedClientHello"] = options.echConfigList;
|
|
141
|
+
}
|
|
126
142
|
if (profile) {
|
|
127
143
|
const { ciphers, ciphersuites } = buildCipherString(profile.tls.cipherSuites);
|
|
128
144
|
tlsOpts.ciphers = ciphers;
|
|
@@ -134,6 +150,11 @@ export class NodeTLSEngine {
|
|
|
134
150
|
if (options.socket) {
|
|
135
151
|
tlsOpts.socket = options.socket;
|
|
136
152
|
}
|
|
153
|
+
const origin = `${options.servername ?? options.host}:${options.port}`;
|
|
154
|
+
const cached = this.sessionCache.get(origin);
|
|
155
|
+
if (cached) {
|
|
156
|
+
tlsOpts.session = cached.ticket;
|
|
157
|
+
}
|
|
137
158
|
const timeoutMs = options.timeout ?? 30_000;
|
|
138
159
|
const socket = tls.connect(tlsOpts);
|
|
139
160
|
let settled = false;
|
|
@@ -169,13 +190,30 @@ export class NodeTLSEngine {
|
|
|
169
190
|
settled = true;
|
|
170
191
|
if (timer)
|
|
171
192
|
clearTimeout(timer);
|
|
193
|
+
if (options.pinnedPublicKey) {
|
|
194
|
+
try {
|
|
195
|
+
const peerCert = socket.getPeerCertificate(true);
|
|
196
|
+
if (peerCert && peerCert.raw) {
|
|
197
|
+
verifyPinnedPublicKey(peerCert.raw, options.pinnedPublicKey);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
socket.destroy();
|
|
202
|
+
reject(err instanceof Error ? err : new TLSError(String(err)));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
172
206
|
const cipher = socket.getCipher();
|
|
173
207
|
const proto = socket.getProtocol();
|
|
174
208
|
const connectionInfo = {
|
|
175
209
|
version: proto ?? "unknown",
|
|
176
210
|
alpnProtocol: socket.alpnProtocol || null,
|
|
177
211
|
cipher: cipher?.name ?? "unknown",
|
|
212
|
+
resumed: socket.isSessionReused(),
|
|
178
213
|
};
|
|
214
|
+
socket.on("session", (session) => {
|
|
215
|
+
this.sessionCache.set(origin, session, undefined, socket.alpnProtocol || undefined);
|
|
216
|
+
});
|
|
179
217
|
const tlsSocket = Object.assign(socket, {
|
|
180
218
|
connectionInfo,
|
|
181
219
|
destroyTLS() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-engine.js","sourceRoot":"","sources":["../../src/tls/node-engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAKhC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"node-engine.js","sourceRoot":"","sources":["../../src/tls/node-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies that the leaf certificate's SPKI SHA-256 hash matches at least
|
|
3
|
+
* one of the supplied pins. Throws {@link TLSError} on mismatch.
|
|
4
|
+
*
|
|
5
|
+
* @param certDer DER-encoded leaf certificate bytes.
|
|
6
|
+
* @param pins One or more pins in `"sha256//base64hash"` format.
|
|
7
|
+
*/
|
|
8
|
+
export declare function verifyPinnedPublicKey(certDer: Buffer, pins: string | string[]): void;
|
|
9
|
+
//# sourceMappingURL=pin-verification.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pin-verification.d.ts","sourceRoot":"","sources":["../../src/tls/pin-verification.ts"],"names":[],"mappings":"AAUA;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAiBpF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Certificate public key pinning (RFC 7469 concept, curl-style format).
|
|
3
|
+
*
|
|
4
|
+
* Verifies that a server's leaf certificate matches one of a set of
|
|
5
|
+
* SHA-256 hashes computed over the DER-encoded Subject Public Key Info
|
|
6
|
+
* (SPKI). Pin format: `"sha256//base64hash"` (same as curl `--pinnedpubkey`).
|
|
7
|
+
*/
|
|
8
|
+
import { createHash, X509Certificate } from "node:crypto";
|
|
9
|
+
import { TLSError } from "../core/errors.js";
|
|
10
|
+
/**
|
|
11
|
+
* Verifies that the leaf certificate's SPKI SHA-256 hash matches at least
|
|
12
|
+
* one of the supplied pins. Throws {@link TLSError} on mismatch.
|
|
13
|
+
*
|
|
14
|
+
* @param certDer DER-encoded leaf certificate bytes.
|
|
15
|
+
* @param pins One or more pins in `"sha256//base64hash"` format.
|
|
16
|
+
*/
|
|
17
|
+
export function verifyPinnedPublicKey(certDer, pins) {
|
|
18
|
+
const pinArray = typeof pins === "string" ? [pins] : pins;
|
|
19
|
+
if (pinArray.length === 0)
|
|
20
|
+
return;
|
|
21
|
+
const x509 = new X509Certificate(certDer);
|
|
22
|
+
const spki = Buffer.from(x509.publicKey.export({ type: "spki", format: "der" }));
|
|
23
|
+
const hash = createHash("sha256").update(spki).digest("base64");
|
|
24
|
+
const certPin = `sha256//${hash}`;
|
|
25
|
+
const matches = pinArray.some((pin) => {
|
|
26
|
+
if (!pin.startsWith("sha256//"))
|
|
27
|
+
return false;
|
|
28
|
+
return pin === certPin;
|
|
29
|
+
});
|
|
30
|
+
if (!matches) {
|
|
31
|
+
throw new TLSError(`Certificate public key pin mismatch. Server pin: ${certPin}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=pin-verification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pin-verification.js","sourceRoot":"","sources":["../../src/tls/pin-verification.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAe,EAAE,IAAuB;IAC5E,MAAM,QAAQ,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAElC,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IACjF,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAChE,MAAM,OAAO,GAAG,WAAW,IAAI,EAAE,CAAC;IAElC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QACpC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,KAAK,CAAC;QAC9C,OAAO,GAAG,KAAK,OAAO,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,QAAQ,CAAC,oDAAoD,OAAO,EAAE,CAAC,CAAC;IACpF,CAAC;AACH,CAAC"}
|