@anonympins/fingerprint 0.5.2 → 0.5.5
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 +27 -0
- package/package.json +1 -1
- package/src/js/build-client.js +67 -67
- package/src/js/fingerprint.client.js +280 -38
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +474 -163
- package/src/js/fingerprint.utils.js +213 -213
- package/src/js/library.js +24 -8
- package/src/js/mongodb-store.js +165 -79
- package/src/js/obfuscation.worker.js +23 -0
- package/src/js/pow.solver.inline.js +509 -14
- package/src/js/pow.solver.js +350 -32
- package/src/js/problem-manager.js +1 -1
- package/src/js/redis-store.js +114 -42
- package/src/js/tests/cross_parity.test.js +175 -0
- package/src/js/tests/dns-circuit-breaker.test.js +55 -0
- package/src/js/tests/fingerprint.client.init.test.js +143 -143
- package/src/js/tests/fingerprint.client.test.js +128 -128
- package/src/js/tests/fingerprint.test.js +57 -2
- package/src/js/tests/ip-reputation.test.js +1 -1
- package/src/js/tests/obfuscation.test.js +48 -0
- package/src/js/tests/pow.solver.test.js +5 -2
- package/src/js/tests/usefulWorkSolver.test.js +162 -0
- package/src/php/AutoTuner.php +42 -0
- package/src/php/Challenge/ChallengeUtils.php +21 -8
- package/src/php/FingerprintClient.php +22 -5
- package/src/php/FingerprintEngine.php +233 -57
- package/src/php/Optimization/Optimization.php +23 -16
- package/src/php/Optimization/OptimizationOperators.php +27 -0
- package/src/php/ProblemManager.php +1 -1
- package/src/php/Store/MongoDbStore.php +184 -104
- package/src/php/Store/RedisStore.php +91 -11
- package/src/php/Tests/ChallengeUtilsTest.php +285 -285
- package/src/php/Tests/DnsCircuitBreakerTest.php +106 -0
- package/src/php/Tests/IpReputationTest.php +1 -1
- package/src/php/Tests/PowTest.php +1 -1
- package/src/php/Tests/RequestUtilsTest.php +29 -0
- package/src/php/Tests/TLSClientHelloParserTest.php +177 -117
- package/src/php/Utils/BigInt.php +202 -202
- package/src/php/Utils/RequestUtils.php +74 -21
- package/src/php/Utils/TLSClientHelloParser.php +212 -116
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
## Version 0.5.5
|
|
2
|
+
|
|
3
|
+
- **🔒 Client Session Handshake & HMAC Metric Signing**: Implemented elliptic-curve Diffie-Hellman (ECDH over curve `P-256`) key exchange protocol during client initialization (`?fp_handshake=1`). Leveraged the negotiated session key to cryptographically sign behavioral metrics (`signMetrics`) on the client using HMAC-SHA256, protecting against replay attacks and telemetry tampering.
|
|
4
|
+
- **🧵 Non-Blocking Polymorphic Obfuscation Worker**: Offloaded CPU-heavy JavaScript obfuscation and parsing pipelines (`compilePolymorphicJs`) to a dedicated Node.js Worker Thread (`obfuscation.worker.js`). This isolates resource-intensive obfuscation tasks from the main event loop, preventing latency spikes on production servers.
|
|
5
|
+
- **💾 Performance Optimizations with SimpleLRUCache**: Added an in-memory `SimpleLRUCache` to eliminate synchronous file system calls (`readFileSync`). Standardized caching of custom HTML challenge pages and static files (`fp.js`/`fp.wasm`).
|
|
6
|
+
- **🛡️ Auto-Tuner Stability Guardrails & Rollbacks**: Implemented post-calculation validation checks with a configurable `validationTolerance` threshold (default `0.15`). Automatically rolls back unstable parameters and triggers an `autotuning_instability_alert` if proposed changes degrade FPR/FNR metrics.
|
|
7
|
+
- **🧬 Strict Multi-Language Cross-Parity**: Aligned 32-bit single-precision floating-point (IEEE 754) calculations for chaotic map trajectories across WebGPU, WebGL, C++ WASM, and PHP environments. Guaranteed bit-to-bit convergence across 1,000 random seeds.
|
|
8
|
+
- **🧮 Fixed Zero Threshold PoW Shift Error**: Patched a potential bitwise shift crash (`1n << shift` with a negative shift) inside `calculateTarget` when the suspicion score dropped to or below zero. Legitimate requests now resolve directly to the maximum target range.
|
|
9
|
+
|
|
10
|
+
## Version 0.5.4
|
|
11
|
+
|
|
12
|
+
- **🔒 JA3/JA4 Handshake Parsing Hardening (PHP/Python)**: Fixed ALPN length constraints and untangled nested loops in the binary TLS Client Hello decoder, ensuring native and highly reliable raw JA4 fingerprint calculations directly at the socket layer.
|
|
13
|
+
- **⚡ Dynamic WebAssembly (WASM) & Memory Safety**: Secured heap memory allocation and the dynamic loading of the polymorphic WASM module. Added comprehensive client-side unit tests to validate safe execution and clean memory deallocation.
|
|
14
|
+
- **🛠️ Useful Proof-of-Work (uPoW) & Integration Tests**: Finalized the end-to-end integration and orchestration of uPoW tasks, backed by robust validation tests for multi-objective optimization models (specifically CPC and fraud detection).
|
|
15
|
+
- **🛡️ Store Resilience & CHIPS Cookie Support**: Enhanced fault tolerance for Redis and MongoDB database adapters with seamless, automatic fallback to local in-memory storage during network drops. Standardized CHIPS (`Partitioned`) cookie attributes across secure HTTPS environments.
|
|
16
|
+
- **📁 Deterministic Path Resolution Fixes**: Implemented robust absolute and relative path resolution for loading configuration files (such as `problems.config.json`) uniformly across Node.js, PHP, and Python environments.
|
|
17
|
+
|
|
18
|
+
## Version 0.5.3
|
|
19
|
+
|
|
20
|
+
- **DNS Circuit Breaker with Strict Timeouts**: Protected reverse/forward DNS verification of legitimate crawlers against DNS server degradation or DDoS exhaustion. Integrated a stateful Circuit Breaker (`CLOSED`, `OPEN`, `HALF-OPEN`) with a strict **500 ms timeout** and **5-minute temporary negative caching** on resolution failures.
|
|
21
|
+
- **Anti-Poisoning via Hardware Clustering**: Hardened `sanitizeTrafficData` to cluster traffic logs by stable hardware signatures (Canvas + GPU + CPU). Contributions from any single hardware footprint are strictly capped to **2% of the dataset**, neutralizing massive proxy-rotation Sybil attacks designed to manipulate tuning thresholds.
|
|
22
|
+
- **Memory-Safe Traffic Pruning**: Standardized `pruneTrafficData` across Node.js and PHP engines. Includes strict time-based decay and size-capping policy, keeping the in-memory footprint minimal and preventing leaks during long-running background tuning sessions.
|
|
23
|
+
- **Enhanced Subnet Detection**: Upgraded IPv4 and IPv6 subnet masking routines (`getIpSubnet`). Implemented proper binary bitwise masking and IPv6 compression normalization (`::` expansion), ensuring high-reliability network block reputation scoring (`subnetScore`).
|
|
24
|
+
- **Centralized Problem Configuration**: Relocated `problems.config.json` to the central `/config` directory for unified configuration management and cleaner multi-environment deployments.
|
|
25
|
+
- **Stealthy Honeypot Enhancements**: Hardened client-side honeypots by dynamically rendering hidden input elements inside a closed Shadow DOM using nested randomized CSS variables, effectively evading detection by headless crawlers.
|
|
26
|
+
- **Mathematical Benford Test Optimization**: Re-engineered `benfordTest` across JavaScript, PHP, and Python to extract the first significant digit using math operations (logarithms and modulo) instead of string casts, significantly boosting performance on large time-series.
|
|
27
|
+
|
|
1
28
|
## Version 0.5.2
|
|
2
29
|
|
|
3
30
|
- Security updates (CodeQL issues fixed)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anonympins/fingerprint",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Advanced anti-bot library for Node.js using multi-layer fingerprinting (JA3, client-side, headers), behavioral analysis, and adaptive Proof-of-Work (PoW) challenges to mitigate scraping, scalping, and automated threats.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
package/src/js/build-client.js
CHANGED
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
import {promises as fs} from 'node:fs';
|
|
2
|
-
import {dirname, join} from 'node:path';
|
|
3
|
-
import {fileURLToPath} from 'node:url';
|
|
4
|
-
import {exec} from 'node:child_process';
|
|
5
|
-
import JavaScriptObfuscator from 'javascript-obfuscator';
|
|
6
|
-
|
|
7
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
-
const __dirname = dirname(__filename);
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Tente d'exécuter la commande de build pour le module WASM.
|
|
12
|
-
* Ne bloque pas le build si la commande échoue (ex: em++ non trouvé).
|
|
13
|
-
*/
|
|
14
|
-
function buildWasm() {
|
|
15
|
-
return new Promise((resolve) => {
|
|
16
|
-
console.log('Attempting to build WASM module (optional)...');
|
|
17
|
-
const command = "em++ src/cpp/main.cpp src/cpp/utils.cpp -o public/fp.js -s WASM=1 -s MODULARIZE=1 -s EXPORT_NAME='createFingerprintModule' -s \"EXPORTED_FUNCTIONS=['_hash_string','_solve_cpu_target','_solve_memory_challenge']\" -O3 --no-entry";
|
|
18
|
-
|
|
19
|
-
exec(command, (error, stdout, stderr) => {
|
|
20
|
-
if (error) {
|
|
21
|
-
console.warn('WASM build failed (this is optional and can be ignored):', stderr);
|
|
22
|
-
} else {
|
|
23
|
-
console.log('WASM module built successfully.');
|
|
24
|
-
}
|
|
25
|
-
resolve(); // Toujours résoudre la promesse pour ne pas bloquer le build principal.
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function buildClientScript() {
|
|
31
|
-
try {
|
|
32
|
-
console.log('Reading client script...');
|
|
33
|
-
const clientScriptPath = join(__dirname, 'fingerprint.client.js');
|
|
34
|
-
const clientScriptContent = await fs.readFile(clientScriptPath, 'utf-8');
|
|
35
|
-
|
|
36
|
-
console.log('Obfuscating client script...');
|
|
37
|
-
const obfuscationResult = JavaScriptObfuscator.obfuscate(clientScriptContent, {
|
|
38
|
-
compact: true,
|
|
39
|
-
controlFlowFlattening: true, // Aplatit le flux de contrôle
|
|
40
|
-
deadCodeInjection: true, // Injecte du code mort
|
|
41
|
-
stringArray: true,
|
|
42
|
-
stringArrayRotate: true, // Fait tourner le tableau de chaînes
|
|
43
|
-
stringArrayShuffle: true, // Mélange le tableau de chaînes
|
|
44
|
-
// L'utilisation d'une graine (seed) de 0 signifie que l'obfuscation sera déterministe
|
|
45
|
-
// pour une même entrée. Pour une obfuscation unique à chaque build, on peut utiliser
|
|
46
|
-
// une graine aléatoire, par exemple : seed: Math.random()
|
|
47
|
-
seed: Math.random(),
|
|
48
|
-
selfDefending: true,
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const obfuscatedCode = obfuscationResult.getObfuscatedCode();
|
|
52
|
-
|
|
53
|
-
console.log('Writing obfuscated script to fingerprint.client.obfuscated.js...');
|
|
54
|
-
await fs.writeFile(join(__dirname, './fingerprint.client.obfuscated.js'), obfuscatedCode);
|
|
55
|
-
|
|
56
|
-
console.log('Client script build process completed successfully.');
|
|
57
|
-
} catch (error) {
|
|
58
|
-
console.error('Error during client script build:', error);
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function main() {
|
|
64
|
-
await buildClientScript();
|
|
65
|
-
await buildWasm();
|
|
66
|
-
}
|
|
67
|
-
|
|
1
|
+
import {promises as fs} from 'node:fs';
|
|
2
|
+
import {dirname, join} from 'node:path';
|
|
3
|
+
import {fileURLToPath} from 'node:url';
|
|
4
|
+
import {exec} from 'node:child_process';
|
|
5
|
+
import JavaScriptObfuscator from 'javascript-obfuscator';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tente d'exécuter la commande de build pour le module WASM.
|
|
12
|
+
* Ne bloque pas le build si la commande échoue (ex: em++ non trouvé).
|
|
13
|
+
*/
|
|
14
|
+
function buildWasm() {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
console.log('Attempting to build WASM module (optional)...');
|
|
17
|
+
const command = "em++ src/cpp/main.cpp src/cpp/utils.cpp -o public/fp.js -s WASM=1 -s MODULARIZE=1 -s EXPORT_NAME='createFingerprintModule' -s \"EXPORTED_FUNCTIONS=['_hash_string','_solve_cpu_target','_solve_memory_challenge','_generate_gpu_pow_trajectory']\" -O3 --no-entry";
|
|
18
|
+
|
|
19
|
+
exec(command, (error, stdout, stderr) => {
|
|
20
|
+
if (error) {
|
|
21
|
+
console.warn('WASM build failed (this is optional and can be ignored):', stderr);
|
|
22
|
+
} else {
|
|
23
|
+
console.log('WASM module built successfully.');
|
|
24
|
+
}
|
|
25
|
+
resolve(); // Toujours résoudre la promesse pour ne pas bloquer le build principal.
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function buildClientScript() {
|
|
31
|
+
try {
|
|
32
|
+
console.log('Reading client script...');
|
|
33
|
+
const clientScriptPath = join(__dirname, 'fingerprint.client.js');
|
|
34
|
+
const clientScriptContent = await fs.readFile(clientScriptPath, 'utf-8');
|
|
35
|
+
|
|
36
|
+
console.log('Obfuscating client script...');
|
|
37
|
+
const obfuscationResult = JavaScriptObfuscator.obfuscate(clientScriptContent, {
|
|
38
|
+
compact: true,
|
|
39
|
+
controlFlowFlattening: true, // Aplatit le flux de contrôle
|
|
40
|
+
deadCodeInjection: true, // Injecte du code mort
|
|
41
|
+
stringArray: true,
|
|
42
|
+
stringArrayRotate: true, // Fait tourner le tableau de chaînes
|
|
43
|
+
stringArrayShuffle: true, // Mélange le tableau de chaînes
|
|
44
|
+
// L'utilisation d'une graine (seed) de 0 signifie que l'obfuscation sera déterministe
|
|
45
|
+
// pour une même entrée. Pour une obfuscation unique à chaque build, on peut utiliser
|
|
46
|
+
// une graine aléatoire, par exemple : seed: Math.random()
|
|
47
|
+
seed: Math.random(),
|
|
48
|
+
selfDefending: true,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const obfuscatedCode = obfuscationResult.getObfuscatedCode();
|
|
52
|
+
|
|
53
|
+
console.log('Writing obfuscated script to fingerprint.client.obfuscated.js...');
|
|
54
|
+
await fs.writeFile(join(__dirname, './fingerprint.client.obfuscated.js'), obfuscatedCode);
|
|
55
|
+
|
|
56
|
+
console.log('Client script build process completed successfully.');
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('Error during client script build:', error);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function main() {
|
|
64
|
+
await buildClientScript();
|
|
65
|
+
await buildWasm();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
68
|
main();
|
|
@@ -1,14 +1,96 @@
|
|
|
1
1
|
import {cyrb53 as jsCyrb53, FingerprintBuilder} from './fingerprint.builder.js';
|
|
2
2
|
import {solveChallenge} from './pow.solver.js';
|
|
3
3
|
|
|
4
|
+
function secureRandom() {
|
|
5
|
+
if (typeof window !== 'undefined' && (window.crypto || window.msCrypto)) {
|
|
6
|
+
const array = new Uint32Array(1);
|
|
7
|
+
(window.crypto || window.msCrypto).getRandomValues(array);
|
|
8
|
+
return array[0] / 0x100000000;
|
|
9
|
+
}
|
|
10
|
+
return Math.random();
|
|
11
|
+
}
|
|
12
|
+
|
|
4
13
|
// Variable pour stocker la fonction de hachage active.
|
|
5
14
|
// Par défaut, c'est l'implémentation JavaScript.
|
|
6
15
|
let activeCyrb53 = jsCyrb53;
|
|
16
|
+
let derivedKey = null;
|
|
17
|
+
|
|
18
|
+
async function negotiateSessionKey() {
|
|
19
|
+
if (typeof window === 'undefined' || !window.crypto || !window.crypto.subtle) return;
|
|
20
|
+
try {
|
|
21
|
+
const keyPair = await window.crypto.subtle.generateKey(
|
|
22
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
23
|
+
false,
|
|
24
|
+
["deriveKey"]
|
|
25
|
+
);
|
|
26
|
+
const clientPubKeyBuffer = await window.crypto.subtle.exportKey("raw", keyPair.publicKey);
|
|
27
|
+
const clientPubKeyHex = Array.from(new Uint8Array(clientPubKeyBuffer))
|
|
28
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
29
|
+
.join('');
|
|
30
|
+
|
|
31
|
+
const response = await fetch(window.location.pathname + '?fp_handshake=1', {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
'x-client-ephemeral-key': clientPubKeyHex
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({ fp: ClientLibrary.getDeviceFingerprint() })
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const serverPubKeyHex = response.headers.get('x-server-ephemeral-key');
|
|
41
|
+
if (serverPubKeyHex) {
|
|
42
|
+
const serverPubKeyBuffer = new Uint8Array(
|
|
43
|
+
serverPubKeyHex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))
|
|
44
|
+
);
|
|
45
|
+
const serverPubKey = await window.crypto.subtle.importKey(
|
|
46
|
+
"raw",
|
|
47
|
+
serverPubKeyBuffer,
|
|
48
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
49
|
+
true,
|
|
50
|
+
[]
|
|
51
|
+
);
|
|
52
|
+
derivedKey = await window.crypto.subtle.deriveKey(
|
|
53
|
+
{ name: "ECDH", public: serverPubKey },
|
|
54
|
+
keyPair.privateKey,
|
|
55
|
+
{ name: "HMAC", hash: "SHA-256", length: 256 },
|
|
56
|
+
true,
|
|
57
|
+
["sign"]
|
|
58
|
+
);
|
|
59
|
+
console.log('[Fingerprint] Cryptographic session negotiated successfully.');
|
|
60
|
+
}
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.warn('[Fingerprint] Cryptographic session negotiation failed:', e);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function signMetrics(metricsObj) {
|
|
67
|
+
if (!derivedKey) return metricsObj;
|
|
68
|
+
try {
|
|
69
|
+
const copy = JSON.parse(JSON.stringify(metricsObj));
|
|
70
|
+
delete copy.signature;
|
|
71
|
+
const dataToSign = new TextEncoder().encode(JSON.stringify(copy));
|
|
72
|
+
const signatureBuffer = await window.crypto.subtle.sign(
|
|
73
|
+
"HMAC",
|
|
74
|
+
derivedKey,
|
|
75
|
+
dataToSign
|
|
76
|
+
);
|
|
77
|
+
copy.signature = Array.from(new Uint8Array(signatureBuffer))
|
|
78
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
79
|
+
.join('');
|
|
80
|
+
return copy;
|
|
81
|
+
} catch (e) {
|
|
82
|
+
return metricsObj;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
7
85
|
|
|
8
86
|
const DB_NAME = 'wasm-cache-db';
|
|
9
87
|
const DB_VERSION = 1;
|
|
10
88
|
const STORE_NAME = 'wasm-modules';
|
|
11
89
|
|
|
90
|
+
const genRandStr = (len = 8) => {
|
|
91
|
+
return Array.from({ length: len }, () => String.fromCharCode(97 + Math.floor(secureRandom() * 26))).join('');
|
|
92
|
+
};
|
|
93
|
+
|
|
12
94
|
function getCachedWasm(url) {
|
|
13
95
|
return new Promise((resolve) => {
|
|
14
96
|
if (typeof indexedDB === 'undefined') return resolve(null);
|
|
@@ -282,8 +364,10 @@ const ClientLibrary = {
|
|
|
282
364
|
// Création d'un élément interactif fantôme
|
|
283
365
|
const phantom = document.createElement('a');
|
|
284
366
|
phantom.href = '#';
|
|
285
|
-
// Nom trompeur pour attirer les analyseurs automatiques de liens / formulaires
|
|
286
|
-
|
|
367
|
+
// Nom trompeur aléatoire pour attirer les analyseurs automatiques de liens / formulaires
|
|
368
|
+
const phantomNames = ['sys-session-recovery', 'auth-token-refresh', 'debug-console-login', 'admin-portal-access', 'security-bypass-bypass', 'recovery-key-session', 'api-key-test', 'client-secrets-access'];
|
|
369
|
+
phantom.id = phantomNames[Math.floor(secureRandom() * phantomNames.length)] + '-' + genRandStr(6);
|
|
370
|
+
phantom.className = genRandStr(8);
|
|
287
371
|
phantom.tabIndex = 0; // Dans le flux naturel de tabulation
|
|
288
372
|
phantom.setAttribute('aria-hidden', 'true'); // Masqué pour les screen readers légitimes
|
|
289
373
|
|
|
@@ -305,7 +389,21 @@ const ClientLibrary = {
|
|
|
305
389
|
phantom.addEventListener('focus', triggerTrap, { passive: true });
|
|
306
390
|
phantom.addEventListener('mouseover', triggerTrap, { passive: true });
|
|
307
391
|
|
|
308
|
-
document
|
|
392
|
+
// Imbrication polymorphique du piège fantôme dans le corps du document
|
|
393
|
+
const nestingOptions = [
|
|
394
|
+
() => document.body.appendChild(phantom),
|
|
395
|
+
() => {
|
|
396
|
+
const wrapper = document.createElement(secureRandom() > 0.5 ? 'span' : 'div');
|
|
397
|
+
wrapper.className = genRandStr(8);
|
|
398
|
+
wrapper.style.position = 'absolute';
|
|
399
|
+
wrapper.style.width = '0';
|
|
400
|
+
wrapper.style.height = '0';
|
|
401
|
+
wrapper.style.overflow = 'hidden';
|
|
402
|
+
wrapper.appendChild(phantom);
|
|
403
|
+
document.body.appendChild(wrapper);
|
|
404
|
+
}
|
|
405
|
+
];
|
|
406
|
+
nestingOptions[Math.floor(secureRandom() * nestingOptions.length)]();
|
|
309
407
|
},
|
|
310
408
|
|
|
311
409
|
/**
|
|
@@ -631,26 +729,35 @@ const ClientLibrary = {
|
|
|
631
729
|
|
|
632
730
|
const shadow = host.attachShadow({ mode: 'closed' });
|
|
633
731
|
|
|
732
|
+
// Aléatisation des classes, variables CSS et balises de structure
|
|
733
|
+
const wrapperClass = genRandStr(10);
|
|
734
|
+
const posStateVar = `--${genRandStr(8)}`;
|
|
735
|
+
const offValVar = `--${genRandStr(8)}`;
|
|
736
|
+
const visStateVar = `--${genRandStr(8)}`;
|
|
737
|
+
const scaleValVar = `--${genRandStr(8)}`;
|
|
738
|
+
|
|
634
739
|
const style = document.createElement('style');
|
|
635
740
|
style.textContent = `
|
|
636
741
|
:host {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
742
|
+
${posStateVar}: absolute;
|
|
743
|
+
${offValVar}: -9999px;
|
|
744
|
+
${visStateVar}: hidden;
|
|
745
|
+
${scaleValVar}: 0;
|
|
641
746
|
}
|
|
642
|
-
|
|
643
|
-
position: var(
|
|
644
|
-
left: var(
|
|
645
|
-
top: var(
|
|
646
|
-
visibility: var(
|
|
647
|
-
transform: scale(var(
|
|
747
|
+
.${wrapperClass} {
|
|
748
|
+
position: var(${posStateVar});
|
|
749
|
+
left: var(${offValVar});
|
|
750
|
+
top: var(${offValVar});
|
|
751
|
+
visibility: var(${visStateVar});
|
|
752
|
+
transform: scale(var(${scaleValVar}));
|
|
648
753
|
}
|
|
649
754
|
`;
|
|
650
755
|
shadow.appendChild(style);
|
|
651
756
|
|
|
652
|
-
const
|
|
653
|
-
|
|
757
|
+
const wrapperTags = ['div', 'section', 'p', 'span', 'form', 'main'];
|
|
758
|
+
const selectedWrapperTag = wrapperTags[Math.floor(secureRandom() * wrapperTags.length)];
|
|
759
|
+
const wrapper = document.createElement(selectedWrapperTag);
|
|
760
|
+
wrapper.className = wrapperClass;
|
|
654
761
|
|
|
655
762
|
honeypotFieldNames.forEach(fieldName => {
|
|
656
763
|
const label = document.createElement('label');
|
|
@@ -659,6 +766,8 @@ const ClientLibrary = {
|
|
|
659
766
|
input.type = 'text';
|
|
660
767
|
input.name = fieldName;
|
|
661
768
|
input.tabIndex = -1;
|
|
769
|
+
input.className = genRandStr(6);
|
|
770
|
+
input.id = genRandStr(8);
|
|
662
771
|
input.autocomplete = 'off';
|
|
663
772
|
|
|
664
773
|
const trigger = () => {
|
|
@@ -669,14 +778,108 @@ const ClientLibrary = {
|
|
|
669
778
|
input.addEventListener('change', trigger, { passive: true });
|
|
670
779
|
input.addEventListener('focus', trigger, { passive: true });
|
|
671
780
|
|
|
672
|
-
|
|
673
|
-
|
|
781
|
+
// Imbrication polymorphique du label et de l'input
|
|
782
|
+
const nestingType = Math.floor(secureRandom() * 3);
|
|
783
|
+
if (nestingType === 1) {
|
|
784
|
+
label.appendChild(input);
|
|
785
|
+
wrapper.appendChild(label);
|
|
786
|
+
} else if (nestingType === 2) {
|
|
787
|
+
const innerContainer = document.createElement(secureRandom() > 0.5 ? 'span' : 'div');
|
|
788
|
+
innerContainer.className = genRandStr(5);
|
|
789
|
+
innerContainer.appendChild(label);
|
|
790
|
+
innerContainer.appendChild(input);
|
|
791
|
+
wrapper.appendChild(innerContainer);
|
|
792
|
+
} else {
|
|
793
|
+
wrapper.appendChild(label);
|
|
794
|
+
wrapper.appendChild(input);
|
|
795
|
+
}
|
|
674
796
|
});
|
|
675
797
|
|
|
676
798
|
shadow.appendChild(wrapper);
|
|
677
799
|
document.body.appendChild(host);
|
|
678
800
|
}
|
|
679
801
|
},
|
|
802
|
+
/**
|
|
803
|
+
* Sollicite silencieusement l'attestation matérielle FIDO2 (WebAuthn)
|
|
804
|
+
* pour ancrer l'identité physique de l'appareil via TPM / Secure Enclave.
|
|
805
|
+
* @returns {Promise<object|null>}
|
|
806
|
+
*/
|
|
807
|
+
async getWebAuthnAnchor() {
|
|
808
|
+
if (typeof window === 'undefined' || !window.PublicKeyCredential) return null;
|
|
809
|
+
try {
|
|
810
|
+
const isPlatformAvailable = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
|
|
811
|
+
if (!isPlatformAvailable) return null;
|
|
812
|
+
|
|
813
|
+
const storedCredIdBase64 = localStorage.getItem('fp_webauthn_cred_id');
|
|
814
|
+
const challenge = new Uint8Array([109, 101, 116, 114, 105, 99, 115, 95, 97, 110, 99, 104, 111, 114, 95, 115, 101, 99, 117, 114, 101]); // Static stable challenge
|
|
815
|
+
|
|
816
|
+
if (storedCredIdBase64) {
|
|
817
|
+
// Tentative d'assertion silencieuse (Authentification)
|
|
818
|
+
const credentialId = Uint8Array.from(atob(storedCredIdBase64), c => c.charCodeAt(0));
|
|
819
|
+
const assertion = await navigator.credentials.get({
|
|
820
|
+
publicKey: {
|
|
821
|
+
challenge,
|
|
822
|
+
allowCredentials: [{
|
|
823
|
+
id: credentialId,
|
|
824
|
+
type: 'public-key'
|
|
825
|
+
}],
|
|
826
|
+
userVerification: 'discouraged',
|
|
827
|
+
timeout: 1000
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
if (assertion) {
|
|
831
|
+
return {
|
|
832
|
+
type: 'assertion',
|
|
833
|
+
credentialId: storedCredIdBase64,
|
|
834
|
+
signature: btoa(String.fromCharCode(...new Uint8Array(assertion.response.signature))),
|
|
835
|
+
authenticatorData: btoa(String.fromCharCode(...new Uint8Array(assertion.response.authenticatorData))),
|
|
836
|
+
clientDataJSON: btoa(String.fromCharCode(...new Uint8Array(assertion.response.clientDataJSON)))
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
} else {
|
|
840
|
+
// Création silencieuse (Enregistrement initial)
|
|
841
|
+
const options = {
|
|
842
|
+
publicKey: {
|
|
843
|
+
challenge,
|
|
844
|
+
rp: { name: window.location.hostname, id: window.location.hostname },
|
|
845
|
+
user: {
|
|
846
|
+
id: new Uint8Array([102, 112, 105, 100]),
|
|
847
|
+
name: 'silent-device-anchor',
|
|
848
|
+
displayName: 'Hardware Device Anchor'
|
|
849
|
+
},
|
|
850
|
+
pubKeyCredParams: [
|
|
851
|
+
{ type: 'public-key', alg: -7 }, // ES256 (P-256) - Secure Enclave / TPM / Android Keystore
|
|
852
|
+
{ type: 'public-key', alg: -257 } // RS256 - Windows Hello TPM
|
|
853
|
+
],
|
|
854
|
+
authenticatorSelection: {
|
|
855
|
+
authenticatorAttachment: 'platform',
|
|
856
|
+
userVerification: 'discouraged',
|
|
857
|
+
residentKey: 'preferred'
|
|
858
|
+
},
|
|
859
|
+
timeout: 1500,
|
|
860
|
+
attestation: 'indirect'
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
const credential = await navigator.credentials.create(options);
|
|
864
|
+
if (credential) {
|
|
865
|
+
const credIdBase64 = btoa(String.fromCharCode(...new Uint8Array(credential.rawId)));
|
|
866
|
+
localStorage.setItem('fp_webauthn_cred_id', credIdBase64);
|
|
867
|
+
const publicKeyDer = credential.response.getPublicKey ? credential.response.getPublicKey() : null;
|
|
868
|
+
return {
|
|
869
|
+
type: 'registration',
|
|
870
|
+
credentialId: credIdBase64,
|
|
871
|
+
publicKey: publicKeyDer ? btoa(String.fromCharCode(...new Uint8Array(publicKeyDer))) : null,
|
|
872
|
+
attestationObject: btoa(String.fromCharCode(...new Uint8Array(credential.response.getAttestationObject()))),
|
|
873
|
+
clientDataJSON: btoa(String.fromCharCode(...new Uint8Array(credential.response.clientDataJSON)))
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
} catch (e) {
|
|
878
|
+
// Ignore et bypass silencieusement pour garder l'invisibilité en cas de non-support
|
|
879
|
+
console.log('[WebAuthn-Anchor] Silent attestation bypassed:', e.message);
|
|
880
|
+
}
|
|
881
|
+
return null;
|
|
882
|
+
},
|
|
680
883
|
|
|
681
884
|
/**
|
|
682
885
|
* Récupère les métriques comportementales collectées.
|
|
@@ -717,7 +920,8 @@ const ClientLibrary = {
|
|
|
717
920
|
*/
|
|
718
921
|
async protectedFetch(resource, options = {}) {
|
|
719
922
|
const fp = this.getDeviceFingerprint();
|
|
720
|
-
const
|
|
923
|
+
const rawBehavior = this.getClientBehaviorMetrics();
|
|
924
|
+
const behavior = await signMetrics(rawBehavior);
|
|
721
925
|
|
|
722
926
|
const headers = new Headers(options.headers || {});
|
|
723
927
|
headers.set('X-Device-Fingerprint', fp);
|
|
@@ -784,7 +988,7 @@ const ClientLibrary = {
|
|
|
784
988
|
* Si non fourni, protège les requêtes de même origine.
|
|
785
989
|
*/
|
|
786
990
|
initializeFetch(targetDomains = []) {
|
|
787
|
-
const fingerprintInterceptor = (resource, options, next) => {
|
|
991
|
+
const fingerprintInterceptor = async (resource, options, next) => {
|
|
788
992
|
const requestUrl = (resource instanceof Request) ? resource.url : String(resource);
|
|
789
993
|
let shouldProtect = false;
|
|
790
994
|
|
|
@@ -803,7 +1007,8 @@ const ClientLibrary = {
|
|
|
803
1007
|
|
|
804
1008
|
if (shouldProtect) {
|
|
805
1009
|
const fp = this.getDeviceFingerprint();
|
|
806
|
-
const
|
|
1010
|
+
const rawBehavior = this.getClientBehaviorMetrics();
|
|
1011
|
+
const behavior = await signMetrics(rawBehavior);
|
|
807
1012
|
const headers = new Headers(options.headers || {});
|
|
808
1013
|
headers.set('X-Device-Fingerprint', fp);
|
|
809
1014
|
headers.set('X-Behavior-Metrics', JSON.stringify(behavior));
|
|
@@ -836,39 +1041,67 @@ const ClientLibrary = {
|
|
|
836
1041
|
|
|
837
1042
|
const shadow = host.attachShadow({ mode: 'closed' });
|
|
838
1043
|
|
|
1044
|
+
// Aléatisation des classes, variables CSS et balises de structure
|
|
1045
|
+
const wrapperClass = genRandStr(10);
|
|
1046
|
+
const layoutPosVar = `--${genRandStr(8)}`;
|
|
1047
|
+
const offsetValVar = `--${genRandStr(8)}`;
|
|
1048
|
+
const visibilityStateVar = `--${genRandStr(8)}`;
|
|
1049
|
+
const scaleFactorVar = `--${genRandStr(8)}`;
|
|
1050
|
+
const ptrEventsVar = `--${genRandStr(8)}`;
|
|
1051
|
+
const linkColorClass = genRandStr(6);
|
|
1052
|
+
|
|
839
1053
|
const style = document.createElement('style');
|
|
840
1054
|
style.textContent = `
|
|
841
1055
|
:host {
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1056
|
+
${layoutPosVar}: absolute;
|
|
1057
|
+
${offsetValVar}: -9999px;
|
|
1058
|
+
${visibilityStateVar}: hidden;
|
|
1059
|
+
${scaleFactorVar}: 0;
|
|
1060
|
+
${ptrEventsVar}: none;
|
|
847
1061
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1062
|
+
.${wrapperClass} {
|
|
1063
|
+
position: var(${layoutPosVar});
|
|
1064
|
+
left: var(${offsetValVar});
|
|
1065
|
+
top: var(${offsetValVar});
|
|
1066
|
+
visibility: var(${visibilityStateVar});
|
|
1067
|
+
transform: scale(var(${scaleFactorVar}));
|
|
1068
|
+
pointer-events: var(${ptrEventsVar});
|
|
855
1069
|
}
|
|
856
|
-
|
|
1070
|
+
.${linkColorClass} {
|
|
857
1071
|
color: transparent;
|
|
858
1072
|
text-decoration: none;
|
|
859
1073
|
}
|
|
860
1074
|
`;
|
|
861
1075
|
shadow.appendChild(style);
|
|
862
1076
|
|
|
863
|
-
|
|
864
|
-
|
|
1077
|
+
const wrapperTags = ['div', 'section', 'p', 'span', 'nav', 'aside'];
|
|
1078
|
+
const selectedWrapperTag = wrapperTags[Math.floor(secureRandom() * wrapperTags.length)];
|
|
1079
|
+
const wrapper = document.createElement(selectedWrapperTag);
|
|
1080
|
+
wrapper.className = wrapperClass;
|
|
865
1081
|
|
|
866
1082
|
urls.forEach((url, i) => {
|
|
867
1083
|
const link = document.createElement('a');
|
|
868
1084
|
link.href = url;
|
|
869
1085
|
link.rel = 'nofollow';
|
|
870
1086
|
link.tabIndex = -1;
|
|
871
|
-
|
|
1087
|
+
link.className = linkColorClass;
|
|
1088
|
+
link.id = genRandStr(8);
|
|
1089
|
+
|
|
1090
|
+
// Structure interne polymorphique du lien
|
|
1091
|
+
const contentNestingType = Math.floor(secureRandom() * 3);
|
|
1092
|
+
if (contentNestingType === 1) {
|
|
1093
|
+
const span = document.createElement('span');
|
|
1094
|
+
span.className = genRandStr(5);
|
|
1095
|
+
span.innerHTML = `> ${i + 1}`;
|
|
1096
|
+
link.appendChild(span);
|
|
1097
|
+
} else if (contentNestingType === 2) {
|
|
1098
|
+
const b = document.createElement('b');
|
|
1099
|
+
b.className = genRandStr(5);
|
|
1100
|
+
b.innerHTML = `> ${i + 1}`;
|
|
1101
|
+
link.appendChild(b);
|
|
1102
|
+
} else {
|
|
1103
|
+
link.innerHTML = `<span>> ${i + 1}</span>`;
|
|
1104
|
+
}
|
|
872
1105
|
|
|
873
1106
|
const trigger = () => {
|
|
874
1107
|
this.onHoneypotTrigger();
|
|
@@ -877,7 +1110,15 @@ const ClientLibrary = {
|
|
|
877
1110
|
link.addEventListener('focus', trigger, { passive: true });
|
|
878
1111
|
link.addEventListener('mouseover', trigger, { passive: true });
|
|
879
1112
|
|
|
880
|
-
|
|
1113
|
+
// Imbrication du lien de manière polymorphique
|
|
1114
|
+
if (secureRandom() > 0.5) {
|
|
1115
|
+
const itemContainer = document.createElement('span');
|
|
1116
|
+
itemContainer.className = genRandStr(5);
|
|
1117
|
+
itemContainer.appendChild(link);
|
|
1118
|
+
wrapper.appendChild(itemContainer);
|
|
1119
|
+
} else {
|
|
1120
|
+
wrapper.appendChild(link);
|
|
1121
|
+
}
|
|
881
1122
|
});
|
|
882
1123
|
|
|
883
1124
|
shadow.appendChild(wrapper);
|
|
@@ -912,7 +1153,7 @@ const ClientLibrary = {
|
|
|
912
1153
|
|
|
913
1154
|
this._dispatchEvent('challengeSolved', { solution: solutionWrapper.rawSolution });
|
|
914
1155
|
// Ajouter la solution aux paramètres de la requête pour le nouvel essai
|
|
915
|
-
|
|
1156
|
+
const url = new URL((resource instanceof Request) ? resource.url : String(resource), window.location.origin);
|
|
916
1157
|
// La logique de formatage est maintenant cachée dans la classe ChallengeSolution.
|
|
917
1158
|
solutionWrapper.applyToUrl(url);
|
|
918
1159
|
|
|
@@ -949,6 +1190,7 @@ const ClientLibrary = {
|
|
|
949
1190
|
fetch: fetchConfig = {}
|
|
950
1191
|
} = config;
|
|
951
1192
|
|
|
1193
|
+
negotiateSessionKey();
|
|
952
1194
|
// Tentative de chargement du WASM si le chemin est fourni
|
|
953
1195
|
if (wasmPath) {
|
|
954
1196
|
this.initializeWasm(wasmPath);
|