@browsercore/testing 0.1.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 +21 -0
- package/README.md +98 -0
- package/dist/bench/bench.d.ts +21 -0
- package/dist/bench/bench.d.ts.map +1 -0
- package/dist/bench/bench.js +19 -0
- package/dist/bench/bench.js.map +1 -0
- package/dist/captures/manifest.d.ts +18 -0
- package/dist/captures/manifest.d.ts.map +1 -0
- package/dist/captures/manifest.js +62 -0
- package/dist/captures/manifest.js.map +1 -0
- package/dist/categories/capture.d.ts +8 -0
- package/dist/categories/capture.d.ts.map +1 -0
- package/dist/categories/capture.js +31 -0
- package/dist/categories/capture.js.map +1 -0
- package/dist/categories/compression.d.ts +8 -0
- package/dist/categories/compression.d.ts.map +1 -0
- package/dist/categories/compression.js +15 -0
- package/dist/categories/compression.js.map +1 -0
- package/dist/categories/cookies.d.ts +8 -0
- package/dist/categories/cookies.d.ts.map +1 -0
- package/dist/categories/cookies.js +20 -0
- package/dist/categories/cookies.js.map +1 -0
- package/dist/categories/errors.d.ts +11 -0
- package/dist/categories/errors.d.ts.map +1 -0
- package/dist/categories/errors.js +41 -0
- package/dist/categories/errors.js.map +1 -0
- package/dist/categories/http.d.ts +9 -0
- package/dist/categories/http.d.ts.map +1 -0
- package/dist/categories/http.js +46 -0
- package/dist/categories/http.js.map +1 -0
- package/dist/categories/index.d.ts +17 -0
- package/dist/categories/index.d.ts.map +1 -0
- package/dist/categories/index.js +17 -0
- package/dist/categories/index.js.map +1 -0
- package/dist/categories/redirects.d.ts +8 -0
- package/dist/categories/redirects.d.ts.map +1 -0
- package/dist/categories/redirects.js +21 -0
- package/dist/categories/redirects.js.map +1 -0
- package/dist/categories/regression.d.ts +8 -0
- package/dist/categories/regression.d.ts.map +1 -0
- package/dist/categories/regression.js +28 -0
- package/dist/categories/regression.js.map +1 -0
- package/dist/categories/session.d.ts +8 -0
- package/dist/categories/session.d.ts.map +1 -0
- package/dist/categories/session.js +23 -0
- package/dist/categories/session.js.map +1 -0
- package/dist/categories/tls.d.ts +10 -0
- package/dist/categories/tls.d.ts.map +1 -0
- package/dist/categories/tls.js +48 -0
- package/dist/categories/tls.js.map +1 -0
- package/dist/categories/transport.d.ts +8 -0
- package/dist/categories/transport.d.ts.map +1 -0
- package/dist/categories/transport.js +22 -0
- package/dist/categories/transport.js.map +1 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +28 -0
- package/dist/errors.js.map +1 -0
- package/dist/fingerprint/index.d.ts +5 -0
- package/dist/fingerprint/index.d.ts.map +1 -0
- package/dist/fingerprint/index.js +4 -0
- package/dist/fingerprint/index.js.map +1 -0
- package/dist/fingerprint/ja3.d.ts +36 -0
- package/dist/fingerprint/ja3.d.ts.map +1 -0
- package/dist/fingerprint/ja3.js +141 -0
- package/dist/fingerprint/ja3.js.map +1 -0
- package/dist/fingerprint/ja4.d.ts +109 -0
- package/dist/fingerprint/ja4.d.ts.map +1 -0
- package/dist/fingerprint/ja4.js +325 -0
- package/dist/fingerprint/ja4.js.map +1 -0
- package/dist/golden/golden.d.ts +45 -0
- package/dist/golden/golden.d.ts.map +1 -0
- package/dist/golden/golden.js +216 -0
- package/dist/golden/golden.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/reference/node-reference.d.ts +83 -0
- package/dist/reference/node-reference.d.ts.map +1 -0
- package/dist/reference/node-reference.js +161 -0
- package/dist/reference/node-reference.js.map +1 -0
- package/dist/reference/reference.d.ts +143 -0
- package/dist/reference/reference.d.ts.map +1 -0
- package/dist/reference/reference.js +323 -0
- package/dist/reference/reference.js.map +1 -0
- package/dist/rfc/rfcTests.d.ts +14 -0
- package/dist/rfc/rfcTests.d.ts.map +1 -0
- package/dist/rfc/rfcTests.js +22 -0
- package/dist/rfc/rfcTests.js.map +1 -0
- package/dist/types.d.ts +140 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +34 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +29 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +102 -0
- package/dist/utils.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Categories 16–17 — Regression Tests, Performance Benchmarks.
|
|
3
|
+
*
|
|
4
|
+
* See docs/TEST-SUITE.md for the full acceptance criteria of each category.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it } from "vitest";
|
|
7
|
+
import { TestCategory } from "../types.js";
|
|
8
|
+
export const CATEGORY_ID_REGRESSION = TestCategory.Regression;
|
|
9
|
+
export const CATEGORY_ID_BENCHMARKS = TestCategory.PerformanceBenchmarks;
|
|
10
|
+
describe(CATEGORY_ID_REGRESSION, () => {
|
|
11
|
+
it.todo("every discovered issue ships a permanent regression test");
|
|
12
|
+
it.todo("regression test records serialized packets");
|
|
13
|
+
it.todo("regression test records expected parser output");
|
|
14
|
+
it.todo("regression test records expected state transitions");
|
|
15
|
+
it.todo("regression test records expected network behavior");
|
|
16
|
+
it.todo("previously fixed issues do not reappear unnoticed");
|
|
17
|
+
});
|
|
18
|
+
describe(CATEGORY_ID_BENCHMARKS, () => {
|
|
19
|
+
it.todo("measure handshake latency");
|
|
20
|
+
it.todo("measure requests per second");
|
|
21
|
+
it.todo("measure memory usage");
|
|
22
|
+
it.todo("measure CPU usage");
|
|
23
|
+
it.todo("measure packet allocation");
|
|
24
|
+
it.todo("measure TLS throughput");
|
|
25
|
+
it.todo("measure HTTP/2 throughput");
|
|
26
|
+
it.todo("benchmarks are tracked over time to identify regressions");
|
|
27
|
+
});
|
|
28
|
+
//# sourceMappingURL=regression.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"regression.js","sourceRoot":"","sources":["../../src/categories/regression.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,CAAC,MAAM,sBAAsB,GAAG,YAAY,CAAC,UAAU,CAAC;AAC9D,MAAM,CAAC,MAAM,sBAAsB,GAAG,YAAY,CAAC,qBAAqB,CAAC;AAEzE,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;IACpE,EAAE,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IACtD,EAAE,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;IAC1D,EAAE,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;IAC9D,EAAE,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;IAC7D,EAAE,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;AACjE,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IACrC,EAAE,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IACvC,EAAE,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAChC,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAC7B,EAAE,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IACrC,EAAE,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;IAClC,EAAE,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IACrC,EAAE,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;AACxE,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Categories 11–12 — Session Resumption, Connection Reuse.
|
|
3
|
+
*
|
|
4
|
+
* See docs/TEST-SUITE.md for the full acceptance criteria of each category.
|
|
5
|
+
*/
|
|
6
|
+
export declare const CATEGORY_ID_SESSION: "session-resumption";
|
|
7
|
+
export declare const CATEGORY_ID_REUSE: "connection-reuse";
|
|
8
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/categories/session.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,eAAO,MAAM,mBAAmB,sBAAiC,CAAC;AAClE,eAAO,MAAM,iBAAiB,oBAA+B,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Categories 11–12 — Session Resumption, Connection Reuse.
|
|
3
|
+
*
|
|
4
|
+
* See docs/TEST-SUITE.md for the full acceptance criteria of each category.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it } from "vitest";
|
|
7
|
+
import { TestCategory } from "../types.js";
|
|
8
|
+
export const CATEGORY_ID_SESSION = TestCategory.SessionResumption;
|
|
9
|
+
export const CATEGORY_ID_REUSE = TestCategory.ConnectionReuse;
|
|
10
|
+
describe(CATEGORY_ID_SESSION, () => {
|
|
11
|
+
it.todo("TLS session tickets");
|
|
12
|
+
it.todo("session cache");
|
|
13
|
+
it.todo("resumed handshake");
|
|
14
|
+
it.todo("ticket expiration");
|
|
15
|
+
});
|
|
16
|
+
describe(CATEGORY_ID_REUSE, () => {
|
|
17
|
+
it.todo("Keep-Alive");
|
|
18
|
+
it.todo("HTTP/2 multiplexing");
|
|
19
|
+
it.todo("connection pooling");
|
|
20
|
+
it.todo("idle timeout");
|
|
21
|
+
it.todo("maximum streams");
|
|
22
|
+
});
|
|
23
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/categories/session.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,CAAC,MAAM,mBAAmB,GAAG,YAAY,CAAC,iBAAiB,CAAC;AAClE,MAAM,CAAC,MAAM,iBAAiB,GAAG,YAAY,CAAC,eAAe,CAAC;AAE9D,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAC/B,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IACzB,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAC7B,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;AACjC,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACtB,EAAE,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAC/B,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IAC9B,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACxB,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;AAC/B,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Categories 2–4 — TLS Serialization, Browser Profile Comparison,
|
|
3
|
+
* TLS Fingerprint.
|
|
4
|
+
*
|
|
5
|
+
* See docs/TEST-SUITE.md for the full acceptance criteria of each category.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CATEGORY_ID_TLS: "tls-serialization";
|
|
8
|
+
export declare const CATEGORY_ID_PROFILE: "browser-profile-comparison";
|
|
9
|
+
export declare const CATEGORY_ID_FINGERPRINT: "tls-fingerprint";
|
|
10
|
+
//# sourceMappingURL=tls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tls.d.ts","sourceRoot":"","sources":["../../src/categories/tls.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,eAAO,MAAM,eAAe,qBAAgC,CAAC;AAC7D,eAAO,MAAM,mBAAmB,8BAAwC,CAAC;AACzE,eAAO,MAAM,uBAAuB,mBAA8B,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Categories 2–4 — TLS Serialization, Browser Profile Comparison,
|
|
3
|
+
* TLS Fingerprint.
|
|
4
|
+
*
|
|
5
|
+
* See docs/TEST-SUITE.md for the full acceptance criteria of each category.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it } from "vitest";
|
|
8
|
+
import { TestCategory } from "../types.js";
|
|
9
|
+
export const CATEGORY_ID_TLS = TestCategory.TlsSerialization;
|
|
10
|
+
export const CATEGORY_ID_PROFILE = TestCategory.BrowserProfileComparison;
|
|
11
|
+
export const CATEGORY_ID_FINGERPRINT = TestCategory.TlsFingerprint;
|
|
12
|
+
describe(CATEGORY_ID_TLS, () => {
|
|
13
|
+
it.todo("ClientHello serialization");
|
|
14
|
+
it.todo("ServerHello parsing");
|
|
15
|
+
it.todo("extension parsing");
|
|
16
|
+
it.todo("record fragmentation");
|
|
17
|
+
it.todo("Finished message generation");
|
|
18
|
+
it.todo("alert handling");
|
|
19
|
+
it.todo("key schedule generation");
|
|
20
|
+
it.todo("session resumption");
|
|
21
|
+
});
|
|
22
|
+
describe(CATEGORY_ID_PROFILE, () => {
|
|
23
|
+
it.todo("ClientHello: TLS version matches profile");
|
|
24
|
+
it.todo("ClientHello: cipher suite list + ordering matches profile");
|
|
25
|
+
it.todo("ClientHello: extension ordering matches profile");
|
|
26
|
+
it.todo("ClientHello: supported groups match profile");
|
|
27
|
+
it.todo("ClientHello: key shares match profile");
|
|
28
|
+
it.todo("ClientHello: signature algorithms match profile");
|
|
29
|
+
it.todo("ClientHello: ALPN list matches profile");
|
|
30
|
+
it.todo("ClientHello: GREASE values match profile");
|
|
31
|
+
it.todo("ClientHello: session ID length matches profile");
|
|
32
|
+
it.todo("ClientHello: compression methods match profile");
|
|
33
|
+
it.todo("ClientHello: PSK extension matches profile");
|
|
34
|
+
it.todo("ClientHello: padding extension matches profile");
|
|
35
|
+
it.todo("ClientHello: SNI extension matches profile");
|
|
36
|
+
it.todo("ClientHello: record version matches profile");
|
|
37
|
+
it.todo("ClientHello: record fragmentation matches profile");
|
|
38
|
+
});
|
|
39
|
+
describe(CATEGORY_ID_FINGERPRINT, () => {
|
|
40
|
+
it.todo("JA3 fingerprint matches configured profile");
|
|
41
|
+
it.todo("JA4 fingerprint matches configured profile (where applicable)");
|
|
42
|
+
it.todo("ALPN negotiation matches configured profile");
|
|
43
|
+
it.todo("negotiated cipher suite matches configured profile");
|
|
44
|
+
it.todo("negotiated protocol version matches configured profile");
|
|
45
|
+
it.todo("supported signature algorithms match configured profile");
|
|
46
|
+
it.todo("supported elliptic curves match configured profile");
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=tls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tls.js","sourceRoot":"","sources":["../../src/categories/tls.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC,gBAAgB,CAAC;AAC7D,MAAM,CAAC,MAAM,mBAAmB,GAAG,YAAY,CAAC,wBAAwB,CAAC;AACzE,MAAM,CAAC,MAAM,uBAAuB,GAAG,YAAY,CAAC,cAAc,CAAC;AAEnE,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IACrC,EAAE,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAC/B,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAC7B,EAAE,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAChC,EAAE,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IACvC,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC1B,EAAE,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IACnC,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AAClC,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;IACpD,EAAE,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IACrE,EAAE,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;IAC3D,EAAE,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;IACvD,EAAE,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;IACjD,EAAE,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;IAC3D,EAAE,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;IAClD,EAAE,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;IACpD,EAAE,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;IAC1D,EAAE,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;IAC1D,EAAE,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IACtD,EAAE,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;IAC1D,EAAE,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IACtD,EAAE,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;IACvD,EAAE,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;AACjE,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IACtD,EAAE,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;IACzE,EAAE,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;IACvD,EAAE,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;IAC9D,EAAE,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;IAClE,EAAE,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;IACnE,EAAE,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Category 1 — TCP Transport.
|
|
3
|
+
*
|
|
4
|
+
* Verify transport layer behaves correctly. See docs/TEST-SUITE.md
|
|
5
|
+
* ("Test Category 1 — TCP Transport") for full acceptance criteria.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CATEGORY_ID: "tcp-transport";
|
|
8
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/categories/transport.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,eAAO,MAAM,WAAW,iBAA4B,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Category 1 — TCP Transport.
|
|
3
|
+
*
|
|
4
|
+
* Verify transport layer behaves correctly. See docs/TEST-SUITE.md
|
|
5
|
+
* ("Test Category 1 — TCP Transport") for full acceptance criteria.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it } from "vitest";
|
|
8
|
+
import { TestCategory } from "../types.js";
|
|
9
|
+
export const CATEGORY_ID = TestCategory.TcpTransport;
|
|
10
|
+
describe(CATEGORY_ID, () => {
|
|
11
|
+
it.todo("TCP connection establishment");
|
|
12
|
+
it.todo("connection timeout handling");
|
|
13
|
+
it.todo("read buffering");
|
|
14
|
+
it.todo("partial packet handling");
|
|
15
|
+
it.todo("large packet handling");
|
|
16
|
+
it.todo("connection reuse");
|
|
17
|
+
it.todo("graceful shutdown");
|
|
18
|
+
it.todo("unexpected disconnect handling");
|
|
19
|
+
it.todo("IPv4 support");
|
|
20
|
+
it.todo("IPv6 support");
|
|
21
|
+
});
|
|
22
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/categories/transport.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC,YAAY,CAAC;AAErD,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACvB,EAAE,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;IACxC,EAAE,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IACvC,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC1B,EAAE,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IACnC,EAAE,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACjC,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAC5B,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAC7B,EAAE,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;IAC1C,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACxB,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AAC5B,CAAC,CAAC,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed errors for @browsercore/testing.
|
|
3
|
+
*/
|
|
4
|
+
/** Base class for every testing error. */
|
|
5
|
+
export declare class TestingError extends Error {
|
|
6
|
+
readonly kind: "TestingError";
|
|
7
|
+
readonly cause: Error | undefined;
|
|
8
|
+
constructor(message: string, options?: {
|
|
9
|
+
cause?: Error;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
/** Actual bytes diverged from a golden capture. */
|
|
13
|
+
export declare class GoldenMismatchError extends Error {
|
|
14
|
+
readonly kind: "GoldenMismatchError";
|
|
15
|
+
readonly captureId: string;
|
|
16
|
+
readonly divergenceByteIndex: number;
|
|
17
|
+
readonly cause: Error | undefined;
|
|
18
|
+
constructor(captureId: string, divergenceByteIndex: number, options?: {
|
|
19
|
+
cause?: Error;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +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;gBAEtC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE;CAK3D;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;gBAG9C,SAAS,EAAE,MAAM,EACjB,mBAAmB,EAAE,MAAM,EAC3B,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE;CAWlC"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed errors for @browsercore/testing.
|
|
3
|
+
*/
|
|
4
|
+
/** Base class for every testing error. */
|
|
5
|
+
export class TestingError extends Error {
|
|
6
|
+
kind = "TestingError";
|
|
7
|
+
cause;
|
|
8
|
+
constructor(message, options) {
|
|
9
|
+
super(message, options);
|
|
10
|
+
this.name = new.target.name;
|
|
11
|
+
this.cause = options?.cause;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Actual bytes diverged from a golden capture. */
|
|
15
|
+
export class GoldenMismatchError extends Error {
|
|
16
|
+
kind = "GoldenMismatchError";
|
|
17
|
+
captureId;
|
|
18
|
+
divergenceByteIndex;
|
|
19
|
+
cause;
|
|
20
|
+
constructor(captureId, divergenceByteIndex, options) {
|
|
21
|
+
super(`Golden capture ${captureId} diverges at byte ${divergenceByteIndex}`, options);
|
|
22
|
+
this.name = "GoldenMismatchError";
|
|
23
|
+
this.captureId = captureId;
|
|
24
|
+
this.divergenceByteIndex = divergenceByteIndex;
|
|
25
|
+
this.cause = options?.cause;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +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,GAAG,CAAC,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fingerprint/index.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACvE,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/fingerprint/index.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEvE,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JA3 / JA4 TLS fingerprinting (docs/TEST-SUITE.md, Category 4).
|
|
3
|
+
*
|
|
4
|
+
* JA3 (https://github.com/salesforce/ja3) hashes the decimal representation of
|
|
5
|
+
* a ClientHello's selectable fields. Format string:
|
|
6
|
+
*
|
|
7
|
+
* SSLVersion,CipherSuites,Extensions,SupportedGroups,EllipticCurvePointFormats
|
|
8
|
+
*
|
|
9
|
+
* each field is a dash-joined list of integers; the whole string is MD5-hashed
|
|
10
|
+
* to produce the JA3 digest. This implementation parses the ClientHello out of
|
|
11
|
+
* its TLS record wrapper.
|
|
12
|
+
*/
|
|
13
|
+
/** Reasons a ClientHello cannot be parsed into a JA3 input. */
|
|
14
|
+
export declare class Ja3ParseError extends Error {
|
|
15
|
+
readonly kind: "Ja3ParseError";
|
|
16
|
+
constructor(message: string);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parse a TLS ClientHello from a TLS record (or handshake) buffer and return
|
|
20
|
+
* the dash-jojoined JA3 input segments plus the final MD5 digest.
|
|
21
|
+
*
|
|
22
|
+
* Accepts either a full TLS record (record type 0x16) or a bare handshake
|
|
23
|
+
* (handshake type 0x01).
|
|
24
|
+
*/
|
|
25
|
+
export declare function computeJa3(clientHello: Uint8Array): string;
|
|
26
|
+
/** JA3 input segments (for inspection/testing). */
|
|
27
|
+
export interface Ja3Segments {
|
|
28
|
+
readonly version: string;
|
|
29
|
+
readonly ciphers: string;
|
|
30
|
+
readonly extensions: string;
|
|
31
|
+
readonly supportedGroups: string;
|
|
32
|
+
readonly ecPointFormats: string;
|
|
33
|
+
}
|
|
34
|
+
/** Parse a ClientHello into its five JA3 segments. */
|
|
35
|
+
export declare function parseClientHello(clientHello: Uint8Array): Ja3Segments;
|
|
36
|
+
//# sourceMappingURL=ja3.d.ts.map
|
|
@@ -0,0 +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;gBACpC,OAAO,EAAE,MAAM;CAI9B;AAOD;;;;;;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,CAgGrE"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JA3 / JA4 TLS fingerprinting (docs/TEST-SUITE.md, Category 4).
|
|
3
|
+
*
|
|
4
|
+
* JA3 (https://github.com/salesforce/ja3) hashes the decimal representation of
|
|
5
|
+
* a ClientHello's selectable fields. Format string:
|
|
6
|
+
*
|
|
7
|
+
* SSLVersion,CipherSuites,Extensions,SupportedGroups,EllipticCurvePointFormats
|
|
8
|
+
*
|
|
9
|
+
* each field is a dash-joined list of integers; the whole string is MD5-hashed
|
|
10
|
+
* to produce the JA3 digest. This implementation parses the ClientHello out of
|
|
11
|
+
* its TLS record wrapper.
|
|
12
|
+
*/
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
/** Reasons a ClientHello cannot be parsed into a JA3 input. */
|
|
15
|
+
export class Ja3ParseError extends Error {
|
|
16
|
+
kind = "Ja3ParseError";
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "Ja3ParseError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Read a big-endian uint16 at `offset` in `buf`. */
|
|
23
|
+
function uint16(buf, offset) {
|
|
24
|
+
return (buf[offset] << 8) | buf[offset + 1];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parse a TLS ClientHello from a TLS record (or handshake) buffer and return
|
|
28
|
+
* the dash-jojoined JA3 input segments plus the final MD5 digest.
|
|
29
|
+
*
|
|
30
|
+
* Accepts either a full TLS record (record type 0x16) or a bare handshake
|
|
31
|
+
* (handshake type 0x01).
|
|
32
|
+
*/
|
|
33
|
+
export function computeJa3(clientHello) {
|
|
34
|
+
const s = parseClientHello(clientHello);
|
|
35
|
+
const ja3String = [s.version, s.ciphers, s.extensions, s.supportedGroups, s.ecPointFormats].join(",");
|
|
36
|
+
return createHash("md5").update(ja3String).digest("hex");
|
|
37
|
+
}
|
|
38
|
+
/** Parse a ClientHello into its five JA3 segments. */
|
|
39
|
+
export function parseClientHello(clientHello) {
|
|
40
|
+
let pos;
|
|
41
|
+
let handshakeLen;
|
|
42
|
+
// Detect TLS record wrapper (ContentType handshake = 0x16).
|
|
43
|
+
if (clientHello[0] === 0x16) {
|
|
44
|
+
if (clientHello.length < 5) {
|
|
45
|
+
throw new Ja3ParseError("TLS record too short");
|
|
46
|
+
}
|
|
47
|
+
// offset 3..4 = record length; handshake starts at 5
|
|
48
|
+
pos = 5;
|
|
49
|
+
if (clientHello[pos] !== 0x01) {
|
|
50
|
+
throw new Ja3ParseError(`Expected ClientHello (0x01) at record+0, got 0x${clientHello[pos]?.toString(16)}`);
|
|
51
|
+
}
|
|
52
|
+
handshakeLen = readInt24(clientHello, pos + 1);
|
|
53
|
+
pos += 4; // handshake type(1) + length(3)
|
|
54
|
+
const available = clientHello.length - pos;
|
|
55
|
+
if (handshakeLen > available) {
|
|
56
|
+
throw new Ja3ParseError(`Handshake length ${handshakeLen} exceeds available ${available} bytes`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else if (clientHello[0] === 0x01) {
|
|
60
|
+
handshakeLen = readInt24(clientHello, 1);
|
|
61
|
+
pos = 4;
|
|
62
|
+
const available = clientHello.length - pos;
|
|
63
|
+
if (handshakeLen > available) {
|
|
64
|
+
throw new Ja3ParseError(`Handshake length ${handshakeLen} exceeds available ${available} bytes`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
throw new Ja3ParseError(`Not a TLS record or ClientHello (first byte 0x${clientHello[0]?.toString(16)})`);
|
|
69
|
+
}
|
|
70
|
+
const end = pos + handshakeLen;
|
|
71
|
+
// client_version(2)
|
|
72
|
+
const version = uint16(clientHello, pos);
|
|
73
|
+
pos += 2;
|
|
74
|
+
// random(32)
|
|
75
|
+
pos += 32;
|
|
76
|
+
// session_id(variable)
|
|
77
|
+
const sessionIdLen = clientHello[pos];
|
|
78
|
+
pos += 1 + sessionIdLen;
|
|
79
|
+
// cipher_suites(variable)
|
|
80
|
+
const cipherSuitesLen = uint16(clientHello, pos);
|
|
81
|
+
pos += 2;
|
|
82
|
+
const ciphers = readUint16List(clientHello, pos, cipherSuitesLen);
|
|
83
|
+
pos += cipherSuitesLen;
|
|
84
|
+
// compression_methods(variable)
|
|
85
|
+
const compLen = clientHello[pos];
|
|
86
|
+
pos += 1 + compLen;
|
|
87
|
+
// extensions(variable)
|
|
88
|
+
if (pos + 2 > end) {
|
|
89
|
+
// No extensions present.
|
|
90
|
+
return {
|
|
91
|
+
version: String(version),
|
|
92
|
+
ciphers: ciphers.join("-"),
|
|
93
|
+
extensions: "",
|
|
94
|
+
supportedGroups: "",
|
|
95
|
+
ecPointFormats: "",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const extensionsLen = uint16(clientHello, pos);
|
|
99
|
+
pos += 2;
|
|
100
|
+
const extensionsEnd = pos + extensionsLen;
|
|
101
|
+
const extensionTypes = [];
|
|
102
|
+
const supportedGroups = [];
|
|
103
|
+
const ecPointFormats = [];
|
|
104
|
+
while (pos + 4 <= extensionsEnd) {
|
|
105
|
+
const extType = uint16(clientHello, pos);
|
|
106
|
+
const extLen = uint16(clientHello, pos + 2);
|
|
107
|
+
pos += 4;
|
|
108
|
+
extensionTypes.push(extType);
|
|
109
|
+
if (extType === 0x000a && extLen >= 4) {
|
|
110
|
+
// supported_groups(10): list of uint16 group ids
|
|
111
|
+
const listLen = uint16(clientHello, pos);
|
|
112
|
+
supportedGroups.push(...readUint16List(clientHello, pos + 2, listLen));
|
|
113
|
+
}
|
|
114
|
+
else if (extType === 0x000b && extLen >= 1) {
|
|
115
|
+
// ec_point_formats(11): list of uint8 formats
|
|
116
|
+
const listLen = clientHello[pos];
|
|
117
|
+
for (let i = 0; i < listLen; i++) {
|
|
118
|
+
ecPointFormats.push(clientHello[pos + 1 + i]);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
pos += extLen;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
version: String(version),
|
|
125
|
+
ciphers: ciphers.join("-"),
|
|
126
|
+
extensions: extensionTypes.join("-"),
|
|
127
|
+
supportedGroups: supportedGroups.join("-"),
|
|
128
|
+
ecPointFormats: ecPointFormats.join("-"),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function readInt24(buf, offset) {
|
|
132
|
+
return (buf[offset] << 16) | (buf[offset + 1] << 8) | buf[offset + 2];
|
|
133
|
+
}
|
|
134
|
+
function readUint16List(buf, offset, byteLen) {
|
|
135
|
+
const out = [];
|
|
136
|
+
for (let i = 0; i + 1 < byteLen; i += 2) {
|
|
137
|
+
out.push((buf[offset + i] << 8) | buf[offset + i + 1]);
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=ja3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ja3.js","sourceRoot":"","sources":["../../src/fingerprint/ja3.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,+DAA+D;AAC/D,MAAM,OAAO,aAAc,SAAQ,KAAK;IACpB,IAAI,GAAG,eAAwB,CAAC;IAChD,YAAY,OAAe;QACvB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAChC,CAAC;CACJ;AAED,qDAAqD;AACrD,SAAS,MAAM,CAAC,GAAe,EAAE,MAAc;IAC3C,OAAO,CAAC,GAAG,CAAC,MAAM,CAAE,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;AAClD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,WAAuB;IAC9C,MAAM,CAAC,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,IAAI,CAC5F,GAAG,CACN,CAAC;IACF,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC;AAWD,sDAAsD;AACtD,MAAM,UAAU,gBAAgB,CAAC,WAAuB;IACpD,IAAI,GAAW,CAAC;IAChB,IAAI,YAAoB,CAAC;IAEzB,4DAA4D;IAC5D,IAAI,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC1B,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,aAAa,CAAC,sBAAsB,CAAC,CAAC;QACpD,CAAC;QACD,qDAAqD;QACrD,GAAG,GAAG,CAAC,CAAC;QACR,IAAI,WAAW,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;YAC5B,MAAM,IAAI,aAAa,CAAC,kDAAkD,WAAW,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAChH,CAAC;QACD,YAAY,GAAG,SAAS,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;QAC/C,GAAG,IAAI,CAAC,CAAC,CAAC,gCAAgC;QAC1C,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,GAAG,GAAG,CAAC;QAC3C,IAAI,YAAY,GAAG,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,aAAa,CAAC,oBAAoB,YAAY,sBAAsB,SAAS,QAAQ,CAAC,CAAC;QACrG,CAAC;IACL,CAAC;SAAM,IAAI,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACjC,YAAY,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QACzC,GAAG,GAAG,CAAC,CAAC;QACR,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,GAAG,GAAG,CAAC;QAC3C,IAAI,YAAY,GAAG,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,aAAa,CAAC,oBAAoB,YAAY,sBAAsB,SAAS,QAAQ,CAAC,CAAC;QACrG,CAAC;IACL,CAAC;SAAM,CAAC;QACJ,MAAM,IAAI,aAAa,CAAC,iDAAiD,WAAW,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAC9G,CAAC;IAED,MAAM,GAAG,GAAG,GAAG,GAAG,YAAY,CAAC;IAE/B,oBAAoB;IACpB,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IACzC,GAAG,IAAI,CAAC,CAAC;IACT,aAAa;IACb,GAAG,IAAI,EAAE,CAAC;IACV,uBAAuB;IACvB,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,CAAE,CAAC;IACvC,GAAG,IAAI,CAAC,GAAG,YAAY,CAAC;IACxB,0BAA0B;IAC1B,MAAM,eAAe,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IACjD,GAAG,IAAI,CAAC,CAAC;IACT,MAAM,OAAO,GAAG,cAAc,CAAC,WAAW,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;IAClE,GAAG,IAAI,eAAe,CAAC;IACvB,gCAAgC;IAChC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAE,CAAC;IAClC,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC;IACnB,uBAAuB;IACvB,IAAI,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC;QAChB,yBAAyB;QACzB,OAAO;YACH,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC;YACxB,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;YAC1B,UAAU,EAAE,EAAE;YACd,eAAe,EAAE,EAAE;YACnB,cAAc,EAAE,EAAE;SACrB,CAAC;IACN,CAAC;IACD,MAAM,aAAa,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IAC/C,GAAG,IAAI,CAAC,CAAC;IACT,MAAM,aAAa,GAAG,GAAG,GAAG,aAAa,CAAC;IAE1C,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,MAAM,eAAe,GAAa,EAAE,CAAC;IACrC,MAAM,cAAc,GAAa,EAAE,CAAC;IAEpC,OAAO,GAAG,GAAG,CAAC,IAAI,aAAa,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;QAC5C,GAAG,IAAI,CAAC,CAAC;QACT,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAE7B,IAAI,OAAO,KAAK,MAAM,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;YACpC,iDAAiD;YACjD,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;YACzC,eAAe,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QAC3E,CAAC;aAAM,IAAI,OAAO,KAAK,MAAM,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;YAC3C,8CAA8C;YAC9C,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAE,CAAC;YAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC/B,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC;YACnD,CAAC;QACL,CAAC;QAED,GAAG,IAAI,MAAM,CAAC;IAClB,CAAC;IAED,OAAO;QACH,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC;QACxB,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;QAC1B,UAAU,EAAE,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC;QACpC,eAAe,EAAE,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC;QAC1C,cAAc,EAAE,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC;KAC3C,CAAC;AACN,CAAC;AAED,SAAS,SAAS,CAAC,GAAe,EAAE,MAAc;IAC9C,OAAO,CAAC,GAAG,CAAC,MAAM,CAAE,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAE,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;AAC7E,CAAC;AAED,SAAS,cAAc,CAAC,GAAe,EAAE,MAAc,EAAE,OAAe;IACpE,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAE,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JA4 TLS fingerprint (docs/TEST-SUITE.md, Category 4).
|
|
3
|
+
*
|
|
4
|
+
* JA4 is a multi-part fingerprint family that captures significantly more signal
|
|
5
|
+
* than JA3. We compute the standard four-part TLS fingerprint:
|
|
6
|
+
*
|
|
7
|
+
* JA4_a connection prefix — transport, TLS version, SNI flag, cipher count,
|
|
8
|
+
* extension count, ALPN code.
|
|
9
|
+
* JA4_b cipher-suite hash — sorted non-GREASE cipher suites, SHA-256,
|
|
10
|
+
* first 12 hex chars.
|
|
11
|
+
* JA4_c extension hash — sorted non-GREASE extensions (excluding SNI +
|
|
12
|
+
* ALPN), SHA-256, first 12 hex chars.
|
|
13
|
+
* JA4_f raw-fields hash — TLS version, ciphers, extensions, supported
|
|
14
|
+
* groups, EC point formats, SHA-256, first 12 hex.
|
|
15
|
+
*
|
|
16
|
+
* The concatenated `JA4_a_JA4_b_JA4_c_JA4_f` string is the canonical JA4 tag.
|
|
17
|
+
*
|
|
18
|
+
* JA4H (the HTTP-layer sibling) is computed separately by {@link computeJa4h}
|
|
19
|
+
* from method/version/headers/cookies — it is the HTTP analogue used by the
|
|
20
|
+
* reference provider to fingerprint a captured request.
|
|
21
|
+
*
|
|
22
|
+
* Reference: https://github.com/FoxIO-LLC/JA4
|
|
23
|
+
*/
|
|
24
|
+
/** Reasons a ClientHello cannot be parsed into a JA4 input. */
|
|
25
|
+
export declare class Ja4ParseError extends Error {
|
|
26
|
+
readonly kind: "Ja4ParseError";
|
|
27
|
+
constructor(message: string);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parsed fields from a ClientHello relevant to JA4.
|
|
31
|
+
*
|
|
32
|
+
* `cipherSuites` and `extensions` are the raw (non-GREASE) numeric values;
|
|
33
|
+
* `supportedGroups` and `ecPointFormats` are the raw values from their
|
|
34
|
+
* respective extensions; `alpnRaw` is the raw ALPN protocol string list.
|
|
35
|
+
*/
|
|
36
|
+
export interface Ja4ClientHello {
|
|
37
|
+
readonly tlsVersion: string;
|
|
38
|
+
readonly sniPresent: boolean;
|
|
39
|
+
readonly cipherSuites: readonly number[];
|
|
40
|
+
readonly extensions: readonly number[];
|
|
41
|
+
readonly supportedGroups: readonly number[];
|
|
42
|
+
readonly ecPointFormats: readonly number[];
|
|
43
|
+
readonly alpnRaw: string;
|
|
44
|
+
}
|
|
45
|
+
/** The four computed JA4 parts. */
|
|
46
|
+
export interface Ja4Fingerprint {
|
|
47
|
+
readonly a: string;
|
|
48
|
+
readonly b: string;
|
|
49
|
+
readonly c: string;
|
|
50
|
+
readonly f: string;
|
|
51
|
+
/** Canonical `JA4_a_JA4_b_JA4_c_JA4_f` tag. */
|
|
52
|
+
readonly tag: string;
|
|
53
|
+
}
|
|
54
|
+
/** The three computed JA4H parts. */
|
|
55
|
+
export interface Ja4hFingerprint {
|
|
56
|
+
readonly a: string;
|
|
57
|
+
readonly b: string;
|
|
58
|
+
readonly c: string;
|
|
59
|
+
readonly d: string;
|
|
60
|
+
/** Canonical `JA4H_a_JA4H_b_JA4H_c_JA4H_d` tag. */
|
|
61
|
+
readonly tag: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Parse a TLS ClientHello (either wrapped in a TLS record or bare) into the
|
|
65
|
+
* fields JA4 needs. Throws {@link Ja4ParseError} on malformed input.
|
|
66
|
+
*/
|
|
67
|
+
export declare function parseJa4ClientHello(clientHello: Uint8Array): Ja4ClientHello;
|
|
68
|
+
/**
|
|
69
|
+
* Compute the canonical JA4 tag (`JA4_a_JA4_b_JA4_c_JA4_f`) from a ClientHello.
|
|
70
|
+
*
|
|
71
|
+
* This is the string form used by the public API (index.ts) and the reference
|
|
72
|
+
* provider. Use {@link computeJa4Fingerprint} when you need the parts.
|
|
73
|
+
*/
|
|
74
|
+
export declare function computeJa4(clientHello: Uint8Array): string;
|
|
75
|
+
/**
|
|
76
|
+
* Compute the four-part JA4 TLS fingerprint from a ClientHello buffer.
|
|
77
|
+
*
|
|
78
|
+
* Returns the individual parts plus the canonical `JA4_a_JA4_b_JA4_c_JA4_f`
|
|
79
|
+
* tag. The caller can use any part — comparison is typically done against the
|
|
80
|
+
* full tag or against JA4_a (the connection prefix) for grouping.
|
|
81
|
+
*/
|
|
82
|
+
export declare function computeJa4Fingerprint(clientHello: Uint8Array): Ja4Fingerprint;
|
|
83
|
+
/**
|
|
84
|
+
* HTTP request fields needed to compute a JA4H fingerprint.
|
|
85
|
+
*
|
|
86
|
+
* Only the parts JA4H inspects are required — method, HTTP version, header
|
|
87
|
+
* names (in order), cookies, and the Accept-Language prefix.
|
|
88
|
+
*/
|
|
89
|
+
export interface Ja4hRequest {
|
|
90
|
+
readonly method: "GET" | "POST" | "HEAD" | "PUT" | "DELETE" | "OPTIONS" | "PATCH";
|
|
91
|
+
readonly httpVersion: "1.1" | "2" | "3";
|
|
92
|
+
/** Header names in the order they are serialized (lowercased). */
|
|
93
|
+
readonly headerNames: readonly string[];
|
|
94
|
+
/** Cookie name=value pairs (as they appear in the Cookie header). */
|
|
95
|
+
readonly cookies: readonly string[];
|
|
96
|
+
/** The Accept-Language header value (only its 4-char prefix is used). */
|
|
97
|
+
readonly acceptLanguage?: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Compute the four-part JA4H HTTP fingerprint from a captured request.
|
|
101
|
+
*
|
|
102
|
+
* Format: `JA4H_a_JA4H_b_JA4H_c_JA4H_d` where:
|
|
103
|
+
* - JA4H_a: `{method:02s}{version}{has_cookies:d}{has_referer:d}{header_count:02d}{lang_prefix}`
|
|
104
|
+
* - JA4H_b: sorted cookie name=value pairs, SHA-256, first 12 hex.
|
|
105
|
+
* - JA4H_c: sorted cookie names, SHA-256, first 12 hex.
|
|
106
|
+
* - JA4H_d: sorted header names (excluding cookie), SHA-256, first 12 hex.
|
|
107
|
+
*/
|
|
108
|
+
export declare function computeJa4h(request: Ja4hRequest): Ja4hFingerprint;
|
|
109
|
+
//# sourceMappingURL=ja4.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ja4.d.ts","sourceRoot":"","sources":["../../src/fingerprint/ja4.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,+DAA+D;AAC/D,qBAAa,aAAc,SAAQ,KAAK;IACpC,SAAgB,IAAI,EAAG,eAAe,CAAU;gBACpC,OAAO,EAAE,MAAM;CAI9B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC5B;AAED,mCAAmC;AACnC,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACxB;AAED,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC5B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACxB;AA4CD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,UAAU,GAAG,cAAc,CAgL3E;AAeD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,UAAU,GAAG,MAAM,CAE1D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,UAAU,GAAG,cAAc,CAgC7E;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;IAClF,QAAQ,CAAC,WAAW,EAAE,KAAK,GAAG,GAAG,GAAG,GAAG,CAAC;IACxC,kEAAkE;IAClE,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,yEAAyE;IACzE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,eAAe,CAkCjE"}
|