@anonympins/fingerprint 0.4.4 → 0.4.6

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.
@@ -225,6 +225,16 @@
225
225
  $deviceData = null;
226
226
  $newCookie = null;
227
227
 
228
+ // Cookieless Identity Tracking: Attempt to restore device ID using TLS session resume ID
229
+ $tlsSessionId = $context->tlsSessionId;
230
+ if (!$deviceId && $tlsSessionId) {
231
+ $resumedDeviceId = $store->get("tls-session:{$tlsSessionId}");
232
+ if ($resumedDeviceId) {
233
+ $deviceId = $resumedDeviceId;
234
+ $this->log('Identity resumed via TLS Session ID', ['deviceId' => $deviceId, 'tlsSessionId' => $tlsSessionId]);
235
+ }
236
+ }
237
+
228
238
  if ($deviceId) {
229
239
  $deviceData = $store->get("device:{$deviceId}");
230
240
  }
@@ -275,6 +285,11 @@
275
285
  }
276
286
  }
277
287
 
288
+ // Bind the current TLS session ID to the device ID
289
+ if ($deviceId && $tlsSessionId) {
290
+ $store->set("tls-session:{$tlsSessionId}", $deviceId, 3600); // 1h cache duration
291
+ }
292
+
278
293
  return [
279
294
  'deviceId' => $deviceId,
280
295
  'deviceData' => $deviceData,
@@ -425,6 +440,9 @@
425
440
  // NOUVEAU: Score de réputation du sous-réseau IP
426
441
  $subnetScore = RequestUtils::getSubnetScore($context, $deviceId);
427
442
 
443
+ // Score d'anomalie de pile TCP/IP
444
+ $tcpAnomaly = RequestUtils::getTcpAnomalyScore($context);
445
+
428
446
  // Assemblage du vecteur de suspicion final
429
447
  $suspicionVector = array_merge($suspicionVector, [
430
448
  'inconsistencyScore' => $inconsistencyScore,
@@ -443,6 +461,7 @@
443
461
  'clientHintsInconsistencyScore' => $clientHintsInconsistency['clientHintsInconsistencyScore'],
444
462
  'subnetScore' => $subnetScore['subnetScore'],
445
463
  'botnetClusterScore' => $botnetCluster['botnetClusterScore'],
464
+ 'tcpAnomalyScore' => $tcpAnomaly['tcpAnomalyScore'],
446
465
  ]);
447
466
 
448
467
  // Sauvegarder l'état mis à jour de l'appareil dans le store
@@ -511,6 +530,53 @@
511
530
  ];
512
531
  }
513
532
 
533
+
534
+ private function decodePolymorphicFingerprint(string $fpString, array $mapping): string
535
+ {
536
+ if (empty($mapping['keys'])) {
537
+ return $fpString;
538
+ }
539
+ $reverseKeys = array_flip($mapping['keys']);
540
+ $parts = explode('|', $fpString);
541
+ $mappedParts = [];
542
+ foreach ($parts as $part) {
543
+ $pair = explode(':', $part, 2);
544
+ if (count($pair) === 2) {
545
+ $origKey = $reverseKeys[$pair[0]] ?? $pair[0];
546
+ $mappedParts[] = "{$origKey}:{$pair[1]}";
547
+ } else {
548
+ $mappedParts[] = $part;
549
+ }
550
+ }
551
+ return implode('|', $mappedParts);
552
+ }
553
+ private function translatePolymorphicHeaders(RequestContext $context): void
554
+ {
555
+ $store = StoreManager::getStore();
556
+ $activeMappings = $store->get('active-polymorphic-mappings') ?: [];
557
+ if (!is_array($activeMappings)) {
558
+ return;
559
+ }
560
+
561
+ foreach ($activeMappings as $mapping) {
562
+ $devFpHeader = strtolower($mapping['headers']['x-device-fingerprint'] ?? '');
563
+ $behaviorHeader = strtolower($mapping['headers']['x-behavior-metrics'] ?? '');
564
+
565
+ if (!empty($devFpHeader) && isset($context->headers[$devFpHeader])) {
566
+ $context->headers['x-device-fingerprint'] = $context->headers[$devFpHeader];
567
+ if (!empty($behaviorHeader) && isset($context->headers[$behaviorHeader])) {
568
+ $context->headers['x-behavior-metrics'] = $context->headers[$behaviorHeader];
569
+ }
570
+
571
+ $clientFp = $context->headers['x-device-fingerprint'];
572
+ if ($clientFp && is_string($clientFp)) {
573
+ $context->headers['x-device-fingerprint'] = $this->decodePolymorphicFingerprint($clientFp, $mapping);
574
+ }
575
+ break;
576
+ }
577
+ }
578
+ }
579
+
514
580
  /**
515
581
  * Traite une requête entrante et retourne une décision.
516
582
  * @param RequestContext $context Le contexte de la requête.
@@ -518,8 +584,21 @@
518
584
  */
519
585
  public function processRequest(RequestContext $context): array
520
586
  {
587
+ $this->translatePolymorphicHeaders($context);
588
+
521
589
  // Initialiser le vecteur de suspicion pour éviter les erreurs de type.
522
590
  $suspicionVector = [];
591
+
592
+ $this->log('Processing request', ['clientIp' => $context->clientIp, 'path' => $context->path]);
593
+
594
+ // Parse GraphQL query if applicable
595
+ if ($context->path === '/graphql' && !empty($context->body)) {
596
+ $gqlInfo = RequestUtils::parseGraphQLQuery(is_array($context->body) ? $context->body : []);
597
+ if ($gqlInfo) {
598
+ $context->graphqlOperation = $gqlInfo;
599
+ }
600
+ }
601
+
523
602
 
524
603
  $this->log('Processing request', ['clientIp' => $context->clientIp, 'path' => $context->path]);
525
604
 
@@ -545,6 +624,7 @@
545
624
  $isChallengeSubmission = $powNonce && (
546
625
  isset($context->query['pow_solution']) ||
547
626
  isset($context->query['pow_solution_cpu']) ||
627
+ isset($context->query['pow_solution_space']) ||
548
628
  (isset($context->query['pow_type']) && $context->query['pow_type'] === 'useful_work_task')
549
629
  );
550
630
  if ($isChallengeSubmission) {
@@ -583,6 +663,28 @@
583
663
  $isValid = $isValid && $isMemValid;
584
664
  }
585
665
  }
