This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Del\Auth; |
||
4 | |||
5 | use Exception; |
||
6 | |||
7 | class GoogleAuthenticator |
||
8 | { |
||
9 | protected $_codeLength = 6; |
||
10 | |||
11 | /** |
||
12 | * Create new secret. |
||
13 | * 16 characters, randomly chosen from the allowed base32 characters. |
||
14 | * |
||
15 | * @param int $secretLength |
||
16 | * |
||
17 | * @return string |
||
18 | */ |
||
19 | public function createSecret($secretLength = 16) |
||
20 | { |
||
21 | $validChars = $this->_getBase32LookupTable(); |
||
22 | |||
23 | // Valid secret lengths are 80 to 640 bits |
||
24 | if ($secretLength < 16 || $secretLength > 128) { |
||
25 | throw new Exception('Bad secret length'); |
||
26 | } |
||
27 | $secret = ''; |
||
28 | $rnd = false; |
||
29 | if (function_exists('random_bytes')) { |
||
30 | $rnd = random_bytes($secretLength); |
||
31 | } elseif (function_exists('mcrypt_create_iv')) { |
||
32 | $rnd = mcrypt_create_iv($secretLength, MCRYPT_DEV_URANDOM); |
||
33 | } elseif (function_exists('openssl_random_pseudo_bytes')) { |
||
34 | $rnd = openssl_random_pseudo_bytes($secretLength, $cryptoStrong); |
||
35 | if (!$cryptoStrong) { |
||
0 ignored issues
–
show
|
|||
36 | $rnd = false; |
||
37 | } |
||
38 | } |
||
39 | if ($rnd !== false) { |
||
40 | for ($i = 0; $i < $secretLength; ++$i) { |
||
41 | $secret .= $validChars[ord($rnd[$i]) & 31]; |
||
42 | } |
||
43 | } else { |
||
44 | throw new Exception('No source of secure random'); |
||
45 | } |
||
46 | |||
47 | return $secret; |
||
48 | } |
||
49 | |||
50 | /** |
||
51 | * Calculate the code, with given secret and point in time. |
||
52 | * |
||
53 | * @param string $secret |
||
54 | * @param int|null $timeSlice |
||
55 | * |
||
56 | * @return string |
||
57 | */ |
||
58 | public function getCode($secret, $timeSlice = null) |
||
59 | { |
||
60 | if ($timeSlice === null) { |
||
61 | $timeSlice = floor(time() / 30); |
||
62 | } |
||
63 | |||
64 | $secretkey = $this->_base32Decode($secret); |
||
65 | |||
66 | // Pack time into binary string |
||
67 | $time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice); |
||
68 | // Hash it with users secret key |
||
69 | $hm = hash_hmac('SHA1', $time, $secretkey, true); |
||
70 | // Use last nipple of result as index/offset |
||
71 | $offset = ord(substr($hm, -1)) & 0x0F; |
||
72 | // grab 4 bytes of the result |
||
73 | $hashpart = substr($hm, $offset, 4); |
||
74 | |||
75 | // Unpak binary value |
||
76 | $value = unpack('N', $hashpart); |
||
77 | $value = $value[1]; |
||
78 | // Only 32 bits |
||
79 | $value = $value & 0x7FFFFFFF; |
||
80 | |||
81 | $modulo = pow(10, $this->_codeLength); |
||
82 | |||
83 | return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT); |
||
84 | } |
||
85 | |||
86 | /** |
||
87 | * Get QR-Code URL for image, from google charts. |
||
88 | * |
||
89 | * @param string $name |
||
90 | * @param string $secret |
||
91 | * @param string $title |
||
92 | * @param array $params |
||
93 | * |
||
94 | * @return string |
||
95 | */ |
||
96 | public function getQRCodeGoogleUrl($name, $secret, $title = null, $params = array()) |
||
97 | { |
||
98 | $width = !empty($params['width']) && (int) $params['width'] > 0 ? (int) $params['width'] : 200; |
||
99 | $height = !empty($params['height']) && (int) $params['height'] > 0 ? (int) $params['height'] : 200; |
||
100 | $level = !empty($params['level']) && array_search($params['level'], array('L', 'M', 'Q', 'H')) !== false ? $params['level'] : 'M'; |
||
101 | |||
102 | $urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.''); |
||
103 | if (isset($title)) { |
||
104 | $urlencoded .= urlencode('&issuer='.urlencode($title)); |
||
105 | } |
||
106 | |||
107 | return 'https://chart.googleapis.com/chart?chs='.$width.'x'.$height.'&chld='.$level.'|0&cht=qr&chl='.$urlencoded.''; |
||
108 | } |
||
109 | |||
110 | /** |
||
111 | * Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now. |
||
112 | * |
||
113 | * @param string $secret |
||
114 | * @param string $code |
||
115 | * @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after) |
||
116 | * @param int|null $currentTimeSlice time slice if we want use other that time() |
||
117 | * |
||
118 | * @return bool |
||
119 | */ |
||
120 | public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null) |
||
121 | { |
||
122 | if ($currentTimeSlice === null) { |
||
123 | $currentTimeSlice = floor(time() / 30); |
||
124 | } |
||
125 | |||
126 | if (strlen($code) != 6) { |
||
127 | return false; |
||
128 | } |
||
129 | |||
130 | for ($i = -$discrepancy; $i <= $discrepancy; ++$i) { |
||
131 | $calculatedCode = $this->getCode($secret, $currentTimeSlice + $i); |
||
132 | if ($this->timingSafeEquals($calculatedCode, $code)) { |
||
133 | return true; |
||
134 | } |
||
135 | } |
||
136 | |||
137 | return false; |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * Set the code length, should be >=6. |
||
142 | * |
||
143 | * @param int $length |
||
144 | * |
||
145 | * @return PHPGangsta_GoogleAuthenticator |
||
146 | */ |
||
147 | public function setCodeLength($length) |
||
148 | { |
||
149 | $this->_codeLength = $length; |
||
150 | |||
151 | return $this; |
||
152 | } |
||
153 | |||
154 | /** |
||
155 | * Helper class to decode base32. |
||
156 | * |
||
157 | * @param $secret |
||
158 | * |
||
159 | * @return bool|string |
||
160 | */ |
||
161 | protected function _base32Decode($secret) |
||
162 | { |
||
163 | if (empty($secret)) { |
||
164 | return ''; |
||
165 | } |
||
166 | |||
167 | $base32chars = $this->_getBase32LookupTable(); |
||
168 | $base32charsFlipped = array_flip($base32chars); |
||
169 | |||
170 | $paddingCharCount = substr_count($secret, $base32chars[32]); |
||
171 | $allowedValues = array(6, 4, 3, 1, 0); |
||
172 | if (!in_array($paddingCharCount, $allowedValues)) { |
||
173 | return false; |
||
174 | } |
||
175 | for ($i = 0; $i < 4; ++$i) { |
||
176 | if ($paddingCharCount == $allowedValues[$i] && |
||
177 | substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) { |
||
178 | return false; |
||
179 | } |
||
180 | } |
||
181 | $secret = str_replace('=', '', $secret); |
||
182 | $secret = str_split($secret); |
||
183 | $binaryString = ''; |
||
184 | for ($i = 0; $i < count($secret); $i = $i + 8) { |
||
0 ignored issues
–
show
It seems like you are calling the size function
count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}
// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
![]() |
|||
185 | $x = ''; |
||
186 | if (!in_array($secret[$i], $base32chars)) { |
||
187 | return false; |
||
188 | } |
||
189 | for ($j = 0; $j < 8; ++$j) { |
||
190 | $x .= str_pad(base_convert(@$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT); |
||
191 | } |
||
192 | $eightBits = str_split($x, 8); |
||
193 | for ($z = 0; $z < count($eightBits); ++$z) { |
||
0 ignored issues
–
show
It seems like you are calling the size function
count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}
// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
![]() |
|||
194 | $binaryString .= (($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48) ? $y : ''; |
||
195 | } |
||
196 | } |
||
197 | |||
198 | return $binaryString; |
||
199 | } |
||
200 | |||
201 | /** |
||
202 | * Get array with all 32 characters for decoding from/encoding to base32. |
||
203 | * |
||
204 | * @return array |
||
205 | */ |
||
206 | protected function _getBase32LookupTable() |
||
207 | { |
||
208 | return array( |
||
209 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7 |
||
210 | 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15 |
||
211 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23 |
||
212 | 'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31 |
||
213 | '=', // padding char |
||
214 | ); |
||
215 | } |
||
216 | |||
217 | /** |
||
218 | * A timing safe equals comparison |
||
219 | * more info here: http://blog.ircmaxell.com/2014/11/its-all-about-time.html. |
||
220 | * |
||
221 | * @param string $safeString The internal (safe) value to be checked |
||
222 | * @param string $userString The user submitted (unsafe) value |
||
223 | * |
||
224 | * @return bool True if the two strings are identical |
||
225 | */ |
||
226 | private function timingSafeEquals($safeString, $userString) |
||
227 | { |
||
228 | if (function_exists('hash_equals')) { |
||
229 | return hash_equals($safeString, $userString); |
||
230 | } |
||
231 | $safeLen = strlen($safeString); |
||
232 | $userLen = strlen($userString); |
||
233 | |||
234 | if ($userLen != $safeLen) { |
||
235 | return false; |
||
236 | } |
||
237 | |||
238 | $result = 0; |
||
239 | |||
240 | for ($i = 0; $i < $userLen; ++$i) { |
||
241 | $result |= (ord($safeString[$i]) ^ ord($userString[$i])); |
||
242 | } |
||
243 | |||
244 | // They are only identical strings if $result is exactly 0... |
||
245 | return $result === 0; |
||
246 | } |
||
247 | } |
||
248 |
If an expression can have both
false
, andnull
as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.