Bladeren bron

First commit

Brendan Abolivier 7 jaren geleden
commit
77df43511d

+ 1
- 0
.gitignore Bestand weergeven

@@ -0,0 +1 @@
1
+*.swp

+ 79
- 0
Macaroons/Caveat.php Bestand weergeven

@@ -0,0 +1,79 @@
1
+<?php
2
+
3
+namespace Macaroons;
4
+
5
+class Caveat
6
+{
7
+  private $caveat_id;
8
+  private $verification_id;
9
+  private $caveat_location;
10
+
11
+  public function __construct($caveatId, $verificationId = NULL, $caveatLocation = NULL)
12
+  {
13
+    $this->caveat_id       = $caveatId;
14
+    $this->verification_id = $verificationId;
15
+    $this->caveat_location = $caveatLocation;
16
+  }
17
+
18
+  public function getCaveatId()
19
+  {
20
+    return $this->caveat_id;
21
+  }
22
+
23
+  public function getCaveatLocation()
24
+  {
25
+    return $this->caveat_location;
26
+  }
27
+
28
+  public function getVerificationId()
29
+  {
30
+    return $this->verification_id;
31
+  }
32
+
33
+  public function setCaveatLocation($caveatLocation)
34
+  {
35
+    $this->caveat_location = $caveatLocation;
36
+  }
37
+
38
+  public function setVerificationId($verificationId)
39
+  {
40
+    $this->verification_id = $verificationId;
41
+  }
42
+
43
+  public function isFirstParty()
44
+  {
45
+    return $this->verification_id === NULL;
46
+  }
47
+
48
+  public function isThirdParty()
49
+  {
50
+    return !$this->isFirstParty();
51
+  }
52
+
53
+  public function toArray()
54
+  {
55
+    $caveatKeys = array('cid' => $this->getCaveatId());
56
+    if ($this->isThirdParty())
57
+    {
58
+      $caveatKeys = array_merge(
59
+                                $caveatKeys,
60
+                                array(
61
+                                      'vid' => $this->getVerificationId(),
62
+                                      'cl' => $this->getCaveatLocation()
63
+                                      )
64
+                                );
65
+    }
66
+    return $caveatKeys;
67
+  }
68
+
69
+  public function __toString()
70
+  {
71
+    $caveatAsArray = $this->toArray();
72
+    if ($this->isThirdParty())
73
+      $caveatAsArray['vid'] = Utils::hexlify($caveatAsArray['vid']);
74
+    return join("\n", array_map(function($key, $value) {
75
+      return "$key $value";
76
+    }, array_keys($caveatAsArray), $caveatAsArray));
77
+  }
78
+
79
+}

+ 7
- 0
Macaroons/Exceptions/CaveatUnsatisfiedException.php Bestand weergeven

@@ -0,0 +1,7 @@
1
+<?php
2
+
3
+namespace Macaroons\Exceptions;
4
+
5
+class CaveatUnsatisfiedException extends \Exception
6
+{
7
+}

+ 7
- 0
Macaroons/Exceptions/InvalidMacaroonKeyException.php Bestand weergeven

@@ -0,0 +1,7 @@
1
+<?php
2
+
3
+namespace Macaroons\Exceptions;
4
+
5
+class InvalidMacaroonKeyException extends \Exception
6
+{
7
+}

+ 7
- 0
Macaroons/Exceptions/SignatureMismatchException.php Bestand weergeven

@@ -0,0 +1,7 @@
1
+<?php
2
+
3
+namespace Macaroons\Exceptions;
4
+
5
+class SignatureMismatchException extends \Exception
6
+{
7
+}

+ 250
- 0
Macaroons/Macaroon.php Bestand weergeven

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

+ 66
- 0
Macaroons/Packet.php Bestand weergeven

