| 1: | <?php
|
| 2: |
|
| 3: | namespace Firebase\JWT;
|
| 4: |
|
| 5: | use DomainException;
|
| 6: | use InvalidArgumentException;
|
| 7: | use UnexpectedValueException;
|
| 8: |
|
| 9: | |
| 10: | |
| 11: | |
| 12: | |
| 13: | |
| 14: | |
| 15: | |
| 16: | |
| 17: | |
| 18: | |
| 19: | |
| 20: |
|
| 21: | class JWK
|
| 22: | {
|
| 23: | |
| 24: | |
| 25: | |
| 26: | |
| 27: | |
| 28: | |
| 29: | |
| 30: | |
| 31: | |
| 32: | |
| 33: | |
| 34: | |
| 35: |
|
| 36: | public static function parseKeySet(array $jwks)
|
| 37: | {
|
| 38: | $keys = array();
|
| 39: |
|
| 40: | if (!isset($jwks['keys'])) {
|
| 41: | throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
|
| 42: | }
|
| 43: | if (empty($jwks['keys'])) {
|
| 44: | throw new InvalidArgumentException('JWK Set did not contain any keys');
|
| 45: | }
|
| 46: |
|
| 47: | foreach ($jwks['keys'] as $k => $v) {
|
| 48: | $kid = isset($v['kid']) ? $v['kid'] : $k;
|
| 49: | if ($key = self::parseKey($v)) {
|
| 50: | $keys[$kid] = $key;
|
| 51: | }
|
| 52: | }
|
| 53: |
|
| 54: | if (0 === \count($keys)) {
|
| 55: | throw new UnexpectedValueException('No supported algorithms found in JWK Set');
|
| 56: | }
|
| 57: |
|
| 58: | return $keys;
|
| 59: | }
|
| 60: |
|
| 61: | |
| 62: | |
| 63: | |
| 64: | |
| 65: | |
| 66: | |
| 67: | |
| 68: | |
| 69: | |
| 70: | |
| 71: | |
| 72: | |
| 73: |
|
| 74: | public static function parseKey(array $jwk)
|
| 75: | {
|
| 76: | if (empty($jwk)) {
|
| 77: | throw new InvalidArgumentException('JWK must not be empty');
|
| 78: | }
|
| 79: | if (!isset($jwk['kty'])) {
|
| 80: | throw new UnexpectedValueException('JWK must contain a "kty" parameter');
|
| 81: | }
|
| 82: | if (!isset($jwk['alg'])) {
|
| 83: |
|
| 84: |
|
| 85: |
|
| 86: | throw new UnexpectedValueException('JWK must contain an "alg" parameter');
|
| 87: | }
|
| 88: |
|
| 89: | switch ($jwk['kty']) {
|
| 90: | case 'RSA':
|
| 91: | if (!empty($jwk['d'])) {
|
| 92: | throw new UnexpectedValueException('RSA private keys are not supported');
|
| 93: | }
|
| 94: | if (!isset($jwk['n']) || !isset($jwk['e'])) {
|
| 95: | throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"');
|
| 96: | }
|
| 97: |
|
| 98: | $pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']);
|
| 99: | $publicKey = \openssl_pkey_get_public($pem);
|
| 100: | if (false === $publicKey) {
|
| 101: | throw new DomainException(
|
| 102: | 'OpenSSL error: ' . \openssl_error_string()
|
| 103: | );
|
| 104: | }
|
| 105: | return new Key($publicKey, $jwk['alg']);
|
| 106: | default:
|
| 107: |
|
| 108: | break;
|
| 109: | }
|
| 110: | }
|
| 111: |
|
| 112: | |
| 113: | |
| 114: | |
| 115: | |
| 116: | |
| 117: | |
| 118: | |
| 119: | |
| 120: | |
| 121: |
|
| 122: | private static function createPemFromModulusAndExponent($n, $e)
|
| 123: | {
|
| 124: | $modulus = JWT::urlsafeB64Decode($n);
|
| 125: | $publicExponent = JWT::urlsafeB64Decode($e);
|
| 126: |
|
| 127: | $components = array(
|
| 128: | 'modulus' => \pack('Ca*a*', 2, self::encodeLength(\strlen($modulus)), $modulus),
|
| 129: | 'publicExponent' => \pack('Ca*a*', 2, self::encodeLength(\strlen($publicExponent)), $publicExponent)
|
| 130: | );
|
| 131: |
|
| 132: | $rsaPublicKey = \pack(
|
| 133: | 'Ca*a*a*',
|
| 134: | 48,
|
| 135: | self::encodeLength(\strlen($components['modulus']) + \strlen($components['publicExponent'])),
|
| 136: | $components['modulus'],
|
| 137: | $components['publicExponent']
|
| 138: | );
|
| 139: |
|
| 140: |
|
| 141: | $rsaOID = \pack('H*', '300d06092a864886f70d0101010500');
|
| 142: | $rsaPublicKey = \chr(0) . $rsaPublicKey;
|
| 143: | $rsaPublicKey = \chr(3) . self::encodeLength(\strlen($rsaPublicKey)) . $rsaPublicKey;
|
| 144: |
|
| 145: | $rsaPublicKey = \pack(
|
| 146: | 'Ca*a*',
|
| 147: | 48,
|
| 148: | self::encodeLength(\strlen($rsaOID . $rsaPublicKey)),
|
| 149: | $rsaOID . $rsaPublicKey
|
| 150: | );
|
| 151: |
|
| 152: | $rsaPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" .
|
| 153: | \chunk_split(\base64_encode($rsaPublicKey), 64) .
|
| 154: | '-----END PUBLIC KEY-----';
|
| 155: |
|
| 156: | return $rsaPublicKey;
|
| 157: | }
|
| 158: |
|
| 159: | |
| 160: | |
| 161: | |
| 162: | |
| 163: | |
| 164: | |
| 165: | |
| 166: | |
| 167: |
|
| 168: | private static function encodeLength($length)
|
| 169: | {
|
| 170: | if ($length <= 0x7F) {
|
| 171: | return \chr($length);
|
| 172: | }
|
| 173: |
|
| 174: | $temp = \ltrim(\pack('N', $length), \chr(0));
|
| 175: |
|
| 176: | return \pack('Ca*', 0x80 | \strlen($temp), $temp);
|
| 177: | }
|
| 178: | }
|
| 179: | |