@browsercore/testing 0.1.3 → 0.2.1
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 +5 -0
- package/bin/coverage-md.mjs +99 -0
- package/captures/DIVERGENCE_REPORT.md +325 -0
- package/captures/IMPERSIONATION_GAPS_REPORT.md +367 -0
- package/captures/README.md +56 -0
- package/captures/REVIEW_FINDINGS.md +71 -0
- package/captures/_probe/analyze.py +67 -0
- package/captures/_probe/capture_bytes.py +127 -0
- package/captures/_probe/chrome131_determinism.py +32 -0
- package/captures/_probe/diagnose_capture.py +59 -0
- package/captures/_probe/golden_analysis.py +91 -0
- package/captures/_probe/inspect_golden.py +55 -0
- package/captures/_probe/inspect_sv.py +16 -0
- package/captures/_probe/make_golden.py +170 -0
- package/captures/_probe/output/chrome100.json +219 -0
- package/captures/_probe/output/chrome101.json +219 -0
- package/captures/_probe/output/chrome104.json +219 -0
- package/captures/_probe/output/chrome107.json +220 -0
- package/captures/_probe/output/chrome110.json +220 -0
- package/captures/_probe/output/chrome116.json +220 -0
- package/captures/_probe/output/chrome119.json +219 -0
- package/captures/_probe/output/chrome120.json +219 -0
- package/captures/_probe/output/chrome123.json +223 -0
- package/captures/_probe/output/chrome124.json +224 -0
- package/captures/_probe/output/chrome131.json +224 -0
- package/captures/_probe/output/chrome131_fresh.json +224 -0
- package/captures/_probe/output/chrome133a.json +224 -0
- package/captures/_probe/output/chrome136.json +224 -0
- package/captures/_probe/output/chrome99.json +219 -0
- package/captures/_probe/output/edge101.json +219 -0
- package/captures/_probe/output/edge99.json +219 -0
- package/captures/_probe/output/firefox133.json +227 -0
- package/captures/_probe/output/firefox135.json +230 -0
- package/captures/_probe/output/safari155.json +210 -0
- package/captures/_probe/output/safari170.json +214 -0
- package/captures/_probe/output/safari170_fresh.json +214 -0
- package/captures/_probe/output/safari180.json +216 -0
- package/captures/_probe/output/safari180_ios.json +216 -0
- package/captures/_probe/output/safari184.json +215 -0
- package/captures/_probe/output/safari260.json +211 -0
- package/captures/_probe/output/summary.json +2520 -0
- package/captures/_probe/parse_existing_settings.py +15 -0
- package/captures/_probe/peetprint.py +39 -0
- package/captures/_probe/probe.py +137 -0
- package/captures/_probe/raw_bytes/chrome-131_client_hello.bin +0 -0
- package/captures/_probe/raw_bytes/chrome-131_client_hello.hex +32 -0
- package/captures/_probe/raw_bytes/firefox-133_client_hello.bin +0 -0
- package/captures/_probe/raw_bytes/firefox-133_client_hello.hex +32 -0
- package/captures/_probe/raw_bytes/safari-17_client_hello.bin +0 -0
- package/captures/_probe/raw_bytes/safari-17_client_hello.hex +32 -0
- package/captures/_probe/relay_capture.py +137 -0
- package/captures/_probe/socks_capture.py +138 -0
- package/captures/_probe/validate_fresh.py +66 -0
- package/captures/_probe/validate_golden.py +97 -0
- package/captures/_probe/verify_capture.py +68 -0
- package/captures/chrome-131/http2/settings.bin +0 -0
- package/captures/chrome-131/http2/settings.meta.json +9 -0
- package/captures/chrome-131/oracle_capture.reference.json +224 -0
- package/captures/chrome-131/tls/client_hello.bin +0 -0
- package/captures/chrome-131/tls/client_hello.meta.json +20 -0
- package/captures/chrome-140/http2/settings.bin +0 -0
- package/captures/chrome-140/http2/settings.meta.json +9 -0
- package/captures/chrome-140/tls/client_hello.bin +0 -0
- package/captures/chrome-140/tls/client_hello.meta.json +12 -0
- package/captures/firefox-128/tls/client_hello.bin +0 -0
- package/captures/firefox-128/tls/client_hello.meta.json +12 -0
- package/captures/firefox-133/http2/settings.bin +0 -0
- package/captures/firefox-133/http2/settings.meta.json +9 -0
- package/captures/firefox-133/oracle_capture.reference.json +227 -0
- package/captures/firefox-133/tls/client_hello.bin +0 -0
- package/captures/firefox-133/tls/client_hello.meta.json +20 -0
- package/captures/safari-17/http2/settings.bin +0 -0
- package/captures/safari-17/http2/settings.meta.json +9 -0
- package/captures/safari-17/oracle_capture.reference.json +214 -0
- package/captures/safari-17/tls/client_hello.bin +0 -0
- package/captures/safari-17/tls/client_hello.meta.json +20 -0
- package/captures/test-malformed/http2/settings.bin +0 -0
- package/captures/test-malformed/http2/settings.meta.json +9 -0
- package/captures/test-no-extensions/tls/client_hello.bin +0 -0
- package/captures/test-no-extensions/tls/client_hello.meta.json +9 -0
- package/captures/test-wrong-type/http2/settings.bin +0 -0
- package/captures/test-wrong-type/http2/settings.meta.json +9 -0
- package/dist/bench/bench.d.ts +24 -6
- package/dist/bench/bench.d.ts.map +1 -1
- package/dist/bench/bench.js +110 -10
- package/dist/bench/bench.js.map +1 -1
- package/dist/captures/manifest.d.ts.map +1 -1
- package/dist/captures/manifest.js +7 -7
- package/dist/captures/manifest.js.map +1 -1
- package/dist/categories/compression.d.ts +6 -4
- package/dist/categories/compression.d.ts.map +1 -1
- package/dist/categories/compression.js +56 -6
- package/dist/categories/compression.js.map +1 -1
- package/dist/categories/cookies.d.ts +3 -2
- package/dist/categories/cookies.d.ts.map +1 -1
- package/dist/categories/cookies.js +216 -11
- package/dist/categories/cookies.js.map +1 -1
- package/dist/categories/redirects.d.ts +8 -2
- package/dist/categories/redirects.d.ts.map +1 -1
- package/dist/categories/redirects.js +97 -12
- package/dist/categories/redirects.js.map +1 -1
- package/dist/e2e/cert-gen.d.ts +31 -0
- package/dist/e2e/cert-gen.d.ts.map +1 -0
- package/dist/e2e/cert-gen.js +158 -0
- package/dist/e2e/cert-gen.js.map +1 -0
- package/dist/e2e/parse-clienthello.d.ts +177 -0
- package/dist/e2e/parse-clienthello.d.ts.map +1 -0
- package/dist/e2e/parse-clienthello.js +343 -0
- package/dist/e2e/parse-clienthello.js.map +1 -0
- package/dist/e2e/sink-server.d.ts +109 -0
- package/dist/e2e/sink-server.d.ts.map +1 -0
- package/dist/e2e/sink-server.js +292 -0
- package/dist/e2e/sink-server.js.map +1 -0
- package/dist/e2e/traffic-server.d.ts +48 -0
- package/dist/e2e/traffic-server.d.ts.map +1 -0
- package/dist/e2e/traffic-server.js +162 -0
- package/dist/e2e/traffic-server.js.map +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/fingerprint/index.d.ts +5 -2
- package/dist/fingerprint/index.d.ts.map +1 -1
- package/dist/fingerprint/index.js +3 -2
- package/dist/fingerprint/index.js.map +1 -1
- package/dist/fingerprint/ja3.d.ts.map +1 -1
- package/dist/fingerprint/ja3.js +33 -4
- package/dist/fingerprint/ja3.js.map +1 -1
- package/dist/fingerprint/ja4-errors.d.ts +17 -0
- package/dist/fingerprint/ja4-errors.d.ts.map +1 -0
- package/dist/fingerprint/ja4-errors.js +20 -0
- package/dist/fingerprint/ja4-errors.js.map +1 -0
- package/dist/fingerprint/ja4-reader.d.ts +44 -0
- package/dist/fingerprint/ja4-reader.d.ts.map +1 -0
- package/dist/fingerprint/ja4-reader.js +112 -0
- package/dist/fingerprint/ja4-reader.js.map +1 -0
- package/dist/fingerprint/ja4.d.ts +11 -43
- package/dist/fingerprint/ja4.d.ts.map +1 -1
- package/dist/fingerprint/ja4.js +54 -141
- package/dist/fingerprint/ja4.js.map +1 -1
- package/dist/fingerprint/ja4h.d.ts +46 -0
- package/dist/fingerprint/ja4h.d.ts.map +1 -0
- package/dist/fingerprint/ja4h.js +50 -0
- package/dist/fingerprint/ja4h.js.map +1 -0
- package/dist/golden/golden.d.ts +15 -1
- package/dist/golden/golden.d.ts.map +1 -1
- package/dist/golden/golden.js +42 -28
- package/dist/golden/golden.js.map +1 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -1
- package/dist/index.js.map +1 -1
- package/dist/node-provider.d.ts +42 -0
- package/dist/node-provider.d.ts.map +1 -0
- package/dist/node-provider.js +53 -0
- package/dist/node-provider.js.map +1 -0
- package/dist/provider.d.ts +52 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +14 -0
- package/dist/provider.js.map +1 -0
- package/dist/reference/browser-provider.d.ts +33 -0
- package/dist/reference/browser-provider.d.ts.map +1 -0
- package/dist/reference/browser-provider.js +61 -0
- package/dist/reference/browser-provider.js.map +1 -0
- package/dist/reference/curl-provider.d.ts +40 -0
- package/dist/reference/curl-provider.d.ts.map +1 -0
- package/dist/reference/curl-provider.js +96 -0
- package/dist/reference/curl-provider.js.map +1 -0
- package/dist/reference/dump.d.ts +42 -0
- package/dist/reference/dump.d.ts.map +1 -0
- package/dist/reference/dump.js +102 -0
- package/dist/reference/dump.js.map +1 -0
- package/dist/reference/node-crypto-provider.d.ts +31 -0
- package/dist/reference/node-crypto-provider.d.ts.map +1 -0
- package/dist/reference/node-crypto-provider.js +199 -0
- package/dist/reference/node-crypto-provider.js.map +1 -0
- package/dist/reference/node-reference.d.ts.map +1 -1
- package/dist/reference/node-reference.js +2 -5
- package/dist/reference/node-reference.js.map +1 -1
- package/dist/reference/reference-errors.d.ts +36 -0
- package/dist/reference/reference-errors.d.ts.map +1 -0
- package/dist/reference/reference-errors.js +43 -0
- package/dist/reference/reference-errors.js.map +1 -0
- package/dist/reference/reference-types.d.ts +33 -0
- package/dist/reference/reference-types.d.ts.map +1 -0
- package/dist/reference/reference-types.js +9 -0
- package/dist/reference/reference-types.js.map +1 -0
- package/dist/reference/reference.d.ts +14 -77
- package/dist/reference/reference.d.ts.map +1 -1
- package/dist/reference/reference.js +43 -207
- package/dist/reference/reference.js.map +1 -1
- package/dist/rfc/rfcTests.d.ts +46 -8
- package/dist/rfc/rfcTests.d.ts.map +1 -1
- package/dist/rfc/rfcTests.js +203 -16
- package/dist/rfc/rfcTests.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +11 -5
- package/dist/utils.js.map +1 -1
- package/package.json +68 -55
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E TLS sink server for live handshake verification.
|
|
3
|
+
*
|
|
4
|
+
* Performs a **real** TLS 1.3 handshake against the `@browsercore/tls` client
|
|
5
|
+
* stack and returns the raw ClientHello bytes it saw on the wire — captured
|
|
6
|
+
* *before* Node's `tls` module parses them. This is the missing test oracle:
|
|
7
|
+
* every other TLS test in the stack runs against an in-process fixture; the
|
|
8
|
+
* sink exercises the full stack end-to-end over a real TCP socket.
|
|
9
|
+
*
|
|
10
|
+
* ## Two-socket tap
|
|
11
|
+
*
|
|
12
|
+
* Node's `tls` module parses the ClientHello before user code can see it, so
|
|
13
|
+
* the sink uses a two-socket tap (plan §2.3, Approach A):
|
|
14
|
+
*
|
|
15
|
+
* 1. A raw `node:net` server accepts the connection and pauses the socket.
|
|
16
|
+
* 2. It buffers the first bytes (the ClientHello TLS record) by reading until
|
|
17
|
+
* the record length is satisfied.
|
|
18
|
+
* 3. It unshifts the buffer back onto the socket and wraps the socket in a
|
|
19
|
+
* `tls.TLSSocket` (server-side) with a freshly generated self-signed
|
|
20
|
+
* ECDSA P-256 cert.
|
|
21
|
+
* 4. The handshake completes; the captured bytes + parsed inspection are
|
|
22
|
+
* returned via {@link TlsSinkServer.waitForHandshake}.
|
|
23
|
+
*
|
|
24
|
+
* The sink is single-shot by design: {@link TlsSinkServer.waitForHandshake}
|
|
25
|
+
* resolves on the first completed handshake. Tests create a fresh sink per
|
|
26
|
+
* case (ephemeral port, `maxConnections: 1`).
|
|
27
|
+
*/
|
|
28
|
+
import { createServer } from "node:net";
|
|
29
|
+
import { createSecureContext, TLSSocket } from "node:tls";
|
|
30
|
+
import { generateSelfSignedCert } from "./cert-gen.js";
|
|
31
|
+
/**
|
|
32
|
+
* Lightweight TLS 1.3 sink server.
|
|
33
|
+
*
|
|
34
|
+
* Not a general-purpose TLS server — it exists only to confirm a real
|
|
35
|
+
* handshake completes and to return the wire bytes it saw. All assertions
|
|
36
|
+
* live in the *test*; the sink just reports.
|
|
37
|
+
*/
|
|
38
|
+
export class TlsSinkServer {
|
|
39
|
+
server;
|
|
40
|
+
ctx;
|
|
41
|
+
state = { state: "idle" };
|
|
42
|
+
waiters = [];
|
|
43
|
+
failers = [];
|
|
44
|
+
/** Host the sink listens on (always 127.0.0.1). */
|
|
45
|
+
host;
|
|
46
|
+
/** Ephemeral port the sink listens on. */
|
|
47
|
+
port;
|
|
48
|
+
/** ALPN protocols the sink negotiates (empty = no ALPN negotiation). */
|
|
49
|
+
alpnProtocols;
|
|
50
|
+
constructor(server, ctx, host, port, alpnProtocols) {
|
|
51
|
+
this.server = server;
|
|
52
|
+
this.ctx = ctx;
|
|
53
|
+
this.host = host;
|
|
54
|
+
this.port = port;
|
|
55
|
+
this.alpnProtocols = alpnProtocols;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Start listening on an ephemeral port on 127.0.0.1.
|
|
59
|
+
*
|
|
60
|
+
* The returned sink is ready to accept one connection. The self-signed
|
|
61
|
+
* cert is generated at call time (throwaway — valid for 1 day).
|
|
62
|
+
*
|
|
63
|
+
* @param opts Optional configuration — ALPN protocols the sink should
|
|
64
|
+
* negotiate (chooses the first client-offered protocol that matches).
|
|
65
|
+
*/
|
|
66
|
+
static start(opts = {}) {
|
|
67
|
+
const { certDer, keyPem } = generateSelfSignedCert(["localhost"], ["127.0.0.1"], 1);
|
|
68
|
+
const ctx = createSecureContext({
|
|
69
|
+
key: keyPem,
|
|
70
|
+
cert: derToPem(certDer, "CERTIFICATE"),
|
|
71
|
+
});
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const server = createServer({ allowHalfOpen: false });
|
|
74
|
+
server.maxConnections = 1;
|
|
75
|
+
server.on("error", (err) => {
|
|
76
|
+
// If we haven't finished constructing the sink, reject start.
|
|
77
|
+
reject(err);
|
|
78
|
+
});
|
|
79
|
+
server.listen(0, "127.0.0.1", () => {
|
|
80
|
+
const addr = server.address();
|
|
81
|
+
if (addr === null || typeof addr === "string") {
|
|
82
|
+
reject(new Error("sink server did not bind to an ephemeral port"));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const sink = new TlsSinkServer(server, ctx, addr.address, addr.port, opts.alpnProtocols ?? []);
|
|
86
|
+
sink.attach();
|
|
87
|
+
resolve(sink);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** Wire up the server's connection handler. Must be called once. */
|
|
92
|
+
attach() {
|
|
93
|
+
this.server.on("connection", (socket) => this.onConnection(socket));
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Handle an incoming TCP connection: buffer the ClientHello, then hand the
|
|
97
|
+
* socket to a server-side TLSSocket for a real handshake.
|
|
98
|
+
*/
|
|
99
|
+
onConnection(rawSocket) {
|
|
100
|
+
// Guard: only one handshake per sink.
|
|
101
|
+
if (this.state.state !== "idle") {
|
|
102
|
+
rawSocket.destroy();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const startedAt = Date.now();
|
|
106
|
+
this.state = { state: "capturing", startedAt };
|
|
107
|
+
const chunks = [];
|
|
108
|
+
let totalBuffered = 0;
|
|
109
|
+
// Pause the socket so data accumulates in Node's internal buffer and we
|
|
110
|
+
// can read it deterministically before the TLSSocket consumes it.
|
|
111
|
+
rawSocket.pause();
|
|
112
|
+
/**
|
|
113
|
+
* Read chunks until we have at least one complete TLS record. A TLS
|
|
114
|
+
* record is 5 header bytes + `length` bytes of fragment. The
|
|
115
|
+
* ClientHello typically arrives in a single segment, but we loop to be
|
|
116
|
+
* robust to fragmentation.
|
|
117
|
+
*/
|
|
118
|
+
const tryCapture = () => {
|
|
119
|
+
if (totalBuffered < 5)
|
|
120
|
+
return null;
|
|
121
|
+
const recordLen = 5 + ((chunks[0][3] << 8) | chunks[0][4]);
|
|
122
|
+
if (totalBuffered < recordLen)
|
|
123
|
+
return null;
|
|
124
|
+
// Concatenate the buffered chunks and slice to the record boundary.
|
|
125
|
+
const flat = flattenChunks(chunks, totalBuffered);
|
|
126
|
+
return flat.subarray(0, recordLen);
|
|
127
|
+
};
|
|
128
|
+
const onData = (chunk) => {
|
|
129
|
+
chunks.push(chunk);
|
|
130
|
+
totalBuffered += chunk.length;
|
|
131
|
+
const capture = tryCapture();
|
|
132
|
+
if (capture !== null) {
|
|
133
|
+
rawSocket.removeListener("data", onData);
|
|
134
|
+
this.onClientHello(rawSocket, capture, startedAt);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
rawSocket.on("data", onData);
|
|
138
|
+
rawSocket.on("error", (err) => this.fail(err));
|
|
139
|
+
rawSocket.on("close", () => {
|
|
140
|
+
// If we closed before completing the handshake, fail any waiter.
|
|
141
|
+
if (this.state.state !== "done") {
|
|
142
|
+
this.fail(new Error("socket closed before handshake completed"));
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
// Resume flow so the data handler fires.
|
|
146
|
+
rawSocket.resume();
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* We have the raw ClientHello. Unshift it back onto the socket and wrap
|
|
150
|
+
* the socket in a server-side TLSSocket to complete the handshake.
|
|
151
|
+
*/
|
|
152
|
+
onClientHello(rawSocket, capture, startedAt) {
|
|
153
|
+
// Wrap the raw socket in a TLSSocket BEFORE unshifting. The TLSSocket
|
|
154
|
+
// attaches its own `data` listener on construction; only after that
|
|
155
|
+
// listener is in place does unshift deliver the captured bytes straight
|
|
156
|
+
// into the TLS layer. Unshifting first leaves the bytes buffered with
|
|
157
|
+
// no listener to consume them, so the handshake never starts.
|
|
158
|
+
const tlsSocket = new TLSSocket(rawSocket, {
|
|
159
|
+
isServer: true,
|
|
160
|
+
secureContext: this.ctx,
|
|
161
|
+
// Request but do not require a client cert — keeps the sink simple.
|
|
162
|
+
requestCert: false,
|
|
163
|
+
ALPNProtocols: this.alpnProtocols.length > 0 ? this.alpnProtocols : undefined,
|
|
164
|
+
});
|
|
165
|
+
// Now push the captured bytes back; the TLSSocket reads them as its
|
|
166
|
+
// first input and begins the handshake.
|
|
167
|
+
rawSocket.unshift(capture);
|
|
168
|
+
const clientHello = {
|
|
169
|
+
raw: capture,
|
|
170
|
+
receivedAt: Date.now(),
|
|
171
|
+
};
|
|
172
|
+
this.state = { state: "handshaking", capture: clientHello, startedAt };
|
|
173
|
+
tlsSocket.on("secure", () => {
|
|
174
|
+
// The secure event fires once the handshake is verified.
|
|
175
|
+
const result = {
|
|
176
|
+
clientHello,
|
|
177
|
+
protocolVersion: tlsSocket.getProtocol() ?? "unknown",
|
|
178
|
+
cipherSuite: tlsSocket.getCipher().name,
|
|
179
|
+
alpnProtocol: narrowServerName(tlsSocket.alpnProtocol),
|
|
180
|
+
serverName: narrowServerName(tlsSocket.servername),
|
|
181
|
+
handshakeDurationMs: Date.now() - startedAt,
|
|
182
|
+
};
|
|
183
|
+
this.done(result);
|
|
184
|
+
// Close the connection after reporting — single-shot sink.
|
|
185
|
+
tlsSocket.end();
|
|
186
|
+
});
|
|
187
|
+
tlsSocket.on("error", (err) => {
|
|
188
|
+
// Distinguish handshake rejection from transport errors.
|
|
189
|
+
this.fail(new Error(`TLS handshake failed: ${err.message}`, { cause: err }));
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/** Transition to the done state and resolve any waiters. */
|
|
193
|
+
done(result) {
|
|
194
|
+
if (this.state.state === "done")
|
|
195
|
+
return;
|
|
196
|
+
this.state = { state: "done", result };
|
|
197
|
+
const w = this.waiters;
|
|
198
|
+
this.waiters = [];
|
|
199
|
+
this.failers = [];
|
|
200
|
+
for (const resolve of w)
|
|
201
|
+
resolve(result);
|
|
202
|
+
}
|
|
203
|
+
/** Transition to the failed state and reject any waiters. */
|
|
204
|
+
fail(error) {
|
|
205
|
+
if (this.state.state === "done")
|
|
206
|
+
return;
|
|
207
|
+
this.state = { state: "failed", error };
|
|
208
|
+
const f = this.failers;
|
|
209
|
+
this.failers = [];
|
|
210
|
+
this.waiters = [];
|
|
211
|
+
for (const reject of f)
|
|
212
|
+
reject(error);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Resolve once a ClientHello has been captured and the handshake completed.
|
|
216
|
+
*
|
|
217
|
+
* @param timeoutMs Max wait in milliseconds (default 5000). The sink is
|
|
218
|
+
* single-shot — only the first handshake resolves this promise.
|
|
219
|
+
* @throws {Error} on timeout or handshake failure.
|
|
220
|
+
*/
|
|
221
|
+
waitForHandshake(timeoutMs = 5000) {
|
|
222
|
+
if (this.state.state === "done") {
|
|
223
|
+
return Promise.resolve(this.state.result);
|
|
224
|
+
}
|
|
225
|
+
if (this.state.state === "failed") {
|
|
226
|
+
return Promise.reject(this.state.error);
|
|
227
|
+
}
|
|
228
|
+
return new Promise((resolve, reject) => {
|
|
229
|
+
let timer;
|
|
230
|
+
this.waiters.push((result) => {
|
|
231
|
+
if (timer !== undefined)
|
|
232
|
+
clearTimeout(timer);
|
|
233
|
+
resolve(result);
|
|
234
|
+
});
|
|
235
|
+
this.failers.push((err) => {
|
|
236
|
+
if (timer !== undefined)
|
|
237
|
+
clearTimeout(timer);
|
|
238
|
+
reject(err);
|
|
239
|
+
});
|
|
240
|
+
timer = setTimeout(() => {
|
|
241
|
+
this.fail(new Error(`waitForHandshake timed out after ${timeoutMs}ms`));
|
|
242
|
+
}, timeoutMs);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/** Stop the server. Resolves once the underlying socket is closed. */
|
|
246
|
+
stop() {
|
|
247
|
+
// Reject any pending waiters so callers don't hang.
|
|
248
|
+
this.fail(new Error("sink server stopped"));
|
|
249
|
+
return new Promise((resolve, reject) => {
|
|
250
|
+
this.server.close((err) => {
|
|
251
|
+
if (err !== undefined)
|
|
252
|
+
reject(err);
|
|
253
|
+
else
|
|
254
|
+
resolve();
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// --- Byte helpers -------------------------------------------------------
|
|
260
|
+
/** Concatenate an array of chunks into a single buffer of `total` bytes. */
|
|
261
|
+
function flattenChunks(chunks, total) {
|
|
262
|
+
const out = new Uint8Array(total);
|
|
263
|
+
let o = 0;
|
|
264
|
+
for (const c of chunks) {
|
|
265
|
+
out.set(c, o);
|
|
266
|
+
o += c.length;
|
|
267
|
+
}
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Coerce Node's `TLSSocket.servername` (`string | false | null`) to
|
|
272
|
+
* `string | null`. `false` means SNI was requested but not matched; `null`
|
|
273
|
+
* means no SNI was sent. Both map to "no usable servername" here.
|
|
274
|
+
*/
|
|
275
|
+
function narrowServerName(value) {
|
|
276
|
+
return typeof value === "string" ? value : null;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Convert a DER-encoded object to PEM (base64 with 64-char lines + headers).
|
|
280
|
+
* Used to feed the self-signed cert into `tls.createSecureContext`, which
|
|
281
|
+
* expects PEM for the `cert` option.
|
|
282
|
+
*/
|
|
283
|
+
function derToPem(der, label) {
|
|
284
|
+
const b64 = Buffer.from(der).toString("base64");
|
|
285
|
+
const lines = [`-----BEGIN ${label}-----`];
|
|
286
|
+
for (let i = 0; i < b64.length; i += 64) {
|
|
287
|
+
lines.push(b64.slice(i, i + 64));
|
|
288
|
+
}
|
|
289
|
+
lines.push(`-----END ${label}-----`);
|
|
290
|
+
return lines.join("\n");
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=sink-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sink-server.js","sourceRoot":"","sources":["../../src/e2e/sink-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E traffic server for Bug 6 (AES-128-GCM record decryption) regression.
|
|
3
|
+
*
|
|
4
|
+
* Extends the sink-server pattern: performs a **real** TLS 1.3 handshake
|
|
5
|
+
* against the @browsercore/tls client stack, then serves an encrypted HTTP
|
|
6
|
+
* response. The client must decrypt the full server flight (handshake +
|
|
7
|
+
* application data) — if AEAD key derivation, nonce construction, or record
|
|
8
|
+
* parsing is wrong, decryption fails and the fetch throws.
|
|
9
|
+
*
|
|
10
|
+
* This is the deterministic, network-free reproduction for Bug 6: the same
|
|
11
|
+
* TLS code path that fails against example.com fails here, but without a
|
|
12
|
+
* network dependency.
|
|
13
|
+
*/
|
|
14
|
+
/** Result of a completed handshake against the traffic server. */
|
|
15
|
+
export interface TrafficResult {
|
|
16
|
+
/** Negotiated protocol version, e.g. "TLSv1.3". */
|
|
17
|
+
readonly protocolVersion: string;
|
|
18
|
+
/** Negotiated cipher suite. */
|
|
19
|
+
readonly cipherSuite: string;
|
|
20
|
+
/** ALPN protocol negotiated, or null. */
|
|
21
|
+
readonly alpnProtocol: string | null;
|
|
22
|
+
/** Wall-clock handshake duration in milliseconds. */
|
|
23
|
+
readonly handshakeDurationMs: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Single-shot TLS 1.3 traffic server. Performs a real handshake, then sends
|
|
27
|
+
* an encrypted HTTP response the client must decrypt.
|
|
28
|
+
*/
|
|
29
|
+
export declare class TrafficServer {
|
|
30
|
+
private readonly server;
|
|
31
|
+
private readonly ctx;
|
|
32
|
+
private state;
|
|
33
|
+
private waiters;
|
|
34
|
+
private failers;
|
|
35
|
+
readonly host: string;
|
|
36
|
+
readonly port: number;
|
|
37
|
+
private constructor();
|
|
38
|
+
/** Start listening on an ephemeral port on 127.0.0.1. */
|
|
39
|
+
static start(): Promise<TrafficServer>;
|
|
40
|
+
private attach;
|
|
41
|
+
private onConnection;
|
|
42
|
+
private done;
|
|
43
|
+
private fail;
|
|
44
|
+
/** Resolve once handshake completes. */
|
|
45
|
+
waitForHandshake(timeoutMs?: number): Promise<TrafficResult>;
|
|
46
|
+
stop(): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=traffic-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traffic-server.d.ts","sourceRoot":"","sources":["../../src/e2e/traffic-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAgBH,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC1B,mDAAmD;IACnD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,+BAA+B;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,yCAAyC;IACzC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,qDAAqD;IACrD,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;CACxC;AAQD;;;GAGG;AACH,qBAAa,aAAa;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgB;IACpC,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,OAAO,CAA8C;IAC7D,OAAO,CAAC,OAAO,CAAmC;IAElD,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,IAAI,EAAE,MAAM,CAAC;IAE7B,OAAO,eAKN;IAED,yDAAyD;IACzD,MAAM,CAAC,KAAK,IAAI,OAAO,CAAC,aAAa,CAAC,CAsBrC;IAED,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,YAAY;IA0CpB,OAAO,CAAC,IAAI;IASZ,OAAO,CAAC,IAAI;IASZ,wCAAwC;IACxC,gBAAgB,CAAC,SAAS,SAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CASzD;IAED,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAKpB;CACJ"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E traffic server for Bug 6 (AES-128-GCM record decryption) regression.
|
|
3
|
+
*
|
|
4
|
+
* Extends the sink-server pattern: performs a **real** TLS 1.3 handshake
|
|
5
|
+
* against the @browsercore/tls client stack, then serves an encrypted HTTP
|
|
6
|
+
* response. The client must decrypt the full server flight (handshake +
|
|
7
|
+
* application data) — if AEAD key derivation, nonce construction, or record
|
|
8
|
+
* parsing is wrong, decryption fails and the fetch throws.
|
|
9
|
+
*
|
|
10
|
+
* This is the deterministic, network-free reproduction for Bug 6: the same
|
|
11
|
+
* TLS code path that fails against example.com fails here, but without a
|
|
12
|
+
* network dependency.
|
|
13
|
+
*/
|
|
14
|
+
import { createServer } from "node:net";
|
|
15
|
+
import { createSecureContext, TLSSocket } from "node:tls";
|
|
16
|
+
import { generateSelfSignedCert } from "./cert-gen.js";
|
|
17
|
+
/** HTTP response the server sends after a successful handshake. */
|
|
18
|
+
const HTTP_RESPONSE = [
|
|
19
|
+
"HTTP/1.1 200 OK",
|
|
20
|
+
"Content-Type: text/plain",
|
|
21
|
+
"X-Traffic-Gate: bug6",
|
|
22
|
+
"Content-Length: 19",
|
|
23
|
+
"",
|
|
24
|
+
"TRAFFIC_GATE_OK_BUG6",
|
|
25
|
+
].join("\r\n");
|
|
26
|
+
/**
|
|
27
|
+
* Single-shot TLS 1.3 traffic server. Performs a real handshake, then sends
|
|
28
|
+
* an encrypted HTTP response the client must decrypt.
|
|
29
|
+
*/
|
|
30
|
+
export class TrafficServer {
|
|
31
|
+
server;
|
|
32
|
+
ctx;
|
|
33
|
+
state = { state: "idle" };
|
|
34
|
+
waiters = [];
|
|
35
|
+
failers = [];
|
|
36
|
+
host;
|
|
37
|
+
port;
|
|
38
|
+
constructor(server, ctx, host, port) {
|
|
39
|
+
this.server = server;
|
|
40
|
+
this.ctx = ctx;
|
|
41
|
+
this.host = host;
|
|
42
|
+
this.port = port;
|
|
43
|
+
}
|
|
44
|
+
/** Start listening on an ephemeral port on 127.0.0.1. */
|
|
45
|
+
static start() {
|
|
46
|
+
const { certDer, keyPem } = generateSelfSignedCert(["localhost"], ["127.0.0.1"], 1);
|
|
47
|
+
const ctx = createSecureContext({
|
|
48
|
+
key: keyPem,
|
|
49
|
+
cert: derToPem(certDer, "CERTIFICATE"),
|
|
50
|
+
});
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const server = createServer({ allowHalfOpen: false });
|
|
53
|
+
server.maxConnections = 1;
|
|
54
|
+
server.on("error", reject);
|
|
55
|
+
server.listen(0, "127.0.0.1", () => {
|
|
56
|
+
const addr = server.address();
|
|
57
|
+
if (addr === null || typeof addr === "string") {
|
|
58
|
+
reject(new Error("traffic server did not bind"));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const traffic = new TrafficServer(server, ctx, addr.address, addr.port);
|
|
62
|
+
traffic.attach();
|
|
63
|
+
resolve(traffic);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
attach() {
|
|
68
|
+
this.server.on("connection", (socket) => this.onConnection(socket));
|
|
69
|
+
}
|
|
70
|
+
onConnection(rawSocket) {
|
|
71
|
+
if (this.state.state !== "idle") {
|
|
72
|
+
rawSocket.destroy();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const startedAt = Date.now();
|
|
76
|
+
this.state = { state: "handshaking", startedAt };
|
|
77
|
+
const tlsSocket = new TLSSocket(rawSocket, {
|
|
78
|
+
isServer: true,
|
|
79
|
+
secureContext: this.ctx,
|
|
80
|
+
requestCert: false,
|
|
81
|
+
ALPNProtocols: ["h2", "http/1.1"],
|
|
82
|
+
});
|
|
83
|
+
tlsSocket.on("secure", () => {
|
|
84
|
+
// Handshake completed. Now send the encrypted HTTP response.
|
|
85
|
+
// The client must decrypt this to read the body — this is what
|
|
86
|
+
// exercises the Bug 6 path.
|
|
87
|
+
tlsSocket.write(HTTP_RESPONSE, () => {
|
|
88
|
+
const result = {
|
|
89
|
+
protocolVersion: tlsSocket.getProtocol() ?? "unknown",
|
|
90
|
+
cipherSuite: tlsSocket.getCipher().name,
|
|
91
|
+
alpnProtocol: narrowServerName(tlsSocket.alpnProtocol),
|
|
92
|
+
handshakeDurationMs: Date.now() - startedAt,
|
|
93
|
+
};
|
|
94
|
+
this.done(result);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
tlsSocket.on("error", (err) => {
|
|
98
|
+
this.fail(new Error(`TLS handshake failed: ${err.message}`, { cause: err }));
|
|
99
|
+
});
|
|
100
|
+
tlsSocket.on("close", () => {
|
|
101
|
+
if (this.state.state !== "done") {
|
|
102
|
+
this.fail(new Error("socket closed before handshake completed"));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
done(result) {
|
|
107
|
+
if (this.state.state === "done")
|
|
108
|
+
return;
|
|
109
|
+
this.state = { state: "done", result };
|
|
110
|
+
const w = this.waiters;
|
|
111
|
+
this.waiters = [];
|
|
112
|
+
this.failers = [];
|
|
113
|
+
for (const resolve of w)
|
|
114
|
+
resolve(result);
|
|
115
|
+
}
|
|
116
|
+
fail(error) {
|
|
117
|
+
if (this.state.state === "done")
|
|
118
|
+
return;
|
|
119
|
+
this.state = { state: "failed", error };
|
|
120
|
+
const f = this.failers;
|
|
121
|
+
this.failers = [];
|
|
122
|
+
this.waiters = [];
|
|
123
|
+
for (const reject of f)
|
|
124
|
+
reject(error);
|
|
125
|
+
}
|
|
126
|
+
/** Resolve once handshake completes. */
|
|
127
|
+
waitForHandshake(timeoutMs = 5000) {
|
|
128
|
+
if (this.state.state === "done")
|
|
129
|
+
return Promise.resolve(this.state.result);
|
|
130
|
+
if (this.state.state === "failed")
|
|
131
|
+
return Promise.reject(this.state.error);
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
let timer;
|
|
134
|
+
this.waiters.push((r) => { if (timer)
|
|
135
|
+
clearTimeout(timer); resolve(r); });
|
|
136
|
+
this.failers.push((e) => { if (timer)
|
|
137
|
+
clearTimeout(timer); reject(e); });
|
|
138
|
+
timer = setTimeout(() => this.fail(new Error("waitForHandshake timed out")), timeoutMs);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
stop() {
|
|
142
|
+
this.fail(new Error("traffic server stopped"));
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
this.server.close((err) => { if (err)
|
|
145
|
+
reject(err);
|
|
146
|
+
else
|
|
147
|
+
resolve(); });
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function narrowServerName(value) {
|
|
152
|
+
return typeof value === "string" ? value : null;
|
|
153
|
+
}
|
|
154
|
+
function derToPem(der, label) {
|
|
155
|
+
const b64 = Buffer.from(der).toString("base64");
|
|
156
|
+
const lines = [`-----BEGIN ${label}-----`];
|
|
157
|
+
for (let i = 0; i < b64.length; i += 64)
|
|
158
|
+
lines.push(b64.slice(i, i + 64));
|
|
159
|
+
lines.push(`-----END ${label}-----`);
|
|
160
|
+
return lines.join("\n");
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=traffic-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traffic-server.js","sourceRoot":"","sources":["../../src/e2e/traffic-server.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,0CAA0C;AAC1C,qBAAa,YAAa,SAAQ,KAAK;IACnC,SAAgB,IAAI,EAAG,cAAc,CAAU;IAC/C,SAAyB,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,0CAA0C;AAC1C,qBAAa,YAAa,SAAQ,KAAK;IACnC,SAAgB,IAAI,EAAG,cAAc,CAAU;IAC/C,SAAyB,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;IAElD,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE,EAIvD;CACJ;AAED,mDAAmD;AACnD,qBAAa,mBAAoB,SAAQ,KAAK;IAC1C,SAAgB,IAAI,EAAG,qBAAqB,CAAU;IACtD,SAAgB,SAAS,EAAE,MAAM,CAAC;IAClC,SAAgB,mBAAmB,EAAE,MAAM,CAAC;IAC5C,SAAyB,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;IAElD,YACI,SAAS,EAAE,MAAM,EACjB,mBAAmB,EAAE,MAAM,EAC3B,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE,EAU9B;CACJ"}
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,0CAA0C;AAC1C,MAAM,OAAO,YAAa,SAAQ,KAAK;IACnB,IAAI,GAAG,cAAuB,CAAC;IACtB,KAAK,CAAoB;IAElD,YAAY,OAAe,EAAE,OAA2B;QACpD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,0CAA0C;AAC1C,MAAM,OAAO,YAAa,SAAQ,KAAK;IACnB,IAAI,GAAG,cAAuB,CAAC;IACtB,KAAK,CAAoB;IAElD,YAAY,OAAe,EAAE,OAA2B;QACpD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC;QAC5B,IAAI,CAAC,KAAK,GAAG,OAAO,EAAE,KAAK,CAAC;IAChC,CAAC;CACJ;AAED,mDAAmD;AACnD,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC1B,IAAI,GAAG,qBAA8B,CAAC;IACtC,SAAS,CAAS;IAClB,mBAAmB,CAAS;IACnB,KAAK,CAAoB;IAElD,YACI,SAAiB,EACjB,mBAA2B,EAC3B,OAA2B;QAE3B,KAAK,CACD,kBAAkB,SAAS,qBAAqB,mBAAmB,EAAE,EACrE,OAAO,CACV,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,mBAAmB,GAAG,mBAAmB,CAAC;QAC/C,IAAI,CAAC,KAAK,GAAG,OAAO,EAAE,KAAK,CAAC;IAChC,CAAC;CACJ"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
/** Barrel for fingerprint computation (JA3 / JA4). */
|
|
1
|
+
/** Barrel for fingerprint computation (JA3 / JA4 / JA4H). */
|
|
2
2
|
export { computeJa3, parseClientHello, Ja3ParseError } from "./ja3.js";
|
|
3
3
|
export type { Ja3Segments } from "./ja3.js";
|
|
4
|
-
export { computeJa4 } from "./ja4.js";
|
|
4
|
+
export { computeJa4, computeJa4Fingerprint, Ja4ParseError } from "./ja4.js";
|
|
5
|
+
export type { Ja4ClientHello, Ja4Fingerprint } from "./ja4.js";
|
|
6
|
+
export { computeJa4h } from "./ja4h.js";
|
|
7
|
+
export type { Ja4hFingerprint, Ja4hRequest } from "./ja4h.js";
|
|
5
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fingerprint/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fingerprint/index.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACvE,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC5E,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
/** Barrel for fingerprint computation (JA3 / JA4). */
|
|
1
|
+
/** Barrel for fingerprint computation (JA3 / JA4 / JA4H). */
|
|
2
2
|
export { computeJa3, parseClientHello, Ja3ParseError } from "./ja3.js";
|
|
3
|
-
export { computeJa4 } from "./ja4.js";
|
|
3
|
+
export { computeJa4, computeJa4Fingerprint, Ja4ParseError } from "./ja4.js";
|
|
4
|
+
export { computeJa4h } from "./ja4h.js";
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/fingerprint/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/fingerprint/index.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEvE,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE5E,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ja3.d.ts","sourceRoot":"","sources":["../../src/fingerprint/ja3.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,+DAA+D;AAC/D,qBAAa,aAAc,SAAQ,KAAK;IACpC,SAAgB,IAAI,EAAG,eAAe,CAAU;
|
|
1
|
+
{"version":3,"file":"ja3.d.ts","sourceRoot":"","sources":["../../src/fingerprint/ja3.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,+DAA+D;AAC/D,qBAAa,aAAc,SAAQ,KAAK;IACpC,SAAgB,IAAI,EAAG,eAAe,CAAU;IAChD,YAAY,OAAO,EAAE,MAAM,EAG1B;CACJ;AAYD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,UAAU,GAAG,MAAM,CAM1D;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACnC;AAED,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,UAAU,GAAG,WAAW,CA6GrE"}
|
package/dist/fingerprint/ja3.js
CHANGED
|
@@ -21,7 +21,12 @@ export class Ja3ParseError extends Error {
|
|
|
21
21
|
}
|
|
22
22
|
/** Read a big-endian uint16 at `offset` in `buf`. */
|
|
23
23
|
function uint16(buf, offset) {
|
|
24
|
-
|
|
24
|
+
const hi = buf[offset];
|
|
25
|
+
const lo = buf[offset + 1];
|
|
26
|
+
if (hi === undefined || lo === undefined) {
|
|
27
|
+
throw new Ja3ParseError(`uint16 read out of bounds at offset ${offset}`);
|
|
28
|
+
}
|
|
29
|
+
return (hi << 8) | lo;
|
|
25
30
|
}
|
|
26
31
|
/**
|
|
27
32
|
* Parse a TLS ClientHello from a TLS record (or handshake) buffer and return
|
|
@@ -75,6 +80,9 @@ export function parseClientHello(clientHello) {
|
|
|
75
80
|
pos += 32;
|
|
76
81
|
// session_id(variable)
|
|
77
82
|
const sessionIdLen = clientHello[pos];
|
|
83
|
+
if (sessionIdLen === undefined) {
|
|
84
|
+
throw new Ja3ParseError("ClientHello truncated at session id length");
|
|
85
|
+
}
|
|
78
86
|
pos += 1 + sessionIdLen;
|
|
79
87
|
// cipher_suites(variable)
|
|
80
88
|
const cipherSuitesLen = uint16(clientHello, pos);
|
|
@@ -83,6 +91,9 @@ export function parseClientHello(clientHello) {
|
|
|
83
91
|
pos += cipherSuitesLen;
|
|
84
92
|
// compression_methods(variable)
|
|
85
93
|
const compLen = clientHello[pos];
|
|
94
|
+
if (compLen === undefined) {
|
|
95
|
+
throw new Ja3ParseError("ClientHello truncated at compression methods length");
|
|
96
|
+
}
|
|
86
97
|
pos += 1 + compLen;
|
|
87
98
|
// extensions(variable)
|
|
88
99
|
if (pos + 2 > end) {
|
|
@@ -114,8 +125,15 @@ export function parseClientHello(clientHello) {
|
|
|
114
125
|
else if (extType === 0x000b && extLen >= 1) {
|
|
115
126
|
// ec_point_formats(11): list of uint8 formats
|
|
116
127
|
const listLen = clientHello[pos];
|
|
128
|
+
if (listLen === undefined) {
|
|
129
|
+
throw new Ja3ParseError("ClientHello truncated at ec_point_formats length");
|
|
130
|
+
}
|
|
117
131
|
for (let i = 0; i < listLen; i++) {
|
|
118
|
-
|
|
132
|
+
const fmt = clientHello[pos + 1 + i];
|
|
133
|
+
if (fmt === undefined) {
|
|
134
|
+
throw new Ja3ParseError("ClientHello truncated in ec_point_formats list");
|
|
135
|
+
}
|
|
136
|
+
ecPointFormats.push(fmt);
|
|
119
137
|
}
|
|
120
138
|
}
|
|
121
139
|
pos += extLen;
|
|
@@ -129,12 +147,23 @@ export function parseClientHello(clientHello) {
|
|
|
129
147
|
};
|
|
130
148
|
}
|
|
131
149
|
function readInt24(buf, offset) {
|
|
132
|
-
|
|
150
|
+
const hi = buf[offset];
|
|
151
|
+
const mid = buf[offset + 1];
|
|
152
|
+
const lo = buf[offset + 2];
|
|
153
|
+
if (hi === undefined || mid === undefined || lo === undefined) {
|
|
154
|
+
throw new Ja3ParseError(`readInt24 out of bounds at offset ${offset}`);
|
|
155
|
+
}
|
|
156
|
+
return (hi << 16) | (mid << 8) | lo;
|
|
133
157
|
}
|
|
134
158
|
function readUint16List(buf, offset, byteLen) {
|
|
135
159
|
const out = [];
|
|
136
160
|
for (let i = 0; i + 1 < byteLen; i += 2) {
|
|
137
|
-
|
|
161
|
+
const hi = buf[offset + i];
|
|
162
|
+
const lo = buf[offset + i + 1];
|
|
163
|
+
if (hi === undefined || lo === undefined) {
|
|
164
|
+
throw new Ja3ParseError(`readUint16List out of bounds at offset ${offset + i}`);
|
|
165
|
+
}
|
|
166
|
+
out.push((hi << 8) | lo);
|
|
138
167
|
}
|
|
139
168
|
return out;
|
|
140
169
|
}
|