Subversion Repositories oidplus

Rev

Rev 565 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 565 Rev 566
Line 70... Line 70...
70
 
70
 
71
try {
71
try {
72
 
72
 
73
        // Decode JWT "id_token"
73
        // Decode JWT "id_token"
74
        // see https://medium.com/@darutk/understanding-id-token-5f83f50fa02e
74
        // see https://medium.com/@darutk/understanding-id-token-5f83f50fa02e
75
        // and https://github.com/firebase/php-jwt/blob/master/src/JWT.php
-
 
76
        // Note: We do not need to verify the signature because the token comes directly from Google,
75
        // Note: We do not need to verify the signature because the token comes directly from Google,
77
        //       but we do it anyway. Just to be sure!
76
        //       but we do it anyway. Just to be sure!
78
        $verification_certs = json_decode(file_get_contents('https://www.googleapis.com/oauth2/v1/certs'), true);
77
        $verification_certs = json_decode(file_get_contents('https://www.googleapis.com/oauth2/v1/certs'), true);
79
        $data = decode_idtoken($id_token, $verification_certs);
78
        $data = (array) \Firebase\JWT\JWT::decode($id_token, $verification_certs, array('ES256', 'RS256', 'RS384', 'RS512'));
80
        if (($data === false) || !isset($data['iss']) || ($data['iss'] !== 'https://accounts.google.com')) {
79
        if (($data === false) || !isset($data['iss']) || ($data['iss'] !== 'https://accounts.google.com')) {
81
                throw new OIDplusException(_L('JWT token could not be decoded'));
80
                throw new OIDplusException(_L('JWT token could not be decoded'));
82
        }
81
        }
83
 
82
 
84
        // Check if the email was verified
83
        // Check if the email was verified
Line 142... Line 141...
142
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
141
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
143
        curl_exec($ch);
142
        curl_exec($ch);
144
        curl_close($ch);
143
        curl_close($ch);
145
 
144
 
146
}
145
}
147
 
-
 
148
# ----------------------------------
-
 
149
 
-
 
