@anonympins/fingerprint 0.3.7 → 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.
- package/CHANGELOG.md +331 -244
- package/README.md +63 -1201
- package/composer.json +38 -38
- package/index.js +4 -4
- package/package.json +103 -103
- package/phpunit.xml +20 -20
- package/public/fp.js +1 -1
- package/public/fp.wasm +0 -0
- package/src/js/build-client.js +1 -1
- package/src/js/fingerprint.client.js +2 -0
- package/src/js/fingerprint.js +4429 -4220
- package/src/js/mongodb-store.js +79 -79
- package/src/js/pow.solver.inline.js +31 -0
- package/src/js/pow.solver.js +31 -0
- package/src/js/tests/fingerprint.builder.test.js +79 -0
- package/src/js/tests/fingerprint.client.init.test.js +120 -0
- package/src/js/tests/fingerprint.client.test.js +105 -0
- package/src/js/tests/fingerprint.engine.test.js +371 -0
- package/src/js/tests/fingerprint.isMalicious.test.js +117 -0
- package/src/js/tests/fingerprint.test.js +2319 -0
- package/src/js/tests/ip-reputation.test.js +132 -0
- package/src/js/tests/ja3AnomalyDetector.test.js +135 -0
- package/src/js/tests/library.test.js +96 -0
- package/src/js/tests/metrics.test.js +104 -0
- package/src/js/tests/pow.solver.test.js +198 -0
- package/src/js/tests/problem-manager.test.js +323 -0
- package/src/js/tests/stores.test.js +118 -0
- package/src/php/Challenge/ChallengeUtils.php +361 -305
- package/src/php/Config/SecurityProfiles.php +271 -266
- package/src/php/FingerprintBuilder.php +185 -185
- package/src/php/FingerprintClient.php +131 -131
- package/src/php/FingerprintEngine.php +1006 -1006
- package/src/php/Ja3AnomalyDetector.php +227 -227
- package/src/php/Optimization/FunctionRegistry.php +62 -62
- package/src/php/Optimization/Optimization.php +255 -255
- package/src/php/Optimization/OptimizationOperators.php +304 -304
- package/src/php/Store/InMemoryStore.php +66 -66
- package/src/php/Store/MongoDbStore.php +104 -104
- package/src/php/Store/RedisStore.php +53 -53
- package/src/php/Tests/ChallengeUtilsTest.php +81 -81
- package/src/php/Tests/FingerprintBuilderTest.php +57 -57
- package/src/php/Tests/FingerprintClientTest.php +71 -0
- package/src/php/Tests/FingerprintEngineTest.php +299 -299
- package/src/php/Tests/IpReputationTest.php +156 -156
- package/src/php/Tests/Ja3AnomalyDetectorTest.php +179 -179
- package/src/php/Tests/MetricsTest.php +45 -45
- package/src/php/Tests/PowTest.php +39 -39
- package/src/php/Tests/ProblemManagerTest.php +296 -296
- package/src/php/Tests/RequestUtilsTest.php +253 -145
- package/src/php/Tests/TLSClientHelloParserTest.php +118 -0
- package/src/php/Tests/problems.config.json +8 -8
- package/src/php/Utils/BigInt.php +144 -144
- package/src/php/Utils/Logger.php +29 -29
- package/src/php/Utils/MaliciousPatterns.php +58 -58
- package/src/php/Utils/MetricsManager.php +166 -166
- package/src/php/Utils/RequestUtils.php +31 -4
- package/src/php/Utils/TLSClientHelloParser.php +117 -0
- package/src/php/bin/auto-tune.php +117 -117
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Store;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Implémentation en mémoire de l'interface IStore.
|
|
9
|
-
* Utile pour le développement ou les applications à instance unique sans persistance externe.
|
|
10
|
-
*/
|
|
11
|
-
class InMemoryStore implements IStore
|
|
12
|
-
{
|
|
13
|
-
/**
|
|
14
|
-
* @var array<string, array{value: mixed, expiresAt: int|null}>
|
|
15
|
-
*/
|
|
16
|
-
private array $data = [];
|
|
17
|
-
|
|
18
|
-
public function get(string $key)
|
|
19
|
-
{
|
|
20
|
-
if (!isset($this->data[$key])) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
$item = $this->data[$key];
|
|
25
|
-
if ($item['expiresAt'] !== null && $item['expiresAt'] < time()) {
|
|
26
|
-
$this->delete($key); // Supprime l'élément expiré
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return $item['value'];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
public function set(string $key, $value, ?int $ttl = null): void
|
|
34
|
-
{
|
|
35
|
-
$expiresAt = $ttl !== null ? time() + $ttl : null;
|
|
36
|
-
|
|
37
|
-
$this->data[$key] = ['value' => $value, 'expiresAt' => $expiresAt];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
public function has(string $key): bool
|
|
41
|
-
{
|
|
42
|
-
if (!isset($this->data[$key])) {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
$item = $this->data[$key];
|
|
47
|
-
if ($item['expiresAt'] !== null && $item['expiresAt'] < time()) {
|
|
48
|
-
$this->delete($key);
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
public function delete(string $key): void
|
|
56
|
-
{
|
|
57
|
-
unset($this->data[$key]);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Efface toutes les données du store. Utile pour les tests.
|
|
62
|
-
*/
|
|
63
|
-
public function clear(): void
|
|
64
|
-
{
|
|
65
|
-
$this->data = [];
|
|
66
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Store;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Implémentation en mémoire de l'interface IStore.
|
|
9
|
+
* Utile pour le développement ou les applications à instance unique sans persistance externe.
|
|
10
|
+
*/
|
|
11
|
+
class InMemoryStore implements IStore
|
|
12
|
+
{
|
|
13
|
+
/**
|
|
14
|
+
* @var array<string, array{value: mixed, expiresAt: int|null}>
|
|
15
|
+
*/
|
|
16
|
+
private array $data = [];
|
|
17
|
+
|
|
18
|
+
public function get(string $key)
|
|
19
|
+
{
|
|
20
|
+
if (!isset($this->data[$key])) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
$item = $this->data[$key];
|
|
25
|
+
if ($item['expiresAt'] !== null && $item['expiresAt'] < time()) {
|
|
26
|
+
$this->delete($key); // Supprime l'élément expiré
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return $item['value'];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public function set(string $key, $value, ?int $ttl = null): void
|
|
34
|
+
{
|
|
35
|
+
$expiresAt = $ttl !== null ? time() + $ttl : null;
|
|
36
|
+
|
|
37
|
+
$this->data[$key] = ['value' => $value, 'expiresAt' => $expiresAt];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public function has(string $key): bool
|
|
41
|
+
{
|
|
42
|
+
if (!isset($this->data[$key])) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
$item = $this->data[$key];
|
|
47
|
+
if ($item['expiresAt'] !== null && $item['expiresAt'] < time()) {
|
|
48
|
+
$this->delete($key);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public function delete(string $key): void
|
|
56
|
+
{
|
|
57
|
+
unset($this->data[$key]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Efface toutes les données du store. Utile pour les tests.
|
|
62
|
+
*/
|
|
63
|
+
public function clear(): void
|
|
64
|
+
{
|
|
65
|
+
$this->data = [];
|
|
66
|
+
}
|
|
67
67
|
}
|
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Store;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Adaptateur de stockage MongoDB pour le moteur Fingerprint.
|
|
9
|
-
* Utilise la bibliothèque officielle mongodb/mongodb.
|
|
10
|
-
*/
|
|
11
|
-
class MongoDbStore implements IStore
|
|
12
|
-
{
|
|
13
|
-
/**
|
|
14
|
-
* @var \MongoDB\Collection
|
|
15
|
-
*/
|
|
16
|
-
private $collection;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @param \MongoDB\Collection $collection Collection dédiée au stockage des empreintes
|
|
20
|
-
*/
|
|
21
|
-
public function __construct($collection)
|
|
22
|
-
{
|
|
23
|
-
$this->collection = $collection;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
public function get(string $key)
|
|
27
|
-
{
|
|
28
|
-
$doc = $this->collection->findOne(['_id' => $key]);
|
|
29
|
-
if (!$doc) {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Vérification d'expiration active pour bypasser le délai de 60s du démon de MongoDB
|
|
34
|
-
if (isset($doc['expiresAt'])) {
|
|
35
|
-
$expiresAt = $doc['expiresAt'];
|
|
36
|
-
if ($expiresAt instanceof \MongoDB\BSON\UTCDateTime) {
|
|
37
|
-
$expiresAtMs = $expiresAt->toDateTime()->getTimestamp() * 1000;
|
|
38
|
-
$nowMs = (int)(microtime(true) * 1000);
|
|
39
|
-
if ($expiresAtMs < $nowMs) {
|
|
40
|
-
$this->delete($key);
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return isset($doc['value']) ? json_decode($doc['value'], true) : null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
public function set(string $key, $value, ?int $ttl = null): void
|
|
50
|
-
{
|
|
51
|
-
$doc = [
|
|
52
|
-
'_id' => $key,
|
|
53
|
-
'value' => json_encode($value),
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
if ($ttl !== null && $ttl > 0) {
|
|
57
|
-
$doc['expiresAt'] = new \MongoDB\BSON\UTCDateTime((time() + $ttl) * 1000);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
$this->collection->replaceOne(
|
|
61
|
-
['_id' => $key],
|
|
62
|
-
$doc,
|
|
63
|
-
['upsert' => true]
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
public function has(string $key): bool
|
|
68
|
-
{
|
|
69
|
-
$doc = $this->collection->findOne(
|
|
70
|
-
['_id' => $key],
|
|
71
|
-
['projection' => ['expiresAt' => 1]]
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
if (!$doc) {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (isset($doc['expiresAt']) && $doc['expiresAt'] instanceof \MongoDB\BSON\UTCDateTime) {
|
|
79
|
-
$expiresAtMs = $doc['expiresAt']->toDateTime()->getTimestamp() * 1000;
|
|
80
|
-
$nowMs = (int)(microtime(true) * 1000);
|
|
81
|
-
if ($expiresAtMs < $nowMs) {
|
|
82
|
-
$this->delete($key);
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
public function delete(string $key): void
|
|
91
|
-
{
|
|
92
|
-
$this->collection->deleteOne(['_id' => $key]);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Automatise la configuration de l'index TTL nécessaire dans MongoDB.
|
|
97
|
-
*/
|
|
98
|
-
public function init(): void
|
|
99
|
-
{
|
|
100
|
-
$this->collection->createIndex(
|
|
101
|
-
['expiresAt' => 1],
|
|
102
|
-
['expireAfterSeconds' => 0]
|
|
103
|
-
);
|
|
104
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Store;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Adaptateur de stockage MongoDB pour le moteur Fingerprint.
|
|
9
|
+
* Utilise la bibliothèque officielle mongodb/mongodb.
|
|
10
|
+
*/
|
|
11
|
+
class MongoDbStore implements IStore
|
|
12
|
+
{
|
|
13
|
+
/**
|
|
14
|
+
* @var \MongoDB\Collection
|
|
15
|
+
*/
|
|
16
|
+
private $collection;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param \MongoDB\Collection $collection Collection dédiée au stockage des empreintes
|
|
20
|
+
*/
|
|
21
|
+
public function __construct($collection)
|
|
22
|
+
{
|
|
23
|
+
$this->collection = $collection;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public function get(string $key)
|
|
27
|
+
{
|
|
28
|
+
$doc = $this->collection->findOne(['_id' => $key]);
|
|
29
|
+
if (!$doc) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Vérification d'expiration active pour bypasser le délai de 60s du démon de MongoDB
|
|
34
|
+
if (isset($doc['expiresAt'])) {
|
|
35
|
+
$expiresAt = $doc['expiresAt'];
|
|
36
|
+
if ($expiresAt instanceof \MongoDB\BSON\UTCDateTime) {
|
|
37
|
+
$expiresAtMs = $expiresAt->toDateTime()->getTimestamp() * 1000;
|
|
38
|
+
$nowMs = (int)(microtime(true) * 1000);
|
|
39
|
+
if ($expiresAtMs < $nowMs) {
|
|
40
|
+
$this->delete($key);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return isset($doc['value']) ? json_decode($doc['value'], true) : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public function set(string $key, $value, ?int $ttl = null): void
|
|
50
|
+
{
|
|
51
|
+
$doc = [
|
|
52
|
+
'_id' => $key,
|
|
53
|
+
'value' => json_encode($value),
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
if ($ttl !== null && $ttl > 0) {
|
|
57
|
+
$doc['expiresAt'] = new \MongoDB\BSON\UTCDateTime((time() + $ttl) * 1000);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
$this->collection->replaceOne(
|
|
61
|
+
['_id' => $key],
|
|
62
|
+
$doc,
|
|
63
|
+
['upsert' => true]
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public function has(string $key): bool
|
|
68
|
+
{
|
|
69
|
+
$doc = $this->collection->findOne(
|
|
70
|
+
['_id' => $key],
|
|
71
|
+
['projection' => ['expiresAt' => 1]]
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (!$doc) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (isset($doc['expiresAt']) && $doc['expiresAt'] instanceof \MongoDB\BSON\UTCDateTime) {
|
|
79
|
+
$expiresAtMs = $doc['expiresAt']->toDateTime()->getTimestamp() * 1000;
|
|
80
|
+
$nowMs = (int)(microtime(true) * 1000);
|
|
81
|
+
if ($expiresAtMs < $nowMs) {
|
|
82
|
+
$this->delete($key);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public function delete(string $key): void
|
|
91
|
+
{
|
|
92
|
+
$this->collection->deleteOne(['_id' => $key]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Automatise la configuration de l'index TTL nécessaire dans MongoDB.
|
|
97
|
+
*/
|
|
98
|
+
public function init(): void
|
|
99
|
+
{
|
|
100
|
+
$this->collection->createIndex(
|
|
101
|
+
['expiresAt' => 1],
|
|
102
|
+
['expireAfterSeconds' => 0]
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
105
|
}
|
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Store;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Adaptateur de stockage Redis pour le moteur Fingerprint.
|
|
9
|
-
* Compatible avec phpredis et predis.
|
|
10
|
-
*/
|
|
11
|
-
class RedisStore implements IStore
|
|
12
|
-
{
|
|
13
|
-
/**
|
|
14
|
-
* @var mixed Une instance de \Redis ou de \Predis\Client
|
|
15
|
-
*/
|
|
16
|
-
private $redis;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @param mixed $redis Client Redis déjà configuré et connecté
|
|
20
|
-
*/
|
|
21
|
-
public function __construct($redis)
|
|
22
|
-
{
|
|
23
|
-
$this->redis = $redis;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
public function get(string $key)
|
|
27
|
-
{
|
|
28
|
-
$value = $this->redis->get($key);
|
|
29
|
-
if ($value === false || $value === null) {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
return json_decode($value, true);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
public function set(string $key, $value, ?int $ttl = null): void
|
|
36
|
-
{
|
|
37
|
-
$stringValue = json_encode($value);
|
|
38
|
-
if ($ttl !== null && $ttl > 0) {
|
|
39
|
-
$this->redis->setex($key, $ttl, $stringValue);
|
|
40
|
-
} else {
|
|
41
|
-
$this->redis->set($key, $stringValue);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
public function has(string $key): bool
|
|
46
|
-
{
|
|
47
|
-
return (bool)$this->redis->exists($key);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
public function delete(string $key): void
|
|
51
|
-
{
|
|
52
|
-
$this->redis->del($key);
|
|
53
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Store;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Adaptateur de stockage Redis pour le moteur Fingerprint.
|
|
9
|
+
* Compatible avec phpredis et predis.
|
|
10
|
+
*/
|
|
11
|
+
class RedisStore implements IStore
|
|
12
|
+
{
|
|
13
|
+
/**
|
|
14
|
+
* @var mixed Une instance de \Redis ou de \Predis\Client
|
|
15
|
+
*/
|
|
16
|
+
private $redis;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param mixed $redis Client Redis déjà configuré et connecté
|
|
20
|
+
*/
|
|
21
|
+
public function __construct($redis)
|
|
22
|
+
{
|
|
23
|
+
$this->redis = $redis;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public function get(string $key)
|
|
27
|
+
{
|
|
28
|
+
$value = $this->redis->get($key);
|
|
29
|
+
if ($value === false || $value === null) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return json_decode($value, true);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public function set(string $key, $value, ?int $ttl = null): void
|
|
36
|
+
{
|
|
37
|
+
$stringValue = json_encode($value);
|
|
38
|
+
if ($ttl !== null && $ttl > 0) {
|
|
39
|
+
$this->redis->setex($key, $ttl, $stringValue);
|
|
40
|
+
} else {
|
|
41
|
+
$this->redis->set($key, $stringValue);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public function has(string $key): bool
|
|
46
|
+
{
|
|
47
|
+
return (bool)$this->redis->exists($key);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public function delete(string $key): void
|
|
51
|
+
{
|
|
52
|
+
$this->redis->del($key);
|
|
53
|
+
}
|
|
54
54
|
}
|
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
-
|
|
7
|
-
use Anonympins\Fingerprint\Challenge\ChallengeUtils;
|
|
8
|
-
use Anonympins\Fingerprint\Utils\BigInt;
|
|
9
|
-
use PHPUnit\Framework\TestCase;
|
|
10
|
-
|
|
11
|
-
class ChallengeUtilsTest extends TestCase
|
|
12
|
-
{
|
|
13
|
-
protected function setUp(): void
|
|
14
|
-
{
|
|
15
|
-
// Définir une clé secrète pour les tests
|
|
16
|
-
$_ENV['POW_SECRET'] = 'test-secret-key-that-is-long-enough-for-hmac';
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
public function testIsTicketValid(): void
|
|
20
|
-
{
|
|
21
|
-
$ip = '127.0.0.1';
|
|
22
|
-
$expiry = (int)floor(microtime(true) * 1000) + 3600000; // 1 heure
|
|
23
|
-
$signature = hash_hmac('sha256', "{$ip}:{$expiry}", $_ENV['POW_SECRET']);
|
|
24
|
-
$validTicket = "{$expiry}:{$signature}";
|
|
25
|
-
|
|
26
|
-
$this->assertTrue(ChallengeUtils::isTicketValid($ip, $validTicket));
|
|
27
|
-
$this->assertFalse(ChallengeUtils::isTicketValid('192.168.1.1', $validTicket), "Le ticket ne doit pas être valide pour une autre IP.");
|
|
28
|
-
|
|
29
|
-
$expiredExpiry = (int)floor(microtime(true) * 1000) - 1000;
|
|
30
|
-
$expiredSignature = hash_hmac('sha256', "{$ip}:{$expiredExpiry}", $_ENV['POW_SECRET']);
|
|
31
|
-
$expiredTicket = "{$expiredExpiry}:{$expiredSignature}";
|
|
32
|
-
$this->assertFalse(ChallengeUtils::isTicketValid($ip, $expiredTicket), "Un ticket expiré doit être invalide.");
|
|
33
|
-
|
|
34
|
-
$this->assertFalse(ChallengeUtils::isTicketValid($ip, 'invalid-ticket-format'), "Un format de ticket invalide doit être rejeté.");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
public function testCpuTargetCalculation(): void
|
|
38
|
-
{
|
|
39
|
-
$config = ['cpu' => ['minDifficultyBits' => 8, 'maxDifficultyBits' => 24]];
|
|
40
|
-
|
|
41
|
-
// Low suspicion -> minimum difficulty
|
|
42
|
-
$targetLow = ChallengeUtils::calculateCpuTarget(0.0, $config);
|
|
43
|
-
$expectedTargetLow = (new BigInt(1))->shiftLeft(256 - 8);
|
|
44
|
-
$this->assertEquals(0, BigInt::fromHex($targetLow)->compareTo($expectedTargetLow), "Target for 0.0 suspicion should correspond to 8 bits of difficulty.");
|
|
45
|
-
|
|
46
|
-
// High suspicion -> maximum difficulty
|
|
47
|
-
$targetHigh = ChallengeUtils::calculateCpuTarget(1.0, $config);
|
|
48
|
-
$expectedTargetHigh = (new BigInt(1))->shiftLeft(256 - 24);
|
|
49
|
-
$this->assertEquals(0, BigInt::fromHex($targetHigh)->compareTo($expectedTargetHigh), "Target for 1.0 suspicion should correspond to 24 bits of difficulty.");
|
|
50
|
-
|
|
51
|
-
// Medium suspicion -> intermediate difficulty
|
|
52
|
-
$targetMid = ChallengeUtils::calculateCpuTarget(0.5, $config);
|
|
53
|
-
$expectedBits = 8 + 0.5 * (24 - 8); // 16 bits
|
|
54
|
-
$expectedTargetMid = (new BigInt(1))->shiftLeft(256 - (int)$expectedBits);
|
|
55
|
-
$this->assertEquals(0, BigInt::fromHex($targetMid)->compareTo($expectedTargetMid), "Target for 0.5 suspicion should correspond to 16 bits of difficulty.");
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
public function testVerifyCpuTargetPoW(): void
|
|
59
|
-
{
|
|
60
|
-
$ip = '127.0.0.1';
|
|
61
|
-
$nonce = 'test-nonce';
|
|
62
|
-
$fingerprint = 'ua:test-fp';
|
|
63
|
-
$clientSecret = 'test-secret';
|
|
64
|
-
$baseBlock = ChallengeUtils::createCpuChallengeBaseBlock($nonce, $clientSecret, $fingerprint);
|
|
65
|
-
|
|
66
|
-
// Cible facile pour un test rapide (ex: 4 zéros hexadécimaux -> 16 bits)
|
|
67
|
-
$targetHex = '0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
|
|
68
|
-
$challengeContext = ['cpuTarget' => $targetHex, 'baseBlock' => $baseBlock];
|
|
69
|
-
|
|
70
|
-
// Trouver une solution valide
|
|
71
|
-
$solution = 0;
|
|
72
|
-
while (true) {
|
|
73
|
-
$hash = hash('sha256', $baseBlock . $solution);
|
|
74
|
-
if (strcmp($hash, $targetHex) < 0) break;
|
|
75
|
-
$solution++;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
$ticket = ChallengeUtils::verifyCpuTargetPoWAndGenerateTicket($ip, 3600, $nonce, (string)$solution, $challengeContext);
|
|
79
|
-
$this->assertNotNull($ticket, "Un ticket valide aurait dû être généré.");
|
|
80
|
-
$this->assertTrue(ChallengeUtils::isTicketValid($ip, $ticket));
|
|
81
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\Challenge\ChallengeUtils;
|
|
8
|
+
use Anonympins\Fingerprint\Utils\BigInt;
|
|
9
|
+
use PHPUnit\Framework\TestCase;
|
|
10
|
+
|
|
11
|
+
class ChallengeUtilsTest extends TestCase
|
|
12
|
+
{
|
|
13
|
+
protected function setUp(): void
|
|
14
|
+
{
|
|
15
|
+
// Définir une clé secrète pour les tests
|
|
16
|
+
$_ENV['POW_SECRET'] = 'test-secret-key-that-is-long-enough-for-hmac';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public function testIsTicketValid(): void
|
|
20
|
+
{
|
|
21
|
+
$ip = '127.0.0.1';
|
|
22
|
+
$expiry = (int)floor(microtime(true) * 1000) + 3600000; // 1 heure
|
|
23
|
+
$signature = hash_hmac('sha256', "{$ip}:{$expiry}", $_ENV['POW_SECRET']);
|
|
24
|
+
$validTicket = "{$expiry}:{$signature}";
|
|
25
|
+
|
|
26
|
+
$this->assertTrue(ChallengeUtils::isTicketValid($ip, $validTicket));
|
|
27
|
+
$this->assertFalse(ChallengeUtils::isTicketValid('192.168.1.1', $validTicket), "Le ticket ne doit pas être valide pour une autre IP.");
|
|
28
|
+
|
|
29
|
+
$expiredExpiry = (int)floor(microtime(true) * 1000) - 1000;
|
|
30
|
+
$expiredSignature = hash_hmac('sha256', "{$ip}:{$expiredExpiry}", $_ENV['POW_SECRET']);
|
|
31
|
+
$expiredTicket = "{$expiredExpiry}:{$expiredSignature}";
|
|
32
|
+
$this->assertFalse(ChallengeUtils::isTicketValid($ip, $expiredTicket), "Un ticket expiré doit être invalide.");
|
|
33
|
+
|
|
34
|
+
$this->assertFalse(ChallengeUtils::isTicketValid($ip, 'invalid-ticket-format'), "Un format de ticket invalide doit être rejeté.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public function testCpuTargetCalculation(): void
|
|
38
|
+
{
|
|
39
|
+
$config = ['cpu' => ['minDifficultyBits' => 8, 'maxDifficultyBits' => 24]];
|
|
40
|
+
|
|
41
|
+
// Low suspicion -> minimum difficulty
|
|
42
|
+
$targetLow = ChallengeUtils::calculateCpuTarget(0.0, $config);
|
|
43
|
+
$expectedTargetLow = (new BigInt(1))->shiftLeft(256 - 8);
|
|
44
|
+
$this->assertEquals(0, BigInt::fromHex($targetLow)->compareTo($expectedTargetLow), "Target for 0.0 suspicion should correspond to 8 bits of difficulty.");
|
|
45
|
+
|
|
46
|
+
// High suspicion -> maximum difficulty
|
|
47
|
+
$targetHigh = ChallengeUtils::calculateCpuTarget(1.0, $config);
|
|
48
|
+
$expectedTargetHigh = (new BigInt(1))->shiftLeft(256 - 24);
|
|
49
|
+
$this->assertEquals(0, BigInt::fromHex($targetHigh)->compareTo($expectedTargetHigh), "Target for 1.0 suspicion should correspond to 24 bits of difficulty.");
|
|
50
|
+
|
|
51
|
+
// Medium suspicion -> intermediate difficulty
|
|
52
|
+
$targetMid = ChallengeUtils::calculateCpuTarget(0.5, $config);
|
|
53
|
+
$expectedBits = 8 + 0.5 * (24 - 8); // 16 bits
|
|
54
|
+
$expectedTargetMid = (new BigInt(1))->shiftLeft(256 - (int)$expectedBits);
|
|
55
|
+
$this->assertEquals(0, BigInt::fromHex($targetMid)->compareTo($expectedTargetMid), "Target for 0.5 suspicion should correspond to 16 bits of difficulty.");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public function testVerifyCpuTargetPoW(): void
|
|
59
|
+
{
|
|
60
|
+
$ip = '127.0.0.1';
|
|
61
|
+
$nonce = 'test-nonce';
|
|
62
|
+
$fingerprint = 'ua:test-fp';
|
|
63
|
+
$clientSecret = 'test-secret';
|
|
64
|
+
$baseBlock = ChallengeUtils::createCpuChallengeBaseBlock($nonce, $clientSecret, $fingerprint);
|
|
65
|
+
|
|
66
|
+
// Cible facile pour un test rapide (ex: 4 zéros hexadécimaux -> 16 bits)
|
|
67
|
+
$targetHex = '0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
|
|
68
|
+
$challengeContext = ['cpuTarget' => $targetHex, 'baseBlock' => $baseBlock];
|
|
69
|
+
|
|
70
|
+
// Trouver une solution valide
|
|
71
|
+
$solution = 0;
|
|
72
|
+
while (true) {
|
|
73
|
+
$hash = hash('sha256', $baseBlock . $solution);
|
|
74
|
+
if (strcmp($hash, $targetHex) < 0) break;
|
|
75
|
+
$solution++;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
$ticket = ChallengeUtils::verifyCpuTargetPoWAndGenerateTicket($ip, 3600, $nonce, (string)$solution, $challengeContext);
|
|
79
|
+
$this->assertNotNull($ticket, "Un ticket valide aurait dû être généré.");
|
|
80
|
+
$this->assertTrue(ChallengeUtils::isTicketValid($ip, $ticket));
|
|
81
|
+
}
|
|
82
82
|
}
|