| 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: |  |