150
function decode_idtoken($id_token, $verification_certs=null) {
-
 
151
        // Parts taken and simplified from https://github.com/firebase/php-jwt , licensed by BSD-3-clause
-
 
152
        // Here is a great page for encode and decode tokens for testing: https://jwt.io/
-
 
153
 
-
 
154
        $parts = explode('.', $id_token);
-
 
155
        if (count($parts) === 5) return false; // encrypted JWT not yet supported
-
 
156
        if (count($parts) !== 3) return false;
-
 
157
        list($header_base64, $payload_base64, $signature_base64) = $parts;
-
 
158
 
-
 
159
        $header_ary = json_decode(urlsafeB64Decode($header_base64),true);
-
 
160
        if ($header_ary['typ'] !== 'JWT') return false;
-
 
161
 
-
 
162
        if ($verification_certs) {
-
 
163
                $key = isset($header_ary['kid']) ? $verification_certs[$header_ary['kid']] : $verification_certs;
-
 
164
 
-
 
165
                $msg = $header_base64.'.'.$payload_base64;
-
 
166
                $signature = urlsafeB64Decode($signature_base64);
-
 
167
 
-
 
168
                $jwt_algo = $header_ary['alg'];
-
 
169
                if ($jwt_algo != 'none') {
-
 
170
                        $php_algo = 'SHA'.substr($jwt_algo,2,3);
-
 
171
                        switch (substr($jwt_algo,0,2)) {
-
 
172
                                case 'ES':
-
 
173
                                        // OpenSSL expects an ASN.1 DER sequence for ES256 signatures
-
 
174
                                        $signature = signatureToDER($signature);
-
 
175
                                        if (!function_exists('openssl_verify')) break; // if OpenSSL is not installed, we just accept the JWT
-
 
176
                                        if (!openssl_verify($msg, $signature, $key, $php_algo)) return false;
-
 
177
                                        break;
-
 
178
                                case 'RS':
-
 
179
                                        if (!function_exists('openssl_verify')) break; // if OpenSSL is not installed, we just accept the JWT
-
 
180
                                        if (!openssl_verify($msg, $signature, $key, $php_algo)) return false;
-
 
181
                                        break;
-
 
182
                                case 'HS':
-
 
183
                                        $hash = @hash_hmac($php_algo, $msg, $key, true);
-
 
184
                                        if (!$hash) break; // if the hash algo is not available, we just accept the JWT
-
 
185
                                        if (!hash_equals($signature, $hash)) return false;
-
 
186
                                        break;
-
 
187
                                case 'PS':
-
 
188
                                        file_put_contents($msg_file = tempnam("/tmp", ""), $msg);
-
 
189
                                        file_put_contents($sig_file = tempnam("/tmp", ""), $signature);
-
 
190
                                        file_put_contents($key_file = tempnam("/tmp", ""), $key);
-
 
191
                                        $ec = -1;
-
 
192
                                        $out = array();
-
 
193
                                        $cmd = "openssl dgst -".strtolower($php_algo)." -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1 -verify ".escapeshellarg($key_file)." -signature ".escapeshellarg($sig_file)." ".escapeshellarg($msg_file);
-
 
194
                                        $cmd .= (strtoupper(substr(PHP_OS,0,3)) === 'WIN') ? ' 2> NUL' : ' 2> /dev/null';
-
 
195
                                        exec($cmd, $out, $ec);
-
 
196
                                        unlink($msg_file);
-
 
197
                                        unlink($sig_file);
-
 
198
                                        unlink($key_file);
-
 
199
                                        if (($ec !== 0) && (count($out) === 0)) break; // If OpenSSL is not found, we just accept the JWT
-
 
200
                                        if (($ec !== 0) || (strpos(implode("\n",$out),"Verified OK") === false)) return false;
-
 
201
                                        break;
-
 
202
                                default:
-
 
203
                                        return false;
-
 
204
                        }
-
 
205
                }
-
 
206
        }
-
 
207
 
-
 
208
        $payload_ary = json_decode(urlsafeB64Decode($payload_base64), true);
-
 
209
 
-
 
210
        $leeway = 60; // 1 Minute
-
 
211
        if (isset($payload_ary['nbf']) && (time()+$leeway<$payload_ary['nbf'])) return false;
-
 
212
        if (isset($payload_ary['exp']) && (time()-$leeway>$payload_ary['exp'])) return false;
-
 
213
 
-
 
214
        return $payload_ary;
-
 
215
}
-
 
216
 
-
 
217
function urlsafeB64Decode($input) {
-
 
218
        // Taken from https://github.com/firebase/php-jwt , licensed by BSD-3-clause
-
 
219
        $remainder = strlen($input) % 4;
-
 
220
        if ($remainder) {
-
 
221
                $padlen = 4 - $remainder;
-
 
222
                $input .= str_repeat('=', $padlen);
-
 
223
        }
-
 
224
        return base64_decode(strtr($input, '-_', '+/'));
-
 
225
}
-
 
226
 
-
 
227
function signatureToDER($sig) {
-
 
228
        // Taken from https://github.com/firebase/php-jwt , licensed by BSD-3-clause, modified
-
 
229
 
-
 
230
        // Separate the signature into r-value and s-value
-
 
231
        list($r, $s) = str_split($sig, (int) (strlen($sig) / 2));
-
 
232
 
-
 
233
        // Trim leading zeros
-
 
234
        $r = ltrim($r, "\x00");
-
 
235
        $s = ltrim($s, "\x00");
-
 
236
 
-
 
237
        // Convert r-value and s-value from unsigned big-endian integers to signed two's complement
-
 
238
        if (ord($r[0]) > 0x7f) $r = "\x00" . $r;
-
 
239
        if (ord($s[0]) > 0x7f) $s = "\x00" . $s;
-
 
240
 
-
 
241
        $der_r = chr(0x00/*primitive*/ | 0x02/*INTEGER*/).chr(strlen($r)).$r;
-
 
242
        $der_s = chr(0x00/*primitive*/ | 0x02/*INTEGER*/).chr(strlen($s)).$s;
-
 
243
        $der = chr(0x20/*constructed*/ | 0x10/*SEQUENCE*/).chr(strlen($der_r.$der_s)).$der_r.$der_s;
-
 
244
        return $der;
-
 
245
}
-