From 54feb03316550f87837408b54f17c31f6664685d Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 25 Jan 2022 13:54:50 +0100 Subject: [PATCH] Added sign, decrypt and encrypt for #89 --- .../app/libraries/snappymail/pgp/gnupg.php | 425 ++++++------------ .../app/libraries/snappymail/pgp/gpg.php | 201 ++++++--- .../snappymail/pgp/gpgkeysettings.php | 7 +- 3 files changed, 289 insertions(+), 344 deletions(-) diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gnupg.php b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gnupg.php index 67b8dcf32..c337818bb 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gnupg.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gnupg.php @@ -11,41 +11,60 @@ class GnuPG // Instance of \SnappyMail\PGP\GPG $GPG; - public static function isSupported() : bool - { - return \class_exists('gnupg') - || \SnappyMail\PGP\GPG::isSupported(); - } - - public static function getInstance(string $homedir) : ?self + function __construct(string $homedir) { $homedir = \rtrim($homedir, '/\\'); // BSD 4.4 max length if (104 <= \strlen($homedir . '/S.gpg-agent.extra')) { throw new \Exception('socket name for S.gpg-agent.extra is too long'); } + $this->homedir = $homedir; +// \putenv("GNUPGHOME={$homedir}"); - $self = null; // if (\version_compare(\phpversion('gnupg'), '1.5', '>=')) { if (\class_exists('gnupg')) { - $self = new self; - $self->GnuPG = new \gnupg([ + $this->GnuPG = new \gnupg([ // It is the file name of the executable program implementing this protocol which is usually path of the gpg executable. // 'file_name' => '/usr/bin/gpg', // It is the directory name of the configuration directory. It also overrides GNUPGHOME environment variable that is used for the same purpose. 'home_dir' => $homedir ]); // Output is ASCII - $self->GnuPG->setarmor(1); - } else if (\SnappyMail\PGP\GPG::isSupported()) { - $self = new self; - $self->GPG = new \SnappyMail\PGP\GPG($homedir); + $this->GnuPG->setarmor(1); + } else { + $this->getGPG(); } - if ($self) { - $self->homedir = $homedir; -// \putenv("GNUPGHOME={$homedir}"); + } + + public static function isSupported() : bool + { + return \class_exists('gnupg') + || \SnappyMail\PGP\GPG::isSupported(); + } + + private static $instance; + public static function getInstance(string $homedir) : ?self + { + if (!static::$instance) { + static::$instance = new self($homedir); } - return $self; + return static::$instance; + } + + public function handler() + { + return $this->GnuPG ?: $this->GPG; + } + + public function getGPG() + { + if (!$this->GPG) { + if (!\SnappyMail\PGP\GPG::isSupported()) { + throw new \Exception('GnuPG not supported'); + } + $this->GPG = new \SnappyMail\PGP\GPG($this->homedir); + } + return $this->GPG; } /** @@ -53,13 +72,7 @@ class GnuPG */ public function addDecryptKey(string $fingerprint, string $passphrase) : bool { - if ($this->GnuPG) { - return $this->GnuPG->adddecryptkey($fingerprint, $passphrase); - } - if ($this->GPG) { - return $this->GPG->adddecryptkey($fingerprint, $passphrase); - } - return false; + return $this->handler()->adddecryptkey($fingerprint, $passphrase); } /** @@ -67,13 +80,7 @@ class GnuPG */ public function addEncryptKey(string $fingerprint) : bool { - if ($this->GnuPG) { - return $this->GnuPG->addencryptkey($fingerprint); - } - if ($this->GPG) { - return $this->GPG->addencryptkey($fingerprint); - } - return false; + return $this->handler()->addencryptkey($fingerprint); } /** @@ -81,13 +88,7 @@ class GnuPG */ public function addSignKey(string $fingerprint, ?string $passphrase) : bool { - if ($this->GnuPG) { - return $this->GnuPG->addsignkey($fingerprint, $passphrase); - } - if ($this->GPG) { - return $this->GPG->addsignkey($fingerprint, $passphrase); - } - return false; + return $this->handler()->addsignkey($fingerprint, $passphrase); } /** @@ -95,13 +96,7 @@ class GnuPG */ public function clearDecryptKeys() : bool { - if ($this->GnuPG) { - return $this->GnuPG->cleardecryptkeys(); - } - if ($this->GPG) { - return $this->GPG->cleardecryptkeys(); - } - return false; + return $this->handler()->cleardecryptkeys(); } /** @@ -109,13 +104,7 @@ class GnuPG */ public function clearEncryptKeys() : bool { - if ($this->GnuPG) { - return $this->GnuPG->clearencryptkeys(); - } - if ($this->GPG) { - return $this->GPG->clearencryptkeys(); - } - return false; + return $this->handler()->clearencryptkeys(); } /** @@ -123,13 +112,7 @@ class GnuPG */ public function clearSignKeys() : bool { - if ($this->GnuPG) { - return $this->GnuPG->clearsignkeys(); - } - if ($this->GPG) { - return $this->GPG->clearsignkeys(); - } - return false; + return $this->handler()->clearsignkeys(); } /** @@ -137,13 +120,9 @@ class GnuPG */ public function decrypt(string $text) /*: string|false */ { - if ($this->GnuPG) { - return $this->GnuPG->decrypt($text); - } - if ($this->GPG) { - return $this->GPG->decrypt($text); - } - return false; + return $this->GnuPG + ? $this->GnuPG->decrypt($text) + : $this->GPG->decrypt($text); } /** @@ -151,13 +130,9 @@ class GnuPG */ public function decryptFile(string $filename) /*: string|false */ { - if ($this->GnuPG) { - return $this->GnuPG->decrypt(\file_get_contents($filename)); - } - if ($this->GPG) { - return $this->GPG->decryptFile($filename); - } - return false; + return $this->GnuPG + ? $this->GnuPG->decrypt(\file_get_contents($filename)) + : $this->GPG->decryptFile($filename); } /** @@ -165,13 +140,9 @@ class GnuPG */ public function decryptVerify(string $text, string &$plaintext) /*: array|false*/ { - if ($this->GnuPG) { - return $this->GnuPG->decryptverify($text, $plaintext); - } - if ($this->GPG) { - return $this->GPG->decryptverify($text, $plaintext); - } - return false; + return $this->GnuPG + ? $this->GnuPG->decryptverify($text, $plaintext) + : $this->GPG->decryptverify($text, $plaintext); } /** @@ -179,13 +150,9 @@ class GnuPG */ public function decryptVerifyFile(string $filename, string &$plaintext) /*: array|false*/ { - if ($this->GnuPG) { - return $this->GnuPG->decryptverify(\file_get_contents($filename), $plaintext); - } - if ($this->GPG) { - return $this->GPG->decryptverifyFile($filename, $plaintext); - } - return false; + return $this->GnuPG + ? $this->GnuPG->decryptverify(\file_get_contents($filename), $plaintext) + : $this->GPG->decryptverifyFile($filename, $plaintext); } /** @@ -193,13 +160,9 @@ class GnuPG */ public function encrypt(string $plaintext) /*: string|false*/ { - if ($this->GnuPG) { - return $this->GnuPG->encrypt($plaintext); - } - if ($this->GPG) { - return $this->GPG->encrypt($plaintext); - } - return false; + return $this->GnuPG + ? $this->GnuPG->encrypt($plaintext) + : $this->GPG->encrypt($plaintext); } /** @@ -207,13 +170,9 @@ class GnuPG */ public function encryptFile(string $filename) /*: string|false*/ { - if ($this->GnuPG) { - return $this->GnuPG->encrypt(\file_get_contents($filename)); - } - if ($this->GPG) { - return $this->GPG->encryptFile($filename); - } - return false; + return $this->GnuPG + ? $this->GnuPG->encrypt(\file_get_contents($filename)) + : $this->GPG->encryptFile($filename); } /** @@ -221,13 +180,9 @@ class GnuPG */ public function encryptSign(string $plaintext) /*: string|false*/ { - if ($this->GnuPG) { - return $this->GnuPG->encryptsign($plaintext); - } - if ($this->GPG) { - return $this->GPG->encryptsign($plaintext); - } - return false; + return $this->GnuPG + ? $this->GnuPG->encryptsign($plaintext) + : $this->GPG->encryptsign($plaintext); } /** @@ -235,13 +190,9 @@ class GnuPG */ public function encryptSignFile(string $filename) /*: string|false*/ { - if ($this->GnuPG) { - return $this->GnuPG->encryptsign(\file_get_contents($filename)); - } - if ($this->GPG) { - return $this->GPG->encryptsignFile($filename); - } - return false; + return $this->GnuPG + ? $this->GnuPG->encryptsign(\file_get_contents($filename)) + : $this->GPG->encryptsignFile($filename); } /** @@ -249,13 +200,9 @@ class GnuPG */ public function export(string $fingerprint) /*: string|false*/ { - if ($this->GnuPG) { - return $this->GnuPG->export($fingerprint); - } - if ($this->GPG) { - return $this->GPG->export($fingerprint); - } - return false; + return $this->GnuPG + ? $this->GnuPG->export($fingerprint) + : $this->GPG->export($fingerprint); } /** @@ -263,13 +210,7 @@ class GnuPG */ public function getEngineInfo() : array { - if ($this->GnuPG) { - return $this->GnuPG->getengineinfo(); - } - if ($this->GPG) { - return $this->GPG->getengineinfo(); - } - return false; + return $this->handler()->getengineinfo(); } /** @@ -277,13 +218,7 @@ class GnuPG */ public function getError() /*: string|false*/ { - if ($this->GnuPG) { - return $this->GnuPG->geterror(); - } - if ($this->GPG) { - return $this->GPG->geterror(); - } - return false; + return $this->handler()->geterror(); } /** @@ -291,13 +226,7 @@ class GnuPG */ public function getErrorInfo() : array { - if ($this->GnuPG) { - return $this->GnuPG->geterrorinfo(); - } - if ($this->GPG) { - return $this->GPG->geterrorinfo(); - } - return false; + return $this->handler()->geterrorinfo(); } /** @@ -305,13 +234,7 @@ class GnuPG */ public function getProtocol() : int { - if ($this->GnuPG) { - return $this->GnuPG->getprotocol(); - } - if ($this->GPG) { - return $this->GPG->getprotocol(); - } - return false; + return $this->handler()->getprotocol(); } /** @@ -333,13 +256,7 @@ class GnuPG */ public function import(string $keydata) /*: array|false*/ { - if ($this->GnuPG) { - return $this->GnuPG->import($keydata); - } - if ($this->GPG) { - return $this->GPG->import($keydata); - } - return false; + return $this->handler()->import($keydata); } /** @@ -347,13 +264,9 @@ class GnuPG */ public function importFile(string $filename) /*: array|false*/ { - if ($this->GnuPG) { - return $this->GnuPG->import(\file_get_contents($filename)); - } - if ($this->GPG) { - return $this->GPG->importFile($filename); - } - return false; + return $this->GnuPG + ? $this->GnuPG->import(\file_get_contents($filename)) + : $this->GPG->importFile($filename); } /** @@ -362,63 +275,60 @@ class GnuPG public function keyInfo(string $pattern) : array { $keys = []; - $GPG = $this->GnuPG ?: $this->GPG; - if ($GPG) { - // Public - foreach ($GPG->keyinfo($pattern) as $info) { - if (!$info['disabled'] && !$info['expired'] && !$info['revoked']) { - foreach ($info['uids'] as $uid) { - $id = $uid['email']; - if (isset($keys[$id])) { - $keys[$id]['can_sign'] = $keys[$id]['can_sign'] || $info['can_sign']; - $keys[$id]['can_encrypt'] = $keys[$id]['can_encrypt'] || $info['can_encrypt']; - } else { - $keys[$id] = [ - 'name' => $uid['name'], - 'email' => $uid['email'], - // Public Key tasks - 'can_verify' => $info['can_sign'], - 'can_encrypt' => $info['can_encrypt'], - // Private Key tasks - 'can_sign' => false, - 'can_decrypt' => false, - // The keys - 'publicKeys' => [], - 'privateKeys' => [] - ]; - } - foreach ($info['subkeys'] as $key) { - $keys[$id]['publicKeys'][$key['fingerprint']] = $key; - } + // Public + foreach ($this->handler()->keyinfo($pattern) as $info) { + if (!$info['disabled'] && !$info['expired'] && !$info['revoked']) { + foreach ($info['uids'] as $uid) { + $id = $uid['email']; + if (isset($keys[$id])) { + $keys[$id]['can_sign'] = $keys[$id]['can_sign'] || $info['can_sign']; + $keys[$id]['can_encrypt'] = $keys[$id]['can_encrypt'] || $info['can_encrypt']; + } else { + $keys[$id] = [ + 'name' => $uid['name'], + 'email' => $uid['email'], + // Public Key tasks + 'can_verify' => $info['can_sign'], + 'can_encrypt' => $info['can_encrypt'], + // Private Key tasks + 'can_sign' => false, + 'can_decrypt' => false, + // The keys + 'publicKeys' => [], + 'privateKeys' => [] + ]; + } + foreach ($info['subkeys'] as $key) { + $keys[$id]['publicKeys'][$key['fingerprint']] = $key; } } } - // Private, read https://github.com/php-gnupg/php-gnupg/issues/5 - foreach ($GPG->keyinfo($pattern, 1) as $info) { - if (!$info['disabled'] && !$info['expired'] && !$info['revoked']) { - foreach ($info['uids'] as $uid) { - $id = $uid['email']; - if (isset($keys[$id])) { - $keys[$id]['can_sign'] = $keys[$id]['can_sign'] || $info['can_sign']; - $keys[$id]['can_decrypt'] = $keys[$id]['can_decrypt'] || $info['can_encrypt']; - } else { - $keys[$id] = [ - 'name' => $uid['name'], - 'email' => $uid['email'], - // Public Key tasks - 'can_verify' => false, - 'can_encrypt' => false, - // Private Key tasks - 'can_sign' => $info['can_sign'], - 'can_decrypt' => $info['can_encrypt'], - // The keys - 'publicKeys' => [], - 'privateKeys' => [] - ]; - } - foreach ($info['subkeys'] as $key) { - $keys[$id]['privateKeys'][$key['fingerprint']] = $key; - } + } + // Private, read https://github.com/php-gnupg/php-gnupg/issues/5 + foreach ($this->handler()->keyinfo($pattern, 1) as $info) { + if (!$info['disabled'] && !$info['expired'] && !$info['revoked']) { + foreach ($info['uids'] as $uid) { + $id = $uid['email']; + if (isset($keys[$id])) { + $keys[$id]['can_sign'] = $keys[$id]['can_sign'] || $info['can_sign']; + $keys[$id]['can_decrypt'] = $keys[$id]['can_decrypt'] || $info['can_encrypt']; + } else { + $keys[$id] = [ + 'name' => $uid['name'], + 'email' => $uid['email'], + // Public Key tasks + 'can_verify' => false, + 'can_encrypt' => false, + // Private Key tasks + 'can_sign' => $info['can_sign'], + 'can_decrypt' => $info['can_encrypt'], + // The keys + 'publicKeys' => [], + 'privateKeys' => [] + ]; + } + foreach ($info['subkeys'] as $key) { + $keys[$id]['privateKeys'][$key['fingerprint']] = $key; } } } @@ -432,13 +342,7 @@ class GnuPG */ public function setArmor(bool $armor = true) : bool { - if ($this->GnuPG) { - return $this->GnuPG->setarmor($armor ? 1 : 0); - } - if ($this->GPG) { - return $this->GPG->setarmor($armor ? 1 : 0); - } - return false; + return $this->handler()->setarmor($armor ? 1 : 0); } /** @@ -448,12 +352,7 @@ class GnuPG */ public function setErrorMode(int $errormode) : void { - if ($this->GnuPG) { - $this->GnuPG->seterrormode($errormode); - } - if ($this->GPG) { - $this->GPG->seterrormode($errormode); - } + $this->handler()->seterrormode($errormode); } /** @@ -463,13 +362,7 @@ class GnuPG */ public function setSignMode(int $signmode) : bool { - if ($this->GnuPG) { - return $this->GnuPG->setsignmode($signmode); - } - if ($this->GPG) { - return $this->GPG->setsignmode($signmode); - } - return false; + return $this->handler()->setsignmode($signmode); } /** @@ -477,13 +370,9 @@ class GnuPG */ public function sign(string $plaintext) /*: string|false*/ { - if ($this->GnuPG) { - return $this->GnuPG->sign($plaintext); - } - if ($this->GPG) { - return $this->GPG->sign($plaintext); - } - return false; + return $this->GnuPG + ? $this->GnuPG->sign($plaintext) + : $this->GPG->sign($plaintext); } /** @@ -491,13 +380,9 @@ class GnuPG */ public function signFile(string $filename) /*: string|false*/ { - if ($this->GnuPG) { - return $this->GnuPG->sign(\file_get_contents($filename)); - } - if ($this->GPG) { - return $this->GPG->signFile($filename); - } - return false; + return $this->GnuPG + ? $this->GnuPG->sign(\file_get_contents($filename)) + : $this->GPG->signFile($filename); } /** @@ -505,13 +390,9 @@ class GnuPG */ public function verify(string $signed_text, string $signature, string &$plaintext = null) /*: array|false*/ { - if ($this->GnuPG) { - return $this->GnuPG->verify($signed_text, $signature, $plaintext); - } - if ($this->GPG) { - return $this->GPG->verify($signed_text, $signature, $plaintext); - } - return false; + return $this->GnuPG + ? $this->GnuPG->verify($signed_text, $signature, $plaintext) + : $this->GPG->verify($signed_text, $signature, $plaintext); } /** @@ -519,26 +400,8 @@ class GnuPG */ public function verifyFile(string $filename, string $signature, string &$plaintext = null) /*: array|false*/ { - if ($this->GnuPG) { - return $this->GnuPG->verify(\file_get_contents($filename), $signature, $plaintext); - } - if ($this->GPG) { - return $this->GPG->verifyFile($filename, $signature, $plaintext); - } - return false; - } - - /** - * RFC 4880 - * https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.5 - */ - public function signatureIssuer(string $signature) /*: array|false*/ - { - if (preg_match('/-----BEGIN PGP SIGNATURE-----(.+)-----END PGP SIGNATURE-----/', $signature, $match)) { - // TODO: use https://github.com/singpolyma/openpgp-php ? - $binary = \base64_decode(\trim($match[1])); - return \strtoupper(\bin2hex(\substr($binary, 24, 8))); - } - return false; + return $this->GnuPG + ? $this->GnuPG->verify(\file_get_contents($filename), $signature, $plaintext) + : $this->GPG->verifyFile($filename, $signature, $plaintext); } } diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gpg.php b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gpg.php index 7bcd8e97b..454199515 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gpg.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gpg.php @@ -62,6 +62,10 @@ class GPG $proc_resource, + $armor = true, + + $signmode = 2, + $options = [ 'homedir' => '', 'keyring' => '', @@ -143,7 +147,7 @@ class GPG */ public function addDecryptKey(string $fingerprint, string $passphrase) : bool { - $this->signKeys[$fingerprint] = $passphrase; + $this->decryptKeys[$fingerprint] = $passphrase; return true; } @@ -161,7 +165,7 @@ class GPG */ public function addSignKey(string $fingerprint, ?string $passphrase) : bool { - $this->decryptKeys[$fingerprint] = $passphrase; + $this->signKeys[$fingerprint] = $passphrase; return false; } @@ -192,12 +196,27 @@ class GPG return true; } + protected function _decrypt(/*string|resource*/ $input, /*string|resource*/ $output = null) + { + $this->setInput($input); + + $fclose = $this->setOutput($output); + + $_ENV['PINENTRY_USER_DATA'] = \json_encode($this->decryptKeys); + + $result = $this->exec('--decrypt --skip-verify'); + + $fclose && \fclose($fclose); + + return $output ? true : $result['output']; + } + /** * Decrypts a given text */ public function decrypt(string $text) /*: string|false */ { - return false; + return $this->_decrypt($text); } /** @@ -205,7 +224,26 @@ class GPG */ public function decryptFile(string $filename) /*: string|false */ { - return false; + $fp = \fopen($filename, 'rb'); + try { + if (!$fp) { + throw new \Exception("Could not open file '{$filename}'"); + } + return $this->_decrypt($fp, $output); + } finally { + $fp && \fclose($fp); + } + } + + /** + * Decrypts a given stream + */ + public function decryptStream($fp, /*string|resource*/ $output = null) /*: string|false*/ + { + if (!$fp || !\is_resource($fp)) { + throw new \Exception('Invalid stream resource'); + } + return $this->_decrypt($fp, $output); } /** @@ -224,26 +262,20 @@ class GPG return false; } - protected function _encrypt(/*resource*/ $input, /*string|resource*/ $output = null, bool $armor = true) + protected function _encrypt(/*string|resource*/ $input, /*string|resource*/ $output = null) { if (!$this->encryptKeys) { throw new \Exception('No encryption keys specified.'); } - $fclose = false; - if ($output && !\is_resource($output)) { - $output = \fopen($output, 'rb'); - if (!$output) { - throw new \Exception("Could not open file '{$filename}'"); - } - $fclose = true; - } - $this->_output = $output; + $this->setInput($input); + + $fclose = $this->setOutput($output); $arguments = [ '--encrypt' ]; - if ($armor) { + if ($this->armor) { $arguments[] = '--armor'; } @@ -251,10 +283,9 @@ class GPG $arguments[] = '--recipient ' . \escapeshellarg($key['fingerprint']); } - $this->setInput($input); $result = $this->exec($arguments); - $fclose && \fclose($output); + $fclose && \fclose($fclose); return $output ? true : $result['output']; } @@ -283,7 +314,7 @@ class GPG } } - public function encryptStream($fp, /*string|resource*/ $output = null) /*: string|false*/ + public function encryptStream(/*resource*/ $fp, /*string|resource*/ $output = null) /*: string|false*/ { if (!$fp || !\is_resource($fp)) { throw new \Exception('Invalid stream resource'); @@ -349,10 +380,10 @@ class GPG */ public function getProtocol() : int { - return false; + return 0; } - public function addPassphrase($key, $passphrase) + public function addPassphrase($keyId, $passphrase) { $this->passphrases[$key] = $passphrase; return $this; @@ -425,15 +456,11 @@ class GPG { $arguments = ['--import']; - $envKeys = []; - if (empty($this->passphrases)) { - $arguments[] = '--batch'; + if ($this->passphrases) { + $_ENV['PINENTRY_USER_DATA'] = \json_encode($this->passphrases); } else { - foreach ($this->passphrases as $keyId => $key) { - $envKeys[$keyId] = \is_array($key) ? $key['passphrase'] : $key; - } + $arguments[] = '--batch'; } - $_ENV['PINENTRY_USER_DATA'] = \json_encode($envKeys); $this->setInput($input); $result = $this->exec($arguments); @@ -639,12 +666,12 @@ class GPG } /** - * Toggle armored output - * When true the output is ASCII + * Toggle the armored output */ - public function setArmor(bool $armor = true) : bool + public function setArmor(int $armor = 1) : bool { - return false; + $this->armor = !!$armor; + return true; } /** @@ -658,43 +685,108 @@ class GPG /** * Sets the mode for signing - * GNUPG_SIG_MODE_NORMAL, GNUPG_SIG_MODE_DETACH and GNUPG_SIG_MODE_CLEAR. + * GNUPG_SIG_MODE_NORMAL, GNUPG_SIG_MODE_DETACH, GNUPG_SIG_MODE_CLEAR * By default GNUPG_SIG_MODE_CLEAR */ public function setSignMode(int $signmode) : bool { - return false; + $this->signmode = $signmode; + return true; + } + + protected function _sign(/*string|resource*/ $input, /*string|resource*/ $output = null, bool $textmode = true) /*: string|false*/ + { + if (!$this->hasSignKeys()) { + throw new \Exception('No signing keys specified.'); + } + + $this->setInput($input); + + $fclose = $this->setOutput($output); + + $arguments = []; + + switch ($this->signmode) + { + case 0: // GNUPG_SIG_MODE_NORMAL + $arguments[] = '--sign'; + break; + case 1: // GNUPG_SIG_MODE_DETACH + $arguments[] = '--detach-sign'; + break; + case 2: // GNUPG_SIG_MODE_CLEAR + default: + $arguments[] = '--clearsign'; + break; + } + + if ($this->armor) { + $arguments[] = '--armor'; + } + if ($textmode) { + $arguments[] = '--textmode'; + } + + foreach ($this->signKeys as $fingerprint => $pass) { + $arguments[] = '--local-user ' . \escapeshellarg($fingerprint); + } + $_ENV['PINENTRY_USER_DATA'] = \json_encode($this->signKeys); + + $result = $this->exec($arguments); + + $fclose && \fclose($fclose); + + return $output ? true : $result['output']; } /** * Signs a given text */ - public function sign(string $plaintext) /*: string|false*/ + public function sign(string $plaintext, /*string|resource*/ $output = null) /*: string|false*/ { - return false; + return $this->_sign($plaintext, $output); } /** * Signs a given file */ - public function signFile(string $filename) /*: string|false*/ + public function signFile(string $filename, /*string|resource*/ $output = null) /*: string|false*/ { - return false; + $fp = \fopen($filename, 'rb'); + try { + if (!$fp) { + throw new \Exception("Could not open file '{$filename}'"); + } + return $this->_sign($fp, $output); + } finally { + $fp && \fclose($fp); + } + } + + /** + * Signs a given file + */ + public function signStream($fp, /*string|resource*/ $output = null) /*: array|false*/ + { + if (!$fp || !\is_resource($fp)) { + throw new \Exception('Invalid stream resource'); + } + return $this->_sign($fp, $output); } protected function _verify($input, string $signature) { $arguments = ['--verify']; - if ('' === $signature) { - // signed or clearsigned data - $this->setInput($input); - } else { + if ($signature) { // detached signature $this->setInput($signature); $this->_message =& $input; // Signed data goes in FD_MESSAGE, detached signature data goes in FD_INPUT. $arguments[] = '--enable-special-filenames'; $arguments[] = '- "-&' . self::FD_MESSAGE . '"'; + } else { + // signed or clearsigned data + $this->setInput($input); } $result = $this->exec($arguments); @@ -779,20 +871,6 @@ class GPG return $this->_verify($fp, $signature); } - /** - * RFC 4880 - * https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.5 - */ - public function signatureIssuer(string $signature) /*: array|false*/ - { - if (preg_match('/-----BEGIN PGP SIGNATURE-----(.+)-----END PGP SIGNATURE-----/', $signature, $match)) { - // TODO: use https://github.com/singpolyma/openpgp-php ? - $binary = \base64_decode(\trim($match[1])); - return \strtoupper(\bin2hex(\substr($binary, 24, 8))); - } - return false; - } - private function _debug(string $msg) : void { if ($this->debug) { @@ -805,9 +883,18 @@ class GPG $this->_input =& $input; } - private function setOutput($output) : void + private function setOutput($output)/* : resource|false*/ { - $this->_output = \is_resource($output) ? $output : null; + $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() diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gpgkeysettings.php b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gpgkeysettings.php index 3f07f57e8..3936f51b3 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gpgkeysettings.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gpgkeysettings.php @@ -1,9 +1,4 @@ usage}"; } - /** Somehow this is broken + /** Somehow this is broken and not working in v2.3.4 $subkey = $this->subkeys[0]; if (!empty($subkey['type'])) { $keyParams[] = "Subkey-Type: {$subkey['type']}";