Mercurial > hg > rc1
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 ?> |