Completed
Pull Request — develop (#816)
by Kristijan
07:28
created

Jwt::decode()   D

Complexity

Conditions 10
Paths 9

Size

Total Lines 40
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 40
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 19
nc 9
nop 3

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace OAuth2\Encryption;
4
5
/**
6
 * @link https://github.com/F21/jwt
7
 * @author F21
8
 */
9
class Jwt implements EncryptionInterface
10
{
11
    public function encode($payload, $key, $algo = 'HS256')
12
    {
13
        $header = $this->generateJwtHeader($payload, $algo);
14
15
        $segments = array(
16
            $this->urlSafeB64Encode(json_encode($header)),
17
            $this->urlSafeB64Encode(json_encode($payload))
18
        );
19
20
        $signing_input = implode('.', $segments);
21
22
        $signature = $this->sign($signing_input, $key, $algo);
23
        $segments[] = $this->urlsafeB64Encode($signature);
24
25
        return implode('.', $segments);
26
    }
27
28
    public function decode($jwt, $key = null, $allowedAlgorithms = true)
29
    {
30
        if (!strpos($jwt, '.')) {
31
            return false;
32
        }
33
34
        $tks = explode('.', $jwt);
35
36
        if (count($tks) != 3) {
37
            return false;
38
        }
39
40
        list($headb64, $payloadb64, $cryptob64) = $tks;
41
42
        if (null === ($header = json_decode($this->urlSafeB64Decode($headb64), true))) {
43
            return false;
44
        }
45
46
        if (null === $payload = json_decode($this->urlSafeB64Decode($payloadb64), true)) {
47
            return false;
48
        }
49
50
        $sig = $this->urlSafeB64Decode($cryptob64);
51
52
        if ((bool) $allowedAlgorithms) {
53
            if (!isset($header['alg'])) {
54
                return false;
55
            }
56
57
            // check if bool arg supplied here to maintain BC
58
            if (is_array($allowedAlgorithms) && !in_array($header['alg'], $allowedAlgorithms)) {
59
                return false;
60
            }
61
62
            if (!$this->verifySignature($sig, "$headb64.$payloadb64", $key, $header['alg'])) {
63
                return false;
64
            }
65
        }
66
67
        return $payload;
68
    }
69
70
    private function verifySignature($signature, $input, $key, $algo = 'HS256')
71
    {
72
        // use constants when possible, for HipHop support
73
        switch ($algo) {
74
            case'HS256':
75
            case'HS384':
76
            case'HS512':
77
                return $this->hash_equals(
78
                    $this->sign($input, $key, $algo),
79
                    $signature
80
                );
81
82
            case 'RS256':
83
                return openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256')  === 1;
0 ignored issues
show
Bug introduced by
It seems like defined('OPENSSL_ALGO_SH..._ALGO_SHA256 : 'sha256' can also be of type string; however, parameter $signature_alg of openssl_verify() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

83
                return openssl_verify($input, $signature, $key, /** @scrutinizer ignore-type */ defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256')  === 1;
Loading history...
84
85
            case 'RS384':
86
                return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384') === 1;
87
88
            case 'RS512':
89
                return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512') === 1;
90
91
            default:
92
                throw new \InvalidArgumentException("Unsupported or invalid signing algorithm.");
93
        }
94
    }
95
96
    private function sign($input, $key, $algo = 'HS256')
97
    {
98
        switch ($algo) {
99
            case 'HS256':
100
                return hash_hmac('sha256', $input, $key, true);
101
102
            case 'HS384':
103
                return hash_hmac('sha384', $input, $key, true);
104
105
            case 'HS512':
106
                return hash_hmac('sha512', $input, $key, true);
107
108
            case 'RS256':
109
                return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256');
110
111
            case 'RS384':
112
                return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384');
113
114
            case 'RS512':
115
                return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512');
116
117
            default:
118
                throw new \Exception("Unsupported or invalid signing algorithm.");
119
        }
120
    }
121
122
    private function generateRSASignature($input, $key, $algo)
123
    {
124
        if (!openssl_sign($input, $signature, $key, $algo)) {
125
            throw new \Exception("Unable to sign data.");
126
        }
127
128
        return $signature;
129
    }
130
131
    public function urlSafeB64Encode($data)
132
    {
133
        $b64 = base64_encode($data);
134
        $b64 = str_replace(array('+', '/', "\r", "\n", '='),
135
                array('-', '_'),
136
                $b64);
137
138
        return $b64;
139
    }
140
141
    public function urlSafeB64Decode($b64)
142
    {
143
        $b64 = str_replace(array('-', '_'),
144
                array('+', '/'),
145
                $b64);
146
147
        return base64_decode($b64);
148
    }
149
150
    /**
151
     * Override to create a custom header
152
     */
153
    protected function generateJwtHeader($payload, $algorithm)
0 ignored issues
show
Unused Code introduced by
The parameter $payload is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

153
    protected function generateJwtHeader(/** @scrutinizer ignore-unused */ $payload, $algorithm)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
154
    {
155
        return array(
156
            'typ' => 'JWT',
157
            'alg' => $algorithm,
158
        );
159
    }
160
161
    protected function hash_equals($a, $b)
162
    {
163
        if (function_exists('hash_equals')) {
164
            return hash_equals($a, $b);
165
        }
166
        $diff = strlen($a) ^ strlen($b);
167
        for ($i = 0; $i < strlen($a) && $i < strlen($b); $i++) {
168
            $diff |= ord($a[$i]) ^ ord($b[$i]);
169
        }
170
171
        return $diff === 0;
172
    }
173
}
174