diff options
-rw-r--r-- | app/helpers.php | 52 | ||||
-rw-r--r-- | tests/HelpersTest.php | 15 |
2 files changed, 66 insertions, 1 deletions
diff --git a/app/helpers.php b/app/helpers.php index 3bce65f..8765fc6 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -1,6 +1,24 @@ <?php /** + * Register polyfills for old PHP versions. + * + * This way, the real function will only be called if it + * is available, and we won't force the use of our own + * implementation. + */ +function register_polyfills() +{ + if (!function_exists('hash_equals')) { + function hash_equals($known_string, $user_string) { + call_user_func_array('_hash_equals', func_get_args()); + } + } +} + +register_polyfills(); + +/** * Path to the _custom_ directory. * * @param string $file Append this filename to the returned path. @@ -72,4 +90,36 @@ function removeCustomFiles() unlink($path); } } -}
\ No newline at end of file +} + +/** + * Compare two strings in a constant-time manner. + * + * It returns `true` if both strings are exactly the same + * (same size and same value). + * + * @param string $known_string + * @param string $user_string + * @return bool + */ +function _hash_equals($known_string = '', $user_string = '') +{ + // In our case, it's not problematic if `$known_string`'s + // size leaks, we will only compare password hashes and + // CSRF tokens—their size is already somehow public. + if (!is_string($known_string) || !is_string($user_string) + || strlen($known_string) !== strlen($user_string)) { + return false; + } + + $ret = 0; + + // Do not stop the comparison when a difference is found, + // always completely compare them. + for ($i = 0; $i < strlen($known_string); $i++) { + $ret |= (ord($known_string[$i]) ^ ord($user_string[$i])); + } + + return !$ret; +} + diff --git a/tests/HelpersTest.php b/tests/HelpersTest.php new file mode 100644 index 0000000..141e604 --- /dev/null +++ b/tests/HelpersTest.php @@ -0,0 +1,15 @@ +<?php + +use PHPUnit\Framework\TestCase; + +class HelpersTest extends TestCase +{ + function test_constant_time_compare() + { + $this->assertTrue(_hash_equals('abc', 'abc')); + $this->assertFalse(_hash_equals('abc', 'ab')); + $this->assertFalse(_hash_equals('ab', 'abc')); + $this->assertFalse(_hash_equals('abcd', 'adbc')); + $this->assertFalse(_hash_equals(0, 0)); + } +} |