@@ -0,0 +1,66 @@
1
+<?php
2
+
3
+namespace Macaroons;
4
+
5
+class Packet
6
+{
7
+  const PACKET_PREFIX_LENGTH = 4;
8
+  private $key;
9
+  private $data;
10
+  public function __construct($key = NULL, $data = NULL)
11
+  {
12
+    $this->key = $key;
13
+    $this->data = $data;
14
+  }
15
+
16
+  public function packetize(Array $packets)
17
+  {
18
+    // PHP 5.3 workaround
19
+    // $this isn't bound in anonymous functions
20
+    return join('',
21
+                    array_map(
22
+                                array($this, 'mapPacketsCallback'),
23
+                                array_keys($packets),
24
+                                $packets
25
+                              )
26
+                );
27
+  }
28
+
29
+  public function getKey()
30
+  {
31
+    return $this->key;
32
+  }
33
+
34
+  public function getData()
35
+  {
36
+    return $this->data;
37
+  }
38
+
39
+  private function mapPacketsCallback($key, $data)
40
+  {
41
+    return $this->encode($key, $data);
42
+  }
43
+
44
+  private function encode($key, $data)
45
+  {
46
+    $packetSize = self::PACKET_PREFIX_LENGTH + 2 + strlen($key) + strlen($data);
47
+    // packetSize can't be larger than 0xFFFF
48
+    if ($packetSize > pow(16, 4))
49
+      throw new \InvalidArgumentException('Data is too long for a binary packet.');
50
+
51
+    // hexadecimal representation with lowercase letters
52
+    $packetSizeHex = sprintf("%x", $packetSize);
53
+    $header = str_pad($packetSizeHex, 4, '0', STR_PAD_LEFT);
54
+    $packetContent = "$key $data\n";
55
+    $packet = $header . $packetContent;
56
+    return $packet;
57
+  }
58
+
59
+  public function decode($packet)
60
+  {
61
+    $packets = explode(' ', $packet);
62
+    $key = array_shift($packets);
63
+    $data = substr($packet, strlen($key) + 1);
64
+    return new Packet($key, $data);
65
+  }
66
+}

+ 87
- 0
Macaroons/Utils.php Bestand weergeven

@@ -0,0 +1,87 @@
1
+<?php
2
+
3
+namespace Macaroons;
4
+
5
+class Utils
6
+{
7
+  public static function hexlify($value)
8
+  {
9
+    return join('', array_map(function($byte){
10
+        return sprintf("%02X", $byte);
11
+      }, unpack('C*', $value)));
12
+  }
13
+
14
+  public static function unhexlify($value)
15
+  {
16
+    return pack('H*', $value);
17
+  }
18
+
19
+  public static function hmac($key, $data, $digest = 'sha256')
20
+  {
21
+    return hash_hmac($digest, $data, $key, true);
22
+  }
23
+
24
+  public static function generateDerivedKey($key)
25
+  {
26
+    return self::hmac('macaroons-key-generator', $key);
27
+  }
28
+
29
+  public static function truncateOrPad($str, $size = 32)
30
+  {
31
+    if (strlen($str) > $size)
32
+      return substr($str, 0, $size);
33
+    else if (strlen($str) < $size)
34
+      return str_pad($str, $size, "\0", STR_PAD_RIGHT);
35
+    return $str;
36
+  }
37
+
38
+  public static function startsWith($str, $prefix)
39
+  {
40
+    if (!(is_string($str) && is_string($prefix)))
41
+      throw new \InvalidArgumentException('Both arguments must be strings');
42
+    return substr($str, 0, strlen($prefix)) === $prefix;
43
+  }
44
+
45
+  public static function base64_strict_encode($data)
46
+  {
47
+    $data = str_replace("\r\n", '', base64_encode($data));
48
+    $data = str_replace("\r", '', $data);
49
+    return str_replace("\n", '', $data);
50
+  }
51
+
52
+  public static function base64_url_safe_encode($data)
53
+  {
54
+    $data = str_replace('+', '-', self::base64_strict_encode($data));
55
+    return str_replace('/', '_', $data);
56
+  }
57
+
58
+  public static function base64_url_safe_decode($data)
59
+  {
60
+    $data = str_replace('-', '+', $data);
61
+    $data = str_replace('_', '/', $data);
62
+    return base64_decode($data);
63
+  }
64
+
65
+  public static function base64_url_encode($data)
66
+  {
67
+    return str_replace('=', '', self::base64_url_safe_encode($data));
68
+  }
69
+
70
+  public static function base64_url_decode($data)
71
+  {
72
+    return self::base64_url_safe_decode(str_pad($data, (4 - (strlen($data) % 4)) % 4, '=', STR_PAD_RIGHT));
73
+  }
74
+
75
+  public static function signFirstPartyCaveat($signature, $predicate)
76
+  {
77
+    return self::hmac($signature, $predicate);
78
+  }
79
+
80
+  public static function signThirdPartyCaveat($signature, $verificationId, $caveatId)
81
+  {
82
+    $verification_id_hmac = self::hmac($signature, $verificationId);
83
+    $caveat_id_hmac       = self::hmac($signature, $caveatId);
84
+    $combined             = $verification_id_hmac . $caveat_id_hmac;
85
+    return self::hmac($signature, $combined);
86
+  }
87
+}

