You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
roundcubemail/program/lib/Crypt/GPG/DecryptStatusHandler.php

345 lines
10 KiB
PHP

<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Crypt_GPG is a package to use GnuPG from PHP
*
* This file contains an object that handles GnuPG's status output for the
* decrypt operation.
*
* PHP version 5
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008-2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @version CVS: $Id$
* @link http://pear.php.net/package/Crypt_GPG
* @link http://www.gnupg.org/
*/
/**
* Crypt_GPG base class
*/
require_once 'Crypt/GPG.php';
/**
* Crypt_GPG exception classes
*/
require_once 'Crypt/GPG/Exceptions.php';
/**
* Status line handler for the decrypt operation
*
* This class is used internally by Crypt_GPG and does not need be used
* directly. See the {@link Crypt_GPG} class for end-user API.
*
* This class is responsible for sending the passphrase commands when required
* by the {@link Crypt_GPG::decrypt()} method. See <b>doc/DETAILS</b> in the
* {@link http://www.gnupg.org/download/ GnuPG distribution} for detailed
* information on GnuPG's status output for the decrypt operation.
*
* This class is also responsible for parsing error status and throwing a
* meaningful exception in the event that decryption fails.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008-2013 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @link http://www.gnupg.org/
*/
class Crypt_GPG_DecryptStatusHandler
{
// {{{ protected properties
/**
* Keys used to decrypt
*
* The array is of the form:
* <code>
* array(
* $key_id => array(
* 'fingerprint' => $fingerprint,
* 'passphrase' => $passphrase
* )
* );
* </code>
*
* @var array
*/
protected $keys = array();
/**
* Engine used to which passphrases are passed
*
* @var Crypt_GPG_Engine
*/
protected $engine = null;
/**
* The id of the current sub-key used for decryption
*
* @var string
*/
protected $currentSubKey = '';
/**
* Whether or not decryption succeeded
*
* If the message is only signed (compressed) and not encrypted, this is
* always true. If the message is encrypted, this flag is set to false
* until we know the decryption succeeded.
*
* @var boolean
*/
protected $decryptionOkay = true;
/**
* Whether or not there was no data for decryption
*
* @var boolean
*/
protected $noData = false;
/**
* Keys for which the passhprase is missing
*
* This contains primary user ids indexed by sub-key id and is used to
* create helpful exception messages.
*
* @var array
*/
protected $missingPassphrases = array();
/**
* Keys for which the passhprase is incorrect
*
* This contains primary user ids indexed by sub-key id and is used to
* create helpful exception messages.
*
* @var array
*/
protected $badPassphrases = array();
/**
* Keys that can be used to decrypt the data but are missing from the
* keychain
*
* This is an array with both the key and value being the sub-key id of
* the missing keys.
*
* @var array
*/
protected $missingKeys = array();
// }}}
// {{{ __construct()
/**
* Creates a new decryption status handler
*
* @param Crypt_GPG_Engine $engine the GPG engine to which passphrases are
* passed.
* @param array $keys the decryption keys to use.
*/
public function __construct(Crypt_GPG_Engine $engine, array $keys)
{
$this->engine = $engine;
$this->keys = $keys;
}
// }}}
// {{{ handle()
/**
* Handles a status line
*
* @param string $line the status line to handle.
*
* @return void
*/
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;
}
}
// }}}
// {{{ throwException()
/**
* Takes the final status of the decrypt operation and throws an
* appropriate exception
*
* If decryption was successful, no exception is thrown.
*
* @return void
*
* @throws Crypt_GPG_KeyNotFoundException if the private key needed to
* decrypt the data is not in the user's keyring.
*
* @throws Crypt_GPG_NoDataException if specified data does not contain
* GPG encrypted data.
*
* @throws Crypt_GPG_BadPassphraseException if a required passphrase is
* incorrect or if a required passphrase is not specified. See
* {@link Crypt_GPG::addDecryptKey()}.
*
* @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
* Use the <i>debug</i> option and file a bug report if these
* exceptions occur.
*/
public function throwException()
{
$code = Crypt_GPG::ERROR_NONE;
if (!$this->decryptionOkay) {
if (count($this->badPassphrases) > 0) {
$code = Crypt_GPG::ERROR_BAD_PASSPHRASE;
} elseif (count($this->missingKeys) > 0) {
$code = Crypt_GPG::ERROR_KEY_NOT_FOUND;
} else {
$code = Crypt_GPG::ERROR_UNKNOWN;
}
} elseif ($this->noData) {
$code = Crypt_GPG::ERROR_NO_DATA;
}
switch ($code) {
case Crypt_GPG::ERROR_NONE:
break;
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
if (count($this->missingKeys) > 0) {
$keyId = reset($this->missingKeys);
} else {
$keyId = '';
}
throw new Crypt_GPG_KeyNotFoundException(
'Cannot decrypt data. No suitable private key is in the ' .
'keyring. Import a suitable private key before trying to ' .
'decrypt this data.',
$code,
$keyId
);
case Crypt_GPG::ERROR_BAD_PASSPHRASE:
$badPassphrases = array_diff_key(
$this->badPassphrases,
$this->missingPassphrases
);
$missingPassphrases = array_intersect_key(
$this->badPassphrases,
$this->missingPassphrases
);
$message = 'Cannot decrypt data.';
if (count($badPassphrases) > 0) {
$message = ' Incorrect passphrase provided for keys: "' .
implode('", "', $badPassphrases) . '".';
}
if (count($missingPassphrases) > 0) {
$message = ' No passphrase provided for keys: "' .
implode('", "', $badPassphrases) . '".';
}
throw new Crypt_GPG_BadPassphraseException(
$message,
$code,
$badPassphrases,
$missingPassphrases
);
case Crypt_GPG::ERROR_NO_DATA:
throw new Crypt_GPG_NoDataException(
'Cannot decrypt data. No PGP encrypted data was found in '.
'the provided data.',
$code
);
default:
throw new Crypt_GPG_Exception(
'Unknown error decrypting data.',
$code
);
}
}
// }}}
}
?>