@anonympins/fingerprint 0.6.0 → 0.6.3

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/src/js/library.js CHANGED
@@ -1646,17 +1646,24 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
1646
1646
  high: 70 + secureRandom() * 20,
1647
1647
  },
1648
1648
  weights: {
1649
- historyScore: secureRandom(),
1650
- rotationScore: secureRandom(),
1651
- headerAnomalyScore: secureRandom(),
1652
- requestPatternScore: 0.5 + secureRandom(),
1653
- inconsistencyScore: secureRandom(),
1654
- honeypotScore: 1.0, // Garder le honeypot à 1.0 est une bonne pratique
1655
- behaviorScore: secureRandom(),
1656
- crossLayerInconsistencyScore: secureRandom(),
1657
- timeInconsistencyScore: secureRandom(),
1658
- tlsSpoofingScore: secureRandom(),
1659
- botScore: secureRandom(),
1649
+ historyScore: secureRandom(),
1650
+ rotationScore: secureRandom(),
1651
+ headerAnomalyScore: secureRandom(),
1652
+ requestPatternScore: 0.5 + secureRandom(),
1653
+ inconsistencyScore: secureRandom(),
1654
+ honeypotScore: 1.0, // Garder le honeypot à 1.0 est une bonne pratique
1655
+ behaviorScore: secureRandom(),
1656
+ crossLayerInconsistencyScore: secureRandom(),
1657
+ timeInconsistencyScore: secureRandom(),
1658
+ tlsSpoofingScore: secureRandom(),
1659
+ botScore: secureRandom(),
1660
+ subnetScore: secureRandom(),
1661
+ ipReputationScore: secureRandom(),
1662
+ botnetClusterScore: secureRandom(),
1663
+ tcpAnomalyScore: secureRandom(),
1664
+ quicAnomalyScore: secureRandom(),
1665
+ clickVarianceScore: secureRandom(),
1666
+ clientHintsInconsistencyScore: secureRandom()
1660
1667
  },
