Subversion Repositories oidplus

Rev

Rev 604 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
604 daniel-mar 1
<?php
2
 
3
/*
4
 * JWT Decoder for PHP
5
 * Copyright 2021 Daniel Marschall, ViaThinkSoft
6
 * Version 2021-05-15
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
 
21
function decode_idtoken($id_token, $verification_certs=null, $allowed_algorithms = array()) {
22
        // Parts taken and simplified from https://github.com/firebase/php-jwt , licensed by BSD-3-clause
23
        // Here is a great page for encode and decode tokens for testing: https://jwt.io/
24
 
25
        $parts = explode('.', $id_token);
26
        if (count($parts) === 5) return false; // encrypted JWT not yet supported
27
        if (count($parts) !== 3) return false;
28
        list($header_base64, $payload_base64, $signature_base64) = $parts;
29
 
30
        $header_ary = json_decode(urlsafeB64Decode($header_base64),true);
31
        if ($header_ary['typ'] !== 'JWT') return false;
32
 
33
        if ($verification_certs) {
34
                $key = isset($header_ary['kid']) ? $verification_certs[$header_ary['kid']] : $verification_certs;
35
 
36
                $msg = $header_base64.'.'.$payload_base64;
37
                $signature = urlsafeB64Decode($signature_base64);
38
 
39
                $jwt_algo = $header_ary['alg'];
40
 
41
                // see https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
42
                //     https://datatracker.ietf.org/doc/html/rfc8725#section-3.1
43
                if (!in_array($jwt_algo, $allowed_algorithms)) return false;
44
 
45
                if ($jwt_algo != 'none') {
46
                        $php_algo = 'SHA'.substr($jwt_algo,2,3);
47
                        switch (substr($jwt_algo,0,2)) {
48
                                case 'ES':
49
                                        // OpenSSL expects an ASN.1 DER sequence for ES256 signatures
50
                                        $signature = signatureToDER($signature);
51
                                        if (!function_exists('openssl_verify')) break; // if OpenSSL is not installed, we just accept the JWT
52
                                        if (!openssl_verify($msg, $signature, $key, $php_algo)) return false;
53
                                        break;
54
                                case 'RS':
55
                                        if (!function_exists('openssl_verify')) break; // if OpenSSL is not installed, we just accept the JWT
56
                                        if (!openssl_verify($msg, $signature, $key, $php_algo)) return false;
57
                                        break;
58
                                case 'HS':
59
                                        $hash = @hash_hmac($php_algo, $msg, $key, true);
60
                                        if (!$hash) break; // if the hash algo is not available, we just accept the JWT
1469 daniel-mar 61
                                        if (!hash_equals($hash, $signature)) return false;
604 daniel-mar 62
                                        break;
63
                                case 'PS':
64
                                        // This feature is new and not yet available in php-jwt
65
                                        file_put_contents($msg_file = tempnam("/tmp", ""), $msg);
66
                                        file_put_contents($sig_file = tempnam("/tmp", ""), $signature);
67
                                        file_put_contents($key_file = tempnam("/tmp", ""), $key);
68
                                        $ec = -1;
69
                                        $out = array();
70
                                        $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);
71
                                        $cmd .= (strtoupper(substr(PHP_OS,0,3)) === 'WIN') ? ' 2> NUL' : ' 2> /dev/null';
72
                                        exec($cmd, $out, $ec);
73
                                        unlink($msg_file);
74
                                        unlink($sig_file);
75
                                        unlink($key_file);
76
                                        if (($ec !== 0) && (count($out) === 0)) break; // If OpenSSL is not found, we just accept the JWT
77
                                        if (($ec !== 0) || (strpos(implode("\n",$out),"Verified OK") === false)) return false;
78
                                        break;
79
                                default:
80
                                        return false;
81
                        }
82
                }
83
        }
84
 
85
        $payload_ary = json_decode(urlsafeB64Decode($payload_base64), true);
86
 
87
        $leeway = 60; // 1 Minute
88
        if (isset($payload_ary['nbf']) && (time()+$leeway<$payload_ary['nbf'])) return false;
89
        if (isset($payload_ary['exp']) && (time()-$leeway>$payload_ary['exp'])) return false;
90
 
91
        return $payload_ary;
92
}
93
 
94
function urlsafeB64Decode($input) {
95
        // Taken from https://github.com/firebase/php-jwt , licensed by BSD-3-clause
96
        $remainder = strlen($input) % 4;
97
        if ($remainder) {
98
                $padlen = 4 - $remainder;
99
                $input .= str_repeat('=', $padlen);
100
        }
101
        return base64_decode(strtr($input, '-_', '+/'));
102
}
103
 
104
function signatureToDER($sig) {
105
        // Taken from https://github.com/firebase/php-jwt , licensed by BSD-3-clause, modified
106
 
107
        // Separate the signature into r-value and s-value
108
        list($r, $s) = str_split($sig, (int) (strlen($sig) / 2));
109
 
110
        // Trim leading zeros
111
        $r = ltrim($r, "\x00");
112
        $s = ltrim($s, "\x00");
113
 
114
        // Convert r-value and s-value from unsigned big-endian integers to signed two's complement
115
        if (ord($r[0]) > 0x7f) $r = "\x00" . $r;
116
        if (ord($s[0]) > 0x7f) $s = "\x00" . $s;
117
 
118
        $der_r = chr(0x00/*primitive*/ | 0x02/*INTEGER*/).chr(strlen($r)).$r;
119
        $der_s = chr(0x00/*primitive*/ | 0x02/*INTEGER*/).chr(strlen($s)).$s;
120
        $der = chr(0x20/*constructed*/ | 0x10/*SEQUENCE*/).chr(strlen($der_r.$der_s)).$der_r.$der_s;
121
        return $der;
122
}