666
+ } elseif ($powType === 'pospace') {
667
+ $powSolutionSpace = $context->query['pow_solution_space'] ?? null;
668
+ if ($powSolutionSpace && isset($challengeContext['queries'])) {
669
+ $isSpaceValid = ChallengeUtils::verifySpacePoW(
670
+ $powNonce,
671
+ $powSolutionSpace,
672
+ $challengeContext['queries'],
673
+ $powNonce . ":" . $challengeContext['clientSecret'],
674
+ $challengeContext['clientSecret']
675
+ );
676
+ $isValid = $isSpaceValid;
677
+ if ($isValid) {
678
+ $ticketTtl = $this->securityConfig['ticketMaxAge'] ?? 3600000;
679
+ $expiry = (int)floor(microtime(true) * 1000) + $ticketTtl;
680
+ $ticket = ChallengeUtils::generateStatelessTicket([
681
+ 'expiry' => $expiry,
682
+ 'originalIp' => $context->clientIp,
683
+ 'deviceId' => '',
684
+ 'deviceHash' => ''
685
+ ]);
686
+ }
687
+ }
586
688
  } elseif ($powType === 'useful_work_task') {
587
689
  $problemId = $context->query['pow_problem_id'] ?? null;
588
690
  $workResultJson = $context->query['pow_solution_work_result'] ?? null;
@@ -618,10 +720,6 @@
618
720
  $this->log('Challenge solution valid - issuing ticket', ['ticketMaxAge' => $ticketTtl]);
619
721
 
620
722
  return [
621
- // ... (le reste de la logique de redirection)
622
- 'action' => 'redirect',
623
- 'path' => RequestUtils::cleanUrlFromPowParams($challengeContext['originalPath'] ?? '/', $context->query),
624
- 'score' => 0.0,
625
723
  'action' => 'redirect',
626
724
  'path' => RequestUtils::cleanUrlFromPowParams($challengeContext['originalPath'] ?? '/', $context->query),
627
725
  'score' => 0.0,
@@ -787,7 +885,11 @@
787
885
 
788
886
  $nonce = bin2hex(random_bytes(16));
789
887
  $clientSecret = bin2hex(random_bytes(16));
790
-
888
+ $highThreshold = $thresholds['high'] ?? 75;
889
+
890
+ $suspicionFactor = ($finalScore - $lowThreshold) / (($thresholds['high'] ?? 75) - $lowThreshold);
891
+ $suspicionFactor = max(0, min(1.5, $suspicionFactor));
892
+
791
893
  // --- NOUVELLE LOGIQUE uPoW ---
792
894
  $shouldUseUsefulWork = ($this->securityConfig['enableUsefulWork'] ?? false) && (
793
895
  ($this->securityConfig['forceUsefulWork'] ?? false) || (random_int(0, 255) / 255) > 0.5
@@ -839,9 +941,39 @@
839
941
 
840
942
  // --- FIN DE LA LOGIQUE uPoW (le reste est le fallback) ---
841
943
 
842
- $suspicionFactor = ($finalScore - $lowThreshold) / (($thresholds['high'] ?? 75) - $lowThreshold);
843
- $suspicionFactor = max(0, min(1.5, $suspicionFactor));
844
-
944
+ if ($this->securityConfig['enableProofOfSpace'] ?? false) {
945
+ $spaceChallenge = ChallengeUtils::generateSpaceChallenge($context->clientIp, $nonce, $suspicionFactor, $context->path, $this->securityConfig);
946
+ $store->set("secret:{$nonce}", [
947
+ 'clientSecret' => $clientSecret,
948
+ 'suspicionScore' => $finalScore,
949
+ 'queries' => $spaceChallenge['queries'],
950
+ 'sizeMb' => $spaceChallenge['sizeMb'],
951
+ 'fingerprint' => RequestUtils::getCompositeDeviceHash($context),
952
+ 'originalPath' => $context->path,
953
+ ], $this->securityConfig['challengeTtl'] ?? 300);
954
+
955
+ if ($deviceData) {
956
+ $deviceData['lastChallengeNonce'] = $nonce;
957
+ $store->set("device:{$deviceId}", $deviceData); // @phpstan-ignore-line
958
+ }
959
+
960
+ if ($isApiRequest) {
961
+ $decision['body'] = [
962
+ 'challenge' => [
963
+ 'type' => 'pospace',
964
+ 'nonce' => $nonce,
965
+ 'clientSecret' => $clientSecret,
966
+ 'queries' => $spaceChallenge['queries'],
967
+ 'sizeMb' => $spaceChallenge['sizeMb'],
968
+ ]
969
+ ];
970
+ } else {
971
+ $page = ChallengeUtils::generateSpaceChallengePage($spaceChallenge, $clientSecret, $this->securityConfig);
972
+ $decision['body'] = $page;
973
+ }
974
+ return $decision;
975
+ }
976
+
845
977
  $cpuChallengeDetails = [
846
978
  'type' => 'cpu_target',
847
979
  'nonce' => $nonce,