Fix GPG/PGP exec() return false handling

This commit is contained in:
the-djmaze 2024-04-28 14:49:48 +02:00
parent f9b5a37555
commit 685b2ea93a

View file

@ -83,28 +83,17 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface
protected function listDecryptKeys(/*string|resource*/ $input, /*string|resource*/ $output = null) protected function listDecryptKeys(/*string|resource*/ $input, /*string|resource*/ $output = null)
{ {
$this->setInput($input); $this->setInput($input);
$fclose = $this->setOutput($output);
$_ENV['PINENTRY_USER_DATA'] = ''; $_ENV['PINENTRY_USER_DATA'] = '';
$result = $this->exec(['--list-packets']); return $this->execOutput(['--list-packets'], $output);
$fclose && \fclose($fclose);
return $output ? true : ($result ? $result['output'] : false);
} }
protected function _decrypt(/*string|resource*/ $input, /*string|resource*/ $output = null) protected function _decrypt(/*string|resource*/ $input, /*string|resource*/ $output = null)
{ {
$this->setInput($input); $this->setInput($input);
$fclose = $this->setOutput($output);
if ($this->pinentries) { if ($this->pinentries) {
$_ENV['PINENTRY_USER_DATA'] = \json_encode(\array_map('strval', $this->pinentries)); $_ENV['PINENTRY_USER_DATA'] = \json_encode(\array_map('strval', $this->pinentries));
} }
return $this->execOutput(['--decrypt','--skip-verify'], $output);
$result = $this->exec(['--decrypt','--skip-verify']);
$fclose && \fclose($fclose);
return $output ? true : ($result ? $result['output'] : false);
} }
/** /**
@ -169,8 +158,6 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface
$this->setInput($input); $this->setInput($input);
$fclose = $this->setOutput($output);
$arguments = [ $arguments = [
'--encrypt' '--encrypt'
]; ];
@ -182,11 +169,7 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface
$arguments[] = '--recipient ' . \escapeshellarg($fingerprint); $arguments[] = '--recipient ' . \escapeshellarg($fingerprint);
} }
$result = $this->exec($arguments); return $this->execOutput($arguments, $output);
$fclose && \fclose($fclose);
return $output ? true : $result['output'];
} }
/** /**
@ -257,7 +240,7 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface
'--armor', '--armor',
\escapeshellarg($keys[0]['subkeys'][0]['fingerprint']), \escapeshellarg($keys[0]['subkeys'][0]['fingerprint']),
]); ]);
return $result['output']; return $result ? $result['output'] : false;
} }
/** /**
@ -315,6 +298,9 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface
$settings->algo(), $settings->algo(),
$settings->usage $settings->usage
])); ]));
if (!$result) {
return false;
}
$fingerprint = ''; $fingerprint = '';
foreach ($result['status'] as $line) { foreach ($result['status'] as $line) {
@ -364,24 +350,24 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface
$this->setInput($input); $this->setInput($input);
$result = $this->exec($arguments); $result = $this->exec($arguments);
if ($result) {
foreach ($result['status'] as $line) { foreach ($result['status'] as $line) {
if (false !== \strpos($line, 'IMPORT_RES')) { if (false !== \strpos($line, 'IMPORT_RES')) {
$line = \explode(' ', \explode('IMPORT_RES ', $line)[1]); $line = \explode(' ', \explode('IMPORT_RES ', $line)[1]);
return [ return [
'imported' => (int) $line[2], 'imported' => (int) $line[2],
'unchanged' => (int) $line[4], 'unchanged' => (int) $line[4],
'newuserids' => (int) $line[5], 'newuserids' => (int) $line[5],
'newsubkeys' => (int) $line[6], 'newsubkeys' => (int) $line[6],
'secretimported' => (int) $line[10], 'secretimported' => (int) $line[10],
'secretunchanged' => (int) $line[11], 'secretunchanged' => (int) $line[11],
'newsignatures' => (int) $line[7], 'newsignatures' => (int) $line[7],
'skippedkeys' => (int) $line[12], 'skippedkeys' => (int) $line[12],
'fingerprint' => '' 'fingerprint' => ''
]; ];
}
} }
} }
return false; return false;
} }
@ -433,7 +419,7 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface
// $result['errors'][0] = 'gpg: error reading key: No public key' // $result['errors'][0] = 'gpg: error reading key: No public key'
// print_r($result); // print_r($result);
return true; return !!$result;
} }
/** /**
@ -458,106 +444,108 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface
$result = $this->exec($arguments); $result = $this->exec($arguments);
$keys = []; $keys = [];
$key = null; // current key if ($result) {
$subKey = null; // current sub-key $key = null; // current key
$subKey = null; // current sub-key
foreach (\explode(PHP_EOL, $result['output']) as $line) { foreach (\explode(PHP_EOL, $result['output']) as $line) {
$tokens = \explode(':', $line); $tokens = \explode(':', $line);
switch ($tokens[0]) switch ($tokens[0])
{ {
case 'tru': case 'tru':
break; break;
case 'sec': case 'sec':
case 'pub': case 'pub':
// new primary key means last key should be added to the array // new primary key means last key should be added to the array
if ($key !== null) { if ($key !== null) {
$keys[] = $key; $keys[] = $key;
}
$key = [
'disabled' => false,
'expired' => false,
'revoked' => false,
'is_secret' => 'ssb' === $tokens[0],
'can_sign' => \str_contains($tokens[11], 's'),
'can_encrypt' => \str_contains($tokens[11], 'e'),
'uids' => [],
'subkeys' => []
];
// Fall through to add subkey
case 'ssb': // secure subkey
case 'sub': // public subkey
$key['subkeys'][] = [
'fingerprint' => '', // fpr:::::::::....:
'keyid' => $tokens[4],
'timestamp' => $tokens[5],
'expires' => $tokens[6],
'is_secret' => 'ssb' === $tokens[0],
'invalid' => false,
// escaESCA
'can_encrypt' => \str_contains($tokens[11], 'e'),
'can_sign' => \str_contains($tokens[11], 's'),
'can_certify' => \str_contains($tokens[11], 'c'),
'can_authenticate' => \str_contains($tokens[11], 'a'),
'disabled' => false,
'expired' => false,
'revoked' => 'r' === $tokens[1],
'length' => $tokens[2],
'algorithm' => $tokens[3],
];
break;
case 'fpr':
$key['subkeys'][\array_key_last($key['subkeys'])]['fingerprint'] = $tokens[9];
break;
case 'grp':
$key['subkeys'][\array_key_last($key['subkeys'])]['keygrip'] = $tokens[9];
break;
case 'uid':
$string = \stripcslashes($tokens[9]); // as per documentation
$name = '';
$email = '';
$comment = '';
$matches = [];
// get email address from end of string if it exists
if (\preg_match('/^(.*?)<([^>]+)>$/', $string, $matches)) {
$string = \trim($matches[1]);
$email = $matches[2];
}
// get comment from end of string if it exists
$matches = [];
if (\preg_match('/^(.+?) \(([^\)]+)\)$/', $string, $matches)) {
$string = $matches[1];
$comment = $matches[2];
}
// there can be an email without a name
if (!$email && \preg_match('/^[\S]+@[\S]+$/', $string, $matches)) {
$email = $string;
} else {
$name = $string;
}
$key['uids'][] = [
'name' => $name,
'comment' => $comment,
'email' => $email,
'uid' => $tokens[9],
'revoked' => 'r' === $tokens[1],
'invalid' => false,
];
break;
} }
$key = [
'disabled' => false,
'expired' => false,
'revoked' => false,
'is_secret' => 'ssb' === $tokens[0],
'can_sign' => \str_contains($tokens[11], 's'),
'can_encrypt' => \str_contains($tokens[11], 'e'),
'uids' => [],
'subkeys' => []
];
// Fall through to add subkey
case 'ssb': // secure subkey
case 'sub': // public subkey
$key['subkeys'][] = [
'fingerprint' => '', // fpr:::::::::....:
'keyid' => $tokens[4],
'timestamp' => $tokens[5],
'expires' => $tokens[6],
'is_secret' => 'ssb' === $tokens[0],
'invalid' => false,
// escaESCA
'can_encrypt' => \str_contains($tokens[11], 'e'),
'can_sign' => \str_contains($tokens[11], 's'),
'can_certify' => \str_contains($tokens[11], 'c'),
'can_authenticate' => \str_contains($tokens[11], 'a'),
'disabled' => false,
'expired' => false,
'revoked' => 'r' === $tokens[1],
'length' => $tokens[2],
'algorithm' => $tokens[3],
];
break;
case 'fpr':
$key['subkeys'][\array_key_last($key['subkeys'])]['fingerprint'] = $tokens[9];
break;
case 'grp':
$key['subkeys'][\array_key_last($key['subkeys'])]['keygrip'] = $tokens[9];
break;
case 'uid':
$string = \stripcslashes($tokens[9]); // as per documentation
$name = '';
$email = '';
$comment = '';
$matches = [];
// get email address from end of string if it exists
if (\preg_match('/^(.*?)<([^>]+)>$/', $string, $matches)) {
$string = \trim($matches[1]);
$email = $matches[2];
}
// get comment from end of string if it exists
$matches = [];
if (\preg_match('/^(.+?) \(([^\)]+)\)$/', $string, $matches)) {
$string = $matches[1];
$comment = $matches[2];
}
// there can be an email without a name
if (!$email && \preg_match('/^[\S]+@[\S]+$/', $string, $matches)) {
$email = $string;
} else {
$name = $string;
}
$key['uids'][] = [
'name' => $name,
'comment' => $comment,
'email' => $email,
'uid' => $tokens[9],
'revoked' => 'r' === $tokens[1],
'invalid' => false,
];
break;
} }
}
// add last key // add last key
if ($key) { if ($key) {
$keys[] = $key; $keys[] = $key;
}
} }
return $keys; return $keys;
@ -615,8 +603,6 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface
$this->setInput($input); $this->setInput($input);
$fclose = $this->setOutput($output);
$arguments = []; $arguments = [];
switch ($this->signmode) switch ($this->signmode)
@ -647,11 +633,7 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface
$_ENV['PINENTRY_USER_DATA'] = \json_encode(\array_map('strval', $this->pinentries)); $_ENV['PINENTRY_USER_DATA'] = \json_encode(\array_map('strval', $this->pinentries));
} }
$result = $this->exec($arguments); return $this->execOutput($arguments, $output);
$fclose && \fclose($fclose);
return $output ? true : $result['output'];
} }
/** /**
@ -708,44 +690,46 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface
$result = $this->exec($arguments); $result = $this->exec($arguments);
$signatures = []; $signatures = [];
foreach ($result['status'] as $line) { if ($result) {
$tokens = \explode(' ', $line); foreach ($result['status'] as $line) {
switch ($tokens[0]) $tokens = \explode(' ', $line);
{ switch ($tokens[0])
case 'VERIFICATION_COMPLIANCE_MODE': {
case 'TRUST_FULLY': case 'VERIFICATION_COMPLIANCE_MODE':
break; case 'TRUST_FULLY':
break;
case 'EXPSIG': case 'EXPSIG':
case 'EXPKEYSIG': case 'EXPKEYSIG':
case 'REVKEYSIG': case 'REVKEYSIG':
case 'BADSIG': case 'BADSIG':
case 'ERRSIG': case 'ERRSIG':
case 'GOODSIG': case 'GOODSIG':
$signatures[] = [ $signatures[] = [
'fingerprint' => '', 'fingerprint' => '',
'validity' => 0, 'validity' => 0,
'timestamp' => 0, 'timestamp' => 0,
'status' => 'GOODSIG' === $tokens[0] ? 0 : 1, 'status' => 'GOODSIG' === $tokens[0] ? 0 : 1,
'summary' => 'GOODSIG' === $tokens[0] ? 0 : 4, 'summary' => 'GOODSIG' === $tokens[0] ? 0 : 4,
'keyid' => $tokens[1], 'keyid' => $tokens[1],
'uid' => \rawurldecode(\implode(' ', \array_splice($tokens, 2))), 'uid' => \rawurldecode(\implode(' ', \array_splice($tokens, 2))),
'valid' => false 'valid' => false
]; ];
break; break;
case 'VALIDSIG': case 'VALIDSIG':
$last = \array_key_last($signatures); $last = \array_key_last($signatures);
$signatures[$last]['fingerprint'] = $tokens[1]; $signatures[$last]['fingerprint'] = $tokens[1];
$signatures[$last]['timestamp'] = (int) $tokens[3]; $signatures[$last]['timestamp'] = (int) $tokens[3];
$signatures[$last]['expires'] = (int) $tokens[4]; $signatures[$last]['expires'] = (int) $tokens[4];
$signatures[$last]['version'] = (int) $tokens[5]; $signatures[$last]['version'] = (int) $tokens[5];
// $signatures[$last]['reserved'] = (int) $tokens[6]; // $signatures[$last]['reserved'] = (int) $tokens[6];
// $signatures[$last]['pubkey-algo'] = (int) $tokens[7]; // $signatures[$last]['pubkey-algo'] = (int) $tokens[7];
// $signatures[$last]['hash-algo'] = (int) $tokens[8]; // $signatures[$last]['hash-algo'] = (int) $tokens[8];
// $signatures[$last]['sig-class'] = $tokens[9]; // $signatures[$last]['sig-class'] = $tokens[9];
// $signatures[$last]['primary-fingerprint'] = $tokens[10]; // $signatures[$last]['primary-fingerprint'] = $tokens[10];
$signatures[$last]['valid'] = 0; $signatures[$last]['valid'] = 0;
}
} }
} }
@ -799,16 +783,26 @@ class PGP extends Base implements \SnappyMail\PGP\PGPInterface
// 'KEY_CONSIDERED' => [], // 'KEY_CONSIDERED' => [],
// 'NO_SECKEY' => [], // 'NO_SECKEY' => [],
]; ];
foreach ($result['status'] as $line) { if ($result) {
$tokens = \explode(' ', $line); foreach ($result['status'] as $line) {
if (isset($info[$tokens[0]])) { $tokens = \explode(' ', $line);
$info[$tokens[0]][] = $tokens[1]; if (isset($info[$tokens[0]])) {
$info[$tokens[0]][] = $tokens[1];
}
} }
} }
$this->_debug('END DETECT MESSAGE KEY IDs'); $this->_debug('END DETECT MESSAGE KEY IDs');
return $info['ENC_TO']; return $info['ENC_TO'];
} }
protected function execOutput(array $arguments, /*string|resource*/ $output = null)
{
$fclose = $this->setOutput($output);
$result = $this->exec($arguments);
$fclose && \fclose($fclose);
return $output ? true : ($result ? $result['output'] : false);
}
private function exec(array $arguments, bool $throw = true) /*: array|false*/ private function exec(array $arguments, bool $throw = true) /*: array|false*/
{ {
if (\version_compare($this->version, '2.2.5', '<')) { if (\version_compare($this->version, '2.2.5', '<')) {