@anonympins/fingerprint 0.7.5 → 0.8.1
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 +57 -0
- package/README.md +1 -1
- package/package.json +3 -2
- package/public/anonympins-bot-mitigation-pow.zip +0 -0
- package/src/js/asn-lookup.js +223 -0
- package/src/js/fingerprint.client.js +1784 -1772
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +236 -40
- package/src/js/fingerprint.utils.js +252 -2
- package/src/js/pow.worker.js +2 -2
- package/src/js/tests/asn-lookup.test.js +57 -0
- package/src/js/tests/cross_parity.test.js +3 -3
- package/src/js/tests/dns-circuit-breaker.test.js +6 -6
- package/src/js/tests/fingerprint.builder.test.js +76 -78
- package/src/js/tests/fingerprint.client.test.js +16 -17
- package/src/js/tests/fingerprint.test.js +19 -12
- package/src/js/tests/ja3AnomalyDetector.test.js +135 -136
- package/src/js/tests/library.test.js +94 -95
- package/src/js/tests/obfuscation.test.js +8 -8
- package/src/js/tests/quicFingerprint.test.js +47 -0
- package/src/js/tests/tcpFingerprint.test.js +4 -4
- package/src/js/upow-model-task.js +155 -112
- package/src/php/AsnLookupEngine.php +173 -0
- package/src/php/Challenge/ChallengeUtils.php +430 -117
- package/src/php/Config/SecurityProfiles.php +75 -62
- package/src/php/DirectFingerprint.php +80 -13
- package/src/php/FingerprintBuilder.php +13 -13
- package/src/php/FingerprintClient.php +6 -6
- package/src/php/FingerprintEngine.php +2141 -1837
- package/src/php/Ja3AnomalyDetector.php +22 -22
- package/src/php/NetworkProfile.php +68 -0
- package/src/php/Optimization/FunctionRegistry.php +4 -4
- package/src/php/Optimization/Optimization.php +6 -6
- package/src/php/ProblemManager.php +12 -2
- package/src/php/RequestContext.php +11 -11
- package/src/php/Store/InMemoryStore.php +96 -66
- package/src/php/Store/MongoDbStore.php +2 -2
- package/src/php/Store/RedisStore.php +1 -1
- package/src/php/Store/StoreManager.php +48 -35
- package/src/php/Tests/AsnLookupTest.php +61 -0
- package/src/php/Tests/ChallengeUtilsTest.php +31 -0
- package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
- package/src/php/Tests/FingerprintClientTest.php +70 -70
- package/src/php/Tests/FingerprintEngineTest.php +4 -2
- package/src/php/Tests/PowTest.php +37 -39
- package/src/php/Tests/QuicFingerprintTest.php +115 -53
- package/src/php/Tests/ThreatIntelTest.php +2 -2
- package/src/php/Utils/BigInt.php +202 -202
- package/src/php/Utils/BlockList.php +13 -13
- package/src/php/Utils/Env.php +7 -2
- package/src/php/Utils/Logger.php +19 -1
- package/src/php/Utils/MaliciousPatterns.php +6 -6
- package/src/php/Utils/MetricsManager.php +209 -209
- package/src/php/Utils/RequestUtils.php +348 -18
- package/src/php/Utils/TLSClientHelloParser.php +385 -369
- package/src/php/WordPress/WpDbStore.php +34 -7
- package/src/php/WordPress/anonympins-bot-mitigation-pow.php +1222 -0
- package/src/php/WordPress/fingerprint-anti-bot.php +0 -559
- package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.po +232 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +297 -261
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +348 -267
- package/src/php/WordPress/package.php +88 -99
- package/src/php/WordPress/readme.txt +28 -6
- package/src/php/bin/auto-tune.php +1 -1
- package/public/fingerprint-anti-bot.zip +0 -0
|
@@ -1,54 +1,116 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
-
|
|
7
|
-
use Anonympins\Fingerprint\RequestContext;
|
|
8
|
-
use Anonympins\Fingerprint\Utils\RequestUtils;
|
|
9
|
-
use PHPUnit\Framework\TestCase;
|
|
10
|
-
|
|
11
|
-
class QuicFingerprintTest extends TestCase
|
|
12
|
-
{
|
|
13
|
-
public function testGetProtocolAnomalyScoreReturnsZeroIfNoFp(): void
|
|
14
|
-
{
|
|
15
|
-
$context = $this->createMock(RequestContext::class);
|
|
16
|
-
$context->method('getHeader')->willReturn(null);
|
|
17
|
-
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
18
|
-
$this->assertEquals(0.0, $res['protocolAnomalyScore']);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
public function testGetProtocolAnomalyScoreDetectsSpoofedChrome(): void
|
|
22
|
-
{
|
|
23
|
-
$context = $this->createMock(RequestContext::class);
|
|
24
|
-
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
25
|
-
if ($name === 'user-agent') {
|
|
26
|
-
return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
27
|
-
}
|
|
28
|
-
if ($name === 'x-quic-fp') {
|
|
29
|
-
return '1;1=65536,4=50;i=0';
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
35
|
-
$this->assertEquals(100.0, $res['protocolAnomalyScore']);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
public function testGetProtocolAnomalyScoreAllowsLegitChrome(): void
|
|
39
|
-
{
|
|
40
|
-
$context = $this->createMock(RequestContext::class);
|
|
41
|
-
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
42
|
-
if ($name === 'user-agent') {
|
|
43
|
-
return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
44
|
-
}
|
|
45
|
-
if ($name === 'x-quic-fp') {
|
|
46
|
-
return '1;1=1572864,4=100;u=2,i';
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
52
|
-
$this->assertEquals(0.0, $res['protocolAnomalyScore']);
|
|
53
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\RequestContext;
|
|
8
|
+
use Anonympins\Fingerprint\Utils\RequestUtils;
|
|
9
|
+
use PHPUnit\Framework\TestCase;
|
|
10
|
+
|
|
11
|
+
class QuicFingerprintTest extends TestCase
|
|
12
|
+
{
|
|
13
|
+
public function testGetProtocolAnomalyScoreReturnsZeroIfNoFp(): void
|
|
14
|
+
{
|
|
15
|
+
$context = $this->createMock(RequestContext::class);
|
|
16
|
+
$context->method('getHeader')->willReturn(null);
|
|
17
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
18
|
+
$this->assertEquals(0.0, $res['protocolAnomalyScore']);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public function testGetProtocolAnomalyScoreDetectsSpoofedChrome(): void
|
|
22
|
+
{
|
|
23
|
+
$context = $this->createMock(RequestContext::class);
|
|
24
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
25
|
+
if ($name === 'user-agent') {
|
|
26
|
+
return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
27
|
+
}
|
|
28
|
+
if ($name === 'x-quic-fp') {
|
|
29
|
+
return '1;1=65536,4=50;i=0';
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
35
|
+
$this->assertEquals(100.0, $res['protocolAnomalyScore']);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public function testGetProtocolAnomalyScoreAllowsLegitChrome(): void
|
|
39
|
+
{
|
|
40
|
+
$context = $this->createMock(RequestContext::class);
|
|
41
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
42
|
+
if ($name === 'user-agent') {
|
|
43
|
+
return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
44
|
+
}
|
|
45
|
+
if ($name === 'x-quic-fp') {
|
|
46
|
+
return '1;1=1572864,4=100;u=2,i';
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
52
|
+
$this->assertEquals(0.0, $res['protocolAnomalyScore']);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public function testRfc9218PriorityWithoutIncrementalFlag(): void
|
|
56
|
+
{
|
|
57
|
+
$context = $this->createMock(RequestContext::class);
|
|
58
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
59
|
+
if ($name === 'user-agent') return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
60
|
+
if ($name === 'x-quic-fp') return '1;1=1572864,4=100;u=2'; // Manque le drapeau 'i'
|
|
61
|
+
return null;
|
|
62
|
+
});
|
|
63
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
64
|
+
$this->assertEquals(40.0, $res['quicAnomalyScore']);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public function testCompressionRatioPenalty(): void
|
|
68
|
+
{
|
|
69
|
+
$context = $this->createMock(RequestContext::class);
|
|
70
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
71
|
+
if ($name === 'user-agent') return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
72
|
+
if ($name === 'x-compression-info') return 'req_count:5,hpack_ratio:0.2';
|
|
73
|
+
return null;
|
|
74
|
+
});
|
|
75
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
76
|
+
$this->assertEquals(40.0, $res['http2AnomalyScore']);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public function testRfc9218UrgencyOutOfRange(): void
|
|
80
|
+
{
|
|
81
|
+
$context = $this->createMock(RequestContext::class);
|
|
82
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
83
|
+
if ($name === 'user-agent') return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
84
|
+
if ($name === 'x-quic-fp') return '1;1=1572864,4=100;u=9,i';
|
|
85
|
+
return null;
|
|
86
|
+
});
|
|
87
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
88
|
+
$this->assertGreaterThanOrEqual(50.0, $res['quicAnomalyScore']);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public function testHttp2DefaultGoScraperDependencyTree(): void
|
|
92
|
+
{
|
|
93
|
+
$context = $this->createMock(RequestContext::class);
|
|
94
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
95
|
+
if ($name === 'user-agent') return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
96
|
+
if ($name === 'x-http2-fingerprint') return 's:1:65536,2:0,3:1000,4:6291456,6:262144|15663105|1:0:0:16|m,a,s,p|p:3,w:4,c:1';
|
|
97
|
+
return null;
|
|
98
|
+
});
|
|
99
|
+
$context->httpVersion = '2.0';
|
|
100
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
101
|
+
$this->assertGreaterThanOrEqual(55.0, $res['http2AnomalyScore']);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public function testScraperWithZeroDynamicTableEntries(): void
|
|
105
|
+
{
|
|
106
|
+
$context = $this->createMock(RequestContext::class);
|
|
107
|
+
$context->method('getHeader')->willReturnCallback(function($name) {
|
|
108
|
+
if ($name === 'user-agent') return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
109
|
+
if ($name === 'x-compression-info') return 'req_count:5,dynamic_table_entries:0';
|
|
110
|
+
return null;
|
|
111
|
+
});
|
|
112
|
+
$context->httpVersion = '2.0';
|
|
113
|
+
$res = RequestUtils::getProtocolAnomalyScore($context);
|
|
114
|
+
$this->assertGreaterThanOrEqual(45.0, $res['http2AnomalyScore']);
|
|
115
|
+
}
|
|
54
116
|
}
|
|
@@ -70,7 +70,7 @@ class ThreatIntelTest extends TestCase
|
|
|
70
70
|
'differentialPrivacy' => [
|
|
71
71
|
'enabled' => true,
|
|
72
72
|
'epsilon' => 1.0,
|
|
73
|
-
'dummyRate' => 1.0 //
|
|
73
|
+
'dummyRate' => 1.0 // Force decoy injection for test
|
|
74
74
|
]
|
|
75
75
|
];
|
|
76
76
|
|
|
@@ -93,6 +93,6 @@ class ThreatIntelTest extends TestCase
|
|
|
93
93
|
$realZkpY = 'realzkpykey12345';
|
|
94
94
|
$broadcastMethod->invoke($engineSub, $realZkpY);
|
|
95
95
|
|
|
96
|
-
$this->assertCount(2, $postedCalls, '
|
|
96
|
+
$this->assertCount(2, $postedCalls, 'Should broadcast both real and decoy keys');
|
|
97
97
|
}
|
|
98
98
|
}
|
package/src/php/Utils/BigInt.php
CHANGED
|
@@ -1,203 +1,203 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Utils;
|
|
6
|
-
|
|
7
|
-
class BigInt
|
|
8
|
-
{
|
|
9
|
-
private static ?bool $useGmp = null;
|
|
10
|
-
/** @var \GMP|string */
|
|
11
|
-
private $value;
|
|
12
|
-
|
|
13
|
-
private static function useGmp(): bool
|
|
14
|
-
{
|
|
15
|
-
if (self::$useGmp === null) {
|
|
16
|
-
self::$useGmp = extension_loaded('gmp');
|
|
17
|
-
}
|
|
18
|
-
return self::$useGmp;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @param \GMP|string|int $number
|
|
23
|
-
*/
|
|
24
|
-
public function __construct($number)
|
|
25
|
-
{
|
|
26
|
-
if (self::useGmp()) {
|
|
27
|
-
if ($number instanceof \GMP) {
|
|
28
|
-
$this->value = $number;
|
|
29
|
-
} else {
|
|
30
|
-
$str = (string)$number;
|
|
31
|
-
if (($dot = strpos($str, '.')) !== false) {
|
|
32
|
-
$str = substr($str, 0, $dot);
|
|
33
|
-
}
|
|
34
|
-
$this->value = gmp_init($str);
|
|
35
|
-
}
|
|
36
|
-
} else {
|
|
37
|
-
if ($number instanceof BigInt) {
|
|
38
|
-
$this->value = $number->value;
|
|
39
|
-
} else {
|
|
40
|
-
$str = (string)$number;
|
|
41
|
-
if (($dot = strpos($str, '.')) !== false) {
|
|
42
|
-
$str = substr($str, 0, $dot);
|
|
43
|
-
}
|
|
44
|
-
$this->value = $str;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
*
|
|
51
|
-
*/
|
|
52
|
-
public static function fromHex(string $hex): BigInt
|
|
53
|
-
{
|
|
54
|
-
$hex = trim(strtolower($hex));
|
|
55
|
-
if (substr($hex, 0, 2) === '0x') {
|
|
56
|
-
$hex = substr($hex, 2);
|
|
57
|
-
}
|
|
58
|
-
if (self::useGmp()) {
|
|
59
|
-
return new self(gmp_init($hex, 16));
|
|
60
|
-
} else {
|
|
61
|
-
$dec = '0';
|
|
62
|
-
$len = strlen($hex);
|
|
63
|
-
for ($i = 0; $i < $len; $i++) {
|
|
64
|
-
$dec = bcadd(bcmul($dec, '16', 0), (string)hexdec($hex[$i]), 0);
|
|
65
|
-
}
|
|
66
|
-
return new self($dec);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
*
|
|
72
|
-
* @return int < 0
|
|
73
|
-
*/
|
|
74
|
-
public function compareTo(BigInt $other): int
|
|
75
|
-
{
|
|
76
|
-
if (self::useGmp()) {
|
|
77
|
-
return gmp_cmp($this->value, $other->value);
|
|
78
|
-
} else {
|
|
79
|
-
return bccomp($this->value, $other->value, 0);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
*
|
|
85
|
-
*/
|
|
86
|
-
public function shiftLeft(int $bits): BigInt
|
|
87
|
-
{
|
|
88
|
-
if (self::useGmp()) {
|
|
89
|
-
return new self(gmp_mul($this->value, gmp_pow("2", $bits)));
|
|
90
|
-
} else {
|
|
91
|
-
return new self(bcmul($this->value, bcpow("2", (string)$bits, 0), 0));
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
*
|
|
97
|
-
*/
|
|
98
|
-
public function shiftRight(int $bits): BigInt
|
|
99
|
-
{
|
|
100
|
-
if (self::useGmp()) {
|
|
101
|
-
return new self(gmp_div_q($this->value, gmp_pow("2", $bits)));
|
|
102
|
-
} else {
|
|
103
|
-
return new self(bcdiv($this->value, bcpow("2", (string)$bits, 0), 0));
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
*
|
|
109
|
-
*/
|
|
110
|
-
public function __toString(): string
|
|
111
|
-
{
|
|
112
|
-
if (self::useGmp()) {
|
|
113
|
-
return gmp_strval($this->value);
|
|
114
|
-
} else {
|
|
115
|
-
return $this->value;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
*
|
|
121
|
-
*/
|
|
122
|
-
public function toHex(): string
|
|
123
|
-
{
|
|
124
|
-
if (self::useGmp()) {
|
|
125
|
-
return gmp_strval($this->value, 16);
|
|
126
|
-
} else {
|
|
127
|
-
$hex = '';
|
|
128
|
-
$dec = $this->value;
|
|
129
|
-
while (bccomp($dec, '0', 0) > 0) {
|
|
130
|
-
$rem = bcmod($dec, '16', 0);
|
|
131
|
-
$hex = dechex((int)$rem) . $hex;
|
|
132
|
-
$dec = bcdiv($dec, '16', 0);
|
|
133
|
-
}
|
|
134
|
-
return $hex ?: '0';
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
*
|
|
140
|
-
*/
|
|
141
|
-
public static function pow(int $base, int $exp): BigInt
|
|
142
|
-
{
|
|
143
|
-
if (self::useGmp()) {
|
|
144
|
-
return new self(gmp_pow((string)$base, $exp));
|
|
145
|
-
} else {
|
|
146
|
-
return new self(bcpow((string)$base, (string)$exp, 0));
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
*
|
|
152
|
-
*/
|
|
153
|
-
public function sub(BigInt $other): BigInt
|
|
154
|
-
{
|
|
155
|
-
if (self::useGmp()) {
|
|
156
|
-
return new self(gmp_sub($this->value, $other->value));
|
|
157
|
-
} else {
|
|
158
|
-
return new self(bcsub($this->value, $other->value, 0));
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Computes ($this ^ $exponent) % $modulus
|
|
164
|
-
*/
|
|
165
|
-
public function modPow(BigInt $exponent, BigInt $modulus): BigInt
|
|
166
|
-
{
|
|
167
|
-
if (self::useGmp()) {
|
|
168
|
-
return new self(gmp_powm($this->value, $exponent->value, $modulus->value));
|
|
169
|
-
} else {
|
|
170
|
-
return new self(bcpowmod((string)$this->value, (string)$exponent->value, (string)$modulus->value, 0));
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Computes $this % $modulus
|
|
176
|
-
*/
|
|
177
|
-
public function mod(BigInt $modulus): BigInt
|
|
178
|
-
{
|
|
179
|
-
if (self::useGmp()) {
|
|
180
|
-
return new self(gmp_mod($this->value, $modulus->value));
|
|
181
|
-
} else {
|
|
182
|
-
return new self(bcmod((string)$this->value, (string)$modulus->value, 0));
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
public function add(BigInt $other): BigInt
|
|
187
|
-
{
|
|
188
|
-
if (self::useGmp()) {
|
|
189
|
-
return new self(gmp_add($this->value, $other->value));
|
|
190
|
-
} else {
|
|
191
|
-
return new self(bcadd((string)$this->value, (string)$other->value, 0));
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
public function mul(BigInt $other): BigInt
|
|
196
|
-
{
|
|
197
|
-
if (self::useGmp()) {
|
|
198
|
-
return new self(gmp_mul($this->value, $other->value));
|
|
199
|
-
} else {
|
|
200
|
-
return new self(bcmul((string)$this->value, (string)$other->value, 0));
|
|
201
|
-
}
|
|
202
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Utils;
|
|
6
|
+
|
|
7
|
+
class BigInt
|
|
8
|
+
{
|
|
9
|
+
private static ?bool $useGmp = null;
|
|
10
|
+
/** @var \GMP|string */
|
|
11
|
+
private $value;
|
|
12
|
+
|
|
13
|
+
private static function useGmp(): bool
|
|
14
|
+
{
|
|
15
|
+
if (self::$useGmp === null) {
|
|
16
|
+
self::$useGmp = extension_loaded('gmp');
|
|
17
|
+
}
|
|
18
|
+
return self::$useGmp;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param \GMP|string|int $number Initial number.
|
|
23
|
+
*/
|
|
24
|
+
public function __construct($number)
|
|
25
|
+
{
|
|
26
|
+
if (self::useGmp()) {
|
|
27
|
+
if ($number instanceof \GMP) {
|
|
28
|
+
$this->value = $number;
|
|
29
|
+
} else {
|
|
30
|
+
$str = (string)$number;
|
|
31
|
+
if (($dot = strpos($str, '.')) !== false) {
|
|
32
|
+
$str = substr($str, 0, $dot);
|
|
33
|
+
}
|
|
34
|
+
$this->value = gmp_init($str);
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
if ($number instanceof BigInt) {
|
|
38
|
+
$this->value = $number->value;
|
|
39
|
+
} else {
|
|
40
|
+
$str = (string)$number;
|
|
41
|
+
if (($dot = strpos($str, '.')) !== false) {
|
|
42
|
+
$str = substr($str, 0, $dot);
|
|
43
|
+
}
|
|
44
|
+
$this->value = $str;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates an instance from a hexadecimal string.
|
|
51
|
+
*/
|
|
52
|
+
public static function fromHex(string $hex): BigInt
|
|
53
|
+
{
|
|
54
|
+
$hex = trim(strtolower($hex));
|
|
55
|
+
if (substr($hex, 0, 2) === '0x') {
|
|
56
|
+
$hex = substr($hex, 2);
|
|
57
|
+
}
|
|
58
|
+
if (self::useGmp()) {
|
|
59
|
+
return new self(gmp_init($hex, 16));
|
|
60
|
+
} else {
|
|
61
|
+
$dec = '0';
|
|
62
|
+
$len = strlen($hex);
|
|
63
|
+
for ($i = 0; $i < $len; $i++) {
|
|
64
|
+
$dec = bcadd(bcmul($dec, '16', 0), (string)hexdec($hex[$i]), 0);
|
|
65
|
+
}
|
|
66
|
+
return new self($dec);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Compares this instance with another.
|
|
72
|
+
* @return int < 0 if this < other, 0 if this == other, > 0 if this > other.
|
|
73
|
+
*/
|
|
74
|
+
public function compareTo(BigInt $other): int
|
|
75
|
+
{
|
|
76
|
+
if (self::useGmp()) {
|
|
77
|
+
return gmp_cmp($this->value, $other->value);
|
|
78
|
+
} else {
|
|
79
|
+
return bccomp($this->value, $other->value, 0);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Performs bitwise left shift (<<).
|
|
85
|
+
*/
|
|
86
|
+
public function shiftLeft(int $bits): BigInt
|
|
87
|
+
{
|
|
88
|
+
if (self::useGmp()) {
|
|
89
|
+
return new self(gmp_mul($this->value, gmp_pow("2", $bits)));
|
|
90
|
+
} else {
|
|
91
|
+
return new self(bcmul($this->value, bcpow("2", (string)$bits, 0), 0));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Performs bitwise right shift (>>).
|
|
97
|
+
*/
|
|
98
|
+
public function shiftRight(int $bits): BigInt
|
|
99
|
+
{
|
|
100
|
+
if (self::useGmp()) {
|
|
101
|
+
return new self(gmp_div_q($this->value, gmp_pow("2", $bits)));
|
|
102
|
+
} else {
|
|
103
|
+
return new self(bcdiv($this->value, bcpow("2", (string)$bits, 0), 0));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Returns the string representation.
|
|
109
|
+
*/
|
|
110
|
+
public function __toString(): string
|
|
111
|
+
{
|
|
112
|
+
if (self::useGmp()) {
|
|
113
|
+
return gmp_strval($this->value);
|
|
114
|
+
} else {
|
|
115
|
+
return $this->value;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Returns the hexadecimal representation.
|
|
121
|
+
*/
|
|
122
|
+
public function toHex(): string
|
|
123
|
+
{
|
|
124
|
+
if (self::useGmp()) {
|
|
125
|
+
return gmp_strval($this->value, 16);
|
|
126
|
+
} else {
|
|
127
|
+
$hex = '';
|
|
128
|
+
$dec = $this->value;
|
|
129
|
+
while (bccomp($dec, '0', 0) > 0) {
|
|
130
|
+
$rem = bcmod($dec, '16', 0);
|
|
131
|
+
$hex = dechex((int)$rem) . $hex;
|
|
132
|
+
$dec = bcdiv($dec, '16', 0);
|
|
133
|
+
}
|
|
134
|
+
return $hex ?: '0';
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Creates an instance from a power of two.
|
|
140
|
+
*/
|
|
141
|
+
public static function pow(int $base, int $exp): BigInt
|
|
142
|
+
{
|
|
143
|
+
if (self::useGmp()) {
|
|
144
|
+
return new self(gmp_pow((string)$base, $exp));
|
|
145
|
+
} else {
|
|
146
|
+
return new self(bcpow((string)$base, (string)$exp, 0));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Subtracts another BigInt.
|
|
152
|
+
*/
|
|
153
|
+
public function sub(BigInt $other): BigInt
|
|
154
|
+
{
|
|
155
|
+
if (self::useGmp()) {
|
|
156
|
+
return new self(gmp_sub($this->value, $other->value));
|
|
157
|
+
} else {
|
|
158
|
+
return new self(bcsub($this->value, $other->value, 0));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Computes ($this ^ $exponent) % $modulus
|
|
164
|
+
*/
|
|
165
|
+
public function modPow(BigInt $exponent, BigInt $modulus): BigInt
|
|
166
|
+
{
|
|
167
|
+
if (self::useGmp()) {
|
|
168
|
+
return new self(gmp_powm($this->value, $exponent->value, $modulus->value));
|
|
169
|
+
} else {
|
|
170
|
+
return new self(bcpowmod((string)$this->value, (string)$exponent->value, (string)$modulus->value, 0));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Computes $this % $modulus
|
|
176
|
+
*/
|
|
177
|
+
public function mod(BigInt $modulus): BigInt
|
|
178
|
+
{
|
|
179
|
+
if (self::useGmp()) {
|
|
180
|
+
return new self(gmp_mod($this->value, $modulus->value));
|
|
181
|
+
} else {
|
|
182
|
+
return new self(bcmod((string)$this->value, (string)$modulus->value, 0));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public function add(BigInt $other): BigInt
|
|
187
|
+
{
|
|
188
|
+
if (self::useGmp()) {
|
|
189
|
+
return new self(gmp_add($this->value, $other->value));
|
|
190
|
+
} else {
|
|
191
|
+
return new self(bcadd((string)$this->value, (string)$other->value, 0));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public function mul(BigInt $other): BigInt
|
|
196
|
+
{
|
|
197
|
+
if (self::useGmp()) {
|
|
198
|
+
return new self(gmp_mul($this->value, $other->value));
|
|
199
|
+
} else {
|
|
200
|
+
return new self(bcmul((string)$this->value, (string)$other->value, 0));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
203
|
}
|
|
@@ -5,18 +5,18 @@ declare(strict_types=1);
|
|
|
5
5
|
namespace Anonympins\Fingerprint\Utils;
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* PHP implementation of an IP/CIDR blocklist, similar to Node.js `net.BlockList`.
|
|
9
9
|
*/
|
|
10
10
|
class BlockList
|
|
11
11
|
{
|
|
12
12
|
/**
|
|
13
|
-
* @var array<string>
|
|
13
|
+
* @var array<string> List of IPs or CIDR blocks to filter.
|
|
14
14
|
*/
|
|
15
15
|
private array $entries = [];
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
* @param string $entry
|
|
18
|
+
* Adds an IP address or CIDR range to the list.
|
|
19
|
+
* @param string $entry An IP address (e.g. '192.168.1.1') or CIDR range (e.g. '192.168.1.0/24').
|
|
20
20
|
* @return void
|
|
21
21
|
*/
|
|
22
22
|
public function add(string $entry): void
|
|
@@ -25,20 +25,20 @@ class BlockList
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
* @param string $ip
|
|
30
|
-
* @return bool True
|
|
28
|
+
* Checks if an IP address exists in the blocklist.
|
|
29
|
+
* @param string $ip The IP address to verify.
|
|
30
|
+
* @return bool True if the IP matches, false otherwise.
|
|
31
31
|
*/
|
|
32
32
|
public function check(string $ip): bool
|
|
33
33
|
{
|
|
34
34
|
foreach ($this->entries as $entry) {
|
|
35
35
|
if (str_contains($entry, '/')) {
|
|
36
|
-
//
|
|
36
|
+
// CIDR range
|
|
37
37
|
if ($this->ipInCidr($ip, $entry)) {
|
|
38
38
|
return true;
|
|
39
39
|
}
|
|
40
40
|
} else {
|
|
41
|
-
//
|
|
41
|
+
// Direct IP match
|
|
42
42
|
if ($ip === $entry) {
|
|
43
43
|
return true;
|
|
44
44
|
}
|
|
@@ -48,10 +48,10 @@ class BlockList
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
*
|
|
52
|
-
* @param string $ip
|
|
53
|
-
* @param string $cidr
|
|
54
|
-
* @return bool True
|
|
51
|
+
* Checks if an IP address belongs to a given CIDR range.
|
|
52
|
+
* @param string $ip The IP address to verify.
|
|
53
|
+
* @param string $cidr The CIDR range (e.g. '192.168.1.0/24').
|
|
54
|
+
* @return bool True if the IP is within the range, false otherwise.
|
|
55
55
|
*/
|
|
56
56
|
private function ipInCidr(string $ip, string $cidr): bool
|
|
57
57
|
{
|