diff vendor/pear/crypt_gpg/Crypt/GPG/ProcessHandler.php @ 0:1e000243b222

vanilla 1.3.3 distro, I hope
author Charlie Root
date Thu, 04 Jan 2018 15:50:29 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/pear/crypt_gpg/Crypt/GPG/ProcessHandler.php	Thu Jan 04 15:50:29 2018 -0500
@@ -0,0 +1,928 @@
+<?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.
+ *
+ * PHP version 5
+ *
+ * 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';
+
+// {{{ class Crypt_GPG_ProcessHandler
+
+/**
+ * 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
+{
+    // {{{ protected class properties
+
+    /**
+     * Engine used to control the GPG subprocess
+     *
+     * @var Crypt_GPG_Engine
+     */
+    protected $engine;
+
+    /**
+     * The error code of the current operation
+     *
+     * @var integer
+     */
+    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 integer
+     */
+    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 = array();
+
+    /**
+     * 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;
+
+    // }}}
+    // {{{ __construct()
+
+    /**
+     * Creates a new instance
+     *
+     * @param Crypt_GPG_Engine $engine Engine object
+     */
+    public function __construct($engine)
+    {
+        $this->engine = $engine;
+    }
+
+    // }}}
+    // {{{ setOperation()
+
+    /**
+     * 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;
+    }
+
+    // }}}
+    // {{{ handleStatus()
+
+    /**
+     * 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'] = array($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->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 (strcspn($keyId, '0')) {
+                $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 = array();
+            }
+
+            // @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;
+        }
+    }
+
+    // }}}
+    // {{{ handleError()
+
+    /**
+     * 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 ($this->errorCode === Crypt_GPG::ERROR_NONE) {
+            $pattern = '/no valid OpenPGP data found/';
+            if (preg_match($pattern, $line) === 1) {
+                $this->errorCode = Crypt_GPG::ERROR_NO_DATA;
+            }
+        }
+
+        if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
+            $pattern = '/No secret key|secret key not available/';
+            if (preg_match($pattern, $line) === 1) {
+                $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
+            }
+        }
+
+        if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
+            $pattern = '/No public key|public key not found/';
+            if (preg_match($pattern, $line) === 1) {
+                $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
+            }
+        }
+
+        if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
+            $matches = array();
+            $pattern = '/can\'t (?:access|open) `(.*?)\'/';
+            if (preg_match($pattern, $line, $matches) === 1) {
+                $this->data['ErrorFilename'] = $matches[1];
+                $this->errorCode = Crypt_GPG::ERROR_FILE_PERMISSIONS;
+            }
+        }
+
+        // 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
+        if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
+            $matches = array();
+            $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;
+                    }
+                }
+            }
+        }
+
+        if ($this->errorCode === Crypt_GPG::ERROR_NONE && $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]);
+            }
+        }
+    }
+
+    // }}}
+    // {{{ throwException()
+
+    /**
+     * 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;
+
+        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;
+
+        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;
+
+        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;
+
+        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
+                );
+            }
+        }
+    }
+
+    // }}}
+    // {{{ throwException()
+
+    /**
+     * 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;
+    }
+
+    // }}}
+    // {{{ getData()
+
+    /**
+     * 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
+     *
+     * @return mixed
+     */
+    public function getData($name)
+    {
+        return isset($this->data[$name]) ? $this->data[$name] : null;
+    }
+
+    // }}}
+    // {{{ setData()
+
+    /**
+     * 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;
+        }
+    }
+
+    // }}}
+    // {{{ setData()
+
+    /**
+     * 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'] : array(),
+            isset($this->data['MissingPassphrases']) ? $this->data['MissingPassphrases'] : array()
+        );
+
+        $missingPassphrases = array_intersect_key(
+            isset($this->data['BadPassphrases']) ? $this->data['BadPassphrases'] : array(),
+            isset($this->data['MissingPassphrases']) ? $this->data['MissingPassphrases'] : array()
+        );
+
+        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
+        );
+    }
+
+    // }}}
+    // {{{ getPin()
+
+    /**
+     * 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;
+    }
+
+    // }}}
+}
+
+// }}}
+
+?>