+ 226
- 0
Macaroons/Verifier.php Bestand weergeven

@@ -0,0 +1,226 @@
1
+<?php
2
+
3
+namespace Macaroons;
4
+
5
+use Macaroons\Exceptions\SignatureMismatchException;
6
+use Macaroons\Exceptions\CaveatUnsatisfiedException;
7
+
8
+class Verifier
9
+{
10
+  private $predicates = array();
11
+  private $callbacks = array();
12
+  private $calculatedSignature;
13
+
14
+  /**
15
+   * return predicates to verify
16
+   * @return Array|array
17
+   */
18
+  public function getPredicates()
19
+  {
20
+    return $this->predicates;
21
+  }
22
+
23
+  /**
24
+   * returns verifier callbacks
25
+   * @return Array|array
26
+   */
27
+  public function getCallbacks()
28
+  {
29
+    return $this->callbacks;
30
+  }
31
+
32
+  /**
33
+   * sets array of predicates
34
+   * @param Array $predicates
35
+   */
36
+  public function setPredicates(Array $predicates)
37
+  {
38
+    $this->predicates = $predicates;
39
+  }
40
+
41
+  /**
42
+   * set array of callbacks
43
+   * @param Array $callbacks
44
+   */
45
+  public function setCallbacks(Array $callbacks)
46
+  {
47
+    $this->callbacks = $callbacks;
48
+  }
49
+
50
+  /**
51
+   * adds a predicate to the verifier
52
+   * @param string
53
+   */
54
+  public function satisfyExact($predicate)
55
+  {
56
+    if (!isset($predicate))
57
+      throw new \InvalidArgumentException('Must provide predicate');
58
+    array_push($this->predicates, $predicate);
59
+  }
60
+
61
+  /**
62
+   * adds a callback to array of callbacks
63
+   * $callback can be anything that is callable including objects
64
+   * that implement __invoke
65
+   * See http://php.net/manual/en/language.types.callable.php for more details
66
+   * @param function|object|array
67
+   */
68
+  public function satisfyGeneral($callback)
69
+  {
70
+    if (!isset($callback))
71
+      throw new \InvalidArgumentException('Must provide a callback function');
72
+    if (!is_callable($callback))
73
+      throw new \InvalidArgumentException('Callback must be a function');
74
+    array_push($this->callbacks, $callback);
75
+  }
76
+
77
+  /**
78
+   * [verify description]
79
+   * @param  Macaroon $macaroon
80
+   * @param  string   $key
81
+   * @param  Array    $dischargeMacaroons
82
+   * @return boolean
83
+   */
84
+  public function verify(Macaroon $macaroon, $key, Array $dischargeMacaroons = array())
85
+  {
86
+    $key = Utils::generateDerivedKey($key);
87
+    return $this->verifyDischarge(
88
+                                  $macaroon,
89
+                                  $macaroon,
90
+                                  $key,
91
+                                  $dischargeMacaroons
92
+                                  );
93
+  }
94
+
95
+  /**
96
+   * [verifyDischarge description]
97
+   * @param  Macaroon    $rootMacaroon
98
+   * @param  Macaroon    $macaroon
99
+   * @param  string      $key
100
+   * @param  Array|array $dischargeMacaroons
101
+   * @return boolean|throws SignatureMismatchException
102
+   */
103
+  public function verifyDischarge(Macaroon $rootMacaroon, Macaroon $macaroon, $key, Array $dischargeMacaroons = array())
104
+  {
105
+    $this->calculatedSignature = Utils::hmac($key, $macaroon->getIdentifier());
106
+    $this->verifyCaveats($macaroon, $dischargeMacaroons);
107
+
108
+    if ($rootMacaroon != $macaroon)
109
+    {
110
+      $this->calculatedSignature = $rootMacaroon->bindSignature(strtolower(Utils::hexlify($this->calculatedSignature)));
111
+    }
112
+
113
+    $signature = Utils::unhexlify($macaroon->getSignature());
114
+    if ($this->signaturesMatch($this->calculatedSignature, $signature) === FALSE)
115
+    {
116
+      throw new SignatureMismatchException('Signatures do not match.');
117
+    }
118
+    return true;
119
+  }
120
+
121
+  /**
122
+   * verifies all first and third party caveats of macaroon are valid
123
+   * @param  Macaroon
124
+   * @param  Array
125
+   */
126
+  private function verifyCaveats(Macaroon $macaroon, Array $dischargeMacaroons = array())
127
+  {
128
+    foreach ($macaroon->getCaveats() as $caveat)
129
+    {
130
+      $caveatMet = false;
131
+      if ($caveat->isFirstParty())
132
+        $caveatMet = $this->verifyFirstPartyCaveat($caveat);
133
+      else if ($caveat->isThirdParty())
134
+        $caveatMet = $this->verifyThirdPartyCaveat($caveat, $macaroon, $dischargeMacaroons);
135
+      if (!$caveatMet)
136
+        throw new CaveatUnsatisfiedException("Caveat not met. Unable to satisfy: {$caveat->getCaveatId()}");
137
+    }
138
+  }
139
+
140
+  private function verifyFirstPartyCaveat(Caveat $caveat)
141
+  {
142
+    $caveatMet = false;
143
+    if (in_array($caveat->getCaveatId(), $this->predicates))
144
+      $caveatMet = true;
145
+    else
146
+    {
147
+      foreach ($this->callbacks as $callback)
148
+      {
149
+        if ($callback($caveat->getCaveatId()))
150
+          $caveatMet = true;
151
+      }
152
+    }
153
+    if ($caveatMet)
154
+      $this->calculatedSignature = Utils::signFirstPartyCaveat($this->calculatedSignature, $caveat->getCaveatId());
155
+
156
+    return $caveatMet;
157
+  }
158
+
159
+  private function verifyThirdPartyCaveat(Caveat $caveat, Macaroon $rootMacaroon, Array $dischargeMacaroons)
160
+  {
161
+    $caveatMet = false;
162
+
163
+    $dischargesMatchingCaveat = array_filter($dischargeMacaroons, function($discharge) use ($rootMacaroon, $caveat) {
164
+      return $discharge->getIdentifier() === $caveat->getCaveatId();
165
+    });
166
+
167
+    $caveatMacaroon = array_shift($dischargesMatchingCaveat);
168
+
169
+    if (!$caveatMacaroon)
170
+      throw new CaveatUnsatisfiedException("Caveat not met. No discharge macaroon found for identifier: {$caveat->getCaveatId()}");
171
+
172
+    $caveatKey = $this->extractCaveatKey($this->calculatedSignature, $caveat);
173
+    $caveatMacaroonVerifier = new Verifier();
174
+    $caveatMacaroonVerifier->setPredicates($this->predicates);
175
+    $caveatMacaroonVerifier->setCallbacks($this->callbacks);
176
+
177
+    $caveatMet = $caveatMacaroonVerifier->verifyDischarge(
178
+                                                          $rootMacaroon,
179
+                                                          $caveatMacaroon,
180
+                                                          $caveatKey,
181
+                                                          $dischargeMacaroons
182
+                                                          );
183
+    if ($caveatMet)
184
+    {
185
+      $this->calculatedSignature = Utils::signThirdPartyCaveat(
186
+                                                                $this->calculatedSignature,
187
+                                                                $caveat->getVerificationId(),
188
+                                                                $caveat->getCaveatId()
189
+                                                              );
190
+    }
191
+
192
+    return $caveatMet;
193
+  }
194
+
195
+  /**
196
+   * returns the derived key from the caveat verification id
197
+   * @param  string $signature
198
+   * @param  Caveat $caveat
199
+   * @return string
200
+   */
201
+  private function extractCaveatKey($signature, Caveat $caveat)
202
+  {
203
+    $verificationHash = $caveat->getVerificationId();
204
+    $nonce            = substr($verificationHash, 0, \Sodium\CRYPTO_SECRETBOX_NONCEBYTES);
205
+    $verificationId   = substr($verificationHash, \Sodium\CRYPTO_SECRETBOX_NONCEBYTES);
206
+    $key              = Utils::truncateOrPad($signature);
207
+    return \Sodium\crypto_secretbox_open($verificationId, $nonce, $key);
208
+  }
209
+
210
+  /**
211
+   * compares the calculated signature of a macaroon and the macaroon supplied
212
+   * by the client
213
+   * The user supplied string MUST be the second argument or this will leak
214
+   * the length of the actual signature
215
+   * @param  string $a known signature from our key and macaroon metadata
216
+   * @param  string $b signature from macaroon we are verifying (from the client)
217
+   * @return boolean
218
+   */
219
+  private function signaturesMatch($a, $b)
220
+  {
221
+    $ret = strlen($a) ^ strlen($b);
222
+    $ret |= array_sum(unpack("C*", $a^$b));
223
+
224
+    return !$ret;
225
+  }
226
+}

