From 11b88458545536bad7f2a2348ee91fe5abfd8a6e Mon Sep 17 00:00:00 2001
From: Konrad Mohrfeldt <konrad.mohrfeldt@farbdev.org>
Date: Wed, 18 Jul 2018 02:05:51 +0200
Subject: [PATCH] add argon2i password hashing support

This adds argon2i password hashing support.
On PHP 7.2+ the built-in password_hash function is used
to generate a password. Whenever native support is not
present it will fallback to the libargon binary.
---
 lib/Horde/Auth.php | 55 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/lib/Horde/Auth.php b/lib/Horde/Auth.php
index caa3981..f31f388 100644
--- a/lib/Horde/Auth.php
+++ b/lib/Horde/Auth.php
@@ -176,6 +176,52 @@ class Horde_Auth
 
             return '$apr1$' . $salt . '$' . implode('', $p) . self::_toAPRMD5(ord($binary[11]), 3);
 
+        case 'argon2i':
+            $time_cost = 6;
+            $memory_cost = 12;
+            $parallelism = 1;
+
+            if (defined('PASSWORD_ARGON2I')) {
+                // we have built-in argon2i support
+                $options = array(
+                    'memory_cost' => pow(2, $memory_cost),
+                    'time_cost' => $time_cost,
+                    'threads' => $parallelism,
+                    'salt' => $salt,
+                );
+
+                $encrypted = password_hash($plaintext, PASSWORD_ARGON2I, $options);
+            } elseif (file_exists('/usr/bin/argon2')) {
+                // for some reason password_hash requires 16-bytes-salts but only
+                // processes the first 12 bytes. in order to produce consistent
+                // hashes we take the first 12 bytes from the salt.
+                $cmd_salt = base64_encode(substr($salt, 0, 12));
+                $cmd = '/usr/bin/argon2 %s -t %d -m %d -p %d -e';
+                $cmd = sprintf($cmd, $cmd_salt, $time_cost, $memory_cost, $parallelism);
+                $process = proc_open($cmd, array(
+                    0 => array('pipe', 'r'),
+                    1 => array('pipe', 'w'),
+                    2 => array('pipe', 'w'),
+                ), $pipes);
+                list($stdin, $stdout, $stderr) = $pipes;
+                fwrite($stdin, $plaintext);
+                fclose($stdin);
+                $encrypted = trim(stream_get_contents($stdout));
+                fclose($stdout);
+                $error = trim(stream_get_contents($stderr));
+                fclose($stderr);
+                $return_value = proc_close($process);
+
+                if ($return_value !== 0) {
+                    // TODO: maybe log error?
+                    throw new RuntimeException('error while processing password with libargon');
+                }
+            } else {
+                throw new RuntimeException('upgrade to PHP 7.2 or install libargon');
+            }
+
+            return $show_encrypt ? '{ARGON2}' . $encrypted : $encrypted;
+
         case 'crypt':
         case 'crypt-des':
         case 'crypt-md5':
@@ -259,6 +305,15 @@ class Horde_Auth
                 return $salt;
             }
 
+        case 'argon2i':
+                return $seed
+                    ? base64_decode(explode('$', $seed)[4])
+                    : (
+                        function_exists('random_bytes')
+                            ? random_bytes(16)
+                            : openssl_random_pseudo_bytes(16)
+                    );
+
         case 'crypt':
         case 'crypt-des':
             return $seed
-- 
2.18.0