diff --git a/dev/Model/Message.js b/dev/Model/Message.js index c0a24bb02..2ba35074a 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -658,7 +658,7 @@ export class MessageModel extends AbstractModel { if (params) { params.Folder = this.folder; params.Uid = this.uid; - rl.app.Remote.post('PgpVerify', null, params) + rl.app.Remote.post('MessagePgpVerify', null, params) .then(data => { console.dir(data); }) diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php index c4112a061..8e6e0b2fc 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php @@ -713,7 +713,7 @@ trait Admin '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[] = [ 'name' => $name, 'loaded' => \extension_loaded(\strtolower($name)) diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php index 1066c283e..1afb437e4 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php @@ -12,40 +12,6 @@ use MailSo\Imap\Enumerations\MessageFlag; 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 */ @@ -686,6 +652,93 @@ trait Messages 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 \MailSo\Net\Exceptions\ConnectionException diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage.php index 75e974988..3f1790b84 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage.php @@ -66,6 +66,11 @@ class Storage extends \RainLoop\Providers\AbstractProvider 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 { return true; diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/Enumerations/StorageType.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/Enumerations/StorageType.php index fee728a75..18fd63f74 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/Enumerations/StorageType.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/Enumerations/StorageType.php @@ -9,4 +9,5 @@ class StorageType const NOBODY = 3; const SIGN_ME = 4; const SESSION = 5; + const PGP = 6; } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/FileStorage.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/FileStorage.php index 1909c3cec..ccc703151 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/FileStorage.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Storage/FileStorage.php @@ -89,10 +89,7 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage return $this->bLocal; } - /** - * @param \RainLoop\Model\Account|string|null $mAccount - */ - protected function generateFileName($mAccount, int $iStorageType, string $sKey, bool $bMkDir = false, bool $bForDeleteAction = false) : string + public function GenerateFilePath($mAccount, int $iStorageType, bool $bMkDir = false, bool $bForDeleteAction = false) : string { $sEmail = $sSubFolder = ''; if (null === $mAccount) { @@ -103,6 +100,8 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage $sSubFolder = '.sign_me'; } else if (StorageType::SESSION === $iStorageType) { $sSubFolder = '.sessions'; + } else if (StorageType::PGP === $iStorageType) { + $sSubFolder = '.pgp'; } } else if ($mAccount instanceof \RainLoop\Model\AdditionalAccount) { $sEmail = $mAccount->ParentEmail(); @@ -117,11 +116,12 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage switch ($iStorageType) { case StorageType::NOBODY: - $sFilePath = $this->sDataPath.'/__nobody__/'.\sha1($sKey ?: \time()); + $sFilePath = $this->sDataPath.'/__nobody__/'; break; case StorageType::SIGN_ME: case StorageType::SESSION: case StorageType::CONFIG: + case StorageType::PGP: if (empty($sEmail)) { return ''; } @@ -133,8 +133,7 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage $sFilePath = $this->sDataPath .'/'.\RainLoop\Utils::fixName($sDomain ?: 'unknown.tld') .'/'.\RainLoop\Utils::fixName(\implode('@', $aEmail) ?: '.unknown') - .'/'.($sSubFolder ? \RainLoop\Utils::fixName($sSubFolder).'/' : '') - .($sKey ? \RainLoop\Utils::fixName($sKey) : ''); + .'/'.($sSubFolder ? \RainLoop\Utils::fixName($sSubFolder).'/' : ''); break; default: throw new \Exception("Invalid storage type {$iStorageType}"); @@ -151,6 +150,22 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage 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) { $this->oLogger = $oLogger; diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/pgp/keyservers.php b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/keyservers.php index 9e4568e76..ad9a6d711 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/pgp/keyservers.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/keyservers.php @@ -13,22 +13,24 @@ namespace SnappyMail\PGP; abstract class Keyservers { - public static $keyservers = [ + public static $hosts = [ /* - 'hkp://keys.gnupg.net', - 'hkps://keyserver.ubuntu.com', - keys.openpgp.org - pgp.mit.edu - keyring.debian.org - keyserver.ubuntu.com - attester.flowcrypt.com - zimmermann.mayfirst.org + 'https://keys.openpgp.org', + 'https://pgp.mit.edu', + 'https://keyring.debian.org', + 'https://attester.flowcrypt.com', + 'https://zimmermann.mayfirst.org', + 'https://pool.sks-keyservers.net', */ - '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); $fingerprint = $fingerprint ? '&fingerprint=on' : ''; $exact = $exact ? '&exact=on' : ''; @@ -57,7 +59,7 @@ abstract class Keyservers $keyId = '0x' . $keyId; } - foreach ($this->keyservers as $host) { + foreach (static::$hosts as $host) { $oResponse = static::fetch($host, 'get', $keyId); if (!$oResponse) { \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 { $keys = []; - foreach ($this->keyservers as $host) { + foreach (static::$hosts as $host) { $oResponse = static::fetch($host, 'index', $search, $fingerprint, $exact); if (!$oResponse) { \SnappyMail\Log::info('PGP', "No response for key {$keyId} on {$host}"); @@ -98,6 +100,7 @@ abstract class Keyservers } $result = \explode("\n", $oResponse->body); + $curKey = null; foreach ($result as $line) { // https://datatracker.ietf.org/doc/html/draft-shaw-openpgp-hkp-00#section-5.2 $line = \explode(':', $line); diff --git a/snappymail/v/0.0.0/setup.php b/snappymail/v/0.0.0/setup.php index d514f866a..4cbaa5e5e 100644 --- a/snappymail/v/0.0.0/setup.php +++ b/snappymail/v/0.0.0/setup.php @@ -13,6 +13,7 @@ if (defined('APP_VERSION')) $aOptional = array( 'cURL' => extension_loaded('curl'), 'exif' => extension_loaded('exif'), + 'gnupg' => extension_loaded('gnupg'), 'gd' => extension_loaded('gd'), 'gmagick' => extension_loaded('gmagick'), 'imagick' => extension_loaded('imagick'),