summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornashe <thomas@chauchefoin.fr>2017-12-18 22:59:42 +0100
committernashe <thomas@chauchefoin.fr>2017-12-18 22:59:42 +0100
commit0979b67e1baf88d7534d39c9744801a54d487b7f (patch)
treed05a7ceee24d1c1aefc64e724e5e4daec1bf7f36
parentf8a64e98db4bcc406196a33eb869d0bcff3a4058 (diff)
downloadplanet-0979b67e1baf88d7534d39c9744801a54d487b7f.tar
planet-0979b67e1baf88d7534d39c9744801a54d487b7f.tar.gz
planet-0979b67e1baf88d7534d39c9744801a54d487b7f.tar.bz2
planet-0979b67e1baf88d7534d39c9744801a54d487b7f.tar.xz
planet-0979b67e1baf88d7534d39c9744801a54d487b7f.zip
Add hash_equals polyfill and related tests
-rw-r--r--app/helpers.php52
-rw-r--r--tests/HelpersTest.php15
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));
+ }
+}