LTI Integration Library 4.10.3
PHP class library for building LTI integrations
 
Loading...
Searching...
No Matches
SpomkyLabsClient.php
1<?php
2
3namespace ceLTIc\LTI\Jwt;
4
5use Jose;
6use Jose\Object\JWK;
7use Jose\Object\JWE;
8use Jose\Object\JWS;
9use Jose\Factory\JWKFactory;
10use Jose\KeyConverter\RSAKey;
11use Jose\Algorithm\Signature;
12use Jose\Checker\CheckerManager;
13use Jose\Checker\ExpirationTimeChecker;
14use Jose\Checker\IssuedAtChecker;
15use Jose\Checker\NotBeforeChecker;
16use Base64Url\Base64Url;
18
30{
31
35 const SUPPORTED_ALGORITHMS = array('RS256', 'RS384', 'RS512');
36
42 private $jwe = null;
43
49 private $jwt = null;
50
56 private $payload = null;
57
63 private static $lastHeaders = null;
64
70 private static $lastPayload = null;
71
75 public function __construct()
76 {
77 Util::logDebug('Class ceLTIc\LTI\Jwt\SpomkyLabsClient has been deprecated; please try using ceLTIc\LTI\Jwt\WebTokenClient instead.',
78 true);
79 }
80
86 public static function getSupportedAlgorithms()
87 {
89 }
90
96 public function hasJwt()
97 {
98 return !empty($this->jwt);
99 }
100
106 public function isEncrypted()
107 {
108 return !empty($this->jwe);
109 }
110
119 public function load($jwtString, $privateKey = null)
120 {
121 $ok = true;
122 $this->jwe = null;
123 $this->jwt = null;
124 try {
125 $loader = new Jose\Loader();
126 $this->jwt = $loader->load($jwtString);
127 $parts = explode('.', $jwtString);
128 if (count($parts) >= 2) {
129 $this->payload = Util::jsonDecode(Base64Url::decode($parts[1]));
130 }
131 $this->decrypt($privateKey);
132 } catch (\Exception $e) {
133 $ok = false;
134 }
135
136 return $ok;
137 }
138
144 public function getJweHeaders()
145 {
146 if ($this->isEncrypted()) {
147 $headers = $this->jwe->getSharedProtectedHeaders();
148 } else {
149 $headers = array();
150 }
151
152 return $headers;
153 }
154
162 public function hasHeader($name)
163 {
164 if ($this->jwt instanceof JWS) {
165 $ok = $this->jwt->getSignature(0)->hasProtectedHeader($name);
166 } else {
167 $ok = false;
168 }
169
170 return $ok;
171 }
172
181 public function getHeader($name, $defaultValue = null)
182 {
183 try {
184 $value = $this->jwt->getSignature(0)->getProtectedHeader($name);
185 } catch (\Exception $e) {
186 $value = $defaultValue;
187 }
188
189 return $value;
190 }
191
197 public function getHeaders()
198 {
199 $headers = null;
200 if ($this->jwt instanceof JWS) {
201 $headers = $this->jwt->getSignature(0)->getProtectedHeaders();
202 }
203
204 return $headers;
205 }
206
212 public static function getLastHeaders()
213 {
214 return self::$lastHeaders;
215 }
216
224 public function hasClaim($name)
225 {
226 return isset($this->payload->{$name});
227 }
228
237 public function getClaim($name, $defaultValue = null)
238 {
239 if ($this->hasClaim($name)) {
240 $value = $this->payload->{$name};
241 } else {
242 $value = defaultValue;
243 }
244
245 return $value;
246 }
247
253 public function getPayload()
254 {
255 return $this->payload;
256 }
257
263 public static function getLastPayload()
264 {
265 return self::$lastPayload;
266 }
267
276 public function verify($publicKey, $jku = null)
277 {
278 $ok = false;
279 $hasPublicKey = !empty($publicKey);
280 $retry = false;
281 $leeway = Jwt::$leeway;
282 do {
283 try {
284 $checkerManager = new CheckerManager();
285 $checkerManager->addClaimChecker(new ExpirationTimeChecker($leeway));
286 $checkerManager->addClaimChecker(new IssuedAtChecker($leeway));
287 $checkerManager->addClaimChecker(new NotBeforeChecker($leeway));
288 $checkerManager->checkJWS($this->jwt, 0);
289 $verifier = new Jose\Verifier(['RS256', 'RS384', 'RS512']);
290 switch ($this->getHeader('alg')) {
291 case 'RS256':
292 case 'RS384':
293 case 'RS512':
294 if ((Jwt::$allowJkuHeader && $this->hasHeader('jku')) || (!empty($jku) && empty($publicKey))) {
295 if (Jwt::$allowJkuHeader && $this->hasHeader('jku')) {
296 $jwks = JWKFactory::createFromJKU($this->getHeader('jku'), true, null, 86400, true);
297 } else {
298 $jwks = JWKFactory::createFromJKU($jku, true, null, 86400, true);
299 }
300 $verifier->verifyWithKeySet($this->jwt, $jwks);
301 $jwk = $jwks->selectKey('sig', $this->getHeader('alg'), ['kid' => $this->getHeader('kid')]);
302 } else {
303 $jwk = self::getJwk($publicKey, ['alg' => $this->getHeader('alg'), 'use' => 'sig']);
304 $verifier->verifyWithKey($this->jwt, $jwk);
305 }
306 break;
307 }
308 $ok = true;
309 } catch (\Exception $e) {
310 Util::logError($e->getMessage());
311 if ($retry) {
312 $retry = false;
313 } elseif ($hasPublicKey && !empty($jku)) {
314 $retry = true;
315 $publicKey = null;
316 $hasPublicKey = false;
317 }
318 }
319 } while (!$ok && $retry);
320
321 return $ok;
322 }
323
338 public static function sign($payload, $signatureMethod, $privateKey, $kid = null, $jku = null, $encryptionMethod = null,
339 $publicKey = null)
340 {
341 switch ($signatureMethod) {
342 case 'RS512':
343 $signature = new Signature\RS512();
344 break;
345 case 'RS384':
346 $signature = new Signature\RS384();
347 break;
348 default:
349 $signatureMethod = 'RS256';
350 $signature = new Signature\RS256();
351 break;
352 }
353 $jwk = self::getJwk($privateKey, ['alg' => $signatureMethod, 'use' => 'sig']);
354 $signer = new Jose\Signer([$signature]);
355 $jwtCreator = new Jose\JWTCreator($signer);
356 $headers = ['typ' => 'JWT', 'alg' => $signatureMethod];
357 if (!empty($kid)) {
358 $headers['kid'] = $kid;
359 if (!empty($jku)) {
360 $headers['jku'] = $jku;
361 }
362 }
363 if (empty($encryptionMethod)) {
364 $jws = $jwtCreator->sign($payload, $headers, $jwk);
365 } elseif (!empty($publicKey)) {
366 $keyEnc = 'RSA-OAEP-256';
367 $encHeaders = ['use' => 'enc', 'alg' => $keyEnc, 'enc' => $encryptionMethod, 'zip' => 'DEF'];
368 $encJwk = self::getJwk($publicKey, $encHeaders);
369 $encrypter = Jose\Encrypter::createEncrypter([$keyEnc], [$encryptionMethod], ['DEF']);
370 $jwtCreator->enableEncryptionSupport($encrypter);
371 $jws = $jwtCreator->signAndEncrypt($payload, $headers, $jwk, $encHeaders, $encJwk);
372 } else {
373 $errorMessage = 'No public key provided for encrypting JWT content';
374 Util::logError($errorMessage);
375 throw new \Exception($errorMessage);
376 }
377 self::$lastHeaders = $headers;
378 self::$lastPayload = $payload;
379
380 return $jws;
381 }
382
390 public static function generateKey($signatureMethod = 'RS256')
391 {
392 switch ($signatureMethod) {
393 case 'RS512':
394 $size = 4096;
395 break;
396 case 'RS384':
397 $size = 3072;
398 break;
399 case 'RS256':
400 default:
401 $size = 2048;
402 $signatureMethod = 'RS256';
403 break;
404 }
405 $jwk = JWKFactory::createKey([
406 'kty' => 'RSA',
407 'size' => $size,
408 'alg' => $signatureMethod,
409 'use' => 'sig',
410 ]);
411 $rsa = new RSAKey($jwk);
412 $privateKey = $rsa->toPEM();
413
414 return $privateKey;
415 }
416
424 public static function getPublicKey($privateKey)
425 {
426 $publicKey = null;
427 try {
428 $jwk = self::getJwk($privateKey, ['use' => 'sig']);
429 $jwk = $jwk->toPublic();
430 $rsa = new RSAKey($jwk);
431 $publicKey = $rsa->toPEM();
432 } catch (\Exception $e) {
433
434 }
435
436 return $publicKey;
437 }
438
448 public static function getJWKS($key, $signatureMethod, $kid)
449 {
450 $keys['keys'] = array();
451 $additionalValues = ['alg' => $signatureMethod, 'use' => 'sig'];
452 if (!empty($kid)) {
453 $additionalValues['kid'] = $kid;
454 }
455 try {
456 $jwk = self::getJwk($key, $additionalValues);
457 $jwk = $jwk->toPublic();
458 $rsa = new RSAKey($jwk);
459 $rsa = $rsa::toPublic($rsa);
460 $keys['keys'][] = $rsa->toArray();
461 } catch (\Exception $e) {
462
463 }
464
465 return $keys;
466 }
467
468###
469### PRIVATE METHODS
470###
471
477 private function decrypt($privateKey)
478 {
479 if ($this->jwt instanceof JWE) {
480 $this->jwe = clone $this->jwt;
481 $keyEnc = $this->jwe->getSharedProtectedHeader('alg');
482 $encryptionMethod = $this->jwe->getSharedProtectedHeader('enc');
483 $jwk = self::getJwk($privateKey, ['alg' => $keyEnc, 'use' => 'enc']);
484 $decrypter = Jose\Decrypter::createDecrypter([$keyEnc], [$encryptionMethod], ['DEF', 'GZ', 'ZLIB']);
485 $decrypter->decryptUsingKey($this->jwt, $jwk);
486 $jwtString = $this->jwt->getPayload();
487 $loader = new Jose\Loader();
488 $this->jwt = $loader->load($jwtString);
489 $parts = explode('.', $jwtString);
490 if (count($parts) >= 2) {
491 $this->payload = Util::jsonDecode(Base64Url::decode($parts[1]));
492 }
493 $ok = $this->jwt instanceof JWS;
494 }
495 }
496
505 private static function getJwk($key, $additionalValues)
506 {
507 $keyValues = Util::jsonDecode($key, true);
508 if (!is_array($keyValues)) {
509 $jwk = JWKFactory::createFromKey($key, null, $additionalValues);
510 } else {
511 $keyValues = array_merge($keyValues, $additionalValues);
512 $jwk = new JWK($keyValues);
513 }
514
515 return $jwk;
516 }
517
518}
Class to represent an HTTP message request.
Definition Jwt.php:15
Class to implement the JWT interface using the Spomky-Labs JWT library from https://github....
static getLastHeaders()
Get the value of the headers for the last signed JWT (before any encryption).
static getLastPayload()
Get the value of the payload for the last signed JWT (before any encryption).
getHeaders()
Get the value of the headers.
hasJwt()
Check if a JWT is defined.
getJweHeaders()
Get the value of the JWE headers.
static getPublicKey($privateKey)
Get the public key for a private key.
load($jwtString, $privateKey=null)
Load a JWT from a string.
static generateKey($signatureMethod='RS256')
Generate a new private key in PEM format.
const SUPPORTED_ALGORITHMS
Supported signature algorithms.
isEncrypted()
Check if a JWT's content is encrypted.
static getSupportedAlgorithms()
Return an array of supported signature algorithms.
getHeader($name, $defaultValue=null)
Get the value of the header with the specified name.
getClaim($name, $defaultValue=null)
Get the value of the claim with the specified name.
static sign($payload, $signatureMethod, $privateKey, $kid=null, $jku=null, $encryptionMethod=null, $publicKey=null)
Sign the JWT.
static getJWKS($key, $signatureMethod, $kid)
Get the public JWKS from a key in PEM or JWK format.
hasHeader($name)
Check whether a JWT has a header with the specified name.
getPayload()
Get the value of the payload.
hasClaim($name)
Check whether a JWT has a claim with the specified name.
verify($publicKey, $jku=null)
Verify the signature of the JWT.
Class to implement utility methods.
Definition Util.php:15
static jsonDecode($str, $associative=false)
Decode a JSON string.
Definition Util.php:560
static logDebug($message, $showSource=false)
Log a debug message.
Definition Util.php:274
Interface to represent an HWT client.