mirror of
https://github.com/postfixadmin/postfixadmin.git
synced 2024-09-19 19:22:14 +02:00
Merge remote-tracking branch 'origin/master' into issue-327-collation-schema-update
This commit is contained in:
commit
df76f7b4ae
60
.github/workflows/php.yml
vendored
60
.github/workflows/php.yml
vendored
@ -3,7 +3,38 @@ name: GitHubBuild
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
lint_etc:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 7.4
|
||||
tools: composer
|
||||
|
||||
- name: run install.sh
|
||||
run: /bin/bash install.sh
|
||||
|
||||
- name: check composer
|
||||
run: composer validate
|
||||
|
||||
# Needing to 'update' here isn't ideal, but we need to cope with tests that run under different PHP versions :-/
|
||||
- name: Install dependencies
|
||||
run: composer update --prefer-dist -n
|
||||
|
||||
- name: check formatting
|
||||
run: composer check-format
|
||||
|
||||
- name: touch config.local.php
|
||||
run: touch config.local.php
|
||||
|
||||
- name: psalm static analysis
|
||||
run: composer psalm
|
||||
|
||||
testsuite:
|
||||
needs: [lint_etc]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@ -18,11 +49,8 @@ jobs:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: composer
|
||||
|
||||
- name: Validate composer.json and composer.lock
|
||||
run: composer validate
|
||||
|
||||
- name: setup templates_c
|
||||
run: mkdir templates_c || true
|
||||
- name: run insall.sh
|
||||
run: /bin/bash install.sh
|
||||
|
||||
- name: touch config.local.php
|
||||
run: touch config.local.php && php -v
|
||||
@ -31,18 +59,10 @@ jobs:
|
||||
run: composer install --prefer-dist -n
|
||||
|
||||
- name: Build/test
|
||||
run: composer build
|
||||
run: composer test
|
||||
|
||||
- name: build coveralls coverage
|
||||
run: php -m xdebug.mode=coverage vendor/bin/phpunit tests
|
||||
|
||||
- name: Coveralls
|
||||
run: vendor/bin/php-coveralls ./clover.xml || true
|
||||
env:
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
||||
|
||||
post_build:
|
||||
needs: [build]
|
||||
build_coverage_report:
|
||||
needs: [testsuite]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@ -53,14 +73,14 @@ jobs:
|
||||
php-version: '7.4'
|
||||
tools: composer
|
||||
|
||||
- name: setup templates_c
|
||||
run: mkdir templates_c || true
|
||||
- name: run insall.sh
|
||||
run: /bin/bash install.sh
|
||||
|
||||
- name: touch config.local.php
|
||||
run: touch config.local.php && php -v
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist -n
|
||||
run: composer update --prefer-dist -n
|
||||
|
||||
- name: build coveralls coverage
|
||||
run: php -d xdebug.mode=coverage vendor/bin/phpunit tests
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,7 +1,7 @@
|
||||
/config.local.php
|
||||
/templates_c/*.tpl.php
|
||||
/templates_c/*menu.conf.php
|
||||
/vendor/
|
||||
/.php_cs.cache
|
||||
/.idea
|
||||
/vendor
|
||||
/composer.lock
|
||||
|
@ -6,6 +6,9 @@ They should not be stored in plain text.
|
||||
|
||||
Whatever format you choose will need to be supported by your IMAP server (and whatever provides SASL auth for Postfix)
|
||||
|
||||
If you can, use a format that includes a different salt per password (e.g. one of the crypt variants, like Blowfish (BLF-CRYPT) or Argon2I/Argon2ID).
|
||||
Try and avoid formats that are unsalted hashes (md5, SHA1) as these offer minimal protection in the event of a data leak.
|
||||
|
||||
## Configuration
|
||||
|
||||
See config.inc.php (or config.local.php) and look for
|
||||
@ -20,6 +23,49 @@ This document is probably not complete.
|
||||
|
||||
It possibly provides better documentation than was present before. This may not say much.
|
||||
|
||||
Supported hash formats include :
|
||||
|
||||
* MD5-CRYPT (aka MD5),
|
||||
* SHA1,
|
||||
* SHA1-CRYPT,
|
||||
* SSHA (4 char salted sha1),
|
||||
* BLF-CRYPT (Blowfish),
|
||||
* SHA512,
|
||||
* SHA512-CRYPT,
|
||||
* ARGON2I,
|
||||
* ARGON2ID,
|
||||
* SHA256,
|
||||
* SHA256-CRYPT,
|
||||
* PLAIN-MD5 (aka md5)
|
||||
* CRYPT
|
||||
|
||||
Historically PostfixAdmin has supported all dovecot algorithms (methods) by using the 'doveadm' system binary. As of XXXXXXX, we attempted to use a native/PHP implementation for a number of these to remove issues caused by use of proc_open / dovecot file permissions etc (see e.g. #379).
|
||||
|
||||
It's recommended you use the algorithm/mechanism from your MTA, and configure PostfixAdmin with the same value prefixed by the MTA name -
|
||||
|
||||
For example, if dovecot has `default_pass_scheme = SHA256` use `$CONF['encrypt'] = 'dovecot:SHA256'; ` in PostfixAdmin.
|
||||
|
||||
|
||||
| Dovecot pass scheme | PostfixAdmin `$CONF['encrypt']` setting |
|
||||
|---------------------|-----------------------------------------|
|
||||
| SHA256 | dovecot:SHA256 |
|
||||
| SHA256-CRYPT.B64 | dovecot:SHA256-CRYPT.B64 |
|
||||
| SHA256-CRYPT | dovecot:SHA256-CRYPT |
|
||||
| SHA512-CRYPT | dovecot:SHA512-CRYPT |
|
||||
| ARGON2I | dovecot:ARGON2I |
|
||||
| ARGON2ID | dovecot:ARGON2ID |
|
||||
|
||||
|
||||
|
||||
| Courier Example | PostfixAdmin |
|
||||
|-----------------|--------------|
|
||||
| md5 | courier:md5 |
|
||||
| md5raw | courier:md5raw |
|
||||
| sha1 | courier:sha1 |
|
||||
| ssha | courier:ssha |
|
||||
| sha256 | courier:sha256 |
|
||||
|
||||
|
||||
### cleartext
|
||||
|
||||
No hashing. May be useful for debugging.
|
||||
@ -58,9 +104,9 @@ You should not use this (it does not offer a high level of security), but is pro
|
||||
|
||||
Uses PHP's crypt function.
|
||||
|
||||
Probably throws an E_NOTICE. Avoid?
|
||||
Probably throws an E_NOTICE.
|
||||
|
||||
example : `$1$tWgqTIuF$1HFciCXrhVpACGjBMxNr/0`
|
||||
Example : `$1$tWgqTIuF$1HFciCXrhVpACGjBMxNr/0`
|
||||
|
||||
### authlib
|
||||
|
||||
@ -86,16 +132,18 @@ Presumably weak.
|
||||
|
||||
Uses sha1, base64 encoded. Unsalted. Avoid.
|
||||
|
||||
### dovecot:CRYPT-METHOD
|
||||
### dovecot:METHOD
|
||||
|
||||
Uses dovecot binary to produce hash.
|
||||
May use dovecot binary to produce hash, if the format you request isn't in PFACrypt::DOVECOT_NATIVE
|
||||
|
||||
Pros -
|
||||
Using a format that PostfixAdmin doesn't support natively has the following pros/cons :
|
||||
|
||||
#### Pros
|
||||
|
||||
* Minimal dependency on PostfixAdmin / PHP code.
|
||||
* Hash should definitely work with dovecot!
|
||||
|
||||
Cons -
|
||||
#### Cons
|
||||
|
||||
* file permissions and/or execution of doveadm by the web server may be problematic.
|
||||
* requires: proc_open(...) - which might be blocked by e.g. safemode.
|
||||
|
19
INSTALL.TXT
19
INSTALL.TXT
@ -41,10 +41,10 @@ DOCUMENTS/ folder.
|
||||
|
||||
(if you installed PostfixAdmin as RPM or DEB package, you can obviously skip this step.)
|
||||
|
||||
Assuming we are installing Postfixadmin into /srv/postfixadmin, then something like this should work. Please check https://github.com/postfixadmin/postfixadmin/releases to get the latest stable release first (the 3.2.4 version/url below is probably stale)
|
||||
Assuming we are installing Postfixadmin into /srv/postfixadmin, then something like this should work. Please check https://github.com/postfixadmin/postfixadmin/releases to get the latest stable release first (the 3.2.10 version/url below is probably stale)
|
||||
|
||||
$ cd /srv/
|
||||
$ wget -O postfixadmin.tgz https://github.com/postfixadmin/postfixadmin/archive/postfixadmin-3.2.4.tar.gz
|
||||
$ wget -O postfixadmin.tgz https://github.com/postfixadmin/postfixadmin/archive/postfixadmin-3.2.10.tar.gz
|
||||
$ tar -zxvf postfixadmin.tgz
|
||||
$ mv postfixadmin-postfixadmin-3.2 postfixadmin
|
||||
|
||||
@ -53,7 +53,20 @@ Alternatively :
|
||||
$ cd /srv
|
||||
$ git clone https://github.com/postfixadmin/postfixadmin.git
|
||||
$ cd postfixadmin
|
||||
$ git checkout postfixadmin-3.2.4
|
||||
$ git checkout postfixadmin-3.2.10
|
||||
|
||||
If you're happy to try out newer functionality and perhaps hit unfixed bugs, you can try the 'master' branch by a `git checkout master` (or don't run the final git checkout in the above list).
|
||||
|
||||
If you're using the 'master' branch, you'll need to also run :
|
||||
|
||||
```bash
|
||||
/bin/bash install.sh
|
||||
```
|
||||
|
||||
Which will
|
||||
* install the 'composer' tool locally (composer.phar) and
|
||||
* download dependent PHP libraries.
|
||||
* create a templates_c directory if one does not exist.
|
||||
|
||||
2. Setup Web Server
|
||||
-------------------
|
||||
|
20
common.php
20
common.php
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
require_once(dirname(__FILE__) . '/vendor/autoload.php');
|
||||
|
||||
/**
|
||||
* Postfix Admin
|
||||
*
|
||||
@ -35,23 +38,6 @@ if (!defined('POSTFIXADMIN')) {
|
||||
|
||||
$incpath = dirname(__FILE__);
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* __autoload implementation, for use with spl_autoload_register().
|
||||
*/
|
||||
function postfixadmin_autoload($class)
|
||||
{
|
||||
$PATH = dirname(__FILE__) . '/model/' . $class . '.php';
|
||||
|
||||
if (is_file($PATH)) {
|
||||
require_once($PATH);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
spl_autoload_register('postfixadmin_autoload');
|
||||
|
||||
if (!is_file("$incpath/config.inc.php")) {
|
||||
die("config.inc.php is missing!");
|
||||
}
|
||||
|
7
composer-update.sh
Normal file
7
composer-update.sh
Normal file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# for github :
|
||||
composer update --no-dev
|
||||
|
||||
# for local testing/dev:
|
||||
# composer update
|
@ -3,6 +3,9 @@
|
||||
"description": "web based administration interface for Postfix mail servers",
|
||||
"type": "project",
|
||||
"license": "GPL-2.0",
|
||||
"config": {
|
||||
"preferred-install":"dist"
|
||||
},
|
||||
"scripts": {
|
||||
"build" : [
|
||||
"@check-format",
|
||||
@ -19,7 +22,8 @@
|
||||
"psalm": "@php ./vendor/bin/psalm --no-cache --show-info=false "
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0"
|
||||
"php": ">=7.2",
|
||||
"postfixadmin/password-hashing": "^0.0.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-mysqli": "*",
|
||||
@ -33,6 +37,7 @@
|
||||
"shardj/zf1-future" : "^1.12"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap" : [ "model/" ],
|
||||
"files": [
|
||||
"config.inc.php",
|
||||
"functions.inc.php",
|
||||
|
@ -187,7 +187,7 @@ $CONF['smtp_sendmail_tls'] = 'NO';
|
||||
// authlib = support for courier-authlib style passwords - also set $CONF['authlib_default_flavor']
|
||||
// dovecot:CRYPT-METHOD = use dovecotpw -s 'CRYPT-METHOD'. Example: dovecot:CRAM-MD5
|
||||
// php_crypt:CRYPT-METHOD:DIFFICULTY:PREFIX = use PHP built in crypt()-function. Example: php_crypt:SHA512:50000
|
||||
// - php_crypt CRYPT-METHOD: Supported values are DES, MD5, BLOWFISH, SHA256, SHA512
|
||||
// - php_crypt CRYPT-METHOD: Supported values are DES, MD5, BLOWFISH, SHA256, SHA512 (default)
|
||||
// - php_crypt DIFFICULTY: Larger value is more secure, but uses more CPU and time for each login.
|
||||
// - php_crypt DIFFICULTY: Set this according to your CPU processing power.
|
||||
// - php_crypt DIFFICULTY: Supported values are BLOWFISH:4-31, SHA256:1000-999999999, SHA512:1000-999999999
|
||||
@ -197,7 +197,7 @@ $CONF['smtp_sendmail_tls'] = 'NO';
|
||||
// - you'll need at least dovecot 2.1 for salted passwords ('doveadm pw' 2.0.x doesn't support the '-t' option)
|
||||
// - dovecot 2.0.0 - 2.0.7 is not supported
|
||||
// - php_crypt PREFIX: hash has specified prefix - example: php_crypt:SHA512::{SHA256-CRYPT}
|
||||
// sha512.b64 - {SHA512-CRYPT.B64} (base64 encoded sha512) (no dovecot dependency; should support migration from md5crypt)
|
||||
// sha512.b64 - {SHA512-CRYPT.B64} (base64 encoded sha512 crypt) (no dovecot dependency; should support migration from md5crypt)
|
||||
$CONF['encrypt'] = 'php_crypt';
|
||||
|
||||
// In what flavor should courier-authlib style passwords be encrypted?
|
||||
|
@ -936,6 +936,7 @@ function validate_password($password)
|
||||
* @param string $pw
|
||||
* @param string $pw_db - encrypted hash
|
||||
* @return string crypt'ed password, should equal $pw_db if $pw matches the original
|
||||
* @deprecated
|
||||
*/
|
||||
function _pacrypt_md5crypt($pw, $pw_db = '')
|
||||
{
|
||||
@ -952,6 +953,7 @@ function _pacrypt_md5crypt($pw, $pw_db = '')
|
||||
|
||||
/**
|
||||
* @todo fix this to not throw an E_NOTICE or deprecate/remove.
|
||||
* @deprecated
|
||||
*/
|
||||
function _pacrypt_crypt($pw, $pw_db = '')
|
||||
{
|
||||
@ -1291,65 +1293,44 @@ function _php_crypt_random_string($characters, $length)
|
||||
* @param string $pw_db optional encrypted password
|
||||
* @return string encrypted password - if this matches $pw_db then the original password is $pw.
|
||||
*/
|
||||
function pacrypt($pw, $pw_db="")
|
||||
function pacrypt($pw, $pw_db = "")
|
||||
{
|
||||
global $CONF;
|
||||
|
||||
switch ($CONF['encrypt']) {
|
||||
case 'md5crypt':
|
||||
return _pacrypt_md5crypt($pw, $pw_db);
|
||||
case 'md5':
|
||||
return md5($pw);
|
||||
case 'system':
|
||||
return _pacrypt_crypt($pw, $pw_db);
|
||||
case 'cleartext':
|
||||
return $pw;
|
||||
case 'mysql_encrypt':
|
||||
return _pacrypt_mysql_encrypt($pw, $pw_db);
|
||||
case 'authlib':
|
||||
return _pacrypt_authlib($pw, $pw_db);
|
||||
case 'sha512.b64':
|
||||
return _pacrypt_sha512_b64($pw, $pw_db);
|
||||
$mechanism = $CONF['encrypt'] ?? 'CRYPT';
|
||||
|
||||
$mechanism = strtoupper($mechanism);
|
||||
|
||||
$crypts = ['PHP_CRYPT', 'MD5CRYPT', 'PHP_CRYPT:DES', 'PHP_CRYPT:MD5', 'PHP_CRYPT:SHA256'];
|
||||
|
||||
if (in_array($mechanism, $crypts)) {
|
||||
$mechanism = 'CRYPT';
|
||||
}
|
||||
if ($mechanism == 'PHP_CRYPT:SHA512') {
|
||||
$mechanism = 'SHA512-CRYPT';
|
||||
}
|
||||
|
||||
if (preg_match("/^dovecot:/", $CONF['encrypt'])) {
|
||||
return _pacrypt_dovecot($pw, $pw_db);
|
||||
if ($mechanism == 'SHA512.B64') {
|
||||
// postfixadmin incorrectly uses this as a SHA512-CRYPT.B64
|
||||
$mechanism = 'SHA512-CRYPT.B64';
|
||||
}
|
||||
|
||||
if (preg_match('/^DOVECOT:(.*)$/i', $mechanism, $matches)) {
|
||||
$mechanism = strtoupper($matches[1]);
|
||||
}
|
||||
|
||||
if (substr($CONF['encrypt'], 0, 9) === 'php_crypt') {
|
||||
return _pacrypt_php_crypt($pw, $pw_db);
|
||||
if (preg_match('/^COURIER:(.*)$/i', $mechanism, $matches)) {
|
||||
$mechanism = strtoupper($mechanism);
|
||||
}
|
||||
if (empty($pw_db)) {
|
||||
$pw_db = null;
|
||||
}
|
||||
|
||||
throw new Exception('unknown/invalid $CONF["encrypt"] setting: ' . $CONF['encrypt']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/postfixadmin/postfixadmin/issues/58
|
||||
*/
|
||||
function _pacrypt_sha512_b64($pw, $pw_db="")
|
||||
{
|
||||
if (!function_exists('random_bytes') || !function_exists('crypt') || !defined('CRYPT_SHA512') || !function_exists('mb_substr')) {
|
||||
throw new Exception("sha512.b64 not supported!");
|
||||
if ($mechanism == 'AUTHLIB') {
|
||||
return _pacrypt_authlib($pw, $pw_db);
|
||||
}
|
||||
if (!$pw_db) {
|
||||
$salt = mb_substr(rtrim(base64_encode(random_bytes(16)),'='),0,16,'8bit');
|
||||
return '{SHA512-CRYPT.B64}'.base64_encode(crypt($pw,'$6$'.$salt));
|
||||
}
|
||||
|
||||
|
||||
$password="#Thepasswordcannotbeverified";
|
||||
if (strncmp($pw_db,'{SHA512-CRYPT.B64}',18)==0) {
|
||||
$dcpwd = base64_decode(mb_substr($pw_db,18,null,'8bit'),true);
|
||||
if ($dcpwd !== false && !empty($dcpwd) && strncmp($dcpwd,'$6$',3)==0) {
|
||||
$password = '{SHA512-CRYPT.B64}'.base64_encode(crypt($pw,$dcpwd));
|
||||
}
|
||||
} elseif (strncmp($pw_db,'{MD5-CRYPT}',11)==0) {
|
||||
$dcpwd = mb_substr($pw_db,11,null,'8bit');
|
||||
if (!empty($dcpwd) && strncmp($dcpwd,'$1$',3)==0) {
|
||||
$password = '{MD5-CRYPT}'.crypt($pw,$dcpwd);
|
||||
}
|
||||
}
|
||||
return $password;
|
||||
$hasher = new \PostfixAdmin\PasswordHashing\Crypt($mechanism);
|
||||
return $hasher->crypt($pw, $pw_db);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1360,6 +1341,7 @@ function _pacrypt_sha512_b64($pw, $pw_db="")
|
||||
* @param string $salt (optional)
|
||||
* @param string $magic (optional)
|
||||
* @return string hashed password in crypt format.
|
||||
* @deprecated see PFACrypt::cryptMd5() (note this returns {MD5} prefix
|
||||
*/
|
||||
function md5crypt($pw, $salt="", $magic="")
|
||||
{
|
||||
|
62
install.sh
Normal file
62
install.sh
Normal file
@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
# PostfixAdmin install script.
|
||||
# 1. Downloads 'composer.phar' to the current directory.
|
||||
# 2. Runs 'php composer.phar install' which should install required runtime libraries for Postfixadmin
|
||||
# 3. Runs 'mkdir templates_c && chmod 777 templates_c'
|
||||
|
||||
PATH=/bin:/usr/bin:/usr/local/bin
|
||||
export PATH
|
||||
|
||||
COMPOSER_URL=https://getcomposer.org/download/latest-stable/composer.phar
|
||||
|
||||
type php >/dev/null 2>&1 || { echo >&2 "I require php but it's not installed. Aborting."; exit 1; }
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Check for $(pwd)/composer.phar
|
||||
|
||||
echo " * Checking for composer.phar "
|
||||
|
||||
if [ ! -f composer.phar ]; then
|
||||
|
||||
echo " * Trying to download composer.phar from $COMPOSER_URL "
|
||||
# try and download it one way or another
|
||||
if [ -x /usr/bin/wget ]; then
|
||||
wget -q -O composer.phar $COMPOSER_URL
|
||||
else
|
||||
if [ -x /usr/bin/curl ]; then
|
||||
curl -o composer.phar $COMPOSER_URL
|
||||
else
|
||||
echo " ** Could not find wget or curl; please download $COMPOSER_URL to pwd" >/dev/stderr
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " * Running composer install --no-dev"
|
||||
|
||||
php composer.phar install --prefer-dist -n --no-dev
|
||||
|
||||
|
||||
if [ ! -d templates_c ]; then
|
||||
|
||||
|
||||
mkdir -p templates_c && chmod 777 templates_c
|
||||
|
||||
echo
|
||||
echo " Warning: "
|
||||
echo " templates_c directory didn't exist, now created."
|
||||
echo
|
||||
echo " You should change the ownership and reduce permissions on templates_c to 750. "
|
||||
echo " The ownership needs to match the user used to execute PHP scripts, perhaps 'www-data' or 'httpd'"
|
||||
echo
|
||||
echo " e.g. chown www-data templates_c && chmod 750 templates_c"
|
||||
echo
|
||||
fi
|
||||
echo
|
||||
echo "Please continue configuration / setup within your web browser. "
|
||||
echo "See also : https://github.com/postfixadmin/postfixadmin/blob/master/INSTALL.TXT#L58 "
|
||||
echo
|
359
model/PFACrypt.php
Normal file
359
model/PFACrypt.php
Normal file
@ -0,0 +1,359 @@
|
||||
<?php
|
||||
|
||||
class PFACrypt
|
||||
{
|
||||
private $algorithm;
|
||||
|
||||
const DOVECOT_NATIVE = [
|
||||
'SHA1', 'SHA1.HEX', 'SHA1.B64',
|
||||
'SSHA',
|
||||
'BLF-CRYPT', 'BLF-CRYPT.B64',
|
||||
'SHA512-CRYPT', 'SHA512-CRYPT.B64',
|
||||
'ARGON2I',
|
||||
'ARGON2I.B64',
|
||||
'ARGON2ID',
|
||||
'ARGON2ID.B64',
|
||||
'SHA256', 'SHA256-CRYPT', 'SHA256-CRYPT.B64',
|
||||
'SHA512', 'SHA512.B64',
|
||||
'MD5-CRYPT',
|
||||
'PLAIN-MD5',
|
||||
'CRYPT',
|
||||
];
|
||||
|
||||
public function __construct(string $algorithm)
|
||||
{
|
||||
$this->algorithm = $algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* When called with a 'pw' and a 'pw_db' (hash from e.g. database).
|
||||
*
|
||||
* If the return value matches $pw_db, then the plain text password ('pw') is correct.
|
||||
*
|
||||
* @param string $pw - plain text password
|
||||
* @param string $pw_db - hash from e.g. database (what we're comparing $pw to).
|
||||
* @return string if $pw is correct (hashes to $pw_db) then we return $pw_db. Else we return a new hash.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function pacrypt(string $pw, string $pw_db = ''): string
|
||||
{
|
||||
$algorithm = strtoupper($this->algorithm);
|
||||
|
||||
// try and 'upgrade' some dovecot commands to use local algorithms (rather tnan a dependency on the dovecot binary).
|
||||
if (preg_match('/^DOVECOT:/i', $algorithm)) {
|
||||
$tmp = preg_replace('/^DOVECOT:/i', '', $algorithm);
|
||||
if (in_array($tmp, self::DOVECOT_NATIVE)) {
|
||||
$algorithm = $tmp;
|
||||
} else {
|
||||
error_log("Warning: using algorithm that requires proc_open: $algorithm, consider using one of : " . implode(', ', self::DOVECOT_NATIVE));
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($pw_db) && preg_match('/^{([0-9a-z-\.]+)}/i', $pw_db, $matches)) {
|
||||
$method_in_hash = $matches[1];
|
||||
if ('COURIER:' . strtoupper($method_in_hash) == $algorithm) {
|
||||
// don't try and be clever.
|
||||
} elseif ($algorithm != $method_in_hash) {
|
||||
error_log("Hey, you fed me a password using {$method_in_hash}, but the system is configured to use {$algorithm}");
|
||||
$algorithm = $method_in_hash;
|
||||
}
|
||||
}
|
||||
if ($algorithm == 'SHA512CRYPT.B64') {
|
||||
$algorithm = 'SHA512-CRYPT.B64';
|
||||
}
|
||||
|
||||
|
||||
switch ($algorithm) {
|
||||
|
||||
case 'SHA1':
|
||||
case 'SHA1.B64':
|
||||
case 'SHA1.HEX':
|
||||
return $this->hashSha1($pw, $pw_db, $algorithm);
|
||||
|
||||
case 'BLF-CRYPT':
|
||||
case 'BLF-CRYPT.B64':
|
||||
return $this->blowfishCrypt($pw, $pw_db, $algorithm);
|
||||
|
||||
case 'SHA512-CRYPT':
|
||||
case 'SHA512-CRYPT.B64':
|
||||
return $this->sha512Crypt($pw, $pw_db, $algorithm);
|
||||
|
||||
case 'ARGON2I':
|
||||
case 'ARGON2I.B64':
|
||||
return $this->argon2iCrypt($pw, $pw_db, $algorithm);
|
||||
|
||||
case 'ARGON2ID':
|
||||
case 'ARGON2ID.B64':
|
||||
return $this->argon2idCrypt($pw, $pw_db, $algorithm);
|
||||
|
||||
case 'SSHA':
|
||||
case 'COURIER:SSHA':
|
||||
return $this->hashSha1Salted($pw, $pw_db);
|
||||
|
||||
case 'SHA256':
|
||||
case 'COURIER:SHA256':
|
||||
return $this->hashSha256($pw);
|
||||
|
||||
case 'SHA256-CRYPT':
|
||||
case 'SHA256-CRYPT.B64':
|
||||
return $this->sha256Crypt($pw, $pw_db, $algorithm);
|
||||
|
||||
case 'SHA512':
|
||||
case 'SHA512B.b64':
|
||||
case 'SHA512.B64':
|
||||
return $this->hashSha512($pw, $algorithm);
|
||||
|
||||
case 'PLAIN-MD5': // {PLAIN-MD5} prefix
|
||||
case 'MD5': // no prefix
|
||||
return $this->hashMd5($pw, $algorithm); // this is hex encoded.
|
||||
|
||||
case 'COURIER:MD5':
|
||||
return '{MD5}' . base64_encode(md5($pw, true)); // seems to need to be base64 encoded.
|
||||
|
||||
case 'COURIER:MD5RAW':
|
||||
return '{MD5RAW}' . md5($pw);
|
||||
|
||||
case 'MD5-CRYPT':
|
||||
return $this->cryptMd5($pw, $pw_db, $algorithm);
|
||||
|
||||
case 'CRYPT':
|
||||
if (!empty($pw_db)) {
|
||||
$pw_db = preg_replace('/^{CRYPT}/', '', $pw_db);
|
||||
}
|
||||
if (empty($pw_db)) {
|
||||
$pw_db = '$2y$10$' . substr(sha1(random_bytes(8)), 0, 22);
|
||||
}
|
||||
return '{CRYPT}' . crypt($pw, $pw_db);
|
||||
|
||||
case 'SYSTEM':
|
||||
return crypt($pw, $pw_db);
|
||||
|
||||
case 'CLEAR':
|
||||
case 'PLAIN':
|
||||
case 'CLEARTEXT':
|
||||
if (!empty($pw_db)) {
|
||||
if ($pw_db == "{{$algorithm}}$pw") {
|
||||
return $pw_db;
|
||||
}
|
||||
return $pw;
|
||||
}
|
||||
return '{' . $algorithm . '}' . $pw;
|
||||
case 'MYSQL_ENCRYPT':
|
||||
return _pacrypt_mysql_encrypt($pw, $pw_db);
|
||||
|
||||
// these are all supported by the above (SHA,
|
||||
case 'AUTHLIB':
|
||||
return _pacrypt_authlib($pw, $pw_db);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (preg_match("/^DOVECOT:/", $algorithm)) {
|
||||
return _pacrypt_dovecot($pw, $pw_db);
|
||||
}
|
||||
|
||||
if (substr($algorithm, 0, 9) === 'PHP_CRYPT') {
|
||||
return _pacrypt_php_crypt($pw, $pw_db);
|
||||
}
|
||||
|
||||
throw new Exception('unknown/invalid $CONF["encrypt"] setting: ' . $algorithm);
|
||||
}
|
||||
|
||||
|
||||
public function hashSha1(string $pw, string $pw_db = '', string $algorithm = 'SHA1'): string
|
||||
{
|
||||
$hash = hash('sha1', $pw, true);
|
||||
|
||||
if (preg_match('/\.HEX$/', $algorithm)) {
|
||||
$hash = bin2hex($hash);
|
||||
} else {
|
||||
$hash = base64_encode($hash);
|
||||
}
|
||||
return "{{$algorithm}}{$hash}";
|
||||
}
|
||||
|
||||
public function hashSha1Salted(string $pw, string $pw_db = ''): string
|
||||
{
|
||||
if (empty($pw_db)) {
|
||||
$salt = base64_encode(random_bytes(3)); // 4 char salt.
|
||||
} else {
|
||||
$salt = substr(base64_decode(substr($pw_db, 6)), 20);
|
||||
}
|
||||
return '{SSHA}' . base64_encode(sha1($pw . $salt, true) . $salt);
|
||||
}
|
||||
|
||||
public function hashSha512(string $pw, string $algorithm = 'SHA512')
|
||||
{
|
||||
$prefix = '{SHA512}';
|
||||
|
||||
if ($algorithm == 'SHA512.B64' || $algorithm == 'sha512b.b64') {
|
||||
$prefix = '{SHA512.B64}';
|
||||
}
|
||||
|
||||
return $prefix . base64_encode(hash('sha512', $pw, true));
|
||||
}
|
||||
|
||||
public function hashMd5(string $pw, string $algorithm = 'PLAIN-MD5'): string
|
||||
{
|
||||
if ($algorithm == 'PLAIN-MD5') {
|
||||
return '{PLAIN-MD5}' . md5($pw);
|
||||
}
|
||||
return md5($pw);
|
||||
}
|
||||
|
||||
public function hashSha256(string $pw): string
|
||||
{
|
||||
return '{SHA256}' . base64_encode(hash('sha256', $pw, true));
|
||||
}
|
||||
|
||||
public function cryptMd5(string $pw, string $pw_db = '', $algorithm = 'MD5-CRYPT')
|
||||
{
|
||||
if (!empty($pw_db)) {
|
||||
$pw_db = preg_replace('/^{MD5.*}/', '', $pw_db);
|
||||
}
|
||||
if (empty($pw_db)) {
|
||||
$pw_db = '$1$' . substr(sha1(random_bytes(8)), 0, 16);
|
||||
}
|
||||
return "{{$algorithm}}" . crypt($pw, $pw_db);
|
||||
}
|
||||
|
||||
public function blowfishCrypt(string $pw, string $pw_db = '', string $algorithm = 'BLF-CRYPT'): string
|
||||
{
|
||||
if (!empty($pw_db)) {
|
||||
if ($algorithm == 'BLF-CRYPT') {
|
||||
$pw_db = preg_replace('/^{BLF-CRYPT}/', '', $pw_db);
|
||||
}
|
||||
if ($algorithm == 'BLF-CRYPT.B64') {
|
||||
$pw_db = base64_decode(preg_replace('/^{BLF-CRYPT.B64}/', '', $pw_db));
|
||||
}
|
||||
$hash = crypt($pw, $pw_db);
|
||||
|
||||
if ($algorithm == 'BLF-CRYPT.B64') {
|
||||
$hash = base64_encode($hash);
|
||||
}
|
||||
return "{{$algorithm}}{$hash}";
|
||||
}
|
||||
|
||||
$r = password_hash($pw, PASSWORD_BCRYPT);
|
||||
if (!is_string($r)) {
|
||||
throw new \RuntimeException("Failed to generate password");
|
||||
}
|
||||
if ($algorithm == 'BLF-CRYPT.B64') {
|
||||
return '{BLF-CRYPT.B64}' . base64_encode($r);
|
||||
}
|
||||
return '{BLF-CRYPT}' . $r;
|
||||
}
|
||||
|
||||
public function sha256Crypt(string $pw, string $pw_db = '', string $algorithm = 'SHA256-CRYPT'): string
|
||||
{
|
||||
if (!empty($pw_db)) {
|
||||
$pw_db = preg_replace('/^{SHA256-CRYPT(\.B64)?}/', '', $pw_db);
|
||||
|
||||
if ($algorithm == 'SHA256-CRYPT.B64') {
|
||||
$pw_db = base64_decode($pw_db);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($pw_db)) {
|
||||
$pw_db = '$5$' . substr(sha1(random_bytes(8)), 0, 16);
|
||||
}
|
||||
|
||||
$hash = crypt($pw, $pw_db);
|
||||
|
||||
if ($algorithm == 'SHA256-CRYPT.B64') {
|
||||
return '{SHA256-CRYPT.B64}' . base64_encode($hash);
|
||||
}
|
||||
return "{SHA256-CRYPT}" . $hash;
|
||||
}
|
||||
|
||||
public function sha512Crypt(string $pw, string $pw_db = '', $algorithm = 'SHA512-CRYPT'): string
|
||||
{
|
||||
if (!empty($pw_db)) {
|
||||
$pw_db = preg_replace('/^{SHA512-CRYPT(\.B64)?}/', '', $pw_db);
|
||||
|
||||
if ($algorithm == 'SHA512-CRYPT.B64') {
|
||||
$pw_db = base64_decode($pw_db);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($pw_db)) {
|
||||
$pw_db = '$6$' . substr(sha1(random_bytes(8)), 0, 16);
|
||||
}
|
||||
|
||||
$hash = crypt($pw, $pw_db);
|
||||
|
||||
if ($algorithm == 'SHA512-CRYPT.B64') {
|
||||
$hash = base64_encode($hash);
|
||||
return "{SHA512-CRYPT.B64}{$hash}";
|
||||
}
|
||||
|
||||
return "{SHA512-CRYPT}$hash";
|
||||
}
|
||||
|
||||
public function argon2ICrypt(string $pw, string $pw_db = '', $algorithm = 'ARGON2I'): string
|
||||
{
|
||||
if (!empty($pw_db)) {
|
||||
$pw_db = preg_replace('/^{ARGON2I(\.B64)?}/', '', $pw_db);
|
||||
$orig_pwdb = $pw_db;
|
||||
if ($algorithm == 'ARGON2I.B64') {
|
||||
$pw_db = base64_decode($pw_db);
|
||||
}
|
||||
|
||||
if (password_verify($pw, $pw_db)) {
|
||||
return "{{$algorithm}}" . $orig_pwdb;
|
||||
}
|
||||
$hash = password_hash($pw, PASSWORD_ARGON2I);
|
||||
if ($algorithm == 'ARGON2I') {
|
||||
return '{ARGON2I}' . $hash;
|
||||
}
|
||||
return '{ARGON2I.B64}' . base64_encode($hash);
|
||||
;
|
||||
}
|
||||
|
||||
$hash = password_hash($pw, PASSWORD_ARGON2I);
|
||||
|
||||
if ($algorithm == 'ARGON2I') {
|
||||
return '{ARGON2I}' . $hash;
|
||||
}
|
||||
|
||||
return "{ARGON2I.B64}" . base64_encode($hash);
|
||||
}
|
||||
|
||||
public function argon2idCrypt(string $pw, string $pw_db = '', string $algorithm = 'ARGON2ID'): string
|
||||
{
|
||||
if (!defined('PASSWORD_ARGON2ID')) {
|
||||
throw new \Exception("Requires PHP 7.3+");
|
||||
}
|
||||
|
||||
if (!empty($pw_db)) {
|
||||
$pw_db = preg_replace('/^{ARGON2ID(\.B64)?}/', '', $pw_db);
|
||||
|
||||
$orig_pwdb = $pw_db;
|
||||
|
||||
if ($algorithm == 'ARGON2ID.B64') {
|
||||
$pw_db = base64_decode($pw_db);
|
||||
}
|
||||
|
||||
if (password_verify($pw, $pw_db)) {
|
||||
return "{{$algorithm}}" . $orig_pwdb;
|
||||
}
|
||||
|
||||
$hash = password_hash($pw, PASSWORD_ARGON2ID);
|
||||
|
||||
if ($algorithm == 'ARGON2ID') {
|
||||
return '{ARGON2ID}' . $hash;
|
||||
}
|
||||
// if($algorithm == 'ARGON2ID.B64') {
|
||||
return '{ARGON2ID.B64}' . base64_encode($hash);
|
||||
}
|
||||
|
||||
$hash = password_hash($pw, PASSWORD_ARGON2ID);
|
||||
|
||||
if ($algorithm == 'ARGON2ID') {
|
||||
return '{ARGON2ID}' . $hash;
|
||||
}
|
||||
return '{ARGON2ID.B64}' . base64_encode($hash);
|
||||
}
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
<?php
|
||||
|
||||
require_once(__DIR__ . '/../model/PFACrypt.php');
|
||||
|
||||
class PaCryptTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testMd5Crypt()
|
||||
{
|
||||
$hash = _pacrypt_md5crypt('test', '');
|
||||
|
||||
$h = new PFACrypt('MD5-CRYPT');
|
||||
|
||||
$this->assertNotEmpty($hash);
|
||||
$this->assertNotEquals('test', $hash);
|
||||
|
||||
@ -68,6 +72,7 @@ class PaCryptTest extends \PHPUnit\Framework\TestCase
|
||||
public function testPacryptDovecot()
|
||||
{
|
||||
global $CONF;
|
||||
|
||||
if (!file_exists('/usr/bin/doveadm')) {
|
||||
$this->markTestSkipped("No /usr/bin/doveadm");
|
||||
}
|
||||
@ -78,7 +83,6 @@ class PaCryptTest extends \PHPUnit\Framework\TestCase
|
||||
$expected_hash = '{SHA1}qUqP5cyxm6YcTAhz05Hph5gvu9M=';
|
||||
|
||||
$this->assertEquals($expected_hash, _pacrypt_dovecot('test', ''));
|
||||
|
||||
$this->assertEquals($expected_hash, _pacrypt_dovecot('test', $expected_hash));
|
||||
|
||||
// This should also work.
|
||||
@ -89,7 +93,27 @@ class PaCryptTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertNotEquals($sha512, _pacrypt_dovecot('foobarbaz', $sha512));
|
||||
}
|
||||
|
||||
|
||||
public function testPhpCrypt()
|
||||
{
|
||||
$config = Config::getInstance();
|
||||
Config::write('encrypt', 'php_crypt');
|
||||
|
||||
|
||||
$CONF = Config::getInstance()->getAll();
|
||||
|
||||
$sha512_crypt = '$6$ijF8bgunALqnEHTo$LHVa6XQBpM5Gt16RMFQuXqrGAS0y0ymaLS8pnkeVUTSx3t2DrGqWwRj6q4ef3V3SWYkb5xkuN9bv7joxNd8kA1';
|
||||
|
||||
$enc = _pacrypt_php_crypt('foo', $sha512_crypt);
|
||||
|
||||
$this->assertEquals($enc, $sha512_crypt);
|
||||
|
||||
$fail = _pacrypt_php_crypt('bar', $sha512_crypt);
|
||||
|
||||
$this->assertNotEquals($fail, $sha512_crypt);
|
||||
}
|
||||
|
||||
public function testPhpCryptMd5()
|
||||
{
|
||||
$config = Config::getInstance();
|
||||
Config::write('encrypt', 'php_crypt:MD5');
|
||||
@ -155,29 +179,129 @@ class PaCryptTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertFalse(strcmp($str1, $str2) == 0 && strcmp($str1, $str3) == 0);
|
||||
}
|
||||
|
||||
public function testSha512B64()
|
||||
|
||||
public function testNewDovecotStuff()
|
||||
{
|
||||
$str1 = _pacrypt_sha512_b64('test', '');
|
||||
$str2 = _pacrypt_sha512_b64('test', '');
|
||||
global $CONF;
|
||||
|
||||
$this->assertNotEmpty($str1);
|
||||
$this->assertNotEmpty($str2);
|
||||
$this->assertNotEquals($str1, $str2); // should have different salts
|
||||
// should all be from 'test123', generated via dovecot.
|
||||
|
||||
$algo_to_example = [
|
||||
'SHA1' => '{SHA1}cojt0Pw//L6ToM8G41aOKFIWh7w=',
|
||||
'SHA1.B64' => '{SHA1.B64}cojt0Pw//L6ToM8G41aOKFIWh7w=',
|
||||
'BLF-CRYPT' => '{BLF-CRYPT}$2y$05$cEEZv2h/NtLXII.emi2TP.rMZyB7VRSkyToXWBqqz6cXDoyay166q',
|
||||
'BLF-CRYPT.B64' => '{BLF-CRYPT.B64}JDJ5JDA1JEhlR0lBeGFHR2tNUGxjRWpyeFc0eU9oRjZZZ1NuTWVOTXFxNWp4bmFwVjUwdGU3c2x2L1VT',
|
||||
'SHA512-CRYPT' => '{SHA512-CRYPT}$6$MViNQUSbWyXWL9wZ$63VsBU2a/ZFb9f/dK4EmaXABE9jAcNltR7y6a2tXLKoV5F5jMezno.2KpmtD3U0FDjfa7A.pkCluVMlZJ.F64.',
|
||||
'SHA512-CRYPT.B64' => '{SHA512-CRYPT.B64}JDYkR2JwY3NiZXNMWk9DdERXbiRYdXlhdEZTdy9oa3lyUFE0d24wenpGQTZrSlpTUE9QVWdPcjVRUC40bTRMTjEzdy81aWMvWTdDZllRMWVqSWlhNkd3Q2Z0ZnNjZEFpam9OWjl3OU5tLw==',
|
||||
'SHA512' => '{SHA512}2u9JU7l4M2XK1mFSI3IFBsxGxRZ80Wq1APpZeqCP+WTrJPsZaH8012Zfd4/LbFNY/ApbgeFmLPkPc6JnHFP5kQ==',
|
||||
|
||||
$actualHash = '{SHA512-CRYPT.B64}JDYkM2NWcFM1WFNlUHl5MzdwSiRZWW80d0FmeWg5MXpxcS4uY3dtYUR1Y1RodTJGTDY1NHpXNUNvRU0wT3hXVFFzZkxIZ1JJSTZmT281OVpDUWJOTTF2L0JXajloME0vVjJNbENNMUdwLg==';
|
||||
// postfixadmin 'incorrectly' classes sha512.b64 as a sha512-crypted string that's b64 encoded.
|
||||
// really SHA512.B64 should be base64_encode(hash('sha512', 'something', true));
|
||||
'SHA512.B64' => '{SHA512-CRYPT.B64}JDYkMDBpOFJXQ0JwMlFMMDlobCRFMVFWLzJjbENPbEo4OTg0SjJyY1oxeXNTaFJIYVhJeVdFTDdHRGl3aHliYkhQUHBUQjZTM0lFMlYya2ZXczZWbHY0aDVNa3N0anpud0xuRTBWZVRELw==',
|
||||
'CRYPT' => '{CRYPT}$2y$05$ORqzr0AagWr25v3ixHD5QuMXympIoNTbipEFZz6aAmovGNoij2vDO',
|
||||
'MD5-CRYPT' => '{MD5-CRYPT}$1$AIjpWveQ$2s3eEAbZiqkJhMYUIVR240',
|
||||
'PLAIN-MD5' => '{PLAIN-MD5}cc03e747a6afbbcbf8be7668acfebee5',
|
||||
'SSHA' => '{SSHA}ZkqrSEAhvd0FTHaK1IxAQCRa5LWbxGQY',
|
||||
'PLAIN' => '{PLAIN}test123',
|
||||
'CLEAR' => '{CLEAR}test123',
|
||||
'CLEARTEXT' => '{CLEARTEXT}test123',
|
||||
'ARGON2I' => '{ARGON2I}$argon2i$v=19$m=32768,t=4,p=1$xoOcAGa27k0Sr6ZPbA9ODw$wl/KAZVmJooD/35IFG5oGwyQiAREXrLss5BPS1PDKfA',
|
||||
'ARGON2ID' => '{ARGON2ID}$argon2id$v=19$m=65536,t=3,p=1$eaXP376O9/VxleLw9OQIxg$jOoDyECeRRV4eta3eSN/j0RdBgqaA1VBGAA/pbviI20',
|
||||
'ARGON2ID.B64' => '{ARGON2ID.B64}JGFyZ29uMmlkJHY9MTkkbT02NTUzNix0PTMscD0xJEljdG9DWko1T04zWlYzM3I0TVMrNEEkMUVtNTJRWkdsRlJzNnBsRXpwVmtMeVd4dVNPRUZ2dUZnaVNhTmNlb08rOA==',
|
||||
'SHA256' => '{SHA256}7NcYcNGWMxapfjrDQIyYNa2M8PPBvHA1J8MCZVNPda4=',
|
||||
'SHA256-CRYPT' => '{SHA256-CRYPT}$5$CFly6wzfn2az3U8j$EhfQPTdjpMGAisfCjCKektLke5GGEmtdLVaCZSmsKw2',
|
||||
'SHA256-CRYPT.B64' => '{SHA256-CRYPT.B64}JDUkUTZZS1ZzZS5sSVJoLndodCR6TWNOUVFVVkhtTmM1ME1SQk9TR3BEeGpRY2M1TzJTQ1lkbWhPN1YxeHlD',
|
||||
];
|
||||
|
||||
$check = _pacrypt_sha512_b64('test', $actualHash);
|
||||
// php 7.3 and below do not support these.
|
||||
if (phpversion() < '7.3') {
|
||||
unset($algo_to_example['ARGON2ID']);
|
||||
unset($algo_to_example['ARGON2ID.B64']);
|
||||
}
|
||||
|
||||
$this->assertTrue(hash_equals($check, $actualHash));
|
||||
foreach ($algo_to_example as $algorithm => $example_hash) {
|
||||
$CONF['encrypt'] = $algorithm;
|
||||
$pfa_new_hash = pacrypt('test123');
|
||||
|
||||
$str3 = _pacrypt_sha512_b64('foo', '');
|
||||
$pacrypt_check = pacrypt('test123', $example_hash);
|
||||
$pacrypt_sanity = pacrypt('zzzzzzz', $example_hash);
|
||||
|
||||
$this->assertNotEmpty($str3);
|
||||
$this->assertNotEquals($example_hash, $pacrypt_sanity, "Should not match, zzzz password. $algorithm / $pacrypt_sanity");
|
||||
|
||||
$this->assertFalse(hash_equals('test', $str3));
|
||||
$this->assertEquals($example_hash, $pacrypt_check, "Should match, algorithm: $algorithm generated:{$pacrypt_check} vs example:{$example_hash}");
|
||||
|
||||
$this->assertTrue(hash_equals(_pacrypt_sha512_b64('foo', $str3), $str3));
|
||||
$new_new = pacrypt('test123', $pfa_new_hash);
|
||||
|
||||
$this->assertEquals($pfa_new_hash, $new_new, "Trying: $algorithm => gave: $new_new with $pfa_new_hash ... ");
|
||||
}
|
||||
}
|
||||
|
||||
public function testSha512CryptB64()
|
||||
{
|
||||
$c = new PFACrypt('SHA512CRYPT.B64');
|
||||
|
||||
// "SHA512-CRYPT.B64": "{SHA512-CRYPT.B64}JDYkR2JwY3NiZXNMWk9DdERXbiRYdXlhdEZTdy9oa3lyUFE0d24wenpGQTZrSlpTUE9QVWdPcjVRUC40bTRMTjEzdy81aWMvWTdDZllRMWVqSWlhNkd3Q2Z0ZnNjZEFpam9OWjl3OU5tLw==",
|
||||
$crypt = '{SHA512-CRYPT.B64}JDYkR2JwY3NiZXNMWk9DdERXbiRYdXlhdEZTdy9oa3lyUFE0d24wenpGQTZrSlpTUE9QVWdPcjVRUC40bTRMTjEzdy81aWMvWTdDZllRMWVqSWlhNkd3Q2Z0ZnNjZEFpam9OWjl3OU5tLw==';
|
||||
$this->assertEquals($crypt, $c->pacrypt('test123', $crypt));
|
||||
}
|
||||
|
||||
public function testWeCopeWithDifferentMethodThanConfigured()
|
||||
{
|
||||
$c = new PFACrypt('MD5-CRYPT');
|
||||
$md5Crypt = '{MD5-CRYPT}$1$AIjpWveQ$2s3eEAbZiqkJhMYUIVR240';
|
||||
|
||||
$this->assertEquals($md5Crypt, $c->pacrypt('test123', $md5Crypt));
|
||||
|
||||
$c = new PFACrypt('SHA1');
|
||||
|
||||
$this->assertEquals($md5Crypt, $c->pacrypt('test123', $md5Crypt));
|
||||
|
||||
$sha1Crypt = '{SHA1}cojt0Pw//L6ToM8G41aOKFIWh7w=';
|
||||
|
||||
$this->assertEquals($sha1Crypt, $c->pacrypt('test123', $sha1Crypt));
|
||||
}
|
||||
|
||||
public function testSomeCourierHashes()
|
||||
{
|
||||
global $CONF;
|
||||
|
||||
$options = [
|
||||
'courier:md5' => '{MD5}zAPnR6avu8v4vnZorP6+5Q==',
|
||||
'courier:md5raw' => '{MD5RAW}cc03e747a6afbbcbf8be7668acfebee5',
|
||||
'courier:ssha' => '{SSHA}pJTac1QSIHoi0qBPdqnBvgPdjfFtDRVY',
|
||||
'courier:sha256' => '{SHA256}7NcYcNGWMxapfjrDQIyYNa2M8PPBvHA1J8MCZVNPda4=',
|
||||
];
|
||||
|
||||
foreach ($options as $algorithm => $example_hash) {
|
||||
$CONF['encrypt'] = $algorithm;
|
||||
|
||||
$pacrypt_check = pacrypt('test123', $example_hash);
|
||||
$pacrypt_sanity = pacrypt('zzzzz', $example_hash);
|
||||
$pfa_new_hash = pacrypt('test123');
|
||||
|
||||
$this->assertNotEquals($pacrypt_sanity, $pfa_new_hash);
|
||||
$this->assertNotEquals($pacrypt_sanity, $example_hash);
|
||||
|
||||
$this->assertEquals($example_hash, $pacrypt_check, "Should match, algorithm: $algorithm generated:{$pacrypt_check} vs example:{$example_hash}");
|
||||
|
||||
$new = pacrypt('test123', $pfa_new_hash);
|
||||
|
||||
$this->assertEquals($new, $pfa_new_hash, "Trying: $algorithm => gave: $new with $pfa_new_hash");
|
||||
}
|
||||
}
|
||||
|
||||
public function testWeSupportWhatWeSayWeDo()
|
||||
{
|
||||
foreach (PFACrypt::DOVECOT_NATIVE as $algorithm) {
|
||||
if (phpversion() < 7.3 && ($algorithm == 'ARGON2ID' || $algorithm == 'ARGON2ID.B64')) {
|
||||
continue; // needs PHP7.3+
|
||||
}
|
||||
$c = new PFACrypt($algorithm);
|
||||
$hash1 = $c->pacrypt('test123');
|
||||
|
||||
$this->assertEquals($hash1, $c->pacrypt('test123', $hash1));
|
||||
$this->assertNotEquals($hash1, $c->pacrypt('9999test9999', $hash1));
|
||||
}
|
||||
}
|
||||
|
||||
public function testObviousMechs()
|
||||
@ -190,13 +314,13 @@ class PaCryptTest extends \PHPUnit\Framework\TestCase
|
||||
'cleartext' => 'test123',
|
||||
'mysql_encrypt' => '$6$$KMCDSuWNoVgNrK5P1zDS12ZZt.LV4z9v9NtD0AG0T5Rv/n0wWVvZmHMSKKZQciP7lrqrlbrBrBd4lhBSGy1BU0',
|
||||
'authlib' => '{md5raw}cc03e747a6afbbcbf8be7668acfebee5',
|
||||
'php_crypt:SHA512' => '$6$IeqpXtDIXF09ADdc$IsE.SSK3zuwtS9fdWZ0oVxXQjPDj834xqxTiv3Qfidq3AbAjPb0DNyI28JyzmDVlbfC9uSfNxD9RUyeO1.7FV/',
|
||||
'php_crypt:SHA512' => '{SHA512-CRYPT}$6$IeqpXtDIXF09ADdc$IsE.SSK3zuwtS9fdWZ0oVxXQjPDj834xqxTiv3Qfidq3AbAjPb0DNyI28JyzmDVlbfC9uSfNxD9RUyeO1.7FV/',
|
||||
'php_crypt:DES' => 'VXAXutUnpVYg6',
|
||||
'php_crypt:MD5' => '$1$rGTbP.KE$wimpECWs/wQa7rnSwCmHU.',
|
||||
'php_crypt:SHA256' => '$5$UaZs6ZuaLkVPx3bM$4JwAqdphXVutFYw7COgAkp/vj09S1DfjIftxtjqDrr/',
|
||||
'sha512.b64' => '{SHA512-CRYPT.B64}JDYkMDBpOFJXQ0JwMlFMMDlobCRFMVFWLzJjbENPbEo4OTg0SjJyY1oxeXNTaFJIYVhJeVdFTDdHRGl3aHliYkhQUHBUQjZTM0lFMlYya2ZXczZWbHY0aDVNa3N0anpud0xuRTBWZVRELw==',
|
||||
];
|
||||
|
||||
|
||||
foreach ($mechs as $mech => $example_hash) {
|
||||
if ($mech == 'mysql_encrypt' && Config::read_string('database_type') != 'mysql') {
|
||||
continue;
|
||||
|
Loading…
Reference in New Issue
Block a user