@anonympins/fingerprint 0.6.7 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +3 -7
- package/package.json +1 -1
- package/public/fp.wasm +0 -0
- package/src/js/build-client.js +1 -1
- package/src/js/dynamic-wasm.js +7 -7
- package/src/js/fingerprint.client.js +1463 -1447
- package/src/js/fingerprint.js +369 -95
- package/src/js/library.js +15 -8
- package/src/js/pow.solver.inline.js +56 -10
- package/src/js/pow.solver.js +56 -10
- package/src/js/pow.worker.js +35 -13
- package/src/js/tests/fingerprint.test.js +69 -35
- package/src/php/Challenge/ChallengeUtils.php +148 -22
- package/src/php/Config/SecurityProfiles.php +1 -0
- package/src/php/FingerprintClient.php +148 -148
- package/src/php/FingerprintEngine.php +1834 -1880
- package/src/php/Ja3AnomalyDetector.php +257 -227
- package/src/php/Optimization/OptimizationOperators.php +2 -2
- package/src/php/Tests/FingerprintEngineTest.php +2 -33
- package/src/php/Utils/RequestUtils.php +113 -18
- package/src/php/Utils/TLSClientHelloParser.php +158 -1
- package/src/js/fingerprint.client.obfuscated.js +0 -1
package/src/js/library.js
CHANGED
|
@@ -19,7 +19,9 @@ import crypto from "node:crypto";
|
|
|
19
19
|
* Génère un nombre flottant aléatoire entre 0 (inclus) et 1 (exclus).
|
|
20
20
|
* @returns {number}
|
|
21
21
|
*/
|
|
22
|
-
const random = () =>
|
|
22
|
+
const random = () => {
|
|
23
|
+
return crypto.randomInt(0, 4294967296) / 4294967296;
|
|
24
|
+
};
|
|
23
25
|
|
|
24
26
|
const Optimization = {
|
|
25
27
|
// eslint-disable-line no-unused-vars
|
|
@@ -1573,7 +1575,7 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
|
|
|
1573
1575
|
importance: 3.0,
|
|
1574
1576
|
ux_vs_security_ratio: 0.8, // 80% FPR / 20% FNR (UX prioritaire)
|
|
1575
1577
|
target_threshold: 'low',
|
|
1576
|
-
indicators: ['requestPatternScore', 'renderingAnomalyScore', 'clientHintsInconsistencyScore']
|
|
1578
|
+
indicators: ['requestPatternScore', 'renderingAnomalyScore', 'clientHintsInconsistencyScore', 'virtualizationScore']
|
|
1577
1579
|
},
|
|
1578
1580
|
distributed_botnets: {
|
|
1579
1581
|
importance: 6.0,
|
|
@@ -1585,7 +1587,7 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
|
|
|
1585
1587
|
importance: 5.0,
|
|
1586
1588
|
ux_vs_security_ratio: 0.4, // 40% FPR / 60% FNR
|
|
1587
1589
|
target_threshold: 'medium',
|
|
1588
|
-
indicators: ['botScore', 'tlsSpoofingScore', 'tcpAnomalyScore']
|
|
1590
|
+
indicators: ['botScore', 'tlsSpoofingScore', 'tcpAnomalyScore', 'virtualizationScore']
|
|
1589
1591
|
}
|
|
1590
1592
|
};
|
|
1591
1593
|
|
|
@@ -1600,7 +1602,8 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
|
|
|
1600
1602
|
crossLayerInconsistencyScore: 0, timeInconsistencyScore: 0, tlsSpoofingScore: 0,
|
|
1601
1603
|
clickVarianceScore: 0, clientHintsInconsistencyScore: 0, subnetScore: 0,
|
|
1602
1604
|
ipReputationScore: 0, botnetClusterScore: 0, tcpAnomalyScore: 0,
|
|
1603
|
-
quicAnomalyScore: 0, renderingAnomalyScore: 0, threatIntelScore: 0
|
|
1605
|
+
quicAnomalyScore: 0, renderingAnomalyScore: 0, threatIntelScore: 0,
|
|
1606
|
+
virtualizationScore: 0
|
|
1604
1607
|
}
|
|
1605
1608
|
},
|
|
1606
1609
|
{
|
|
@@ -1612,7 +1615,8 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
|
|
|
1612
1615
|
crossLayerInconsistencyScore: 80, timeInconsistencyScore: 100, tlsSpoofingScore: 100,
|
|
1613
1616
|
clickVarianceScore: 100, clientHintsInconsistencyScore: 90, subnetScore: 100,
|
|
1614
1617
|
ipReputationScore: 100, botnetClusterScore: 100, tcpAnomalyScore: 100,
|
|
1615
|
-
quicAnomalyScore: 100, renderingAnomalyScore: 100, threatIntelScore: 100
|
|
1618
|
+
quicAnomalyScore: 100, renderingAnomalyScore: 100, threatIntelScore: 100,
|
|
1619
|
+
virtualizationScore: 100
|
|
1616
1620
|
}
|
|
1617
1621
|
},
|
|
1618
1622
|
{
|
|
@@ -1624,7 +1628,8 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
|
|
|
1624
1628
|
crossLayerInconsistencyScore: 10, timeInconsistencyScore: 10, tlsSpoofingScore: 10,
|
|
1625
1629
|
clickVarianceScore: 0, clientHintsInconsistencyScore: 10, subnetScore: 5,
|
|
1626
1630
|
ipReputationScore: 5, botnetClusterScore: 5, tcpAnomalyScore: 10,
|
|
1627
|
-
quicAnomalyScore: 10, renderingAnomalyScore: 5, threatIntelScore: 0
|
|
1631
|
+
quicAnomalyScore: 10, renderingAnomalyScore: 5, threatIntelScore: 0,
|
|
1632
|
+
virtualizationScore: 0
|
|
1628
1633
|
}
|
|
1629
1634
|
},
|
|
1630
1635
|
{
|
|
@@ -1636,7 +1641,8 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
|
|
|
1636
1641
|
crossLayerInconsistencyScore: 20, timeInconsistencyScore: 20, tlsSpoofingScore: 30,
|
|
1637
1642
|
clickVarianceScore: 20, clientHintsInconsistencyScore: 20, subnetScore: 15,
|
|
1638
1643
|
ipReputationScore: 15, botnetClusterScore: 20, tcpAnomalyScore: 30,
|
|
1639
|
-
quicAnomalyScore: 20, renderingAnomalyScore: 20, threatIntelScore: 0
|
|
1644
|
+
quicAnomalyScore: 20, renderingAnomalyScore: 20, threatIntelScore: 0,
|
|
1645
|
+
virtualizationScore: 30
|
|
1640
1646
|
}
|
|
1641
1647
|
}
|
|
1642
1648
|
];
|
|
@@ -1813,7 +1819,8 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
|
|
|
1813
1819
|
tcpAnomalyScore: 0.8,
|
|
1814
1820
|
quicAnomalyScore: 0.8,
|
|
1815
1821
|
renderingAnomalyScore: 0.8,
|
|
1816
|
-
threatIntelScore: 1.0
|
|
1822
|
+
threatIntelScore: 1.0,
|
|
1823
|
+
virtualizationScore: 0.8
|
|
1817
1824
|
};
|
|
1818
1825
|
|
|
1819
1826
|
// Un "individu" est un objet de configuration complet
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
9
|
function secureRandom() {
|
|
10
|
-
if (typeof
|
|
10
|
+
if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.getRandomValues) {
|
|
11
11
|
const array = new Uint32Array(1);
|
|
12
|
-
|
|
13
|
-
return array[0] /
|
|
12
|
+
globalThis.crypto.getRandomValues(array);
|
|
13
|
+
return array[0] / 0xffffffff;
|
|
14
14
|
}
|
|
15
15
|
return Math.random();
|
|
16
16
|
}
|
|
@@ -53,6 +53,25 @@ function generateBlock(seed, blockIndex, blockSize = 1024) {
|
|
|
53
53
|
return block;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
async function readSpaceBlock(blockIdx) {
|
|
57
|
+
const db = await openDb();
|
|
58
|
+
const transaction = db.transaction("blocks", "readonly");
|
|
59
|
+
const store = transaction.objectStore("blocks");
|
|
60
|
+
const getReq = store.get(blockIdx);
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
getReq.onsuccess = () => {
|
|
63
|
+
const block = getReq.result;
|
|
64
|
+
if (block) {
|
|
65
|
+
const hex = Array.from(block).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
66
|
+
resolve(hex);
|
|
67
|
+
} else {
|
|
68
|
+
reject(new Error("Block not found"));
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
getReq.onerror = () => reject(getReq.error);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
56
75
|
async function loadTfjs() {
|
|
57
76
|
if (typeof tf !== 'undefined') return tf;
|
|
58
77
|
if (typeof window !== 'undefined') {
|
|
@@ -475,7 +494,7 @@ const ClientOptimizers = {
|
|
|
475
494
|
|
|
476
495
|
const mutate = (solution) => {
|
|
477
496
|
const newSolution = [...solution];
|
|
478
|
-
const i = Math.floor(
|
|
497
|
+
const i = Math.floor(secureRandom() * 4);
|
|
479
498
|
const mutationFactors = [500, 1000, 0.1, 2];
|
|
480
499
|
newSolution[i] += (secureRandom() - 0.5) * mutationFactors[i];
|
|
481
500
|
return newSolution;
|
|
@@ -741,18 +760,44 @@ async function solveCpuTargetInline(baseBlock, target, progressCallback) {
|
|
|
741
760
|
const targetStr = typeof target === 'string' ? target : target.toString(16);
|
|
742
761
|
const targetPtr = wasmModule._malloc(targetStr.length + 1);
|
|
743
762
|
for (let i = 0; i < targetStr.length; i++) {
|
|
744
|
-
wasmModule.
|
|
763
|
+
wasmModule.HEAPU8[targetPtr + i] = targetStr.charCodeAt(i);
|
|
764
|
+
}
|
|
765
|
+
wasmModule.HEAPU8[targetPtr + targetStr.length] = 0; // Null-terminator
|
|
766
|
+
try {
|
|
767
|
+
const solution = wasmModule._solve_cpu_target(ptr, len, targetPtr);
|
|
768
|
+
return solution;
|
|
769
|
+
} finally {
|
|
770
|
+
wasmModule._free(ptr);
|
|
771
|
+
wasmModule._free(targetPtr);
|
|
745
772
|
}
|
|
746
|
-
wasmModule.HEAP8[targetPtr + targetStr.length] = 0;
|
|
747
|
-
const solution = wasmModule._solve_cpu_target(ptr, len, targetPtr);
|
|
748
|
-
wasmModule._free(ptr);
|
|
749
|
-
wasmModule._free(targetPtr);
|
|
750
|
-
return solution;
|
|
751
773
|
}
|
|
752
774
|
|
|
753
775
|
// Try Web Worker execution first if supported and not blocked by CSP
|
|
754
776
|
if (typeof window !== 'undefined' && typeof Worker !== 'undefined') {
|
|
755
777
|
try {
|
|
778
|
+
const staticWorkerPath = window.ClientLibrary?.workerPath || (window.ClientConfig && window.ClientConfig.workerPath);
|
|
779
|
+
if (staticWorkerPath) {
|
|
780
|
+
return await new Promise((resolve, reject) => {
|
|
781
|
+
const worker = new Worker(staticWorkerPath);
|
|
782
|
+
worker.onmessage = (event) => {
|
|
783
|
+
if (event.data.type === 'progress') {
|
|
784
|
+
if (progressCallback) progressCallback(event.data.solution);
|
|
785
|
+
} else if (event.data.type === 'success') {
|
|
786
|
+
resolve(event.data.solution);
|
|
787
|
+
worker.terminate();
|
|
788
|
+
} else if (event.data.solution !== undefined) {
|
|
789
|
+
// Fallback pour compatibilité avec l'ancienne signature
|
|
790
|
+
resolve(event.data.solution);
|
|
791
|
+
worker.terminate();
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
worker.onerror = (err) => {
|
|
795
|
+
worker.terminate();
|
|
796
|
+
reject(err);
|
|
797
|
+
};
|
|
798
|
+
worker.postMessage({ baseBlock, target: cpuTarget.toString() });
|
|
799
|
+
});
|
|
800
|
+
}
|
|
756
801
|
return await new Promise((resolve, reject) => {
|
|
757
802
|
const workerCode = `
|
|
758
803
|
self.onmessage = async (e) => {
|
|
@@ -1117,6 +1162,7 @@ async function solveChallenge(challenge, fingerprint = '') { // fingerprint para
|
|
|
1117
1162
|
if (typeof window !== 'undefined') {
|
|
1118
1163
|
window.solveCpuChallengeInline = solveCpuTargetInline;
|
|
1119
1164
|
window.solveMemoryChallenge = solveMemory;
|
|
1165
|
+
window.readSpaceBlock = readSpaceBlock;
|
|
1120
1166
|
window.solveTspChallenge = solveTsp;
|
|
1121
1167
|
window.solveChallenge = solveChallenge;
|
|
1122
1168
|
}
|
package/src/js/pow.solver.js
CHANGED
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
12
|
function secureRandom() {
|
|
13
|
-
if (typeof
|
|
13
|
+
if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.getRandomValues) {
|
|
14
14
|
const array = new Uint32Array(1);
|
|
15
|
-
|
|
16
|
-
return array[0] /
|
|
15
|
+
globalThis.crypto.getRandomValues(array);
|
|
16
|
+
return array[0] / 0xffffffff;
|
|
17
17
|
}
|
|
18
18
|
return Math.random();
|
|
19
19
|
}
|
|
@@ -102,6 +102,25 @@ export async function initializeSpace(seed, sizeMb) {
|
|
|
102
102
|
store.put({ sizeMb, seed }, metadataKey);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
export async function readSpaceBlock(blockIdx) {
|
|
106
|
+
const db = await openDb();
|
|
107
|
+
const transaction = db.transaction("blocks", "readonly");
|
|
108
|
+
const store = transaction.objectStore("blocks");
|
|
109
|
+
const getReq = store.get(blockIdx);
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
getReq.onsuccess = () => {
|
|
112
|
+
const block = getReq.result;
|
|
113
|
+
if (block) {
|
|
114
|
+
const hex = Array.from(block).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
115
|
+
resolve(hex);
|
|
116
|
+
} else {
|
|
117
|
+
reject(new Error("Block not found"));
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
getReq.onerror = () => reject(getReq.error);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
105
124
|
export async function solveSpaceChallenge(seed, queries, nonce, clientSecret, peerBlock = '') {
|
|
106
125
|
const db = await openDb();
|
|
107
126
|
const transaction = db.transaction("blocks", "readonly");
|
|
@@ -163,18 +182,44 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
|
|
|
163
182
|
const targetStr = typeof target === 'string' ? target : target.toString(16);
|
|
164
183
|
const targetPtr = wasmModule._malloc(targetStr.length + 1);
|
|
165
184
|
for (let i = 0; i < targetStr.length; i++) {
|
|
166
|
-
wasmModule.
|
|
185
|
+
wasmModule.HEAPU8[targetPtr + i] = targetStr.charCodeAt(i);
|
|
186
|
+
}
|
|
187
|
+
wasmModule.HEAPU8[targetPtr + targetStr.length] = 0; // Null-terminator
|
|
188
|
+
try {
|
|
189
|
+
const solution = wasmModule._solve_cpu_target(ptr, len, targetPtr);
|
|
190
|
+
return solution;
|
|
191
|
+
} finally {
|
|
192
|
+
wasmModule._free(ptr);
|
|
193
|
+
wasmModule._free(targetPtr);
|
|
167
194
|
}
|
|
168
|
-
wasmModule.HEAP8[targetPtr + targetStr.length] = 0;
|
|
169
|
-
const solution = wasmModule._solve_cpu_target(ptr, len, targetPtr);
|
|
170
|
-
wasmModule._free(ptr);
|
|
171
|
-
wasmModule._free(targetPtr);
|
|
172
|
-
return solution;
|
|
173
195
|
}
|
|
174
196
|
|
|
175
197
|
// Try Web Worker execution first if supported and not blocked by CSP
|
|
176
198
|
if (typeof window !== 'undefined' && typeof Worker !== 'undefined') {
|
|
177
199
|
try {
|
|
200
|
+
const staticWorkerPath = window.ClientLibrary?.workerPath || (window.ClientConfig && window.ClientConfig.workerPath);
|
|
201
|
+
if (staticWorkerPath) {
|
|
202
|
+
return await new Promise((resolve, reject) => {
|
|
203
|
+
const worker = new Worker(staticWorkerPath);
|
|
204
|
+
worker.onmessage = (event) => {
|
|
205
|
+
if (event.data.type === 'progress') {
|
|
206
|
+
if (progressCallback) progressCallback(event.data.solution);
|
|
207
|
+
} else if (event.data.type === 'success') {
|
|
208
|
+
resolve(event.data.solution);
|
|
209
|
+
worker.terminate();
|
|
210
|
+
} else if (event.data.solution !== undefined) {
|
|
211
|
+
// Fallback pour compatibilité avec l'ancienne signature
|
|
212
|
+
resolve(event.data.solution);
|
|
213
|
+
worker.terminate();
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
worker.onerror = (err) => {
|
|
217
|
+
worker.terminate();
|
|
218
|
+
reject(err);
|
|
219
|
+
};
|
|
220
|
+
worker.postMessage({ baseBlock, target: cpuTarget.toString() });
|
|
221
|
+
});
|
|
222
|
+
}
|
|
178
223
|
return await new Promise((resolve, reject) => {
|
|
179
224
|
const workerCode = `
|
|
180
225
|
self.onmessage = async (e) => {
|
|
@@ -781,7 +826,7 @@ const ClientOptimizers = {
|
|
|
781
826
|
|
|
782
827
|
const mutate = (solution) => {
|
|
783
828
|
const newSolution = [...solution];
|
|
784
|
-
const i = Math.floor(
|
|
829
|
+
const i = Math.floor(secureRandom() * 4);
|
|
785
830
|
const mutationFactors = [500, 1000, 0.1, 2];
|
|
786
831
|
newSolution[i] += (secureRandom() - 0.5) * mutationFactors[i];
|
|
787
832
|
return newSolution;
|
|
@@ -1147,6 +1192,7 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
|
|
|
1147
1192
|
if (typeof window !== 'undefined') {
|
|
1148
1193
|
window.solveCpuChallengeInline = solveCpuTargetInline;
|
|
1149
1194
|
window.solveMemoryChallenge = solveMemory;
|
|
1195
|
+
window.readSpaceBlock = readSpaceBlock;
|
|
1150
1196
|
window.solveTspChallenge = solveTsp;
|
|
1151
1197
|
window.solveChallenge = solveChallenge;
|
|
1152
1198
|
}
|
package/src/js/pow.worker.js
CHANGED
|
@@ -5,23 +5,45 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
self.onmessage = async (event) => {
|
|
8
|
-
const { message, target } = event.data;
|
|
8
|
+
const { message, baseBlock, target } = event.data;
|
|
9
|
+
const cpuTarget = BigInt(target);
|
|
9
10
|
let solution = 0;
|
|
10
11
|
const encoder = new TextEncoder();
|
|
12
|
+
if (baseBlock) {
|
|
13
|
+
// Mode solveCpuTargetInline (Uint8Array base block)
|
|
14
|
+
const blockArray = new Uint8Array(baseBlock);
|
|
15
|
+
while (true) {
|
|
16
|
+
const solutionBytes = encoder.encode(String(solution));
|
|
17
|
+
const finalBlock = new Uint8Array(blockArray.length + solutionBytes.length);
|
|
18
|
+
finalBlock.set(blockArray);
|
|
19
|
+
finalBlock.set(solutionBytes, blockArray.length);
|
|
20
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', finalBlock);
|
|
21
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
22
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
11
23
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
if (BigInt('0x' + hashHex) < cpuTarget) {
|
|
25
|
+
self.postMessage({ type: 'success', solution });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
solution++;
|
|
29
|
+
if (solution % 25000 === 0) {
|
|
30
|
+
self.postMessage({ type: 'progress', solution });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
// Mode solveCpuTarget standard/legacy (Message string)
|
|
35
|
+
while (true) {
|
|
36
|
+
const currentMessage = `${message}:${solution}`;
|
|
37
|
+
const data = encoder.encode(currentMessage);
|
|
38
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
39
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
40
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
19
41
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
42
|
+
if (BigInt('0x' + hashHex) < cpuTarget) {
|
|
43
|
+
self.postMessage({ solution });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
solution++;
|
|
24
47
|
}
|
|
25
|
-
solution++;
|
|
26
48
|
}
|
|
27
49
|
};
|
|
@@ -665,38 +665,6 @@ describe('Fingerprint & PoW Security Suite', () => {
|
|
|
665
665
|
expect(next, 'next() should have been called for a request with a valid ticket').toHaveBeenCalled();
|
|
666
666
|
});
|
|
667
667
|
|
|
668
|
-
test('should issue a new challenge (re-challenge) if suspicion score is high even with a valid ticket', async () => {
|
|
669
|
-
vi.spyOn(__internal, 'getSuspicionVector').mockResolvedValue({
|
|
670
|
-
historyScore: 80,
|
|
671
|
-
rotationScore: 80,
|
|
672
|
-
headerAnomalyScore: 80,
|
|
673
|
-
inconsistencyScore: 80,
|
|
674
|
-
requestPatternScore: 80,
|
|
675
|
-
honeypotScore: 0
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
const ip = '127.0.0.1';
|
|
679
|
-
const expiry = Date.now() + 3600000;
|
|
680
|
-
const signature = createHmac("sha256", process.env.POW_SECRET || "fallback-dev-secret-32-chars-minimum").update(`${ip}:${expiry}`).digest("hex");
|
|
681
|
-
const validTicket = `${expiry}:${signature}`;
|
|
682
|
-
|
|
683
|
-
const req = { path: '/', ip, cookies: { pow_clearance: validTicket }, query: {}, headers: { 'user-agent': 'test-ua' } };
|
|
684
|
-
let sentStatus, sentBody;
|
|
685
|
-
const res = {
|
|
686
|
-
status: (s) => { sentStatus = s; return res; },
|
|
687
|
-
send: (b) => { sentBody = b; },
|
|
688
|
-
cookie: vi.fn()
|
|
689
|
-
};
|
|
690
|
-
const next = vi.fn();
|
|
691
|
-
|
|
692
|
-
req.fingerprint = {};
|
|
693
|
-
await powMiddleware(securityConfig)(req, res, next);
|
|
694
|
-
|
|
695
|
-
expect(next).not.toHaveBeenCalled();
|
|
696
|
-
expect(sentStatus).toBe(404);
|
|
697
|
-
expect(sentBody).toContain('Enhanced Verification');
|
|
698
|
-
});
|
|
699
|
-
|
|
700
668
|
test('should redirect after a valid PoW solution is provided', async () => {
|
|
701
669
|
// --- 1. Setup: Define context and mock a suspicious score ---
|
|
702
670
|
const ip = '127.0.0.1';
|
|
@@ -2405,7 +2373,7 @@ describe('Subnet Scoring (Node.js)', () => {
|
|
|
2405
2373
|
deviceIds: ['d1', 'd2'], // count < 10, score += 0
|
|
2406
2374
|
});
|
|
2407
2375
|
({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
|
|
2408
|
-
expect(subnetScore).toBe(
|
|
2376
|
+
expect(subnetScore).toBe(58.6);
|
|
2409
2377
|
|
|
2410
2378
|
// 3. Many devices and high scores
|
|
2411
2379
|
const deviceIds = Array.from({ length: 20 }, (_, i) => `d${i}`);
|
|
@@ -2414,7 +2382,7 @@ describe('Subnet Scoring (Node.js)', () => {
|
|
|
2414
2382
|
deviceIds: deviceIds, // count = 20. score += (20-10)*5 = 50
|
|
2415
2383
|
});
|
|
2416
2384
|
({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
|
|
2417
|
-
expect(subnetScore).toBe(
|
|
2385
|
+
expect(subnetScore).toBe(99.4);
|
|
2418
2386
|
});
|
|
2419
2387
|
});
|
|
2420
2388
|
|
|
@@ -2611,6 +2579,14 @@ describe('Additional Suspicion Vectors Coverage', () => {
|
|
|
2611
2579
|
});
|
|
2612
2580
|
|
|
2613
2581
|
describe('Ed25519 Asymmetric Tickets', () => {
|
|
2582
|
+
const localStore = {
|
|
2583
|
+
_map: new Map(),
|
|
2584
|
+
async get(key) { return this._map.get(key); },
|
|
2585
|
+
async set(key, value) { this._map.set(key, value); },
|
|
2586
|
+
async has(key) { return this._map.has(key); },
|
|
2587
|
+
async delete(key) { this._map.delete(key); },
|
|
2588
|
+
async clear() { this._map.clear(); }
|
|
2589
|
+
};
|
|
2614
2590
|
const { privateKey, publicKey } = generateKeyPairSync('ed25519', {
|
|
2615
2591
|
privateKeyEncoding: { format: 'pem', type: 'pkcs8' },
|
|
2616
2592
|
publicKeyEncoding: { format: 'pem', type: 'spki' }
|
|
@@ -2618,7 +2594,9 @@ describe('Ed25519 Asymmetric Tickets', () => {
|
|
|
2618
2594
|
const privateKeyPem = privateKey;
|
|
2619
2595
|
const publicKeyPem = publicKey;
|
|
2620
2596
|
|
|
2621
|
-
beforeEach(() => {
|
|
2597
|
+
beforeEach(async () => {
|
|
2598
|
+
await localStore.clear();
|
|
2599
|
+
configureStore(localStore);
|
|
2622
2600
|
process.env.ED25519_PRIVATE_KEY = privateKeyPem;
|
|
2623
2601
|
process.env.ED25519_PUBLIC_KEY = publicKeyPem;
|
|
2624
2602
|
});
|
|
@@ -2697,6 +2675,62 @@ describe('Ed25519 Asymmetric Tickets', () => {
|
|
|
2697
2675
|
expect(resPoll.requests[0].req_id).toBe('req-123');
|
|
2698
2676
|
expect(resPoll.requests[0].block_idx).toBe(42);
|
|
2699
2677
|
});
|
|
2678
|
+
|
|
2679
|
+
test('WebRTC P2P Signaling Flow (Node.js)', async () => {
|
|
2680
|
+
const clientIp = '127.0.0.1';
|
|
2681
|
+
const nodeIdA = 'node-a-webrtc';
|
|
2682
|
+
const nodeIdB = 'node-b-webrtc';
|
|
2683
|
+
const clientSecretA = 'secret-a-webrtc';
|
|
2684
|
+
const clientSecretB = 'secret-b-webrtc';
|
|
2685
|
+
|
|
2686
|
+
const store = __internal.store;
|
|
2687
|
+
await store.set(`secret:${nodeIdA}`, { clientSecret: clientSecretA });
|
|
2688
|
+
await store.set(`secret:${nodeIdB}`, { clientSecret: clientSecretB });
|
|
2689
|
+
|
|
2690
|
+
await __internal.registerCooperativeNode(clientIp, nodeIdA, 'seed-a');
|
|
2691
|
+
await __internal.registerCooperativeNode(clientIp, nodeIdB, 'seed-b');
|
|
2692
|
+
|
|
2693
|
+
// 1. Découverte d'un pair dans le sous-réseau
|
|
2694
|
+
const sigFind = createHash('sha256')
|
|
2695
|
+
.update(`${clientSecretA}:find_peer:${nodeIdA}`)
|
|
2696
|
+
.digest('hex');
|
|
2697
|
+
const findRes = await __internal.handleCooperativeRequest({
|
|
2698
|
+
coop_op: 'find_peer',
|
|
2699
|
+
node_id: nodeIdA,
|
|
2700
|
+
coop_sig: sigFind
|
|
2701
|
+
}, clientIp);
|
|
2702
|
+
expect(findRes.status).toBe('peer_found');
|
|
2703
|
+
expect(findRes.peer_id).toBe(nodeIdB);
|
|
2704
|
+
|
|
2705
|
+
// 2. Envoi d'une offre SDP WebRTC (A -> B)
|
|
2706
|
+
const offerData = '{"type":"offer","sdp":"v=0..."}';
|
|
2707
|
+
const sigSignal = createHash('sha256')
|
|
2708
|
+
.update(`${clientSecretA}:webrtc_signal:${nodeIdA}:${nodeIdB}:offer:${offerData}`)
|
|
2709
|
+
.digest('hex');
|
|
2710
|
+
const signalRes = await __internal.handleCooperativeRequest({
|
|
2711
|
+
coop_op: 'webrtc_signal',
|
|
2712
|
+
node_id: nodeIdA,
|
|
2713
|
+
target_peer_id: nodeIdB,
|
|
2714
|
+
signal_type: 'offer',
|
|
2715
|
+
signal_data: offerData,
|
|
2716
|
+
coop_sig: sigSignal
|
|
2717
|
+
}, clientIp);
|
|
2718
|
+
expect(signalRes.status).toBe('signal_queued');
|
|
2719
|
+
|
|
2720
|
+
// 3. Récupération des signaux par le nœud B
|
|
2721
|
+
const sigPoll = createHash('sha256')
|
|
2722
|
+
.update(`${clientSecretB}:poll_signals:${nodeIdB}`)
|
|
2723
|
+
.digest('hex');
|
|
2724
|
+
const pollRes = await __internal.handleCooperativeRequest({
|
|
2725
|
+
coop_op: 'poll_signals',
|
|
2726
|
+
node_id: nodeIdB,
|
|
2727
|
+
coop_sig: sigPoll
|
|
2728
|
+
}, clientIp);
|
|
2729
|
+
expect(pollRes.status).toBe('ok');
|
|
2730
|
+
expect(pollRes.signals.length).toBe(1);
|
|
2731
|
+
expect(pollRes.signals[0].from_peer_id).toBe(nodeIdA);
|
|
2732
|
+
expect(pollRes.signals[0].signal_type).toBe('offer');
|
|
2733
|
+
});
|
|
2700
2734
|
});
|
|
2701
2735
|
|
|
2702
2736
|
describe('Botnet Cluster Scoring (Node.js)', () => {
|