]> git.donarmstrong.com Git - roundcube.git/blob - plugins/enigma/lib/Crypt/GPG/DecryptStatusHandler.php
Imported Upstream version 0.6+dfsg
[roundcube.git] / plugins / enigma / lib / Crypt / GPG / DecryptStatusHandler.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6  * Crypt_GPG is a package to use GPG from PHP
7  *
8  * This file contains an object that handles GPG's status output for the
9  * decrypt operation.
10  *
11  * PHP version 5
12  *
13  * LICENSE:
14  *
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.
19  *
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.
24  *
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
28  *
29  * @category  Encryption
30  * @package   Crypt_GPG
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/
37  */
38
39 /**
40  * Crypt_GPG base class
41  */
42 require_once 'Crypt/GPG.php';
43
44 /**
45  * GPG exception classes
46  */
47 require_once 'Crypt/GPG/Exceptions.php';
48
49
50 /**
51  * Status line handler for the decrypt operation
52  *
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.
55  *
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.
60  *
61  * This class is also responsible for parsing error status and throwing a
62  * meaningful exception in the event that decryption fails.
63  *
64  * @category  Encryption
65  * @package   Crypt_GPG
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/
71  */
72 class Crypt_GPG_DecryptStatusHandler
73 {
74     // {{{ protected properties
75
76     /**
77      * Keys used to decrypt
78      *
79      * The array is of the form:
80      * <code>
81      * array(
82      *   $key_id => array(
83      *     'fingerprint' => $fingerprint,
84      *     'passphrase'  => $passphrase
85      *   )
86      * );
87      * </code>
88      *
89      * @var array
90      */
91     protected $keys = array();
92
93     /**
94      * Engine used to which passphrases are passed
95      *
96      * @var Crypt_GPG_Engine
97      */
98     protected $engine = null;
99
100     /**
101      * The id of the current sub-key used for decryption
102      *
103      * @var string
104      */
105     protected $currentSubKey = '';
106
107     /**
108      * Whether or not decryption succeeded
109      *
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.
113      *
114      * @var boolean
115      */
116     protected $decryptionOkay = true;
117
118     /**
119      * Whether or not there was no data for decryption
120      *
121      * @var boolean
122      */
123     protected $noData = false;
124
125     /**
126      * Keys for which the passhprase is missing
127      *
128      * This contains primary user ids indexed by sub-key id and is used to
129      * create helpful exception messages.
130      *
131      * @var array
132      */
133     protected $missingPassphrases = array();
134
135     /**
136      * Keys for which the passhprase is incorrect
137      *
138      * This contains primary user ids indexed by sub-key id and is used to
139      * create helpful exception messages.
140      *
141      * @var array
142      */
143     protected $badPassphrases = array();
144
145     /**
146      * Keys that can be used to decrypt the data but are missing from the
147      * keychain
148      *
149      * This is an array with both the key and value being the sub-key id of
150      * the missing keys.
151      *
152      * @var array
153      */
154     protected $missingKeys = array();
155
156     // }}}
157     // {{{ __construct()
158
159     /**
160      * Creates a new decryption status handler
161      *
162      * @param Crypt_GPG_Engine $engine the GPG engine to which passphrases are
163      *                                 passed.
164      * @param array            $keys   the decryption keys to use.
165      */
166     public function __construct(Crypt_GPG_Engine $engine, array $keys)
167     {
168         $this->engine = $engine;
169         $this->keys   = $keys;
170     }
171
172     // }}}
173     // {{{ handle()
174
175     /**
176      * Handles a status line
177      *
178      * @param string $line the status line to handle.
179      *
180      * @return void
181      */
182     public function handle($line)
183     {
184         $tokens = explode(' ', $line);
185         switch ($tokens[0]) {
186         case 'ENC_TO':
187             // Now we know the message is encrypted. Set flag to check if
188             // decryption succeeded.
189             $this->decryptionOkay = false;
190
191             // this is the new key message
192             $this->currentSubKeyId = $tokens[1];
193             break;
194
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);
201             } else {
202                 $this->engine->sendCommand('');
203             }
204             break;
205
206         case 'USERID_HINT':
207             // remember the user id for pretty exception messages
208             $this->badPassphrases[$tokens[1]]
209                 = implode(' ', array_splice($tokens, 2));
210
211             break;
212
213         case 'GOOD_PASSPHRASE':
214             // if we got a good passphrase, remove the key from the list of
215             // bad passphrases.
216             unset($this->badPassphrases[$this->currentSubKeyId]);
217             break;
218
219         case 'MISSING_PASSPHRASE':
220             $this->missingPassphrases[$this->currentSubKeyId]
221                 = $this->currentSubKeyId;
222
223             break;
224
225         case 'NO_SECKEY':
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];
229             break;
230
231         case 'NODATA':
232             $this->noData = true;
233             break;
234
235         case 'DECRYPTION_OKAY':
236             // If the message is encrypted, this is the all-clear signal.
237             $this->decryptionOkay = true;
238             break;
239         }
240     }
241
242     // }}}
243     // {{{ throwException()
244
245     /**
246      * Takes the final status of the decrypt operation and throws an
247      * appropriate exception
248      *
249      * If decryption was successful, no exception is thrown.
250      *
251      * @return void
252      *
253      * @throws Crypt_GPG_KeyNotFoundException if the private key needed to
254      *         decrypt the data is not in the user's keyring.
255      *
256      * @throws Crypt_GPG_NoDataException if specified data does not contain
257      *         GPG encrypted data.
258      *
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()}.
262      *
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
265      *         exceptions occur.
266      */
267     public function throwException()
268     {
269         $code = Crypt_GPG::ERROR_NONE;
270
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;
276             } else {
277                 $code = Crypt_GPG::ERROR_UNKNOWN;
278             }
279         } elseif ($this->noData) {
280             $code = Crypt_GPG::ERROR_NO_DATA;
281         }
282
283         switch ($code) {
284         case Crypt_GPG::ERROR_NONE:
285             break;
286
287         case Crypt_GPG::ERROR_KEY_NOT_FOUND:
288             if (count($this->missingKeys) > 0) {
289                 $keyId = reset($this->missingKeys);
290             } else {
291                 $keyId = '';
292             }
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);
297
298         case Crypt_GPG::ERROR_BAD_PASSPHRASE:
299             $badPassphrases = array_diff_key(
300                 $this->badPassphrases,
301                 $this->missingPassphrases
302             );
303
304             $missingPassphrases = array_intersect_key(
305                 $this->badPassphrases,
306                 $this->missingPassphrases
307             );
308
309             $message =  'Cannot decrypt data.';
310             if (count($badPassphrases) > 0) {
311                 $message = ' Incorrect passphrase provided for keys: "' .
312                     implode('", "', $badPassphrases) . '".';
313             }
314             if (count($missingPassphrases) > 0) {
315                 $message = ' No passphrase provided for keys: "' .
316                     implode('", "', $badPassphrases) . '".';
317             }
318
319             throw new Crypt_GPG_BadPassphraseException($message, $code,
320                 $badPassphrases, $missingPassphrases);
321
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);
326
327         default:
328             throw new Crypt_GPG_Exception(
329                 'Unknown error decrypting data.', $code);
330         }
331     }
332
333     // }}}
334 }
335
336 ?>