Beispiel #1
1
 /**
  * @param \ParagonIE\AsgardClient\Structures\MerkleTree $tree
  * @param string $prevhash
  * @param string $nexthash
  */
 public function __construct(MerkleTree $tree, $prevhash = null, $nexthash = null)
 {
     $this->merkleTree = $tree;
     $this->currentHash = $tree->getRoot();
     $this->previousHash = $prevhash;
     $this->nextHash = $nexthash;
     if (empty($prevhash)) {
         $this->tailHash = $this->currentHash;
     } else {
         $this->tailHash = \Sodium::crypto_generichash(\bin2hex($this->previousHash) . \bin2hex($this->currentHash));
     }
 }
Beispiel #2
0
 public function testPersonalizedHash()
 {
     $treeA = new MerkleTree(new Node('a'), new Node('b'), new Node('c'), new Node('d'), new Node('e'));
     $this->assertSame('6781891a87aa476454b74dc635c5cdebfc8f887438829ce2e81423f54906c058', $treeA->getRoot());
     $treeA->setPersonalizationString('Halite unit test framework');
     $this->assertSame('e912ee25c680b0e3ee30b52eec0f0d79b502e15c9091c19cec7afc3115260b78', $treeA->getRoot());
 }
 public function testCompat()
 {
     $treeA = new MerkleTree(new Node('a'), new Node('b'), new Node('c'), new Node('d'));
     $treeB = new TrimmedMerkleTree(new Node('a'), new Node('b'), new Node('c'), new Node('d'));
     $this->assertSame($treeA->getRoot(), $treeB->getRoot());
     $personal = \random_bytes(32);
     $treeA->setPersonalizationString($personal);
     $treeB->setPersonalizationString($personal);
     $this->assertSame($treeA->getRoot(), $treeB->getRoot());
 }
 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());
 }
Beispiel #5
0
 /**
  * Get key updates from the channel
  *
  * @param MerkleTree $tree
  * @return Node[]
  */
 protected function getKeyUpdates(MerkleTree $tree) : array
 {
     $newNodes = [];
     foreach ($this->getChannelUpdates($tree->getRoot()) as $new) {
         $newNode = new Node($new['data']);
         $tree = $tree->getExpandedTree($newNode);
         // Verify that we've calculated the same Merkle root for each new leaf:
         if (\hash_equals($new['root'], $tree->getRoot())) {
             // Attempt to store the update (and create/revoke copies of the public keys):
             if ($this->storeUpdate($new)) {
                 $newNodes[] = $newNode;
             }
         }
     }
     if (\count($newNodes) > 0) {
         $this->notifyPeersOfNewUpdate();
     }
     return $newNodes;
 }
Beispiel #6
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;
 }