| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 | <?php
namespace Macaroons;
use Macaroons\Exceptions\SignatureMismatchException;
use Macaroons\Exceptions\CaveatUnsatisfiedException;
class Verifier
{
  private $predicates = array();
  private $callbacks = array();
  private $calculatedSignature;
  /**
   * return predicates to verify
   * @return Array|array
   */
  public function getPredicates()
  {
    return $this->predicates;
  }
  /**
   * returns verifier callbacks
   * @return Array|array
   */
  public function getCallbacks()
  {
    return $this->callbacks;
  }
  /**
   * sets array of predicates
   * @param Array $predicates
   */
  public function setPredicates(Array $predicates)
  {
    $this->predicates = $predicates;
  }
  /**
   * set array of callbacks
   * @param Array $callbacks
   */
  public function setCallbacks(Array $callbacks)
  {
    $this->callbacks = $callbacks;
  }
  /**
   * adds a predicate to the verifier
   * @param string
   */
  public function satisfyExact($predicate)
  {
    if (!isset($predicate))
      throw new \InvalidArgumentException('Must provide predicate');
    array_push($this->predicates, $predicate);
  }
  /**
   * adds a callback to array of callbacks
   * $callback can be anything that is callable including objects
   * that implement __invoke
   * See http://php.net/manual/en/language.types.callable.php for more details
   * @param function|object|array
   */
  public function satisfyGeneral($callback)
  {
    if (!isset($callback))
      throw new \InvalidArgumentException('Must provide a callback function');
    if (!is_callable($callback))
      throw new \InvalidArgumentException('Callback must be a function');
    array_push($this->callbacks, $callback);
  }
  /**
   * [verify description]
   * @param  Macaroon $macaroon
   * @param  string   $key
   * @param  Array    $dischargeMacaroons
   * @return boolean
   */
  public function verify(Macaroon $macaroon, $key, Array $dischargeMacaroons = array())
  {
    $key = Utils::generateDerivedKey($key);
    return $this->verifyDischarge(
                                  $macaroon,
                                  $macaroon,
                                  $key,
                                  $dischargeMacaroons
                                  );
  }
  /**
   * [verifyDischarge description]
   * @param  Macaroon    $rootMacaroon
   * @param  Macaroon    $macaroon
   * @param  string      $key
   * @param  Array|array $dischargeMacaroons
   * @return boolean|throws SignatureMismatchException
   */
  public function verifyDischarge(Macaroon $rootMacaroon, Macaroon $macaroon, $key, Array $dischargeMacaroons = array())
  {
    $this->calculatedSignature = Utils::hmac($key, $macaroon->getIdentifier());
    $this->verifyCaveats($macaroon, $dischargeMacaroons);
    if ($rootMacaroon != $macaroon)
    {
      $this->calculatedSignature = $rootMacaroon->bindSignature(strtolower(Utils::hexlify($this->calculatedSignature)));
    }
    $signature = Utils::unhexlify($macaroon->getSignature());
    if ($this->signaturesMatch($this->calculatedSignature, $signature) === FALSE)
    {
      throw new SignatureMismatchException('Signatures do not match.');
    }
    return true;
  }
  /**
   * verifies all first and third party caveats of macaroon are valid
   * @param  Macaroon
   * @param  Array
   */
  private function verifyCaveats(Macaroon $macaroon, Array $dischargeMacaroons = array())
  {
    foreach ($macaroon->getCaveats() as $caveat)
    {
      $caveatMet = false;
      if ($caveat->isFirstParty())
        $caveatMet = $this->verifyFirstPartyCaveat($caveat);
      else if ($caveat->isThirdParty())
        $caveatMet = $this->verifyThirdPartyCaveat($caveat, $macaroon, $dischargeMacaroons);
      if (!$caveatMet)
        throw new CaveatUnsatisfiedException("Caveat not met. Unable to satisfy: {$caveat->getCaveatId()}");
    }
  }
  private function verifyFirstPartyCaveat(Caveat $caveat)
  {
    $caveatMet = false;
    if (in_array($caveat->getCaveatId(), $this->predicates))
      $caveatMet = true;
    else
    {
      foreach ($this->callbacks as $callback)
      {
        if ($callback($caveat->getCaveatId()))
          $caveatMet = true;
      }
    }
    if ($caveatMet)
      $this->calculatedSignature = Utils::signFirstPartyCaveat($this->calculatedSignature, $caveat->getCaveatId());
    return $caveatMet;
  }
  private function verifyThirdPartyCaveat(Caveat $caveat, Macaroon $rootMacaroon, Array $dischargeMacaroons)
  {
    $caveatMet = false;
    $dischargesMatchingCaveat = array_filter($dischargeMacaroons, function($discharge) use ($rootMacaroon, $caveat) {
      return $discharge->getIdentifier() === $caveat->getCaveatId();
    });
    $caveatMacaroon = array_shift($dischargesMatchingCaveat);
    if (!$caveatMacaroon)
      throw new CaveatUnsatisfiedException("Caveat not met. No discharge macaroon found for identifier: {$caveat->getCaveatId()}");
    $caveatKey = $this->extractCaveatKey($this->calculatedSignature, $caveat);
    $caveatMacaroonVerifier = new Verifier();
    $caveatMacaroonVerifier->setPredicates($this->predicates);
    $caveatMacaroonVerifier->setCallbacks($this->callbacks);
    $caveatMet = $caveatMacaroonVerifier->verifyDischarge(
                                                          $rootMacaroon,
                                                          $caveatMacaroon,
                                                          $caveatKey,
                                                          $dischargeMacaroons
                                                          );
    if ($caveatMet)
    {
      $this->calculatedSignature = Utils::signThirdPartyCaveat(
                                                                $this->calculatedSignature,
                                                                $caveat->getVerificationId(),
                                                                $caveat->getCaveatId()
                                                              );
    }
    return $caveatMet;
  }
  /**
   * returns the derived key from the caveat verification id
   * @param  string $signature
   * @param  Caveat $caveat
   * @return string
   */
  private function extractCaveatKey($signature, Caveat $caveat)
  {
    $verificationHash = $caveat->getVerificationId();
    $nonce            = substr($verificationHash, 0, \Sodium\CRYPTO_SECRETBOX_NONCEBYTES);
    $verificationId   = substr($verificationHash, \Sodium\CRYPTO_SECRETBOX_NONCEBYTES);
    $key              = Utils::truncateOrPad($signature);
    return \Sodium\crypto_secretbox_open($verificationId, $nonce, $key);
  }
  /**
   * compares the calculated signature of a macaroon and the macaroon supplied
   * by the client
   * The user supplied string MUST be the second argument or this will leak
   * the length of the actual signature
   * @param  string $a known signature from our key and macaroon metadata
   * @param  string $b signature from macaroon we are verifying (from the client)
   * @return boolean
   */
  private function signaturesMatch($a, $b)
  {
    $ret = strlen($a) ^ strlen($b);
    $ret |= array_sum(unpack("C*", $a^$b));
    return !$ret;
  }
}
 |