mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Added GnuPG->verify() for testing #89
Bugfix SnappyMail\PGP\Keyservers Renamed DoPgpVerify to DoMessagePgpVerify
This commit is contained in:
parent
fde44c0102
commit
bf84684965
8 changed files with 134 additions and 56 deletions
|
|
@ -658,7 +658,7 @@ export class MessageModel extends AbstractModel {
|
||||||
if (params) {
|
if (params) {
|
||||||
params.Folder = this.folder;
|
params.Folder = this.folder;
|
||||||
params.Uid = this.uid;
|
params.Uid = this.uid;
|
||||||
rl.app.Remote.post('PgpVerify', null, params)
|
rl.app.Remote.post('MessagePgpVerify', null, params)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
console.dir(data);
|
console.dir(data);
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -713,7 +713,7 @@ trait Admin
|
||||||
'loaded' => true
|
'loaded' => true
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
foreach (['APCu', 'cURL','GD','Gmagick','Imagick','intl','LDAP','OpenSSL','pdo_mysql','pdo_pgsql','pdo_sqlite','redis','Sodium','uuid','XXTEA','Zip'] as $name) {
|
foreach (['APCu', 'cURL','GnuPG','GD','Gmagick','Imagick','intl','LDAP','OpenSSL','pdo_mysql','pdo_pgsql','pdo_sqlite','redis','Sodium','uuid','XXTEA','Zip'] as $name) {
|
||||||
$aResult[] = [
|
$aResult[] = [
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'loaded' => \extension_loaded(\strtolower($name))
|
'loaded' => \extension_loaded(\strtolower($name))
|
||||||
|
|
|
||||||
|
|
@ -12,40 +12,6 @@ use MailSo\Imap\Enumerations\MessageFlag;
|
||||||
|
|
||||||
trait Messages
|
trait Messages
|
||||||
{
|
{
|
||||||
public function DoPgpVerify() : array
|
|
||||||
{
|
|
||||||
$sFolderName = $this->GetActionParam('Folder', '');
|
|
||||||
$iUid = (int) $this->GetActionParam('Uid', 0);
|
|
||||||
$sBodyPartId = $this->GetActionParam('BodyPartId', '');
|
|
||||||
$sSigPartId = $this->GetActionParam('SigPartId', '');
|
|
||||||
$sMicAlg = $this->GetActionParam('MicAlg', '');
|
|
||||||
|
|
||||||
$oAccount = $this->initMailClientConnection();
|
|
||||||
|
|
||||||
$oImapClient = $this->MailClient()->ImapClient();
|
|
||||||
$oImapClient->FolderExamine($sFolderName);
|
|
||||||
|
|
||||||
$aFetchResponse = $oImapClient->Fetch([
|
|
||||||
// An empty section specification refers to the entire message, including the header.
|
|
||||||
// But Dovecot does not return it with BODY.PEEK[1], so we also use BODY.PEEK[1.MIME].
|
|
||||||
$aFetchItems[] = FetchType::BODY_PEEK.'['.$sBodyPartId.'.MIME]',
|
|
||||||
$aFetchItems[] = FetchType::BODY_PEEK.'['.$sBodyPartId.']',
|
|
||||||
$aFetchItems[] = FetchType::BODY_PEEK.'['.$sSigPartId.']'
|
|
||||||
], $iUid, true);
|
|
||||||
|
|
||||||
$oFetchResponse = $aFetchResponse[0];
|
|
||||||
|
|
||||||
$result = [
|
|
||||||
'MIME' => \trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.'.MIME]')),
|
|
||||||
'Body' => \trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.']')),
|
|
||||||
'Signature' => \trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sSigPartId.']'))
|
|
||||||
];
|
|
||||||
|
|
||||||
// TODO: check gnugp and \SnappyMail\PGP\Keyservers::get() and \SnappyMail\PGP\Keyservers::index()
|
|
||||||
|
|
||||||
return $this->DefaultResponse(__FUNCTION__, $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws \MailSo\Base\Exceptions\Exception
|
* @throws \MailSo\Base\Exceptions\Exception
|
||||||
*/
|
*/
|
||||||
|
|
@ -686,6 +652,93 @@ trait Messages
|
||||||
return $this->DefaultResponse(__FUNCTION__, $mResult);
|
return $this->DefaultResponse(__FUNCTION__, $mResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function DoMessagePgpVerify() : array
|
||||||
|
{
|
||||||
|
$sFolderName = $this->GetActionParam('Folder', '');
|
||||||
|
$iUid = (int) $this->GetActionParam('Uid', 0);
|
||||||
|
$sBodyPartId = $this->GetActionParam('BodyPartId', '');
|
||||||
|
$sSigPartId = $this->GetActionParam('SigPartId', '');
|
||||||
|
$sMicAlg = $this->GetActionParam('MicAlg', '');
|
||||||
|
|
||||||
|
$oAccount = $this->initMailClientConnection();
|
||||||
|
|
||||||
|
$oImapClient = $this->MailClient()->ImapClient();
|
||||||
|
$oImapClient->FolderExamine($sFolderName);
|
||||||
|
|
||||||
|
$aFetchResponse = $oImapClient->Fetch([
|
||||||
|
// An empty section specification refers to the entire message, including the header.
|
||||||
|
// But Dovecot does not return it with BODY.PEEK[1], so we also use BODY.PEEK[1.MIME].
|
||||||
|
FetchType::BODY_PEEK.'['.$sBodyPartId.'.MIME]',
|
||||||
|
FetchType::BODY_PEEK.'['.$sBodyPartId.']',
|
||||||
|
FetchType::BODY_PEEK.'['.$sSigPartId.']',
|
||||||
|
FetchType::BuildBodyCustomHeaderRequest([
|
||||||
|
\MailSo\Mime\Enumerations\Header::FROM_,
|
||||||
|
], true)
|
||||||
|
], $iUid, true);
|
||||||
|
|
||||||
|
$oFetchResponse = $aFetchResponse[0];
|
||||||
|
|
||||||
|
$sKey = '';
|
||||||
|
$sFrom = $oFetchResponse->GetFetchValue('BODY[HEADER.FIELDS (FROM)]');
|
||||||
|
$aFrom = [];
|
||||||
|
if (\preg_match('/[^\\s<>]+@[^\\s<>]+/', $sFrom, $aFrom)) {
|
||||||
|
$sFrom = $aFrom[0];
|
||||||
|
}
|
||||||
|
if ($sFrom) {
|
||||||
|
/* // Check expired/revoked
|
||||||
|
$aKey = $this->StorageProvider()->Get(
|
||||||
|
$oAccount,
|
||||||
|
\RainLoop\Providers\Storage\Enumerations\StorageType::PGP,
|
||||||
|
\sha1($sFrom)
|
||||||
|
);
|
||||||
|
if ($aKey) {
|
||||||
|
$sKey = $aKeys[0]['key'];
|
||||||
|
} else
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
$aKeys = \SnappyMail\PGP\Keyservers::index($sFrom);
|
||||||
|
if ($aKeys) {
|
||||||
|
$sKey = \SnappyMail\PGP\Keyservers::get($aKeys[0]['keyid']);
|
||||||
|
if ($sKey) {
|
||||||
|
$aKeys[0]['key'] = $sKey;
|
||||||
|
/*
|
||||||
|
$this->StorageProvider()->Put(
|
||||||
|
$oAccount,
|
||||||
|
\RainLoop\Providers\Storage\Enumerations\StorageType::PGP,
|
||||||
|
\sha1($sFrom),
|
||||||
|
$aKeys[0]
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'MIME' => \trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.'.MIME]')),
|
||||||
|
'Body' => \trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.']')),
|
||||||
|
'Signature' => \trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sSigPartId.']')),
|
||||||
|
'From' => $sFrom,
|
||||||
|
'PubKey' => $sKey
|
||||||
|
];
|
||||||
|
|
||||||
|
// TODO: this fails
|
||||||
|
if ($result['PubKey'] && \class_exists('gnupg')) {
|
||||||
|
$pgp_dir = $this->StorageProvider()->GenerateFilePath($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::PGP);
|
||||||
|
$gpg = new \gnupg(['home_dir' => \dirname($pgp_dir) . '/.gnupg']);
|
||||||
|
$gpg->import($result['PubKey']);
|
||||||
|
$info = $gpg->verify(
|
||||||
|
\trim(\trim($result['MIME']) . "\r\n\r\n" . \trim($result['Body'])),
|
||||||
|
$result['Signature']
|
||||||
|
);
|
||||||
|
$result['gnupg'] = $info;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->DefaultResponse(__FUNCTION__, $result);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws \RainLoop\Exceptions\ClientException
|
* @throws \RainLoop\Exceptions\ClientException
|
||||||
* @throws \MailSo\Net\Exceptions\ConnectionException
|
* @throws \MailSo\Net\Exceptions\ConnectionException
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,11 @@ class Storage extends \RainLoop\Providers\AbstractProvider
|
||||||
return $this->oDriver->DeleteStorage($mAccount);
|
return $this->oDriver->DeleteStorage($mAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function GenerateFilePath($mAccount, int $iStorageType, bool $bMkDir = false) : string
|
||||||
|
{
|
||||||
|
return $this->oDriver->GenerateFilePath($mAccount, $iStorageType, $bMkDir);
|
||||||
|
}
|
||||||
|
|
||||||
public function IsActive() : bool
|
public function IsActive() : bool
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,5 @@ class StorageType
|
||||||
const NOBODY = 3;
|
const NOBODY = 3;
|
||||||
const SIGN_ME = 4;
|
const SIGN_ME = 4;
|
||||||
const SESSION = 5;
|
const SESSION = 5;
|
||||||
|
const PGP = 6;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,10 +89,7 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
|
||||||
return $this->bLocal;
|
return $this->bLocal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function GenerateFilePath($mAccount, int $iStorageType, bool $bMkDir = false, bool $bForDeleteAction = false) : string
|
||||||
* @param \RainLoop\Model\Account|string|null $mAccount
|
|
||||||
*/
|
|
||||||
protected function generateFileName($mAccount, int $iStorageType, string $sKey, bool $bMkDir = false, bool $bForDeleteAction = false) : string
|
|
||||||
{
|
{
|
||||||
$sEmail = $sSubFolder = '';
|
$sEmail = $sSubFolder = '';
|
||||||
if (null === $mAccount) {
|
if (null === $mAccount) {
|
||||||
|
|
@ -103,6 +100,8 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
|
||||||
$sSubFolder = '.sign_me';
|
$sSubFolder = '.sign_me';
|
||||||
} else if (StorageType::SESSION === $iStorageType) {
|
} else if (StorageType::SESSION === $iStorageType) {
|
||||||
$sSubFolder = '.sessions';
|
$sSubFolder = '.sessions';
|
||||||
|
} else if (StorageType::PGP === $iStorageType) {
|
||||||
|
$sSubFolder = '.pgp';
|
||||||
}
|
}
|
||||||
} else if ($mAccount instanceof \RainLoop\Model\AdditionalAccount) {
|
} else if ($mAccount instanceof \RainLoop\Model\AdditionalAccount) {
|
||||||
$sEmail = $mAccount->ParentEmail();
|
$sEmail = $mAccount->ParentEmail();
|
||||||
|
|
@ -117,11 +116,12 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
|
||||||
switch ($iStorageType)
|
switch ($iStorageType)
|
||||||
{
|
{
|
||||||
case StorageType::NOBODY:
|
case StorageType::NOBODY:
|
||||||
$sFilePath = $this->sDataPath.'/__nobody__/'.\sha1($sKey ?: \time());
|
$sFilePath = $this->sDataPath.'/__nobody__/';
|
||||||
break;
|
break;
|
||||||
case StorageType::SIGN_ME:
|
case StorageType::SIGN_ME:
|
||||||
case StorageType::SESSION:
|
case StorageType::SESSION:
|
||||||
case StorageType::CONFIG:
|
case StorageType::CONFIG:
|
||||||
|
case StorageType::PGP:
|
||||||
if (empty($sEmail)) {
|
if (empty($sEmail)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
@ -133,8 +133,7 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
|
||||||
$sFilePath = $this->sDataPath
|
$sFilePath = $this->sDataPath
|
||||||
.'/'.\RainLoop\Utils::fixName($sDomain ?: 'unknown.tld')
|
.'/'.\RainLoop\Utils::fixName($sDomain ?: 'unknown.tld')
|
||||||
.'/'.\RainLoop\Utils::fixName(\implode('@', $aEmail) ?: '.unknown')
|
.'/'.\RainLoop\Utils::fixName(\implode('@', $aEmail) ?: '.unknown')
|
||||||
.'/'.($sSubFolder ? \RainLoop\Utils::fixName($sSubFolder).'/' : '')
|
.'/'.($sSubFolder ? \RainLoop\Utils::fixName($sSubFolder).'/' : '');
|
||||||
.($sKey ? \RainLoop\Utils::fixName($sKey) : '');
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new \Exception("Invalid storage type {$iStorageType}");
|
throw new \Exception("Invalid storage type {$iStorageType}");
|
||||||
|
|
@ -151,6 +150,22 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
|
||||||
return $sFilePath;
|
return $sFilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \RainLoop\Model\Account|string|null $mAccount
|
||||||
|
*/
|
||||||
|
protected function generateFileName($mAccount, int $iStorageType, string $sKey, bool $bMkDir = false, bool $bForDeleteAction = false) : string
|
||||||
|
{
|
||||||
|
$sFilePath = $this->GenerateFilePath($mAccount, $iStorageType, $bMkDir, $bForDeleteAction);
|
||||||
|
if ($sFilePath) {
|
||||||
|
if (StorageType::NOBODY === $iStorageType) {
|
||||||
|
$sFilePath .= \sha1($sKey ?: \time());
|
||||||
|
} else {
|
||||||
|
$sFilePath .= ($sKey ? \RainLoop\Utils::fixName($sKey) : '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $sFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
public function SetLogger(?\MailSo\Log\Logger $oLogger)
|
public function SetLogger(?\MailSo\Log\Logger $oLogger)
|
||||||
{
|
{
|
||||||
$this->oLogger = $oLogger;
|
$this->oLogger = $oLogger;
|
||||||
|
|
|
||||||
|
|
@ -13,22 +13,24 @@ namespace SnappyMail\PGP;
|
||||||
|
|
||||||
abstract class Keyservers
|
abstract class Keyservers
|
||||||
{
|
{
|
||||||
public static $keyservers = [
|
public static $hosts = [
|
||||||
/*
|
/*
|
||||||
'hkp://keys.gnupg.net',
|
'https://keys.openpgp.org',
|
||||||
'hkps://keyserver.ubuntu.com',
|
'https://pgp.mit.edu',
|
||||||
keys.openpgp.org
|
'https://keyring.debian.org',
|
||||||
pgp.mit.edu
|
'https://attester.flowcrypt.com',
|
||||||
keyring.debian.org
|
'https://zimmermann.mayfirst.org',
|
||||||
keyserver.ubuntu.com
|
'https://pool.sks-keyservers.net',
|
||||||
attester.flowcrypt.com
|
|
||||||
zimmermann.mayfirst.org
|
|
||||||
*/
|
*/
|
||||||
'https://keys.fedoraproject.org'
|
'https://keyserver.ubuntu.com',
|
||||||
|
'https://keys.fedoraproject.org',
|
||||||
|
'https://keys.openpgp.org'
|
||||||
];
|
];
|
||||||
|
|
||||||
private static function fetch(string $host, string $op, string $search, bool $fingerprint = false, bool $exact = false) : ?Response
|
private static function fetch(string $host, string $op, string $search, bool $fingerprint = false, bool $exact = false) : ?\SnappyMail\HTTP\Response
|
||||||
{
|
{
|
||||||
|
$host = \str_replace('hkp://', 'http://', $host);
|
||||||
|
$host = \str_replace('hkps://', 'https://', $host);
|
||||||
$search = \urlencode($search);
|
$search = \urlencode($search);
|
||||||
$fingerprint = $fingerprint ? '&fingerprint=on' : '';
|
$fingerprint = $fingerprint ? '&fingerprint=on' : '';
|
||||||
$exact = $exact ? '&exact=on' : '';
|
$exact = $exact ? '&exact=on' : '';
|
||||||
|
|
@ -57,7 +59,7 @@ abstract class Keyservers
|
||||||
$keyId = '0x' . $keyId;
|
$keyId = '0x' . $keyId;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->keyservers as $host) {
|
foreach (static::$hosts as $host) {
|
||||||
$oResponse = static::fetch($host, 'get', $keyId);
|
$oResponse = static::fetch($host, 'get', $keyId);
|
||||||
if (!$oResponse) {
|
if (!$oResponse) {
|
||||||
\SnappyMail\Log::info('PGP', "No response for key {$keyId} on {$host}");
|
\SnappyMail\Log::info('PGP', "No response for key {$keyId} on {$host}");
|
||||||
|
|
@ -86,7 +88,7 @@ abstract class Keyservers
|
||||||
public static function index(string $search, bool $fingerprint = true, bool $exact = false) : array
|
public static function index(string $search, bool $fingerprint = true, bool $exact = false) : array
|
||||||
{
|
{
|
||||||
$keys = [];
|
$keys = [];
|
||||||
foreach ($this->keyservers as $host) {
|
foreach (static::$hosts as $host) {
|
||||||
$oResponse = static::fetch($host, 'index', $search, $fingerprint, $exact);
|
$oResponse = static::fetch($host, 'index', $search, $fingerprint, $exact);
|
||||||
if (!$oResponse) {
|
if (!$oResponse) {
|
||||||
\SnappyMail\Log::info('PGP', "No response for key {$keyId} on {$host}");
|
\SnappyMail\Log::info('PGP', "No response for key {$keyId} on {$host}");
|
||||||
|
|
@ -98,6 +100,7 @@ abstract class Keyservers
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = \explode("\n", $oResponse->body);
|
$result = \explode("\n", $oResponse->body);
|
||||||
|
$curKey = null;
|
||||||
foreach ($result as $line) {
|
foreach ($result as $line) {
|
||||||
// https://datatracker.ietf.org/doc/html/draft-shaw-openpgp-hkp-00#section-5.2
|
// https://datatracker.ietf.org/doc/html/draft-shaw-openpgp-hkp-00#section-5.2
|
||||||
$line = \explode(':', $line);
|
$line = \explode(':', $line);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ if (defined('APP_VERSION'))
|
||||||
$aOptional = array(
|
$aOptional = array(
|
||||||
'cURL' => extension_loaded('curl'),
|
'cURL' => extension_loaded('curl'),
|
||||||
'exif' => extension_loaded('exif'),
|
'exif' => extension_loaded('exif'),
|
||||||
|
'gnupg' => extension_loaded('gnupg'),
|
||||||
'gd' => extension_loaded('gd'),
|
'gd' => extension_loaded('gd'),
|
||||||
'gmagick' => extension_loaded('gmagick'),
|
'gmagick' => extension_loaded('gmagick'),
|
||||||
'imagick' => extension_loaded('imagick'),
|
'imagick' => extension_loaded('imagick'),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue