3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * Crypt_GPG is a package to use GPG from PHP
8 * This file contains an object that handles GPG's status output for the
15 * This library is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU Lesser General Public License as
17 * published by the Free Software Foundation; either version 2.1 of the
18 * License, or (at your option) any later version.
20 * This library is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * Lesser General Public License for more details.
25 * You should have received a copy of the GNU Lesser General Public
26 * License along with this library; if not, write to the Free Software
27 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 * @category Encryption
31 * @author Michael Gauthier <mike@silverorange.com>
32 * @copyright 2008-2009 silverorange
33 * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
34 * @version CVS: $Id: DecryptStatusHandler.php 302814 2010-08-26 15:43:07Z gauthierm $
35 * @link http://pear.php.net/package/Crypt_GPG
36 * @link http://www.gnupg.org/
40 * Crypt_GPG base class
42 require_once 'Crypt/GPG.php';
45 * GPG exception classes
47 require_once 'Crypt/GPG/Exceptions.php';
51 * Status line handler for the decrypt operation
53 * This class is used internally by Crypt_GPG and does not need be used
54 * directly. See the {@link Crypt_GPG} class for end-user API.
56 * This class is responsible for sending the passphrase commands when required
57 * by the {@link Crypt_GPG::decrypt()} method. See <b>doc/DETAILS</b> in the
58 * {@link http://www.gnupg.org/download/ GPG distribution} for detailed
59 * information on GPG's status output for the decrypt operation.
61 * This class is also responsible for parsing error status and throwing a
62 * meaningful exception in the event that decryption fails.
64 * @category Encryption
66 * @author Michael Gauthier <mike@silverorange.com>
67 * @copyright 2008 silverorange
68 * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
69 * @link http://pear.php.net/package/Crypt_GPG
70 * @link http://www.gnupg.org/
72 class Crypt_GPG_DecryptStatusHandler
74 // {{{ protected properties
77 * Keys used to decrypt
79 * The array is of the form:
83 * 'fingerprint' => $fingerprint,
84 * 'passphrase' => $passphrase
91 protected $keys = array();
94 * Engine used to which passphrases are passed
96 * @var Crypt_GPG_Engine
98 protected $engine = null;
101 * The id of the current sub-key used for decryption
105 protected $currentSubKey = '';
108 * Whether or not decryption succeeded
110 * If the message is only signed (compressed) and not encrypted, this is
111 * always true. If the message is encrypted, this flag is set to false
112 * until we know the decryption succeeded.
116 protected $decryptionOkay = true;
119 * Whether or not there was no data for decryption
123 protected $noData = false;
126 * Keys for which the passhprase is missing
128 * This contains primary user ids indexed by sub-key id and is used to
129 * create helpful exception messages.
133 protected $missingPassphrases = array();
136 * Keys for which the passhprase is incorrect
138 * This contains primary user ids indexed by sub-key id and is used to
139 * create helpful exception messages.
143 protected $badPassphrases = array();
146 * Keys that can be used to decrypt the data but are missing from the
149 * This is an array with both the key and value being the sub-key id of
154 protected $missingKeys = array();
160 * Creates a new decryption status handler
162 * @param Crypt_GPG_Engine $engine the GPG engine to which passphrases are
164 * @param array $keys the decryption keys to use.
166 public function __construct(Crypt_GPG_Engine $engine, array $keys)
168 $this->engine = $engine;
176 * Handles a status line
178 * @param string $line the status line to handle.
182 public function handle($line)
184 $tokens = explode(' ', $line);
185 switch ($tokens[0]) {
187 // Now we know the message is encrypted. Set flag to check if
188 // decryption succeeded.
189 $this->decryptionOkay = false;
191 // this is the new key message
192 $this->currentSubKeyId = $tokens[1];
195 case 'NEED_PASSPHRASE':
196 // send passphrase to the GPG engine
197 $subKeyId = $tokens[1];
198 if (array_key_exists($subKeyId, $this->keys)) {
199 $passphrase = $this->keys[$subKeyId]['passphrase'];
200 $this->engine->sendCommand($passphrase);
202 $this->engine->sendCommand('');
207 // remember the user id for pretty exception messages
208 $this->badPassphrases[$tokens[1]]
209 = implode(' ', array_splice($tokens, 2));
213 case 'GOOD_PASSPHRASE':
214 // if we got a good passphrase, remove the key from the list of
216 unset($this->badPassphrases[$this->currentSubKeyId]);
219 case 'MISSING_PASSPHRASE':
220 $this->missingPassphrases[$this->currentSubKeyId]
221 = $this->currentSubKeyId;
226 // note: this message is also received if there are multiple
227 // recipients and a previous key had a correct passphrase.
228 $this->missingKeys[$tokens[1]] = $tokens[1];
232 $this->noData = true;
235 case 'DECRYPTION_OKAY':
236 // If the message is encrypted, this is the all-clear signal.
237 $this->decryptionOkay = true;
243 // {{{ throwException()
246 * Takes the final status of the decrypt operation and throws an
247 * appropriate exception
249 * If decryption was successful, no exception is thrown.
253 * @throws Crypt_GPG_KeyNotFoundException if the private key needed to
254 * decrypt the data is not in the user's keyring.
256 * @throws Crypt_GPG_NoDataException if specified data does not contain
257 * GPG encrypted data.
259 * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
260 * incorrect or if a required passphrase is not specified. See
261 * {@link Crypt_GPG::addDecryptKey()}.
263 * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
264 * Use the <i>debug</i> option and file a bug report if these
267 public function throwException()
269 $code = Crypt_GPG::ERROR_NONE;
271 if (!$this->decryptionOkay) {
272 if (count($this->badPassphrases) > 0) {
273 $code = Crypt_GPG::ERROR_BAD_PASSPHRASE;
274 } elseif (count($this->missingKeys) > 0) {
275 $code = Crypt_GPG::ERROR_KEY_NOT_FOUND;
277 $code = Crypt_GPG::ERROR_UNKNOWN;
279 } elseif ($this->noData) {
280 $code = Crypt_GPG::ERROR_NO_DATA;
284 case Crypt_GPG::ERROR_NONE:
287 case Crypt_GPG::ERROR_KEY_NOT_FOUND:
288 if (count($this->missingKeys) > 0) {
289 $keyId = reset($this->missingKeys);
293 throw new Crypt_GPG_KeyNotFoundException(
294 'Cannot decrypt data. No suitable private key is in the ' .
295 'keyring. Import a suitable private key before trying to ' .
296 'decrypt this data.', $code, $keyId);
298 case Crypt_GPG::ERROR_BAD_PASSPHRASE:
299 $badPassphrases = array_diff_key(
300 $this->badPassphrases,
301 $this->missingPassphrases
304 $missingPassphrases = array_intersect_key(
305 $this->badPassphrases,
306 $this->missingPassphrases
309 $message = 'Cannot decrypt data.';
310 if (count($badPassphrases) > 0) {
311 $message = ' Incorrect passphrase provided for keys: "' .
312 implode('", "', $badPassphrases) . '".';
314 if (count($missingPassphrases) > 0) {
315 $message = ' No passphrase provided for keys: "' .
316 implode('", "', $badPassphrases) . '".';
319 throw new Crypt_GPG_BadPassphraseException($message, $code,
320 $badPassphrases, $missingPassphrases);
322 case Crypt_GPG::ERROR_NO_DATA:
323 throw new Crypt_GPG_NoDataException(
324 'Cannot decrypt data. No PGP encrypted data was found in '.
325 'the provided data.', $code);
328 throw new Crypt_GPG_Exception(
329 'Unknown error decrypting data.', $code);