@anonympins/fingerprint 0.3.8 → 0.4.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +331 -256
  2. package/README.md +62 -53
  3. package/composer.json +38 -38
  4. package/index.js +4 -4
  5. package/package.json +103 -103
  6. package/phpunit.xml +20 -20
  7. package/public/fp.js +1 -1
  8. package/public/fp.wasm +0 -0
  9. package/src/js/build-client.js +1 -1
  10. package/src/js/fingerprint.client.js +2 -0
  11. package/src/js/fingerprint.js +4429 -4381
  12. package/src/js/mongodb-store.js +79 -79
  13. package/src/js/pow.solver.inline.js +31 -0
  14. package/src/js/pow.solver.js +31 -0
  15. package/src/js/tests/fingerprint.builder.test.js +79 -0
  16. package/src/js/tests/fingerprint.client.init.test.js +120 -0
  17. package/src/js/tests/fingerprint.client.test.js +105 -0
  18. package/src/js/tests/fingerprint.engine.test.js +371 -0
  19. package/src/js/tests/fingerprint.isMalicious.test.js +117 -0
  20. package/src/js/tests/fingerprint.test.js +2319 -0
  21. package/src/js/tests/ip-reputation.test.js +132 -0
  22. package/src/js/tests/ja3AnomalyDetector.test.js +135 -0
  23. package/src/js/tests/library.test.js +96 -0
  24. package/src/js/tests/metrics.test.js +104 -0
  25. package/src/js/tests/pow.solver.test.js +198 -0
  26. package/src/js/tests/problem-manager.test.js +323 -0
  27. package/src/js/tests/stores.test.js +118 -0
  28. package/src/php/Challenge/ChallengeUtils.php +361 -361
  29. package/src/php/Config/SecurityProfiles.php +271 -266
  30. package/src/php/FingerprintBuilder.php +185 -185
  31. package/src/php/FingerprintClient.php +131 -131
  32. package/src/php/FingerprintEngine.php +1006 -1006
  33. package/src/php/Ja3AnomalyDetector.php +227 -227
  34. package/src/php/Optimization/FunctionRegistry.php +62 -62
  35. package/src/php/Optimization/Optimization.php +255 -255
  36. package/src/php/Optimization/OptimizationOperators.php +304 -304
  37. package/src/php/Store/InMemoryStore.php +66 -66
  38. package/src/php/Store/MongoDbStore.php +104 -104
  39. package/src/php/Store/RedisStore.php +53 -53
  40. package/src/php/Tests/ChallengeUtilsTest.php +81 -81
  41. package/src/php/Tests/FingerprintBuilderTest.php +57 -57
  42. package/src/php/Tests/FingerprintClientTest.php +71 -0
  43. package/src/php/Tests/FingerprintEngineTest.php +299 -299
  44. package/src/php/Tests/IpReputationTest.php +156 -156
  45. package/src/php/Tests/Ja3AnomalyDetectorTest.php +179 -179
  46. package/src/php/Tests/MetricsTest.php +45 -45
  47. package/src/php/Tests/PowTest.php +39 -39
  48. package/src/php/Tests/ProblemManagerTest.php +296 -296
  49. package/src/php/Tests/RequestUtilsTest.php +253 -145
  50. package/src/php/Tests/TLSClientHelloParserTest.php +118 -0
  51. package/src/php/Tests/problems.config.json +8 -8
  52. package/src/php/Utils/BigInt.php +144 -144
  53. package/src/php/Utils/Logger.php +29 -29
  54. package/src/php/Utils/MaliciousPatterns.php +58 -58
  55. package/src/php/Utils/MetricsManager.php +166 -166
  56. package/src/php/Utils/RequestUtils.php +1169 -1169
  57. package/src/php/Utils/TLSClientHelloParser.php +117 -0
  58. package/src/php/bin/auto-tune.php +117 -117
