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 ?> |
