/** * Handles a status line. * * @param string $line the status line to handle. */ public function handle($line) { $tokens = explode(' ', $line); switch ($tokens[0]) { case 'ENC_TO': // Now we know the message is encrypted. Set flag to check if // decryption succeeded. $this->decryptionOkay = false; // this is the new key message $this->currentSubKeyId = $tokens[1]; break; case 'NEED_PASSPHRASE': // send passphrase to the GPG engine $subKeyId = $tokens[1]; if (array_key_exists($subKeyId, $this->keys)) { $passphrase = $this->keys[$subKeyId]['passphrase']; $this->engine->sendCommand($passphrase); } else { $this->engine->sendCommand(''); } break; case 'USERID_HINT': // remember the user id for pretty exception messages $this->badPassphrases[$tokens[1]] = implode(' ', array_splice($tokens, 2)); break; case 'GOOD_PASSPHRASE': // if we got a good passphrase, remove the key from the list of // bad passphrases. unset($this->badPassphrases[$this->currentSubKeyId]); break; case 'MISSING_PASSPHRASE': $this->missingPassphrases[$this->currentSubKeyId] = $this->currentSubKeyId; break; case 'NO_SECKEY': // note: this message is also received if there are multiple // recipients and a previous key had a correct passphrase. $this->missingKeys[$tokens[1]] = $tokens[1]; break; case 'NODATA': $this->noData = true; break; case 'DECRYPTION_OKAY': // If the message is encrypted, this is the all-clear signal. $this->decryptionOkay = true; break; } }
/** * Handles error values in the status output from GPG * * This method is responsible for setting the * {@link self::$errorCode}. See <b>doc/DETAILS</b> in the * {@link http://www.gnupg.org/download/ GPG distribution} for detailed * information on GPG's status output. * * @param string $line the status line to handle. * * @return void */ public function handleStatus($line) { $tokens = explode(' ', $line); switch ($tokens[0]) { case 'NODATA': $this->errorCode = Crypt_GPG::ERROR_NO_DATA; break; case 'DECRYPTION_OKAY': // If the message is encrypted, this is the all-clear signal. $this->data['DecryptionOkay'] = true; $this->errorCode = Crypt_GPG::ERROR_NONE; break; case 'DELETE_PROBLEM': if ($tokens[1] == '1') { $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND; break; } elseif ($tokens[1] == '2') { $this->errorCode = Crypt_GPG::ERROR_DELETE_PRIVATE_KEY; break; } break; case 'IMPORT_OK': $this->data['Import']['fingerprint'] = $tokens[2]; if (empty($this->data['Import']['fingerprints'])) { $this->data['Import']['fingerprints'] = array($tokens[2]); } else { if (!in_array($tokens[2], $this->data['Import']['fingerprints'])) { $this->data['Import']['fingerprints'][] = $tokens[2]; } } break; case 'IMPORT_RES': $this->data['Import']['public_imported'] = intval($tokens[3]); $this->data['Import']['public_unchanged'] = intval($tokens[5]); $this->data['Import']['private_imported'] = intval($tokens[11]); $this->data['Import']['private_unchanged'] = intval($tokens[12]); break; case 'NO_PUBKEY': case 'NO_SECKEY': $this->data['ErrorKeyId'] = $tokens[1]; if ($this->errorCode != Crypt_GPG::ERROR_MISSING_PASSPHRASE && $this->errorCode != Crypt_GPG::ERROR_BAD_PASSPHRASE) { $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND; } // note: this message is also received if there are multiple // recipients and a previous key had a correct passphrase. $this->data['MissingKeys'][$tokens[1]] = $tokens[1]; // @FIXME: remove missing passphrase registered in ENC_TO handler // This is for GnuPG 2.1 unset($this->data['MissingPassphrases'][$tokens[1]]); break; case 'KEY_CONSIDERED': // In GnuPG 2.1.x exporting/importing a secret key requires passphrase // However, no NEED_PASSPRASE is returned, https://bugs.gnupg.org/gnupg/issue2667 // So, handling KEY_CONSIDERED and GET_HIDDEN is needed. if (!array_key_exists('KeyConsidered', $this->data)) { $this->data['KeyConsidered'] = $tokens[1]; } break; case 'USERID_HINT': // remember the user id for pretty exception messages // GnuPG 2.1.15 gives me: "USERID_HINT 0000000000000000 [?]" $keyId = $tokens[1]; if (strcspn($keyId, '0')) { $username = implode(' ', array_splice($tokens, 2)); $this->data['BadPassphrases'][$keyId] = $username; } break; case 'ENC_TO': // Now we know the message is encrypted. Set flag to check if // decryption succeeded. $this->data['DecryptionOkay'] = false; // this is the new key message $this->data['CurrentSubKeyId'] = $keyId = $tokens[1]; // For some reason in GnuPG 2.1.11 I get only ENC_TO and no // NEED_PASSPHRASE/MISSING_PASSPHRASE/USERID_HINT // This is not needed for GnuPG 2.1.15 if (!empty($_ENV['PINENTRY_USER_DATA'])) { $passphrases = json_decode($_ENV['PINENTRY_USER_DATA'], true); } else { $passphrases = array(); } // @TODO: Get user name/email $this->data['BadPassphrases'][$keyId] = $keyId; if (empty($passphrases) || empty($passphrases[$keyId])) { $this->data['MissingPassphrases'][$keyId] = $keyId; } break; case 'GOOD_PASSPHRASE': // if we got a good passphrase, remove the key from the list of // bad passphrases. if (isset($this->data['CurrentSubKeyId'])) { unset($this->data['BadPassphrases'][$this->data['CurrentSubKeyId']]); unset($this->data['MissingPassphrases'][$this->data['CurrentSubKeyId']]); } $this->needPassphrase--; break; case 'BAD_PASSPHRASE': $this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE; break; case 'MISSING_PASSPHRASE': if (isset($this->data['CurrentSubKeyId'])) { $this->data['MissingPassphrases'][$this->data['CurrentSubKeyId']] = $this->data['CurrentSubKeyId']; } $this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE; break; case 'GET_HIDDEN': if ($tokens[1] == 'passphrase.enter' && isset($this->data['KeyConsidered'])) { $tokens[1] = $this->data['KeyConsidered']; } else { break; } // no break // no break case 'NEED_PASSPHRASE': $passphrase = $this->getPin($tokens[1]); $this->engine->sendCommand($passphrase); if ($passphrase === '') { $this->needPassphrase++; } break; case 'SIG_CREATED': $this->data['SigCreated'] = $line; break; case 'SIG_ID': // note: signature id comes before new signature line and may not // exist for some signature types $this->data['SignatureId'] = $tokens[1]; break; case 'EXPSIG': case 'EXPKEYSIG': case 'REVKEYSIG': case 'BADSIG': case 'ERRSIG': $this->errorCode = Crypt_GPG::ERROR_BAD_SIGNATURE; // no break // no break case 'GOODSIG': $signature = new Crypt_GPG_Signature(); // if there was a signature id, set it on the new signature if (!empty($this->data['SignatureId'])) { $signature->setId($this->data['SignatureId']); $this->data['SignatureId'] = ''; } // Detect whether fingerprint or key id was returned and set // signature values appropriately. Key ids are strings of either // 16 or 8 hexadecimal characters. Fingerprints are strings of 40 // hexadecimal characters. The key id is the last 16 characters of // the key fingerprint. if (mb_strlen($tokens[1], '8bit') > 16) { $signature->setKeyFingerprint($tokens[1]); $signature->setKeyId(mb_substr($tokens[1], -16, null, '8bit')); } else { $signature->setKeyId($tokens[1]); } // get user id string if ($tokens[0] != 'ERRSIG') { $string = implode(' ', array_splice($tokens, 2)); $string = rawurldecode($string); $signature->setUserId(Crypt_GPG_UserId::parse($string)); } $this->data['Signatures'][] = $signature; break; case 'VALIDSIG': if (empty($this->data['Signatures'])) { break; } $signature = end($this->data['Signatures']); $signature->setValid(true); $signature->setKeyFingerprint($tokens[1]); if (strpos($tokens[3], 'T') === false) { $signature->setCreationDate($tokens[3]); } else { $signature->setCreationDate(strtotime($tokens[3])); } if (array_key_exists(4, $tokens)) { if (strpos($tokens[4], 'T') === false) { $signature->setExpirationDate($tokens[4]); } else { $signature->setExpirationDate(strtotime($tokens[4])); } } break; case 'KEY_CREATED': if (isset($this->data['Handle']) && $tokens[3] == $this->data['Handle']) { $this->data['KeyCreated'] = $tokens[2]; } break; case 'KEY_NOT_CREATED': if (isset($this->data['Handle']) && $tokens[1] == $this->data['Handle']) { $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_CREATED; } break; case 'PROGRESS': // todo: at some point, support reporting status async break; // GnuPG 2.1 uses FAILURE and ERROR responses // GnuPG 2.1 uses FAILURE and ERROR responses case 'FAILURE': case 'ERROR': $errnum = (int) $tokens[2]; $source = $errnum >> 24; $errcode = $errnum & 0xffffff; switch ($errcode) { case 11: // bad passphrase // bad passphrase case 87: // bad PIN $this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE; break; case 177: // no passphrase // no passphrase case 178: // no PIN $this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE; break; case 58: $this->errorCode = Crypt_GPG::ERROR_NO_DATA; break; } break; } }
/** * Handles the status output from GPG for the sign operation * * This method is responsible for sending the passphrase commands when * required by the {@link Crypt_GPG::sign()} method. See <b>doc/DETAILS</b> * in the {@link http://www.gnupg.org/download/ GPG distribution} for * detailed information on GPG's status output. * * @param string $line the status line to handle. * * @return void * * @see Crypt_GPG::sign() */ public function handleSignStatus($line) { $tokens = explode(' ', $line); switch ($tokens[0]) { case 'NEED_PASSPHRASE': $subKeyId = $tokens[1]; if (array_key_exists($subKeyId, $this->signKeys)) { $passphrase = $this->signKeys[$subKeyId]['passphrase']; $this->engine->sendCommand($passphrase); } else { $this->engine->sendCommand(''); } break; case 'SIG_CREATED': $this->lastSignatureInfo = $line; break; } }