0
|
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 handler for status and error pipes of GPG process.
|
|
9 *
|
|
10 * PHP version 5
|
|
11 *
|
|
12 * LICENSE:
|
|
13 *
|
|
14 * This library is free software; you can redistribute it and/or modify
|
|
15 * it under the terms of the GNU Lesser General Public License as
|
|
16 * published by the Free Software Foundation; either version 2.1 of the
|
|
17 * License, or (at your option) any later version.
|
|
18 *
|
|
19 * This library is distributed in the hope that it will be useful,
|
|
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
22 * Lesser General Public License for more details.
|
|
23 *
|
|
24 * You should have received a copy of the GNU Lesser General Public
|
|
25 * License along with this library; if not, see
|
|
26 * <http://www.gnu.org/licenses/>
|
|
27 *
|
|
28 * @category Encryption
|
|
29 * @package Crypt_GPG
|
|
30 * @author Nathan Fredrickson <nathan@silverorange.com>
|
|
31 * @author Michael Gauthier <mike@silverorange.com>
|
|
32 * @author Aleksander Machniak <alec@alec.pl>
|
|
33 * @copyright 2005-2013 silverorange
|
|
34 * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
|
|
35 * @link http://pear.php.net/package/Crypt_GPG
|
|
36 * @link http://www.gnupg.org/
|
|
37 */
|
|
38
|
|
39 /**
|
|
40 * GPG exception classes.
|
|
41 */
|
|
42 require_once 'Crypt/GPG/Exceptions.php';
|
|
43
|
|
44 /**
|
|
45 * Signature object class definition
|
|
46 */
|
|
47 require_once 'Crypt/GPG/Signature.php';
|
|
48
|
|
49 // {{{ class Crypt_GPG_ProcessHandler
|
|
50
|
|
51 /**
|
|
52 * Status/Error handler for GPG process pipes.
|
|
53 *
|
|
54 * This class is used internally by Crypt_GPG_Engine and does not need to be used
|
|
55 * directly. See the {@link Crypt_GPG} class for end-user API.
|
|
56 *
|
|
57 * @category Encryption
|
|
58 * @package Crypt_GPG
|
|
59 * @author Nathan Fredrickson <nathan@silverorange.com>
|
|
60 * @author Michael Gauthier <mike@silverorange.com>
|
|
61 * @author Aleksander Machniak <alec@alec.pl>
|
|
62 * @copyright 2005-2013 silverorange
|
|
63 * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
|
|
64 * @link http://pear.php.net/package/Crypt_GPG
|
|
65 * @link http://www.gnupg.org/
|
|
66 */
|
|
67 class Crypt_GPG_ProcessHandler
|
|
68 {
|
|
69 // {{{ protected class properties
|
|
70
|
|
71 /**
|
|
72 * Engine used to control the GPG subprocess
|
|
73 *
|
|
74 * @var Crypt_GPG_Engine
|
|
75 */
|
|
76 protected $engine;
|
|
77
|
|
78 /**
|
|
79 * The error code of the current operation
|
|
80 *
|
|
81 * @var integer
|
|
82 */
|
|
83 protected $errorCode = Crypt_GPG::ERROR_NONE;
|
|
84
|
|
85 /**
|
|
86 * The number of currently needed passphrases
|
|
87 *
|
|
88 * If this is not zero when the GPG command is completed, the error code is
|
|
89 * set to {@link Crypt_GPG::ERROR_MISSING_PASSPHRASE}.
|
|
90 *
|
|
91 * @var integer
|
|
92 */
|
|
93 protected $needPassphrase = 0;
|
|
94
|
|
95 /**
|
|
96 * Some data collected while processing the operation
|
|
97 * or set for the operation
|
|
98 *
|
|
99 * @var array
|
|
100 * @see self::setData()
|
|
101 * @see self::getData()
|
|
102 */
|
|
103 protected $data = array();
|
|
104
|
|
105 /**
|
|
106 * The name of the current operation
|
|
107 *
|
|
108 * @var string
|
|
109 * @see self::setOperation()
|
|
110 */
|
|
111 protected $operation = null;
|
|
112
|
|
113 /**
|
|
114 * The value of the argument of current operation
|
|
115 *
|
|
116 * @var string
|
|
117 * @see self::setOperation()
|
|
118 */
|
|
119 protected $operationArg = null;
|
|
120
|
|
121 // }}}
|
|
122 // {{{ __construct()
|
|
123
|
|
124 /**
|
|
125 * Creates a new instance
|
|
126 *
|
|
127 * @param Crypt_GPG_Engine $engine Engine object
|
|
128 */
|
|
129 public function __construct($engine)
|
|
130 {
|
|
131 $this->engine = $engine;
|
|
132 }
|
|
133
|
|
134 // }}}
|
|
135 // {{{ setOperation()
|
|
136
|
|
137 /**
|
|
138 * Sets the operation that is being performed by the engine.
|
|
139 *
|
|
140 * @param string $operation The GPG operation to perform.
|
|
141 *
|
|
142 * @return void
|
|
143 */
|
|
144 public function setOperation($operation)
|
|
145 {
|
|
146 $op = null;
|
|
147 $opArg = null;
|
|
148
|
|
149 // Regexp matching all GPG "operational" arguments
|
|
150 $regexp = '/--('
|
|
151 . 'version|import|list-public-keys|list-secret-keys'
|
|
152 . '|list-keys|delete-key|delete-secret-key|encrypt|sign|clearsign'
|
|
153 . '|detach-sign|decrypt|verify|export-secret-keys|export|gen-key'
|
|
154 . ')/';
|
|
155
|
|
156 if (strpos($operation, ' ') === false) {
|
|
157 $op = trim($operation, '- ');
|
|
158 } else if (preg_match($regexp, $operation, $matches, PREG_OFFSET_CAPTURE)) {
|
|
159 $op = trim($matches[0][0], '-');
|
|
160 $op_len = $matches[0][1] + mb_strlen($op, '8bit') + 3;
|
|
161 $command = mb_substr($operation, $op_len, null, '8bit');
|
|
162
|
|
163 // we really need the argument if it is a key ID/fingerprint or email
|
|
164 // address se we can use simplified regexp to "revert escapeshellarg()"
|
|
165 if (preg_match('/^[\'"]([a-zA-Z0-9:@._-]+)[\'"]/', $command, $matches)) {
|
|
166 $opArg = $matches[1];
|
|
167 }
|
|
168 }
|
|
169
|
|
170 $this->operation = $op;
|
|
171 $this->operationArg = $opArg;
|
|
172 }
|
|
173
|
|
174 // }}}
|
|
175 // {{{ handleStatus()
|
|
176
|
|
177 /**
|
|
178 * Handles error values in the status output from GPG
|
|
179 *
|
|
180 * This method is responsible for setting the
|
|
181 * {@link self::$errorCode}. See <b>doc/DETAILS</b> in the
|
|
182 * {@link http://www.gnupg.org/download/ GPG distribution} for detailed
|
|
183 * information on GPG's status output.
|
|
184 *
|
|
185 * @param string $line the status line to handle.
|
|
186 *
|
|
187 * @return void
|
|
188 */
|
|
189 public function handleStatus($line)
|
|
190 {
|
|
191 $tokens = explode(' ', $line);
|
|
192 switch ($tokens[0]) {
|
|
193 case 'NODATA':
|
|
194 $this->errorCode = Crypt_GPG::ERROR_NO_DATA;
|
|
195 break;
|
|
196
|
|
197 case 'DECRYPTION_OKAY':
|
|
198 // If the message is encrypted, this is the all-clear signal.
|
|
199 $this->data['DecryptionOkay'] = true;
|
|
200 $this->errorCode = Crypt_GPG::ERROR_NONE;
|
|
201 break;
|
|
202
|
|
203 case 'DELETE_PROBLEM':
|
|
204 if ($tokens[1] == '1') {
|
|
205 $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
|
|
206 break;
|
|
207 } elseif ($tokens[1] == '2') {
|
|
208 $this->errorCode = Crypt_GPG::ERROR_DELETE_PRIVATE_KEY;
|
|
209 break;
|
|
210 }
|
|
211 break;
|
|
212
|
|
213 case 'IMPORT_OK':
|
|
214 $this->data['Import']['fingerprint'] = $tokens[2];
|
|
215
|
|
216 if (empty($this->data['Import']['fingerprints'])) {
|
|
217 $this->data['Import']['fingerprints'] = array($tokens[2]);
|
|
218 } else if (!in_array($tokens[2], $this->data['Import']['fingerprints'])) {
|
|
219 $this->data['Import']['fingerprints'][] = $tokens[2];
|
|
220 }
|
|
221
|
|
222 break;
|
|
223
|
|
224 case 'IMPORT_RES':
|
|
225 $this->data['Import']['public_imported'] = intval($tokens[3]);
|
|
226 $this->data['Import']['public_unchanged'] = intval($tokens[5]);
|
|
227 $this->data['Import']['private_imported'] = intval($tokens[11]);
|
|
228 $this->data['Import']['private_unchanged'] = intval($tokens[12]);
|
|
229 break;
|
|
230
|
|
231 case 'NO_PUBKEY':
|
|
232 case 'NO_SECKEY':
|
|
233 $this->data['ErrorKeyId'] = $tokens[1];
|
|
234
|
|
235 if ($this->errorCode != Crypt_GPG::ERROR_MISSING_PASSPHRASE
|
|
236 && $this->errorCode != Crypt_GPG::ERROR_BAD_PASSPHRASE
|
|
237 ) {
|
|
238 $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
|
|
239 }
|
|
240
|
|
241 // note: this message is also received if there are multiple
|
|
242 // recipients and a previous key had a correct passphrase.
|
|
243 $this->data['MissingKeys'][$tokens[1]] = $tokens[1];
|
|
244
|
|
245 // @FIXME: remove missing passphrase registered in ENC_TO handler
|
|
246 // This is for GnuPG 2.1
|
|
247 unset($this->data['MissingPassphrases'][$tokens[1]]);
|
|
248 break;
|
|
249
|
|
250 case 'KEY_CONSIDERED':
|
|
251 // In GnuPG 2.1.x exporting/importing a secret key requires passphrase
|
|
252 // However, no NEED_PASSPRASE is returned, https://bugs.gnupg.org/gnupg/issue2667
|
|
253 // So, handling KEY_CONSIDERED and GET_HIDDEN is needed.
|
|
254 if (!array_key_exists('KeyConsidered', $this->data)) {
|
|
255 $this->data['KeyConsidered'] = $tokens[1];
|
|
256 }
|
|
257 break;
|
|
258
|
|
259 case 'USERID_HINT':
|
|
260 // remember the user id for pretty exception messages
|
|
261 // GnuPG 2.1.15 gives me: "USERID_HINT 0000000000000000 [?]"
|
|
262 $keyId = $tokens[1];
|
|
263 if (strcspn($keyId, '0')) {
|
|
264 $username = implode(' ', array_splice($tokens, 2));
|
|
265 $this->data['BadPassphrases'][$keyId] = $username;
|
|
266 }
|
|
267 break;
|
|
268
|
|
269 case 'ENC_TO':
|
|
270 // Now we know the message is encrypted. Set flag to check if
|
|
271 // decryption succeeded.
|
|
272 $this->data['DecryptionOkay'] = false;
|
|
273
|
|
274 // this is the new key message
|
|
275 $this->data['CurrentSubKeyId'] = $keyId = $tokens[1];
|
|
276
|
|
277 // For some reason in GnuPG 2.1.11 I get only ENC_TO and no
|
|
278 // NEED_PASSPHRASE/MISSING_PASSPHRASE/USERID_HINT
|
|
279 // This is not needed for GnuPG 2.1.15
|
|
280 if (!empty($_ENV['PINENTRY_USER_DATA'])) {
|
|
281 $passphrases = json_decode($_ENV['PINENTRY_USER_DATA'], true);
|
|
282 } else {
|
|
283 $passphrases = array();
|
|
284 }
|
|
285
|
|
286 // @TODO: Get user name/email
|
|
287 $this->data['BadPassphrases'][$keyId] = $keyId;
|
|
288 if (empty($passphrases) || empty($passphrases[$keyId])) {
|
|
289 $this->data['MissingPassphrases'][$keyId] = $keyId;
|
|
290 }
|
|
291 break;
|
|
292
|
|
293 case 'GOOD_PASSPHRASE':
|
|
294 // if we got a good passphrase, remove the key from the list of
|
|
295 // bad passphrases.
|
|
296 if (isset($this->data['CurrentSubKeyId'])) {
|
|
297 unset($this->data['BadPassphrases'][$this->data['CurrentSubKeyId']]);
|
|
298 unset($this->data['MissingPassphrases'][$this->data['CurrentSubKeyId']]);
|
|
299 }
|
|
300
|
|
301 $this->needPassphrase--;
|
|
302 break;
|
|
303
|
|
304 case 'BAD_PASSPHRASE':
|
|
305 $this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
|
|
306 break;
|
|
307
|
|
308 case 'MISSING_PASSPHRASE':
|
|
309 if (isset($this->data['CurrentSubKeyId'])) {
|
|
310 $this->data['MissingPassphrases'][$this->data['CurrentSubKeyId']]
|
|
311 = $this->data['CurrentSubKeyId'];
|
|
312 }
|
|
313
|
|
314 $this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
|
|
315 break;
|
|
316
|
|
317 case 'GET_HIDDEN':
|
|
318 if ($tokens[1] == 'passphrase.enter' && isset($this->data['KeyConsidered'])) {
|
|
319 $tokens[1] = $this->data['KeyConsidered'];
|
|
320 } else {
|
|
321 break;
|
|
322 }
|
|
323 // no break
|
|
324
|
|
325 case 'NEED_PASSPHRASE':
|
|
326 $passphrase = $this->getPin($tokens[1]);
|
|
327
|
|
328 $this->engine->sendCommand($passphrase);
|
|
329
|
|
330 if ($passphrase === '') {
|
|
331 $this->needPassphrase++;
|
|
332 }
|
|
333 break;
|
|
334
|
|
335 case 'SIG_CREATED':
|
|
336 $this->data['SigCreated'] = $line;
|
|
337 break;
|
|
338
|
|
339 case 'SIG_ID':
|
|
340 // note: signature id comes before new signature line and may not
|
|
341 // exist for some signature types
|
|
342 $this->data['SignatureId'] = $tokens[1];
|
|
343 break;
|
|
344
|
|
345 case 'EXPSIG':
|
|
346 case 'EXPKEYSIG':
|
|
347 case 'REVKEYSIG':
|
|
348 case 'BADSIG':
|
|
349 case 'ERRSIG':
|
|
350 $this->errorCode = Crypt_GPG::ERROR_BAD_SIGNATURE;
|
|
351 // no break
|
|
352 case 'GOODSIG':
|
|
353 $signature = new Crypt_GPG_Signature();
|
|
354
|
|
355 // if there was a signature id, set it on the new signature
|
|
356 if (!empty($this->data['SignatureId'])) {
|
|
357 $signature->setId($this->data['SignatureId']);
|
|
358 $this->data['SignatureId'] = '';
|
|
359 }
|
|
360
|
|
361 // Detect whether fingerprint or key id was returned and set
|
|
362 // signature values appropriately. Key ids are strings of either
|
|
363 // 16 or 8 hexadecimal characters. Fingerprints are strings of 40
|
|
364 // hexadecimal characters. The key id is the last 16 characters of
|
|
365 // the key fingerprint.
|
|
366 if (mb_strlen($tokens[1], '8bit') > 16) {
|
|
367 $signature->setKeyFingerprint($tokens[1]);
|
|
368 $signature->setKeyId(mb_substr($tokens[1], -16, null, '8bit'));
|
|
369 } else {
|
|
370 $signature->setKeyId($tokens[1]);
|
|
371 }
|
|
372
|
|
373 // get user id string
|
|
374 if ($tokens[0] != 'ERRSIG') {
|
|
375 $string = implode(' ', array_splice($tokens, 2));
|
|
376 $string = rawurldecode($string);
|
|
377
|
|
378 $signature->setUserId(Crypt_GPG_UserId::parse($string));
|
|
379 }
|
|
380
|
|
381 $this->data['Signatures'][] = $signature;
|
|
382 break;
|
|
383
|
|
384 case 'VALIDSIG':
|
|
385 if (empty($this->data['Signatures'])) {
|
|
386 break;
|
|
387 }
|
|
388
|
|
389 $signature = end($this->data['Signatures']);
|
|
390
|
|
391 $signature->setValid(true);
|
|
392 $signature->setKeyFingerprint($tokens[1]);
|
|
393
|
|
394 if (strpos($tokens[3], 'T') === false) {
|
|
395 $signature->setCreationDate($tokens[3]);
|
|
396 } else {
|
|
397 $signature->setCreationDate(strtotime($tokens[3]));
|
|
398 }
|
|
399
|
|
400 if (array_key_exists(4, $tokens)) {
|
|
401 if (strpos($tokens[4], 'T') === false) {
|
|
402 $signature->setExpirationDate($tokens[4]);
|
|
403 } else {
|
|
404 $signature->setExpirationDate(strtotime($tokens[4]));
|
|
405 }
|
|
406 }
|
|
407
|
|
408 break;
|
|
409
|
|
410 case 'KEY_CREATED':
|
|
411 if (isset($this->data['Handle']) && $tokens[3] == $this->data['Handle']) {
|
|
412 $this->data['KeyCreated'] = $tokens[2];
|
|
413 }
|
|
414 break;
|
|
415
|
|
416 case 'KEY_NOT_CREATED':
|
|
417 if (isset($this->data['Handle']) && $tokens[1] == $this->data['Handle']) {
|
|
418 $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_CREATED;
|
|
419 }
|
|
420 break;
|
|
421
|
|
422 case 'PROGRESS':
|
|
423 // todo: at some point, support reporting status async
|
|
424 break;
|
|
425
|
|
426 // GnuPG 2.1 uses FAILURE and ERROR responses
|
|
427 case 'FAILURE':
|
|
428 case 'ERROR':
|
|
429 $errnum = (int) $tokens[2];
|
|
430 $source = $errnum >> 24;
|
|
431 $errcode = $errnum & 0xFFFFFF;
|
|
432
|
|
433 switch ($errcode) {
|
|
434 case 11: // bad passphrase
|
|
435 case 87: // bad PIN
|
|
436 $this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
|
|
437 break;
|
|
438
|
|
439 case 177: // no passphrase
|
|
440 case 178: // no PIN
|
|
441 $this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
|
|
442 break;
|
|
443
|
|
444 case 58:
|
|
445 $this->errorCode = Crypt_GPG::ERROR_NO_DATA;
|
|
446 break;
|
|
447 }
|
|
448
|
|
449 break;
|
|
450 }
|
|
451 }
|
|
452
|
|
453 // }}}
|
|
454 // {{{ handleError()
|
|
455
|
|
456 /**
|
|
457 * Handles error values in the error output from GPG
|
|
458 *
|
|
459 * This method is responsible for setting the
|
|
460 * {@link Crypt_GPG_Engine::$_errorCode}.
|
|
461 *
|
|
462 * @param string $line the error line to handle.
|
|
463 *
|
|
464 * @return void
|
|
465 */
|
|
466 public function handleError($line)
|
|
467 {
|
|
468 if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
|
|
469 $pattern = '/no valid OpenPGP data found/';
|
|
470 if (preg_match($pattern, $line) === 1) {
|
|
471 $this->errorCode = Crypt_GPG::ERROR_NO_DATA;
|
|
472 }
|
|
473 }
|
|
474
|
|
475 if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
|
|
476 $pattern = '/No secret key|secret key not available/';
|
|
477 if (preg_match($pattern, $line) === 1) {
|
|
478 $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
|
|
479 }
|
|
480 }
|
|
481
|
|
482 if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
|
|
483 $pattern = '/No public key|public key not found/';
|
|
484 if (preg_match($pattern, $line) === 1) {
|
|
485 $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
|
|
486 }
|
|
487 }
|
|
488
|
|
489 if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
|
|
490 $matches = array();
|
|
491 $pattern = '/can\'t (?:access|open) `(.*?)\'/';
|
|
492 if (preg_match($pattern, $line, $matches) === 1) {
|
|
493 $this->data['ErrorFilename'] = $matches[1];
|
|
494 $this->errorCode = Crypt_GPG::ERROR_FILE_PERMISSIONS;
|
|
495 }
|
|
496 }
|
|
497
|
|
498 // GnuPG 2.1: It should return MISSING_PASSPHRASE, but it does not
|
|
499 // we have to detect it this way. This happens e.g. on private key import
|
|
500 if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
|
|
501 $matches = array();
|
|
502 $pattern = '/key ([0-9A-F]+).* (Bad|No) passphrase/';
|
|
503 if (preg_match($pattern, $line, $matches) === 1) {
|
|
504 $keyId = $matches[1];
|
|
505 // @TODO: Get user name/email
|
|
506 if (empty($this->data['BadPassphrases'][$keyId])) {
|
|
507 $this->data['BadPassphrases'][$keyId] = $keyId;
|
|
508 }
|
|
509 if ($matches[2] == 'Bad') {
|
|
510 $this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
|
|
511 } else {
|
|
512 $this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
|
|
513 if (empty($this->data['MissingPassphrases'][$keyId])) {
|
|
514 $this->data['MissingPassphrases'][$keyId] = $keyId;
|
|
515 }
|
|
516 }
|
|
517 }
|
|
518 }
|
|
519
|
|
520 if ($this->errorCode === Crypt_GPG::ERROR_NONE && $this->operation == 'gen-key') {
|
|
521 $pattern = '/:([0-9]+): invalid algorithm$/';
|
|
522 if (preg_match($pattern, $line, $matches) === 1) {
|
|
523 $this->errorCode = Crypt_GPG::ERROR_BAD_KEY_PARAMS;
|
|
524 $this->data['LineNumber'] = intval($matches[1]);
|
|
525 }
|
|
526 }
|
|
527 }
|
|
528
|
|
529 // }}}
|
|
530 // {{{ throwException()
|
|
531
|
|
532 /**
|
|
533 * On error throws exception
|
|
534 *
|
|
535 * @param int $exitcode GPG process exit code
|
|
536 *
|
|
537 * @return void
|
|
538 * @throws Crypt_GPG_Exception
|
|
539 */
|
|
540 public function throwException($exitcode = 0)
|
|
541 {
|
|
542 if ($exitcode > 0 && $this->errorCode === Crypt_GPG::ERROR_NONE) {
|
|
543 $this->errorCode = $this->setErrorCode($exitcode);
|
|
544 }
|
|
545
|
|
546 if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
|
|
547 return;
|
|
548 }
|
|
549
|
|
550 $code = $this->errorCode;
|
|
551 $note = "Please use the 'debug' option when creating the Crypt_GPG " .
|
|
552 "object, and file a bug report at " . Crypt_GPG::BUG_URI;
|
|
553
|
|
554 switch ($this->operation) {
|
|
555 case 'version':
|
|
556 throw new Crypt_GPG_Exception(
|
|
557 'Unknown error getting GnuPG version information. ' . $note,
|
|
558 $code
|
|
559 );
|
|
560
|
|
561 case 'list-secret-keys':
|
|
562 case 'list-public-keys':
|
|
563 case 'list-keys':
|
|
564 switch ($code) {
|
|
565 case Crypt_GPG::ERROR_KEY_NOT_FOUND:
|
|
566 // ignore not found key errors
|
|
567 break;
|
|
568
|
|
569 case Crypt_GPG::ERROR_FILE_PERMISSIONS:
|
|
570 if (!empty($this->data['ErrorFilename'])) {
|
|
571 throw new Crypt_GPG_FileException(
|
|
572 sprintf(
|
|
573 'Error reading GnuPG data file \'%s\'. Check to make ' .
|
|
574 'sure it is readable by the current user.',
|
|
575 $this->data['ErrorFilename']
|
|
576 ),
|
|
577 $code,
|
|
578 $this->data['ErrorFilename']
|
|
579 );
|
|
580 }
|
|
581 throw new Crypt_GPG_FileException(
|
|
582 'Error reading GnuPG data file. Check to make sure that ' .
|
|
583 'GnuPG data files are readable by the current user.',
|
|
584 $code
|
|
585 );
|
|
586
|
|
587 default:
|
|
588 throw new Crypt_GPG_Exception(
|
|
589 'Unknown error getting keys. ' . $note, $code
|
|
590 );
|
|
591 }
|
|
592 break;
|
|
593
|
|
594 case 'delete-key':
|
|
595 case 'delete-secret-key':
|
|
596 switch ($code) {
|
|
597 case Crypt_GPG::ERROR_KEY_NOT_FOUND:
|
|
598 throw new Crypt_GPG_KeyNotFoundException(
|
|
599 'Key not found: ' . $this->operationArg,
|
|
600 $code,
|
|
601 $this->operationArg
|
|
602 );
|
|
603
|
|
604 case Crypt_GPG::ERROR_DELETE_PRIVATE_KEY:
|
|
605 throw new Crypt_GPG_DeletePrivateKeyException(
|
|
606 'Private key must be deleted before public key can be ' .
|
|
607 'deleted.',
|
|
608 $code,
|
|
609 $this->operationArg
|
|
610 );
|
|
611
|
|
612 default:
|
|
613 throw new Crypt_GPG_Exception(
|
|
614 'Unknown error deleting key. ' . $note, $code
|
|
615 );
|
|
616 }
|
|
617 break;
|
|
618
|
|
619 case 'import':
|
|
620 switch ($code) {
|
|
621 case Crypt_GPG::ERROR_NO_DATA:
|
|
622 throw new Crypt_GPG_NoDataException(
|
|
623 'No valid GPG key data found.', $code
|
|
624 );
|
|
625
|
|
626 case Crypt_GPG::ERROR_BAD_PASSPHRASE:
|
|
627 case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
|
|
628 throw $this->badPassException($code, 'Cannot import private key.');
|
|
629
|
|
630 default:
|
|
631 throw new Crypt_GPG_Exception(
|
|
632 'Unknown error importing GPG key. ' . $note, $code
|
|
633 );
|
|
634 }
|
|
635 break;
|
|
636
|
|
637 case 'export':
|
|
638 case 'export-secret-keys':
|
|
639 switch ($code) {
|
|
640 case Crypt_GPG::ERROR_BAD_PASSPHRASE:
|
|
641 case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
|
|
642 throw $this->badPassException($code, 'Cannot export private key.');
|
|
643
|
|
644 default:
|
|
645 throw new Crypt_GPG_Exception(
|
|
646 'Unknown error exporting a key. ' . $note, $code
|
|
647 );
|
|
648 }
|
|
649 break;
|
|
650
|
|
651 case 'encrypt':
|
|
652 case 'sign':
|
|
653 case 'clearsign':
|
|
654 case 'detach-sign':
|
|
655 switch ($code) {
|
|
656 case Crypt_GPG::ERROR_KEY_NOT_FOUND:
|
|
657 throw new Crypt_GPG_KeyNotFoundException(
|
|
658 'Cannot sign data. Private key not found. Import the '.
|
|
659 'private key before trying to sign data.',
|
|
660 $code,
|
|
661 !empty($this->data['ErrorKeyId']) ? $this->data['ErrorKeyId'] : null
|
|
662 );
|
|
663
|
|
664 case Crypt_GPG::ERROR_BAD_PASSPHRASE:
|
|
665 throw new Crypt_GPG_BadPassphraseException(
|
|
666 'Cannot sign data. Incorrect passphrase provided.', $code
|
|
667 );
|
|
668
|
|
669 case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
|
|
670 throw new Crypt_GPG_BadPassphraseException(
|
|
671 'Cannot sign data. No passphrase provided.', $code
|
|
672 );
|
|
673
|
|
674 default:
|
|
675 throw new Crypt_GPG_Exception(
|
|
676 "Unknown error {$this->operation}ing data. $note", $code
|
|
677 );
|
|
678 }
|
|
679 break;
|
|
680
|
|
681 case 'verify':
|
|
682 switch ($code) {
|
|
683 case Crypt_GPG::ERROR_BAD_SIGNATURE:
|
|
684 // ignore bad signature errors
|
|
685 break;
|
|
686
|
|
687 case Crypt_GPG::ERROR_NO_DATA:
|
|
688 throw new Crypt_GPG_NoDataException(
|
|
689 'No valid signature data found.', $code
|
|
690 );
|
|
691
|
|
692 case Crypt_GPG::ERROR_KEY_NOT_FOUND:
|
|
693 throw new Crypt_GPG_KeyNotFoundException(
|
|
694 'Public key required for data verification not in keyring.',
|
|
695 $code,
|
|
696 !empty($this->data['ErrorKeyId']) ? $this->data['ErrorKeyId'] : null
|
|
697 );
|
|
698
|
|
699 default:
|
|
700 throw new Crypt_GPG_Exception(
|
|
701 'Unknown error validating signature details. ' . $note,
|
|
702 $code
|
|
703 );
|
|
704 }
|
|
705 break;
|
|
706
|
|
707 case 'decrypt':
|
|
708 switch ($code) {
|
|
709 case Crypt_GPG::ERROR_BAD_SIGNATURE:
|
|
710 // ignore bad signature errors
|
|
711 break;
|
|
712
|
|
713 case Crypt_GPG::ERROR_KEY_NOT_FOUND:
|
|
714 if (!empty($this->data['MissingKeys'])) {
|
|
715 $keyId = reset($this->data['MissingKeys']);
|
|
716 } else {
|
|
717 $keyId = '';
|
|
718 }
|
|
719
|
|
720 throw new Crypt_GPG_KeyNotFoundException(
|
|
721 'Cannot decrypt data. No suitable private key is in the ' .
|
|
722 'keyring. Import a suitable private key before trying to ' .
|
|
723 'decrypt this data.',
|
|
724 $code,
|
|
725 $keyId
|
|
726 );
|
|
727
|
|
728 case Crypt_GPG::ERROR_BAD_PASSPHRASE:
|
|
729 case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
|
|
730 throw $this->badPassException($code, 'Cannot decrypt data.');
|
|
731
|
|
732 case Crypt_GPG::ERROR_NO_DATA:
|
|
733 throw new Crypt_GPG_NoDataException(
|
|
734 'Cannot decrypt data. No PGP encrypted data was found in '.
|
|
735 'the provided data.',
|
|
736 $code
|
|
737 );
|
|
738
|
|
739 default:
|
|
740 throw new Crypt_GPG_Exception(
|
|
741 'Unknown error decrypting data.', $code
|
|
742 );
|
|
743 }
|
|
744 break;
|
|
745
|
|
746 case 'gen-key':
|
|
747 switch ($code) {
|
|
748 case Crypt_GPG::ERROR_BAD_KEY_PARAMS:
|
|
749 throw new Crypt_GPG_InvalidKeyParamsException(
|
|
750 'Invalid key algorithm specified.', $code
|
|
751 );
|
|
752
|
|
753 default:
|
|
754 throw new Crypt_GPG_Exception(
|
|
755 'Unknown error generating key-pair. ' . $note, $code
|
|
756 );
|
|
757 }
|
|
758 }
|
|
759 }
|
|
760
|
|
761 // }}}
|
|
762 // {{{ throwException()
|
|
763
|
|
764 /**
|
|
765 * Check exit code of the GPG operation.
|
|
766 *
|
|
767 * @param int $exitcode GPG process exit code
|
|
768 *
|
|
769 * @return int Internal error code
|
|
770 */
|
|
771 protected function setErrorCode($exitcode)
|
|
772 {
|
|
773 if ($this->needPassphrase > 0) {
|
|
774 return Crypt_GPG::ERROR_MISSING_PASSPHRASE;
|
|
775 }
|
|
776
|
|
777 if ($this->operation == 'import') {
|
|
778 return Crypt_GPG::ERROR_NONE;
|
|
779 }
|
|
780
|
|
781 if ($this->operation == 'decrypt' && !empty($this->data['DecryptionOkay'])) {
|
|
782 if (!empty($this->data['IgnoreVerifyErrors'])) {
|
|
783 return Crypt_GPG::ERROR_NONE;
|
|
784 }
|
|
785 if (!empty($this->data['MissingKeys'])) {
|
|
786 return Crypt_GPG::ERROR_KEY_NOT_FOUND;
|
|
787 }
|
|
788 }
|
|
789
|
|
790 return Crypt_GPG::ERROR_UNKNOWN;
|
|
791 }
|
|
792
|
|
793 // }}}
|
|
794 // {{{ getData()
|
|
795
|
|
796 /**
|
|
797 * Get data from the last process execution.
|
|
798 *
|
|
799 * @param string $name Data element name:
|
|
800 * - SigCreated: The last SIG_CREATED status.
|
|
801 * - KeyConsidered: The last KEY_CONSIDERED status identifier.
|
|
802 * - KeyCreated: The KEY_CREATED status (for specified Handle).
|
|
803 * - Signatures: Signatures data from verification process.
|
|
804 * - LineNumber: Number of the gen-key error line.
|
|
805 * - Import: Result of IMPORT_OK/IMPORT_RES
|
|
806 *
|
|
807 * @return mixed
|
|
808 */
|
|
809 public function getData($name)
|
|
810 {
|
|
811 return isset($this->data[$name]) ? $this->data[$name] : null;
|
|
812 }
|
|
813
|
|
814 // }}}
|
|
815 // {{{ setData()
|
|
816
|
|
817 /**
|
|
818 * Set data for the process execution.
|
|
819 *
|
|
820 * @param string $name Data element name:
|
|
821 * - Handle: The unique key handle used by this handler
|
|
822 * The key handle is used to track GPG status output
|
|
823 * for a particular key on --gen-key command before
|
|
824 * the key has its own identifier.
|
|
825 * - IgnoreVerifyErrors: Do not throw exceptions
|
|
826 * when signature verification failes because
|
|
827 * of a missing public key.
|
|
828 * @param mixed $value Data element value
|
|
829 *
|
|
830 * @return void
|
|
831 */
|
|
832 public function setData($name, $value)
|
|
833 {
|
|
834 switch ($name) {
|
|
835 case 'Handle':
|
|
836 $this->data[$name] = strval($value);
|
|
837 break;
|
|
838
|
|
839 case 'IgnoreVerifyErrors':
|
|
840 $this->data[$name] = (bool) $value;
|
|
841 break;
|
|
842 }
|
|
843 }
|
|
844
|
|
845 // }}}
|
|
846 // {{{ setData()
|
|
847
|
|
848 /**
|
|
849 * Create Crypt_GPG_BadPassphraseException from operation data.
|
|
850 *
|
|
851 * @param int $code Error code
|
|
852 * @param string $message Error message
|
|
853 *
|
|
854 * @return Crypt_GPG_BadPassphraseException
|
|
855 */
|
|
856 protected function badPassException($code, $message)
|
|
857 {
|
|
858 $badPassphrases = array_diff_key(
|
|
859 isset($this->data['BadPassphrases']) ? $this->data['BadPassphrases'] : array(),
|
|
860 isset($this->data['MissingPassphrases']) ? $this->data['MissingPassphrases'] : array()
|
|
861 );
|
|
862
|
|
863 $missingPassphrases = array_intersect_key(
|
|
864 isset($this->data['BadPassphrases']) ? $this->data['BadPassphrases'] : array(),
|
|
865 isset($this->data['MissingPassphrases']) ? $this->data['MissingPassphrases'] : array()
|
|
866 );
|
|
867
|
|
868 if (count($badPassphrases) > 0) {
|
|
869 $message .= ' Incorrect passphrase provided for keys: "' .
|
|
870 implode('", "', $badPassphrases) . '".';
|
|
871 }
|
|
872 if (count($missingPassphrases) > 0) {
|
|
873 $message .= ' No passphrase provided for keys: "' .
|
|
874 implode('", "', $missingPassphrases) . '".';
|
|
875 }
|
|
876
|
|
877 return new Crypt_GPG_BadPassphraseException(
|
|
878 $message,
|
|
879 $code,
|
|
880 $badPassphrases,
|
|
881 $missingPassphrases
|
|
882 );
|
|
883 }
|
|
884
|
|
885 // }}}
|
|
886 // {{{ getPin()
|
|
887
|
|
888 /**
|
|
889 * Get registered passphrase for specified key.
|
|
890 *
|
|
891 * @param string $key Key identifier
|
|
892 *
|
|
893 * @return string Passphrase
|
|
894 */
|
|
895 protected function getPin($key)
|
|
896 {
|
|
897 $passphrase = '';
|
|
898 $keyIdLength = mb_strlen($key, '8bit');
|
|
899
|
|
900 if ($keyIdLength && !empty($_ENV['PINENTRY_USER_DATA'])) {
|
|
901 $passphrases = json_decode($_ENV['PINENTRY_USER_DATA'], true);
|
|
902 foreach ($passphrases as $_keyId => $pass) {
|
|
903 $keyId = $key;
|
|
904 $_keyIdLength = mb_strlen($_keyId, '8bit');
|
|
905
|
|
906 // Get last X characters of key identifier to compare
|
|
907 if ($keyIdLength < $_keyIdLength) {
|
|
908 $_keyId = mb_substr($_keyId, -$keyIdLength, null, '8bit');
|
|
909 } else if ($keyIdLength > $_keyIdLength) {
|
|
910 $keyId = mb_substr($keyId, -$_keyIdLength, null, '8bit');
|
|
911 }
|
|
912
|
|
913 if ($_keyId === $keyId) {
|
|
914 $passphrase = $pass;
|
|
915 break;
|
|
916 }
|
|
917 }
|
|
918 }
|
|
919
|
|
920 return $passphrase;
|
|
921 }
|
|
922
|
|
923 // }}}
|
|
924 }
|
|
925
|
|
926 // }}}
|
|
927
|
|
928 ?>
|