PluginProbe ʕ •ᴥ•ʔ
ShareThis Dashboard for Google Analytics / 3.2.0
ShareThis Dashboard for Google Analytics v3.2.0
3.3.2 trunk 1.0.7 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.1 2.1.2 2.1.3 2.1.4 2.1.5 2.2.5 2.3.5 2.3.6 2.3.7 2.3.8 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 3.0.0 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.3.0 3.3.1
googleanalytics / lib / analytics-admin / vendor / firebase / php-jwt / src / JWK.php
googleanalytics / lib / analytics-admin / vendor / firebase / php-jwt / src Last commit date
BeforeValidException.php 3 years ago CachedKeySet.php 3 years ago ExpiredException.php 3 years ago JWK.php 3 years ago JWT.php 3 years ago Key.php 3 years ago SignatureInvalidException.php 3 years ago
JWK.php
323 lines
1 <?php
2
3 namespace Firebase\JWT;
4
5 use DomainException;
6 use InvalidArgumentException;
7 use UnexpectedValueException;
8
9 /**
10 * JSON Web Key implementation, based on this spec:
11 * https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41
12 *
13 * PHP version 5
14 *
15 * @category Authentication
16 * @package Authentication_JWT
17 * @author Bui Sy Nguyen <nguyenbs@gmail.com>
18 * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
19 * @link https://github.com/firebase/php-jwt
20 */
21 class JWK
22 {
23 private const OID = '1.2.840.10045.2.1';
24 private const ASN1_OBJECT_IDENTIFIER = 0x06;
25 private const ASN1_SEQUENCE = 0x10; // also defined in JWT
26 private const ASN1_BIT_STRING = 0x03;
27 private const EC_CURVES = [
28 'P-256' => '1.2.840.10045.3.1.7', // Len: 64
29 // 'P-384' => '1.3.132.0.34', // Len: 96 (not yet supported)
30 // 'P-521' => '1.3.132.0.35', // Len: 132 (not supported)
31 ];
32
33 /**
34 * Parse a set of JWK keys
35 *
36 * @param array<mixed> $jwks The JSON Web Key Set as an associative array
37 * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the
38 * JSON Web Key Set
39 *
40 * @return array<string, Key> An associative array of key IDs (kid) to Key objects
41 *
42 * @throws InvalidArgumentException Provided JWK Set is empty
43 * @throws UnexpectedValueException Provided JWK Set was invalid
44 * @throws DomainException OpenSSL failure
45 *
46 * @uses parseKey
47 */
48 public static function parseKeySet(array $jwks, string $defaultAlg = null): array
49 {
50 $keys = [];
51
52 if (!isset($jwks['keys'])) {
53 throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
54 }
55
56 if (empty($jwks['keys'])) {
57 throw new InvalidArgumentException('JWK Set did not contain any keys');
58 }
59
60 foreach ($jwks['keys'] as $k => $v) {
61 $kid = isset($v['kid']) ? $v['kid'] : $k;
62 if ($key = self::parseKey($v, $defaultAlg)) {
63 $keys[(string) $kid] = $key;
64 }
65 }
66
67 if (0 === \count($keys)) {
68 throw new UnexpectedValueException('No supported algorithms found in JWK Set');
69 }
70
71 return $keys;
72 }
73
74 /**
75 * Parse a JWK key
76 *
77 * @param array<mixed> $jwk An individual JWK
78 * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the
79 * JSON Web Key Set
80 *
81 * @return Key The key object for the JWK
82 *
83 * @throws InvalidArgumentException Provided JWK is empty
84 * @throws UnexpectedValueException Provided JWK was invalid
85 * @throws DomainException OpenSSL failure
86 *
87 * @uses createPemFromModulusAndExponent
88 */
89 public static function parseKey(array $jwk, string $defaultAlg = null): ?Key
90 {
91 if (empty($jwk)) {
92 throw new InvalidArgumentException('JWK must not be empty');
93 }
94
95 if (!isset($jwk['kty'])) {
96 throw new UnexpectedValueException('JWK must contain a "kty" parameter');
97 }
98
99 if (!isset($jwk['alg'])) {
100 if (\is_null($defaultAlg)) {
101 // The "alg" parameter is optional in a KTY, but an algorithm is required
102 // for parsing in this library. Use the $defaultAlg parameter when parsing the
103 // key set in order to prevent this error.
104 // @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4
105 throw new UnexpectedValueException('JWK must contain an "alg" parameter');
106 }
107 $jwk['alg'] = $defaultAlg;
108 }
109
110 switch ($jwk['kty']) {
111 case 'RSA':
112 if (!empty($jwk['d'])) {
113 throw new UnexpectedValueException('RSA private keys are not supported');
114 }
115 if (!isset($jwk['n']) || !isset($jwk['e'])) {
116 throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"');
117 }
118
119 $pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']);
120 $publicKey = \openssl_pkey_get_public($pem);
121 if (false === $publicKey) {
122 throw new DomainException(
123 'OpenSSL error: ' . \openssl_error_string()
124 );
125 }
126 return new Key($publicKey, $jwk['alg']);
127 case 'EC':
128 if (isset($jwk['d'])) {
129 // The key is actually a private key
130 throw new UnexpectedValueException('Key data must be for a public key');
131 }
132
133 if (empty($jwk['crv'])) {
134 throw new UnexpectedValueException('crv not set');
135 }
136
137 if (!isset(self::EC_CURVES[$jwk['crv']])) {
138 throw new DomainException('Unrecognised or unsupported EC curve');
139 }
140
141 if (empty($jwk['x']) || empty($jwk['y'])) {
142 throw new UnexpectedValueException('x and y not set');
143 }
144
145 $publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']);
146 return new Key($publicKey, $jwk['alg']);
147 default:
148 // Currently only RSA is supported
149 break;
150 }
151
152 return null;
153 }
154
155 /**
156 * Converts the EC JWK values to pem format.
157 *
158 * @param string $crv The EC curve (only P-256 is supported)
159 * @param string $x The EC x-coordinate
160 * @param string $y The EC y-coordinate
161 *
162 * @return string
163 */
164 private static function createPemFromCrvAndXYCoordinates(string $crv, string $x, string $y): string
165 {
166 $pem =
167 self::encodeDER(
168 self::ASN1_SEQUENCE,
169 self::encodeDER(
170 self::ASN1_SEQUENCE,
171 self::encodeDER(
172 self::ASN1_OBJECT_IDENTIFIER,
173 self::encodeOID(self::OID)
174 )
175 . self::encodeDER(
176 self::ASN1_OBJECT_IDENTIFIER,
177 self::encodeOID(self::EC_CURVES[$crv])
178 )
179 ) .
180 self::encodeDER(
181 self::ASN1_BIT_STRING,
182 \chr(0x00) . \chr(0x04)
183 . JWT::urlsafeB64Decode($x)
184 . JWT::urlsafeB64Decode($y)
185 )
186 );
187
188 return sprintf(
189 "-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n",
190 wordwrap(base64_encode($pem), 64, "\n", true)
191 );
192 }
193
194 /**
195 * Create a public key represented in PEM format from RSA modulus and exponent information
196 *
197 * @param string $n The RSA modulus encoded in Base64
198 * @param string $e The RSA exponent encoded in Base64
199 *
200 * @return string The RSA public key represented in PEM format
201 *
202 * @uses encodeLength
203 */
204 private static function createPemFromModulusAndExponent(
205 string $n,
206 string $e
207 ): string {
208 $mod = JWT::urlsafeB64Decode($n);
209 $exp = JWT::urlsafeB64Decode($e);
210
211 $modulus = \pack('Ca*a*', 2, self::encodeLength(\strlen($mod)), $mod);
212 $publicExponent = \pack('Ca*a*', 2, self::encodeLength(\strlen($exp)), $exp);
213
214 $rsaPublicKey = \pack(
215 'Ca*a*a*',
216 48,
217 self::encodeLength(\strlen($modulus) + \strlen($publicExponent)),
218 $modulus,
219 $publicExponent
220 );
221
222 // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption.
223 $rsaOID = \pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
224 $rsaPublicKey = \chr(0) . $rsaPublicKey;
225 $rsaPublicKey = \chr(3) . self::encodeLength(\strlen($rsaPublicKey)) . $rsaPublicKey;
226
227 $rsaPublicKey = \pack(
228 'Ca*a*',
229 48,
230 self::encodeLength(\strlen($rsaOID . $rsaPublicKey)),
231 $rsaOID . $rsaPublicKey
232 );
233
234 return "-----BEGIN PUBLIC KEY-----\r\n" .
235 \chunk_split(\base64_encode($rsaPublicKey), 64) .
236 '-----END PUBLIC KEY-----';
237 }
238
239 /**
240 * DER-encode the length
241 *
242 * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
243 * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
244 *
245 * @param int $length
246 * @return string
247 */
248 private static function encodeLength(int $length): string
249 {
250 if ($length <= 0x7F) {
251 return \chr($length);
252 }
253
254 $temp = \ltrim(\pack('N', $length), \chr(0));
255
256 return \pack('Ca*', 0x80 | \strlen($temp), $temp);
257 }
258
259 /**
260 * Encodes a value into a DER object.
261 * Also defined in Firebase\JWT\JWT
262 *
263 * @param int $type DER tag
264 * @param string $value the value to encode
265 * @return string the encoded object
266 */
267 private static function encodeDER(int $type, string $value): string
268 {
269 $tag_header = 0;
270 if ($type === self::ASN1_SEQUENCE) {
271 $tag_header |= 0x20;
272 }
273
274 // Type
275 $der = \chr($tag_header | $type);
276
277 // Length
278 $der .= \chr(\strlen($value));
279
280 return $der . $value;
281 }
282
283 /**
284 * Encodes a string into a DER-encoded OID.
285 *
286 * @param string $oid the OID string
287 * @return string the binary DER-encoded OID
288 */
289 private static function encodeOID(string $oid): string
290 {
291 $octets = explode('.', $oid);
292
293 // Get the first octet
294 $first = (int) array_shift($octets);
295 $second = (int) array_shift($octets);
296 $oid = \chr($first * 40 + $second);
297
298 // Iterate over subsequent octets
299 foreach ($octets as $octet) {
300 if ($octet == 0) {
301 $oid .= \chr(0x00);
302 continue;
303 }
304 $bin = '';
305
306 while ($octet) {
307 $bin .= \chr(0x80 | ($octet & 0x7f));
308 $octet >>= 7;
309 }
310 $bin[0] = $bin[0] & \chr(0x7f);
311
312 // Convert to big endian if necessary
313 if (pack('V', 65534) == pack('L', 65534)) {
314 $oid .= strrev($bin);
315 } else {
316 $oid .= $bin;
317 }
318 }
319
320 return $oid;
321 }
322 }
323