Completed
Push — master ( 9d71dc...200e3f )
by Дмитрий
03:55
created

JWT::urlsafeB64Decode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 0
cts 9
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 1
crap 6
1
<?php
2
/**
3
 * @author Patsura Dmitry https://github.com/ovr <[email protected]>
4
 */
5
6
namespace SocialConnect\OpenIDConnect;
7
8
use SocialConnect\OpenIDConnect\Exception\InvalidJWT;
9
use SocialConnect\OpenIDConnect\Exception\UnsupportedSignatureAlgoritm;
10
11
class JWT
12
{
13
    /**
14
     * Map of supported algorithms
15
     *
16
     * @var array
17
     */
18
    public static $algorithms = array(
19
        // HS
20
        'HS256' => ['hash_hmac', MHASH_SHA256],
21
        'HS384' => ['hash_hmac', MHASH_SHA384],
22
        'HS512' => ['hash_hmac', MHASH_SHA512],
23
        // RS
24
        'RS256' => ['openssl', OPENSSL_ALGO_SHA256],
25
        'RS384' => ['openssl', OPENSSL_ALGO_SHA384],
26
        'RS512' => ['openssl', OPENSSL_ALGO_SHA512],
27
    );
28
29
    /**
30
     * @var array
31
     */
32
    protected $parts;
33
34
    /**
35
     * @var array
36
     */
37
    protected $header;
38
39
    /**
40
     * @var array
41
     */
42
    protected $payload;
43
44
    /**
45
     * @var string
46
     */
47
    protected $signature;
48
49
    /**
50
     * @param string $input
51
     * @return string
52
     */
53
    public static function urlsafeB64Decode($input)
54
    {
55
        $remainder = strlen($input) % 4;
56
57
        if ($remainder) {
58
            $padlen = 4 - $remainder;
59
            $input .= str_repeat('=', $padlen);
60
        }
61
62
        return base64_decode(strtr($input, '-_', '+/'));
63
    }
64
65
    /**
66
     * @param string $token
67
     * @param array $keys
68
     * @throws InvalidJWT
69
     */
70
    public function __construct($token, array $keys)
71
    {
72
        $parts = explode('.', $token);
73
        if (count($parts) !== 3) {
74
            throw new InvalidJWT('Wrong number of segments');
75
        }
76
77
        list ($header64, $payload64, $token64) = $parts;
78
79
        $headerPayload = base64_decode($header64, true);
80
        $this->header = json_decode($headerPayload);
0 ignored issues
show
Documentation Bug introduced by
It seems like json_decode($headerPayload) of type * is incompatible with the declared type array of property $header.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
81
82
        $decodedPayload = base64_decode($payload64, true);
83
        $this->payload = json_decode($decodedPayload);
0 ignored issues
show
Documentation Bug introduced by
It seems like json_decode($decodedPayload) of type * is incompatible with the declared type array of property $payload.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
84
85
        $this->signature = self::urlsafeB64Decode($token64);
86
87
        $this->validate("{$header64}.{$payload64}", $keys);
88
    }
89
90
    /**
91
     * @param string $data
92
     * @param array $keys
93
     * @throws InvalidJWT
94
     */
95
    protected function validate($data, array $keys)
96
    {
97
        if (!isset($this->header->alg)) {
98
            throw new InvalidJWT('No alg inside header');
99
        }
100
101
        if (!isset($this->header->kid)) {
102
            throw new InvalidJWT('No kid inside header');
103
        }
104
105
        $result = $this->verifySignature($data, $keys);
106
        if (!$result) {
107
            throw new InvalidJWT('Unexpected signature');
108
        }
109
    }
110
111
    /**
112
     * @param array $keys
113
     * @param string $kid
114
     * @return JWK
115
     * @throws \RuntimeException
116
     */
117
    protected function findKeyByKind(array $keys, $kid)
118
    {
119
        foreach ($keys as $key) {
120
            if ($key['kid'] === $kid) {
121
                return new JWK($key);
122
            }
123
        }
124
125
        throw new \RuntimeException('Unknown key');
126
    }
127
128
    /**
129
     * @return bool
130
     * @throws \RuntimeException
131
     * @throws \SocialConnect\OpenIDConnect\Exception\UnsupportedSignatureAlgoritm
132
     */
133
    protected function verifySignature($data, array $keys)
134
    {
135
        $supported = isset(self::$algorithms[$this->header->alg]);
136
        if (!$supported) {
137
            throw new UnsupportedSignatureAlgoritm($this->header->alg);
138
        }
139
140
        list ($function, $signatureAlg) = self::$algorithms[$this->header->alg];
141
        switch ($function) {
142
            case 'openssl':
143
                if (!function_exists('openssl_verify')) {
144
                    throw new \RuntimeException('Openssl-ext is required to use RSA encryption.');
145
                }
146
147
                $jwk = $this->findKeyByKind($keys, $this->header->kid);
148
149
                $result = openssl_verify(
150
                    $data,
151
                    $this->signature,
152
                    $jwk->getPublicKey(),
153
                    $signatureAlg
154
                );
155
                
156
                return $result == 1;
157
        }
158
159
        throw new UnsupportedSignatureAlgoritm($this->header->alg);
160
    }
161
}
162