mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-11 00:14:50 +03:00
Added draft code to handle S/MIME through GnuPG #259
This commit is contained in:
parent
9cceafcb3e
commit
9933a0e83e
8 changed files with 1695 additions and 372 deletions
|
|
@ -14,6 +14,7 @@ namespace MailSo\Mail;
|
|||
use MailSo\Base\Utils;
|
||||
use MailSo\Imap\Enumerations\FetchType;
|
||||
use MailSo\Mime\Enumerations\Header as MimeHeader;
|
||||
use SnappyMail\GPG\PGP as GPG;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
|
|
@ -379,8 +380,8 @@ class Message implements \JsonSerializable
|
|||
|
||||
if (\str_contains($sText, '-----BEGIN PGP MESSAGE-----')) {
|
||||
$keyIds = [];
|
||||
if (\SnappyMail\PGP\GPG::isSupported()) {
|
||||
$GPG = new \SnappyMail\PGP\GPG('');
|
||||
if (GPG::isSupported()) {
|
||||
$GPG = new GPG('');
|
||||
$keyIds = $GPG->getEncryptedMessageKeys($sText);
|
||||
}
|
||||
$oMessage->pgpEncrypted = [
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
namespace RainLoop\Actions;
|
||||
|
||||
use SnappyMail\PGP\Backup;
|
||||
use SnappyMail\PGP\Keyservers;
|
||||
use SnappyMail\PGP\GnuPG;
|
||||
|
||||
trait Pgp
|
||||
{
|
||||
|
|
@ -14,7 +16,7 @@ trait Pgp
|
|||
{
|
||||
$result = [];
|
||||
|
||||
$keys = \SnappyMail\PGP\Backup::getKeys();
|
||||
$keys = Backup::getKeys();
|
||||
foreach ($keys['public'] as $key) {
|
||||
$result[] = $key['value'];
|
||||
}
|
||||
|
|
@ -38,7 +40,7 @@ trait Pgp
|
|||
|
||||
public function DoSearchPGPKey() : array
|
||||
{
|
||||
$result = \SnappyMail\PGP\Keyservers::get(
|
||||
$result = Keyservers::get(
|
||||
$this->GetActionParam('query', '')
|
||||
);
|
||||
return $this->DefaultResponse($result ?: false);
|
||||
|
|
@ -47,7 +49,7 @@ trait Pgp
|
|||
/**
|
||||
* @throws \MailSo\RuntimeException
|
||||
*/
|
||||
public function GnuPG() : ?\SnappyMail\PGP\GnuPG
|
||||
public function GnuPG() : ?GnuPG
|
||||
{
|
||||
$oAccount = $this->getMainAccountFromToken();
|
||||
if (!$oAccount) {
|
||||
|
|
@ -103,7 +105,7 @@ trait Pgp
|
|||
}
|
||||
}
|
||||
|
||||
return \SnappyMail\PGP\GnuPG::getInstance($homedir);
|
||||
return GnuPG::getInstance($homedir);
|
||||
}
|
||||
|
||||
public function DoGnupgDecrypt() : array
|
||||
|
|
@ -203,14 +205,14 @@ trait Pgp
|
|||
$sEmail = $aMatch[0];
|
||||
}
|
||||
if ($sEmail) {
|
||||
$aKeys = \SnappyMail\PGP\Keyservers::index($sEmail);
|
||||
$aKeys = Keyservers::index($sEmail);
|
||||
if ($aKeys) {
|
||||
$sKeyId = $aKeys[0]['keyid'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($sKeyId) {
|
||||
$sKey = \SnappyMail\PGP\Keyservers::get($sKeyId);
|
||||
$sKey = Keyservers::get($sKeyId);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// ignore
|
||||
|
|
@ -237,7 +239,7 @@ trait Pgp
|
|||
*/
|
||||
public function DoGetStoredPGPKeys() : array
|
||||
{
|
||||
return $this->DefaultResponse(\SnappyMail\PGP\Backup::getKeys());
|
||||
return $this->DefaultResponse(Backup::getKeys());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
489
snappymail/v/0.0.0/app/libraries/snappymail/gpg/base.php
Normal file
489
snappymail/v/0.0.0/app/libraries/snappymail/gpg/base.php
Normal file
|
|
@ -0,0 +1,489 @@
|
|||
<?php
|
||||
/**
|
||||
* GnuPG
|
||||
*/
|
||||
|
||||
namespace SnappyMail\GPG;
|
||||
|
||||
abstract class Base
|
||||
{
|
||||
const
|
||||
// @see https://pear.php.net/bugs/bug.php?id=21077
|
||||
CHUNK_SIZE = 65536,
|
||||
|
||||
// This is used to pass data to the GPG process.
|
||||
FD_INPUT = 0,
|
||||
|
||||
// This is used to receive normal output from the GPG process.
|
||||
FD_OUTPUT = 1,
|
||||
|
||||
// This is used to receive error output from the GPG process.
|
||||
FD_ERROR = 2,
|
||||
|
||||
/**
|
||||
* GPG status output file descriptor. The status file descriptor outputs
|
||||
* detailed information for many GPG commands. See the second section of
|
||||
* the file <b>doc/DETAILS</b> in the
|
||||
* {@link http://www.gnupg.org/download/ GPG package} for a detailed
|
||||
* description of GPG's status output.
|
||||
*/
|
||||
FD_STATUS = 3,
|
||||
|
||||
// This is used for methods requiring passphrases.
|
||||
FD_COMMAND = 4,
|
||||
|
||||
// This is used for passing signed data when verifying a detached signature.
|
||||
FD_MESSAGE = 5;
|
||||
|
||||
public
|
||||
$strict = false;
|
||||
|
||||
protected
|
||||
$binary,
|
||||
$version = '2.0',
|
||||
$passphrases = [],
|
||||
$signKeys = [],
|
||||
$encryptKeys = [],
|
||||
$decryptKeys = [],
|
||||
// Create PEM encoded output
|
||||
$armor = true,
|
||||
|
||||
$_input,
|
||||
$_output,
|
||||
|
||||
$proc_resource,
|
||||
$_openPipes, // ProcPipes
|
||||
|
||||
// https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html
|
||||
$options = [
|
||||
'homedir' => '',
|
||||
'keyring' => '',
|
||||
'digest-algo' => '',
|
||||
'cipher-algo' => '',
|
||||
];
|
||||
|
||||
function __construct(string $homedir)
|
||||
{
|
||||
$this->options['homedir'] = \rtrim($homedir, '/\\');
|
||||
}
|
||||
|
||||
function __destruct()
|
||||
{
|
||||
$this->proc_close();
|
||||
|
||||
$gpgconf = static::findBinary('gpgconf');
|
||||
if ($gpgconf) {
|
||||
$env = ['GNUPGHOME' => $this->options['homedir']];
|
||||
$pipes = [];
|
||||
if ($process = \proc_open($gpgconf . ' --kill gpg-agent --homedir ' . \escapeshellarg($this->options['homedir']), [], $pipes, null, $env)) {
|
||||
\proc_close($process);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function isSupported() : bool
|
||||
{
|
||||
return \is_callable('proc_open');
|
||||
}
|
||||
|
||||
protected function listDecryptKeys(/*string|resource*/ $input, /*string|resource*/ $output = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a given text
|
||||
*/
|
||||
public function decrypt(string $text) /*: string|false */
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a given file
|
||||
*/
|
||||
public function decryptFile(string $filename) /*: string|false */
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a given stream
|
||||
*/
|
||||
public function decryptStream($fp, /*string|resource*/ $output = null) /*: string|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts and verifies a given text
|
||||
*/
|
||||
public function decryptVerify(string $text, string &$plaintext) /*: array|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts and verifies a given file
|
||||
*/
|
||||
public function decryptVerifyFile(string $filename, string &$plaintext) /*: array|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a given text
|
||||
*/
|
||||
public function encrypt(string $plaintext, /*string|resource*/ $output = null) /*: string|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a given text
|
||||
*/
|
||||
public function encryptFile(string $filename, /*string|resource*/ $output = null) /*: string|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function encryptStream(/*resource*/ $fp, /*string|resource*/ $output = null) /*: string|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts and signs a given text
|
||||
*/
|
||||
public function encryptSign(string $plaintext) /*: string|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts and signs a given text
|
||||
*/
|
||||
public function encryptSignFile(string $filename) /*: string|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a public key
|
||||
*/
|
||||
public function export(string $fingerprint) /*: string|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a private key
|
||||
*/
|
||||
public function exportPrivateKey(string $fingerprint) /*: string|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Imports a key
|
||||
*/
|
||||
public function import(string $keydata) /*: array|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a key
|
||||
*/
|
||||
public function importFile(string $filename) /*: array|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function deleteKey(string $keyId, bool $private)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with information about all keys that matches the given pattern
|
||||
*/
|
||||
public function keyInfo(string $pattern, int $private = 0) : array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the mode for error_reporting
|
||||
* GNUPG_ERROR_WARNING, GNUPG_ERROR_EXCEPTION and GNUPG_ERROR_SILENT.
|
||||
* By default GNUPG_ERROR_SILENT is used.
|
||||
*/
|
||||
public function setErrorMode(int $errormode) : void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs a given text
|
||||
*/
|
||||
public function sign(string $plaintext, /*string|resource*/ $output = null) /*: string|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs a given file
|
||||
*/
|
||||
public function signFile(string $filename, /*string|resource*/ $output = null) /*: string|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs a given file
|
||||
*/
|
||||
public function signStream($fp, /*string|resource*/ $output = null) /*: array|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a signed text
|
||||
*/
|
||||
public function verify(string $signed_text, string $signature, string &$plaintext = null) /*: array|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a signed file
|
||||
*/
|
||||
public function verifyFile(string $filename, string $signature, string &$plaintext = null) /*: array|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a signed file
|
||||
*/
|
||||
public function verifyStream($fp, string $signature, string &$plaintext = null) /*: array|false*/
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getEncryptedMessageKeys(/*string|resource*/ $data) : array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/******************************************************************
|
||||
* Defined methods
|
||||
******************************************************************/
|
||||
|
||||
/**
|
||||
* Add a key for decryption
|
||||
*/
|
||||
public function addDecryptKey(string $fingerprint,
|
||||
#[\SensitiveParameter]
|
||||
string $passphrase
|
||||
) : bool
|
||||
{
|
||||
$this->decryptKeys[$fingerprint] = $passphrase;
|
||||
// $this->decryptKeys[\substr($fingerprint, -16)] = $passphrase;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a key for encryption
|
||||
*/
|
||||
public function addEncryptKey(string $fingerprint) : bool
|
||||
{
|
||||
$this->encryptKeys[$fingerprint] = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a key for signing
|
||||
*/
|
||||
public function addSignKey(string $fingerprint,
|
||||
#[\SensitiveParameter]
|
||||
string $passphrase
|
||||
) : bool
|
||||
{
|
||||
$this->signKeys[$fingerprint] = $passphrase;
|
||||
// $this->signKeys[\substr($fingerprint, -16)] = $passphrase;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all keys which were set for decryption before
|
||||
*/
|
||||
public function clearDecryptKeys() : bool
|
||||
{
|
||||
$this->decryptKeys = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all keys which were set for encryption before
|
||||
*/
|
||||
public function clearEncryptKeys() : bool
|
||||
{
|
||||
$this->encryptKeys = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all keys which were set for signing before
|
||||
*/
|
||||
public function clearSignKeys() : bool
|
||||
{
|
||||
$this->signKeys = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the engine info
|
||||
*/
|
||||
public function getEngineInfo() : array
|
||||
{
|
||||
return [
|
||||
'protocol' => null,
|
||||
'file_name' => $this->binary,
|
||||
'home_dir' => $this->options['homedir'],
|
||||
'version' => $this->version
|
||||
];
|
||||
}
|
||||
|
||||
public function addPassphrase($keyId,
|
||||
#[\SensitiveParameter]
|
||||
$passphrase
|
||||
)
|
||||
{
|
||||
$this->passphrases[$keyId] = $passphrase;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the armored output
|
||||
*/
|
||||
public function setArmor(int $armor = 1) : bool
|
||||
{
|
||||
$this->armor = !!$armor;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function _debug(string $msg) : void
|
||||
{
|
||||
\SnappyMail\Log::debug('GPG', $msg);
|
||||
}
|
||||
|
||||
protected function setInput(&$input) : void
|
||||
{
|
||||
if (\is_resource($input)) {
|
||||
// https://github.com/the-djmaze/snappymail/issues/331
|
||||
// $meta['stream_type'] == MEMORY or $meta['wrapper_data'] == MailSo\Base\StreamWrappers\Literal
|
||||
$meta = \stream_get_meta_data($input);
|
||||
if (!\in_array($meta['stream_type'], ['STDIO', 'TEMP'])) {
|
||||
/*
|
||||
$fp = \fopen('php://temp');
|
||||
\stream_copy_to_stream($input, $fp);
|
||||
$input = $fp;
|
||||
*/
|
||||
$input = \stream_get_contents($input);
|
||||
}
|
||||
}
|
||||
$this->_input =& $input;
|
||||
}
|
||||
|
||||
protected function setOutput($output)/* : resource|false*/
|
||||
{
|
||||
$fclose = false;
|
||||
if ($output && !\is_resource($output)) {
|
||||
$output = \fopen($output, 'wb');
|
||||
if (!$output) {
|
||||
throw new \Exception("Could not open file '{$filename}'");
|
||||
}
|
||||
$fclose = $output;
|
||||
}
|
||||
$this->_output = $output;
|
||||
return $fclose;
|
||||
}
|
||||
|
||||
public function agent()
|
||||
{
|
||||
// $home = \escapeshellarg($this->options['homedir']);
|
||||
// echo `gpg-agent --daemon --homedir $home 2>&1`;
|
||||
}
|
||||
|
||||
protected function getPassphrase($key)
|
||||
{
|
||||
$passphrase = '';
|
||||
$keyIdLength = \strlen($key);
|
||||
if ($keyIdLength && !empty($_ENV['PINENTRY_USER_DATA'])) {
|
||||
$passphrases = \json_decode($_ENV['PINENTRY_USER_DATA'], true);
|
||||
foreach ($passphrases as $keyId => $pass) {
|
||||
$length = \min($keyIdLength, \strlen($keyId));
|
||||
if (\substr($keyId, -$length) === \substr($key, -$length)) {
|
||||
return $pass;
|
||||
}
|
||||
}
|
||||
}
|
||||
// throw new \Exception("Passphrase not found for {$key}");
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function proc_close() : int
|
||||
{
|
||||
$exitCode = 0;
|
||||
|
||||
// clear PINs from environment if they were set
|
||||
$_ENV['PINENTRY_USER_DATA'] = null;
|
||||
|
||||
if (\is_resource($this->proc_resource)) {
|
||||
$this->_debug('CLOSING SUBPROCESS');
|
||||
|
||||
// close remaining open pipes
|
||||
$this->_openPipes->closeAll();
|
||||
|
||||
$status = \proc_get_status($this->proc_resource);
|
||||
$exitCode = \proc_close($this->proc_resource);
|
||||
$this->proc_resource = null;
|
||||
|
||||
// proc_close() can return -1 in some cases,
|
||||
// get the real exit code from the process status
|
||||
if ($exitCode < 0 && $status && !$status['running']) {
|
||||
$exitCode = $status['exitcode'];
|
||||
}
|
||||
|
||||
if ($exitCode > 0) {
|
||||
$this->_debug('=> subprocess returned an unexpected exit code: ' . $exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
return $exitCode;
|
||||
}
|
||||
|
||||
protected static function findBinary($name) : ?string
|
||||
{
|
||||
$binary = \trim((string) `which $name`);
|
||||
if ($binary && \is_executable($binary)) {
|
||||
return $binary;
|
||||
}
|
||||
$locations = \array_filter([
|
||||
'/sw/bin/',
|
||||
'/usr/bin/',
|
||||
'/usr/local/bin/',
|
||||
'/opt/local/bin/',
|
||||
'/run/current-system/sw/bin/'
|
||||
], '\RainLoop\Utils::inOpenBasedir');
|
||||
foreach ($locations as $location) {
|
||||
if (\is_executable($location . $name)) {
|
||||
return $location . $name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,74 +5,29 @@
|
|||
* It does not support gpg < v2.2.5 as they are from before 2018
|
||||
*/
|
||||
|
||||
namespace SnappyMail\PGP;
|
||||
namespace SnappyMail\GPG;
|
||||
|
||||
class GPG
|
||||
class PGP extends Base
|
||||
{
|
||||
const
|
||||
// @see https://pear.php.net/bugs/bug.php?id=21077
|
||||
CHUNK_SIZE = 65536,
|
||||
|
||||
// This is used to pass data to the GPG process.
|
||||
FD_INPUT = 0,
|
||||
|
||||
// This is used to receive normal output from the GPG process.
|
||||
FD_OUTPUT = 1,
|
||||
|
||||
// This is used to receive error output from the GPG process.
|
||||
FD_ERROR = 2,
|
||||
|
||||
/**
|
||||
* GPG status output file descriptor. The status file descriptor outputs
|
||||
* detailed information for many GPG commands. See the second section of
|
||||
* the file <b>doc/DETAILS</b> in the
|
||||
* {@link http://www.gnupg.org/download/ GPG package} for a detailed
|
||||
* description of GPG's status output.
|
||||
*/
|
||||
FD_STATUS = 3,
|
||||
|
||||
// This is used for methods requiring passphrases.
|
||||
FD_COMMAND = 4,
|
||||
|
||||
// This is used for passing signed data when verifying a detached signature.
|
||||
FD_MESSAGE = 5;
|
||||
|
||||
public
|
||||
$strict = false;
|
||||
|
||||
private
|
||||
$_message,
|
||||
$_input,
|
||||
$_output,
|
||||
|
||||
$signKeys = [],
|
||||
$encryptKeys = [],
|
||||
$decryptKeys = [];
|
||||
|
||||
private
|
||||
$binary,
|
||||
$version = '2.0',
|
||||
$ciphers = [],
|
||||
$digests = [],
|
||||
$curves = [],
|
||||
$pubkey_types = [],
|
||||
$compressions = [],
|
||||
$passphrases = [],
|
||||
|
||||
$proc_resource,
|
||||
$_openPipes, // GpgProcPipes
|
||||
|
||||
$armor = true,
|
||||
|
||||
$signmode = 2,
|
||||
$signmode = 2;
|
||||
|
||||
protected
|
||||
// https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html
|
||||
$options = [
|
||||
'homedir' => '',
|
||||
'keyring' => '',
|
||||
'secret-keyring' => '',
|
||||
'digest-algo' => '',
|
||||
'cipher-algo' => '',
|
||||
'secret-keyring' => '',
|
||||
/*
|
||||
2 = ZLIB (GnuPG, default)
|
||||
1 = ZIP (PGP)
|
||||
|
|
@ -83,7 +38,7 @@ class GPG
|
|||
|
||||
function __construct(string $homedir)
|
||||
{
|
||||
$this->options['homedir'] = \rtrim($homedir, '/\\');
|
||||
parent::__construct($homedir);
|
||||
|
||||
// the random seed file makes subsequent actions faster so only disable it if we have to.
|
||||
if ($this->options['homedir'] && !\is_writable($this->options['homedir'])) {
|
||||
|
|
@ -114,85 +69,9 @@ class GPG
|
|||
}
|
||||
}
|
||||
|
||||
function __destruct()
|
||||
{
|
||||
$this->proc_close();
|
||||
|
||||
$gpgconf = static::findBinary('gpgconf');
|
||||
if ($gpgconf) {
|
||||
$env = ['GNUPGHOME' => $this->options['homedir']];
|
||||
$pipes = [];
|
||||
if ($process = \proc_open($gpgconf . ' --kill gpg-agent --homedir ' . \escapeshellarg($this->options['homedir']), [], $pipes, null, $env)) {
|
||||
\proc_close($process);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function isSupported() : bool
|
||||
{
|
||||
return \is_callable('proc_open') && static::findBinary('gpg');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a key for decryption
|
||||
*/
|
||||
public function addDecryptKey(string $fingerprint,
|
||||
#[\SensitiveParameter]
|
||||
string $passphrase
|
||||
) : bool
|
||||
{
|
||||
$this->decryptKeys[$fingerprint] = $passphrase;
|
||||
// $this->decryptKeys[\substr($fingerprint, -16)] = $passphrase;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a key for encryption
|
||||
*/
|
||||
public function addEncryptKey(string $fingerprint) : bool
|
||||
{
|
||||
$this->encryptKeys[$fingerprint] = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a key for signing
|
||||
*/
|
||||
public function addSignKey(string $fingerprint,
|
||||
#[\SensitiveParameter]
|
||||
string $passphrase
|
||||
) : bool
|
||||
{
|
||||
$this->signKeys[$fingerprint] = $passphrase;
|
||||
// $this->signKeys[\substr($fingerprint, -16)] = $passphrase;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all keys which were set for decryption before
|
||||
*/
|
||||
public function clearDecryptKeys() : bool
|
||||
{
|
||||
$this->decryptKeys = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all keys which were set for encryption before
|
||||
*/
|
||||
public function clearEncryptKeys() : bool
|
||||
{
|
||||
$this->encryptKeys = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all keys which were set for signing before
|
||||
*/
|
||||
public function clearSignKeys() : bool
|
||||
{
|
||||
$this->signKeys = [];
|
||||
return true;
|
||||
return parent::isSupported() && static::findBinary('gpg');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -388,19 +267,6 @@ class GPG
|
|||
return $this->_exportKey($fingerprint, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the engine info
|
||||
*/
|
||||
public function getEngineInfo() : array
|
||||
{
|
||||
return [
|
||||
'protocol' => null,
|
||||
'file_name' => $this->binary,
|
||||
'home_dir' => $this->options['homedir'],
|
||||
'version' => $this->version
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the errortext, if a function fails
|
||||
*/
|
||||
|
|
@ -414,7 +280,7 @@ class GPG
|
|||
*/
|
||||
public function getErrorInfo() : array
|
||||
{
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -422,16 +288,7 @@ class GPG
|
|||
*/
|
||||
public function getProtocol() : int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function addPassphrase($keyId,
|
||||
#[\SensitiveParameter]
|
||||
$passphrase
|
||||
)
|
||||
{
|
||||
$this->passphrases[$keyId] = $passphrase;
|
||||
return $this;
|
||||
return 0; // GPGME_PROTOCOL_OpenPGP
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -439,7 +296,7 @@ class GPG
|
|||
* Also saves revocation certificate in {homedir}/openpgp-revocs.d/
|
||||
* https://www.gnupg.org/documentation/manuals/gnupg/OpenPGP-Key-Management.html
|
||||
*/
|
||||
public function generateKey(GPGKeySettings $settings) /*: string|false*/
|
||||
public function generateKey(PGPKeySettings $settings) /*: string|false*/
|
||||
{
|
||||
$arguments = [
|
||||
'--batch',
|
||||
|
|
@ -715,15 +572,6 @@ class GPG
|
|||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the armored output
|
||||
*/
|
||||
public function setArmor(int $armor = 1) : bool
|
||||
{
|
||||
$this->armor = !!$armor;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mode for error_reporting
|
||||
* GNUPG_ERROR_WARNING, GNUPG_ERROR_EXCEPTION and GNUPG_ERROR_SILENT.
|
||||
|
|
@ -923,49 +771,6 @@ class GPG
|
|||
return $this->_verify($fp, $signature);
|
||||
}
|
||||
|
||||
private function _debug(string $msg) : void
|
||||
{
|
||||
\SnappyMail\Log::debug('GPG', $msg);
|
||||
}
|
||||
|
||||
private function setInput(&$input) : void
|
||||
{
|
||||
if (\is_resource($input)) {
|
||||
// https://github.com/the-djmaze/snappymail/issues/331
|
||||
// $meta['stream_type'] == MEMORY or $meta['wrapper_data'] == MailSo\Base\StreamWrappers\Literal
|
||||
$meta = \stream_get_meta_data($input);
|
||||
if (!\in_array($meta['stream_type'], ['STDIO', 'TEMP'])) {
|
||||
/*
|
||||
$fp = \fopen('php://temp');
|
||||
\stream_copy_to_stream($input, $fp);
|
||||
$input = $fp;
|
||||
*/
|
||||
$input = \stream_get_contents($input);
|
||||
}
|
||||
}
|
||||
$this->_input =& $input;
|
||||
}
|
||||
|
||||
private function setOutput($output)/* : resource|false*/
|
||||
{
|
||||
$fclose = false;
|
||||
if ($output && !\is_resource($output)) {
|
||||
$output = \fopen($output, 'wb');
|
||||
if (!$output) {
|
||||
throw new \Exception("Could not open file '{$filename}'");
|
||||
}
|
||||
$fclose = $output;
|
||||
}
|
||||
$this->_output = $output;
|
||||
return $fclose;
|
||||
}
|
||||
|
||||
public function agent()
|
||||
{
|
||||
// $home = \escapeshellarg($this->options['homedir']);
|
||||
// echo `gpg-agent --daemon --homedir $home 2>&1`;
|
||||
}
|
||||
|
||||
public function getEncryptedMessageKeys(/*string|resource*/ $data) : array
|
||||
{
|
||||
$this->_debug('BEGIN DETECT MESSAGE KEY IDs');
|
||||
|
|
@ -1068,7 +873,7 @@ class GPG
|
|||
throw new \Exception('Unable to open process.');
|
||||
}
|
||||
|
||||
$this->_openPipes = new GpgProcPipes($proc_pipes);
|
||||
$this->_openPipes = new ProcPipes($proc_pipes);
|
||||
|
||||
$this->_debug('BEGIN PROCESSING');
|
||||
|
||||
|
|
@ -1373,158 +1178,4 @@ class GPG
|
|||
'errors' => $errors
|
||||
];
|
||||
}
|
||||
|
||||
private function getPassphrase($key)
|
||||
{
|
||||
$passphrase = '';
|
||||
$keyIdLength = \strlen($key);
|
||||
if ($keyIdLength && !empty($_ENV['PINENTRY_USER_DATA'])) {
|
||||
$passphrases = \json_decode($_ENV['PINENTRY_USER_DATA'], true);
|
||||
foreach ($passphrases as $keyId => $pass) {
|
||||
$length = \min($keyIdLength, \strlen($keyId));
|
||||
if (\substr($keyId, -$length) === \substr($key, -$length)) {
|
||||
return $pass;
|
||||
}
|
||||
}
|
||||
}
|
||||
// throw new \Exception("Passphrase not found for {$key}");
|
||||
return '';
|
||||
}
|
||||
|
||||
private function proc_close() : int
|
||||
{
|
||||
$exitCode = 0;
|
||||
|
||||
// clear PINs from environment if they were set
|
||||
$_ENV['PINENTRY_USER_DATA'] = null;
|
||||
|
||||
if (\is_resource($this->proc_resource)) {
|
||||
$this->_debug('CLOSING SUBPROCESS');
|
||||
|
||||
// close remaining open pipes
|
||||
$this->_openPipes->closeAll();
|
||||
|
||||
$status = \proc_get_status($this->proc_resource);
|
||||
$exitCode = \proc_close($this->proc_resource);
|
||||
$this->proc_resource = null;
|
||||
|
||||
// proc_close() can return -1 in some cases,
|
||||
// get the real exit code from the process status
|
||||
if ($exitCode < 0 && $status && !$status['running']) {
|
||||
$exitCode = $status['exitcode'];
|
||||
}
|
||||
|
||||
if ($exitCode > 0) {
|
||||
$this->_debug('=> subprocess returned an unexpected exit code: ' . $exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
return $exitCode;
|
||||
}
|
||||
|
||||
private static function findBinary($name) : ?string
|
||||
{
|
||||
$binary = \trim((string) `which $name`);
|
||||
if ($binary && \is_executable($binary)) {
|
||||
return $binary;
|
||||
}
|
||||
$locations = \array_filter([
|
||||
'/sw/bin/',
|
||||
'/usr/bin/',
|
||||
'/usr/local/bin/',
|
||||
'/opt/local/bin/',
|
||||
'/run/current-system/sw/bin/'
|
||||
], '\RainLoop\Utils::inOpenBasedir');
|
||||
foreach ($locations as $location) {
|
||||
if (\is_executable($location . $name)) {
|
||||
return $location . $name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class GpgProcPipes
|
||||
{
|
||||
private $pipes;
|
||||
|
||||
function __construct(array $pipes)
|
||||
{
|
||||
// Set streams as non-blocking.
|
||||
foreach ($pipes as $pipe) {
|
||||
\stream_set_blocking($pipe, 0);
|
||||
\stream_set_write_buffer($pipe, GPG::CHUNK_SIZE);
|
||||
\stream_set_chunk_size($pipe, GPG::CHUNK_SIZE);
|
||||
\stream_set_read_buffer($pipe, GPG::CHUNK_SIZE);
|
||||
}
|
||||
$this->pipes = $pipes;
|
||||
}
|
||||
|
||||
function __destruct()
|
||||
{
|
||||
$this->closeAll();
|
||||
}
|
||||
|
||||
public function closeAll() : void
|
||||
{
|
||||
foreach (\array_keys($this->pipes) as $number) {
|
||||
$this->close($number);
|
||||
}
|
||||
}
|
||||
|
||||
public function get(int $number)
|
||||
{
|
||||
if (\array_key_exists($number, $this->pipes) && \is_resource($this->pipes[$number])) {
|
||||
return $this->pipes[$number];
|
||||
}
|
||||
}
|
||||
|
||||
public function close(int $number) : void
|
||||
{
|
||||
if (\array_key_exists($number, $this->pipes)) {
|
||||
\fflush($this->pipes[$number]);
|
||||
\fclose($this->pipes[$number]);
|
||||
unset($this->pipes[$number]);
|
||||
}
|
||||
}
|
||||
|
||||
private $buffers = [];
|
||||
public function readPipeLines(int $number) : iterable
|
||||
{
|
||||
$pipe = $this->get($number);
|
||||
if ($pipe) {
|
||||
$chunk = \fread($pipe, GPG::CHUNK_SIZE);
|
||||
$length = \strlen($chunk);
|
||||
$eolLength = \strlen(PHP_EOL);
|
||||
if (!isset($this->buffers[$number])) {
|
||||
$this->buffers[$number] = '';
|
||||
}
|
||||
$this->buffers[$number] .= $chunk;
|
||||
while (false !== ($pos = \strpos($this->buffers[$number], PHP_EOL))) {
|
||||
yield \substr($this->buffers[$number], 0, $pos);
|
||||
$this->buffers[$number] = \substr($this->buffers[$number], $pos + $eolLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function writePipe(int $number, string $data, int $length = 0) : int
|
||||
{
|
||||
$pipe = $this->get($number);
|
||||
if ($pipe) {
|
||||
$chunk = \substr($data, 0, $length ?: \strlen($data));
|
||||
$length = \strlen($chunk);
|
||||
$length = \fwrite($pipe, $chunk, $length);
|
||||
if (!$length) {
|
||||
// If we wrote 0 bytes it was either EAGAIN or EPIPE. Since
|
||||
// the pipe was seleted for writing, we assume it was EPIPE.
|
||||
// There's no way to get the actual error code in PHP. See
|
||||
// PHP Bug #39598. https://bugs.php.net/bug.php?id=39598
|
||||
$this->close($number);
|
||||
}
|
||||
return $length ?: 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace SnappyMail\PGP;
|
||||
namespace SnappyMail\GPG;
|
||||
|
||||
class GPGKeySettings
|
||||
class PGPKeySettings
|
||||
{
|
||||
public
|
||||
// Primary key
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
/**
|
||||
* GnuPG
|
||||
*/
|
||||
|
||||
namespace SnappyMail\GPG;
|
||||
|
||||
class ProcPipes
|
||||
{
|
||||
private $pipes;
|
||||
|
||||
function __construct(array $pipes)
|
||||
{
|
||||
// Set streams as non-blocking.
|
||||
foreach ($pipes as $pipe) {
|
||||
\stream_set_blocking($pipe, 0);
|
||||
\stream_set_write_buffer($pipe, Base::CHUNK_SIZE);
|
||||
\stream_set_chunk_size($pipe, Base::CHUNK_SIZE);
|
||||
\stream_set_read_buffer($pipe, Base::CHUNK_SIZE);
|
||||
}
|
||||
$this->pipes = $pipes;
|
||||
}
|
||||
|
||||
function __destruct()
|
||||
{
|
||||
$this->closeAll();
|
||||
}
|
||||
|
||||
public function closeAll() : void
|
||||
{
|
||||
foreach (\array_keys($this->pipes) as $number) {
|
||||
$this->close($number);
|
||||
}
|
||||
}
|
||||
|
||||
public function get(int $number)
|
||||
{
|
||||
if (\array_key_exists($number, $this->pipes) && \is_resource($this->pipes[$number])) {
|
||||
return $this->pipes[$number];
|
||||
}
|
||||
}
|
||||
|
||||
public function close(int $number) : void
|
||||
{
|
||||
if (\array_key_exists($number, $this->pipes)) {
|
||||
\fflush($this->pipes[$number]);
|
||||
\fclose($this->pipes[$number]);
|
||||
unset($this->pipes[$number]);
|
||||
}
|
||||
}
|
||||
|
||||
private $buffers = [];
|
||||
public function readPipeLines(int $number) : iterable
|
||||
{
|
||||
$pipe = $this->get($number);
|
||||
if ($pipe) {
|
||||
$chunk = \fread($pipe, Base::CHUNK_SIZE);
|
||||
$length = \strlen($chunk);
|
||||
$eolLength = \strlen(\PHP_EOL);
|
||||
if (!isset($this->buffers[$number])) {
|
||||
$this->buffers[$number] = '';
|
||||
}
|
||||
$this->buffers[$number] .= $chunk;
|
||||
while (false !== ($pos = \strpos($this->buffers[$number], \PHP_EOL))) {
|
||||
yield \substr($this->buffers[$number], 0, $pos);
|
||||
$this->buffers[$number] = \substr($this->buffers[$number], $pos + $eolLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function writePipe(int $number, string $data, int $length = 0) : int
|
||||
{
|
||||
$pipe = $this->get($number);
|
||||
if ($pipe) {
|
||||
$chunk = \substr($data, 0, $length ?: \strlen($data));
|
||||
$length = \strlen($chunk);
|
||||
$length = \fwrite($pipe, $chunk, $length);
|
||||
if (!$length) {
|
||||
// If we wrote 0 bytes it was either EAGAIN or EPIPE. Since
|
||||
// the pipe was seleted for writing, we assume it was EPIPE.
|
||||
// There's no way to get the actual error code in PHP. See
|
||||
// PHP Bug #39598. https://bugs.php.net/bug.php?id=39598
|
||||
$this->close($number);
|
||||
}
|
||||
return $length ?: 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
1089
snappymail/v/0.0.0/app/libraries/snappymail/gpg/smime.php
Normal file
1089
snappymail/v/0.0.0/app/libraries/snappymail/gpg/smime.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -6,13 +6,15 @@ defined('GNUPG_SIG_MODE_NORMAL') || define('GNUPG_SIG_MODE_NORMAL', 0);
|
|||
defined('GNUPG_SIG_MODE_DETACH') || define('GNUPG_SIG_MODE_DETACH', 1);
|
||||
defined('GNUPG_SIG_MODE_CLEAR') || define('GNUPG_SIG_MODE_CLEAR', 2);
|
||||
|
||||
use SnappyMail\GPG\PGP as GPG;
|
||||
|
||||
class GnuPG
|
||||
{
|
||||
private
|
||||
$homedir,
|
||||
// Instance of gnupg pecl extension https://www.php.net/gnupg
|
||||
$GnuPG,
|
||||
// Instance of \SnappyMail\PGP\GPG
|
||||
// Instance of \SnappyMail\GPG\PGP
|
||||
$GPG;
|
||||
|
||||
function __construct(string $homedir)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue