@prosopo/provider 4.15.13 → 4.15.15
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/.turbo/turbo-build$colon$cjs.log +106 -106
- package/.turbo/turbo-build$colon$tsc.log +24 -24
- package/.turbo/turbo-build.log +107 -104
- package/CHANGELOG.md +78 -0
- package/dist/_virtual/_rolldown/runtime.js +3 -0
- package/dist/api/admin/apiAdminRoutesProvider.js +27 -46
- package/dist/api/admin/apiClearAllCountersEndpoint.js +62 -57
- package/dist/api/admin/apiDnsEventEndpoint.js +79 -92
- package/dist/api/admin/apiGetAllDecisionMachinesEndpoint.js +43 -45
- package/dist/api/admin/apiGetDecisionMachineEndpoint.js +40 -40
- package/dist/api/admin/apiRegisterSiteKeyEndpoint.js +23 -22
- package/dist/api/admin/apiRegisterSiteKeysEndpoint.js +26 -30
- package/dist/api/admin/apiRemoveAllDecisionMachinesEndpoint.js +35 -37
- package/dist/api/admin/apiRemoveDecisionMachineEndpoint.js +40 -40
- package/dist/api/admin/apiRemoveDetectorKeyEndpoint.d.ts.map +1 -1
- package/dist/api/admin/apiRemoveDetectorKeyEndpoint.js +29 -31
- package/dist/api/admin/apiRemoveSiteKeyEndpoint.js +33 -29
- package/dist/api/admin/apiRemoveSiteKeysEndpoint.js +32 -31
- package/dist/api/admin/apiToggleMaintenanceModeEndpoint.js +43 -35
- package/dist/api/admin/apiUpdateDecisionMachineEndpoint.js +43 -63
- package/dist/api/admin/apiUpdateDetectorKeyEndpoint.js +36 -35
- package/dist/api/admin/createApiAdminRoutesProvider.js +6 -8
- package/dist/api/blacklistRequestInspector.js +285 -377
- package/dist/api/block.js +17 -30
- package/dist/api/captcha/checkSpamEmail.js +88 -98
- package/dist/api/captcha/contextAwareValidation.js +21 -15
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.js +126 -140
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.js +11 -12
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js +236 -299
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.js +10 -13
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js +336 -462
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/honeypotResponse.js +17 -14
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.js +27 -27
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js +42 -63
- package/dist/api/captcha/getFrictionlessCaptchaChallenge.js +2 -4
- package/dist/api/captcha/getImageCaptchaChallenge.js +136 -207
- package/dist/api/captcha/getPoWCaptchaChallenge.js +128 -198
- package/dist/api/captcha/getPuzzleCaptchaChallenge.js +132 -201
- package/dist/api/captcha/maintenanceModeResponses.js +38 -47
- package/dist/api/captcha/submitImageCaptchaSolution.js +75 -101
- package/dist/api/captcha/submitPoWCaptchaSolution.js +137 -207
- package/dist/api/captcha/submitPuzzleCaptchaSolution.js +73 -105
- package/dist/api/captcha.js +76 -58
- package/dist/api/dnsEventUrl.js +20 -22
- package/dist/api/domainMiddleware.js +104 -115
- package/dist/api/forwardVerify.js +62 -48
- package/dist/api/handshakeTimingMiddleware.js +43 -62
- package/dist/api/hardBlockVerdictCache.js +114 -117
- package/dist/api/headerCheckMiddleware.js +34 -34
- package/dist/api/ignoreMiddleware.js +13 -15
- package/dist/api/ipInfoMiddleware.js +17 -19
- package/dist/api/ja4.js +149 -188
- package/dist/api/ja4Middleware.js +39 -54
- package/dist/api/metrics.js +245 -257
- package/dist/api/public.js +52 -54
- package/dist/api/robotsMiddleware.js +10 -10
- package/dist/api/startProviderApi.js +205 -232
- package/dist/api/testSiteKey.js +16 -16
- package/dist/api/validateAddress.js +23 -21
- package/dist/api/verify.js +334 -377
- package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/cjs/api/admin/apiAdminRoutesProvider.cjs +40 -59
- package/dist/cjs/api/admin/apiClearAllCountersEndpoint.cjs +63 -58
- package/dist/cjs/api/admin/apiDnsEventEndpoint.cjs +82 -93
- package/dist/cjs/api/admin/apiGetAllDecisionMachinesEndpoint.cjs +46 -48
- package/dist/cjs/api/admin/apiGetDecisionMachineEndpoint.cjs +41 -41
- package/dist/cjs/api/admin/apiRegisterSiteKeyEndpoint.cjs +25 -24
- package/dist/cjs/api/admin/apiRegisterSiteKeysEndpoint.cjs +28 -32
- package/dist/cjs/api/admin/apiRemoveAllDecisionMachinesEndpoint.cjs +38 -40
- package/dist/cjs/api/admin/apiRemoveDecisionMachineEndpoint.cjs +41 -41
- package/dist/cjs/api/admin/apiRemoveDetectorKeyEndpoint.cjs +31 -33
- package/dist/cjs/api/admin/apiRemoveSiteKeyEndpoint.cjs +35 -31
- package/dist/cjs/api/admin/apiRemoveSiteKeysEndpoint.cjs +34 -33
- package/dist/cjs/api/admin/apiToggleMaintenanceModeEndpoint.cjs +45 -35
- package/dist/cjs/api/admin/apiUpdateDecisionMachineEndpoint.cjs +45 -65
- package/dist/cjs/api/admin/apiUpdateDetectorKeyEndpoint.cjs +38 -37
- package/dist/cjs/api/admin/createApiAdminRoutesProvider.cjs +7 -8
- package/dist/cjs/api/blacklistRequestInspector.cjs +288 -376
- package/dist/cjs/api/block.cjs +18 -30
- package/dist/cjs/api/captcha/checkSpamEmail.cjs +90 -100
- package/dist/cjs/api/captcha/contextAwareValidation.cjs +21 -14
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.cjs +125 -140
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/constants.cjs +10 -10
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.cjs +238 -301
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.cjs +9 -12
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/handler.cjs +349 -473
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/honeypotResponse.cjs +18 -14
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.cjs +26 -26
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.cjs +42 -63
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge.cjs +2 -3
- package/dist/cjs/api/captcha/getImageCaptchaChallenge.cjs +138 -208
- package/dist/cjs/api/captcha/getPoWCaptchaChallenge.cjs +131 -200
- package/dist/cjs/api/captcha/getPuzzleCaptchaChallenge.cjs +135 -203
- package/dist/cjs/api/captcha/maintenanceModeResponses.cjs +38 -44
- package/dist/cjs/api/captcha/submitImageCaptchaSolution.cjs +77 -102
- package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +140 -209
- package/dist/cjs/api/captcha/submitPuzzleCaptchaSolution.cjs +76 -107
- package/dist/cjs/api/captcha.cjs +79 -58
- package/dist/cjs/api/dnsEventUrl.cjs +20 -21
- package/dist/cjs/api/domainMiddleware.cjs +106 -117
- package/dist/cjs/api/forwardVerify.cjs +63 -49
- package/dist/cjs/api/handshakeTimingMiddleware.cjs +43 -61
- package/dist/cjs/api/hardBlockVerdictCache.cjs +114 -114
- package/dist/cjs/api/headerCheckMiddleware.cjs +34 -34
- package/dist/cjs/api/ignoreMiddleware.cjs +12 -14
- package/dist/cjs/api/ipInfoMiddleware.cjs +16 -18
- package/dist/cjs/api/ja4.cjs +149 -187
- package/dist/cjs/api/ja4Middleware.cjs +40 -53
- package/dist/cjs/api/metrics.cjs +229 -228
- package/dist/cjs/api/public.cjs +57 -57
- package/dist/cjs/api/robotsMiddleware.cjs +9 -9
- package/dist/cjs/api/startProviderApi.cjs +219 -237
- package/dist/cjs/api/testSiteKey.cjs +15 -14
- package/dist/cjs/api/validateAddress.cjs +24 -21
- package/dist/cjs/api/verify.cjs +338 -379
- package/dist/cjs/compositeIpAddress.cjs +40 -47
- package/dist/cjs/index.cjs +65 -71
- package/dist/cjs/pairs.cjs +31 -35
- package/dist/cjs/rules/lang.cjs +10 -14
- package/dist/cjs/schedulers/captchaScheduler.cjs +26 -32
- package/dist/cjs/schedulers/getClientList.cjs +35 -33
- package/dist/cjs/schedulers/setClientEntropy.cjs +35 -33
- package/dist/cjs/schedulers/updateSpamEmailDomains.cjs +39 -43
- package/dist/cjs/services/ipComparison.cjs +122 -119
- package/dist/cjs/tasks/captchaManager.cjs +407 -486
- package/dist/cjs/tasks/client/clientTasks.cjs +347 -470
- package/dist/cjs/tasks/dataset/datasetTasks.cjs +25 -29
- package/dist/cjs/tasks/dataset/datasetTasksUtils.cjs +16 -32
- package/dist/cjs/tasks/decisionMachine/decisionMachineRunner.cjs +220 -307
- package/dist/cjs/tasks/detection/decodeBehavior.cjs +408 -317
- package/dist/cjs/tasks/detection/decodePayload.cjs +686 -468
- package/dist/cjs/tasks/detection/decodeSimd.cjs +462 -304
- package/dist/cjs/tasks/detection/getBotScore.cjs +37 -43
- package/dist/cjs/tasks/dnsEvent/enrichDnsEvent.cjs +88 -71
- package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +348 -486
- package/dist/cjs/tasks/frictionless/frictionlessTasksUtils.cjs +13 -30
- package/dist/cjs/tasks/frictionless/routingMachine.cjs +71 -55
- package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +555 -806
- package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasksUtils.cjs +22 -23
- package/dist/cjs/tasks/index.cjs +2 -4
- package/dist/cjs/tasks/powCaptcha/powTasks.cjs +491 -687
- package/dist/cjs/tasks/powCaptcha/powTasksUtils.cjs +17 -29
- package/dist/cjs/tasks/puzzleCaptcha/puzzleTasks.cjs +422 -591
- package/dist/cjs/tasks/puzzleCaptcha/puzzleTasksUtils.cjs +8 -5
- package/dist/cjs/tasks/spam/checkSpamEmail.cjs +133 -141
- package/dist/cjs/tasks/spam/checkTrafficFilter.cjs +118 -76
- package/dist/cjs/tasks/spam/evaluateEmailSpamRules.cjs +75 -84
- package/dist/cjs/tasks/spam/updateSpamEmailDomains.cjs +40 -56
- package/dist/cjs/tasks/tasks.cjs +107 -158
- package/dist/cjs/util/usageCounters.cjs +220 -211
- package/dist/cjs/util.cjs +329 -359
- package/dist/cjs/utils/devicePlatform.cjs +14 -8
- package/dist/cjs/utils/dns.cjs +68 -93
- package/dist/cjs/utils/hashUserAgent.cjs +14 -7
- package/dist/cjs/utils/hashUserIp.cjs +14 -6
- package/dist/cjs/utils/honeypot/encoders.cjs +74 -80
- package/dist/cjs/utils/honeypot/phraseBank.cjs +51 -44
- package/dist/cjs/utils/normalizeRequestIp.cjs +33 -25
- package/dist/compositeIpAddress.js +38 -46
- package/dist/index.js +12 -56
- package/dist/pairs.js +31 -37
- package/dist/rules/lang.js +11 -15
- package/dist/schedulers/captchaScheduler.js +25 -31
- package/dist/schedulers/getClientList.js +34 -32
- package/dist/schedulers/setClientEntropy.js +34 -32
- package/dist/schedulers/updateSpamEmailDomains.js +38 -42
- package/dist/services/ipComparison.js +122 -119
- package/dist/tasks/captchaManager.js +403 -483
- package/dist/tasks/client/clientTasks.js +343 -467
- package/dist/tasks/dataset/datasetTasks.js +24 -28
- package/dist/tasks/dataset/datasetTasksUtils.js +15 -31
- package/dist/tasks/decisionMachine/decisionMachineRunner.js +217 -306
- package/dist/tasks/detection/getBotScore.js +37 -43
- package/dist/tasks/dnsEvent/enrichDnsEvent.d.ts.map +1 -1
- package/dist/tasks/dnsEvent/enrichDnsEvent.js +88 -74
- package/dist/tasks/dnsEvent/enrichDnsEvent.js.map +1 -1
- package/dist/tasks/frictionless/frictionlessTasks.js +342 -479
- package/dist/tasks/frictionless/frictionlessTasksUtils.js +14 -32
- package/dist/tasks/frictionless/routingMachine.js +72 -56
- package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +550 -801
- package/dist/tasks/imgCaptcha/imgCaptchaTasksUtils.js +21 -22
- package/dist/tasks/index.js +1 -3
- package/dist/tasks/powCaptcha/powTasks.d.ts.map +1 -1
- package/dist/tasks/powCaptcha/powTasks.js +486 -682
- package/dist/tasks/powCaptcha/powTasks.js.map +1 -1
- package/dist/tasks/powCaptcha/powTasksUtils.js +15 -28
- package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts.map +1 -1
- package/dist/tasks/puzzleCaptcha/puzzleTasks.js +417 -586
- package/dist/tasks/puzzleCaptcha/puzzleTasks.js.map +1 -1
- package/dist/tasks/puzzleCaptcha/puzzleTasksUtils.js +9 -6
- package/dist/tasks/spam/checkSpamEmail.js +133 -141
- package/dist/tasks/spam/checkTrafficFilter.d.ts +1 -0
- package/dist/tasks/spam/checkTrafficFilter.d.ts.map +1 -1
- package/dist/tasks/spam/checkTrafficFilter.js +117 -77
- package/dist/tasks/spam/checkTrafficFilter.js.map +1 -1
- package/dist/tasks/spam/evaluateEmailSpamRules.js +75 -86
- package/dist/tasks/spam/updateSpamEmailDomains.js +36 -54
- package/dist/tasks/tasks.js +98 -150
- package/dist/tests/integration/api/blacklistRequestInspector.integration.test.js +4 -1
- package/dist/tests/integration/api/blacklistRequestInspector.integration.test.js.map +1 -1
- package/dist/tests/unit/api/block.unit.test.js +8 -4
- package/dist/tests/unit/api/block.unit.test.js.map +1 -1
- package/dist/tests/unit/api/captcha/maintenanceModeShortCircuit.unit.test.js +8 -6
- package/dist/tests/unit/api/captcha/maintenanceModeShortCircuit.unit.test.js.map +1 -1
- package/dist/tests/unit/api/forwardVerify.unit.test.js +3 -1
- package/dist/tests/unit/api/forwardVerify.unit.test.js.map +1 -1
- package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js +15 -7
- package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js.map +1 -1
- package/dist/tests/unit/schedulers/captchaScheduler.unit.test.js +24 -18
- package/dist/tests/unit/schedulers/captchaScheduler.unit.test.js.map +1 -1
- package/dist/tests/unit/schedulers/getClientList.unit.test.js +19 -13
- package/dist/tests/unit/schedulers/getClientList.unit.test.js.map +1 -1
- package/dist/tests/unit/schedulers/setClientEntropy.unit.test.js +19 -13
- package/dist/tests/unit/schedulers/setClientEntropy.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/client/clientTasks.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasksUtils.unit.test.js +22 -16
- package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasksUtils.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/powCaptcha/powTasks.unit.test.js +6 -0
- package/dist/tests/unit/tasks/powCaptcha/powTasks.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js +60 -1
- package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.js +140 -0
- package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.js.map +1 -1
- package/dist/tests/unit/testUtils/mockProviderEnv.d.ts +7 -7
- package/dist/util/usageCounters.js +220 -212
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +327 -364
- package/dist/util.js.map +1 -1
- package/dist/utils/devicePlatform.js +15 -9
- package/dist/utils/dns.js +64 -77
- package/dist/utils/hashUserAgent.js +14 -7
- package/dist/utils/hashUserIp.js +14 -6
- package/dist/utils/honeypot/encoders.js +74 -80
- package/dist/utils/honeypot/phraseBank.js +49 -44
- package/dist/utils/normalizeRequestIp.js +34 -26
- package/package.json +24 -24
package/dist/cjs/api/ja4.cjs
CHANGED
|
@@ -1,203 +1,165 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const EXT_SUPPORTED_VERSIONS = 43;
|
|
1
|
+
let node_crypto = require("node:crypto");
|
|
2
|
+
//#region src/api/ja4.ts
|
|
3
|
+
var RECORD_HEADER_LENGTH = 5;
|
|
4
|
+
var HANDSHAKE_HEADER_LENGTH = 4;
|
|
5
|
+
var HANDSHAKE_RECORD_TYPE = 22;
|
|
6
|
+
var HANDSHAKE_MESSAGE_TYPE = 1;
|
|
7
|
+
var EXT_SERVER_NAME = 0;
|
|
8
|
+
var EXT_SIGNATURE_ALGORITHMS = 13;
|
|
9
|
+
var EXT_ALPN = 16;
|
|
10
|
+
var EXT_SUPPORTED_VERSIONS = 43;
|
|
12
11
|
function isGrease(id) {
|
|
13
|
-
|
|
12
|
+
return (id & 3855) === 2570 && id >> 8 === (id & 255);
|
|
14
13
|
}
|
|
15
14
|
function hash12(input) {
|
|
16
|
-
|
|
15
|
+
return (0, node_crypto.createHash)("sha256").update(input).digest("hex").slice(0, 12);
|
|
17
16
|
}
|
|
18
17
|
function formatIdList(ids) {
|
|
19
|
-
|
|
18
|
+
return ids.map((id) => id.toString(16).padStart(4, "0")).join(",");
|
|
20
19
|
}
|
|
21
20
|
function alpnChar(b) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
b >= 97 && b <= 122) {
|
|
25
|
-
return String.fromCharCode(b);
|
|
26
|
-
}
|
|
27
|
-
return b.toString(16).padStart(2, "0");
|
|
21
|
+
if (b >= 48 && b <= 57 || b >= 65 && b <= 90 || b >= 97 && b <= 122) return String.fromCharCode(b);
|
|
22
|
+
return b.toString(16).padStart(2, "0");
|
|
28
23
|
}
|
|
29
24
|
function alpnFirstLast(protocol) {
|
|
30
|
-
|
|
31
|
-
const last = protocol.length > 1 ? alpnChar(protocol.readUInt8(protocol.length - 1)) : "0";
|
|
32
|
-
return [first, last];
|
|
25
|
+
return [protocol.length > 0 ? alpnChar(protocol.readUInt8(0)) : "0", protocol.length > 1 ? alpnChar(protocol.readUInt8(protocol.length - 1)) : "0"];
|
|
33
26
|
}
|
|
34
27
|
function tlsVersionStr(version) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
case 772:
|
|
43
|
-
return "13";
|
|
44
|
-
default:
|
|
45
|
-
return "00";
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
class Ja4ParseError extends Error {
|
|
49
|
-
constructor(message) {
|
|
50
|
-
super(message);
|
|
51
|
-
this.name = "Ja4ParseError";
|
|
52
|
-
}
|
|
28
|
+
switch (version) {
|
|
29
|
+
case 769: return "10";
|
|
30
|
+
case 770: return "11";
|
|
31
|
+
case 771: return "12";
|
|
32
|
+
case 772: return "13";
|
|
33
|
+
default: return "00";
|
|
34
|
+
}
|
|
53
35
|
}
|
|
36
|
+
var Ja4ParseError = class extends Error {
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "Ja4ParseError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Compute a JA4 TLS fingerprint from raw ClientHello bytes.
|
|
44
|
+
*
|
|
45
|
+
* Expects the full TLS record:
|
|
46
|
+
* 5-byte record header (content_type + version + record_len)
|
|
47
|
+
* 4-byte handshake header (msg_type + msg_len)
|
|
48
|
+
* ClientHello body
|
|
49
|
+
*
|
|
50
|
+
* Throws Ja4ParseError for structural failures in the TLS record or handshake
|
|
51
|
+
* header (wrong record type, wrong message type, declared lengths exceeding the
|
|
52
|
+
* buffer, odd cipher-suite length).
|
|
53
|
+
*
|
|
54
|
+
* Extension-level malformation (truncated extension body, malformed ALPN
|
|
55
|
+
* entries) is handled defensively: parsing stops at the first bad entry and
|
|
56
|
+
* a fingerprint is produced from whatever was successfully parsed. This matches
|
|
57
|
+
* the Rust reference implementation and avoids turning extension parse errors
|
|
58
|
+
* into connection-fingerprinting failures.
|
|
59
|
+
*/
|
|
54
60
|
function calculateJa4(data) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (best > 0) clientVersion = best;
|
|
157
|
-
}
|
|
158
|
-
} else if (extId === EXT_SIGNATURE_ALGORITHMS) {
|
|
159
|
-
if (extData.length >= 2) {
|
|
160
|
-
const algLen = extData.readUInt16BE(0);
|
|
161
|
-
for (let i = 2; i + 1 < extData.length && i + 1 < algLen + 2; i += 2) {
|
|
162
|
-
const alg = extData.readUInt16BE(i);
|
|
163
|
-
if (!isGrease(alg)) sigAlgorithms.push(alg);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
} else if (extId === EXT_ALPN) {
|
|
167
|
-
if (extData.length >= 2) {
|
|
168
|
-
const listLen = extData.readUInt16BE(0);
|
|
169
|
-
const listEnd = Math.min(extData.length, 2 + listLen);
|
|
170
|
-
let alpnOff = 2;
|
|
171
|
-
while (alpnOff < listEnd) {
|
|
172
|
-
const protoLen = extData.readUInt8(alpnOff);
|
|
173
|
-
alpnOff += 1;
|
|
174
|
-
if (alpnOff + protoLen > listEnd) break;
|
|
175
|
-
alpnProtocols.push(extData.subarray(alpnOff, alpnOff + protoLen));
|
|
176
|
-
alpnOff += protoLen;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
const versionStr = tlsVersionStr(clientVersion);
|
|
182
|
-
const sni = extIds.includes(EXT_SERVER_NAME) ? "d" : "i";
|
|
183
|
-
const firstAlpn = alpnProtocols[0];
|
|
184
|
-
const [alpnFirst, alpnLast] = firstAlpn ? alpnFirstLast(firstAlpn) : ["0", "0"];
|
|
185
|
-
const nonGreaseCiphers = cipherSuites.filter((c) => !isGrease(c));
|
|
186
|
-
const nonGreaseExts = extIds.filter((e) => !isGrease(e));
|
|
187
|
-
const cipherCount = Math.min(99, nonGreaseCiphers.length).toString().padStart(2, "0");
|
|
188
|
-
const extCount = Math.min(99, nonGreaseExts.length).toString().padStart(2, "0");
|
|
189
|
-
const firstChunk = `t${versionStr}${sni}${cipherCount}${extCount}${alpnFirst}${alpnLast}`;
|
|
190
|
-
const sortedCiphers = [...nonGreaseCiphers].sort((a, b) => a - b);
|
|
191
|
-
const cipherHash = hash12(formatIdList(sortedCiphers));
|
|
192
|
-
const filteredExts = nonGreaseExts.filter(
|
|
193
|
-
(e) => e !== EXT_SERVER_NAME && e !== EXT_ALPN
|
|
194
|
-
);
|
|
195
|
-
const sortedExts = [...filteredExts].sort((a, b) => a - b);
|
|
196
|
-
const extStr = formatIdList(sortedExts);
|
|
197
|
-
const sigAlgStr = formatIdList(sigAlgorithms);
|
|
198
|
-
const extHashInput = sigAlgStr.length > 0 ? `${extStr}_${sigAlgStr}` : extStr;
|
|
199
|
-
const extHash = hash12(extHashInput);
|
|
200
|
-
return `${firstChunk}_${cipherHash}_${extHash}`;
|
|
61
|
+
let offset = 0;
|
|
62
|
+
if (data.length < RECORD_HEADER_LENGTH + HANDSHAKE_HEADER_LENGTH) throw new Ja4ParseError("ClientHello too short");
|
|
63
|
+
const recordType = data.readUInt8(offset);
|
|
64
|
+
offset += 1;
|
|
65
|
+
if (recordType !== HANDSHAKE_RECORD_TYPE) throw new Ja4ParseError(`Invalid record type: 0x${recordType.toString(16)}`);
|
|
66
|
+
offset += 2;
|
|
67
|
+
const recordLen = data.readUInt16BE(offset);
|
|
68
|
+
offset += 2;
|
|
69
|
+
if (data.length < RECORD_HEADER_LENGTH + recordLen) throw new Ja4ParseError("Record length exceeds buffer");
|
|
70
|
+
data = data.subarray(0, RECORD_HEADER_LENGTH + recordLen);
|
|
71
|
+
if (data.length < RECORD_HEADER_LENGTH + HANDSHAKE_HEADER_LENGTH) throw new Ja4ParseError("Record too short for handshake header");
|
|
72
|
+
const msgType = data.readUInt8(offset);
|
|
73
|
+
offset += 1;
|
|
74
|
+
if (msgType !== HANDSHAKE_MESSAGE_TYPE) throw new Ja4ParseError(`Not a ClientHello: handshake type 0x${msgType.toString(16)}`);
|
|
75
|
+
const msgLen = data.readUIntBE(offset, 3);
|
|
76
|
+
offset += 3;
|
|
77
|
+
if (data.length - offset < msgLen) throw new Ja4ParseError("Handshake message length exceeds record");
|
|
78
|
+
data = data.subarray(0, offset + msgLen);
|
|
79
|
+
if (data.length - offset < 35) throw new Ja4ParseError("ClientHello body too short");
|
|
80
|
+
let clientVersion = data.readUInt16BE(offset);
|
|
81
|
+
offset += 2;
|
|
82
|
+
offset += 32;
|
|
83
|
+
const sessionIdLen = data.readUInt8(offset);
|
|
84
|
+
offset += 1;
|
|
85
|
+
if (data.length - offset < sessionIdLen) throw new Ja4ParseError("Truncated at session ID");
|
|
86
|
+
offset += sessionIdLen;
|
|
87
|
+
if (data.length - offset < 2) throw new Ja4ParseError("Truncated at cipher suites length");
|
|
88
|
+
const cipherSuitesLen = data.readUInt16BE(offset);
|
|
89
|
+
offset += 2;
|
|
90
|
+
if (data.length - offset < cipherSuitesLen) throw new Ja4ParseError("Truncated at cipher suites");
|
|
91
|
+
if (cipherSuitesLen % 2 !== 0) throw new Ja4ParseError("Cipher suites length is not a multiple of 2");
|
|
92
|
+
const cipherSuites = [];
|
|
93
|
+
for (let i = 0; i < cipherSuitesLen; i += 2) cipherSuites.push(data.readUInt16BE(offset + i));
|
|
94
|
+
offset += cipherSuitesLen;
|
|
95
|
+
if (data.length - offset < 1) throw new Ja4ParseError("Truncated at compression methods");
|
|
96
|
+
const compressionLen = data.readUInt8(offset);
|
|
97
|
+
offset += 1;
|
|
98
|
+
if (data.length - offset < compressionLen) throw new Ja4ParseError("Truncated at compression methods data");
|
|
99
|
+
offset += compressionLen;
|
|
100
|
+
if (data.length - offset < 2) throw new Ja4ParseError("Truncated at extensions length");
|
|
101
|
+
const extensionsLen = data.readUInt16BE(offset);
|
|
102
|
+
offset += 2;
|
|
103
|
+
if (data.length - offset < extensionsLen) throw new Ja4ParseError("Truncated at extensions");
|
|
104
|
+
const extIds = [];
|
|
105
|
+
const sigAlgorithms = [];
|
|
106
|
+
const alpnProtocols = [];
|
|
107
|
+
let extOffset = offset;
|
|
108
|
+
const extEnd = offset + extensionsLen;
|
|
109
|
+
while (extOffset + 4 <= extEnd) {
|
|
110
|
+
const extId = data.readUInt16BE(extOffset);
|
|
111
|
+
extOffset += 2;
|
|
112
|
+
const extLen = data.readUInt16BE(extOffset);
|
|
113
|
+
extOffset += 2;
|
|
114
|
+
if (extOffset + extLen > extEnd) break;
|
|
115
|
+
const extData = data.subarray(extOffset, extOffset + extLen);
|
|
116
|
+
extOffset += extLen;
|
|
117
|
+
extIds.push(extId);
|
|
118
|
+
if (extId === EXT_SUPPORTED_VERSIONS) {
|
|
119
|
+
if (extData.length >= 1) {
|
|
120
|
+
const listLen = extData.readUInt8(0);
|
|
121
|
+
let best = 0;
|
|
122
|
+
for (let i = 1; i + 1 < extData.length && i + 1 <= listLen; i += 2) {
|
|
123
|
+
const v = extData.readUInt16BE(i);
|
|
124
|
+
if (!isGrease(v) && v > best) best = v;
|
|
125
|
+
}
|
|
126
|
+
if (best > 0) clientVersion = best;
|
|
127
|
+
}
|
|
128
|
+
} else if (extId === EXT_SIGNATURE_ALGORITHMS) {
|
|
129
|
+
if (extData.length >= 2) {
|
|
130
|
+
const algLen = extData.readUInt16BE(0);
|
|
131
|
+
for (let i = 2; i + 1 < extData.length && i + 1 < algLen + 2; i += 2) {
|
|
132
|
+
const alg = extData.readUInt16BE(i);
|
|
133
|
+
if (!isGrease(alg)) sigAlgorithms.push(alg);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} else if (extId === EXT_ALPN) {
|
|
137
|
+
if (extData.length >= 2) {
|
|
138
|
+
const listLen = extData.readUInt16BE(0);
|
|
139
|
+
const listEnd = Math.min(extData.length, 2 + listLen);
|
|
140
|
+
let alpnOff = 2;
|
|
141
|
+
while (alpnOff < listEnd) {
|
|
142
|
+
const protoLen = extData.readUInt8(alpnOff);
|
|
143
|
+
alpnOff += 1;
|
|
144
|
+
if (alpnOff + protoLen > listEnd) break;
|
|
145
|
+
alpnProtocols.push(extData.subarray(alpnOff, alpnOff + protoLen));
|
|
146
|
+
alpnOff += protoLen;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const versionStr = tlsVersionStr(clientVersion);
|
|
152
|
+
const sni = extIds.includes(EXT_SERVER_NAME) ? "d" : "i";
|
|
153
|
+
const firstAlpn = alpnProtocols[0];
|
|
154
|
+
const [alpnFirst, alpnLast] = firstAlpn ? alpnFirstLast(firstAlpn) : ["0", "0"];
|
|
155
|
+
const nonGreaseCiphers = cipherSuites.filter((c) => !isGrease(c));
|
|
156
|
+
const nonGreaseExts = extIds.filter((e) => !isGrease(e));
|
|
157
|
+
const firstChunk = `t${versionStr}${sni}${Math.min(99, nonGreaseCiphers.length).toString().padStart(2, "0")}${Math.min(99, nonGreaseExts.length).toString().padStart(2, "0")}${alpnFirst}${alpnLast}`;
|
|
158
|
+
const cipherHash = hash12(formatIdList([...nonGreaseCiphers].sort((a, b) => a - b)));
|
|
159
|
+
const extStr = formatIdList([...nonGreaseExts.filter((e) => e !== EXT_SERVER_NAME && e !== EXT_ALPN)].sort((a, b) => a - b));
|
|
160
|
+
const sigAlgStr = formatIdList(sigAlgorithms);
|
|
161
|
+
return `${firstChunk}_${cipherHash}_${hash12(sigAlgStr.length > 0 ? `${extStr}_${sigAlgStr}` : extStr)}`;
|
|
201
162
|
}
|
|
163
|
+
//#endregion
|
|
202
164
|
exports.Ja4ParseError = Ja4ParseError;
|
|
203
165
|
exports.calculateJa4 = calculateJa4;
|
|
@@ -1,58 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return { ja4PlusFingerprint };
|
|
30
|
-
} catch (e) {
|
|
31
|
-
logger$1.error(() => ({
|
|
32
|
-
msg: "Error generating JA4+ fingerprint:",
|
|
33
|
-
err: e instanceof Error ? e : new Error(String(e))
|
|
34
|
-
}));
|
|
35
|
-
return { ja4PlusFingerprint: DEFAULT_JA4 };
|
|
36
|
-
}
|
|
1
|
+
const require_ja4 = require("./ja4.cjs");
|
|
2
|
+
let _prosopo_logger = require("@prosopo/logger");
|
|
3
|
+
let _prosopo_util_crypto = require("@prosopo/util-crypto");
|
|
4
|
+
let _prosopo_api_express_router = require("@prosopo/api-express-router");
|
|
5
|
+
//#region src/api/ja4Middleware.ts
|
|
6
|
+
var DEFAULT_JA4 = "ja4";
|
|
7
|
+
var getJA4 = async (headers, logger) => {
|
|
8
|
+
logger = logger || (0, _prosopo_logger.getLogger)("info", "provider:ja4");
|
|
9
|
+
if (process.env.NODE_ENV === "development") return { ja4PlusFingerprint: `ja4${(0, _prosopo_util_crypto.randomAsHex)().slice(28, 32)}` };
|
|
10
|
+
try {
|
|
11
|
+
const xTlsClientHello = (headers["x-tls-clienthello"] || "").toString();
|
|
12
|
+
const clientHelloBuffer = Buffer.from(xTlsClientHello, "base64");
|
|
13
|
+
logger.debug(() => ({
|
|
14
|
+
msg: "ClientHello First Bytes:",
|
|
15
|
+
data: { hex: clientHelloBuffer.subarray(0, 5).toString("hex") }
|
|
16
|
+
}));
|
|
17
|
+
if (clientHelloBuffer[5] !== 1) {
|
|
18
|
+
logger.debug(() => ({ msg: "Invalid ClientHello message: First byte is not 0x01" }));
|
|
19
|
+
return { ja4PlusFingerprint: "ja4" };
|
|
20
|
+
}
|
|
21
|
+
return { ja4PlusFingerprint: require_ja4.calculateJa4(clientHelloBuffer) };
|
|
22
|
+
} catch (e) {
|
|
23
|
+
logger.error(() => ({
|
|
24
|
+
msg: "Error generating JA4+ fingerprint:",
|
|
25
|
+
err: e instanceof Error ? e : new Error(String(e))
|
|
26
|
+
}));
|
|
27
|
+
return { ja4PlusFingerprint: "ja4" };
|
|
28
|
+
}
|
|
37
29
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
);
|
|
50
|
-
next();
|
|
51
|
-
} catch (err) {
|
|
52
|
-
return apiExpressRouter.handleErrors(err, req, res, next);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
30
|
+
var ja4Middleware = (env) => {
|
|
31
|
+
return async (req, res, next) => {
|
|
32
|
+
try {
|
|
33
|
+
req.logger.debug(() => ({ data: { url: req.url } }));
|
|
34
|
+
req.ja4 = (await getJA4(req.headers, req.logger)).ja4PlusFingerprint || "";
|
|
35
|
+
req.logger = req.logger.with({ ja4: req.ja4 }, "ja4");
|
|
36
|
+
next();
|
|
37
|
+
} catch (err) {
|
|
38
|
+
return (0, _prosopo_api_express_router.handleErrors)(err, req, res, next);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
55
41
|
};
|
|
42
|
+
//#endregion
|
|
56
43
|
exports.DEFAULT_JA4 = DEFAULT_JA4;
|
|
57
44
|
exports.getJA4 = getJA4;
|
|
58
45
|
exports.ja4Middleware = ja4Middleware;
|