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;
- }
- }
|