9use InvalidArgumentException;
10use OpenSSLAsymmetricKey;
11use OpenSSLCertificate;
13use UnexpectedValueException;
56 'ES384' => [
'openssl',
'SHA384'],
57 'ES256' => [
'openssl',
'SHA256'],
58 'ES256K' => [
'openssl',
'SHA256'],
59 'HS256' => [
'hash_hmac',
'SHA256'],
60 'HS384' => [
'hash_hmac',
'SHA384'],
61 'HS512' => [
'hash_hmac',
'SHA512'],
62 'RS256' => [
'openssl',
'SHA256'],
63 'RS384' => [
'openssl',
'SHA384'],
64 'RS512' => [
'openssl',
'SHA512'],
65 'EdDSA' => [
'sodium_crypto',
'EdDSA'],
99 stdClass &$headers =
null
104 if (empty($keyOrKeyArray)) {
105 throw new InvalidArgumentException(
'Key may not be empty');
107 $tks = \explode(
'.',
$jwt);
108 if (\count($tks) !== 3) {
109 throw new UnexpectedValueException(
'Wrong number of segments');
111 list($headb64, $bodyb64, $cryptob64) = $tks;
112 $headerRaw = static::urlsafeB64Decode($headb64);
113 if (
null === ($header = static::jsonDecode($headerRaw))) {
114 throw new UnexpectedValueException(
'Invalid header encoding');
116 if ($headers !==
null) {
119 $payloadRaw = static::urlsafeB64Decode($bodyb64);
120 if (
null === (
$payload = static::jsonDecode($payloadRaw))) {
121 throw new UnexpectedValueException(
'Invalid claims encoding');
127 if (!
$payload instanceof stdClass) {
128 throw new UnexpectedValueException(
'Payload must be a JSON object');
130 $sig = static::urlsafeB64Decode($cryptob64);
131 if (empty($header->alg)) {
132 throw new UnexpectedValueException(
'Empty algorithm');
134 if (empty(static::$supported_algs[$header->alg])) {
135 throw new UnexpectedValueException(
'Algorithm not supported');
138 $key =
self::getKey($keyOrKeyArray, property_exists($header,
'kid') ? $header->kid :
null);
141 if (!self::constantTimeEquals(
$key->getAlgorithm(), $header->alg)) {
143 throw new UnexpectedValueException(
'Incorrect key for this algorithm');
145 if (\in_array($header->alg, [
'ES256',
'ES256K',
'ES384'],
true)) {
149 if (!self::verify(
"{$headb64}.{$bodyb64}", $sig,
$key->getKeyMaterial(), $header->alg)) {
150 throw new SignatureInvalidException(
'Signature verification failed');
156 $ex =
new BeforeValidException(
157 'Cannot handle token with nbf prior to ' . \date(DateTime::ISO8601, (
int)
$payload->nbf)
167 $ex =
new BeforeValidException(
168 'Cannot handle token with iat prior to ' . \date(DateTime::ISO8601, (
int)
$payload->iat)
176 $ex =
new ExpiredException(
'Expired token');
203 string $keyId =
null,
206 $header = [
'typ' =>
'JWT',
'alg' => $alg];
207 if ($keyId !==
null) {
208 $header[
'kid'] = $keyId;
210 if (isset($head) && \is_array($head)) {
211 $header = \array_merge($head, $header);
214 $segments[] = static::urlsafeB64Encode((
string) static::jsonEncode($header));
215 $segments[] = static::urlsafeB64Encode((
string) static::jsonEncode(
$payload));
216 $signing_input = \implode(
'.', $segments);
218 $signature = static::sign($signing_input,
$key, $alg);
219 $segments[] = static::urlsafeB64Encode($signature);
221 return \implode(
'.', $segments);
241 if (empty(static::$supported_algs[$alg])) {
242 throw new DomainException(
'Algorithm not supported');
244 list($function, $algorithm) = static::$supported_algs[$alg];
247 if (!\is_string(
$key)) {
248 throw new InvalidArgumentException(
'key must be a string when using hmac');
250 return \hash_hmac($algorithm, $msg,
$key,
true);
253 $success = \openssl_sign($msg, $signature,
$key, $algorithm);
255 throw new DomainException(
'OpenSSL unable to sign data');
257 if ($alg ===
'ES256' || $alg ===
'ES256K') {
258 $signature = self::signatureFromDER($signature, 256);
259 } elseif ($alg ===
'ES384') {
260 $signature = self::signatureFromDER($signature, 384);
263 case 'sodium_crypto':
264 if (!\function_exists(
'sodium_crypto_sign_detached')) {
265 throw new DomainException(
'libsodium is not available');
267 if (!\is_string(
$key)) {
268 throw new InvalidArgumentException(
'key must be a string when using EdDSA');
272 $lines = array_filter(explode(
"\n",
$key));
273 $key = base64_decode((
string) end($lines));
274 if (\strlen(
$key) === 0) {
275 throw new DomainException(
'Key cannot be empty string');
277 return sodium_crypto_sign_detached($msg,
$key);
278 }
catch (Exception
$e) {
279 throw new DomainException(
$e->getMessage(), 0,
$e);
283 throw new DomainException(
'Algorithm not supported');
305 if (empty(static::$supported_algs[$alg])) {
306 throw new DomainException(
'Algorithm not supported');
309 list($function, $algorithm) = static::$supported_algs[$alg];
312 $success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm);
313 if ($success === 1) {
316 if ($success === 0) {
320 throw new DomainException(
321 'OpenSSL error: ' . \openssl_error_string()
323 case 'sodium_crypto':
324 if (!\function_exists(
'sodium_crypto_sign_verify_detached')) {
325 throw new DomainException(
'libsodium is not available');
327 if (!\is_string($keyMaterial)) {
328 throw new InvalidArgumentException(
'key must be a string when using EdDSA');
332 $lines = array_filter(explode(
"\n", $keyMaterial));
333 $key = base64_decode((
string) end($lines));
334 if (\strlen(
$key) === 0) {
335 throw new DomainException(
'Key cannot be empty string');
337 if (\strlen($signature) === 0) {
338 throw new DomainException(
'Signature cannot be empty string');
340 return sodium_crypto_sign_verify_detached($signature, $msg,
$key);
341 }
catch (Exception
$e) {
342 throw new DomainException(
$e->getMessage(), 0,
$e);
346 if (!\is_string($keyMaterial)) {
347 throw new InvalidArgumentException(
'key must be a string when using hmac');
349 $hash = \hash_hmac($algorithm, $msg, $keyMaterial,
true);
350 return self::constantTimeEquals($hash, $signature);
365 $obj = \json_decode($input,
false, 512, JSON_BIGINT_AS_STRING);
367 if ($errno = \json_last_error()) {
368 self::handleJsonError($errno);
369 } elseif ($obj ===
null && $input !==
'null') {
370 throw new DomainException(
'Null result with non-null input');
386 if (PHP_VERSION_ID >= 50400) {
387 $json = \json_encode($input, \JSON_UNESCAPED_SLASHES);
390 $json = \json_encode($input);
392 if ($errno = \json_last_error()) {
393 self::handleJsonError($errno);
394 } elseif ($json ===
'null') {
395 throw new DomainException(
'Null result with non-null input');
397 if ($json ===
false) {
398 throw new DomainException(
'Provided object could not be encoded to valid JSON');
414 return \base64_decode(self::convertBase64UrlToBase64($input));
429 $remainder = \strlen($input) % 4;
431 $padlen = 4 - $remainder;
432 $input .= \str_repeat(
'=', $padlen);
434 return \strtr($input,
'-_',
'+/');
446 return \str_replace(
'=',
'', \strtr(\base64_encode($input),
'+/',
'-_'));
464 if ($keyOrKeyArray instanceof
Key) {
465 return $keyOrKeyArray;
469 throw new UnexpectedValueException(
'"kid" empty, unable to lookup correct key');
472 if ($keyOrKeyArray instanceof CachedKeySet) {
474 return $keyOrKeyArray[
$kid];
477 if (!isset($keyOrKeyArray[
$kid])) {
478 throw new UnexpectedValueException(
'"kid" invalid, unable to lookup correct key');
481 return $keyOrKeyArray[
$kid];
491 if (\function_exists(
'hash_equals')) {
492 return \hash_equals($left, $right);
494 $len = \min(self::safeStrlen($left), self::safeStrlen($right));
497 for (
$i = 0;
$i < $len;
$i++) {
498 $status |= (\ord($left[
$i]) ^ \ord($right[
$i]));
500 $status |= (self::safeStrlen($left) ^ self::safeStrlen($right));
502 return ($status === 0);
517 JSON_ERROR_DEPTH =>
'Maximum stack depth exceeded',
518 JSON_ERROR_STATE_MISMATCH =>
'Invalid or malformed JSON',
519 JSON_ERROR_CTRL_CHAR =>
'Unexpected control character found',
520 JSON_ERROR_SYNTAX =>
'Syntax error, malformed JSON',
521 JSON_ERROR_UTF8 =>
'Malformed UTF-8 characters'
523 throw new DomainException(
526 :
'Unknown JSON error: ' . $errno
539 if (\function_exists(
'mb_strlen')) {
540 return \mb_strlen($str,
'8bit');
542 return \strlen($str);
554 $length = max(1, (
int) (\strlen($sig) / 2));
555 list($r, $s) = \str_split($sig, $length);
558 $r = \ltrim($r,
"\x00");
559 $s = \ltrim($s,
"\x00");
563 if (\ord($r[0]) > 0x7f) {
566 if (\ord($s[0]) > 0x7f) {
570 return self::encodeDER(
572 self::encodeDER(self::ASN1_INTEGER, $r) .
573 self::encodeDER(self::ASN1_INTEGER, $s)
588 if (
$type === self::ASN1_SEQUENCE) {
593 $der = \chr($tag_header |
$type);
596 $der .= \chr(\strlen($value));
598 return $der . $value;
612 list($offset, $_) = self::readDER($der);
613 list($offset, $r) = self::readDER($der, $offset);
614 list($offset, $s) = self::readDER($der, $offset);
618 $r = \ltrim($r,
"\x00");
619 $s = \ltrim($s,
"\x00");
622 $r = \str_pad($r, $keySize / 8,
"\x00", STR_PAD_LEFT);
623 $s = \str_pad($s, $keySize / 8,
"\x00", STR_PAD_LEFT);
637 private static function readDER(
string $der,
int $offset = 0): array
640 $size = \strlen($der);
641 $constructed = (\ord($der[$pos]) >> 5) & 0x01;
642 $type = \ord($der[$pos++]) & 0x1f;
645 $len = \ord($der[$pos++]);
649 while ($n-- && $pos < $size) {
650 $len = ($len << 8) | \ord($der[$pos++]);
655 if (
$type === self::ASN1_BIT_STRING) {
657 $data = \substr($der, $pos, $len - 1);
659 } elseif (!$constructed) {
660 $data = \substr($der, $pos, $len);
666 return [$pos,
$data];
static signatureToDER(string $sig)
Convert an ECDSA signature to an ASN.1 DER sequence.
static handleJsonError(int $errno)
Helper method to create a JSON error.
static encode(array $payload, $key, string $alg, string $keyId=null, array $head=null)
Converts and signs a PHP array into a JWT string.
static verify(string $msg, string $signature, $keyMaterial, string $alg)
Verify a signature with the message, key and method.
static constantTimeEquals(string $left, string $right)
static signatureFromDER(string $der, int $keySize)
Encodes signature from a DER object.
static sign(string $msg, $key, string $alg)
Sign a string with a given key and algorithm.
static decode(string $jwt, $keyOrKeyArray, stdClass &$headers=null)
Decodes a JWT string into a PHP object.
static urlsafeB64Encode(string $input)
Encode a string with URL-safe Base64.
static encodeDER(int $type, string $value)
Encodes a value into a DER object.
static array $supported_algs
static safeStrlen(string $str)
Get the number of bytes in cryptographic strings.
static getKey( $keyOrKeyArray, ?string $kid)
Determine if an algorithm has been provided for each Key.
static jsonEncode(array $input)
Encode a PHP array into a JSON string.
static readDER(string $der, int $offset=0)
Reads binary DER-encoded data and decodes into a single object.
static jsonDecode(string $input)
Decode a JSON string into a PHP object.
static convertBase64UrlToBase64(string $input)
Convert a string in the base64url (URL-safe Base64) encoding to standard base64.
static urlsafeB64Decode(string $input)
Decode a string with URL-safe Base64.
if(!file_exists(getcwd() . '/ilias.ini.php'))
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
if(count($parts) !=3) $payload
$messages
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...