Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403]

   1  <?php
   2  
   3  namespace Firebase\JWT;
   4  
   5  use DomainException;
   6  use UnexpectedValueException;
   7  
   8  /**
   9   * JSON Web Key implementation, based on this spec:
  10   * https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41
  11   *
  12   * PHP version 5
  13   *
  14   * @category Authentication
  15   * @package  Authentication_JWT
  16   * @author   Bui Sy Nguyen <nguyenbs@gmail.com>
  17   * @license  http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
  18   * @link     https://github.com/firebase/php-jwt
  19   */
  20  class JWK
  21  {
  22      /**
  23       * Parse a set of JWK keys
  24       *
  25       * @param array $jwks The JSON Web Key Set as an associative array
  26       *
  27       * @return array An associative array that represents the set of keys
  28       *
  29       * @throws InvalidArgumentException     Provided JWK Set is empty
  30       * @throws UnexpectedValueException     Provided JWK Set was invalid
  31       * @throws DomainException              OpenSSL failure
  32       *
  33       * @uses parseKey
  34       */
  35      public static function parseKeySet(array $jwks)
  36      {
  37          $keys = array();
  38  
  39          if (!isset($jwks['keys'])) {
  40              throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
  41          }
  42          if (empty($jwks['keys'])) {
  43              throw new InvalidArgumentException('JWK Set did not contain any keys');
  44          }
  45  
  46          foreach ($jwks['keys'] as $k => $v) {
  47              $kid = isset($v['kid']) ? $v['kid'] : $k;
  48              if ($key = self::parseKey($v)) {
  49                  $keys[$kid] = $key;
  50              }
  51          }
  52  
  53          if (0 === \count($keys)) {
  54              throw new UnexpectedValueException('No supported algorithms found in JWK Set');
  55          }
  56  
  57          return $keys;
  58      }
  59  
  60      /**
  61       * Parse a JWK key
  62       *
  63       * @param array $jwk An individual JWK
  64       *
  65       * @return resource|array An associative array that represents the key
  66       *
  67       * @throws InvalidArgumentException     Provided JWK is empty
  68       * @throws UnexpectedValueException     Provided JWK was invalid
  69       * @throws DomainException              OpenSSL failure
  70       *
  71       * @uses createPemFromModulusAndExponent
  72       */
  73      private static function parseKey(array $jwk)
  74      {
  75          if (empty($jwk)) {
  76              throw new InvalidArgumentException('JWK must not be empty');
  77          }
  78          if (!isset($jwk['kty'])) {
  79              throw new UnexpectedValueException('JWK must contain a "kty" parameter');
  80          }
  81  
  82          switch ($jwk['kty']) {
  83              case 'RSA':
  84                  if (\array_key_exists('d', $jwk)) {
  85                      throw new UnexpectedValueException('RSA private keys are not supported');
  86                  }
  87                  if (!isset($jwk['n']) || !isset($jwk['e'])) {
  88                      throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"');
  89                  }
  90  
  91                  $pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']);
  92                  $publicKey = \openssl_pkey_get_public($pem);
  93                  if (false === $publicKey) {
  94                      throw new DomainException(
  95                          'OpenSSL error: ' . \openssl_error_string()
  96                      );
  97                  }
  98                  return $publicKey;
  99              default:
 100                  // Currently only RSA is supported
 101                  break;
 102          }
 103      }
 104  
 105      /**
 106       * Create a public key represented in PEM format from RSA modulus and exponent information
 107       *
 108       * @param string $n The RSA modulus encoded in Base64
 109       * @param string $e The RSA exponent encoded in Base64
 110       *
 111       * @return string The RSA public key represented in PEM format
 112       *
 113       * @uses encodeLength
 114       */
 115      private static function createPemFromModulusAndExponent($n, $e)
 116      {
 117          $modulus = JWT::urlsafeB64Decode($n);
 118          $publicExponent = JWT::urlsafeB64Decode($e);
 119  
 120          $components = array(
 121              'modulus' => \pack('Ca*a*', 2, self::encodeLength(\strlen($modulus)), $modulus),
 122              'publicExponent' => \pack('Ca*a*', 2, self::encodeLength(\strlen($publicExponent)), $publicExponent)
 123          );
 124  
 125          $rsaPublicKey = \pack(
 126              'Ca*a*a*',
 127              48,
 128              self::encodeLength(\strlen($components['modulus']) + \strlen($components['publicExponent'])),
 129              $components['modulus'],
 130              $components['publicExponent']
 131          );
 132  
 133          // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption.
 134          $rsaOID = \pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
 135          $rsaPublicKey = \chr(0) . $rsaPublicKey;
 136          $rsaPublicKey = \chr(3) . self::encodeLength(\strlen($rsaPublicKey)) . $rsaPublicKey;
 137  
 138          $rsaPublicKey = \pack(
 139              'Ca*a*',
 140              48,
 141              self::encodeLength(\strlen($rsaOID . $rsaPublicKey)),
 142              $rsaOID . $rsaPublicKey
 143          );
 144  
 145          $rsaPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" .
 146              \chunk_split(\base64_encode($rsaPublicKey), 64) .
 147              '-----END PUBLIC KEY-----';
 148  
 149          return $rsaPublicKey;
 150      }
 151  
 152      /**
 153       * DER-encode the length
 154       *
 155       * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See
 156       * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
 157       *
 158       * @param int $length
 159       * @return string
 160       */
 161      private static function encodeLength($length)
 162      {
 163          if ($length <= 0x7F) {
 164              return \chr($length);
 165          }
 166  
 167          $temp = \ltrim(\pack('N', $length), \chr(0));
 168  
 169          return \pack('Ca*', 0x80 | \strlen($temp), $temp);
 170      }
 171  }