| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- <?php
- namespace Firebase\JWT;
- use DomainException;
- use InvalidArgumentException;
- use UnexpectedValueException;
- /**
- * JSON Web Key implementation, based on this spec:
- * https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41
- *
- * PHP version 5
- *
- * @category Authentication
- * @package Authentication_JWT
- * @author Bui Sy Nguyen <nguyenbs@gmail.com>
- * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
- * @link https://github.com/firebase/php-jwt
- */
- class JWK
- {
- /**
- * Parse a set of JWK keys
- *
- * @param array $jwks The JSON Web Key Set as an associative array
- *
- * @return array An associative array that represents the set of keys
- *
- * @throws InvalidArgumentException Provided JWK Set is empty
- * @throws UnexpectedValueException Provided JWK Set was invalid
- * @throws DomainException OpenSSL failure
- *
- * @uses parseKey
- */
- public static function parseKeySet(array $jwks)
- {
- $keys = array();
- if (!isset($jwks['keys'])) {
- throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
- }
- if (empty($jwks['keys'])) {
- throw new InvalidArgumentException('JWK Set did not contain any keys');
- }
- foreach ($jwks['keys'] as $k => $v) {
- $kid = isset($v['kid']) ? $v['kid'] : $k;
- if ($key = self::parseKey($v)) {
- $keys[$kid] = $key;
- }
- }
- if (0 === \count($keys)) {
- throw new UnexpectedValueException('No supported algorithms found in JWK Set');
- }
- return $keys;
- }
- /**
- * Parse a JWK key
- *
- * @param array $jwk An individual JWK
- *
- * @return resource|array An associative array that represents the key
- *
- * @throws InvalidArgumentException Provided JWK is empty
- * @throws UnexpectedValueException Provided JWK was invalid
- * @throws DomainException OpenSSL failure
- *
- * @uses createPemFromModulusAndExponent
- */
- public static function parseKey(array $jwk)
- {
- if (empty($jwk)) {
- throw new InvalidArgumentException('JWK must not be empty');
- }
- if (!isset($jwk['kty'])) {
- throw new UnexpectedValueException('JWK must contain a "kty" parameter');
- }
- switch ($jwk['kty']) {
- case 'RSA':
- if (!empty($jwk['d'])) {
- throw new UnexpectedValueException('RSA private keys are not supported');
- }
- if (!isset($jwk['n']) || !isset($jwk['e'])) {
- throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"');
- }
- $pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']);
- $publicKey = \openssl_pkey_get_public($pem);
- if (false === $publicKey) {
- throw new DomainException(
- 'OpenSSL error: ' . \openssl_error_string()
- );
- }
- return $publicKey;
- default:
- // Currently only RSA is supported
- break;
- }
- }
- /**
- * Create a public key represented in PEM format from RSA modulus and exponent information
- *
- * @param string $n The RSA modulus encoded in Base64
- * @param string $e The RSA exponent encoded in Base64
- *
- * @return string The RSA public key represented in PEM format
- *
- * @uses encodeLength
- */
- private static function createPemFromModulusAndExponent($n, $e)
- {
- $modulus = JWT::urlsafeB64Decode($n);
- $publicExponent = JWT::urlsafeB64Decode($e);
- $components = array(
- 'modulus' => \pack('Ca*a*', 2, self::encodeLength(\strlen($modulus)), $modulus),
- 'publicExponent' => \pack('Ca*a*', 2, self::encodeLength(\strlen($publicExponent)), $publicExponent)
- );
- $rsaPublicKey = \pack(
- 'Ca*a*a*',
- 48,
- self::encodeLength(\strlen($components['modulus']) + \strlen($components['publicExponent'])),
- $components['modulus'],
- $components['publicExponent']
- );
- // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption.
- $rsaOID = \pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
- $rsaPublicKey = \chr(0) . $rsaPublicKey;
- $rsaPublicKey = \chr(3) . self::encodeLength(\strlen($rsaPublicKey)) . $rsaPublicKey;
- $rsaPublicKey = \pack(
- 'Ca*a*',
- 48,
- self::encodeLength(\strlen($rsaOID . $rsaPublicKey)),
- $rsaOID . $rsaPublicKey
- );
- $rsaPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" .
- \chunk_split(\base64_encode($rsaPublicKey), 64) .
- '-----END PUBLIC KEY-----';
- return $rsaPublicKey;
- }
- /**
- * DER-encode the length
- *
- * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
- * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
- *
- * @param int $length
- * @return string
- */
- private static function encodeLength($length)
- {
- if ($length <= 0x7F) {
- return \chr($length);
- }
- $temp = \ltrim(\pack('N', $length), \chr(0));
- return \pack('Ca*', 0x80 | \strlen($temp), $temp);
- }
- }
|