@browsercore/profiles 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 +51 -0
- package/dist/diff.d.ts +37 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +140 -0
- package/dist/diff.js.map +1 -0
- package/dist/errors.d.ts +20 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +27 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.internal.d.ts +9 -0
- package/dist/index.internal.d.ts.map +1 -0
- package/dist/index.internal.js +9 -0
- package/dist/index.internal.js.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/profiles/chrome.d.ts +18 -0
- package/dist/profiles/chrome.d.ts.map +1 -0
- package/dist/profiles/chrome.js +214 -0
- package/dist/profiles/chrome.js.map +1 -0
- package/dist/profiles/edge.d.ts +17 -0
- package/dist/profiles/edge.d.ts.map +1 -0
- package/dist/profiles/edge.js +161 -0
- package/dist/profiles/edge.js.map +1 -0
- package/dist/profiles/firefox.d.ts +17 -0
- package/dist/profiles/firefox.d.ts.map +1 -0
- package/dist/profiles/firefox.js +204 -0
- package/dist/profiles/firefox.js.map +1 -0
- package/dist/profiles/safari.d.ts +16 -0
- package/dist/profiles/safari.d.ts.map +1 -0
- package/dist/profiles/safari.js +134 -0
- package/dist/profiles/safari.js.map +1 -0
- package/dist/registry.d.ts +17 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +48 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +83 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +18 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +23 -0
- package/dist/utils.js.map +1 -0
- package/dist/validate.d.ts +80 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +185 -0
- package/dist/validate.js.map +1 -0
- package/package.json +43 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain types for @browsercore/profiles.
|
|
3
|
+
*
|
|
4
|
+
* Pure data: this package defines WHAT a browser fingerprint looks like, never
|
|
5
|
+
* HOW to emit it on the wire. Protocol implementations (tls, http1, http2) read
|
|
6
|
+
* these definitions and translate them into bytes / header order / settings frames.
|
|
7
|
+
*/
|
|
8
|
+
/** Branded browser-profile identifier (e.g. "chrome-140"). */
|
|
9
|
+
export type ProfileId = string & {
|
|
10
|
+
__brand: "ProfileId";
|
|
11
|
+
};
|
|
12
|
+
/** Known browser families. Discriminated-union literal set. */
|
|
13
|
+
export type ProfileName = "chrome" | "firefox" | "safari" | "edge";
|
|
14
|
+
/** TLS-layer fingerprint: everything a TLS client hello reveals about the client. */
|
|
15
|
+
export interface TlsProfile {
|
|
16
|
+
/** Ordered list of offered cipher suites (IANA names). Order matters for fingerprinting. */
|
|
17
|
+
readonly cipherSuites: readonly string[];
|
|
18
|
+
/** Order of TLS extensions in the ClientHello. */
|
|
19
|
+
readonly extensionOrder: readonly number[];
|
|
20
|
+
/** Supported TLS versions, highest first. */
|
|
21
|
+
readonly supportedVersions: readonly string[];
|
|
22
|
+
/** Key-share groups offered (TLS 1.3). */
|
|
23
|
+
readonly keyShareGroups: readonly string[];
|
|
24
|
+
/** Signature algorithms offered. */
|
|
25
|
+
readonly signatureAlgorithms: readonly string[];
|
|
26
|
+
/** record_size_limit extension value, if advertised. Absent when not sent. */
|
|
27
|
+
readonly recordSizeLimit?: number;
|
|
28
|
+
/** Whether this browser randomizes with GREASE values (RFC 8701). */
|
|
29
|
+
readonly grease: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** HTTP/2 SETTINGS + tuning parameters. */
|
|
32
|
+
export interface Http2Profile {
|
|
33
|
+
/** HTTP/2 settings the client sends in its first SETTINGS frame. */
|
|
34
|
+
readonly settings: Partial<Http2Settings>;
|
|
35
|
+
/** Initial TCP-level / stream window size in bytes. */
|
|
36
|
+
readonly initialWindowSize: number;
|
|
37
|
+
/** SETTINGS_MAX_FRAME_SIZE advertised by the client. */
|
|
38
|
+
readonly maxFrameSize: number;
|
|
39
|
+
/** SETTINGS_HEADER_TABLE_SIZE advertised by the client. */
|
|
40
|
+
readonly headerTableSize: number;
|
|
41
|
+
/** Default stream weight (1-256). */
|
|
42
|
+
readonly weight: number;
|
|
43
|
+
/** Default priority dependency / exclusive flag, if any. */
|
|
44
|
+
readonly priority?: Http2Priority;
|
|
45
|
+
}
|
|
46
|
+
/** HTTP/2 numeric settings (RFC 9113 §6.5.1). */
|
|
47
|
+
export interface Http2Settings {
|
|
48
|
+
readonly headerTableSize: number;
|
|
49
|
+
readonly enablePush: boolean;
|
|
50
|
+
readonly maxConcurrentStreams: number;
|
|
51
|
+
readonly initialWindowSize: number;
|
|
52
|
+
readonly maxFrameSize: number;
|
|
53
|
+
readonly maxHeaderListSize: number;
|
|
54
|
+
}
|
|
55
|
+
/** HTTP/2 stream priority descriptor. */
|
|
56
|
+
export interface Http2Priority {
|
|
57
|
+
readonly streamDependency: number;
|
|
58
|
+
readonly exclusive: boolean;
|
|
59
|
+
readonly weight: number;
|
|
60
|
+
}
|
|
61
|
+
/** HTTP/1.1-layer fingerprint: header defaults and ordering. */
|
|
62
|
+
export interface Http1Profile {
|
|
63
|
+
/** Default headers sent on every request, in order. */
|
|
64
|
+
readonly defaultHeaders: Readonly<Record<string, string>>;
|
|
65
|
+
/** Order in which headers are serialized (client-enforced). */
|
|
66
|
+
readonly headerOrder: readonly string[];
|
|
67
|
+
/** Connection header value. */
|
|
68
|
+
readonly connection: "keep-alive" | "close";
|
|
69
|
+
/** Accept-Encoding value sent by default. */
|
|
70
|
+
readonly acceptEncoding: string;
|
|
71
|
+
}
|
|
72
|
+
/** A complete browser fingerprint across all layers. */
|
|
73
|
+
export interface BrowserProfile {
|
|
74
|
+
readonly id: ProfileId;
|
|
75
|
+
/** Human-readable browser name. */
|
|
76
|
+
readonly name: string;
|
|
77
|
+
/** Browser version string (e.g. "140.0.7339.18"). */
|
|
78
|
+
readonly version: string;
|
|
79
|
+
readonly tls: TlsProfile;
|
|
80
|
+
readonly http2: Http2Profile;
|
|
81
|
+
readonly http1: Http1Profile;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,8DAA8D;AAC9D,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC;AAE1D,+DAA+D;AAC/D,MAAM,MAAM,WAAW,GACjB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,MAAM,CAAC;AAEb,qFAAqF;AACrF,MAAM,WAAW,UAAU;IACvB,4FAA4F;IAC5F,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,kDAAkD;IAClD,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,6CAA6C;IAC7C,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,0CAA0C;IAC1C,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,oCAAoC;IACpC,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,8EAA8E;IAC9E,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC5B;AAED,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IACzB,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1C,uDAAuD;IACvD,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,wDAAwD;IACxD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,2DAA2D;IAC3D,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,qCAAqC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC;CACrC;AAED,iDAAiD;AACjD,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACtC;AAED,yCAAyC;AACzC,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CAC3B;AAED,gEAAgE;AAChE,MAAM,WAAW,YAAY;IACzB,uDAAuD;IACvD,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1D,+DAA+D;IAC/D,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,+BAA+B;IAC/B,QAAQ,CAAC,UAAU,EAAE,YAAY,GAAG,OAAO,CAAC;IAC5C,6CAA6C;IAC7C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACnC;AAED,wDAAwD;AACxD,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;IACvB,mCAAmC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;CAChC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain types for @browsercore/profiles.
|
|
3
|
+
*
|
|
4
|
+
* Pure data: this package defines WHAT a browser fingerprint looks like, never
|
|
5
|
+
* HOW to emit it on the wire. Protocol implementations (tls, http1, http2) read
|
|
6
|
+
* these definitions and translate them into bytes / header order / settings frames.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small shared helpers for @browsercore/profiles.
|
|
3
|
+
*
|
|
4
|
+
* Kept dependency-free so every package can copy the pattern without pulling in
|
|
5
|
+
* cross-package imports.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Exhaustiveness check for `switch`/`if-else` over discriminated unions.
|
|
9
|
+
* Call in the `default` branch: `default: assertNever(x)`.
|
|
10
|
+
* Adding a new union member forces every handler to compile-error until handled.
|
|
11
|
+
*/
|
|
12
|
+
export declare function assertNever(x: never): never;
|
|
13
|
+
/**
|
|
14
|
+
* Build a branded {@link ProfileId} from a browser name + version.
|
|
15
|
+
* Format: `${name}-${version}`, e.g. "chrome-140".
|
|
16
|
+
*/
|
|
17
|
+
export declare function createId(name: string, version: string): string;
|
|
18
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;GAIG;AAEH,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,GAAG,KAAK,CAE3C;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9D"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small shared helpers for @browsercore/profiles.
|
|
3
|
+
*
|
|
4
|
+
* Kept dependency-free so every package can copy the pattern without pulling in
|
|
5
|
+
* cross-package imports.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Exhaustiveness check for `switch`/`if-else` over discriminated unions.
|
|
9
|
+
* Call in the `default` branch: `default: assertNever(x)`.
|
|
10
|
+
* Adding a new union member forces every handler to compile-error until handled.
|
|
11
|
+
*/
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
13
|
+
export function assertNever(x) {
|
|
14
|
+
throw new Error(`Unexpected value: ${JSON.stringify(x)}`);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build a branded {@link ProfileId} from a browser name + version.
|
|
18
|
+
* Format: `${name}-${version}`, e.g. "chrome-140".
|
|
19
|
+
*/
|
|
20
|
+
export function createId(name, version) {
|
|
21
|
+
return `${name}-${version}`;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;GAIG;AACH,6DAA6D;AAC7D,MAAM,UAAU,WAAW,CAAC,CAAQ;IAChC,MAAM,IAAI,KAAK,CAAC,qBAAqB,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,OAAe;IAClD,OAAO,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile validation against real captures.
|
|
3
|
+
*
|
|
4
|
+
* Real Wireshark / JA4 captures are produced later by the testing package; this
|
|
5
|
+
* module is the reusable utility that consumes them. It projects a profile's TLS
|
|
6
|
+
* fields onto the wire values a ClientHello would carry, then compares those
|
|
7
|
+
* expectations to a captured ClientHello, reporting diffs.
|
|
8
|
+
*
|
|
9
|
+
* A key honesty: GREASE (RFC 8701) values are randomized per-connection, so a
|
|
10
|
+
* profile cannot predict the exact GREASE bytes. We handle this where the data
|
|
11
|
+
* lets us — cipher suites mark GREASE slots with a named placeholder, so a
|
|
12
|
+
* GREASE slot in the profile matches any GREASE-pattern byte pair in the capture.
|
|
13
|
+
* Extension GREASE is not handled here because the profile stores literal wire
|
|
14
|
+
* codes for extensions (not placeholders), so it cannot robustly flag a GREASE
|
|
15
|
+
* extension slot. See the per-field comments below.
|
|
16
|
+
*/
|
|
17
|
+
import type { BrowserProfile } from "./types.js";
|
|
18
|
+
import type { ProfileDiff } from "./diff.js";
|
|
19
|
+
import { ProfileError } from "./errors.js";
|
|
20
|
+
/** A captured ClientHello, as parsed out of a packet capture by the testing package. */
|
|
21
|
+
export interface TlsCapture {
|
|
22
|
+
/** Offered cipher suites as IANA 2-byte codes, in wire order. */
|
|
23
|
+
readonly cipherSuites: readonly number[];
|
|
24
|
+
/** Extension type codes present, in wire order. */
|
|
25
|
+
readonly extensionTypes: readonly number[];
|
|
26
|
+
/** Supported versions advertised, as 2-byte codes, highest first. */
|
|
27
|
+
readonly supportedVersions: readonly number[];
|
|
28
|
+
/** Key-share named-group ids offered. */
|
|
29
|
+
readonly keyShareGroups: readonly number[];
|
|
30
|
+
/** Signature algorithm codes offered. */
|
|
31
|
+
readonly signatureAlgorithms: readonly number[];
|
|
32
|
+
/** Whether the ClientHello used GREASE randomization. */
|
|
33
|
+
readonly grease: boolean;
|
|
34
|
+
/** Optional JA3 / JA4 strings, when the capture tool computed them. */
|
|
35
|
+
readonly ja3?: string;
|
|
36
|
+
readonly ja4?: string;
|
|
37
|
+
}
|
|
38
|
+
/** The wire values a profile's ClientHello is expected to carry. */
|
|
39
|
+
export interface ClientHelloExpected {
|
|
40
|
+
/** Cipher suites as IANA 2-byte codes, in wire order. */
|
|
41
|
+
readonly cipherSuites: readonly number[];
|
|
42
|
+
/** Extension type codes, in wire order. */
|
|
43
|
+
readonly extensionTypes: readonly number[];
|
|
44
|
+
/** Supported versions as 2-byte codes, highest first. */
|
|
45
|
+
readonly supportedVersions: readonly number[];
|
|
46
|
+
/** Key-share named-group ids. */
|
|
47
|
+
readonly keyShareGroups: readonly number[];
|
|
48
|
+
/** Signature algorithm codes. */
|
|
49
|
+
readonly signatureAlgorithms: readonly number[];
|
|
50
|
+
/** Whether GREASE randomization is expected. */
|
|
51
|
+
readonly grease: boolean;
|
|
52
|
+
/** SNI hostname the client would send, derived from the connection target. */
|
|
53
|
+
readonly sni: string;
|
|
54
|
+
}
|
|
55
|
+
/** Validation / projection failure (unknown profile value, bad capture, etc.). */
|
|
56
|
+
export declare class ValidationError extends ProfileError {
|
|
57
|
+
readonly kind: "ValidationError";
|
|
58
|
+
constructor(message: string, options?: {
|
|
59
|
+
cause?: Error;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Project a profile's TLS fields onto the wire values its ClientHello should
|
|
64
|
+
* carry for a connection to `serverName`. Extension order is taken verbatim from
|
|
65
|
+
* the profile (already stored as wire codes), since extension order is a
|
|
66
|
+
* fingerprint signal.
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildExpectedClientHello(profile: BrowserProfile, serverName: string): ClientHelloExpected;
|
|
69
|
+
/**
|
|
70
|
+
* Validate a profile against a captured ClientHello.
|
|
71
|
+
*
|
|
72
|
+
* Returns `ok: true` when every field matches (respecting GREASE randomization
|
|
73
|
+
* for cipher suites), and the list of diffs otherwise. SNI is not compared
|
|
74
|
+
* because captures do not record the destination hostname.
|
|
75
|
+
*/
|
|
76
|
+
export declare function validateProfileAgainstCapture(profile: BrowserProfile, capture: TlsCapture): {
|
|
77
|
+
ok: boolean;
|
|
78
|
+
diffs: ProfileDiff[];
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,wFAAwF;AACxF,MAAM,WAAW,UAAU;IACvB,iEAAiE;IACjE,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,mDAAmD;IACnD,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,qEAAqE;IACrE,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,yCAAyC;IACzC,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,yCAAyC;IACzC,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,uEAAuE;IACvE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,oEAAoE;AACpE,MAAM,WAAW,mBAAmB;IAChC,yDAAyD;IACzD,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,2CAA2C;IAC3C,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,yDAAyD;IACzD,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,iCAAiC;IACjC,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,iCAAiC;IACjC,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACxB;AAmED,kFAAkF;AAClF,qBAAa,eAAgB,SAAQ,YAAY;IAC7C,SAAyB,IAAI,EAAG,iBAAiB,CAAU;gBAE/C,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE;CAI3D;AAkCD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACpC,OAAO,EAAE,cAAc,EACvB,UAAU,EAAE,MAAM,GACnB,mBAAmB,CAUrB;AAsDD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CACzC,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,UAAU,GACpB;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,WAAW,EAAE,CAAA;CAAE,CAoBvC"}
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile validation against real captures.
|
|
3
|
+
*
|
|
4
|
+
* Real Wireshark / JA4 captures are produced later by the testing package; this
|
|
5
|
+
* module is the reusable utility that consumes them. It projects a profile's TLS
|
|
6
|
+
* fields onto the wire values a ClientHello would carry, then compares those
|
|
7
|
+
* expectations to a captured ClientHello, reporting diffs.
|
|
8
|
+
*
|
|
9
|
+
* A key honesty: GREASE (RFC 8701) values are randomized per-connection, so a
|
|
10
|
+
* profile cannot predict the exact GREASE bytes. We handle this where the data
|
|
11
|
+
* lets us — cipher suites mark GREASE slots with a named placeholder, so a
|
|
12
|
+
* GREASE slot in the profile matches any GREASE-pattern byte pair in the capture.
|
|
13
|
+
* Extension GREASE is not handled here because the profile stores literal wire
|
|
14
|
+
* codes for extensions (not placeholders), so it cannot robustly flag a GREASE
|
|
15
|
+
* extension slot. See the per-field comments below.
|
|
16
|
+
*/
|
|
17
|
+
import { ProfileError } from "./errors.js";
|
|
18
|
+
/** The name Chrome uses in its cipher list to mark a GREASE slot (RFC 8701). */
|
|
19
|
+
const CIPHER_GREASE_PLACEHOLDER = "TLS_GREASE_RESERVED_0";
|
|
20
|
+
/**
|
|
21
|
+
* IANA TLS Cipher Suite Registry — selected codes.
|
|
22
|
+
*
|
|
23
|
+
* Values are the canonical 2-byte wire codes from
|
|
24
|
+
* <https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4>.
|
|
25
|
+
* Only the suites used by the shipped profiles are mapped; an unknown name is a
|
|
26
|
+
* bug in a profile definition and throws.
|
|
27
|
+
*/
|
|
28
|
+
const CIPHER_SUITE_CODES = {
|
|
29
|
+
// GREASE: real value is randomized per-connection (0x0a0a..0xfafa). We use the
|
|
30
|
+
// first canonical GREASE code for the expected representation; validation
|
|
31
|
+
// accepts any GREASE-pattern value at a GREASE slot.
|
|
32
|
+
[CIPHER_GREASE_PLACEHOLDER]: 0x0a0a,
|
|
33
|
+
TLS_AES_128_GCM_SHA256: 0x1301,
|
|
34
|
+
TLS_AES_256_GCM_SHA384: 0x1302,
|
|
35
|
+
TLS_CHACHA20_POLY1305_SHA256: 0x1303,
|
|
36
|
+
TLS_AES_128_CCM_SHA256: 0x1304,
|
|
37
|
+
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: 0xc02b,
|
|
38
|
+
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: 0xc02f,
|
|
39
|
+
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: 0xc02c,
|
|
40
|
+
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: 0xc030,
|
|
41
|
+
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: 0xcca9,
|
|
42
|
+
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: 0xcca8,
|
|
43
|
+
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: 0xc013,
|
|
44
|
+
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: 0xc014,
|
|
45
|
+
TLS_RSA_WITH_AES_128_GCM_SHA256: 0x009c,
|
|
46
|
+
TLS_RSA_WITH_AES_256_GCM_SHA384: 0x009d,
|
|
47
|
+
TLS_RSA_WITH_AES_128_CBC_SHA: 0x002f,
|
|
48
|
+
TLS_RSA_WITH_AES_256_CBC_SHA: 0x0035,
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* IANA TLS Supported Groups Registry (named groups) — selected codes.
|
|
52
|
+
* <https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8>
|
|
53
|
+
*/
|
|
54
|
+
const NAMED_GROUP_CODES = {
|
|
55
|
+
x25519: 0x001d,
|
|
56
|
+
secp256r1: 0x0017,
|
|
57
|
+
secp384r1: 0x0018,
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* IANA TLS SignatureScheme Registry — selected codes.
|
|
61
|
+
* <https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-16>
|
|
62
|
+
*/
|
|
63
|
+
const SIGNATURE_SCHEME_CODES = {
|
|
64
|
+
ecdsa_secp256r1_sha256: 0x0403,
|
|
65
|
+
rsa_pss_rsae_sha256: 0x0804,
|
|
66
|
+
rsa_pkcs1_sha256: 0x0401,
|
|
67
|
+
ecdsa_secp384r1_sha384: 0x0503,
|
|
68
|
+
rsa_pss_rsae_sha384: 0x0805,
|
|
69
|
+
rsa_pkcs1_sha384: 0x0501,
|
|
70
|
+
};
|
|
71
|
+
/** IANA TLS ProtocolVersion codes for the supported_versions extension. */
|
|
72
|
+
const VERSION_CODES = {
|
|
73
|
+
"TLS 1.3": 0x0304,
|
|
74
|
+
"TLS 1.2": 0x0303,
|
|
75
|
+
"TLS 1.1": 0x0302,
|
|
76
|
+
"TLS 1.0": 0x0301,
|
|
77
|
+
};
|
|
78
|
+
/** Validation / projection failure (unknown profile value, bad capture, etc.). */
|
|
79
|
+
export class ValidationError extends ProfileError {
|
|
80
|
+
kind = "ValidationError";
|
|
81
|
+
constructor(message, options) {
|
|
82
|
+
super("ValidationError", message, options);
|
|
83
|
+
this.name = "ValidationError";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function lookupCode(map, name, field) {
|
|
87
|
+
const code = map[name];
|
|
88
|
+
if (code === undefined) {
|
|
89
|
+
throw new ValidationError(`Unknown ${field}: ${name}`);
|
|
90
|
+
}
|
|
91
|
+
return code;
|
|
92
|
+
}
|
|
93
|
+
/** Map a cipher-suite name to its 2-byte IANA code. */
|
|
94
|
+
function mapCipherSuite(name) {
|
|
95
|
+
return lookupCode(CIPHER_SUITE_CODES, name, "cipher suite");
|
|
96
|
+
}
|
|
97
|
+
/** Map a named-group name to its IANA id. */
|
|
98
|
+
function mapNamedGroup(name) {
|
|
99
|
+
return lookupCode(NAMED_GROUP_CODES, name, "named group");
|
|
100
|
+
}
|
|
101
|
+
/** Map a signature-scheme name to its IANA code. */
|
|
102
|
+
function mapSignatureScheme(name) {
|
|
103
|
+
return lookupCode(SIGNATURE_SCHEME_CODES, name, "signature scheme");
|
|
104
|
+
}
|
|
105
|
+
/** Map a TLS version string to its 2-byte wire code. */
|
|
106
|
+
function mapVersion(name) {
|
|
107
|
+
return lookupCode(VERSION_CODES, name, "TLS version");
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Project a profile's TLS fields onto the wire values its ClientHello should
|
|
111
|
+
* carry for a connection to `serverName`. Extension order is taken verbatim from
|
|
112
|
+
* the profile (already stored as wire codes), since extension order is a
|
|
113
|
+
* fingerprint signal.
|
|
114
|
+
*/
|
|
115
|
+
export function buildExpectedClientHello(profile, serverName) {
|
|
116
|
+
return {
|
|
117
|
+
cipherSuites: profile.tls.cipherSuites.map(mapCipherSuite),
|
|
118
|
+
extensionTypes: Array.from(profile.tls.extensionOrder),
|
|
119
|
+
supportedVersions: profile.tls.supportedVersions.map(mapVersion),
|
|
120
|
+
keyShareGroups: profile.tls.keyShareGroups.map(mapNamedGroup),
|
|
121
|
+
signatureAlgorithms: profile.tls.signatureAlgorithms.map(mapSignatureScheme),
|
|
122
|
+
grease: profile.tls.grease,
|
|
123
|
+
sni: serverName,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** A 2-byte value matches the GREASE pattern 0x?a?a (high byte === low byte). */
|
|
127
|
+
function isGreaseValue(v) {
|
|
128
|
+
return (v >> 8) === (v & 0xff);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Element-wise diff of two numeric arrays, reporting one {@link ProfileDiff} per
|
|
132
|
+
* mismatched index (including length differences).
|
|
133
|
+
*/
|
|
134
|
+
function diffNumberArray(path, expected, actual, out) {
|
|
135
|
+
const n = Math.max(expected.length, actual.length);
|
|
136
|
+
for (let i = 0; i < n; i++) {
|
|
137
|
+
const e = expected[i];
|
|
138
|
+
const c = actual[i];
|
|
139
|
+
if (e !== c) {
|
|
140
|
+
out.push({ path: `${path}[${i}]`, a: e, b: c });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** Diff cipher suites with GREASE awareness: a GREASE slot matches any GREASE-pattern value. */
|
|
145
|
+
function diffCipherSuites(profile, expected, capture, out) {
|
|
146
|
+
const n = Math.max(expected.length, capture.length);
|
|
147
|
+
for (let i = 0; i < n; i++) {
|
|
148
|
+
const path = `tls.cipherSuites[${i}]`;
|
|
149
|
+
const e = expected[i];
|
|
150
|
+
const c = capture[i];
|
|
151
|
+
const greaseSlot = profile.tls.cipherSuites[i] === CIPHER_GREASE_PLACEHOLDER;
|
|
152
|
+
if (greaseSlot) {
|
|
153
|
+
// Profile reserves a GREASE slot; accept any GREASE-pattern value, but a
|
|
154
|
+
// missing or non-GREASE value is still a mismatch.
|
|
155
|
+
if (c === undefined || !isGreaseValue(c)) {
|
|
156
|
+
out.push({ path, a: e, b: c });
|
|
157
|
+
}
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (e !== c) {
|
|
161
|
+
out.push({ path, a: e, b: c });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Validate a profile against a captured ClientHello.
|
|
167
|
+
*
|
|
168
|
+
* Returns `ok: true` when every field matches (respecting GREASE randomization
|
|
169
|
+
* for cipher suites), and the list of diffs otherwise. SNI is not compared
|
|
170
|
+
* because captures do not record the destination hostname.
|
|
171
|
+
*/
|
|
172
|
+
export function validateProfileAgainstCapture(profile, capture) {
|
|
173
|
+
const expected = buildExpectedClientHello(profile, "");
|
|
174
|
+
const diffs = [];
|
|
175
|
+
diffCipherSuites(profile, expected.cipherSuites, capture.cipherSuites, diffs);
|
|
176
|
+
diffNumberArray("tls.extensionTypes", expected.extensionTypes, capture.extensionTypes, diffs);
|
|
177
|
+
diffNumberArray("tls.supportedVersions", expected.supportedVersions, capture.supportedVersions, diffs);
|
|
178
|
+
diffNumberArray("tls.keyShareGroups", expected.keyShareGroups, capture.keyShareGroups, diffs);
|
|
179
|
+
diffNumberArray("tls.signatureAlgorithms", expected.signatureAlgorithms, capture.signatureAlgorithms, diffs);
|
|
180
|
+
if (expected.grease !== capture.grease) {
|
|
181
|
+
diffs.push({ path: "tls.grease", a: expected.grease, b: capture.grease });
|
|
182
|
+
}
|
|
183
|
+
return { ok: diffs.length === 0, diffs };
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@browsercore/profiles",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Browser fingerprint definitions (TLS / HTTP/2 / HTTP/1.1). Pure data — no protocol implementation.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/jverneuer/browsercore-profiles.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/jverneuer/browsercore-profiles#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/jverneuer/browsercore-profiles/issues"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": ["dist", "LICENSE", "README.md"],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public",
|
|
26
|
+
"registry": "https://registry.npmjs.org/"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"clean": "rm -rf dist",
|
|
30
|
+
"build": "tsc -p tsconfig.build.json",
|
|
31
|
+
"prepublishOnly": "npm run clean && npm run build",
|
|
32
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
33
|
+
"test": "vitest run"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^20.14.0",
|
|
37
|
+
"typescript": "^5.6.3",
|
|
38
|
+
"vitest": "^2.1.8"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=24.0.0"
|
|
42
|
+
}
|
|
43
|
+
}
|