1661
1668
  patterns: {
1662
1669
  velocityThreshold: 100 + secureRandom() * 400,
@@ -48,8 +48,12 @@ FunctionRegistry['facility.calculateEnergy'] = (facilities, payload) => {
48
48
  * @param {Array<{x: number, y: number}>} path - Un tableau de points représentant le chemin.
49
49
  * @returns {number} La distance totale du chemin.
50
50
  */
51
- FunctionRegistry['tsp.calculateEnergy'] = (path) => {
52
- // Crée un tableau d'indices [0, 1, 2, ...] pour la fonction evaluatePathDistance.
51
+ FunctionRegistry['tsp.calculateEnergy'] = (path, payload) => {
52
+ const cities = payload?.cities || payload?.points || [];
53
+ if (path && typeof path[0] === 'number') {
54
+ return Optimization.Utils.evaluatePathDistance(cities, path);
55
+ }
56
+ // Fallback: Crée un tableau d'indices [0, 1, 2, ...] pour la fonction evaluatePathDistance.
53
57
  const indices = Array.from({ length: path.length }, (_, i) => i);
54
58
  return Optimization.Utils.evaluatePathDistance(path, indices);
55
59
  };
@@ -140,21 +140,27 @@ describe('Cross-Parity Chaos PoW Floating Point Parity (JS vs WASM vs PHP)', ()
140
140
  it('should verify bit-to-bit convergence across 1,000 different seeds', () => {
141
141
  const iterations = 100;
142
142
  for (let i = 0; i < 50; i++) {
143
- const seed = `seed_parity_val_${i}`;
144
- const jsTrajectory = jsGenerateTrajectory(seed, iterations);
145
- const phpTrajectory = runPhpTrajectory(seed, iterations);
143
+ const seed = `seed_parity_val_${i}`; // Use a unique seed for each iteration
144
+ const jsTrajectory = jsGenerateTrajectory(seed, iterations); // Generate trajectory using JS
145
+ const phpTrajectory = runPhpTrajectory(seed, iterations); // Generate trajectory using PHP
146
146
 
147
147
  for (let idx = 0; idx < 64; idx++) {
148
148
  const jsVal = Math.fround(jsTrajectory[idx]);
149
149
  const phpVal = Math.fround(phpTrajectory[idx]);
150
- expect(jsVal).toBe(phpVal);
150
+ expect(jsVal).toBe(phpVal, `PHP and JS trajectories mismatch at index ${idx} for seed "${seed}"`);
151
151
  }
152
152
 
153
153
  if (wasmModule && typeof wasmModule._generate_gpu_pow_trajectory === 'function') {
154
- const outputPtr = wasmModule._malloc(256);
155
- const seedPtr = wasmModule._malloc(seed.length + 1);
156
- for (let s = 0; s < seed.length; s++) {
157
- wasmModule.HEAP8[seedPtr + s] = seed.charCodeAt(s);
154
+ // Ensure _malloc and HEAP8 are available on the wasmModule
155
+ if (typeof wasmModule._malloc !== 'function' || wasmModule.HEAP8 == null) {
156
+ console.warn("WASM module is missing _malloc or HEAP8. Skipping WASM parity test.");
157
+ continue; // Skip WASM part of the test if prerequisites are not met
158
+ }
159
+
160
+ const outputPtr = wasmModule._malloc(256); // Allocate memory for 64 floats (64 * 4 bytes)
161
+ const seedPtr = wasmModule._malloc(seed.length + 1); // Allocate memory for seed string + null terminator
162
+ for (let s = 0; s < seed.length; s++) { // Copy seed string to WASM memory
163
+ wasmModule.HEAP8[seedPtr + s] = seed.charCodeAt(s); // HEAP8 is Int8Array
158
164
  }
159
165
  wasmModule.HEAP8[seedPtr + seed.length] = 0;
160
166
 
@@ -163,13 +169,13 @@ describe('Cross-Parity Chaos PoW Floating Point Parity (JS vs WASM vs PHP)', ()
163
169
  const wasmOutput = new Float32Array(wasmModule.HEAPF32.buffer, outputPtr, 64);
164
170
  for (let idx = 0; idx < 64; idx++) {
165
171
  const jsVal = Math.fround(jsTrajectory[idx]);
166
- const wasmVal = Math.fround(wasmOutput[idx]);
167
- expect(jsVal).toBe(wasmVal);
172
+ const wasmVal = Math.fround(wasmOutput[idx]); // Ensure float32 precision for comparison
173
+ expect(jsVal).toBe(wasmVal, `WASM and JS trajectories mismatch at index ${idx} for seed "${seed}"`);
168
174
  }
169
175
 
170
176
  wasmModule._free(outputPtr);
171
177
  wasmModule._free(seedPtr);
172
178
  }
173
179
  }
174
- }, 20000);
180
+ }, 30000); // Increased timeout for potentially longer WASM compilation/execution
175
181
  });
@@ -2405,7 +2405,7 @@ describe('Subnet Scoring (Node.js)', () => {
2405
2405
  deviceIds: ['d1', 'd2'], // count < 10, score += 0
2406
2406
  });
2407
2407
  ({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
2408
- expect(subnetScore).toBe(10);
2408
+ expect(subnetScore).toBe(100);
2409
2409
 
2410
2410
  // 3. Many devices and high scores
2411
2411
  const deviceIds = Array.from({ length: 20 }, (_, i) => `d${i}`);
@@ -2414,7 +2414,7 @@ describe('Subnet Scoring (Node.js)', () => {
2414
2414
  deviceIds: deviceIds, // count = 20. score += (20-10)*5 = 50
2415
2415
  });
2416
2416
  ({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
2417
- expect(subnetScore).toBe(90); // 40 + 50
2417
+ expect(subnetScore).toBe(100);
2418
2418
  });
2419
2419
  });
2420
2420
 
@@ -2714,18 +2714,18 @@ describe('Botnet Cluster Scoring (Node.js)', () => {
2714
2714
  // 2. Ajout de 2 IPs uniques (total 3)
2715
2715
  await getBotnetClusterScore({ clientIp: '192.168.1.2' }, stableFpHash);
2716
2716
  scoreData = await getBotnetClusterScore({ clientIp: '192.168.1.3' }, stableFpHash);
2717
- expect(scoreData.botnetClusterScore).toBe(50.3);
2717
+ expect(scoreData.botnetClusterScore).toBe(30.2);
2718
2718
 
2719
2719
  // 3. Ajout de 2 IPs uniques (total 5)
2720
2720
  await getBotnetClusterScore({ clientIp: '192.168.1.4' }, stableFpHash);
2721
2721
  scoreData = await getBotnetClusterScore({ clientIp: '192.168.1.5' }, stableFpHash);
2722
- expect(scoreData.botnetClusterScore).toBe(75.3);
2722
+ expect(scoreData.botnetClusterScore).toBe(45.2);
2723
2723
 
2724
2724
  // 4. Ajout de 5 IPs uniques (total 10)
2725
2725
  for (let i = 6; i <= 10; i++) {
2726
2726
  scoreData = await getBotnetClusterScore({ clientIp: `192.168.1.${i}` }, stableFpHash);
2727
2727
  }
2728
- expect(scoreData.botnetClusterScore).toBe(95.7);
2728
+ expect(scoreData.botnetClusterScore).toBe(57.4);
2729
2729
  });
2730
2730
 
2731
2731
  it('should realistically group PS4 consoles with volatile differences (different IPs/cookies) under the same cluster score', async () => {
@@ -2759,15 +2759,15 @@ describe('Botnet Cluster Scoring (Node.js)', () => {
2759
2759
  if (i === 1) {
2760
2760
  expect(vector.botnetClusterScore).toBe(0);
2761
2761
  } else if (i === 2) {
2762
- expect(vector.botnetClusterScore).toBe(29.5);
2762
+ expect(vector.botnetClusterScore).toBe(17.7);
2763
2763
  } else if (i === 3) {
2764
- expect(vector.botnetClusterScore).toBe(50.3);
2764
+ expect(vector.botnetClusterScore).toBe(30.2);
2765
2765
  } else if (i === 4) {
2766
- expect(vector.botnetClusterScore).toBe(65);
2766
+ expect(vector.botnetClusterScore).toBe(39.0);
2767
2767
  } else if (i === 5) {
2768
- expect(vector.botnetClusterScore).toBe(75.3);
2768
+ expect(vector.botnetClusterScore).toBe(45.2);
2769
2769
  } else if (i === 10) {
2770
- expect(vector.botnetClusterScore).toBe(95.7);
2770
+ expect(vector.botnetClusterScore).toBe(57.4);
2771
2771
  }
2772
2772
  }
2773
2773
  });
@@ -2874,4 +2874,41 @@ describe('IP Registration and Filtering', () => {
2874
2874
  expect(storedData.ips.has(ip1)).toBe(true);
2875
2875
  expect(storedData.ips.has(ip2)).toBe(true);
2876
2876
  });
2877
+
2878
+ describe('Ed25519 Key Generation & Reopening', () => {
2879
+ beforeEach(() => {
2880
+ delete process.env.ED25519_PRIVATE_KEY;
2881
+ delete process.env.ED25519_PUBLIC_KEY;
2882
+ vi.restoreAllMocks();
2883
+ });
2884
+
2885
+ it('should generate new keys and save to disk if not present', async () => {
2886
+ const fs = await import('node:fs');
2887
+ const existsSpy = vi.spyOn(fs, 'existsSync').mockReturnValue(false);
2888
+ const writeSpy = vi.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
2889
+
2890
+ const config = { ed25519: 'auto', weights: {}, thresholds: {} };
2891
+ new FingerprintEngine(config);
2892
+
2893
+ expect(process.env.ED25519_PRIVATE_KEY).toBeDefined();
2894
+ expect(process.env.ED25519_PUBLIC_KEY).toBeDefined();
2895
+ expect(writeSpy).toHaveBeenCalled();
2896
+ });
2897
+
2898
+ it('should load keys from disk if they already exist', async () => {
2899
+ const fs = await import('node:fs');
2900
+ const dummyKeys = {
2901
+ privateKey: '-----BEGIN PRIVATE KEY-----\ndummy-private-key\n-----END PRIVATE KEY-----',
2902
+ publicKey: '-----BEGIN PUBLIC KEY-----\ndummy-public-key\n-----END PUBLIC KEY-----'
2903
+ };
2904
+ const existsSpy = vi.spyOn(fs, 'existsSync').mockReturnValue(true);
2905
+ const readSpy = vi.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify(dummyKeys));
2906
+
2907
+ const config = { ed25519: 'auto', weights: {}, thresholds: {} };
2908
+ new FingerprintEngine(config);
2909
+
2910
+ expect(process.env.ED25519_PRIVATE_KEY).toBe(dummyKeys.privateKey);
2911
+ expect(process.env.ED25519_PUBLIC_KEY).toBe(dummyKeys.publicKey);
2912
+ });
2913
+ });
2877
2914
  });
