Completed
Push — master ( 8edf14...9f0de3 )
by Michael
36s queued 16s
created

JWK::parseKeySet()   B

Complexity

Conditions 7
Paths 12

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 12
c 1
b 0
f 0
dl 0
loc 23
rs 8.8333
cc 7
nc 12
nop 1
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 <[email protected]>
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');
0 ignored issues
show
Bug introduced by
The type Firebase\JWT\InvalidArgumentException was not found. Did you mean InvalidArgumentException? If so, make sure to prefix the type with \.
Loading history...
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
}
172