@@ -1,228 +1,228 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint;
6
-
7
- /**
8
- * Classe de détection d'anomalies et d'usurpation TLS (JA3) en PHP.
9
- */
10
- class Ja3AnomalyDetector
11
- {
12
- // Liste décimale des valeurs GREASE (RFC 8701) utilisées par les moteurs Chromium/Safari récents
13
- private const GREASE_VALUES = [
14
- 2570, 6682, 10794, 14906, 19018, 23130, 27242, 31354,
15
- 35466, 39578, 43690, 47802, 51914, 55926, 60038, 64150
16
- ];
17
-
18
- // Base de données locale de signatures JA3 MD5 connues pour la corroboration de base
19
- private const TLS_FINGERPRINT_DB = [
20
- 'e188a442b87f422c5a1e80b05399435b' => ['Chrome'],
21
- 'd8e35855049321c6042a4325c697858f' => ['Chrome'],
22
- 'a9f90958d44533748c139a5d1895b925' => ['Chrome'],
23
- '3b5379916d2b3882253c42885956a350' => ['Chrome'],
24
- '59822058c95c33d2d06e52f410855c8c' => ['Chrome'],
25
- 'b386946a5a586163c7c533636b45c355' => ['Firefox'],
26
- '66236495a523c1785f8f3a105b248b11' => ['Firefox'],
27
- 'b73d470006575b5e35167a0b5a8540e2' => ['Firefox'],
28
- '8443d7562933834333943465d52363cf' => ['Firefox'],
29
- 'b633f21d532d35967c8753c38536b4d3' => ['Safari'],
30
- '4d7a28d5f55b359b69100a311013f03e' => ['Safari', 'Chrome', 'Firefox'],
31
- '8dd3d7532873575314df23c447543001' => ['Safari', 'Chrome', 'Firefox'],
32
- // Bibliothèques et scrapers automatisés connus
33
- '47344a349b75c4e82333475553b5f358' => ['Python'],
34
- 'b29587b8a143c42546133ad7704b3310' => ['Go'],
35
- 'd435b5223b2884c5a832b842637e245f' => ['Java'],
36
- 'c72366b9551263d990b7fa574225332c' => ['curl'],
37
- ];
38
-
39
- /**
40
- * Analyse une chaîne JA3 brute non hachée.
41
- * Format attendu : "TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurveFormats"
42
- */
43
- public static function parseJa3(string $ja3String): ?array
44
- {
45
- if (empty($ja3String)) {
46
- return null;
47
- }
48
-
49
- $parts = explode(',', $ja3String);
50
- if (count($parts) !== 5) {
51
- return null;
52
- }
53
-
54
- return [
55
- 'tlsVersion' => (int)$parts[0],
56
- 'ciphers' => $parts[1] !== '' ? array_map('intval', explode('-', $parts[1])) : [],
57
- 'extensions' => $parts[2] !== '' ? array_map('intval', explode('-', $parts[2])) : [],
58
- 'curves' => $parts[3] !== '' ? array_map('intval', explode('-', $parts[3])) : [],
59
- 'points' => $parts[4] !== '' ? array_map('intval', explode('-', $parts[4])) : []
60
- ];
61
- }
62
-
63
- /**
64
- * Vérifie si un tableau contient au moins une valeur GREASE.
65
- */
66
- public static function hasGrease(array $values): bool
67
- {
68
- foreach ($values as $val) {
69
- if (in_array($val, self::GREASE_VALUES, true)) {
70
- return true;
71
- }
72
- }
73
- return false;
74
- }
75
-
76
- /**
77
- * Parse sommairement le User-Agent pour en extraire la famille de navigateur.
78
- */
79
- public static function getBrowserFamily(string $userAgent): ?string
80
- {
81
- $ua = strtolower($userAgent);
82
- if (strpos($ua, 'edg') !== false) {
83
- return 'Edge';
84
- }
85
- if (strpos($ua, 'chrome') !== false) {
86
- return 'Chrome';
87
- }
88
- if (strpos($ua, 'firefox') !== false) {
89
- return 'Firefox';
90
- }
91
- if (strpos($ua, 'safari') !== false) {
92
- return 'Safari';
93
- }
94
- return null;
95
- }
96
-
97
- /**
98
- * Calcule le score global d'anomalie et d'usurpation JA3.
99
- *
100
- * @param string|null $ja3Hash L'empreinte MD5 du JA3 (32 caractères)
101
- * @param string|null $ja3Raw L'empreinte brute non hachée (si disponible)
102
- * @param string $userAgent Le User-Agent de la requête
103
- * @param string $httpVersion La version HTTP de la requête (ex: "HTTP/2", "HTTP/1.1", ou "2.0")
104
- * @param object|null $cacheInstance Un driver de cache (ex: instance Redis) supportant get() et set() pour la détection de stagnation
105
- * @return int Un score de suspicion compris entre 0 et 100
106
- */
107
- public static function getJa3AnomalyScore(
108
- ?string $ja3Hash,
109
- ?string $ja3Raw,
110
- string $userAgent,
111
- string $httpVersion,
112
- ?object $cacheInstance = null
113
- ): int {
114
- $score = 0;
115
- $claimedBrowser = self::getBrowserFamily($userAgent);
116
- $isHumanBrowser = in_array($claimedBrowser, ['Chrome', 'Firefox', 'Safari', 'Edge'], true);
117
-
118
- // --- ANALYSE 1 : CONTRÔLE SUR LE MD5 DU JA3 ---
119
- if ($ja3Hash && strlen($ja3Hash) === 32) {
120
- if (isset(self::TLS_FINGERPRINT_DB[$ja3Hash])) {
121
- $expectedBrowsers = self::TLS_FINGERPRINT_DB[$ja3Hash];
122
-
123
- // Cas A : L'empreinte correspond à un outil de scraping mais le UA prétend être humain
124
- $isLibrary = array_intersect($expectedBrowsers, ['Python', 'Go', 'Java', 'curl']);
125
- if (!empty($isLibrary) && $isHumanBrowser) {
126
- $score = max($score, 90); // Suspicion maximale : usurpation évidente
127
- }
128
-
129
- // Cas B : Incohérence directe entre le UA prétendu et la stack TLS correspondante
130
- if ($claimedBrowser !== null) {
131
- $matched = false;
132
- foreach ($expectedBrowsers as $expected) {
133
- if (stripos($claimedBrowser, $expected) === 0) {
134
- $matched = true;
135
- break;
136
- }
137
- }
138
- if (!$matched) {
139
- $score = max($score, 80); // Le navigateur déclaré ne correspond pas au client TLS utilisé
140
- }
141
- }
142
- }
143
-
144
- // Cas C : Tracking de stagnation multi-UA (Stateful)
145
- if ($cacheInstance && $claimedBrowser !== null && method_exists($cacheInstance, 'get') && method_exists($cacheInstance, 'set')) {
146
- $cacheKey = "ja3-browsers:" . $ja3Hash;
147
-
148
- try {
149
- $rawCached = $cacheInstance->get($cacheKey);
150
- $seenBrowsers = $rawCached ? json_decode((string)$rawCached, true) : [];
151
- if (!is_array($seenBrowsers)) {
152
- $seenBrowsers = [];
153
- }
154
-
155
- if (!in_array($claimedBrowser, $seenBrowsers, true)) {
156
- $seenBrowsers[] = $claimedBrowser;
157
- // Cache pendant 24 heures (86400 secondes)
158
- if (method_exists($cacheInstance, 'setex')) {
159
- $cacheInstance->setex($cacheKey, 86400, json_encode($seenBrowsers));
160
- } else {
161
- $cacheInstance->set($cacheKey, json_encode($seenBrowsers), 86400);
162
- }
163
- }
164
-
165
- // Si une seule stack TLS génère des requêtes avec différents navigateurs, c'est un bot en rotation de UA
166
- if (count($seenBrowsers) > 1) {
167
- $score = max($score, 85);
168
- }
169
- } catch (\Throwable $e) {
170
- // Tolérance aux pannes du cache
171
- }
172
- }
173
- }
174
-
175
- // --- ANALYSE 2 : CONTRÔLE PROFOND SUR L'EMPREINTE BRUTE (RAW JA3) ---
176
- if ($ja3Raw) {
177
- $parsed = self::parseJa3($ja3Raw);
178
- if ($parsed) {
179
- // Contrôle A : Mécanisme GREASE pour Chrome / Edge (obligatoire)
180
- if ($claimedBrowser === 'Chrome' || $claimedBrowser === 'Edge') {
181
- $hasCiphersGrease = self::hasGrease($parsed['ciphers']);
182
- $hasExtensionsGrease = self::hasGrease($parsed['extensions']);
183
-
184
- if (!$hasCiphersGrease && !$hasExtensionsGrease) {
185
- // Chrome ou Edge moderne sans GREASE = spoofing de bas niveau (ex: python-requests déguisé)
186
- $score = max($score, 75);
187
- }
188
- }
189
-
190
- // Contrôle B : HTTP/2 ou HTTP/3 sans négociation ALPN (Extension 16)
191
- $isH2OrHigher = (
192
- strpos($httpVersion, '2.0') !== false ||
193
- strpos($httpVersion, 'HTTP/2') !== false ||
194
- strpos($httpVersion, 'HTTP/3') !== false
195
- );
196
- $hasAlpnExtension = in_array(16, $parsed['extensions'], true);
197
-
198
- if ($isH2OrHigher && !$hasAlpnExtension) {
199
- // Négociation HTTP/2 active au niveau serveur mais absente au niveau des extensions TLS du client
200
- $score = max($score, 70);
201
- }
202
-
203
- // Contrôle C : Version TLS obsolète négociée par un navigateur moderne (ex: TLS < 1.2, id < 771)
204
- if ($isHumanBrowser && $parsed['tlsVersion'] < 771) {
205
- $score = max($score, 80);
206
- }
207
- }
208
- }
209
-
210
- return $score;
211
- }
212
- }
213
-
214
- // --- EXEMPLE D'UTILISATION PRATIQUE ---
215
- /*
216
- $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
217
- $httpVersion = $_SERVER['SERVER_PROTOCOL'] ?? '';
218
-
219
- // Récupération des en-têtes injectés par votre Reverse-Proxy (Nginx, HAProxy, etc.)
220
- $ja3Hash = $_SERVER['HTTP_X_JA3_HASH'] ?? null;
221
- $ja3Raw = $_SERVER['HTTP_X_JA3_RAW'] ?? null;
222
-
223
- // Redis facultatif pour la détection stateful de rotation UA
224
- $redis = new \Redis();
225
- $redis->connect('127.0.0.1', 6379);
226
-
227
- $suspicionScore = Ja3AnomalyDetector::getJa3AnomalyScore($ja3Hash, $ja3Raw, $userAgent, $httpVersion, $redis);
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint;
6
+
7
+ /**
8
+ * Classe de détection d'anomalies et d'usurpation TLS (JA3) en PHP.
9
+ */
10
+ class Ja3AnomalyDetector
11
+ {
12
+ // Liste décimale des valeurs GREASE (RFC 8701) utilisées par les moteurs Chromium/Safari récents
13
+ private const GREASE_VALUES = [
14
+ 2570, 6682, 10794, 14906, 19018, 23130, 27242, 31354,
15
+ 35466, 39578, 43690, 47802, 51914, 55926, 60038, 64150
16
+ ];
17
+
18
+ // Base de données locale de signatures JA3 MD5 connues pour la corroboration de base
19
+ private const TLS_FINGERPRINT_DB = [
20
+ 'e188a442b87f422c5a1e80b05399435b' => ['Chrome'],
21
+ 'd8e35855049321c6042a4325c697858f' => ['Chrome'],
22
+ 'a9f90958d44533748c139a5d1895b925' => ['Chrome'],
23
+ '3b5379916d2b3882253c42885956a350' => ['Chrome'],
24
+ '59822058c95c33d2d06e52f410855c8c' => ['Chrome'],
25
+ 'b386946a5a586163c7c533636b45c355' => ['Firefox'],
26
+ '66236495a523c1785f8f3a105b248b11' => ['Firefox'],
27
+ 'b73d470006575b5e35167a0b5a8540e2' => ['Firefox'],
28
+ '8443d7562933834333943465d52363cf' => ['Firefox'],
29
+ 'b633f21d532d35967c8753c38536b4d3' => ['Safari'],
30
+ '4d7a28d5f55b359b69100a311013f03e' => ['Safari', 'Chrome', 'Firefox'],
31
+ '8dd3d7532873575314df23c447543001' => ['Safari', 'Chrome', 'Firefox'],
32
+ // Bibliothèques et scrapers automatisés connus
33
+ '47344a349b75c4e82333475553b5f358' => ['Python'],
34
+ 'b29587b8a143c42546133ad7704b3310' => ['Go'],
35
+ 'd435b5223b2884c5a832b842637e245f' => ['Java'],
36
+ 'c72366b9551263d990b7fa574225332c' => ['curl'],
37
+ ];
38
+
39
+ /**
40
+ * Analyse une chaîne JA3 brute non hachée.
41
+ * Format attendu : "TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurveFormats"
42
+ */
43
+ public static function parseJa3(string $ja3String): ?array
44
+ {
45
+ if (empty($ja3String)) {
46
+ return null;
47
+ }
48
+
49
+ $parts = explode(',', $ja3String);
50
+ if (count($parts) !== 5) {
51
+ return null;
52
+ }
53
+
54
+ return [
55
+ 'tlsVersion' => (int)$parts[0],
56
+ 'ciphers' => $parts[1] !== '' ? array_map('intval', explode('-', $parts[1])) : [],
57
+ 'extensions' => $parts[2] !== '' ? array_map('intval', explode('-', $parts[2])) : [],
58
+ 'curves' => $parts[3] !== '' ? array_map('intval', explode('-', $parts[3])) : [],
59
+ 'points' => $parts[4] !== '' ? array_map('intval', explode('-', $parts[4])) : []
60
+ ];
61
+ }
62
+
63
+ /**
64
+ * Vérifie si un tableau contient au moins une valeur GREASE.
65
+ */
66
+ public static function hasGrease(array $values): bool
67
+ {
68
+ foreach ($values as $val) {
69
+ if (in_array($val, self::GREASE_VALUES, true)) {
70
+ return true;
71
+ }
72
+ }
73
+ return false;
74
+ }
75
+
76
+ /**
77
+ * Parse sommairement le User-Agent pour en extraire la famille de navigateur.
78
+ */
79
+ public static function getBrowserFamily(string $userAgent): ?string
80
+ {
81
+ $ua = strtolower($userAgent);
82
+ if (strpos($ua, 'edg') !== false) {
83
+ return 'Edge';
84
+ }
85
+ if (strpos($ua, 'chrome') !== false) {
86
+ return 'Chrome';
87
+ }
88
+ if (strpos($ua, 'firefox') !== false) {
89
+ return 'Firefox';
90
+ }
91
+ if (strpos($ua, 'safari') !== false) {
92
+ return 'Safari';
93
+ }
94
+ return null;
95
+ }
96
+
97
+ /**
98
+ * Calcule le score global d'anomalie et d'usurpation JA3.
99
+ *
100
+ * @param string|null $ja3Hash L'empreinte MD5 du JA3 (32 caractères)
101
+ * @param string|null $ja3Raw L'empreinte brute non hachée (si disponible)
102
+ * @param string $userAgent Le User-Agent de la requête
103
+ * @param string $httpVersion La version HTTP de la requête (ex: "HTTP/2", "HTTP/1.1", ou "2.0")
104
+ * @param object|null $cacheInstance Un driver de cache (ex: instance Redis) supportant get() et set() pour la détection de stagnation
105
+ * @return int Un score de suspicion compris entre 0 et 100
106
+ */
107
+ public static function getJa3AnomalyScore(
108
+ ?string $ja3Hash,
109
+ ?string $ja3Raw,
110
+ string $userAgent,
111
+ string $httpVersion,
112
+ ?object $cacheInstance = null
113
+ ): int {
114
+ $score = 0;
115
+ $claimedBrowser = self::getBrowserFamily($userAgent);
116
+ $isHumanBrowser = in_array($claimedBrowser, ['Chrome', 'Firefox', 'Safari', 'Edge'], true);
117
+
118
+ // --- ANALYSE 1 : CONTRÔLE SUR LE MD5 DU JA3 ---
119
+ if ($ja3Hash && strlen($ja3Hash) === 32) {
120
+ if (isset(self::TLS_FINGERPRINT_DB[$ja3Hash])) {
121
+ $expectedBrowsers = self::TLS_FINGERPRINT_DB[$ja3Hash];
122
+
123
+ // Cas A : L'empreinte correspond à un outil de scraping mais le UA prétend être humain
124
+ $isLibrary = array_intersect($expectedBrowsers, ['Python', 'Go', 'Java', 'curl']);
125
+ if (!empty($isLibrary) && $isHumanBrowser) {
126
+ $score = max($score, 90); // Suspicion maximale : usurpation évidente
127
+ }
128
+
129
+ // Cas B : Incohérence directe entre le UA prétendu et la stack TLS correspondante
130
+ if ($claimedBrowser !== null) {
131
+ $matched = false;
132
+ foreach ($expectedBrowsers as $expected) {
133
+ if (stripos($claimedBrowser, $expected) === 0) {
134
+ $matched = true;
135
+ break;
136
+ }
137
+ }
138
+ if (!$matched) {
139
+ $score = max($score, 80); // Le navigateur déclaré ne correspond pas au client TLS utilisé
140
+ }
141
+ }
142
+ }
143
+
144
+ // Cas C : Tracking de stagnation multi-UA (Stateful)
145
+ if ($cacheInstance && $claimedBrowser !== null && method_exists($cacheInstance, 'get') && method_exists($cacheInstance, 'set')) {
146
+ $cacheKey = "ja3-browsers:" . $ja3Hash;
147
+
148
+ try {
149
+ $rawCached = $cacheInstance->get($cacheKey);
150
+ $seenBrowsers = $rawCached ? json_decode((string)$rawCached, true) : [];
151
+ if (!is_array($seenBrowsers)) {
152
+ $seenBrowsers = [];
153
+ }
154
+
155
+ if (!in_array($claimedBrowser, $seenBrowsers, true)) {
156
+ $seenBrowsers[] = $claimedBrowser;
157
+ // Cache pendant 24 heures (86400 secondes)
158
+ if (method_exists($cacheInstance, 'setex')) {
159
+ $cacheInstance->setex($cacheKey, 86400, json_encode($seenBrowsers));
160
+ } else {
161
+ $cacheInstance->set($cacheKey, json_encode($seenBrowsers), 86400);
162
+ }
163
+ }
164
+
165
+ // Si une seule stack TLS génère des requêtes avec différents navigateurs, c'est un bot en rotation de UA
166
+ if (count($seenBrowsers) > 1) {
167
+ $score = max($score, 85);
168
+ }
169
+ } catch (\Throwable $e) {
170
+ // Tolérance aux pannes du cache
171
+ }
172
+ }
173
+ }
174
+
175
+ // --- ANALYSE 2 : CONTRÔLE PROFOND SUR L'EMPREINTE BRUTE (RAW JA3) ---
176
+ if ($ja3Raw) {
177
+ $parsed = self::parseJa3($ja3Raw);
178
+ if ($parsed) {
179
+ // Contrôle A : Mécanisme GREASE pour Chrome / Edge (obligatoire)
180
+ if ($claimedBrowser === 'Chrome' || $claimedBrowser === 'Edge') {
181
+ $hasCiphersGrease = self::hasGrease($parsed['ciphers']);
182
+ $hasExtensionsGrease = self::hasGrease($parsed['extensions']);
183
+
184
+ if (!$hasCiphersGrease && !$hasExtensionsGrease) {
185
+ // Chrome ou Edge moderne sans GREASE = spoofing de bas niveau (ex: python-requests déguisé)
186
+ $score = max($score, 75);
187
+ }
188
+ }
189
+
190
+ // Contrôle B : HTTP/2 ou HTTP/3 sans négociation ALPN (Extension 16)
191
+ $isH2OrHigher = (
192
+ strpos($httpVersion, '2.0') !== false ||
193
+ strpos($httpVersion, 'HTTP/2') !== false ||
194
+ strpos($httpVersion, 'HTTP/3') !== false
195
+ );
196
+ $hasAlpnExtension = in_array(16, $parsed['extensions'], true);
197
+
198
+ if ($isH2OrHigher && !$hasAlpnExtension) {
199
+ // Négociation HTTP/2 active au niveau serveur mais absente au niveau des extensions TLS du client
200
+ $score = max($score, 70);
201
+ }
202
+
203
+ // Contrôle C : Version TLS obsolète négociée par un navigateur moderne (ex: TLS < 1.2, id < 771)
204
+ if ($isHumanBrowser && $parsed['tlsVersion'] < 771) {
205
+ $score = max($score, 80);
206
+ }
207
+ }
208
+ }
209
+
210
+ return $score;
211
+ }
212
+ }
213
+
214
+ // --- EXEMPLE D'UTILISATION PRATIQUE ---
215
+ /*
216
+ $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
217
+ $httpVersion = $_SERVER['SERVER_PROTOCOL'] ?? '';
218
+
219
+ // Récupération des en-têtes injectés par votre Reverse-Proxy (Nginx, HAProxy, etc.)
220
+ $ja3Hash = $_SERVER['HTTP_X_JA3_HASH'] ?? null;
221
+ $ja3Raw = $_SERVER['HTTP_X_JA3_RAW'] ?? null;
222
+
223
+ // Redis facultatif pour la détection stateful de rotation UA
224
+ $redis = new \Redis();
225
+ $redis->connect('127.0.0.1', 6379);
226
+
227
+ $suspicionScore = Ja3AnomalyDetector::getJa3AnomalyScore($ja3Hash, $ja3Raw, $userAgent, $httpVersion, $redis);
228
228
  */