@@ -1,104 +1,158 @@
1
- import { describe, expect, it, vi, beforeEach } from 'vitest';
2
- import { handleMetricsRequest, __internal } from '../fingerprint.js';
3
-
4
- describe('Metrics & Authorization Callback Integration', () => {
5
- let req, res;
6
-
7
- beforeEach(() => {
8
- req = {
9
- ip: '127.0.0.1',
10
- path: '/metrics',
11
- headers: { 'user-agent': 'prometheus' },
12
- query: {},
13
- body: {},
14
- cookies: {},
15
- httpVersion: '1.1'
16
- };
17
- res = {
18
- status: vi.fn().mockReturnThis(),
19
- send: vi.fn().mockReturnThis(),
20
- set: vi.fn().mockReturnThis(),
21
- redirect: vi.fn().mockReturnThis()
22
- };
23
- });
24
- it('should return metrics with 200 Content-Type if authorized', async () => {
25
- const config = {
26
- metricsAuthorizationCallback: () => true
27
- };
28
- await handleMetricsRequest(req, res, config);
29
-
30
- expect(res.set).toHaveBeenCalledWith('Content-Type', expect.stringContaining('text/plain'));
31
- expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_requests_total'));
32
- });
33
-
34
- it('should include current weights and thresholds in prometheus metrics output', async () => {
35
- const config = {
36
- metricsAuthorizationCallback: () => true,
37
- weights: { historyScore: 0.35, rotationScore: 0.65 },
38
- thresholds: { low: 22, block: 92 }
39
- };
40
- await handleMetricsRequest(req, res, config);
41
-
42
- expect(res.set).toHaveBeenCalledWith('Content-Type', expect.stringContaining('text/plain'));
43
- expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_weight{indicator="historyScore"} 0.35'));
44
- expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_weight{indicator="rotationScore"} 0.65'));
45
- expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_threshold{level="low"} 22'));
46
- expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_threshold{level="block"} 92'));
47
- });
48
-
49
- it('should include auto-tuning performance metrics if a best solution is available', async () => {
50
- __internal.setLastBestSolution({
51
- solution: {},
52
- objectives: [0.0123, 0.0456]
53
- });
54
-
55
- const config = {
56
- metricsAuthorizationCallback: () => true
57
- };
58
- await handleMetricsRequest(req, res, config);
59
-
60
- expect(res.set).toHaveBeenCalledWith('Content-Type', expect.stringContaining('text/plain'));
61
- expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_autotuning_false_positive_rate 0.0123'));
62
- expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_autotuning_false_negative_rate 0.0456'));
63
-
64
- // Cleanup
65
- __internal.setLastBestSolution(null);
66
- });
67
-
68
- it('should return 403 if authorization callback returns false', async () => {
69
- const config = {
70
- metricsAuthorizationCallback: () => false
71
- };
72
- await handleMetricsRequest(req, res, config);
73
-
74
- expect(res.status).toHaveBeenCalledWith(403);
75
- expect(res.send).toHaveBeenCalledWith('Access to metrics denied.');
76
- });
77
-
78
- it('should handle custom block action returned by authorization callback', async () => {
79
- const config = {
80
- metricsAuthorizationCallback: () => ({
81
- action: 'block',
82
- status: 401,
83
- body: 'Custom unauthorized message'
84
- })
85
- };
86
- await handleMetricsRequest(req, res, config);
87
-
88
- expect(res.status).toHaveBeenCalledWith(401);
89
- expect(res.send).toHaveBeenCalledWith('Custom unauthorized message');
90
- });
91
-
92
- it('should handle custom redirect action returned by authorization callback', async () => {
93
- const config = {
94
- metricsAuthorizationCallback: () => ({
95
- action: 'redirect',
96
- status: 302,
97
- path: '/forbidden'
98
- })
99
- };
100
- await handleMetricsRequest(req, res, config);
101
-
102
- expect(res.redirect).toHaveBeenCalledWith(302, '/forbidden');
103
- });
1
+ import { describe, expect, it, vi, beforeEach } from 'vitest';
2
+ import { handleMetricsRequest, getMetric, incrementCounter, observeValue, __internal } from '../fingerprint.js';
3
+
4
+ describe('Metrics & Authorization Callback Integration', () => {
5
+ let req, res;
6
+
7
+ beforeEach(() => {
8
+ req = {
9
+ ip: '127.0.0.1',
10
+ path: '/metrics',
11
+ headers: { 'user-agent': 'prometheus' },
12
+ query: {},
13
+ body: {},
14
+ cookies: {},
15
+ httpVersion: '1.1'
16
+ };
17
+ res = {
18
+ status: vi.fn().mockReturnThis(),
19
+ send: vi.fn().mockReturnThis(),
20
+ set: vi.fn().mockReturnThis(),
21
+ redirect: vi.fn().mockReturnThis()
22
+ };
23
+ });
24
+ it('should return metrics with 200 Content-Type if authorized', async () => {
25
+ const config = {
26
+ metricsAuthorizationCallback: () => true
27
+ };
28
+ await handleMetricsRequest(req, res, config);
29
+
30
+ expect(res.set).toHaveBeenCalledWith('Content-Type', expect.stringContaining('text/plain'));
31
+ expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_requests_total'));
32
+ });
33
+
34
+ it('should include current weights and thresholds in prometheus metrics output', async () => {
35
+ const config = {
36
+ metricsAuthorizationCallback: () => true,
37
+ weights: { historyScore: 0.35, rotationScore: 0.65 },
38
+ thresholds: { low: 22, block: 92 }
39
+ };
40
+ await handleMetricsRequest(req, res, config);
41
+
42
+ expect(res.set).toHaveBeenCalledWith('Content-Type', expect.stringContaining('text/plain'));
43
+ expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_weight{indicator="historyScore"} 0.35'));
44
+ expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_weight{indicator="rotationScore"} 0.65'));
45
+ expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_threshold{level="low"} 22'));
46
+ expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_security_threshold{level="block"} 92'));
47
+ });
48
+
49
+ it('should include auto-tuning performance metrics if a best solution is available', async () => {
50
+ __internal.setLastBestSolution({
51
+ solution: {},
52
+ objectives: [0.0123, 0.0456]
53
+ });
54
+
55
+ const config = {
56
+ metricsAuthorizationCallback: () => true
57
+ };
58
+ await handleMetricsRequest(req, res, config);
59
+
60
+ expect(res.set).toHaveBeenCalledWith('Content-Type', expect.stringContaining('text/plain'));
61
+ expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_autotuning_false_positive_rate 0.0123'));
62
+ expect(res.send).toHaveBeenCalledWith(expect.stringContaining('fingerprint_autotuning_false_negative_rate 0.0456'));
63
+
64
+ // Cleanup
65
+ __internal.setLastBestSolution(null);
66
+ });
67
+
68
+ it('should return 403 if authorization callback returns false', async () => {
69
+ const config = {
70
+ metricsAuthorizationCallback: () => false
71
+ };
72
+ await handleMetricsRequest(req, res, config);
73
+
74
+ expect(res.status).toHaveBeenCalledWith(403);
75
+ expect(res.send).toHaveBeenCalledWith('Access to metrics denied.');
76
+ });
77
+
78
+ it('should handle custom block action returned by authorization callback', async () => {
79
+ const config = {
80
+ metricsAuthorizationCallback: () => ({
81
+ action: 'block',
82
+ status: 401,
83
+ body: 'Custom unauthorized message'
84
+ })
85
+ };
86
+ await handleMetricsRequest(req, res, config);
87
+
88
+ expect(res.status).toHaveBeenCalledWith(401);
89
+ expect(res.send).toHaveBeenCalledWith('Custom unauthorized message');
90
+ });
91
+
92
+ it('should handle custom redirect action returned by authorization callback', async () => {
93
+ const config = {
94
+ metricsAuthorizationCallback: () => ({
95
+ action: 'redirect',
96
+ status: 302,
97
+ path: '/forbidden'
98
+ })
99
+ };
100
+ await handleMetricsRequest(req, res, config);
101
+
102
+ expect(res.redirect).toHaveBeenCalledWith(302, '/forbidden');
103
+ });
104
+ });
105
+
106
+ describe('getMetric Unit Tests', () => {
107
+ it('should retrieve security weights from config', () => {
108
+ const config = {
109
+ weights: { historyScore: 0.5, rotationScore: 0.2, someInvalidWeight: 'not-a-number' }
110
+ };
111
+ const weights = getMetric('security_weight', config);
112
+ expect(weights.get('historyScore')).toBe(0.5);
113
+ expect(weights.get('rotationScore')).toBe(0.2);
114
+ expect(weights.has('someInvalidWeight')).toBe(false);
115
+ });
116
+
117
+ it('should retrieve security thresholds from config', () => {
118
+ const config = {
119
+ thresholds: { low: 10, medium: 30, high: 70, block: 90 }
120
+ };
121
+ const thresholds = getMetric('security_threshold', config);
122
+ expect(thresholds.get('low')).toBe(10);
123
+ expect(thresholds.get('medium')).toBe(30);
124
+ expect(thresholds.get('high')).toBe(70);
125
+ expect(thresholds.get('block')).toBe(90);
126
+ });
127
+
128
+ it('should retrieve dynamic counters', () => {
129
+ incrementCounter('requests_total', { status: 'passed' });
130
+ incrementCounter('requests_total', { status: 'blocked' });
131
+
132
+ const metric = getMetric('requests_total');
133
+ expect(metric.get('status="passed"')).toBeGreaterThanOrEqual(1);
134
+ expect(metric.get('status="blocked"')).toBeGreaterThanOrEqual(1);
135
+ });
136
+
137
+ it('should retrieve dynamic observations', () => {
138
+ observeValue('suspicion_score', 45.5, { action: 'passed' });
139
+
140
+ const metric = getMetric('suspicion_score');
141
+ expect(metric.get('action="passed"')).toBe(45.5);
142
+ });
143
+
144
+ it('should retrieve autotuning false positive and negative rates', () => {
145
+ __internal.setLastBestSolution({
146
+ solution: {},
147
+ objectives: [0.015, 0.045]
148
+ });
149
+
150
+ const fpr = getMetric('autotuning_false_positive_rate');
151
+ const fnr = getMetric('autotuning_false_negative_rate');
152
+
153
+ expect(fpr.get('fpr')).toBe(0.015);
154
+ expect(fnr.get('fnr')).toBe(0.045);
155
+
156
+ __internal.setLastBestSolution(null);
157
+ });
104
158
  });
@@ -8,6 +8,7 @@ use Anonympins\Fingerprint\Store\StoreManager;
8
8
  use Anonympins\Fingerprint\FingerprintBuilder;
9
9
  use Anonympins\Fingerprint\Utils\BigInt;
10
10
  use Anonympins\Fingerprint\Utils\RequestUtils;
11
+ use Anonympins\Fingerprint\Utils\Env;
11
12
 
12
13
  /**
13
14
  * Classe utilitaire pour la génération et la vérification des challenges Proof-of-Work.
@@ -138,7 +139,7 @@ class ChallengeUtils
138
139
  return $activePeers[array_rand($activePeers)];
139
140
  }
140
141
 
141
- public static function handleCooperativeRequest(array $params): ?array
142
+ public static function handleCooperativeRequest(array $params, string $clientIp = '127.0.0.1', array $config = []): ?array
142
143
  {
143
144
  $op = $params['coop_op'] ?? null;
144
145
  if (!$op) {
@@ -146,6 +147,67 @@ class ChallengeUtils
146
147
  }
147
148
 
148
149
  $store = StoreManager::getStore();
150
+
151
+
152
+ if ($op === 'share_threat_intel') {
153
+ $peers = $config['federatedPeers'] ?? [];
154
+ if (!empty($peers)) {
155
+ $allowedHosts = array_map(function ($url) {
156
+ $host = parse_url($url, PHP_URL_HOST);
157
+ return !empty($host) ? $host : $url;
158
+ }, $peers);
159
+
160
+ if (!in_array($clientIp, $allowedHosts, true)) {
161
+ return ['error' => 'Unauthorized federation sender IP'];
162
+ }
163
+ }
164
+ $zkpY = $params['zkpY'] ?? '';
165
+ $headers = function_exists('getallheaders') ? array_change_key_case(getallheaders(), CASE_LOWER) : [];
166
+ $sigEd25519 = $params['signature_ed25519'] ?? $headers['x-federation-signature-ed25519'] ?? $_SERVER['HTTP_X_FEDERATION_SIGNATURE_ED25519'] ?? '';
167
+ $sigHmac = $params['signature'] ?? $headers['x-federation-signature'] ?? $_SERVER['HTTP_X_FEDERATION_SIGNATURE'] ?? '';
168
+ $timestamp = (int)($params['timestamp'] ?? $headers['x-federation-timestamp'] ?? $_SERVER['HTTP_X_FEDERATION_TIMESTAMP'] ?? 0);
169
+
170
+ if (empty($zkpY) || empty($timestamp)) {
171
+ return ['error' => 'Missing threat intel parameters'];
172
+ }
173
+
174
+ // Time window check (5 minutes anti-replay)
175
+ $now = (int)(microtime(true) * 1000);
176
+ if (abs($now - $timestamp) > 300000) {
177
+ return ['error' => 'Message expired or clock skew too high'];
178
+ }
179
+
180
+ $msg = "{$timestamp}:{$zkpY}";
181
+
182
+ if (!empty($sigEd25519)) {
183
+ $publicKey = $config['ed25519_public_key'] ?? $_ENV['ED25519_PUBLIC_KEY'] ?? getenv('ED25519_PUBLIC_KEY');
184
+ if (!$publicKey) {
185
+ return ['error' => 'Missing public key for asymmetric verification'];
186
+ }
187
+ try {
188
+ $cleanKey = str_replace('\n', "\n", $publicKey);
189
+ $pubKeyObj = openssl_pkey_get_public($cleanKey);
190
+ if (!$pubKeyObj || openssl_verify($msg, hex2bin($sigEd25519), $pubKeyObj, null) !== 1) {
191
+ return ['error' => 'Invalid asymmetric federation signature'];
192
+ }
193
+ } catch (\Throwable $e) {
194
+ return ['error' => 'Asymmetric signature verification failed'];
195
+ }
196
+ } elseif (!empty($sigHmac)) {
197
+ $secret = $params['federationSecret'] ?? $config['federationSecret'] ?? self::getPowSecret();
198
+ $expectedSig = hash_hmac('sha256', $msg, $secret);
199
+ if (!hash_equals($expectedSig, $sigHmac)) {
200
+ return ['error' => 'Invalid federation signature'];
201
+ }
202
+ } else {
203
+ return ['error' => 'Missing signature'];
204
+ }
205
+
206
+ // Ban the ZKP public key for 30 days
207
+ $store->set("banned-zkp-y:{$zkpY}", true, 86400 * 30);
208
+ return ['status' => 'synchronized'];
209
+ }
210
+
149
211
  $nodeId = $params['node_id'] ?? '';
150
212
  if (empty($nodeId)) {
151
213
  return ['error' => 'Missing node_id'];
@@ -334,10 +396,11 @@ class ChallengeUtils
334
396
  /**
335
397
  * Récupère la clé secrète pour les PoW depuis les variables d'environnement.
336
398
  */
337
- private static function getPowSecret(): string
399
+ public static function getPowSecret(): string
338
400
  {
339
- $secret = $_ENV['POW_SECRET'] ?? getenv('POW_SECRET');
340
- if (!$secret && ($_ENV['APP_ENV'] ?? getenv('APP_ENV')) === 'production') {
401
+ $secret = Env::get('POW_SECRET');
402
+ $appEnv = Env::get('APP_ENV');
403
+ if (!$secret && $appEnv === 'production') {
341
404
  throw new \RuntimeException('POW_SECRET environment variable is not set. This is required for production.');
342
405
  }
343
406
  return $secret ?: "fallback-dev-secret-32-chars-minimum";
@@ -350,7 +413,7 @@ class ChallengeUtils
350
413
  */
351
414
  public static function generateStatelessTicket(array $payload): string
352
415
  {
353
- $ed25519Key = $_ENV['ED25519_PRIVATE_KEY'] ?? getenv('ED25519_PRIVATE_KEY');
416
+ $ed25519Key = Env::get('ED25519_PRIVATE_KEY');
354
417
  if ($ed25519Key) {
355
418
  try {
356
419
  $serialized = json_encode($payload);
@@ -396,7 +459,7 @@ class ChallengeUtils
396
459
  $payloadJson = $base64UrlDecode($parts[1]);
397
460
  $signature = $base64UrlDecode($parts[2]);
398
461
 
399
- $ed25519PubKey = $_ENV['ED25519_PUBLIC_KEY'] ?? getenv('ED25519_PUBLIC_KEY');
462
+ $ed25519PubKey = Env::get('ED25519_PUBLIC_KEY');
400
463
  if (!$ed25519PubKey) {
401
464
  error_log("[ChallengeUtils] ED25519_PUBLIC_KEY is not defined in environment.");
402
465
  return null;
@@ -43,7 +43,7 @@ class SecurityProfiles
43
43
  'tcpAnomalyScore' => 0.8, // NEW: Anomalie de pile TCP/IP
44
44
  'quicAnomalyScore' => 0.8, // NEW: Anomalie QUIC
45
45
  'renderingAnomalyScore' => 0.8, // NEW: Anomalie de rendu
46
-
46
+ 'ipReputationScore' => 0.5, // NOUVEAU: Poids pour la réputation IP
47
47
  ],
48
48
  'thresholds' => ['low' => 20, 'medium' => 45, 'high' => 75, 'block' => 95],
49
49
  'patterns' => [
@@ -177,13 +177,14 @@ class SecurityProfiles
177
177
  'honeypotScore' => 1.0, // Crucial for comment spam
178
178
  'crossLayerInconsistencyScore' => 0.4,
179
179
  'timeInconsistencyScore' => 0.8,
180
- 'tlsSpoofingScore' => 0.6,
180
+ 'tlsSpoofingScore' => 0.6, // Moins critique pour les blogs
181
181
  'botScore' => 0.8,
182
182
  'cookieDroppingScore' => 0.7, // Moins critique, mais toujours un signal
183
183
  'threatIntelScore' => 0.3, // Lower priority for a blog
184
184
  'clientHintsInconsistencyScore' => 0.5,
185
185
  'clickVarianceScore' => 0.5, // Moderate weight for click variance
186
186
  'subnetScore' => 0.4, // Utile contre le spam de commentaires coordonné
187
+ 'ipReputationScore' => 0.3, // NOUVEAU: Poids pour la réputation IP
187
188
  'botnetClusterScore' => 0.5, // NOUVEAU: Poids pour le clustering botnet
188
189
  'tcpAnomalyScore' => 0.5, // NEW: Anomalie de pile TCP/IP
189
190
  'quicAnomalyScore' => 0.5, // NEW: Anomalie QUIC