288 lines
9.5 KiB
PHP
288 lines
9.5 KiB
PHP
<?php
|
|
|
|
/**
|
|
* This file is part of the Froxlor project.
|
|
* Copyright (c) 2010 the Froxlor Team (see authors).
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you can also view it online at
|
|
* https://files.froxlor.org/misc/COPYING.txt
|
|
*
|
|
* @copyright the authors
|
|
* @author Froxlor team <team@froxlor.org>
|
|
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
|
|
*/
|
|
|
|
namespace Froxlor\System;
|
|
|
|
use Froxlor\Database\Database;
|
|
use Froxlor\Froxlor;
|
|
use Froxlor\Settings;
|
|
use Froxlor\Validate\Validate;
|
|
|
|
class Crypt
|
|
{
|
|
|
|
/**
|
|
* Generates a random password
|
|
*
|
|
* @param int $length optional, will be read from settings if not given
|
|
* @param bool $isSalt optional, default false, do not include special characters
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function generatePassword(int $length = 0, bool $isSalt = false): string
|
|
{
|
|
$alpha_lower = 'abcdefghijklmnopqrstuvwxyz';
|
|
$alpha_upper = strtoupper($alpha_lower);
|
|
$numeric = '0123456789';
|
|
$special = Settings::Get('panel.password_special_char');
|
|
if (empty($length)) {
|
|
$length = Settings::Get('panel.password_min_length') > 3 ? Settings::Get('panel.password_min_length') : 10;
|
|
}
|
|
|
|
$pw = self::specialShuffle($alpha_lower);
|
|
$n = floor(($length) / 4);
|
|
|
|
if (Settings::Get('panel.password_alpha_upper')) {
|
|
$pw .= mb_substr(self::specialShuffle($alpha_upper), 0, $n);
|
|
}
|
|
|
|
if (Settings::Get('panel.password_numeric')) {
|
|
$pw .= mb_substr(self::specialShuffle($numeric), 0, $n);
|
|
}
|
|
|
|
if (Settings::Get('panel.password_special_char_required') && !$isSalt) {
|
|
$pw .= mb_substr(self::specialShuffle($special), 0, $n);
|
|
}
|
|
|
|
$pw = mb_substr($pw, -$length);
|
|
|
|
return self::specialShuffle($pw);
|
|
}
|
|
|
|
/**
|
|
* multibyte-character safe shuffle function
|
|
*
|
|
* @param string $str
|
|
*
|
|
* @return string
|
|
*/
|
|
private static function specialShuffle(string $str): string
|
|
{
|
|
$len = mb_strlen($str);
|
|
$sploded = [];
|
|
while ($len-- > 0) {
|
|
$sploded[] = mb_substr($str, $len, 1);
|
|
}
|
|
shuffle($sploded);
|
|
return join('', $sploded);
|
|
}
|
|
|
|
/**
|
|
* return an array of available hashes
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function getAvailablePasswordHashes(): array
|
|
{
|
|
// get available pwd-hases
|
|
$available_pwdhashes = [
|
|
PASSWORD_DEFAULT => lng('serversettings.systemdefault')
|
|
];
|
|
if (defined('PASSWORD_BCRYPT')) {
|
|
$available_pwdhashes[PASSWORD_BCRYPT] = 'Bcrypt/Blowfish' . (PASSWORD_DEFAULT == PASSWORD_BCRYPT ? ' (' . lng('serversettings.systemdefault') . ')' : '');
|
|
}
|
|
if (defined('PASSWORD_ARGON2I')) {
|
|
$available_pwdhashes[PASSWORD_ARGON2I] = 'Argon2i' . (PASSWORD_DEFAULT == PASSWORD_ARGON2I ? ' (' . lng('serversettings.systemdefault') . ')' : '');
|
|
}
|
|
if (defined('PASSWORD_ARGON2ID')) {
|
|
$available_pwdhashes[PASSWORD_ARGON2ID] = 'Argon2id' . (PASSWORD_DEFAULT == PASSWORD_ARGON2ID ? ' (' . lng('serversettings.systemdefault') . ')' : '');
|
|
}
|
|
|
|
return $available_pwdhashes;
|
|
}
|
|
|
|
/**
|
|
* Function validatePassword
|
|
*
|
|
* if password-min-length is set in settings
|
|
* we check against the length, if not matched
|
|
* an error message will be output and 'exit' is called
|
|
*
|
|
* @param string $password the password to validate
|
|
* @param bool $json_response
|
|
*
|
|
* @return string either the password or an errormessage+exit
|
|
*/
|
|
public static function validatePassword(string $password, bool $json_response = false): string
|
|
{
|
|
if (Settings::Get('panel.password_min_length') > 0) {
|
|
$password = Validate::validate($password, Settings::Get('panel.password_min_length'),
|
|
'/^.{' . (int)Settings::Get('panel.password_min_length') . ',}$/D', 'notrequiredpasswordlength', [],
|
|
$json_response);
|
|
}
|
|
|
|
if (Settings::Get('panel.password_regex') != '') {
|
|
$password = Validate::validate($password, Settings::Get('panel.password_regex'),
|
|
Settings::Get('panel.password_regex'), 'notrequiredpasswordcomplexity', [], $json_response);
|
|
} else {
|
|
if (Settings::Get('panel.password_alpha_lower')) {
|
|
$password = Validate::validate($password, '/.*[a-z]+.*/', '/.*[a-z]+.*/',
|
|
'notrequiredpasswordcomplexity', [], $json_response);
|
|
}
|
|
if (Settings::Get('panel.password_alpha_upper')) {
|
|
$password = Validate::validate($password, '/.*[A-Z]+.*/', '/.*[A-Z]+.*/',
|
|
'notrequiredpasswordcomplexity', [], $json_response);
|
|
}
|
|
if (Settings::Get('panel.password_numeric')) {
|
|
$password = Validate::validate($password, '/.*[0-9]+.*/', '/.*[0-9]+.*/',
|
|
'notrequiredpasswordcomplexity', [], $json_response);
|
|
}
|
|
if (Settings::Get('panel.password_special_char_required')) {
|
|
$password = Validate::validate($password,
|
|
'/.*[' . preg_quote(Settings::Get('panel.password_special_char'), '/') . ']+.*/',
|
|
'/.*[' . preg_quote(Settings::Get('panel.password_special_char'), '/') . ']+.*/',
|
|
'notrequiredpasswordcomplexity', [], $json_response);
|
|
}
|
|
}
|
|
|
|
return $password;
|
|
}
|
|
|
|
/**
|
|
* Function validatePasswordLogin
|
|
*
|
|
* compare user password-hash with given user-password
|
|
* and check if they are the same
|
|
* additionally it updates the hash if the system settings changed
|
|
* or if the very old md5() sum is used
|
|
*
|
|
* @param array $userinfo user-data from table
|
|
* @param string $password the password to validate
|
|
* @param string $table either panel_customers or panel_admins
|
|
* @param string $uid user-id-field in $table
|
|
*
|
|
* @return bool
|
|
* @throws \Exception
|
|
*/
|
|
public static function validatePasswordLogin(
|
|
array $userinfo,
|
|
string $password,
|
|
string $table = 'panel_customers',
|
|
string $uid = 'customerid'
|
|
): bool {
|
|
$algo = Settings::Get('system.passwordcryptfunc') !== null ? Settings::Get('system.passwordcryptfunc') : PASSWORD_DEFAULT;
|
|
if (is_numeric($algo)) {
|
|
// old setting format
|
|
$algo = PASSWORD_DEFAULT;
|
|
Settings::Set('system.passwordcryptfunc', $algo);
|
|
}
|
|
$pwd_hash = $userinfo['password'];
|
|
|
|
$update_hash = false;
|
|
$pwd_check = "";
|
|
// check for good'ole md5
|
|
if (strlen($pwd_hash) == 32 && ctype_xdigit($pwd_hash)) {
|
|
$pwd_check = md5($password);
|
|
$update_hash = true;
|
|
}
|
|
|
|
if ($pwd_hash === $pwd_check || password_verify($password, $pwd_hash)) {
|
|
// check for update of hash (only if our database is ready to handle the bigger string)
|
|
$is_ready = Froxlor::versionCompare2("0.9.33", Froxlor::getVersion()) <= 0;
|
|
if ((password_needs_rehash($pwd_hash, $algo) || $update_hash) && $is_ready) {
|
|
$upd_stmt = Database::prepare("
|
|
UPDATE " . $table . " SET `password` = :newpasswd WHERE `" . $uid . "` = :uid
|
|
");
|
|
$params = [
|
|
'newpasswd' => self::makeCryptPassword($password),
|
|
'uid' => $userinfo[$uid]
|
|
];
|
|
Database::pexecute($upd_stmt, $params);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Make encrypted password from clear text password
|
|
*
|
|
* @param string $password Password to be encrypted
|
|
* @param bool $htpasswd optional whether to generate a bcrypt password for directory protection
|
|
* @param bool $ftpd optional generates sha256 password strings for proftpd/pureftpd
|
|
*
|
|
* @return string encrypted password
|
|
*/
|
|
public static function makeCryptPassword(string $password, bool $htpasswd = false, bool $ftpd = false): string
|
|
{
|
|
if ($htpasswd || $ftpd) {
|
|
if ($ftpd) {
|
|
// sha256 compatible for proftpd and pure-ftpd
|
|
return crypt($password, '$5$' . self::generatePassword(16, true) . '$');
|
|
}
|
|
// bcrypt hash for dir-protection
|
|
return password_hash($password, PASSWORD_BCRYPT);
|
|
}
|
|
// crypt using the specified crypt-algorithm or system default
|
|
$algo = Settings::Get('system.passwordcryptfunc') !== null ? Settings::Get('system.passwordcryptfunc') : PASSWORD_DEFAULT;
|
|
return password_hash($password, $algo);
|
|
}
|
|
|
|
/**
|
|
* creates a self-signed ECC-certificate for the froxlor-vhost
|
|
* and sets the content to the corresponding files set in the
|
|
* settings for ssl-certificate-file and ssl-certificate-key
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function createSelfSignedCertificate()
|
|
{
|
|
// validate that we have file names in the settings
|
|
$certFile = Settings::Get('system.ssl_cert_file');
|
|
$keyFile = Settings::Get('system.ssl_key_file');
|
|
if (empty($certFile)) {
|
|
$certFile = '/etc/ssl/froxlor_selfsigned.pem';
|
|
Settings::Set('system.ssl_cert_file', $certFile);
|
|
}
|
|
if (empty($keyFile)) {
|
|
$keyFile = '/etc/ssl/froxlor_selfsigned.key';
|
|
Settings::Set('system.ssl_key_file', $keyFile);
|
|
}
|
|
|
|
// certificate info
|
|
$dn = [
|
|
"countryName" => "DE",
|
|
"stateOrProvinceName" => "Hessen",
|
|
"localityName" => "Frankfurt am Main",
|
|
"organizationName" => "froxlor",
|
|
"organizationalUnitName" => "froxlor Server Management Panel",
|
|
"commonName" => Settings::Get('system.hostname'),
|
|
"emailAddress" => Settings::Get('panel.adminmail')
|
|
];
|
|
// create private key
|
|
$privkey = openssl_pkey_new([
|
|
"private_key_type" => OPENSSL_KEYTYPE_EC,
|
|
"curve_name" => 'prime256v1',
|
|
]);
|
|
// create signing request
|
|
$csr = openssl_csr_new($dn, $privkey, array('digest_alg' => 'sha384'));
|
|
// sign csr
|
|
$x509 = openssl_csr_sign($csr, null, $privkey, 365, array('digest_alg' => 'sha384'));
|
|
// export to files
|
|
openssl_x509_export_to_file($x509, $certFile);
|
|
openssl_pkey_export_to_file($privkey, $keyFile);
|
|
}
|
|
}
|