+ 198
- 0
auth.php Bestand weergeven

@@ -0,0 +1,198 @@
1
+<?php
2
+// This file is part of Moodle - http://moodle.org/
3
+//
4
+// Moodle is free software: you can redistribute it and/or modify
5
+// it under the terms of the GNU General Public License as published by
6
+// the Free Software Foundation, either version 3 of the License, or
7
+// (at your option) any later version.
8
+//
9
+// Moodle is distributed in the hope that it will be useful,
10
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+// GNU General Public License for more details.
13
+//
14
+// You should have received a copy of the GNU General Public License
15
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
+
17
+/**
18
+ * Anobody can login with any password.
19
+ *
20
+ * @package auth_macaroons
21
+ * @author Brendan Abolivier
22
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
23
+ */
24
+
25
+defined('MOODLE_INTERNAL') || die();
26
+
27
+require_once($CFG->libdir.'/authlib.php');
28
+
29
+require_once($CFG->dirroot.'/auth/macaroons/Macaroons/Macaroon.php');
30
+require_once($CFG->dirroot.'/auth/macaroons/Macaroons/Caveat.php');
31
+require_once($CFG->dirroot.'/auth/macaroons/Macaroons/Packet.php');
32
+require_once($CFG->dirroot.'/auth/macaroons/Macaroons/Utils.php');
33
+require_once($CFG->dirroot.'/auth/macaroons/Macaroons/Verifier.php');
34
+require_once($CFG->dirroot.'/auth/macaroons/Macaroons/Exceptions/CaveatUnsatisfiedException.php');
35
+require_once($CFG->dirroot.'/auth/macaroons/Macaroons/Exceptions/InvalidMacaroonKeyException.php');
36
+require_once($CFG->dirroot.'/auth/macaroons/Macaroons/Exceptions/SignatureMismatchException.php');
37
+
38
+use Macaroons\Macaroon;
39
+use Macaroons\Verifier;
40
+
41
+
42
+/**
43
+ * Plugin for no authentication.
44
+ */
45
+class auth_plugin_macaroons extends auth_plugin_base {
46
+
47
+	/**
48
+	 * Constructor.
49
+	 */
50
+	public function __construct() {
51
+		$this->authtype = 'macaroons';
52
+	}
53
+
54
+	function loginpage_hook() {
55
+		global $message;
56
+		$message = "";
57
+		if(!empty($_COOKIE['das-macaroon'])) {
58
+			try {
59
+				$m = Macaroon::deserialize($_COOKIE['das-macaroon']);
60
+				$frm = new stdClass();
61
+				$frm->username = $m->getIdentifier();
62
+				$frm->password = 'passwdMacaroons';
63
+				$v = new Verifier();
64
+				$v->setCallbacks([
65
+					function($a) {
66
+						return !strcmp($a, "status = student");
67
+					}
68
+				]);
69
+				if($v->verify($m, "pocsecret")) {
70
+					$frm = new stdClass();
71
+					$frm->username = $m->getIdentifier();
72
+					$frm->password = 'passwdMacaroons';
73
+				}
74
+			} catch(Exception $e) {
75
+				$message = $e->getMessage();
76
+			}
77
+			authenticate_user_login($frm->username, sesskey());
78
+		}
79
+	}
80
+
81
+	/**
82
+	 * Old syntax of class constructor. Deprecated in PHP7.
83
+	 *
84
+	 * @deprecated since Moodle 3.1
85
+	 */
86
+	public function auth_plugin_macaroons() {
87
+		debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
88
+		self::__construct();
89
+	}
90
+
91
+	/**
92
+	 * Returns true if the username and password work or don't exist and false
93
+	 * if the user exists and the password is wrong.
94
+	 *
95
+	 * @param string $username The username
96
+	 * @param string $password The password
97
+	 * @return bool Authentication success or failure.
98
+	 */
99
+	function user_login ($username, $password) {
100
+		global $message;
101
+		if(!empty($message)) {
102
+			return false;
103
+		} elseif(!empty($_COOKIE['das-macaroon'])) {
104
+			return true;
105
+		}
106
+	}
107
+
108
+	/**
109
+	 * Updates the user's password.
110
+	 *
111
+	 * called when the user password is updated.
112
+	 *
113
+	 * @param  object  $user		User table object
114
+	 * @param  string  $newpassword Plaintext password
115
+	 * @return boolean result
116
+	 *
117
+	 */
118
+	function user_update_password($user, $newpassword) {
119
+		$user = get_complete_user_data('id', $user->id);
120
+		// This will also update the stored hash to the latest algorithm
121
+		// if the existing hash is using an out-of-date algorithm (or the
122
+		// legacy md5 algorithm).
123
+		return update_internal_user_password($user, $newpassword);
124
+	}
125
+
126
+	function prevent_local_passwords() {
127
+		return false;
128
+	}
129
+
130
+	/**
131
+	 * Returns true if this authentication plugin is 'internal'.
132
+	 *
133
+	 * @return bool
134
+	 */
135
+	function is_internal() {
136
+		return false;
137
+	}
138
+
139
+	/**
140
+	 * Returns true if this authentication plugin can change the user's
141
+	 * password.
142
+	 *
143
+	 * @return bool
144
+	 */
145
+	function can_change_password() {
146
+		return true;
147
+	}
148
+
149
+	/**
150
+	 * Returns the URL for changing the user's pw, or empty if the default can
151
+	 * be used.
152
+	 *
153
+	 * @return moodle_url
154
+	 */
155
+	function change_password_url() {
156
+		return null;
157
+	}
158
+
159
+	/**
160
+	 * Returns true if plugin allows resetting of internal password.
161
+	 *
162
+	 * @return bool
163
+	 */
164
+	function can_reset_password() {
165
+		return true;
166
+	}
167
+
168
+	/**
169
+	 * Returns true if plugin can be manually set.
170
+	 *
171
+	 * @return bool
172
+	 */
173
+	function can_be_manually_set() {
174
+		return true;
175
+	}
176
+
177
+	/**
178
+	 * Prints a form for configuring this authentication plugin.
179
+	 *
180
+	 * This function is called from admin/auth.php, and outputs a full page with
181
+	 * a form for configuring this plugin.
182
+	 *
183
+	 * @param array $page An object containing all the data for this page.
184
+	function config_form($config, $err, $user_fields) {
185
+		include "config.html";
186
+	}
187
+	 */
188
+
189
+	/**
190
+	 * Processes and stores configuration data for this authentication plugin.
191
+	 */
192
+	function process_config($config) {
193
+		return true;
194
+	}
195
+
196
+}
197
+
198
+

