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;
56 private $payload =
null;
63 private static $lastHeaders =
null;
70 private static $lastPayload =
null;
77 Util::logDebug(
'Class ceLTIc\LTI\Jwt\SpomkyLabsClient has been deprecated; please try using ceLTIc\LTI\Jwt\WebTokenClient instead.',
98 return !empty($this->jwt);
108 return !empty($this->jwe);
119 public function load($jwtString, $privateKey =
null)
125 $loader =
new Jose\Loader();
126 $this->jwt = $loader->load($jwtString);
127 $parts = explode(
'.', $jwtString);
128 if (count($parts) >= 2) {
131 $this->decrypt($privateKey);
132 }
catch (\Exception $e) {
147 $headers = $this->jwe->getSharedProtectedHeaders();
164 if ($this->jwt instanceof JWS) {
165 $ok = $this->jwt->getSignature(0)->hasProtectedHeader($name);
184 $value = $this->jwt->getSignature(0)->getProtectedHeader($name);
185 }
catch (\Exception $e) {
186 $value = $defaultValue;
200 if ($this->jwt instanceof JWS) {
201 $headers = $this->jwt->getSignature(0)->getProtectedHeaders();
214 return self::$lastHeaders;
226 return isset($this->payload->{$name});
237 public function getClaim($name, $defaultValue =
null)
239 if ($this->hasClaim($name)) {
240 $value = $this->payload->{$name};
242 $value = defaultValue;
255 return $this->payload;
265 return self::$lastPayload;
276 public function verify($publicKey, $jku =
null)
279 $hasPublicKey = !empty($publicKey);
281 $leeway = Jwt::$leeway;
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')) {
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);
298 $jwks = JWKFactory::createFromJKU($jku,
true,
null, 86400,
true);
300 $verifier->verifyWithKeySet($this->jwt, $jwks);
301 $jwk = $jwks->selectKey(
'sig', $this->getHeader(
'alg'), [
'kid' => $this->getHeader(
'kid')]);
303 $jwk = self::getJwk($publicKey, [
'alg' => $this->getHeader(
'alg'),
'use' =>
'sig']);
304 $verifier->verifyWithKey($this->jwt, $jwk);
309 }
catch (\Exception $e) {
310 Util::logError($e->getMessage());
313 } elseif ($hasPublicKey && !empty($jku)) {
316 $hasPublicKey =
false;
319 }
while (!$ok && $retry);
338 public static function sign($payload, $signatureMethod, $privateKey, $kid =
null, $jku =
null, $encryptionMethod =
null,
341 switch ($signatureMethod) {
343 $signature =
new Signature\RS512();
346 $signature =
new Signature\RS384();
349 $signatureMethod =
'RS256';
350 $signature =
new Signature\RS256();
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];
358 $headers[
'kid'] = $kid;
360 $headers[
'jku'] = $jku;
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);
373 $errorMessage =
'No public key provided for encrypting JWT content';
374 Util::logError($errorMessage);
375 throw new \Exception($errorMessage);
377 self::$lastHeaders = $headers;
378 self::$lastPayload = $payload;
392 switch ($signatureMethod) {
402 $signatureMethod =
'RS256';
405 $jwk = JWKFactory::createKey([
408 'alg' => $signatureMethod,
411 $rsa =
new RSAKey($jwk);
412 $privateKey = $rsa->toPEM();
428 $jwk = self::getJwk($privateKey, [
'use' =>
'sig']);
429 $jwk = $jwk->toPublic();
430 $rsa =
new RSAKey($jwk);
431 $publicKey = $rsa->toPEM();
432 }
catch (\Exception $e) {
448 public static function getJWKS($key, $signatureMethod, $kid)
450 $keys[
'keys'] = array();
451 $additionalValues = [
'alg' => $signatureMethod,
'use' =>
'sig'];
453 $additionalValues[
'kid'] = $kid;
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) {
477 private function decrypt($privateKey)
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]));
493 $ok = $this->jwt instanceof JWS;
505 private static function getJwk($key, $additionalValues)
507 $keyValues = Util::jsonDecode($key,
true);
508 if (!is_array($keyValues)) {
509 $jwk = JWKFactory::createFromKey($key,
null, $additionalValues);
511 $keyValues = array_merge($keyValues, $additionalValues);
512 $jwk =
new JWK($keyValues);
Class to represent an HTTP message request.
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).
__construct()
Class constructor.
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.
static jsonDecode($str, $associative=false)
Decode a JSON string.
static logDebug($message, $showSource=false)
Log a debug message.
Interface to represent an HWT client.