Moodle authentication plugin for Macaroons

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. <?php
  2. namespace Macaroons;
  3. use Macaroons\Exceptions\InvalidMacaroonKeyException;
  4. class Macaroon
  5. {
  6. private $id;
  7. private $location;
  8. private $signature;
  9. private $caveats = array();
  10. public function __construct($key, $identifier, $location)
  11. {
  12. $this->identifier = $identifier;
  13. $this->location = $location;
  14. $this->signature = $this->initialSignature($key, $identifier);
  15. }
  16. public function getIdentifier()
  17. {
  18. return $this->identifier;
  19. }
  20. public function getLocation()
  21. {
  22. return $this->location;
  23. }
  24. public function getSignature()
  25. {
  26. return strtolower( Utils::hexlify( $this->signature ) );
  27. }
  28. public function getFirstPartyCaveats()
  29. {
  30. return array_filter($this->caveats, function(Caveat $caveat){
  31. return $caveat->isFirstParty();
  32. });
  33. }
  34. public function getThirdPartyCaveats()
  35. {
  36. return array_filter($this->caveats, function(Caveat $caveat){
  37. return $caveat->isThirdParty();
  38. });
  39. }
  40. public function getCaveats()
  41. {
  42. return $this->caveats;
  43. }
  44. public function setSignature($signature)
  45. {
  46. if (!isset($signature))
  47. throw new \InvalidArgumentException('Must supply updated signature');
  48. $this->signature = $signature;
  49. }
  50. public function setCaveats(Array $caveats)
  51. {
  52. $this->caveats = $caveats;
  53. }
  54. public function addFirstPartyCaveat($predicate)
  55. {
  56. array_push($this->caveats, new Caveat($predicate));
  57. $this->signature = Utils::signFirstPartyCaveat($this->signature, $predicate);
  58. }
  59. public function addThirdPartyCaveat($caveatKey, $caveatId, $caveatLocation)
  60. {
  61. $derivedCaveatKey = Utils::truncateOrPad( Utils::generateDerivedKey($caveatKey) );
  62. $truncatedOrPaddedSignature = Utils::truncateOrPad( $this->signature );
  63. // Generate cipher using libsodium
  64. $nonce = \Sodium\randombytes_buf(\Sodium\CRYPTO_SECRETBOX_NONCEBYTES);
  65. $verificationId = $nonce . \Sodium\crypto_secretbox($derivedCaveatKey, $nonce, $truncatedOrPaddedSignature);
  66. array_push($this->caveats, new Caveat($caveatId, $verificationId, $caveatLocation));
  67. $this->signature = Utils::signThirdPartyCaveat($this->signature, $verificationId, $caveatId);
  68. \Sodium\memzero($caveatKey);
  69. \Sodium\memzero($derivedCaveatKey);
  70. \Sodium\memzero($caveatId);
  71. }
  72. /**
  73. * [prepareForRequest description]
  74. * @param Macaroon $macaroon
  75. * @return Macaroon bound Macaroon (protected discharge)
  76. */
  77. public function prepareForRequest(Macaroon $macaroon)
  78. {
  79. $boundMacaroon = clone $macaroon;
  80. $boundMacaroon->setSignature($this->bindSignature($macaroon->getSignature()));
  81. return $boundMacaroon;
  82. }
  83. /**
  84. * [bindSignature description]
  85. * @param string $signature
  86. * @return string
  87. */
  88. public function bindSignature($signature)
  89. {
  90. $key = Utils::truncateOrPad("\0");
  91. $currentSignatureHash = Utils::hmac($key, Utils::unhexlify($this->getSignature()));
  92. $newSignatureHash = Utils::hmac($key, Utils::unhexlify($signature));
  93. return Utils::hmac($key, $currentSignatureHash . $newSignatureHash);
  94. }
  95. public function inspect()
  96. {
  97. $str = "location {$this->location}\n";
  98. $str .= "identifier {$this->identifier}\n";
  99. foreach ($this->caveats as $caveat)
  100. {
  101. $str .= "$caveat\n";
  102. }
  103. $str .= "signature {$this->getSignature()}";
  104. return $str;
  105. }
  106. private function initialSignature($key, $identifier)
  107. {
  108. return Utils::hmac( Utils::generateDerivedKey($key), $identifier);
  109. }
  110. // TODO: Move these into a separate object
  111. public function serialize()
  112. {
  113. $p = new Packet();
  114. $s = $p->packetize(
  115. array(
  116. 'location' => $this->location,
  117. 'identifier' => $this->identifier
  118. )
  119. );
  120. foreach ($this->caveats as $caveat)
  121. {
  122. $caveatKeys = array(
  123. 'cid' => $caveat->getCaveatId()
  124. );
  125. if ($caveat->getVerificationId() && $caveat->getCaveatLocation())
  126. {
  127. $caveatKeys = array_merge(
  128. $caveatKeys,
  129. array(
  130. 'vid' => $caveat->getVerificationId(),
  131. 'cl' => $caveat->getCaveatLocation()
  132. )
  133. );
  134. }
  135. $p = new Packet();
  136. $s = $s . $p->packetize($caveatKeys);
  137. }
  138. $p = new Packet();
  139. $s = $s . $p->packetize(array('signature' => $this->signature));
  140. return Utils::base64_url_encode($s);
  141. }
  142. public static function deserialize($serialized)
  143. {
  144. $location = NULL;
  145. $identifier = NULL;
  146. $signature = NULL;
  147. $caveats = array();
  148. $decoded = Utils::base64_url_decode($serialized);
  149. $index = 0;
  150. while ($index < strlen($decoded))
  151. {
  152. // TOOD: Replace 4 with PACKET_PREFIX_LENGTH
  153. $packetLength = hexdec(substr($decoded, $index, 4));
  154. $packetDataStart = $index + 4;
  155. $strippedPacket = substr($decoded, $packetDataStart, $packetLength - 5);
  156. $packet = new Packet();
  157. $packet = $packet->decode($strippedPacket);
  158. switch($packet->getKey())
  159. {
  160. case 'location':
  161. $location = $packet->getData();
  162. break;
  163. case 'identifier':
  164. $identifier = $packet->getData();
  165. break;
  166. case 'signature':
  167. $signature = $packet->getData();
  168. break;
  169. case 'cid':
  170. array_push($caveats, new Caveat($packet->getData()));
  171. break;
  172. case 'vid':
  173. $caveat = $caveats[ count($caveats) - 1 ];
  174. $caveat->setVerificationId($packet->getData());
  175. break;
  176. case 'cl':
  177. $caveat = $caveats[ count($caveats) - 1 ];
  178. $caveat->setCaveatLocation($packet->getData());
  179. break;
  180. default:
  181. throw new InvalidMacaroonKeyException('Invalid key in binary macaroon. Macaroon may be corrupted.');
  182. break;
  183. }
  184. $index = $index + $packetLength;
  185. }
  186. $m = new Macaroon('no_key', $identifier, $location);
  187. $m->setCaveats($caveats);
  188. $m->setSignature($signature);
  189. return $m;
  190. }
  191. public function toJSON()
  192. {
  193. return json_encode(array(
  194. 'location' => $this->location,
  195. 'identifier' => $this->identifier,
  196. 'caveats' => array_map(function(Caveat $caveat){
  197. $caveatAsArray = $caveat->toArray();
  198. if ($caveat->isThirdParty())
  199. $caveatAsArray['vid'] = Utils::hexlify($caveatAsArray['vid']);
  200. return $caveatAsArray;
  201. }, $this->getCaveats()),
  202. 'signature' => $this->getSignature()
  203. ));
  204. }
  205. public static function fromJSON($serialized)
  206. {
  207. $data = json_decode($serialized);
  208. $location = $data->location;
  209. $identifier = $data->identifier;
  210. $signature = $data->signature;
  211. $m = new Macaroon(
  212. 'no_key',
  213. $identifier,
  214. $location
  215. );
  216. $caveats = array_map(function(stdClass $data){
  217. $caveatId = $data->cid;
  218. $verificationId = $data->vid;
  219. $caveatLocation = $data->cl;
  220. return new Caveat($caveatId, $verificationId, $caveatLocation);
  221. }, $data->caveats);
  222. $m->setCaveats($caveats);
  223. $m->setSignature(Utils::unhexlify($signature));
  224. return $m;
  225. }
  226. }