getMainAccountFromToken(); if (!$oAccount) { return null; } $homedir = \dirname($this->StorageProvider()->GenerateFilePath( $oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::PGP )) . '/.gnupg'; if (!\is_dir($homedir)) { \mkdir($homedir, 0700, true); } if (!\is_writable($homedir)) { throw new \Exception("gpg homedir '{$homedir}' not writable"); } /** * Workaround error: socket name for '/very/long/path/to/.gnupg/S.gpg-agent.extra' is too long * BSD 4.4 max length = 104 */ if (80 < \strlen($homedir)) { \clearstatcache(); // First try a symbolic link $tmpdir = \sys_get_temp_dir() . '/snappymail'; // if (\RainLoop\Utils::inOpenBasedir($tmpdir) && if (\is_dir($tmpdir) || \mkdir($tmpdir, 0700, true)) { $link = $tmpdir . '/' . \md5($homedir); if (\is_link($link) || \symlink($homedir, $link)) { $homedir = $link; } } // Else try ~/.gnupg/ + hash(email address) if (80 < \strlen($homedir)) { $tmpdir = ($_SERVER['HOME'] ?: \exec('echo ~') ?: \dirname(\getcwd())) . '/.gnupg/'; if ($oAccount instanceof \RainLoop\Model\AdditionalAccount) { $tmpdir .= \sha1($oAccount->ParentEmail()); } else { $tmpdir .= \sha1($oAccount->Email()); } // if (\RainLoop\Utils::inOpenBasedir($tmpdir) && if (\is_dir($tmpdir) || \mkdir($tmpdir, 0700, true)) { $homedir = $link; } } if (104 <= \strlen($homedir . '/S.gpg-agent.extra')) { throw new \Exception("socket name for '{$homedir}/S.gpg-agent.extra' is too long"); } } return \SnappyMail\PGP\GnuPG::getInstance($homedir); } public function DoGnupgDecrypt() : array { $GPG = $this->GnuPG(); if (!$GPG) { return $this->FalseResponse(__FUNCTION__); } $GPG->addDecryptKey( $this->GetActionParam('KeyId', ''), $this->GetActionParam('Passphrase', '') ); $sData = $this->GetActionParam('Data', ''); $oPart = null; $result = [ 'data' => '', 'signatures' => [] ]; if ($sData) { $result = $GPG->decrypt($sData); // $oPart = \MailSo\Mime\Part::FromString($result); } else { $this->initMailClientConnection(); $this->MailClient()->MessageMimeStream( function ($rResource) use ($GPG, &$result, &$oPart) { if (\is_resource($rResource)) { $result['data'] = $GPG->decryptStream($rResource); // $oPart = \MailSo\Mime\Part::FromString($result); // $GPG->decryptStream($rResource, $rStreamHandle); // $oPart = \MailSo\Mime\Part::FromStream($rStreamHandle); } }, $this->GetActionParam('Folder', ''), (int) $this->GetActionParam('Uid', ''), $this->GetActionParam('PartId', '') ); } if ($oPart && $oPart->IsPgpSigned()) { // $GPG->verifyStream($oPart->SubParts[0]->Body, \stream_get_contents($oPart->SubParts[1]->Body)); // $result['signatures'] = $oPart->SubParts[0]; } return $this->DefaultResponse(__FUNCTION__, $result); } public function DoGnupgGetKeys() : array { $GPG = $this->GnuPG(); return $this->DefaultResponse(__FUNCTION__, $GPG ? $GPG->keyInfo('') : false); } public function DoGnupgExportKey() : array { $GPG = $this->GnuPG(); return $this->DefaultResponse(__FUNCTION__, $GPG ? $GPG->export( $this->GetActionParam('KeyId', ''), $this->GetActionParam('Passphrase', '') ) : false); } public function DoGnupgGenerateKey() : array { $fingerprint = false; $GPG = $this->GnuPG(); if ($GPG) { $sName = $this->GetActionParam('Name', ''); $sEmail = $this->GetActionParam('Email', ''); $fingerprint = $GPG->generateKey( $sName ? "{$sName} <{$sEmail}>" : $sEmail, $this->GetActionParam('Passphrase', '') ); } return $this->DefaultResponse(__FUNCTION__, $fingerprint); } public function DoGnupgDeleteKey() : array { $GPG = $this->GnuPG(); $sKeyId = $this->GetActionParam('KeyId', ''); $bPrivate = !!$this->GetActionParam('isPrivate', 0); return $this->DefaultResponse(__FUNCTION__, $GPG ? $GPG->deleteKey($sKeyId, $bPrivate) : false); } public function DoGnupgImportKey() : array { $sKey = $this->GetActionParam('Key', ''); $sKeyId = $this->GetActionParam('KeyId', ''); $sEmail = $this->GetActionParam('Email', ''); if (!$sKey) { try { if (!$sKeyId) { if (\preg_match('/[^\\s<>]+@[^\\s<>]+/', $sEmail, $aMatch)) { $sEmail = $aMatch[0]; } if ($sEmail) { $aKeys = \SnappyMail\PGP\Keyservers::index($sEmail); if ($aKeys) { $sKeyId = $aKeys[0]['keyid']; } } } if ($sKeyId) { $sKey = \SnappyMail\PGP\Keyservers::get($sKeyId); } } catch (\Throwable $e) { // ignore } } $GPG = $sKey ? $this->GnuPG() : null; return $this->DefaultResponse(__FUNCTION__, $GPG ? $GPG->import($sKey) : false); } /** * Used to import keys in OpenPGP.js * Handy when using multiple browsers */ public function DoGetStoredPGPKeys() : array { $oAccount = $this->getMainAccountFromToken(); if (!$oAccount) { return null; } $dir = $this->StorageProvider()->GenerateFilePath( $oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::PGP ); $keys = []; foreach (\glob("{$dir}/*") as $file) { if (\is_file($file)) { if ('.asc' === \substr($file, -4)) { $keys[] = \file_get_contents($file); } else if ('.key' === \substr($file, -4)) { $key = \json_decode(\file_get_contents($file), true); $mac = \array_pop($key); $hash = $oAccount->CryptKey(); if ($mac === \hash_hmac('sha1', $key[2], $hash)) { $key[1] = \base64_decode($key[1]); $key[2] = \base64_decode($key[2]); $keys[] = \SnappyMail\Crypt::Decrypt($key, $hash); } } } } return $this->DefaultResponse(__FUNCTION__, $keys); } /** * Used to store generated armored key pair from OpenPGP.js * Handy when using multiple browsers */ public function DoPgpStoreKeyPair() : array { $publicKey = $this->GetActionParam('publicKey', ''); $privateKey = $this->GetActionParam('privateKey', ''); $result = [ 'onServer' => [false, false, false], 'inGnuPG' => [false, false, false] ]; $onServer = (int) $this->GetActionParam('onServer', 0); if ($publicKey && $onServer & 1) { $result['onServer'][0] = $this->StorePGPKey($publicKey); } if ($privateKey && $onServer & 2) { $result['onServer'][1] = $this->StorePGPKey($privateKey); } $inGnuPG = (int) $this->GetActionParam('inGnuPG', 0); if ($inGnuPG) { $GPG = $this->GnuPG(); if ($publicKey && $inGnuPG & 1) { $result['inGnuPG'][0] = $GPG->import($publicKey); } if ($privateKey && $inGnuPG & 2) { $result['inGnuPG'][1] = $GPG->import($privateKey); } } // $revocationCertificate = $this->GetActionParam('revocationCertificate', ''); return $this->DefaultResponse(__FUNCTION__, $result); } /** * Used to store key from OpenPGP.js * Handy when using multiple browsers */ public function DoStorePGPKey() : array { $key = $this->GetActionParam('Key', ''); $keyId = $this->GetActionParam('KeyId', ''); return $this->DefaultResponse(__FUNCTION__, ($key && $keyId && $this->StorePGPKey($key, $keyId))); } private function StorePGPKey(string $key, string $keyId = '') : bool { $oAccount = $this->getMainAccountFromToken(); if ($oAccount) { $keyId = $keyId ? "0x{$keyId}" : \sha1($key); $dir = $this->StorageProvider()->GenerateFilePath( $oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::PGP ); if (\str_contains($key, 'PGP PRIVATE KEY')) { $hash = $oAccount->CryptKey(); $key = \SnappyMail\Crypt::Encrypt($key, $hash); $key[1] = \base64_encode($key[1]); $key[2] = \base64_encode($key[2]); $key[] = \hash_hmac('sha1', $key[2], $hash); return !!\file_put_contents("{$dir}{$keyId}.key", \json_encode($key)); } if (\str_contains($key, 'PGP PUBLIC KEY')) { return !!\file_put_contents("{$dir}{$keyId}_public.asc", $key); } } return false; } }