comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:1e000243b222
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 ?>