public function testExpectedBehavior()
 {
     $treeA = new MerkleTree(new Node('a'), new Node('b'), new Node('c'), new Node('d'), new Node('e'));
     $this->assertEquals('6781891a87aa476454b74dc635c5cdebfc8f887438829ce2e81423f54906c058', $treeA->getRoot());
     $treeB = new MerkleTree(new Node('a'), new Node('b'), new Node('c'), new Node('d'), new Node('e'), new Node('e'), new Node('e'), new Node('e'));
     $this->assertEquals($treeA->getRoot(), $treeB->getRoot());
     return;
     $treeC = $treeA->getExpandedTree(new Node('e'), new Node('e'), new Node('e'));
     $this->assertEquals($treeA->getRoot(), $treeC->getRoot());
     $treeD = $treeA->getExpandedTree(new Node('f'), new Node('e'), new Node('e'));
     $this->assertNotEquals($treeA->getRoot(), $treeD->getRoot());
 }
Exemple #2
0
 /**
  * Return true if the Merkle roots match.
  *
  * Dear future security auditors: This is important.
  *
  * This employs challenge-response authentication:
  * @ref https://github.com/paragonie/airship/issues/13
  *
  * @param Channel $channel
  * @param MerkleTree $originalTree
  * @param TreeUpdate[] ...$updates
  * @return bool
  * @throws CouldNotUpdate
  */
 protected function verifyResponseWithPeers(Channel $channel, MerkleTree $originalTree, TreeUpdate ...$updates) : bool
 {
     $state = State::instance();
     $nodes = $this->updatesToNodes($updates);
     $tree = $originalTree->getExpandedTree(...$nodes);
     $maxUpdateIndex = \count($updates) - 1;
     $expectedRoot = $updates[$maxUpdateIndex]->getRoot();
     if (!\hash_equals($tree->getRoot(), $expectedRoot)) {
         // Calculated root did not match.
         self::$continuumLogger->store(LogLevel::EMERGENCY, 'Calculated Merkle root did not match the update.', [$tree->getRoot(), $expectedRoot]);
         throw new CouldNotUpdate(\__('Calculated Merkle root did not match the update.'));
     }
     if ($state->universal['auto-update']['ignore-peer-verification']) {
         // The user has expressed no interest in verification
         return true;
     }
     $peers = $channel->getPeerList();
     $numPeers = \count($peers);
     /**
      * These numbers are negotiable in future versions.
      *
      * If P is the set of trusted peer notaries (where ||P|| is the number
      * of trusted peer notaries):
      *
      * 1. At least 1 must return 'success'.
      * 2. At least ln(||P||) must return 'success'.
      * 3. At most e * ln(||P||) can timeout.
      * 4. If any peer disagrees with what we see, our
      *    result is discarded as invalid.
      *
      * The most harm a malicious peer can do is DoS if they
      * are selected.
      */
     $minSuccess = $channel->getAppropriatePeerSize();
     $maxFailure = (int) \min(\floor($minSuccess * M_E), $numPeers - 1);
     if ($maxFailure < 1) {
         $maxFailure = 1;
     }
     \Airship\secure_shuffle($peers);
     $success = $networkError = 0;
     /**
      * If any peers give a different answer, we're under attack.
      * If too many peers don't respond, assume they're being DDoS'd.
      * If enough peers respond in absolute agreement, we're good.
      */
     for ($i = 0; $i < $numPeers; ++$i) {
         try {
             if (!$this->checkWithPeer($peers[$i], $tree->getRoot())) {
                 // Merkle root mismatch? Abort.
                 return false;
             }
             ++$success;
         } catch (TransferException $ex) {
             self::$continuumLogger->store(LogLevel::EMERGENCY, 'A transfer exception occurred', \Airship\throwableToArray($ex));
             ++$networkError;
         }
         if ($success >= $minSuccess) {
             // We have enough good responses.
             return true;
         } elseif ($networkError >= $maxFailure) {
             // We can't give a confident response here.
             return false;
         }
     }
     self::$continuumLogger->store(LogLevel::EMERGENCY, 'We ran out of peers.', [$numPeers, $minSuccess, $maxFailure]);
     // Fail closed:
     return false;
 }