Skip to content

Commit 6c4226c

Browse files
authored
Switch from argon2 to bcrypt (hestiacp#2752)
* Switch from argon2 to bcrypt Add support for Bcrypt as of "low" costs strength is higher. ArgonID > Bcrypt only after 1000ms or more * code-breaking typo
1 parent 923a548 commit 6c4226c

File tree

4 files changed

+77
-76
lines changed

4 files changed

+77
-76
lines changed

bin/v-add-mail-account

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,16 @@ check_hestia_demo_mode
6666
#----------------------------------------------------------#
6767

6868
# Generating hashed password
69-
if [ -n "$(doveadm pw -l | grep ARGON2ID)" ]; then
69+
70+
if [ -n "$(doveadm pw -l | grep BLF-CRYPT)" ]; then
71+
set +H # disable ! style history substitution
72+
md5="$(doveadm pw -s BLF-CRYPT -p "$password")"
73+
elif [ -n "$(doveadm pw -l | grep ARGON2ID)" ]; then
74+
# Fall back on Argon2id if bcrypt is not available
7075
set +H # disable ! style history substitution
7176
md5="$(doveadm pw -s ARGON2ID -p "$password")"
7277
else
73-
# Fall back on MD5
78+
# Fall back on MD5 if neither bcrypt nor argon2id is available
7479
salt=$(generate_password "$PW_MATRIX" "8")
7580
md5="{MD5}$($BIN/v-generate-password-hash md5 $salt <<<$password)"
7681
fi

bin/v-change-mail-account-password

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,15 @@ check_hestia_demo_mode
5656
#----------------------------------------------------------#
5757

5858
# Generating hashed password
59-
if [ -n "$(doveadm pw -l | grep ARGON2ID)" ]; then
59+
if [ -n "$(doveadm pw -l | grep BLF-CRYPT)" ]; then
60+
set +H # disable ! style history substitution
61+
md5="$(doveadm pw -s BLF-CRYPT -p "$password")"
62+
elif [ -n "$(doveadm pw -l | grep ARGON2ID)" ]; then
63+
# Fall back on Argon2id if bcrypt is not available
6064
set +H # disable ! style history substitution
6165
md5="$(doveadm pw -s ARGON2ID -p "$password")"
6266
else
67+
# Fall back on MD5 if neither bcrypt nor argon2id is available
6368
salt=$(generate_password "$PW_MATRIX" "8")
6469
md5="{MD5}$($BIN/v-generate-password-hash md5 $salt <<<$password)"
6570
fi

bin/v-check-mail-account-hash

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,15 @@ is_password_valid
3535
# Action #
3636
#----------------------------------------------------------#
3737

38-
if [ "$type" = "ARGONID2" ]; then
38+
if [ "$type" = "BCRYPT" ]; then
39+
match=$(doveadm pw -s BLF-CRYPT -p "$password" -t $hash | grep "verified");
40+
if [ -n "$match" ]; then
41+
exit 0;
42+
else
43+
echo $match;
44+
exit 2;
45+
fi
46+
elif [ "$type" = "ARGONID2" ]; then
3947
match=$(doveadm pw -s ARGON2ID -p "$password" -t $hash | grep "verified");
4048
if [ -n "$match" ]; then
4149
exit 0;
@@ -44,10 +52,11 @@ if [ "$type" = "ARGONID2" ]; then
4452
exit 2;
4553
fi
4654
else
47-
echo "Not supported"
55+
echo "unsupported hash type.";
4856
exit 2;
4957
fi
5058

59+
5160
#----------------------------------------------------------#
5261
# Hestia #
5362
#----------------------------------------------------------#

web/reset/mail/index.php

Lines changed: 53 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
// Init
33
define('NO_AUTH_REQUIRED',true);
44
define('NO_AUTH_REQUIRED2',true);
5+
header('Content-Type: text/plain; charset=utf-8');
56

67
include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
78

89
// Checking IP of incoming connection, checking is it NAT address
910
$ok=0;
1011
$ip=$_SERVER['REMOTE_ADDR'];
12+
1113
exec (HESTIA_CMD."v-list-sys-ips json", $output, $return_var);
1214
$output=implode('', $output);
1315
$arr=json_decode($output, true);
@@ -24,83 +26,63 @@
2426
if (isset($_SERVER['HTTP_X_REAL_IP']) || isset($_SERVER['HTTP_X_FORWARDED_FOR'])) exit;
2527

2628

27-
/**
28-
* md5 crypt() password
29-
*
30-
* @param string $password
31-
* @param string $salt
32-
*
33-
* @throws InvalidArgumentException if salt is emptystring
34-
* @throws InvalidArgumentException if salt is longer than 8 characters
35-
* @return string
36-
*/
37-
function md5crypt(string $pw, string $salt): string
38-
{
39-
if (strlen($salt) < 1) {
40-
// old implementation would crash with error "function generate_salt not defined", lets throw an exception instead
41-
throw new InvalidArgumentException('salt not given!');
42-
}
43-
if (strlen($salt) > 8) {
44-
throw new \InvalidArgumentException("maximum supported salt length for MD5 crypt is 8 characters!");
45-
}
46-
return crypt($pw, '$1$' . $salt);
47-
}
48-
4929

5030
// Check arguments
51-
if ((!empty($_POST['email'])) && (!empty($_POST['password'])) && (!empty($_POST['new']))) {
52-
list($v_account, $v_domain) = explode('@', $_POST['email']);
53-
$v_domain = escapeshellarg($v_domain);
54-
$v_account = escapeshellarg($v_account);
55-
$v_password = $_POST['password'];
5631

57-
// Get domain owner
58-
exec (HESTIA_CMD."v-search-domain-owner ".$v_domain." 'mail'", $output, $return_var);
59-
if ($return_var == 0) {
60-
$v_user = $output[0];
61-
}
62-
unset($output);
32+
if (empty($_POST['email'])) {
33+
echo "error email address not provided";
34+
exit;
35+
}
36+
if (empty($_POST['password'])) {
37+
echo "error old password provided";
38+
exit;
39+
}
40+
if (empty($_POST['new'])) {
41+
echo "error new password not provided";
42+
exit;
43+
}
6344

64-
// Get current md5 hash
65-
if (!empty($v_user)) {
66-
exec (HESTIA_CMD."v-get-mail-account-value ".escapeshellarg($v_user)." ".$v_domain." ".$v_account." 'md5'", $output, $return_var);
67-
if ($return_var == 0) {
68-
$v_hash = $output[0];
69-
}
70-
}
71-
unset($output);
45+
list($v_account, $v_domain) = explode('@', $_POST['email']);
46+
$v_domain = escapeshellarg($v_domain);
47+
$v_account = escapeshellarg($v_account);
48+
$v_password = $_POST['password'];
7249

73-
// Compare hashes
74-
if (!empty($v_hash)) {
75-
$salt = explode('$', $v_hash);
76-
if($salt[0] == "{MD5}"){
77-
$n_hash = md5crypt($v_password, $salt[2]);
78-
$n_hash = '{MD5}'.$n_hash;
79-
}else{
80-
$v_password = escapeshellarg($v_password);
81-
$s_hash = escapeshellarg($v_hash);
82-
exec(HESTIA_CMD."v-check-mail-account-hash ARGONID2 ". $v_password ." ". $s_hash, $output, $return_var);
83-
if($return_var != 0){
84-
$n_hash = '';
85-
}else{
86-
$n_hash = $v_hash;
87-
}
88-
}
89-
// Change password
90-
if ( $v_hash == $n_hash ) {
91-
$v_new_password = tempnam("/tmp","vst");
92-
$fp = fopen($v_new_password, "w");
93-
fwrite($fp, $_POST['new']."\n");
94-
fclose($fp);
95-
exec (HESTIA_CMD."v-change-mail-account-password ".escapeshellarg($v_user)." ".$v_domain." ".$v_account." ".$v_new_password, $output, $return_var);
96-
if ($return_var == 0) {
97-
echo "==ok==";
98-
exit;
99-
}
100-
}
101-
}
50+
// Get domain owner
51+
exec(HESTIA_CMD . "v-search-domain-owner " . $v_domain . " 'mail'", $output, $return_var);
52+
if ($return_var != 0 || empty($output[0])) {
53+
echo "error domain owner not found";
54+
exit;
10255
}
56+
$v_user = $output[0];
57+
unset($output);
10358

104-
echo 'error';
10559

60+
// Get current password hash (called "md5" for legacy reasons, it's not guaranteed to be md5)
61+
exec(HESTIA_CMD . "v-get-mail-account-value " . escapeshellarg($v_user) . " " . $v_domain . " " . $v_account . " 'md5'", $output, $return_var);
62+
if ($return_var != 0 || empty($output[0])) {
63+
echo "error unable to get current account password hash";
64+
exit;
65+
}
66+
$v_hash = $output[0];
67+
unset($output);
68+
69+
// v_hash use doveadm password hash format, which is basically {HASH_NAME}normal_crypt_format,
70+
// so we just need to remove the {HASH_NAME} before we can ask password_verify if its correct or not.
71+
$hash_for_password_verify = explode('}', $v_hash, 2);
72+
$hash_for_password_verify = end($hash_for_password_verify);
73+
if (!password_verify($v_password, $hash_for_password_verify)) {
74+
die("error old password does not match");
75+
}
76+
77+
// Change password
78+
$fp = tmpfile();
79+
$new_password_file = stream_get_meta_data($fp)['uri'];
80+
fwrite($fp, $_POST['new'] . "\n");
81+
exec(HESTIA_CMD . "v-change-mail-account-password " . escapeshellarg($v_user) . " " . $v_domain . " " . $v_account . " " . escapeshellarg($new_password_file), $output, $return_var);
82+
fclose($fp);
83+
if ($return_var == 0) {
84+
echo "==ok==";
85+
exit;
86+
}
87+
echo 'error v-change-mail-account-password returned non-zero: ' . $return_var;
10688
exit;

0 commit comments

Comments
 (0)