@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,256 +1,256 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint\Optimization;
6
-
7
- /**
8
- * Bibliothèque d'algorithmes d'optimisation.
9
- */
10
- class Optimization
11
- {
12
- /**
13
- * Génère un nombre flottant aléatoire cryptographiquement sûr entre 0 (inclus) et 1 (exclus).
14
- */
15
- private static function secureRandom(): float
16
- {
17
- try {
18
- return random_int(0, PHP_INT_MAX - 1) / PHP_INT_MAX;
19
- } catch (\Exception $e) {
20
- return (float)mt_rand() / (float)mt_getrandmax(); // Fallback
21
- }
22
- }
23
-
24
- /**
25
- * Algorithme génétique multi-objectifs (inspiré de NSGA-II).
26
- * @param callable $createIndividual
27
- * @param callable $fitnessFunction
28
- * @param callable $crossover
29
- * @param callable $mutate
30
- * @param array $options
31
- * @return array<int, array{solution: mixed, objectives: array<float>}>
32
- */
33
- public static function geneticAlgorithmMultiObjective(
34
- callable $createIndividual,
35
- callable $fitnessFunction,
36
- callable $crossover,
37
- callable $mutate,
38
- array $options = []
39
- ): array {
40
- $generations = $options['generations'] ?? 150;
41
- $populationSize = $options['populationSize'] ?? 60;
42
- $mutationRate = $options['mutationRate'] ?? 0.1;
43
-
44
- $population = [];
45
- for ($i = 0; $i < $populationSize; $i++) {
46
- $individual = $createIndividual();
47
- $population[] = [
48
- 'individual' => $individual,
49
- 'objectives' => $fitnessFunction($individual)
50
- ];
51
- }
52
-
53
- for ($gen = 0; $gen < $generations; $gen++) {
54
- // 1. Créer une population d'enfants
55
- $offspring = [];
56
- for ($i = 0; $i < $populationSize; $i++) {
57
- $parent1 = $population[random_int(0, count($population) - 1)];
58
- $parent2 = $population[random_int(0, count($population) - 1)];
59
- $childIndividual = $crossover($parent1['individual'], $parent2['individual']);
60
- if (self::secureRandom() < $mutationRate) {
61
- $childIndividual = $mutate($childIndividual);
62
- }
63
- $offspring[] = [
64
- 'individual' => $childIndividual,
65
- 'objectives' => $fitnessFunction($childIndividual)
66
- ];
67
- }
68
-
69
- // 2. Combiner parents et enfants
70
- $combinedPopulation = array_merge($population, $offspring);
71
-
72
- // 3. Trier la population combinée en fronts
73
- $fronts = self::nonDominatedSort($combinedPopulation);
74
-
75
- // 4. Construire la nouvelle population
76
- $newPopulation = [];
77
- foreach ($fronts as $front) {
78
- if (count($newPopulation) + count($front) <= $populationSize) {
79
- $newPopulation = array_merge($newPopulation, $front);
80
- } else {
81
- self::calculateCrowdingDistance($front);
82
- // Trier par distance décroissante
83
- usort($front, fn ($a, $b) => $b['crowdingDistance'] <=> $a['crowdingDistance']);
84
- $remaining = $populationSize - count($newPopulation);
85
- $newPopulation = array_merge($newPopulation, array_slice($front, 0, $remaining));
86
- break;
87
- }
88
- }
89
- $population = $newPopulation;
90
- }
91
-
92
- // Retourner le premier front de la population finale
93
- $finalFronts = self::nonDominatedSort($population);
94
- $bestFront = $finalFronts[0] ?? [];
95
-
96
- // Filtrer pour ne garder que les solutions avec des objectifs uniques
97
- $uniqueSolutionsMap = [];
98
- foreach ($bestFront as $p) {
99
- $key = json_encode($p['objectives']);
100
- if (!isset($uniqueSolutionsMap[$key])) {
101
- $uniqueSolutionsMap[$key] = [
102
- 'solution' => $p['individual'],
103
- 'objectives' => $p['objectives'],
104
- ];
105
- }
106
- }
107
- return array_values($uniqueSolutionsMap);
108
- }
109
-
110
- /**
111
- * Détermine si la solution A domine la solution B.
112
- * @param array<float> $objectivesA
113
- * @param array<float> $objectivesB
114
- */
115
- private static function paretoDominates(array $objectivesA, array $objectivesB): bool
116
- {
117
- $aIsBetterInOne = false;
118
- for ($i = 0; $i < count($objectivesA); $i++) {
119
- if ($objectivesA[$i] > $objectivesB[$i]) {
120
- return false; // A est pire sur au moins un objectif
121
- }
122
- if ($objectivesA[$i] < $objectivesB[$i]) {
123
- $aIsBetterInOne = true; // A est strictement meilleur sur au moins un
124
- }
125
- }
126
- return $aIsBetterInOne;
127
- }
128
-
129
- /**
130
- * Trie une population en fronts de Pareto non-dominés.
131
- * @param array<int, array> &$populationWithObjectives
132
- * @return array<int, array>
133
- */
134
- private static function nonDominatedSort(array &$populationWithObjectives): array
135
- {
136
- $fronts = [[]];
137
- $n = count($populationWithObjectives);
138
-
139
- for ($i = 0; $i < $n; $i++) {
140
- $p1 = &$populationWithObjectives[$i];
141
- $p1['dominationCount'] = 0;
142
- $p1['dominatedSolutions'] = [];
143
-
144
- for ($j = 0; $j < $n; $j++) {
145
- if ($i === $j) continue;
146
- $p2 = &$populationWithObjectives[$j];
147
-
148
- if (self::paretoDominates($p1['objectives'], $p2['objectives'])) {
149
- $p1['dominatedSolutions'][] = $j;
150
- } elseif (self::paretoDominates($p2['objectives'], $p1['objectives'])) {
151
- $p1['dominationCount']++;
152
- }
153
- }
154
-
155
- if ($p1['dominationCount'] === 0) {
156
- $p1['rank'] = 0;
157
- $fronts[0][] = $p1;
158
- }
159
- }
160
-
161
- $i = 0;
162
- while (!empty($fronts[$i])) {
163
- $nextFront = [];
164
- foreach ($fronts[$i] as $p1) {
165
- foreach ($p1['dominatedSolutions'] as $p2_idx) {
166
- $p2 = &$populationWithObjectives[$p2_idx];
167
- $p2['dominationCount']--;
168
- if ($p2['dominationCount'] === 0) {
169
- $p2['rank'] = $i + 1;
170
- $nextFront[] = $p2;
171
- }
172
- }
173
- }
174
- $i++;
175
- if (!empty($nextFront)) {
176
- $fronts[$i] = $nextFront;
177
- }
178
- }
179
- return $fronts;
180
- }
181
-
182
- /**
183
- * Calcule la distance de promiscuité (crowding distance) pour un front.
184
- * @param array<int, array> &$front
185
- */
186
- private static function calculateCrowdingDistance(array &$front): void
187
- {
188
- if (empty($front)) return;
189
-
190
- $numObjectives = count($front[0]['objectives']);
191
- $l = count($front);
192
-
193
- foreach ($front as &$p) {
194
- $p['crowdingDistance'] = 0;
195
- }
196
-
197
- for ($i = 0; $i < $numObjectives; $i++) {
198
- // Trier le front par l'objectif courant
199
- usort($front, fn ($a, $b) => $a['objectives'][$i] <=> $b['objectives'][$i]);
200
-
201
- $minObj = $front[0]['objectives'][$i];
202
- $maxObj = $front[$l - 1]['objectives'][$i];
203
-
204
- // Les solutions aux extrémités ont une distance infinie
205
- $front[0]['crowdingDistance'] = INF;
206
- $front[$l - 1]['crowdingDistance'] = INF;
207
-
208
- if ($maxObj === $minObj) continue;
209
-
210
- for ($j = 1; $j < $l - 1; $j++) {
211
- $front[$j]['crowdingDistance'] +=
212
- ($front[$j + 1]['objectives'][$i] - $front[$j - 1]['objectives'][$i]) /
213
- ($maxObj - $minObj);
214
- }
215
- }
216
- }
217
-
218
- /**
219
- * Calcule la déviation d'une série de chiffres par rapport à la loi de Benford.
220
- * @public
221
- * @param array<int|float> $numbers
222
- */
223
- public static function benfordTest(array $numbers): float // Rendre la méthode publique et statique
224
- {
225
- if (count($numbers) < 10) {
226
- return 0.0; // Pas assez de données
227
- }
228
-
229
- $leadingDigits = array_map(function ($n) {
230
- $str = ltrim((string)$n, '0.');
231
- return $str[0] ?? '';
232
- }, $numbers);
233
-
234
- $leadingDigits = array_filter($leadingDigits, fn ($d) => $d >= '1' && $d <= '9');
235
-
236
- if (count($leadingDigits) < 10) {
237
- return 0.0;
238
- }
239
-
240
- $counts = array_fill(1, 9, 0);
241
- foreach ($leadingDigits as $digit) {
242
- $counts[(int)$digit]++;
243
- }
244
-
245
- $benfordDistribution = [1 => 30.1, 2 => 17.6, 3 => 12.5, 4 => 9.7, 5 => 7.9, 6 => 6.7, 7 => 5.8, 8 => 5.1, 9 => 4.6];
246
-
247
- $totalDeviation = 0.0;
248
- for ($i = 1; $i <= 9; $i++) {
249
- $observedFrequency = ($counts[$i] / count($leadingDigits)) * 100;
250
- $expectedFrequency = $benfordDistribution[$i];
251
- $totalDeviation += pow($observedFrequency - $expectedFrequency, 2);
252
- }
253
-
254
- return sqrt($totalDeviation) / 50.0;
255
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Optimization;
6
+
7
+ /**
8
+ * Bibliothèque d'algorithmes d'optimisation.
9
+ */
10
+ class Optimization
11
+ {
12
+ /**
13
+ * Génère un nombre flottant aléatoire cryptographiquement sûr entre 0 (inclus) et 1 (exclus).
14
+ */
15
+ private static function secureRandom(): float
16
+ {
17
+ try {
18
+ return random_int(0, PHP_INT_MAX - 1) / PHP_INT_MAX;
19
+ } catch (\Exception $e) {
20
+ return (float)mt_rand() / (float)mt_getrandmax(); // Fallback
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Algorithme génétique multi-objectifs (inspiré de NSGA-II).
26
+ * @param callable $createIndividual
27
+ * @param callable $fitnessFunction
28
+ * @param callable $crossover
29
+ * @param callable $mutate
30
+ * @param array $options
31
+ * @return array<int, array{solution: mixed, objectives: array<float>}>
32
+ */
33
+ public static function geneticAlgorithmMultiObjective(
34
+ callable $createIndividual,
35
+ callable $fitnessFunction,
36
+ callable $crossover,
37
+ callable $mutate,
38
+ array $options = []
39
+ ): array {
40
+ $generations = $options['generations'] ?? 150;
41
+ $populationSize = $options['populationSize'] ?? 60;
42
+ $mutationRate = $options['mutationRate'] ?? 0.1;
43
+
44
+ $population = [];
45
+ for ($i = 0; $i < $populationSize; $i++) {
46
+ $individual = $createIndividual();
47
+ $population[] = [
48
+ 'individual' => $individual,
49
+ 'objectives' => $fitnessFunction($individual)
50
+ ];
51
+ }
52
+
53
+ for ($gen = 0; $gen < $generations; $gen++) {
54
+ // 1. Créer une population d'enfants
55
+ $offspring = [];
56
+ for ($i = 0; $i < $populationSize; $i++) {
57
+ $parent1 = $population[random_int(0, count($population) - 1)];
58
+ $parent2 = $population[random_int(0, count($population) - 1)];
59
+ $childIndividual = $crossover($parent1['individual'], $parent2['individual']);
60
+ if (self::secureRandom() < $mutationRate) {
61
+ $childIndividual = $mutate($childIndividual);
62
+ }
63
+ $offspring[] = [
64
+ 'individual' => $childIndividual,
65
+ 'objectives' => $fitnessFunction($childIndividual)
66
+ ];
67
+ }
68
+
69
+ // 2. Combiner parents et enfants
70
+ $combinedPopulation = array_merge($population, $offspring);
71
+
72
+ // 3. Trier la population combinée en fronts
73
+ $fronts = self::nonDominatedSort($combinedPopulation);
74
+
75
+ // 4. Construire la nouvelle population
76
+ $newPopulation = [];
77
+ foreach ($fronts as $front) {
78
+ if (count($newPopulation) + count($front) <= $populationSize) {
79
+ $newPopulation = array_merge($newPopulation, $front);
80
+ } else {
81
+ self::calculateCrowdingDistance($front);
82
+ // Trier par distance décroissante
83
+ usort($front, fn ($a, $b) => $b['crowdingDistance'] <=> $a['crowdingDistance']);
84
+ $remaining = $populationSize - count($newPopulation);
85
+ $newPopulation = array_merge($newPopulation, array_slice($front, 0, $remaining));
86
+ break;
87
+ }
88
+ }
89
+ $population = $newPopulation;
90
+ }
91
+
92
+ // Retourner le premier front de la population finale
93
+ $finalFronts = self::nonDominatedSort($population);
94
+ $bestFront = $finalFronts[0] ?? [];
95
+
96
+ // Filtrer pour ne garder que les solutions avec des objectifs uniques
97
+ $uniqueSolutionsMap = [];
98
+ foreach ($bestFront as $p) {
99
+ $key = json_encode($p['objectives']);
100
+ if (!isset($uniqueSolutionsMap[$key])) {
101
+ $uniqueSolutionsMap[$key] = [
102
+ 'solution' => $p['individual'],
103
+ 'objectives' => $p['objectives'],
104
+ ];
105
+ }
106
+ }
107
+ return array_values($uniqueSolutionsMap);
108
+ }
109
+
110
+ /**
111
+ * Détermine si la solution A domine la solution B.
112
+ * @param array<float> $objectivesA
113
+ * @param array<float> $objectivesB
114
+ */
115
+ private static function paretoDominates(array $objectivesA, array $objectivesB): bool
116
+ {
117
+ $aIsBetterInOne = false;
118
+ for ($i = 0; $i < count($objectivesA); $i++) {
119
+ if ($objectivesA[$i] > $objectivesB[$i]) {
120
+ return false; // A est pire sur au moins un objectif
121
+ }
122
+ if ($objectivesA[$i] < $objectivesB[$i]) {
123
+ $aIsBetterInOne = true; // A est strictement meilleur sur au moins un
124
+ }
125
+ }
126
+ return $aIsBetterInOne;
127
+ }
128
+
129
+ /**
130
+ * Trie une population en fronts de Pareto non-dominés.
131
+ * @param array<int, array> &$populationWithObjectives
132
+ * @return array<int, array>
133
+ */
134
+ private static function nonDominatedSort(array &$populationWithObjectives): array
135
+ {
136
+ $fronts = [[]];
137
+ $n = count($populationWithObjectives);
138
+
139
+ for ($i = 0; $i < $n; $i++) {
140
+ $p1 = &$populationWithObjectives[$i];
141
+ $p1['dominationCount'] = 0;
142
+ $p1['dominatedSolutions'] = [];
143
+
144
+ for ($j = 0; $j < $n; $j++) {
145
+ if ($i === $j) continue;
146
+ $p2 = &$populationWithObjectives[$j];
147
+
148
+ if (self::paretoDominates($p1['objectives'], $p2['objectives'])) {
149
+ $p1['dominatedSolutions'][] = $j;
150
+ } elseif (self::paretoDominates($p2['objectives'], $p1['objectives'])) {
151
+ $p1['dominationCount']++;
152
+ }
153
+ }
154
+
155
+ if ($p1['dominationCount'] === 0) {
156
+ $p1['rank'] = 0;
157
+ $fronts[0][] = $p1;
158
+ }
159
+ }
160
+
161
+ $i = 0;
162
+ while (!empty($fronts[$i])) {
163
+ $nextFront = [];
164
+ foreach ($fronts[$i] as $p1) {
165
+ foreach ($p1['dominatedSolutions'] as $p2_idx) {
166
+ $p2 = &$populationWithObjectives[$p2_idx];
167
+ $p2['dominationCount']--;
168
+ if ($p2['dominationCount'] === 0) {
169
+ $p2['rank'] = $i + 1;
170
+ $nextFront[] = $p2;
171
+ }
172
+ }
173
+ }
174
+ $i++;
175
+ if (!empty($nextFront)) {
176
+ $fronts[$i] = $nextFront;
177
+ }
178
+ }
179
+ return $fronts;
180
+ }
181
+
182
+ /**
183
+ * Calcule la distance de promiscuité (crowding distance) pour un front.
184
+ * @param array<int, array> &$front
185
+ */
186
+ private static function calculateCrowdingDistance(array &$front): void
187
+ {
188
+ if (empty($front)) return;
189
+
190
+ $numObjectives = count($front[0]['objectives']);
191
+ $l = count($front);
192
+
193
+ foreach ($front as &$p) {
194
+ $p['crowdingDistance'] = 0;
195
+ }
196
+
197
+ for ($i = 0; $i < $numObjectives; $i++) {
198
+ // Trier le front par l'objectif courant
199
+ usort($front, fn ($a, $b) => $a['objectives'][$i] <=> $b['objectives'][$i]);
200
+
201
+ $minObj = $front[0]['objectives'][$i];
202
+ $maxObj = $front[$l - 1]['objectives'][$i];
203
+
204
+ // Les solutions aux extrémités ont une distance infinie
205
+ $front[0]['crowdingDistance'] = INF;
206
+ $front[$l - 1]['crowdingDistance'] = INF;
207
+
208
+ if ($maxObj === $minObj) continue;
209
+
210
+ for ($j = 1; $j < $l - 1; $j++) {
211
+ $front[$j]['crowdingDistance'] +=
212
+ ($front[$j + 1]['objectives'][$i] - $front[$j - 1]['objectives'][$i]) /
213
+ ($maxObj - $minObj);
214
+ }
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Calcule la déviation d'une série de chiffres par rapport à la loi de Benford.
220
+ * @public
221
+ * @param array<int|float> $numbers
222
+ */
223
+ public static function benfordTest(array $numbers): float // Rendre la méthode publique et statique
224
+ {
225
+ if (count($numbers) < 10) {
226
+ return 0.0; // Pas assez de données
227
+ }
228
+
229
+ $leadingDigits = array_map(function ($n) {
230
+ $str = ltrim((string)$n, '0.');
231
+ return $str[0] ?? '';
232
+ }, $numbers);
233
+
234
+ $leadingDigits = array_filter($leadingDigits, fn ($d) => $d >= '1' && $d <= '9');
235
+
236
+ if (count($leadingDigits) < 10) {
237
+ return 0.0;
238
+ }
239
+
240
+ $counts = array_fill(1, 9, 0);
241
+ foreach ($leadingDigits as $digit) {
242
+ $counts[(int)$digit]++;
243
+ }
244
+
245
+ $benfordDistribution = [1 => 30.1, 2 => 17.6, 3 => 12.5, 4 => 9.7, 5 => 7.9, 6 => 6.7, 7 => 5.8, 8 => 5.1, 9 => 4.6];
246
+
247
+ $totalDeviation = 0.0;
248
+ for ($i = 1; $i <= 9; $i++) {
249
+ $observedFrequency = ($counts[$i] / count($leadingDigits)) * 100;
250
+ $expectedFrequency = $benfordDistribution[$i];
251
+ $totalDeviation += pow($observedFrequency - $expectedFrequency, 2);
252
+ }
253
+
254
+ return sqrt($totalDeviation) / 50.0;
255
+ }
256
256
  }