Improved GnuPG error handling

This commit is contained in:
the-djmaze 2024-02-27 18:45:25 +01:00
parent c73e3637a3
commit 3d176d4e4f
9 changed files with 110 additions and 56 deletions

View file

@ -69,10 +69,10 @@ export const GnuPGUserStore = new class {
} }
}; };
if (isPrivate) { if (isPrivate) {
key.password = async (btnTxt = 'SIGN') => { key.password = async (btnTxt = 'CRYPTO/SIGN') => {
const pass = await Passphrases.ask(key, const pass = await Passphrases.ask(key,
'GnuPG key<br>' + key.id + ' ' + key.emails[0], 'GnuPG key<br>' + key.id + ' ' + key.emails[0],
'OPENPGP/'+btnTxt btnTxt
); );
pass && pass.remember && Passphrases.set(key, pass.password); pass && pass.remember && Passphrases.set(key, pass.password);
return pass?.password; return pass?.password;
@ -82,7 +82,7 @@ export const GnuPGUserStore = new class {
if (key.armor) { if (key.armor) {
callback && callback(); callback && callback();
} else { } else {
let pass = isPrivate ? await key.password('POPUP_VIEW_TITLE') : ''; let pass = isPrivate ? await key.password('OPENPGP/POPUP_VIEW_TITLE') : '';
if (null != pass) try { if (null != pass) try {
const result = await Remote.post('GnupgExportKey', null, { const result = await Remote.post('GnupgExportKey', null, {
keyId: key.id, keyId: key.id,
@ -184,15 +184,20 @@ export const GnuPGUserStore = new class {
uid: message.uid, uid: message.uid,
partId: pgpInfo.partId, partId: pgpInfo.partId,
keyId: key.id, keyId: key.id,
passphrase: await key.password('DECRYPT'), passphrase: await key.password('CRYPTO/DECRYPT'),
data: '' // message.plain() optional data: '' // message.plain() optional
} }
if (null !== params.passphrase) { if (null != params.passphrase) {
const result = await Remote.post('GnupgDecrypt', null, params); try {
if (result?.Result?.data) { const response = await Remote.post('GnupgDecrypt', null, params);
return result.Result; if (response?.Result?.data) {
return response.Result;
}
throw response;
} catch (e) {
Passphrases.delete(key);
throw e;
} }
Passphrases.delete(key);
} }
} }
} }

View file

@ -587,10 +587,13 @@ export class MailMessageView extends AbstractViewRight {
} }
} else { } else {
// TODO: translate // TODO: translate
alert('Decryption failed, canceled or not possible'); throw 'Decryption failed, canceled or not possible';
} }
}) })
.catch(e => console.error(e)); .catch(e => {
console.error(e)
alert(e.message);
});
} }
pgpVerify(/*self, event*/) { pgpVerify(/*self, event*/) {

View file

@ -120,7 +120,7 @@ trait Pgp
$sData = $this->GetActionParam('data', ''); $sData = $this->GetActionParam('data', '');
$oPart = null; $oPart = null;
$result = [ $result = [
'data' => '', 'data' => null,
'signatures' => [] 'signatures' => []
]; ];
if ($sData) { if ($sData) {
@ -130,11 +130,13 @@ trait Pgp
$this->initMailClientConnection(); $this->initMailClientConnection();
$this->MailClient()->MessageMimeStream( $this->MailClient()->MessageMimeStream(
function ($rResource) use ($GPG, &$result, &$oPart) { function ($rResource) use ($GPG, &$result, &$oPart) {
if (\is_resource($rResource)) { if (\is_resource($rResource)) try {
$result['data'] = $GPG->decryptStream($rResource); $result['data'] = $GPG->decryptStream($rResource);
// $oPart = \MailSo\Mime\Part::FromString($result); // $oPart = \MailSo\Mime\Part::FromString($result);
// $GPG->decryptStream($rResource, $rStreamHandle); // $GPG->decryptStream($rResource, $rStreamHandle);
// $oPart = \MailSo\Mime\Part::FromStream($rStreamHandle); // $oPart = \MailSo\Mime\Part::FromStream($rStreamHandle);
} catch (\Throwable $e) {
$result = $e;
} }
}, },
$this->GetActionParam('folder', ''), $this->GetActionParam('folder', ''),
@ -148,6 +150,10 @@ trait Pgp
// $result['signatures'] = $oPart->SubParts[0]; // $result['signatures'] = $oPart->SubParts[0];
} }
if ($result instanceof \Throwable) {
throw $result;
}
return $this->DefaultResponse($result); return $this->DefaultResponse($result);
} }

View file

@ -45,9 +45,10 @@ trait Response
public function ExceptionResponse(\Throwable $oException) : array public function ExceptionResponse(\Throwable $oException) : array
{ {
$iErrorCode = 0; $iErrorCode = Notifications::UnknownError;
$sErrorMessage = ''; $sErrorMessage = $oException->getMessage();
$sErrorMessageAdditional = ''; $sErrorMessageAdditional = '';
$iExceptionCode = 0;
if ($oException instanceof \RainLoop\Exceptions\ClientException) { if ($oException instanceof \RainLoop\Exceptions\ClientException) {
$iErrorCode = $oException->getCode(); $iErrorCode = $oException->getCode();
@ -56,8 +57,7 @@ trait Response
} }
$sErrorMessageAdditional = $oException->getAdditionalMessage(); $sErrorMessageAdditional = $oException->getAdditionalMessage();
} else { } else {
$iErrorCode = Notifications::UnknownError; $iExceptionCode = $oException->getCode();
$sErrorMessage = $oException->getCode().' - '.$oException->getMessage();
} }
$this->logException($oException->getPrevious() ?: $oException); $this->logException($oException->getPrevious() ?: $oException);
@ -65,7 +65,8 @@ trait Response
return $this->DefaultResponse(false, [ return $this->DefaultResponse(false, [
'ErrorCode' => $iErrorCode, 'ErrorCode' => $iErrorCode,
'ErrorMessage' => $sErrorMessage, 'ErrorMessage' => $sErrorMessage,
'ErrorMessageAdditional' => $sErrorMessageAdditional 'ErrorMessageAdditional' => $sErrorMessageAdditional,
'ExceptionCode' => $iExceptionCode
]); ]);
} }