@@ -1,63 +1,63 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint\Optimization;
6
-
7
- /**
8
- * Registre pour exposer de manière contrôlée les fonctions de la bibliothèque d'optimisation.
9
- */
10
- class FunctionRegistry
11
- {
12
- /** @var array<string, callable> */
13
- private static array $functions = [];
14
-
15
- /**
16
- * Initialise le registre avec les fonctions disponibles.
17
- */
18
- private static function initialize(): void
19
- {
20
- if (empty(self::$functions)) {
21
- // Fonctions de "Scoring"
22
- self::$functions['tsp.calculateEnergy'] = [OptimizationUtils::class, 'evaluatePathDistance'];
23
- self::$functions['portfolio.calculateMetrics'] = [OptimizationOperators::class, 'createPortfolioAllocator'];
24
-
25
- // Fonctions de "Résolution"
26
- self::$functions['tsp.solve'] = [OptimizationOperators::class, 'solveTSP'];
27
- self::$functions['portfolio.solve'] = [OptimizationOperators::class, 'solvePortfolio'];
28
- self::$functions['fraud.solve'] = [OptimizationOperators::class, 'solveFraudDetection'];
29
- self::$functions['facility.solve'] = [OptimizationOperators::class, 'solveFacilityLocation'];
30
- self::$functions['security.tune'] = [OptimizationOperators::class, 'solveFullSecurityTuning'];
31
- }
32
- }
33
-
34
- /**
35
- * Récupère une fonction depuis le registre.
36
- */
37
- public static function get(string $name): ?callable
38
- {
39
- self::initialize();
40
- return self::$functions[$name] ?? null;
41
- }
42
- /**
43
- * Enregistre une nouvelle fonction. Principalement pour les tests.
44
- * @internal
45
- * @param string $name
46
- * @param callable $function
47
- * @return void
48
- */
49
- public static function register(string $name, callable $function): void
50
- {
51
- self::initialize();
52
- self::$functions[$name] = $function;
53
- }
54
-
55
- /**
56
- * Réinitialise le registre. Uniquement pour les tests.
57
- * @internal
58
- */
59
- public static function __internal_resetRegistry(): void
60
- {
61
- self::$functions = [];
62
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Optimization;
6
+
7
+ /**
8
+ * Registre pour exposer de manière contrôlée les fonctions de la bibliothèque d'optimisation.
9
+ */
10
+ class FunctionRegistry
11
+ {
12
+ /** @var array<string, callable> */
13
+ private static array $functions = [];
14
+
15
+ /**
16
+ * Initialise le registre avec les fonctions disponibles.
17
+ */
18
+ private static function initialize(): void
19
+ {
20
+ if (empty(self::$functions)) {
21
+ // Fonctions de "Scoring"
22
+ self::$functions['tsp.calculateEnergy'] = [OptimizationUtils::class, 'evaluatePathDistance'];
23
+ self::$functions['portfolio.calculateMetrics'] = [OptimizationOperators::class, 'createPortfolioAllocator'];
24
+
25
+ // Fonctions de "Résolution"
26
+ self::$functions['tsp.solve'] = [OptimizationOperators::class, 'solveTSP'];
27
+ self::$functions['portfolio.solve'] = [OptimizationOperators::class, 'solvePortfolio'];
28
+ self::$functions['fraud.solve'] = [OptimizationOperators::class, 'solveFraudDetection'];
29
+ self::$functions['facility.solve'] = [OptimizationOperators::class, 'solveFacilityLocation'];
30
+ self::$functions['security.tune'] = [OptimizationOperators::class, 'solveFullSecurityTuning'];
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Récupère une fonction depuis le registre.
36
+ */
37
+ public static function get(string $name): ?callable
38
+ {
39
+ self::initialize();
40
+ return self::$functions[$name] ?? null;
41
+ }
42
+ /**
43
+ * Enregistre une nouvelle fonction. Principalement pour les tests.
44
+ * @internal
45
+ * @param string $name
46
+ * @param callable $function
47
+ * @return void
48
+ */
49
+ public static function register(string $name, callable $function): void
50
+ {
51
+ self::initialize();
52
+ self::$functions[$name] = $function;
53
+ }
54
+
55
+ /**
56
+ * Réinitialise le registre. Uniquement pour les tests.
57
+ * @internal
58
+ */
59
+ public static function __internal_resetRegistry(): void
60
+ {
61
+ self::$functions = [];
62
+ }
63
63
  }