Current File : //var/webuzo-data/roundcube/vendor/pear/crypt_gpg/Crypt/GPG/ProcessHandler.php |
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Crypt_GPG is a package to use GPG from PHP
*
* This file contains handler for status and error pipes of GPG process.
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @author Aleksander Machniak <alec@alec.pl>
* @copyright 2005-2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @link http://www.gnupg.org/
*/
/**
* GPG exception classes.
*/
require_once 'Crypt/GPG/Exceptions.php';
/**
* Signature object class definition
*/
require_once 'Crypt/GPG/Signature.php';
/**
* Status/Error handler for GPG process pipes.
*
* This class is used internally by Crypt_GPG_Engine and does not need to be used
* directly. See the {@link Crypt_GPG} class for end-user API.
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @author Aleksander Machniak <alec@alec.pl>
* @copyright 2005-2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @link http://www.gnupg.org/
*/
class Crypt_GPG_ProcessHandler
{
/**
* Engine used to control the GPG subprocess
*
* @var Crypt_GPG_Engine
*/
protected $engine;
/**
* The error code of the current operation
*
* @var int
*/
protected $errorCode = Crypt_GPG::ERROR_NONE;
/**
* The number of currently needed passphrases
*
* If this is not zero when the GPG command is completed, the error code is
* set to {@link Crypt_GPG::ERROR_MISSING_PASSPHRASE}.
*
* @var int
*/
protected $needPassphrase = 0;
/**
* Some data collected while processing the operation
* or set for the operation
*
* @var array
* @see self::setData()
* @see self::getData()
*/
protected $data = [];
/**
* The name of the current operation
*
* @var string
* @see self::setOperation()
*/
protected $operation = null;
/**
* The value of the argument of current operation
*
* @var string
* @see self::setOperation()
*/
protected $operationArg = null;
/**
* Creates a new instance
*
* @param Crypt_GPG_Engine $engine Engine object
*/
public function __construct($engine)
{
$this->engine = $engine;
}
/**
* Sets the operation that is being performed by the engine.
*
* @param string $operation The GPG operation to perform.
*
* @return void
*/
public function setOperation($operation)
{
$op = null;
$opArg = null;
// Regexp matching all GPG "operational" arguments
$regexp = '/--('
. 'version|import|list-public-keys|list-secret-keys'
. '|list-keys|delete-key|delete-secret-key|encrypt|sign|clearsign'
. '|detach-sign|decrypt|verify|export-secret-keys|export|gen-key'
. ')/';
if (strpos($operation, ' ') === false) {
$op = trim($operation, '- ');
} else if (preg_match($regexp, $operation, $matches, PREG_OFFSET_CAPTURE)) {
$op = trim($matches[0][0], '-');
$op_len = $matches[0][1] + mb_strlen($op, '8bit') + 3;
$command = mb_substr($operation, $op_len, null, '8bit');
// we really need the argument if it is a key ID/fingerprint or email
// address se we can use simplified regexp to "revert escapeshellarg()"
if (preg_match('/^[\'"]([a-zA-Z0-9:@._-]+)[\'"]/', $command, $matches)) {
$opArg = $matches[1];
}
}
$this->operation = $op;
$this->operationArg = $opArg;
$this->data['Warnings'] = [];
}
/**
* Handles error values in the status output from GPG
*
* This method is responsible for setting the
* {@link self::$errorCode}. See <b>doc/DETAILS</b> in the
* {@link http://www.gnupg.org/download/ GPG distribution} for detailed
* information on GPG's status output.
*
* @param string $line the status line to handle.
*
* @return void
*/
public function handleStatus($line)
{
$tokens = explode(' ', $line);
switch ($tokens[0]) {
case 'NODATA':
$this->errorCode = Crypt_GPG::ERROR_NO_DATA;
break;
case 'DECRYPTION_OKAY':
// If the message is encrypted, this is the all-clear signal.
$this->data['DecryptionOkay'] = true;
$this->errorCode = Crypt_GPG::ERROR_NONE;
break;
case 'DELETE_PROBLEM':
if ($tokens[1] == '1') {
$this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
break;
} elseif ($tokens[1] == '2') {
$this->errorCode = Crypt_GPG::ERROR_DELETE_PRIVATE_KEY;
break;
}
break;
case 'IMPORT_OK':
$this->data['Import']['fingerprint'] = $tokens[2];
if (empty($this->data['Import']['fingerprints'])) {
$this->data['Import']['fingerprints'] = [$tokens[2]];
} else if (!in_array($tokens[2], $this->data['Import']['fingerprints'])) {
$this->data['Import']['fingerprints'][] = $tokens[2];
}
break;
case 'IMPORT_RES':
$this->data['Import']['public_imported'] = intval($tokens[3]);
$this->data['Import']['public_unchanged'] = intval($tokens[5]);
$this->data['Import']['private_imported'] = intval($tokens[11]);
$this->data['Import']['private_unchanged'] = intval($tokens[12]);
break;
case 'NO_PUBKEY':
case 'NO_SECKEY':
$this->data['ErrorKeyId'] = $tokens[1];
if ($this->errorCode != Crypt_GPG::ERROR_MISSING_PASSPHRASE
&& $this->errorCode != Crypt_GPG::ERROR_BAD_PASSPHRASE
&& !(
$this->operation == 'decrypt'
&& $tokens[0] == 'NO_PUBKEY'
&& !empty($this->data['IgnoreVerifyErrors'])
)
) {
$this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
}
// note: this message is also received if there are multiple
// recipients and a previous key had a correct passphrase.
$this->data['MissingKeys'][$tokens[1]] = $tokens[1];
// @FIXME: remove missing passphrase registered in ENC_TO handler
// This is for GnuPG 2.1
unset($this->data['MissingPassphrases'][$tokens[1]]);
break;
case 'KEY_CONSIDERED':
// In GnuPG 2.1.x exporting/importing a secret key requires passphrase
// However, no NEED_PASSPRASE is returned, https://bugs.gnupg.org/gnupg/issue2667
// So, handling KEY_CONSIDERED and GET_HIDDEN is needed.
if (!array_key_exists('KeyConsidered', $this->data)) {
$this->data['KeyConsidered'] = $tokens[1];
}
break;
case 'USERID_HINT':
// remember the user id for pretty exception messages
// GnuPG 2.1.15 gives me: "USERID_HINT 0000000000000000 [?]"
$keyId = $tokens[1];
if (preg_match('/[1-9A-F]/', $keyId)) {
$username = implode(' ', array_splice($tokens, 2));
$this->data['BadPassphrases'][$keyId] = $username;
}
break;
case 'ENC_TO':
// Now we know the message is encrypted. Set flag to check if
// decryption succeeded.
$this->data['DecryptionOkay'] = false;
// this is the new key message
$this->data['CurrentSubKeyId'] = $keyId = $tokens[1];
// For some reason in GnuPG 2.1.11 I get only ENC_TO and no
// NEED_PASSPHRASE/MISSING_PASSPHRASE/USERID_HINT
// This is not needed for GnuPG 2.1.15
if (!empty($_ENV['PINENTRY_USER_DATA'])) {
$passphrases = json_decode($_ENV['PINENTRY_USER_DATA'], true);
} else {
$passphrases = [];
}
// @TODO: Get user name/email
$this->data['BadPassphrases'][$keyId] = $keyId;
if (empty($passphrases) || empty($passphrases[$keyId])) {
$this->data['MissingPassphrases'][$keyId] = $keyId;
}
break;
case 'GOOD_PASSPHRASE':
// if we got a good passphrase, remove the key from the list of
// bad passphrases.
if (isset($this->data['CurrentSubKeyId'])) {
unset($this->data['BadPassphrases'][$this->data['CurrentSubKeyId']]);
unset($this->data['MissingPassphrases'][$this->data['CurrentSubKeyId']]);
}
$this->needPassphrase--;
break;
case 'BAD_PASSPHRASE':
$this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
break;
case 'MISSING_PASSPHRASE':
if (isset($this->data['CurrentSubKeyId'])) {
$this->data['MissingPassphrases'][$this->data['CurrentSubKeyId']]
= $this->data['CurrentSubKeyId'];
}
$this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
break;
case 'GET_HIDDEN':
if ($tokens[1] == 'passphrase.enter' && isset($this->data['KeyConsidered'])) {
$tokens[1] = $this->data['KeyConsidered'];
} else {
break;
}
// no break
case 'NEED_PASSPHRASE':
$passphrase = $this->getPin($tokens[1]);
$this->engine->sendCommand($passphrase);
if ($passphrase === '') {
$this->needPassphrase++;
}
break;
case 'SIG_CREATED':
$this->data['SigCreated'] = $line;
break;
case 'SIG_ID':
// note: signature id comes before new signature line and may not
// exist for some signature types
$this->data['SignatureId'] = $tokens[1];
break;
case 'EXPSIG':
case 'EXPKEYSIG':
case 'REVKEYSIG':
case 'BADSIG':
case 'ERRSIG':
$this->errorCode = Crypt_GPG::ERROR_BAD_SIGNATURE;
// no break
case 'GOODSIG':
$signature = new Crypt_GPG_Signature();
// if there was a signature id, set it on the new signature
if (!empty($this->data['SignatureId'])) {
$signature->setId($this->data['SignatureId']);
$this->data['SignatureId'] = '';
}
// Detect whether fingerprint or key id was returned and set
// signature values appropriately. Key ids are strings of either
// 16 or 8 hexadecimal characters. Fingerprints are strings of 40
// hexadecimal characters. The key id is the last 16 characters of
// the key fingerprint.
if (mb_strlen($tokens[1], '8bit') > 16) {
$signature->setKeyFingerprint($tokens[1]);
$signature->setKeyId(mb_substr($tokens[1], -16, null, '8bit'));
} else {
$signature->setKeyId($tokens[1]);
}
// get user id string
if ($tokens[0] != 'ERRSIG') {
$string = implode(' ', array_splice($tokens, 2));
$string = rawurldecode($string);
$signature->setUserId(Crypt_GPG_UserId::parse($string));
}
$this->data['Signatures'][] = $signature;
break;
case 'VALIDSIG':
if (empty($this->data['Signatures'])) {
break;
}
$signature = end($this->data['Signatures']);
$signature->setValid(true);
$signature->setKeyFingerprint($tokens[1]);
if (strpos($tokens[3], 'T') === false) {
$signature->setCreationDate($tokens[3]);
} else {
$signature->setCreationDate(strtotime($tokens[3]));
}
if (array_key_exists(4, $tokens)) {
if (strpos($tokens[4], 'T') === false) {
$signature->setExpirationDate($tokens[4]);
} else {
$signature->setExpirationDate(strtotime($tokens[4]));
}
}
break;
case 'KEY_CREATED':
if (isset($this->data['Handle']) && $tokens[3] == $this->data['Handle']) {
$this->data['KeyCreated'] = $tokens[2];
}
break;
case 'KEY_NOT_CREATED':
if (isset($this->data['Handle']) && $tokens[1] == $this->data['Handle']) {
$this->errorCode = Crypt_GPG::ERROR_KEY_NOT_CREATED;
}
break;
case 'PROGRESS':
// todo: at some point, support reporting status async
break;
// GnuPG 2.1 uses FAILURE and ERROR responses
case 'FAILURE':
case 'ERROR':
$errnum = (int) $tokens[2];
$source = $errnum >> 24;
$errcode = $errnum & 0xFFFFFF;
switch ($errcode) {
case 11: // bad passphrase
case 87: // bad PIN
$this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
break;
case 177: // no passphrase
case 178: // no PIN
$this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
break;
case 58:
$this->errorCode = Crypt_GPG::ERROR_NO_DATA;
break;
}
break;
}
}
/**
* Handles error values in the error output from GPG
*
* This method is responsible for setting the
* {@link Crypt_GPG_Engine::$_errorCode}.
*
* @param string $line the error line to handle.
*
* @return void
*/
public function handleError($line)
{
if (stripos($line, 'gpg: WARNING: ') !== false) {
$this->data['Warnings'][] = substr($line, 14);
}
if ($this->errorCode !== Crypt_GPG::ERROR_NONE) {
return;
}
$pattern = '/no valid OpenPGP data found/';
if (preg_match($pattern, $line) === 1) {
$this->errorCode = Crypt_GPG::ERROR_NO_DATA;
return;
}
$pattern = '/No secret key|secret key not available/';
if (preg_match($pattern, $line) === 1) {
$this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
return;
}
$pattern = '/No public key|public key not found/';
if (preg_match($pattern, $line) === 1) {
if ($this->operation == 'decrypt' && !empty($this->data['IgnoreVerifyErrors'])) {
return;
}
$this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
return;
}
$pattern = '/can\'t (?:access|open) `(.*?)\'/';
if (preg_match($pattern, $line, $matches) === 1) {
$this->data['ErrorFilename'] = $matches[1];
$this->errorCode = Crypt_GPG::ERROR_FILE_PERMISSIONS;
return;
}
// GnuPG 2.1: It should return MISSING_PASSPHRASE, but it does not
// we have to detect it this way. This happens e.g. on private key import
$pattern = '/key ([0-9A-F]+).* (Bad|No) passphrase/';
if (preg_match($pattern, $line, $matches) === 1) {
$keyId = $matches[1];
// @TODO: Get user name/email
if (empty($this->data['BadPassphrases'][$keyId])) {
$this->data['BadPassphrases'][$keyId] = $keyId;
}
if ($matches[2] == 'Bad') {
$this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
} else {
$this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
if (empty($this->data['MissingPassphrases'][$keyId])) {
$this->data['MissingPassphrases'][$keyId] = $keyId;
}
}
return;
}
if ($this->operation == 'gen-key') {
$pattern = '/:([0-9]+): invalid algorithm$/';
if (preg_match($pattern, $line, $matches) === 1) {
$this->errorCode = Crypt_GPG::ERROR_BAD_KEY_PARAMS;
$this->data['LineNumber'] = intval($matches[1]);
}
}
}
/**
* On error throws exception
*
* @param int $exitcode GPG process exit code
*
* @return void
* @throws Crypt_GPG_Exception
*/
public function throwException($exitcode = 0)
{
if ($exitcode > 0 && $this->errorCode === Crypt_GPG::ERROR_NONE) {
$this->errorCode = $this->setErrorCode($exitcode);
}
if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
return;
}
$code = $this->errorCode;
$note = "Please use the 'debug' option when creating the Crypt_GPG " .
"object, and file a bug report at " . Crypt_GPG::BUG_URI;
switch ($this->operation) {
case 'version':
throw new Crypt_GPG_Exception(
'Unknown error getting GnuPG version information. ' . $note,
$code
);
case 'list-secret-keys':
case 'list-public-keys':
case 'list-keys':
switch ($code) {
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
// ignore not found key errors
break;
case Crypt_GPG::ERROR_FILE_PERMISSIONS:
if (!empty($this->data['ErrorFilename'])) {
throw new Crypt_GPG_FileException(
sprintf(
'Error reading GnuPG data file \'%s\'. Check to make ' .
'sure it is readable by the current user.',
$this->data['ErrorFilename']
),
$code,
$this->data['ErrorFilename']
);
}
throw new Crypt_GPG_FileException(
'Error reading GnuPG data file. Check to make sure that ' .
'GnuPG data files are readable by the current user.',
$code
);
default:
throw new Crypt_GPG_Exception(
'Unknown error getting keys. ' . $note, $code
);
}
break;
case 'delete-key':
case 'delete-secret-key':
switch ($code) {
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
throw new Crypt_GPG_KeyNotFoundException(
'Key not found: ' . $this->operationArg,
$code,
$this->operationArg
);
case Crypt_GPG::ERROR_DELETE_PRIVATE_KEY:
throw new Crypt_GPG_DeletePrivateKeyException(
'Private key must be deleted before public key can be ' .
'deleted.',
$code,
$this->operationArg
);
default:
throw new Crypt_GPG_Exception(
'Unknown error deleting key. ' . $note, $code
);
}
break; // @phpstan-ignore-line
case 'import':
switch ($code) {
case Crypt_GPG::ERROR_NO_DATA:
throw new Crypt_GPG_NoDataException(
'No valid GPG key data found.', $code
);
case Crypt_GPG::ERROR_BAD_PASSPHRASE:
case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
throw $this->badPassException($code, 'Cannot import private key.');
default:
throw new Crypt_GPG_Exception(
'Unknown error importing GPG key. ' . $note, $code
);
}
break; // @phpstan-ignore-line
case 'export':
case 'export-secret-keys':
switch ($code) {
case Crypt_GPG::ERROR_BAD_PASSPHRASE:
case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
throw $this->badPassException($code, 'Cannot export private key.');
default:
throw new Crypt_GPG_Exception(
'Unknown error exporting a key. ' . $note, $code
);
}
break; // @phpstan-ignore-line
case 'encrypt':
case 'sign':
case 'clearsign':
case 'detach-sign':
switch ($code) {
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
throw new Crypt_GPG_KeyNotFoundException(
'Cannot sign data. Private key not found. Import the '.
'private key before trying to sign data.',
$code,
!empty($this->data['ErrorKeyId']) ? $this->data['ErrorKeyId'] : null
);
case Crypt_GPG::ERROR_BAD_PASSPHRASE:
throw new Crypt_GPG_BadPassphraseException(
'Cannot sign data. Incorrect passphrase provided.', $code
);
case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
throw new Crypt_GPG_BadPassphraseException(
'Cannot sign data. No passphrase provided.', $code
);
default:
throw new Crypt_GPG_Exception(
"Unknown error {$this->operation}ing data. $note", $code
);
}
break; // @phpstan-ignore-line
case 'verify':
switch ($code) {
case Crypt_GPG::ERROR_BAD_SIGNATURE:
// ignore bad signature errors
break;
case Crypt_GPG::ERROR_NO_DATA:
throw new Crypt_GPG_NoDataException(
'No valid signature data found.', $code
);
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
throw new Crypt_GPG_KeyNotFoundException(
'Public key required for data verification not in keyring.',
$code,
!empty($this->data['ErrorKeyId']) ? $this->data['ErrorKeyId'] : null
);
default:
throw new Crypt_GPG_Exception(
'Unknown error validating signature details. ' . $note,
$code
);
}
break;
case 'decrypt':
switch ($code) {
case Crypt_GPG::ERROR_BAD_SIGNATURE:
// ignore bad signature errors
break;
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
if (!empty($this->data['MissingKeys'])) {
$keyId = reset($this->data['MissingKeys']);
} else {
$keyId = '';
}
throw new Crypt_GPG_KeyNotFoundException(
'Cannot decrypt data. No suitable private key is in the ' .
'keyring. Import a suitable private key before trying to ' .
'decrypt this data.',
$code,
$keyId
);
case Crypt_GPG::ERROR_BAD_PASSPHRASE:
case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
throw $this->badPassException($code, 'Cannot decrypt data.');
case Crypt_GPG::ERROR_NO_DATA:
throw new Crypt_GPG_NoDataException(
'Cannot decrypt data. No PGP encrypted data was found in '.
'the provided data.',
$code
);
default:
throw new Crypt_GPG_Exception(
'Unknown error decrypting data.', $code
);
}
break;
case 'gen-key':
switch ($code) {
case Crypt_GPG::ERROR_BAD_KEY_PARAMS:
throw new Crypt_GPG_InvalidKeyParamsException(
'Invalid key algorithm specified.', $code
);
default:
throw new Crypt_GPG_Exception(
'Unknown error generating key-pair. ' . $note, $code
);
}
}
}
/**
* Check exit code of the GPG operation.
*
* @param int $exitcode GPG process exit code
*
* @return int Internal error code
*/
protected function setErrorCode($exitcode)
{
if ($this->needPassphrase > 0) {
return Crypt_GPG::ERROR_MISSING_PASSPHRASE;
}
if ($this->operation == 'import') {
return Crypt_GPG::ERROR_NONE;
}
if ($this->operation == 'decrypt' && !empty($this->data['DecryptionOkay'])) {
if (!empty($this->data['IgnoreVerifyErrors'])) {
return Crypt_GPG::ERROR_NONE;
}
if (!empty($this->data['MissingKeys'])) {
return Crypt_GPG::ERROR_KEY_NOT_FOUND;
}
}
return Crypt_GPG::ERROR_UNKNOWN;
}
/**
* Get data from the last process execution.
*
* @param string $name Data element name:
* - SigCreated: The last SIG_CREATED status.
* - KeyConsidered: The last KEY_CONSIDERED status identifier.
* - KeyCreated: The KEY_CREATED status (for specified Handle).
* - Signatures: Signatures data from verification process.
* - LineNumber: Number of the gen-key error line.
* - Import: Result of IMPORT_OK/IMPORT_RES
* - Warnings: An array of all collected GnuPG warnings
*
* @return mixed
*/
public function getData($name)
{
return isset($this->data[$name]) ? $this->data[$name] : null;
}
/**
* Set data for the process execution.
*
* @param string $name Data element name:
* - Handle: The unique key handle used by this handler
* The key handle is used to track GPG status output
* for a particular key on --gen-key command before
* the key has its own identifier.
* - IgnoreVerifyErrors: Do not throw exceptions
* when signature verification failes because
* of a missing public key.
* @param mixed $value Data element value
*
* @return void
*/
public function setData($name, $value)
{
switch ($name) {
case 'Handle':
$this->data[$name] = strval($value);
break;
case 'IgnoreVerifyErrors':
$this->data[$name] = (bool) $value;
break;
}
}
/**
* Create Crypt_GPG_BadPassphraseException from operation data.
*
* @param int $code Error code
* @param string $message Error message
*
* @return Crypt_GPG_BadPassphraseException
*/
protected function badPassException($code, $message)
{
$badPassphrases = array_diff_key(
isset($this->data['BadPassphrases']) ? $this->data['BadPassphrases'] : [],
isset($this->data['MissingPassphrases']) ? $this->data['MissingPassphrases'] : []
);
$missingPassphrases = array_intersect_key(
isset($this->data['BadPassphrases']) ? $this->data['BadPassphrases'] : [],
isset($this->data['MissingPassphrases']) ? $this->data['MissingPassphrases'] : []
);
if (count($badPassphrases) > 0) {
$message .= ' Incorrect passphrase provided for keys: "' .
implode('", "', $badPassphrases) . '".';
}
if (count($missingPassphrases) > 0) {
$message .= ' No passphrase provided for keys: "' .
implode('", "', $missingPassphrases) . '".';
}
return new Crypt_GPG_BadPassphraseException(
$message,
$code,
$badPassphrases,
$missingPassphrases
);
}
/**
* Get registered passphrase for specified key.
*
* @param string $key Key identifier
*
* @return string Passphrase
*/
protected function getPin($key)
{
$passphrase = '';
$keyIdLength = mb_strlen($key, '8bit');
if ($keyIdLength && !empty($_ENV['PINENTRY_USER_DATA'])) {
$passphrases = json_decode($_ENV['PINENTRY_USER_DATA'], true);
foreach ($passphrases as $_keyId => $pass) {
$keyId = $key;
$_keyIdLength = mb_strlen($_keyId, '8bit');
// Get last X characters of key identifier to compare
if ($keyIdLength < $_keyIdLength) {
$_keyId = mb_substr($_keyId, -$keyIdLength, null, '8bit');
} else if ($keyIdLength > $_keyIdLength) {
$keyId = mb_substr($keyId, -$_keyIdLength, null, '8bit');
}
if ($_keyId === $keyId) {
$passphrase = $pass;
break;
}
}
}
return $passphrase;
}
}