blob: c258b8fc3bf9ff47788a746006148ab82c11ef9c [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2
3namespace OAuth2\Encryption;
4
5use Exception;
6use InvalidArgumentException;
7
8/**
9 * @link https://github.com/F21/jwt
10 * @author F21
11 */
12class Jwt implements EncryptionInterface
13{
14 /**
15 * @param $payload
16 * @param $key
17 * @param string $algo
18 * @return string
19 */
20 public function encode($payload, $key, $algo = 'HS256')
21 {
22 $header = $this->generateJwtHeader($payload, $algo);
23
24 $segments = array(
25 $this->urlSafeB64Encode(json_encode($header)),
26 $this->urlSafeB64Encode(json_encode($payload))
27 );
28
29 $signing_input = implode('.', $segments);
30
31 $signature = $this->sign($signing_input, $key, $algo);
32 $segments[] = $this->urlsafeB64Encode($signature);
33
34 return implode('.', $segments);
35 }
36
37 /**
38 * @param string $jwt
39 * @param null $key
40 * @param array|bool $allowedAlgorithms
41 * @return bool|mixed
42 */
43 public function decode($jwt, $key = null, $allowedAlgorithms = true)
44 {
45 if (!strpos($jwt, '.')) {
46 return false;
47 }
48
49 $tks = explode('.', $jwt);
50
51 if (count($tks) != 3) {
52 return false;
53 }
54
55 list($headb64, $payloadb64, $cryptob64) = $tks;
56
57 if (null === ($header = json_decode($this->urlSafeB64Decode($headb64), true))) {
58 return false;
59 }
60
61 if (null === $payload = json_decode($this->urlSafeB64Decode($payloadb64), true)) {
62 return false;
63 }
64
65 $sig = $this->urlSafeB64Decode($cryptob64);
66
67 if ((bool) $allowedAlgorithms) {
68 if (!isset($header['alg'])) {
69 return false;
70 }
71
72 // check if bool arg supplied here to maintain BC
73 if (is_array($allowedAlgorithms) && !in_array($header['alg'], $allowedAlgorithms)) {
74 return false;
75 }
76
77 if (!$this->verifySignature($sig, "$headb64.$payloadb64", $key, $header['alg'])) {
78 return false;
79 }
80 }
81
82 return $payload;
83 }
84
85 /**
86 * @param $signature
87 * @param $input
88 * @param $key
89 * @param string $algo
90 * @return bool
91 * @throws InvalidArgumentException
92 */
93 private function verifySignature($signature, $input, $key, $algo = 'HS256')
94 {
95 // use constants when possible, for HipHop support
96 switch ($algo) {
97 case'HS256':
98 case'HS384':
99 case'HS512':
100 return $this->hash_equals(
101 $this->sign($input, $key, $algo),
102 $signature
103 );
104
105 case 'RS256':
106 return openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256') === 1;
107
108 case 'RS384':
109 return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384') === 1;
110
111 case 'RS512':
112 return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512') === 1;
113
114 default:
115 throw new InvalidArgumentException("Unsupported or invalid signing algorithm.");
116 }
117 }
118
119 /**
120 * @param $input
121 * @param $key
122 * @param string $algo
123 * @return string
124 * @throws Exception
125 */
126 private function sign($input, $key, $algo = 'HS256')
127 {
128 switch ($algo) {
129 case 'HS256':
130 return hash_hmac('sha256', $input, $key, true);
131
132 case 'HS384':
133 return hash_hmac('sha384', $input, $key, true);
134
135 case 'HS512':
136 return hash_hmac('sha512', $input, $key, true);
137
138 case 'RS256':
139 return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256');
140
141 case 'RS384':
142 return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384');
143
144 case 'RS512':
145 return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512');
146
147 default:
148 throw new Exception("Unsupported or invalid signing algorithm.");
149 }
150 }
151
152 /**
153 * @param $input
154 * @param $key
155 * @param string $algo
156 * @return mixed
157 * @throws Exception
158 */
159 private function generateRSASignature($input, $key, $algo)
160 {
161 if (!openssl_sign($input, $signature, $key, $algo)) {
162 throw new Exception("Unable to sign data.");
163 }
164
165 return $signature;
166 }
167
168 /**
169 * @param string $data
170 * @return string
171 */
172 public function urlSafeB64Encode($data)
173 {
174 $b64 = base64_encode($data);
175 $b64 = str_replace(array('+', '/', "\r", "\n", '='),
176 array('-', '_'),
177 $b64);
178
179 return $b64;
180 }
181
182 /**
183 * @param string $b64
184 * @return mixed|string
185 */
186 public function urlSafeB64Decode($b64)
187 {
188 $b64 = str_replace(array('-', '_'),
189 array('+', '/'),
190 $b64);
191
192 return base64_decode($b64);
193 }
194
195 /**
196 * Override to create a custom header
197 */
198 protected function generateJwtHeader($payload, $algorithm)
199 {
200 return array(
201 'typ' => 'JWT',
202 'alg' => $algorithm,
203 );
204 }
205
206 /**
207 * @param string $a
208 * @param string $b
209 * @return bool
210 */
211 protected function hash_equals($a, $b)
212 {
213 if (function_exists('hash_equals')) {
214 return hash_equals($a, $b);
215 }
216 $diff = strlen($a) ^ strlen($b);
217 for ($i = 0; $i < strlen($a) && $i < strlen($b); $i++) {
218 $diff |= ord($a[$i]) ^ ord($b[$i]);
219 }
220
221 return $diff === 0;
222 }
223}