0
0
mirror of https://github.com/postfixadmin/postfixadmin.git synced 2024-09-19 19:22:14 +02:00

add SSHA format, add some specific courier algorithms, try and partially update docs

This commit is contained in:
David Goodwin 2021-05-09 21:31:53 +01:00
parent 858c0a0ecd
commit 543285a203
3 changed files with 145 additions and 26 deletions

View File

@ -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 attempt 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 #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.

View File

@ -4,6 +4,22 @@ 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',
'PLAIN-MD5',
'CRYPT',
];
public function __construct(string $algorithm)
{
$this->algorithm = $algorithm;
@ -16,26 +32,10 @@ class PFACrypt
// try and 'upgrade' some dovecot commands to use local algorithms (rather tnan a dependency on the dovecot binary).
if (preg_match('/^dovecot:/', $algorithm)) {
$tmp = preg_replace('/^dovecot:/', '', $algorithm);
$supported = [
'SHA1', 'SHA1,HEX', 'SHA1.B64',
'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',
'PLAIN-MD5',
'CRYPT',
];
if (in_array($tmp, $supported)) {
if (in_array($tmp, self::DOVECOT_NATIVE)) {
$algorithm = $tmp;
} else {
error_log("Warning: using alogrithm that requires proc_open: $algorithm, consider using one of : " . implode(', ', $supported));
error_log("Warning: using algorithm that requires proc_open: $algorithm, consider using one of : " . implode(', ', self::DOVECOT_NATIVE));
}
}
@ -62,8 +62,13 @@ class PFACrypt
case 'ARGON2ID.B64':
return $this->argon2idCrypt($pw, $pw_db, $algorithm);
case 'SSHA':
case 'courier:ssha':
return $this->hashSha1Salted($pw, $pw_db);
case 'SHA256':
return '{SHA256}' . base64_encode(hash('sha256', $pw, true));
case 'courier:sha256':
return $this->hashSha256($pw);
case 'SHA256-CRYPT':
case 'SHA256-CRYPT.B64':
@ -79,6 +84,12 @@ class PFACrypt
case 'PLAIN-MD5':
return $this->hashMd5($pw, $algorithm);
case 'courier:md5':
return '{MD5}' . base64_encode(md5($pw, true));
case 'courier:md5raw':
return '{MD5RAW}' . bin2hex(md5($pw, true));
case 'MD5':
case 'MD5-CRYPT':
return $this->cryptMd5($pw, $pw_db, $algorithm);
@ -118,6 +129,7 @@ class PFACrypt
case 'sha512crypt.b64':
return $this->pacrypt_sha512crypt_b64($pw, $pw_db);
}
if (preg_match("/^dovecot:/", $algorithm)) {
@ -144,6 +156,16 @@ class PFACrypt
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}';
@ -160,10 +182,14 @@ class PFACrypt
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)) {

View File

@ -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);
@ -204,6 +208,7 @@ class PaCryptTest extends \PHPUnit\Framework\TestCase
"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",
@ -235,4 +240,44 @@ EOF;
$this->assertEquals($pfa_new_hash, $new_new, "Trying: $algorithm => gave: $new_new with $pfa_new_hash ... ");
}
}
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($pacrypt_check, $example_hash, "Should match, algorithm: $algorithm generated:{$pacrypt_check} vs example:{$example_hash}");
$new = pacrypt('test123', $pfa_new_hash);
$this->assertEquals($pfa_new_hash, $new, "Trying: $algorithm => gave: $new with $pfa_new_hash");
}
}
public function testWeSupportWhatWeSayWeDo()
{
foreach (PFACrypt::DOVECOT_NATIVE as $algorithm) {
$c = new PFACrypt($algorithm);
$hash1 = $c->hash('test123');
$this->assertEquals($hash1, $c->hash('test123', $hash1));
$this->assertNotEquals($hash1, $c->hash('9999test9999', $hash1));
}
}
}