ILIAS  release_8 Revision v8.19
All Data Structures Namespaces Files Functions Variables Modules Pages
FirebaseClient.php
Go to the documentation of this file.
1 <?php
2 
20 
26 
35 {
39  public const SUPPORTED_ALGORITHMS = array('RS256', 'RS384', 'RS512');
40 
41  private ?string $jwtString = null;
42  private ?array $jwtHeaders = null;
43  private ?array $jwtPayload = null;
44  private static ?array $lastHeaders = null;
45  private static ?array $lastPayload = null;
46 
52  public static function getSupportedAlgorithms(): array
53  {
54  return self::SUPPORTED_ALGORITHMS;
55  }
56 
62  public function hasJwt(): bool
63  {
64  return !empty($this->jwtString);
65  }
66 
72  public function isEncrypted(): bool
73  {
74  return false; // Not supported by this client
75  }
76 
83  public function load(string $jwtString, string $privateKey = null): bool
84  {
85  $sections = explode('.', $jwtString);
86  $ok = count($sections) === 3;
87  if ($ok) {
88  $headers = json_decode(JWT::urlsafeB64Decode($sections[0]), true); //changed
89  $payload = json_decode(JWT::urlsafeB64Decode($sections[1]), true); //changed
90  $ok = !is_null($headers) && !is_null($payload);
91  }
92  if ($ok) {
93  $this->jwtString = $jwtString;
94  $this->jwtHeaders = $headers;
95  $this->jwtPayload = $payload;
96  } else {
97  $this->jwtString = null;
98  $this->jwtHeaders = null;
99  $this->jwtPayload = null;
100  }
101 
102  return $ok;
103  }
104 
110  public function getJweHeaders(): array
111  {
112  return array(); // Encryption not supported by this client
113  }
114 
120  public function hasHeader(string $name): bool
121  {
122  return !empty($this->jwtHeaders) && isset($this->jwtHeaders[$name]); //changed
123  }
124 
131  public function getHeader(string $name, string $defaultValue = null): string
132  {
133  if ($this->hasHeader($name)) {
134  $value = $this->jwtHeaders[$name]; //changed
135  } else {
136  $value = $defaultValue;
137  }
138 
139  return $value;
140  }
141 
147  public function getHeaders(): array
148  {
149  return $this->jwtHeaders;
150  }
151 
157  public static function getLastHeaders(): array
158  {
159  return self::$lastHeaders;
160  }
161 
167  public function hasClaim(string $name): bool
168  {
169  return !empty($this->jwtPayload) && isset($this->jwtPayload[$name]); //changed
170  }
171 
178  public function getClaim(string $name, string $defaultValue = null)
179  {
180  if ($this->hasClaim($name)) {
181  $value = $this->jwtPayload[$name]; //changed
182  } else {
183  $value = $defaultValue;
184  }
185 
186  return $value;
187  }
188 
194  public function getPayload(): array
195  {
196  return $this->jwtPayload;
197  }
198 
204  public static function getLastPayload(): array
205  {
206  return self::$lastPayload;
207  }
208 
215  public function verify(string $publicKey, string $jku = null): bool
216  {
217  $ok = false;
218  $hasPublicKey = !empty($publicKey);
219  if ($hasPublicKey) {
220  if (is_string($publicKey)) {
221  $json = json_decode($publicKey, true);
222  if (!is_null($json)) {
223  try {
224  $jwks = array('keys' => array($json));
225  $publicKey = static::parseKeySet($jwks);
226  } catch (\Exception $e) {
227  }
228  } else {
229  $publicKey = new Key($publicKey, $this->getHeader('alg'));
230  }
231  }
232  } elseif (!empty($jku)) {
233  $publicKey = $this->fetchPublicKey($jku);
234  }
236  $retry = false;
237 
238  do {
239  try {
240  JWT::decode($this->jwtString, $publicKey);
241  $ok = true;
242  } catch (\Exception $e) {
243  Util::logError($e->getMessage());
244  if ($retry) {
245  $retry = false;
246  } elseif ($hasPublicKey && !empty($jku)) {
247  try {
248  $publicKey = $this->fetchPublicKey($jku);
249  $retry = true;
250  } catch (\Exception $e) {
251  }
252  }
253  }
254  } while (!$ok && $retry);
255 
256  return $ok;
257  }
258 
270  public static function sign(
271  array $payload,
272  string $signatureMethod,
273  string $privateKey,
274  string $kid = null,
275  string $jku = null,
276  string $encryptionMethod = null,
277  string $publicKey = null
278  ): string {
279  if (!empty($encryptionMethod)) {
280  $errorMessage = 'Encrypted tokens not supported by the Firebase JWT client';
281  Util::logError($errorMessage);
282  throw new \Exception($errorMessage);
283  }
284  $jwtString = JWT::encode($payload, $privateKey, $signatureMethod, $kid);
285  $sections = explode('.', $jwtString);
286  self::$lastHeaders = json_decode(JWT::urlsafeB64Decode($sections[0]));
287  self::$lastPayload = json_decode(JWT::urlsafeB64Decode($sections[1]));
288 
289  return $jwtString;
290  }
291 
297  public static function generateKey(string $signatureMethod = 'RS256'): ?string
298  {
299  $privateKey = null;
300  switch ($signatureMethod) {
301  case 'RS512':
302  $size = 4096;
303  break;
304  case 'RS384':
305  $size = 3072;
306  break;
307  default:
308  $size = 2048;
309  break;
310  }
311  $config = array(
312  "private_key_bits" => $size,
313  "private_key_type" => OPENSSL_KEYTYPE_RSA
314  );
315  $res = openssl_pkey_new($config);
316  if (openssl_pkey_export($res, $privateKey)) {
317  $privateKey = str_replace('-----BEGIN PRIVATE KEY-----', '-----BEGIN RSA PRIVATE KEY-----', $privateKey);
318  $privateKey = str_replace('-----END PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----', $privateKey);
319  }
320 
321  return $privateKey;
322  }
323 
329  public static function getPublicKey(string $privateKey): string
330  {
331  $publicKey = null;
332  $res = openssl_pkey_get_private($privateKey);
333  if ($res !== false) {
334  $details = openssl_pkey_get_details($res);
335  $publicKey = $details['key'];
336  }
337 
338  return $publicKey;
339  }
340 
348  public static function getJWKS(string $pemKey, string $signatureMethod, string $kid = null): array
349  {
350  $keys['keys'] = array();
351  $res = openssl_pkey_get_private($pemKey);
352  if ($res === false) {
353  $res = openssl_pkey_get_public($pemKey);
354  }
355  if ($res !== false) {
356  $details = openssl_pkey_get_details($res);
357  $key = [
358  'kty' => 'RSA',
359  'n' => JWT::urlsafeB64Encode($details['rsa']['n']),
360  'e' => JWT::urlsafeB64Encode($details['rsa']['e']),
361  'alg' => $signatureMethod,
362  'use' => 'sig'
363  ];
364  if (!empty($kid)) {
365  $key['kid'] = $kid;
366  }
367  $keys['keys'][] = $key;
368  }
369 
370  return $keys;
371  }
372 
373  ###
374  ### PRIVATE METHODS
375  ###
376 
382  private function fetchPublicKey(string $jku): array
383  {
384  $publicKey = array();
385  $http = new HttpMessage($jku);
386  if ($http->send()) {
387  $keys = json_decode($http->response, true);
388  $publicKey = static::parseKeySet($keys);
389  }
390  return $publicKey;
391  }
392 
403  private static function parseKeySet(array $jwks): array
404  {
405  $keys = array();
406  if (!isset($jwks['keys'])) {
407  throw new \UnexpectedValueException('"keys" member must exist in the JWK Set');
408  }
409  if (empty($jwks['keys'])) {
410  throw new \InvalidArgumentException('JWK Set did not contain any keys');
411  }
412 
413  foreach ($jwks['keys'] as $k => $v) {
414  if (!empty($v['alg'])) {
415  $kid = isset($v['kid']) ? $v['kid'] : $k;
416  if ($key = JWK::parseKey($v)) {
417  $keys[$kid] = $key; //changed from
418  //$keys[$kid] = new Key($key, $v['alg']);
419  }
420  }
421  }
422 
423  if (empty($keys)) {
424  throw new \UnexpectedValueException('No supported algorithms found in JWK Set');
425  }
426 
427  return $keys;
428  }
429 }
static parseKey(array $jwk, string $defaultAlg=null)
Parse a JWK key.
Definition: JWK.php:96
$res
Definition: ltiservices.php:69
static getLastPayload()
Get the value of the payload for the last signed JWT (before any encryption).
static urlsafeB64Encode(string $input)
Encode a string with URL-safe Base64.
Definition: JWT.php:444
getClaim(string $name, string $defaultValue=null)
Get the value of the claim with the specified name.
static int $leeway
Definition: JWT.php:41
hasJwt()
Check if a JWT is defined.
static sign(array $payload, string $signatureMethod, string $privateKey, string $kid=null, string $jku=null, string $encryptionMethod=null, string $publicKey=null)
Sign the JWT.
static getJWKS(string $pemKey, string $signatureMethod, string $kid=null)
Get the public JWKS from a key in PEM format.
static parseKeySet(array $jwks)
Parse a set of JWK keys.
if(count($parts) !=3) $payload
Definition: ltitoken.php:70
static generateKey(string $signatureMethod='RS256')
Generate a new private key in PEM format.
if(!array_key_exists('PATH_INFO', $_SERVER)) $config
Definition: metadata.php:85
static int $leeway
Leeway (in seconds) to allow when checking timestamps (default is 3 minutes).
Definition: Jwt.php:44
verify(string $publicKey, string $jku=null)
Verify the signature of the JWT.
Class to represent an HTTP message request.
Definition: HttpMessage.php:30
Class to implement the JWT interface using the Firebase JWT library from https://github.com/firebase/php-jwt.
string $kid
Key ID.
Definition: System.php:88
string $signatureMethod
Method used for signing messages.
Definition: System.php:50
array $details
Details for error message relating to last request processed.
Definition: System.php:109
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
if($format !==null) $name
Definition: metadata.php:247
$keys
Definition: metadata.php:204
static getPublicKey(string $privateKey)
Get the public key for a private key.
string $jku
Endpoint for public key.
Definition: System.php:95
const SUPPORTED_ALGORITHMS
Supported signature algorithms.
static array static decode(string $jwt, $keyOrKeyArray, stdClass &$headers=null)
Decodes a JWT string into a PHP object.
Definition: JWT.php:96
string $key
Consumer key/client ID value.
Definition: System.php:193
fetchPublicKey(string $jku)
Fetch the public keys from a URL.
getPayload()
Get the value of the payload.
$http
Definition: raiseError.php:7
isEncrypted()
Check if a JWT&#39;s content is encrypted.
$privateKey
Definition: ltiregstart.php:68
getJweHeaders()
Get the value of the JWE headers.
load(string $jwtString, string $privateKey=null)
Load a JWT from a string.
static encode(array $payload, $key, string $alg, string $keyId=null, array $head=null)
Converts and signs a PHP array into a JWT string.
Definition: JWT.php:199
getHeaders()
Get the value of the headers.
string $encryptionMethod
Algorithm used for encrypting messages.
Definition: System.php:57
hasHeader(string $name)
Check whether a JWT has a header with the specified name.
static getLastHeaders()
Get the value of the headers for the last signed JWT (before any encryption).
static getSupportedAlgorithms()
Return an array of supported signature algorithms.
static logError(string $message, bool $showSource=true)
Log an error message.
Definition: Util.php:337
static urlsafeB64Decode(string $input)
Decode a string with URL-safe Base64.
Definition: JWT.php:412
getHeader(string $name, string $defaultValue=null)
Get the value of the header with the specified name.
Interface to represent an HWT client.
hasClaim(string $name)
Check whether a JWT has a claim with the specified name.