View file

@ -114,14 +114,15 @@ class ServiceActions
if ($_POST) { if ($_POST) {
$this->oActions->SetActionParams($_POST, $sMethodName); $this->oActions->SetActionParams($_POST, $sMethodName);
foreach ($_POST as $key => $value) { $aPost = $_POST;
foreach ($aPost as $key => $value) {
// password & passphrase // password & passphrase
if (false !== \stripos($key, 'pass')) { if (false !== \stripos($key, 'pass')) {
// $_POST[$key] = '*******'; $aPost[$key] = '*******';
$this->oActions->logMask($value); // $this->oActions->logMask($value);
} }
} }
$this->oActions->logWrite(Utils::jsonEncode($_POST), \LOG_INFO, 'POST'); $this->oActions->logWrite(Utils::jsonEncode($aPost), \LOG_INFO, 'POST');
} else if (3 < \count($this->aPaths) && $this->oHttp->IsGet()) { } else if (3 < \count($this->aPaths) && $this->oHttp->IsGet()) {
$this->oActions->SetActionParams(array( $this->oActions->SetActionParams(array(
'RawKey' => empty($this->aPaths[3]) ? '' : $this->aPaths[3] 'RawKey' => empty($this->aPaths[3]) ? '' : $this->aPaths[3]

View file

@ -243,7 +243,7 @@ abstract class Base
/** /**
* Signs a given file * Signs a given file
*/ */
public function signStream($fp, /*string|resource*/ $output = null) /*: array|false*/ public function signStream($fp, /*string|resource*/ $output = null) /*: string|false*/
{ {
return false; return false;
} }

View file

@ -666,7 +666,7 @@ class PGP extends Base
/** /**
* Signs a given file * Signs a given file
*/ */
public function signStream($fp, /*string|resource*/ $output = null) /*: array|false*/ public function signStream($fp, /*string|resource*/ $output = null) /*: string|false*/
{ {
if (!$fp || !\is_resource($fp)) { if (!$fp || !\is_resource($fp)) {
throw new \Exception('Invalid stream resource'); throw new \Exception('Invalid stream resource');
@ -674,7 +674,7 @@ class PGP extends Base
return $this->_sign($fp, $output); return $this->_sign($fp, $output);
} }
protected function _verify($input, string $signature) protected function _verify($input, string $signature) /*: array|false*/
{ {
$arguments = ['--verify']; $arguments = ['--verify'];
if ($signature) { if ($signature) {

View file

@ -540,7 +540,7 @@ class SMIME extends Base
/** /**
* Signs a given file * Signs a given file
*/ */
public function signStream($fp, /*string|resource*/ $output = null) /*: array|false*/ public function signStream($fp, /*string|resource*/ $output = null) /*: string|false*/
{ {
if (!$fp || !\is_resource($fp)) { if (!$fp || !\is_resource($fp)) {
throw new \Exception('Invalid stream resource'); throw new \Exception('Invalid stream resource');
@ -548,7 +548,7 @@ class SMIME extends Base
return $this->_sign($fp, $output); return $this->_sign($fp, $output);
} }
protected function _verify($input, string $signature) protected function _verify($input, string $signature) /*: array|false*/
{ {
$arguments = ['--verify']; $arguments = ['--verify'];
if ($signature) { if ($signature) {

View file

@ -36,6 +36,7 @@ class GnuPG
]); ]);
// Output is ASCII // Output is ASCII
$this->GnuPG->setarmor(1); $this->GnuPG->setarmor(1);
\SnappyMail\Log::info('GnuPG', 'Using PECL');
} else { } else {
$this->getGPG(); $this->getGPG();
} }
@ -52,6 +53,12 @@ class GnuPG
*/ */
} }
function __destruct()
{
$this->clearDecryptKeys();
$this->clearSignKeys();
}
public static function isSupported() : bool public static function isSupported() : bool
{ {
return \class_exists('gnupg') || GPG::isSupported(); return \class_exists('gnupg') || GPG::isSupported();
@ -71,11 +78,20 @@ class GnuPG
return $this->GnuPG ?: $this->GPG; return $this->GnuPG ?: $this->GPG;
} }
public function gnupgError()
{
$error = $this->GnuPG ? $this->GnuPG->geterrorinfo() : null;
if ($error) {
throw new \Exception("{$error['gpgme_source']} {$error['generic_message']}. {$error['gpgme_message']}", $error['gpgme_code']);
}
}
public function getGPG(bool $throw = true) : ?GPG public function getGPG(bool $throw = true) : ?GPG
{ {
if (!$this->GPG) { if (!$this->GPG) {
if (GPG::isSupported()) { if (GPG::isSupported()) {
$this->GPG = new GPG($this->homedir); $this->GPG = new GPG($this->homedir);
\SnappyMail\Log::info('GnuPG', 'Using ' . GPG::class);
} else if ($throw) { } else if ($throw) {
throw new \Exception('GnuPG not supported'); throw new \Exception('GnuPG not supported');
} }
@ -88,7 +104,9 @@ class GnuPG
*/ */
public function addDecryptKey(string $fingerprint, \SnappyMail\SensitiveString $passphrase) : bool public function addDecryptKey(string $fingerprint, \SnappyMail\SensitiveString $passphrase) : bool
{ {
return $this->handler()->adddecryptkey($fingerprint, $passphrase); return $this->GnuPG
? $this->GnuPG->adddecryptkey($fingerprint, \strval($passphrase)) || $this->gnupgError()
: $this->GPG->addDecryptKey($fingerprint, $passphrase);
} }
/** /**
@ -104,7 +122,9 @@ class GnuPG
*/ */
public function addSignKey(string $fingerprint, \SnappyMail\SensitiveString $passphrase) : bool public function addSignKey(string $fingerprint, \SnappyMail\SensitiveString $passphrase) : bool
{ {
return $this->handler()->addsignkey($fingerprint, $passphrase); return $this->GnuPG
? $this->GnuPG->addsignkey($fingerprint, \strval($passphrase)) || $this->gnupgError()
: $this->GPG->addSignKey($fingerprint, $passphrase);
} }
/** /**
@ -136,9 +156,12 @@ class GnuPG
*/ */
public function decrypt(string $text) /*: string|false */ public function decrypt(string $text) /*: string|false */
{ {
return $this->GnuPG if ($this->GnuPG) {
? $this->GnuPG->decrypt($text) $result = $this->GnuPG->decrypt($text);
: $this->GPG->decrypt($text); (false === $result) && $this->gnupgError();
return $result;
}
return $this->GPG->decrypt($text);
} }
/** /**
@ -146,9 +169,12 @@ class GnuPG
*/ */
public function decryptFile(string $filename) /*: string|false */ public function decryptFile(string $filename) /*: string|false */
{ {
return $this->GnuPG if ($this->GnuPG) {
? $this->GnuPG->decrypt(\file_get_contents($filename)) $result = $this->GnuPG->decrypt(\file_get_contents($filename));
: $this->GPG->decryptFile($filename); (false === $result) && $this->gnupgError();
return $result;
}
return $this->GPG->decryptFile($filename);
} }
/** /**
@ -159,9 +185,12 @@ class GnuPG
if (!$fp || !\is_resource($fp)) { if (!$fp || !\is_resource($fp)) {
throw new \Exception('Invalid stream resource'); throw new \Exception('Invalid stream resource');
} }
return $this->GnuPG if ($this->GnuPG) {
? $this->GnuPG->decrypt(\stream_get_contents($fp)) $result = $this->GnuPG->decrypt(\stream_get_contents($fp));
: $this->GPG->decryptStream($fp, $output); (false === $result) && $this->gnupgError();
return $result;
}
return $this->GPG->decryptStream($fp, $output);
} }
/** /**
@ -169,9 +198,12 @@ class GnuPG
*/ */
public function decryptVerify(string $text, string &$plaintext) /*: array|false*/ public function decryptVerify(string $text, string &$plaintext) /*: array|false*/
{ {
return $this->GnuPG if ($this->GnuPG) {
? $this->GnuPG->decryptverify($text, $plaintext) $result = $this->GnuPG->decryptverify($text, $plaintext);
: $this->GPG->decryptverify($text, $plaintext); (false === $result) && $this->gnupgError();
return $result;
}
return $this->GPG->decryptverify($text, $plaintext);
} }
/** /**
@ -179,9 +211,12 @@ class GnuPG
*/ */
public function decryptVerifyFile(string $filename, string &$plaintext) /*: array|false*/ public function decryptVerifyFile(string $filename, string &$plaintext) /*: array|false*/
{ {
return $this->GnuPG if ($this->GnuPG) {
? $this->GnuPG->decryptverify(\file_get_contents($filename), $plaintext) $result = $this->GnuPG->decryptverify(\file_get_contents($filename), $plaintext);
: $this->GPG->decryptverifyFile($filename, $plaintext); (false === $result) && $this->gnupgError();
return $result;
}
return $this->GPG->decryptverifyFile($filename, $plaintext);
} }
public function deleteKey(string $keyId, bool $private) : bool public function deleteKey(string $keyId, bool $private) : bool
@ -195,7 +230,7 @@ class GnuPG
public function encrypt(string $plaintext) /*: string|false*/ public function encrypt(string $plaintext) /*: string|false*/
{ {
return $this->GnuPG return $this->GnuPG
? $this->GnuPG->encrypt($plaintext) ? $this->GnuPG->encrypt($plaintext) ?: $this->gnupgError()
: $this->GPG->encrypt($plaintext); : $this->GPG->encrypt($plaintext);
} }
@ -205,7 +240,7 @@ class GnuPG
public function encryptFile(string $filename) /*: string|false*/ public function encryptFile(string $filename) /*: string|false*/
{ {
return $this->GnuPG return $this->GnuPG
? $this->GnuPG->encrypt(\file_get_contents($filename)) ? $this->GnuPG->encrypt(\file_get_contents($filename)) ?: $this->gnupgError()
: $this->GPG->encryptFile($filename); : $this->GPG->encryptFile($filename);
} }
@ -213,7 +248,7 @@ class GnuPG
{ {
\rewind($fp); \rewind($fp);
return $this->GnuPG return $this->GnuPG
? $this->GnuPG->encrypt(\stream_get_contents($fp)) ? $this->GnuPG->encrypt(\stream_get_contents($fp)) ?: $this->gnupgError()
: $this->GPG->encryptStream($fp); : $this->GPG->encryptStream($fp);
} }
@ -228,7 +263,7 @@ class GnuPG
->exportPrivateKey($fingerprint); ->exportPrivateKey($fingerprint);
} }
return $this->GnuPG return $this->GnuPG
? $this->GnuPG->export($fingerprint) ? $this->GnuPG->export($fingerprint) ?: $this->gnupgError()
: $this->GPG->export($fingerprint); : $this->GPG->export($fingerprint);
} }
@ -287,7 +322,7 @@ class GnuPG
public function importFile(string $filename) /*: array|false*/ public function importFile(string $filename) /*: array|false*/
{ {
return $this->GnuPG return $this->GnuPG
? $this->GnuPG->import(\file_get_contents($filename)) ? $this->GnuPG->import(\file_get_contents($filename)) ?: $this->gnupgError()
: $this->GPG->importFile($filename); : $this->GPG->importFile($filename);
} }
@ -350,7 +385,7 @@ class GnuPG
public function sign(string $plaintext) /*: string|false*/ public function sign(string $plaintext) /*: string|false*/
{ {
return $this->GnuPG return $this->GnuPG
? $this->GnuPG->sign($plaintext) ? $this->GnuPG->sign($plaintext) ?: $this->gnupgError()
: $this->GPG->sign($plaintext); : $this->GPG->sign($plaintext);
} }
@ -360,18 +395,18 @@ class GnuPG
public function signFile(string $filename) /*: string|false*/ public function signFile(string $filename) /*: string|false*/
{ {
return $this->GnuPG return $this->GnuPG
? $this->GnuPG->sign(\file_get_contents($filename)) ? $this->GnuPG->sign(\file_get_contents($filename)) ?: $this->gnupgError()
: $this->GPG->signFile($filename); : $this->GPG->signFile($filename);
} }
/** /**
* Signs a given file * Signs a given file
*/ */
public function signStream($fp, /*string|resource*/ $output = null) /*: array|false*/ public function signStream($fp, /*string|resource*/ $output = null) /*: string|false*/
{ {
\rewind($fp); \rewind($fp);
return $this->GnuPG return $this->GnuPG
? $this->GnuPG->sign(\stream_get_contents($fp)) ? $this->GnuPG->sign(\stream_get_contents($fp)) ?: $this->gnupgError()
: $this->GPG->signStream($fp); : $this->GPG->signStream($fp);
} }
@ -381,7 +416,7 @@ class GnuPG
public function verify(string $signed_text, string $signature, string &$plaintext = null) /*: array|false*/ public function verify(string $signed_text, string $signature, string &$plaintext = null) /*: array|false*/
{ {
$result = $this->GnuPG $result = $this->GnuPG
? $this->GnuPG->verify($signed_text, $signature ?: false, $plaintext) ? $this->GnuPG->verify($signed_text, $signature ?: false, $plaintext) ?: $this->gnupgError()
: $this->GPG->verify($signed_text, $signature, $plaintext); : $this->GPG->verify($signed_text, $signature, $plaintext);
if (!$result) { if (!$result) {
if ($this->GnuPG) { if ($this->GnuPG) {
@ -400,18 +435,21 @@ class GnuPG
public function verifyFile(string $filename, string $signature, string &$plaintext = null) /*: array|false*/ public function verifyFile(string $filename, string $signature, string &$plaintext = null) /*: array|false*/
{ {
return $this->GnuPG return $this->GnuPG
? $this->GnuPG->verify(\file_get_contents($filename), $signature, $plaintext) ? $this->GnuPG->verify(\file_get_contents($filename), $signature, $plaintext) ?: $this->gnupgError()
: $this->GPG->verifyFile($filename, $signature, $plaintext); : $this->GPG->verifyFile($filename, $signature, $plaintext);
} }
/** /**
* Verifies a given resource * Verifies a given resource
*/ */
public function verifyStream(/*resource*/ $fp, string $signature, string &$plaintext = null) /*: string|false */ public function verifyStream(/*resource*/ $fp, string $signature, string &$plaintext = null) /*: array|false */
{ {
if (!$fp || !\is_resource($fp)) { if (!$fp || !\is_resource($fp)) {
throw new \Exception('Invalid stream resource'); throw new \Exception('Invalid stream resource');
} }
// return $this->GnuPG
// ? $this->GnuPG->verify(\stream_get_contents($fp), $signature, $plaintext) ?: $this->gnupgError()
// : $this->GPG->verifyStream($fp, $signature, $plaintext);
return $this->getGPG()->verifyStream($fp, $signature, $plaintext); return $this->getGPG()->verifyStream($fp, $signature, $plaintext);
} }