+ 26
- 0
lang/en/auth_macaroons.php Bestand weergeven

@@ -0,0 +1,26 @@
1
+<?php
2
+// This file is part of Moodle - http://moodle.org/
3
+//
4
+// Moodle is free software: you can redistribute it and/or modify
5
+// it under the terms of the GNU General Public License as published by
6
+// the Free Software Foundation, either version 3 of the License, or
7
+// (at your option) any later version.
8
+//
9
+// Moodle is distributed in the hope that it will be useful,
10
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+// GNU General Public License for more details.
13
+//
14
+// You should have received a copy of the GNU General Public License
15
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
+
17
+/**
18
+ * Strings for component 'auth_macaroons', language 'en'.
19
+ *
20
+ * @package   auth_macaroons
21
+ * @copyright 2017 onwards Brendan Abolivier
22
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
+ */
24
+
25
+$string['auth_macaroonsdescription'] = 'Macaroons usage for authentication';
26
+$string['pluginname'] = 'Macaroons';

+ 29
- 0
version.php Bestand weergeven

@@ -0,0 +1,29 @@
1
+<?php
2
+// This file is part of Moodle - http://moodle.org/
3
+//
4
+// Moodle is free software: you can redistribute it and/or modify
5
+// it under the terms of the GNU General Public License as published by
6
+// the Free Software Foundation, either version 3 of the License, or
7
+// (at your option) any later version.
8
+//
9
+// Moodle is distributed in the hope that it will be useful,
10
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+// GNU General Public License for more details.
13
+//
14
+// You should have received a copy of the GNU General Public License
15
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
+
17
+/**
18
+ * Version information
19
+ *
20
+ * @package    auth_macaroons
21
+ * @copyright  2017 Brendan Abolivier
22
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
+ */
24
+
25
+defined('MOODLE_INTERNAL') || die();
26
+
27
+$plugin->version   = 2016120500;        // The current plugin version (Date: YYYYMMDDXX)
28
+$plugin->requires  = 2016112900;        // Requires this Moodle version
29
+$plugin->component = 'auth_macaroons';       // Full name of the plugin (used for diagnostics)