diff options
Diffstat (limited to 'plugins/enigma/lib')
| -rw-r--r-- | plugins/enigma/lib/Crypt/GPG.php | 2542 | ||||
| -rw-r--r-- | plugins/enigma/lib/Crypt/GPG/DecryptStatusHandler.php | 336 | ||||
| -rw-r--r-- | plugins/enigma/lib/Crypt/GPG/Engine.php | 1758 | ||||
| -rw-r--r-- | plugins/enigma/lib/Crypt/GPG/Exceptions.php | 473 | ||||
| -rw-r--r-- | plugins/enigma/lib/Crypt/GPG/Key.php | 223 | ||||
| -rw-r--r-- | plugins/enigma/lib/Crypt/GPG/Signature.php | 428 | ||||
| -rw-r--r-- | plugins/enigma/lib/Crypt/GPG/SubKey.php | 649 | ||||
| -rw-r--r-- | plugins/enigma/lib/Crypt/GPG/UserId.php | 373 | ||||
| -rw-r--r-- | plugins/enigma/lib/Crypt/GPG/VerifyStatusHandler.php | 216 | ||||
| -rw-r--r-- | plugins/enigma/lib/enigma_driver.php | 106 | ||||
| -rw-r--r-- | plugins/enigma/lib/enigma_driver_gnupg.php | 305 | ||||
| -rw-r--r-- | plugins/enigma/lib/enigma_engine.php | 547 | ||||
| -rw-r--r-- | plugins/enigma/lib/enigma_error.php | 62 | ||||
| -rw-r--r-- | plugins/enigma/lib/enigma_key.php | 129 | ||||
| -rw-r--r-- | plugins/enigma/lib/enigma_signature.php | 34 | ||||
| -rw-r--r-- | plugins/enigma/lib/enigma_subkey.php | 57 | ||||
| -rw-r--r-- | plugins/enigma/lib/enigma_ui.php | 459 | ||||
| -rw-r--r-- | plugins/enigma/lib/enigma_userid.php | 31 | 
18 files changed, 8728 insertions, 0 deletions
| diff --git a/plugins/enigma/lib/Crypt/GPG.php b/plugins/enigma/lib/Crypt/GPG.php new file mode 100644 index 000000000..6e8e717e8 --- /dev/null +++ b/plugins/enigma/lib/Crypt/GPG.php @@ -0,0 +1,2542 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Crypt_GPG is a package to use GPG from PHP + * + * This package provides an object oriented interface to GNU Privacy + * Guard (GPG). It requires the GPG executable to be on the system. + * + * Though GPG can support symmetric-key cryptography, this package is intended + * only to facilitate public-key cryptography. + * + * This file contains the main GPG class. The class in this file lets you + * encrypt, decrypt, sign and verify data; import and delete keys; and perform + * other useful GPG tasks. + * + * Example usage: + * <code> + * <?php + * // encrypt some data + * $gpg = new Crypt_GPG(); + * $gpg->addEncryptKey($mySecretKeyId); + * $encryptedData = $gpg->encrypt($data); + * ?> + * </code> + * + * 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    Nathan Fredrickson <nathan@silverorange.com> + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2005-2010 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version   CVS: $Id: GPG.php 302814 2010-08-26 15:43:07Z gauthierm $ + * @link      http://pear.php.net/package/Crypt_GPG + * @link      http://pear.php.net/manual/en/package.encryption.crypt-gpg.php + * @link      http://www.gnupg.org/ + */ + +/** + * Signature handler class + */ +require_once 'Crypt/GPG/VerifyStatusHandler.php'; + +/** + * Decryption handler class + */ +require_once 'Crypt/GPG/DecryptStatusHandler.php'; + +/** + * GPG key class + */ +require_once 'Crypt/GPG/Key.php'; + +/** + * GPG sub-key class + */ +require_once 'Crypt/GPG/SubKey.php'; + +/** + * GPG user id class + */ +require_once 'Crypt/GPG/UserId.php'; + +/** + * GPG process and I/O engine class + */ +require_once 'Crypt/GPG/Engine.php'; + +/** + * GPG exception classes + */ +require_once 'Crypt/GPG/Exceptions.php'; + +// {{{ class Crypt_GPG + +/** + * A class to use GPG from PHP + * + * This class provides an object oriented interface to GNU Privacy Guard (GPG). + * + * Though GPG can support symmetric-key cryptography, this class is intended + * only to facilitate public-key cryptography. + * + * @category  Encryption + * @package   Crypt_GPG + * @author    Nathan Fredrickson <nathan@silverorange.com> + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2005-2010 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 +{ +    // {{{ class error constants + +    /** +     * Error code returned when there is no error. +     */ +    const ERROR_NONE = 0; + +    /** +     * Error code returned when an unknown or unhandled error occurs. +     */ +    const ERROR_UNKNOWN = 1; + +    /** +     * Error code returned when a bad passphrase is used. +     */ +    const ERROR_BAD_PASSPHRASE = 2; + +    /** +     * Error code returned when a required passphrase is missing. +     */ +    const ERROR_MISSING_PASSPHRASE = 3; + +    /** +     * Error code returned when a key that is already in the keyring is +     * imported. +     */ +    const ERROR_DUPLICATE_KEY = 4; + +    /** +     * Error code returned the required data is missing for an operation. +     * +     * This could be missing key data, missing encrypted data or missing +     * signature data. +     */ +    const ERROR_NO_DATA = 5; + +    /** +     * Error code returned when an unsigned key is used. +     */ +    const ERROR_UNSIGNED_KEY = 6; + +    /** +     * Error code returned when a key that is not self-signed is used. +     */ +    const ERROR_NOT_SELF_SIGNED = 7; + +    /** +     * Error code returned when a public or private key that is not in the +     * keyring is used. +     */ +    const ERROR_KEY_NOT_FOUND = 8; + +    /** +     * Error code returned when an attempt to delete public key having a +     * private key is made. +     */ +    const ERROR_DELETE_PRIVATE_KEY = 9; + +    /** +     * Error code returned when one or more bad signatures are detected. +     */ +    const ERROR_BAD_SIGNATURE = 10; + +    /** +     * Error code returned when there is a problem reading GnuPG data files. +     */ +    const ERROR_FILE_PERMISSIONS = 11; + +    // }}} +    // {{{ class constants for data signing modes + +    /** +     * Signing mode for normal signing of data. The signed message will not +     * be readable without special software. +     * +     * This is the default signing mode. +     * +     * @see Crypt_GPG::sign() +     * @see Crypt_GPG::signFile() +     */ +    const SIGN_MODE_NORMAL = 1; + +    /** +     * Signing mode for clearsigning data. Clearsigned signatures are ASCII +     * armored data and are readable without special software. If the signed +     * message is unencrypted, the message will still be readable. The message +     * text will be in the original encoding. +     * +     * @see Crypt_GPG::sign() +     * @see Crypt_GPG::signFile() +     */ +    const SIGN_MODE_CLEAR = 2; + +    /** +     * Signing mode for creating a detached signature. When using detached +     * signatures, only the signature data is returned. The original message +     * text may be distributed separately from the signature data. This is +     * useful for miltipart/signed email messages as per +     * {@link http://www.ietf.org/rfc/rfc3156.txt RFC 3156}. +     * +     * @see Crypt_GPG::sign() +     * @see Crypt_GPG::signFile() +     */ +    const SIGN_MODE_DETACHED = 3; + +    // }}} +    // {{{ class constants for fingerprint formats + +    /** +     * No formatting is performed. +     * +     * Example: C3BC615AD9C766E5A85C1F2716D27458B1BBA1C4 +     * +     * @see Crypt_GPG::getFingerprint() +     */ +    const FORMAT_NONE = 1; + +    /** +     * Fingerprint is formatted in the format used by the GnuPG gpg command's +     * default output. +     * +     * Example: C3BC 615A D9C7 66E5 A85C  1F27 16D2 7458 B1BB A1C4 +     * +     * @see Crypt_GPG::getFingerprint() +     */ +    const FORMAT_CANONICAL = 2; + +    /** +     * Fingerprint is formatted in the format used when displaying X.509 +     * certificates +     * +     * Example: C3:BC:61:5A:D9:C7:66:E5:A8:5C:1F:27:16:D2:74:58:B1:BB:A1:C4 +     * +     * @see Crypt_GPG::getFingerprint() +     */ +    const FORMAT_X509 = 3; + +    // }}} +    // {{{ other class constants + +    /** +     * URI at which package bugs may be reported. +     */ +    const BUG_URI = 'http://pear.php.net/bugs/report.php?package=Crypt_GPG'; + +    // }}} +    // {{{ protected class properties + +    /** +     * Engine used to control the GPG subprocess +     * +     * @var Crypt_GPG_Engine +     * +     * @see Crypt_GPG::setEngine() +     */ +    protected $engine = null; + +    /** +     * Keys used to encrypt +     * +     * The array is of the form: +     * <code> +     * array( +     *   $key_id => array( +     *     'fingerprint' => $fingerprint, +     *     'passphrase'  => null +     *   ) +     * ); +     * </code> +     * +     * @var array +     * @see Crypt_GPG::addEncryptKey() +     * @see Crypt_GPG::clearEncryptKeys() +     */ +    protected $encryptKeys = array(); + +    /** +     * Keys used to decrypt +     * +     * The array is of the form: +     * <code> +     * array( +     *   $key_id => array( +     *     'fingerprint' => $fingerprint, +     *     'passphrase'  => $passphrase +     *   ) +     * ); +     * </code> +     * +     * @var array +     * @see Crypt_GPG::addSignKey() +     * @see Crypt_GPG::clearSignKeys() +     */ +    protected $signKeys = array(); + +    /** +     * Keys used to sign +     * +     * The array is of the form: +     * <code> +     * array( +     *   $key_id => array( +     *     'fingerprint' => $fingerprint, +     *     'passphrase'  => $passphrase +     *   ) +     * ); +     * </code> +     * +     * @var array +     * @see Crypt_GPG::addDecryptKey() +     * @see Crypt_GPG::clearDecryptKeys() +     */ +    protected $decryptKeys = array(); + +    // }}} +    // {{{ __construct() + +    /** +     * Creates a new GPG object +     * +     * Available options are: +     * +     * - <kbd>string  homedir</kbd>        - the directory where the GPG +     *                                       keyring files are stored. If not +     *                                       specified, Crypt_GPG uses the +     *                                       default of <kbd>~/.gnupg</kbd>. +     * - <kbd>string  publicKeyring</kbd>  - the file path of the public +     *                                       keyring. Use this if the public +     *                                       keyring is not in the homedir, or +     *                                       if the keyring is in a directory +     *                                       not writable by the process +     *                                       invoking GPG (like Apache). Then +     *                                       you can specify the path to the +     *                                       keyring with this option +     *                                       (/foo/bar/pubring.gpg), and specify +     *                                       a writable directory (like /tmp) +     *                                       using the <i>homedir</i> option. +     * - <kbd>string  privateKeyring</kbd> - the file path of the private +     *                                       keyring. Use this if the private +     *                                       keyring is not in the homedir, or +     *                                       if the keyring is in a directory +     *                                       not writable by the process +     *                                       invoking GPG (like Apache). Then +     *                                       you can specify the path to the +     *                                       keyring with this option +     *                                       (/foo/bar/secring.gpg), and specify +     *                                       a writable directory (like /tmp) +     *                                       using the <i>homedir</i> option. +     * - <kbd>string  trustDb</kbd>        - the file path of the web-of-trust +     *                                       database. Use this if the trust +     *                                       database is not in the homedir, or +     *                                       if the database is in a directory +     *                                       not writable by the process +     *                                       invoking GPG (like Apache). Then +     *                                       you can specify the path to the +     *                                       trust database with this option +     *                                       (/foo/bar/trustdb.gpg), and specify +     *                                       a writable directory (like /tmp) +     *                                       using the <i>homedir</i> option. +     * - <kbd>string  binary</kbd>         - the location of the GPG binary. If +     *                                       not specified, the driver attempts +     *                                       to auto-detect the GPG binary +     *                                       location using a list of known +     *                                       default locations for the current +     *                                       operating system. The option +     *                                       <kbd>gpgBinary</kbd> is a +     *                                       deprecated alias for this option. +     * - <kbd>boolean debug</kbd>          - whether or not to use debug mode. +     *                                       When debug mode is on, all +     *                                       communication to and from the GPG +     *                                       subprocess is logged. This can be +     * +     * @param array $options optional. An array of options used to create the +     *                       GPG object. All options are optional and are +     *                       represented as key-value pairs. +     * +     * @throws Crypt_GPG_FileException if the <kbd>homedir</kbd> does not exist +     *         and cannot be created. This can happen if <kbd>homedir</kbd> is +     *         not specified, Crypt_GPG is run as the web user, and the web +     *         user has no home directory. This exception is also thrown if any +     *         of the options <kbd>publicKeyring</kbd>, +     *         <kbd>privateKeyring</kbd> or <kbd>trustDb</kbd> options are +     *         specified but the files do not exist or are are not readable. +     *         This can happen if the user running the Crypt_GPG process (for +     *         example, the Apache user) does not have permission to read the +     *         files. +     * +     * @throws PEAR_Exception if the provided <kbd>binary</kbd> is invalid, or +     *         if no <kbd>binary</kbd> is provided and no suitable binary could +     *         be found. +     */ +    public function __construct(array $options = array()) +    { +        $this->setEngine(new Crypt_GPG_Engine($options)); +    } + +    // }}} +    // {{{ importKey() + +    /** +     * Imports a public or private key into the keyring +     * +     * Keys may be removed from the keyring using +     * {@link Crypt_GPG::deletePublicKey()} or +     * {@link Crypt_GPG::deletePrivateKey()}. +     * +     * @param string $data the key data to be imported. +     * +     * @return array an associative array containing the following elements: +     *               - <kbd>fingerprint</kbd>       - the fingerprint of the +     *                                                imported key, +     *               - <kbd>public_imported</kbd>   - the number of public +     *                                                keys imported, +     *               - <kbd>public_unchanged</kbd>  - the number of unchanged +     *                                                public keys, +     *               - <kbd>private_imported</kbd>  - the number of private +     *                                                keys imported, +     *               - <kbd>private_unchanged</kbd> - the number of unchanged +     *                                                private keys. +     * +     * @throws Crypt_GPG_NoDataException if the key data is missing or if the +     *         data is is not valid key data. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    public function importKey($data) +    { +        return $this->_importKey($data, false); +    } + +    // }}} +    // {{{ importKeyFile() + +    /** +     * Imports a public or private key file into the keyring +     * +     * Keys may be removed from the keyring using +     * {@link Crypt_GPG::deletePublicKey()} or +     * {@link Crypt_GPG::deletePrivateKey()}. +     * +     * @param string $filename the key file to be imported. +     * +     * @return array an associative array containing the following elements: +     *               - <kbd>fingerprint</kbd>       - the fingerprint of the +     *                                                imported key, +     *               - <kbd>public_imported</kbd>   - the number of public +     *                                                keys imported, +     *               - <kbd>public_unchanged</kbd>  - the number of unchanged +     *                                                public keys, +     *               - <kbd>private_imported</kbd>  - the number of private +     *                                                keys imported, +     *               - <kbd>private_unchanged</kbd> - the number of unchanged +     *                                                private keys. +     *                                                  private keys. +     * +     * @throws Crypt_GPG_NoDataException if the key data is missing or if the +     *         data is is not valid key data. +     * +     * @throws Crypt_GPG_FileException if the key file is not readable. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    public function importKeyFile($filename) +    { +        return $this->_importKey($filename, true); +    } + +    // }}} +    // {{{ exportPublicKey() + +    /** +     * Exports a public key from the keyring +     * +     * The exported key remains on the keyring. To delete the public key, use +     * {@link Crypt_GPG::deletePublicKey()}. +     * +     * If more than one key fingerprint is available for the specified +     * <kbd>$keyId</kbd> (for example, if you use a non-unique uid) only the +     * first public key is exported. +     * +     * @param string  $keyId either the full uid of the public key, the email +     *                       part of the uid of the public key or the key id of +     *                       the public key. For example, +     *                       "Test User (example) <test@example.com>", +     *                       "test@example.com" or a hexadecimal string. +     * @param boolean $armor optional. If true, ASCII armored data is returned; +     *                       otherwise, binary data is returned. Defaults to +     *                       true. +     * +     * @return string the public key data. +     * +     * @throws Crypt_GPG_KeyNotFoundException if a public key with the given +     *         <kbd>$keyId</kbd> is not found. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    public function exportPublicKey($keyId, $armor = true) +    { +        $fingerprint = $this->getFingerprint($keyId); + +        if ($fingerprint === null) { +            throw new Crypt_GPG_KeyNotFoundException( +                'Public key not found: ' . $keyId, +                Crypt_GPG::ERROR_KEY_NOT_FOUND, $keyId); +        } + +        $keyData   = ''; +        $operation = '--export ' . escapeshellarg($fingerprint); +        $arguments = ($armor) ? array('--armor') : array(); + +        $this->engine->reset(); +        $this->engine->setOutput($keyData); +        $this->engine->setOperation($operation, $arguments); +        $this->engine->run(); + +        $code = $this->engine->getErrorCode(); + +        if ($code !== Crypt_GPG::ERROR_NONE) { +            throw new Crypt_GPG_Exception( +                'Unknown error exporting public key. Please use the ' . +                '\'debug\' option when creating the Crypt_GPG object, and ' . +                'file a bug report at ' . self::BUG_URI, $code); +        } + +        return $keyData; +    } + +    // }}} +    // {{{ deletePublicKey() + +    /** +     * Deletes a public key from the keyring +     * +     * If more than one key fingerprint is available for the specified +     * <kbd>$keyId</kbd> (for example, if you use a non-unique uid) only the +     * first public key is deleted. +     * +     * The private key must be deleted first or an exception will be thrown. +     * See {@link Crypt_GPG::deletePrivateKey()}. +     * +     * @param string $keyId either the full uid of the public key, the email +     *                      part of the uid of the public key or the key id of +     *                      the public key. For example, +     *                      "Test User (example) <test@example.com>", +     *                      "test@example.com" or a hexadecimal string. +     * +     * @return void +     * +     * @throws Crypt_GPG_KeyNotFoundException if a public key with the given +     *         <kbd>$keyId</kbd> is not found. +     * +     * @throws Crypt_GPG_DeletePrivateKeyException if the specified public key +     *         has an associated private key on the keyring. The private key +     *         must be deleted first. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    public function deletePublicKey($keyId) +    { +        $fingerprint = $this->getFingerprint($keyId); + +        if ($fingerprint === null) { +            throw new Crypt_GPG_KeyNotFoundException( +                'Public key not found: ' . $keyId, +                Crypt_GPG::ERROR_KEY_NOT_FOUND, $keyId); +        } + +        $operation = '--delete-key ' . escapeshellarg($fingerprint); +        $arguments = array( +            '--batch', +            '--yes' +        ); + +        $this->engine->reset(); +        $this->engine->setOperation($operation, $arguments); +        $this->engine->run(); + +        $code = $this->engine->getErrorCode(); + +        switch ($code) { +        case Crypt_GPG::ERROR_NONE: +            break; +        case Crypt_GPG::ERROR_DELETE_PRIVATE_KEY: +            throw new Crypt_GPG_DeletePrivateKeyException( +                'Private key must be deleted before public key can be ' . +                'deleted.', $code, $keyId); +        default: +            throw new Crypt_GPG_Exception( +                'Unknown error deleting public key. Please use the ' . +                '\'debug\' option when creating the Crypt_GPG object, and ' . +                'file a bug report at ' . self::BUG_URI, $code); +        } +    } + +    // }}} +    // {{{ deletePrivateKey() + +    /** +     * Deletes a private key from the keyring +     * +     * If more than one key fingerprint is available for the specified +     * <kbd>$keyId</kbd> (for example, if you use a non-unique uid) only the +     * first private key is deleted. +     * +     * Calls GPG with the <kbd>--delete-secret-key</kbd> command. +     * +     * @param string $keyId either the full uid of the private key, the email +     *                      part of the uid of the private key or the key id of +     *                      the private key. For example, +     *                      "Test User (example) <test@example.com>", +     *                      "test@example.com" or a hexadecimal string. +     * +     * @return void +     * +     * @throws Crypt_GPG_KeyNotFoundException if a private key with the given +     *         <kbd>$keyId</kbd> is not found. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    public function deletePrivateKey($keyId) +    { +        $fingerprint = $this->getFingerprint($keyId); + +        if ($fingerprint === null) { +            throw new Crypt_GPG_KeyNotFoundException( +                'Private key not found: ' . $keyId, +                Crypt_GPG::ERROR_KEY_NOT_FOUND, $keyId); +        } + +        $operation = '--delete-secret-key ' . escapeshellarg($fingerprint); +        $arguments = array( +            '--batch', +            '--yes' +        ); + +        $this->engine->reset(); +        $this->engine->setOperation($operation, $arguments); +        $this->engine->run(); + +        $code = $this->engine->getErrorCode(); + +        switch ($code) { +        case Crypt_GPG::ERROR_NONE: +            break; +        case Crypt_GPG::ERROR_KEY_NOT_FOUND: +            throw new Crypt_GPG_KeyNotFoundException( +                'Private key not found: ' . $keyId, +                $code, $keyId); +        default: +            throw new Crypt_GPG_Exception( +                'Unknown error deleting private key. Please use the ' . +                '\'debug\' option when creating the Crypt_GPG object, and ' . +                'file a bug report at ' . self::BUG_URI, $code); +        } +    } + +    // }}} +    // {{{ getKeys() + +    /** +     * Gets the available keys in the keyring +     * +     * Calls GPG with the <kbd>--list-keys</kbd> command and grabs keys. See +     * the first section of <b>doc/DETAILS</b> in the +     * {@link http://www.gnupg.org/download/ GPG package} for a detailed +     * description of how the GPG command output is parsed. +     * +     * @param string $keyId optional. Only keys with that match the specified +     *                      pattern are returned. The pattern may be part of +     *                      a user id, a key id or a key fingerprint. If not +     *                      specified, all keys are returned. +     * +     * @return array an array of {@link Crypt_GPG_Key} objects. If no keys +     *               match the specified <kbd>$keyId</kbd> an empty array is +     *               returned. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     * +     * @see Crypt_GPG_Key +     */ +    public function getKeys($keyId = '') +    { +        // get private key fingerprints +        if ($keyId == '') { +            $operation = '--list-secret-keys'; +        } else { +            $operation = '--list-secret-keys ' . escapeshellarg($keyId); +        } + +        // According to The file 'doc/DETAILS' in the GnuPG distribution, using +        // double '--with-fingerprint' also prints the fingerprint for subkeys. +        $arguments = array( +            '--with-colons', +            '--with-fingerprint', +            '--with-fingerprint', +            '--fixed-list-mode' +        ); + +        $output = ''; + +        $this->engine->reset(); +        $this->engine->setOutput($output); +        $this->engine->setOperation($operation, $arguments); +        $this->engine->run(); + +        $code = $this->engine->getErrorCode(); + +        switch ($code) { +        case Crypt_GPG::ERROR_NONE: +        case Crypt_GPG::ERROR_KEY_NOT_FOUND: +            // ignore not found key errors +            break; +        case Crypt_GPG::ERROR_FILE_PERMISSIONS: +            $filename = $this->engine->getErrorFilename(); +            if ($filename) { +                throw new Crypt_GPG_FileException(sprintf( +                    'Error reading GnuPG data file \'%s\'. Check to make ' . +                    'sure it is readable by the current user.', $filename), +                    $code, $filename); +            } +            throw new Crypt_GPG_FileException( +                'Error reading GnuPG data file. Check to make GnuPG data ' . +                'files are readable by the current user.', $code); +        default: +            throw new Crypt_GPG_Exception( +                'Unknown error getting keys. Please use the \'debug\' option ' . +                'when creating the Crypt_GPG object, and file a bug report ' . +                'at ' . self::BUG_URI, $code); +        } + +        $privateKeyFingerprints = array(); + +        $lines = explode(PHP_EOL, $output); +        foreach ($lines as $line) { +            $lineExp = explode(':', $line); +            if ($lineExp[0] == 'fpr') { +                $privateKeyFingerprints[] = $lineExp[9]; +            } +        } + +        // get public keys +        if ($keyId == '') { +            $operation = '--list-public-keys'; +        } else { +            $operation = '--list-public-keys ' . escapeshellarg($keyId); +        } + +        $output = ''; + +        $this->engine->reset(); +        $this->engine->setOutput($output); +        $this->engine->setOperation($operation, $arguments); +        $this->engine->run(); + +        $code = $this->engine->getErrorCode(); + +        switch ($code) { +        case Crypt_GPG::ERROR_NONE: +        case Crypt_GPG::ERROR_KEY_NOT_FOUND: +            // ignore not found key errors +            break; +        case Crypt_GPG::ERROR_FILE_PERMISSIONS: +            $filename = $this->engine->getErrorFilename(); +            if ($filename) { +                throw new Crypt_GPG_FileException(sprintf( +                    'Error reading GnuPG data file \'%s\'. Check to make ' . +                    'sure it is readable by the current user.', $filename), +                    $code, $filename); +            } +            throw new Crypt_GPG_FileException( +                'Error reading GnuPG data file. Check to make GnuPG data ' . +                'files are readable by the current user.', $code); +        default: +            throw new Crypt_GPG_Exception( +                'Unknown error getting keys. Please use the \'debug\' option ' . +                'when creating the Crypt_GPG object, and file a bug report ' . +                'at ' . self::BUG_URI, $code); +        } + +        $keys = array(); + +        $key    = null; // current key +        $subKey = null; // current sub-key + +        $lines = explode(PHP_EOL, $output); +        foreach ($lines as $line) { +            $lineExp = explode(':', $line); + +            if ($lineExp[0] == 'pub') { + +                // new primary key means last key should be added to the array +                if ($key !== null) { +                    $keys[] = $key; +                } + +                $key = new Crypt_GPG_Key(); + +                $subKey = Crypt_GPG_SubKey::parse($line); +                $key->addSubKey($subKey); + +            } elseif ($lineExp[0] == 'sub') { + +                $subKey = Crypt_GPG_SubKey::parse($line); +                $key->addSubKey($subKey); + +            } elseif ($lineExp[0] == 'fpr') { + +                $fingerprint = $lineExp[9]; + +                // set current sub-key fingerprint +                $subKey->setFingerprint($fingerprint); + +                // if private key exists, set has private to true +                if (in_array($fingerprint, $privateKeyFingerprints)) { +                    $subKey->setHasPrivate(true); +                } + +            } elseif ($lineExp[0] == 'uid') { + +                $string = stripcslashes($lineExp[9]); // as per documentation +                $userId = new Crypt_GPG_UserId($string); + +                if ($lineExp[1] == 'r') { +                    $userId->setRevoked(true); +                } + +                $key->addUserId($userId); + +            } +        } + +        // add last key +        if ($key !== null) { +            $keys[] = $key; +        } + +        return $keys; +    } + +    // }}} +    // {{{ getFingerprint() + +    /** +     * Gets a key fingerprint from the keyring +     * +     * If more than one key fingerprint is available (for example, if you use +     * a non-unique user id) only the first key fingerprint is returned. +     * +     * Calls the GPG <kbd>--list-keys</kbd> command with the +     * <kbd>--with-fingerprint</kbd> option to retrieve a public key +     * fingerprint. +     * +     * @param string  $keyId  either the full user id of the key, the email +     *                        part of the user id of the key, or the key id of +     *                        the key. For example, +     *                        "Test User (example) <test@example.com>", +     *                        "test@example.com" or a hexadecimal string. +     * @param integer $format optional. How the fingerprint should be formatted. +     *                        Use {@link Crypt_GPG::FORMAT_X509} for X.509 +     *                        certificate format, +     *                        {@link Crypt_GPG::FORMAT_CANONICAL} for the format +     *                        used by GnuPG output and +     *                        {@link Crypt_GPG::FORMAT_NONE} for no formatting. +     *                        Defaults to <code>Crypt_GPG::FORMAT_NONE</code>. +     * +     * @return string the fingerprint of the key, or null if no fingerprint +     *                is found for the given <kbd>$keyId</kbd>. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    public function getFingerprint($keyId, $format = Crypt_GPG::FORMAT_NONE) +    { +        $output    = ''; +        $operation = '--list-keys ' . escapeshellarg($keyId); +        $arguments = array( +            '--with-colons', +            '--with-fingerprint' +        ); + +        $this->engine->reset(); +        $this->engine->setOutput($output); +        $this->engine->setOperation($operation, $arguments); +        $this->engine->run(); + +        $code = $this->engine->getErrorCode(); + +        switch ($code) { +        case Crypt_GPG::ERROR_NONE: +        case Crypt_GPG::ERROR_KEY_NOT_FOUND: +            // ignore not found key errors +            break; +        default: +            throw new Crypt_GPG_Exception( +                'Unknown error getting key fingerprint. Please use the ' . +                '\'debug\' option when creating the Crypt_GPG object, and ' . +                'file a bug report at ' . self::BUG_URI, $code); +        } + +        $fingerprint = null; + +        $lines = explode(PHP_EOL, $output); +        foreach ($lines as $line) { +            if (substr($line, 0, 3) == 'fpr') { +                $lineExp     = explode(':', $line); +                $fingerprint = $lineExp[9]; + +                switch ($format) { +                case Crypt_GPG::FORMAT_CANONICAL: +                    $fingerprintExp = str_split($fingerprint, 4); +                    $format         = '%s %s %s %s %s  %s %s %s %s %s'; +                    $fingerprint    = vsprintf($format, $fingerprintExp); +                    break; + +                case Crypt_GPG::FORMAT_X509: +                    $fingerprintExp = str_split($fingerprint, 2); +                    $fingerprint    = implode(':', $fingerprintExp); +                    break; +                } + +                break; +            } +        } + +        return $fingerprint; +    } + +    // }}} +    // {{{ encrypt() + +    /** +     * Encrypts string data +     * +     * Data is ASCII armored by default but may optionally be returned as +     * binary. +     * +     * @param string  $data  the data to be encrypted. +     * @param boolean $armor optional. If true, ASCII armored data is returned; +     *                       otherwise, binary data is returned. Defaults to +     *                       true. +     * +     * @return string the encrypted data. +     * +     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified. +     *         See {@link Crypt_GPG::addEncryptKey()}. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     * +     * @sensitive $data +     */ +    public function encrypt($data, $armor = true) +    { +        return $this->_encrypt($data, false, null, $armor); +    } + +    // }}} +    // {{{ encryptFile() + +    /** +     * Encrypts a file +     * +     * Encrypted data is ASCII armored by default but may optionally be saved +     * as binary. +     * +     * @param string  $filename      the filename of the file to encrypt. +     * @param string  $encryptedFile optional. The filename of the file in +     *                               which to store the encrypted data. If null +     *                               or unspecified, the encrypted data is +     *                               returned as a string. +     * @param boolean $armor         optional. If true, ASCII armored data is +     *                               returned; otherwise, binary data is +     *                               returned. Defaults to true. +     * +     * @return void|string if the <kbd>$encryptedFile</kbd> parameter is null, +     *                     a string containing the encrypted data is returned. +     * +     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified. +     *         See {@link Crypt_GPG::addEncryptKey()}. +     * +     * @throws Crypt_GPG_FileException if the output file is not writeable or +     *         if the input file is not readable. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    public function encryptFile($filename, $encryptedFile = null, $armor = true) +    { +        return $this->_encrypt($filename, true, $encryptedFile, $armor); +    } + +    // }}} +    // {{{ encryptAndSign() + +    /** +     * Encrypts and signs data +     * +     * Data is encrypted and signed in a single pass. +     * +     * NOTE: Until GnuPG version 1.4.10, it was not possible to verify +     * encrypted-signed data without decrypting it at the same time. If you try +     * to use {@link Crypt_GPG::verify()} method on encrypted-signed data with +     * earlier GnuPG versions, you will get an error. Please use +     * {@link Crypt_GPG::decryptAndVerify()} to verify encrypted-signed data. +     * +     * @param string  $data  the data to be encrypted and signed. +     * @param boolean $armor optional. If true, ASCII armored data is returned; +     *                       otherwise, binary data is returned. Defaults to +     *                       true. +     * +     * @return string the encrypted signed data. +     * +     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified +     *         or if no signing key is specified. See +     *         {@link Crypt_GPG::addEncryptKey()} and +     *         {@link Crypt_GPG::addSignKey()}. +     * +     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is +     *         incorrect or if a required passphrase is not specified. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     * +     * @see Crypt_GPG::decryptAndVerify() +     */ +    public function encryptAndSign($data, $armor = true) +    { +        return $this->_encryptAndSign($data, false, null, $armor); +    } + +    // }}} +    // {{{ encryptAndSignFile() + +    /** +     * Encrypts and signs a file +     * +     * The file is encrypted and signed in a single pass. +     * +     * NOTE: Until GnuPG version 1.4.10, it was not possible to verify +     * encrypted-signed files without decrypting them at the same time. If you +     * try to use {@link Crypt_GPG::verify()} method on encrypted-signed files +     * with earlier GnuPG versions, you will get an error. Please use +     * {@link Crypt_GPG::decryptAndVerifyFile()} to verify encrypted-signed +     * files. +     * +     * @param string  $filename   the name of the file containing the data to +     *                            be encrypted and signed. +     * @param string  $signedFile optional. The name of the file in which the +     *                            encrypted, signed data should be stored. If +     *                            null or unspecified, the encrypted, signed +     *                            data is returned as a string. +     * @param boolean $armor      optional. If true, ASCII armored data is +     *                            returned; otherwise, binary data is returned. +     *                            Defaults to true. +     * +     * @return void|string if the <kbd>$signedFile</kbd> parameter is null, a +     *                     string containing the encrypted, signed data is +     *                     returned. +     * +     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified +     *         or if no signing key is specified. See +     *         {@link Crypt_GPG::addEncryptKey()} and +     *         {@link Crypt_GPG::addSignKey()}. +     * +     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is +     *         incorrect or if a required passphrase is not specified. +     * +     * @throws Crypt_GPG_FileException if the output file is not writeable or +     *         if the input file is not readable. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     * +     * @see Crypt_GPG::decryptAndVerifyFile() +     */ +    public function encryptAndSignFile($filename, $signedFile = null, +        $armor = true +    ) { +        return $this->_encryptAndSign($filename, true, $signedFile, $armor); +    } + +    // }}} +    // {{{ decrypt() + +    /** +     * Decrypts string data +     * +     * This method assumes the required private key is available in the keyring +     * and throws an exception if the private key is not available. To add a +     * private key to the keyring, use the {@link Crypt_GPG::importKey()} or +     * {@link Crypt_GPG::importKeyFile()} methods. +     * +     * @param string $encryptedData the data to be decrypted. +     * +     * @return string the decrypted data. +     * +     * @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 <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    public function decrypt($encryptedData) +    { +        return $this->_decrypt($encryptedData, false, null); +    } + +    // }}} +    // {{{ decryptFile() + +    /** +     * Decrypts a file +     * +     * This method assumes the required private key is available in the keyring +     * and throws an exception if the private key is not available. To add a +     * private key to the keyring, use the {@link Crypt_GPG::importKey()} or +     * {@link Crypt_GPG::importKeyFile()} methods. +     * +     * @param string $encryptedFile the name of the encrypted file data to +     *                              decrypt. +     * @param string $decryptedFile optional. The name of the file to which the +     *                              decrypted data should be written. If null +     *                              or unspecified, the decrypted data is +     *                              returned as a string. +     * +     * @return void|string if the <kbd>$decryptedFile</kbd> parameter is null, +     *                     a string containing the decrypted data is returned. +     * +     * @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_FileException if the output file is not writeable or +     *         if the input file is not readable. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    public function decryptFile($encryptedFile, $decryptedFile = null) +    { +        return $this->_decrypt($encryptedFile, true, $decryptedFile); +    } + +    // }}} +    // {{{ decryptAndVerify() + +    /** +     * Decrypts and verifies string data +     * +     * This method assumes the required private key is available in the keyring +     * and throws an exception if the private key is not available. To add a +     * private key to the keyring, use the {@link Crypt_GPG::importKey()} or +     * {@link Crypt_GPG::importKeyFile()} methods. +     * +     * @param string $encryptedData the encrypted, signed data to be decrypted +     *                              and verified. +     * +     * @return array two element array. The array has an element 'data' +     *               containing the decrypted data and an element +     *               'signatures' containing an array of +     *               {@link Crypt_GPG_Signature} objects for the signed data. +     * +     * @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 <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    public function decryptAndVerify($encryptedData) +    { +        return $this->_decryptAndVerify($encryptedData, false, null); +    } + +    // }}} +    // {{{ decryptAndVerifyFile() + +    /** +     * Decrypts and verifies a signed, encrypted file +     * +     * This method assumes the required private key is available in the keyring +     * and throws an exception if the private key is not available. To add a +     * private key to the keyring, use the {@link Crypt_GPG::importKey()} or +     * {@link Crypt_GPG::importKeyFile()} methods. +     * +     * @param string $encryptedFile the name of the signed, encrypted file to +     *                              to decrypt and verify. +     * @param string $decryptedFile optional. The name of the file to which the +     *                              decrypted data should be written. If null +     *                              or unspecified, the decrypted data is +     *                              returned in the results array. +     * +     * @return array two element array. The array has an element 'data' +     *               containing the decrypted data and an element +     *               'signatures' containing an array of +     *               {@link Crypt_GPG_Signature} objects for the signed data. +     *               If the decrypted data is written to a file, the 'data' +     *               element is null. +     * +     * @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_FileException if the output file is not writeable or +     *         if the input file is not readable. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    public function decryptAndVerifyFile($encryptedFile, $decryptedFile = null) +    { +        return $this->_decryptAndVerify($encryptedFile, true, $decryptedFile); +    } + +    // }}} +    // {{{ sign() + +    /** +     * Signs data +     * +     * Data may be signed using any one of the three available signing modes: +     * - {@link Crypt_GPG::SIGN_MODE_NORMAL} +     * - {@link Crypt_GPG::SIGN_MODE_CLEAR} +     * - {@link Crypt_GPG::SIGN_MODE_DETACHED} +     * +     * @param string  $data     the data to be signed. +     * @param boolean $mode     optional. The data signing mode to use. Should +     *                          be one of {@link Crypt_GPG::SIGN_MODE_NORMAL}, +     *                          {@link Crypt_GPG::SIGN_MODE_CLEAR} or +     *                          {@link Crypt_GPG::SIGN_MODE_DETACHED}. If not +     *                          specified, defaults to +     *                          <kbd>Crypt_GPG::SIGN_MODE_NORMAL</kbd>. +     * @param boolean $armor    optional. If true, ASCII armored data is +     *                          returned; otherwise, binary data is returned. +     *                          Defaults to true. This has no effect if the +     *                          mode <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is +     *                          used. +     * @param boolean $textmode optional. If true, line-breaks in signed data +     *                          are normalized. Use this option when signing +     *                          e-mail, or for greater compatibility between +     *                          systems with different line-break formats. +     *                          Defaults to false. This has no effect if the +     *                          mode <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is +     *                          used as clear-signing always uses textmode. +     * +     * @return string the signed data, or the signature data if a detached +     *                signature is requested. +     * +     * @throws Crypt_GPG_KeyNotFoundException if no signing key is specified. +     *         See {@link Crypt_GPG::addSignKey()}. +     * +     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is +     *         incorrect or if a required passphrase is not specified. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    public function sign($data, $mode = Crypt_GPG::SIGN_MODE_NORMAL, +        $armor = true, $textmode = false +    ) { +        return $this->_sign($data, false, null, $mode, $armor, $textmode); +    } + +    // }}} +    // {{{ signFile() + +    /** +     * Signs a file +     * +     * The file may be signed using any one of the three available signing +     * modes: +     * - {@link Crypt_GPG::SIGN_MODE_NORMAL} +     * - {@link Crypt_GPG::SIGN_MODE_CLEAR} +     * - {@link Crypt_GPG::SIGN_MODE_DETACHED} +     * +     * @param string  $filename   the name of the file containing the data to +     *                            be signed. +     * @param string  $signedFile optional. The name of the file in which the +     *                            signed data should be stored. If null or +     *                            unspecified, the signed data is returned as a +     *                            string. +     * @param boolean $mode       optional. The data signing mode to use. Should +     *                            be one of {@link Crypt_GPG::SIGN_MODE_NORMAL}, +     *                            {@link Crypt_GPG::SIGN_MODE_CLEAR} or +     *                            {@link Crypt_GPG::SIGN_MODE_DETACHED}. If not +     *                            specified, defaults to +     *                            <kbd>Crypt_GPG::SIGN_MODE_NORMAL</kbd>. +     * @param boolean $armor      optional. If true, ASCII armored data is +     *                            returned; otherwise, binary data is returned. +     *                            Defaults to true. This has no effect if the +     *                            mode <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is +     *                            used. +     * @param boolean $textmode   optional. If true, line-breaks in signed data +     *                            are normalized. Use this option when signing +     *                            e-mail, or for greater compatibility between +     *                            systems with different line-break formats. +     *                            Defaults to false. This has no effect if the +     *                            mode <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is +     *                            used as clear-signing always uses textmode. +     * +     * @return void|string if the <kbd>$signedFile</kbd> parameter is null, a +     *                     string containing the signed data (or the signature +     *                     data if a detached signature is requested) is +     *                     returned. +     * +     * @throws Crypt_GPG_KeyNotFoundException if no signing key is specified. +     *         See {@link Crypt_GPG::addSignKey()}. +     * +     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is +     *         incorrect or if a required passphrase is not specified. +     * +     * @throws Crypt_GPG_FileException if the output file is not writeable or +     *         if the input file is not readable. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    public function signFile($filename, $signedFile = null, +        $mode = Crypt_GPG::SIGN_MODE_NORMAL, $armor = true, $textmode = false +    ) { +        return $this->_sign( +            $filename, +            true, +            $signedFile, +            $mode, +            $armor, +            $textmode +        ); +    } + +    // }}} +    // {{{ verify() + +    /** +     * Verifies signed data +     * +     * The {@link Crypt_GPG::decrypt()} method may be used to get the original +     * message if the signed data is not clearsigned and does not use a +     * detached signature. +     * +     * @param string $signedData the signed data to be verified. +     * @param string $signature  optional. If verifying data signed using a +     *                           detached signature, this must be the detached +     *                           signature data. The data that was signed is +     *                           specified in <kbd>$signedData</kbd>. +     * +     * @return array an array of {@link Crypt_GPG_Signature} objects for the +     *               signed data. For each signature that is valid, the +     *               {@link Crypt_GPG_Signature::isValid()} will return true. +     * +     * @throws Crypt_GPG_NoDataException if the provided data is not signed +     *         data. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     * +     * @see Crypt_GPG_Signature +     */ +    public function verify($signedData, $signature = '') +    { +        return $this->_verify($signedData, false, $signature); +    } + +    // }}} +    // {{{ verifyFile() + +    /** +     * Verifies a signed file +     * +     * The {@link Crypt_GPG::decryptFile()} method may be used to get the +     * original message if the signed data is not clearsigned and does not use +     * a detached signature. +     * +     * @param string $filename  the signed file to be verified. +     * @param string $signature optional. If verifying a file signed using a +     *                          detached signature, this must be the detached +     *                          signature data. The file that was signed is +     *                          specified in <kbd>$filename</kbd>. +     * +     * @return array an array of {@link Crypt_GPG_Signature} objects for the +     *               signed data. For each signature that is valid, the +     *               {@link Crypt_GPG_Signature::isValid()} will return true. +     * +     * @throws Crypt_GPG_NoDataException if the provided data is not signed +     *         data. +     * +     * @throws Crypt_GPG_FileException if the input file is not readable. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     * +     * @see Crypt_GPG_Signature +     */ +    public function verifyFile($filename, $signature = '') +    { +        return $this->_verify($filename, true, $signature); +    } + +    // }}} +    // {{{ addDecryptKey() + +    /** +     * Adds a key to use for decryption +     * +     * @param mixed  $key        the key to use. This may be a key identifier, +     *                           user id, fingerprint, {@link Crypt_GPG_Key} or +     *                           {@link Crypt_GPG_SubKey}. The key must be able +     *                           to encrypt. +     * @param string $passphrase optional. The passphrase of the key required +     *                           for decryption. +     * +     * @return void +     * +     * @see Crypt_GPG::decrypt() +     * @see Crypt_GPG::decryptFile() +     * @see Crypt_GPG::clearDecryptKeys() +     * @see Crypt_GPG::_addKey() +     * @see Crypt_GPG_DecryptStatusHandler +     * +     * @sensitive $passphrase +     */ +    public function addDecryptKey($key, $passphrase = null) +    { +        $this->_addKey($this->decryptKeys, true, false, $key, $passphrase); +    } + +    // }}} +    // {{{ addEncryptKey() + +    /** +     * Adds a key to use for encryption +     * +     * @param mixed $key the key to use. This may be a key identifier, user id +     *                   user id, fingerprint, {@link Crypt_GPG_Key} or +     *                   {@link Crypt_GPG_SubKey}. The key must be able to +     *                   encrypt. +     * +     * @return void +     * +     * @see Crypt_GPG::encrypt() +     * @see Crypt_GPG::encryptFile() +     * @see Crypt_GPG::clearEncryptKeys() +     * @see Crypt_GPG::_addKey() +     */ +    public function addEncryptKey($key) +    { +        $this->_addKey($this->encryptKeys, true, false, $key); +    } + +    // }}} +    // {{{ addSignKey() + +    /** +     * Adds a key to use for signing +     * +     * @param mixed  $key        the key to use. This may be a key identifier, +     *                           user id, fingerprint, {@link Crypt_GPG_Key} or +     *                           {@link Crypt_GPG_SubKey}. The key must be able +     *                           to sign. +     * @param string $passphrase optional. The passphrase of the key required +     *                           for signing. +     * +     * @return void +     * +     * @see Crypt_GPG::sign() +     * @see Crypt_GPG::signFile() +     * @see Crypt_GPG::clearSignKeys() +     * @see Crypt_GPG::handleSignStatus() +     * @see Crypt_GPG::_addKey() +     * +     * @sensitive $passphrase +     */ +    public function addSignKey($key, $passphrase = null) +    { +        $this->_addKey($this->signKeys, false, true, $key, $passphrase); +    } + +    // }}} +    // {{{ clearDecryptKeys() + +    /** +     * Clears all decryption keys +     * +     * @return void +     * +     * @see Crypt_GPG::decrypt() +     * @see Crypt_GPG::addDecryptKey() +     */ +    public function clearDecryptKeys() +    { +        $this->decryptKeys = array(); +    } + +    // }}} +    // {{{ clearEncryptKeys() + +    /** +     * Clears all encryption keys +     * +     * @return void +     * +     * @see Crypt_GPG::encrypt() +     * @see Crypt_GPG::addEncryptKey() +     */ +    public function clearEncryptKeys() +    { +        $this->encryptKeys = array(); +    } + +    // }}} +    // {{{ clearSignKeys() + +    /** +     * Clears all signing keys +     * +     * @return void +     * +     * @see Crypt_GPG::sign() +     * @see Crypt_GPG::addSignKey() +     */ +    public function clearSignKeys() +    { +        $this->signKeys = array(); +    } + +    // }}} +    // {{{ handleSignStatus() + +    /** +     * 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; +        } +    } + +    // }}} +    // {{{ handleImportKeyStatus() + +    /** +     * Handles the status output from GPG for the import operation +     * +     * This method is responsible for building the result array that is +     * returned from the {@link Crypt_GPG::importKey()} 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. +     * @param array  &$result the current result array being processed. +     * +     * @return void +     * +     * @see Crypt_GPG::importKey() +     * @see Crypt_GPG::importKeyFile() +     * @see Crypt_GPG_Engine::addStatusHandler() +     */ +    public function handleImportKeyStatus($line, array &$result) +    { +        $tokens = explode(' ', $line); +        switch ($tokens[0]) { +        case 'IMPORT_OK': +            $result['fingerprint'] = $tokens[2]; +            break; + +        case 'IMPORT_RES': +            $result['public_imported']   = intval($tokens[3]); +            $result['public_unchanged']  = intval($tokens[5]); +            $result['private_imported']  = intval($tokens[11]); +            $result['private_unchanged'] = intval($tokens[12]); +            break; +        } +    } + +    // }}} +    // {{{ setEngine() + +    /** +     * Sets the I/O engine to use for GnuPG operations +     * +     * Normally this method does not need to be used. It provides a means for +     * dependency injection. +     * +     * @param Crypt_GPG_Engine $engine the engine to use. +     * +     * @return void +     */ +    public function setEngine(Crypt_GPG_Engine $engine) +    { +        $this->engine = $engine; +    } + +    // }}} +    // {{{ _addKey() + +    /** +     * Adds a key to one of the internal key arrays +     * +     * This handles resolving full key objects from the provided +     * <kbd>$key</kbd> value. +     * +     * @param array   &$array     the array to which the key should be added. +     * @param boolean $encrypt    whether or not the key must be able to +     *                            encrypt. +     * @param boolean $sign       whether or not the key must be able to sign. +     * @param mixed   $key        the key to add. This may be a key identifier, +     *                            user id, fingerprint, {@link Crypt_GPG_Key} or +     *                            {@link Crypt_GPG_SubKey}. +     * @param string  $passphrase optional. The passphrase associated with the +     *                            key. +     * +     * @return void +     * +     * @sensitive $passphrase +     */ +    private function _addKey(array &$array, $encrypt, $sign, $key, +        $passphrase = null +    ) { +        $subKeys = array(); + +        if (is_scalar($key)) { +            $keys = $this->getKeys($key); +            if (count($keys) == 0) { +                throw new Crypt_GPG_KeyNotFoundException( +                    'Key "' . $key . '" not found.', 0, $key); +            } +            $key = $keys[0]; +        } + +        if ($key instanceof Crypt_GPG_Key) { +            if ($encrypt && !$key->canEncrypt()) { +                throw new InvalidArgumentException( +                    'Key "' . $key . '" cannot encrypt.'); +            } + +            if ($sign && !$key->canSign()) { +                throw new InvalidArgumentException( +                    'Key "' . $key . '" cannot sign.'); +            } + +            foreach ($key->getSubKeys() as $subKey) { +                $canEncrypt = $subKey->canEncrypt(); +                $canSign    = $subKey->canSign(); +                if (   ($encrypt && $sign && $canEncrypt && $canSign) +                    || ($encrypt && !$sign && $canEncrypt) +                    || (!$encrypt && $sign && $canSign) +                ) { +                    // We add all subkeys that meet the requirements because we +                    // were not told which subkey is required. +                    $subKeys[] = $subKey; +                } +            } +        } elseif ($key instanceof Crypt_GPG_SubKey) { +            $subKeys[] = $key; +        } + +        if (count($subKeys) === 0) { +            throw new InvalidArgumentException( +                'Key "' . $key . '" is not in a recognized format.'); +        } + +        foreach ($subKeys as $subKey) { +            if ($encrypt && !$subKey->canEncrypt()) { +                throw new InvalidArgumentException( +                    'Key "' . $key . '" cannot encrypt.'); +            } + +            if ($sign && !$subKey->canSign()) { +                throw new InvalidArgumentException( +                    'Key "' . $key . '" cannot sign.'); +            } + +            $array[$subKey->getId()] = array( +                'fingerprint' => $subKey->getFingerprint(), +                'passphrase'  => $passphrase +            ); +        } +    } + +    // }}} +    // {{{ _importKey() + +    /** +     * Imports a public or private key into the keyring +     * +     * @param string  $key    the key to be imported. +     * @param boolean $isFile whether or not the input is a filename. +     * +     * @return array an associative array containing the following elements: +     *               - <kbd>fingerprint</kbd>       - the fingerprint of the +     *                                                imported key, +     *               - <kbd>public_imported</kbd>   - the number of public +     *                                                keys imported, +     *               - <kbd>public_unchanged</kbd>  - the number of unchanged +     *                                                public keys, +     *               - <kbd>private_imported</kbd>  - the number of private +     *                                                keys imported, +     *               - <kbd>private_unchanged</kbd> - the number of unchanged +     *                                                private keys. +     * +     * @throws Crypt_GPG_NoDataException if the key data is missing or if the +     *         data is is not valid key data. +     * +     * @throws Crypt_GPG_FileException if the key file is not readable. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    private function _importKey($key, $isFile) +    { +        $result = array(); + +        if ($isFile) { +            $input = @fopen($key, 'rb'); +            if ($input === false) { +                throw new Crypt_GPG_FileException('Could not open key file "' . +                    $key . '" for importing.', 0, $key); +            } +        } else { +            $input = strval($key); +            if ($input == '') { +                throw new Crypt_GPG_NoDataException( +                    'No valid GPG key data found.', Crypt_GPG::ERROR_NO_DATA); +            } +        } + +        $arguments = array(); +        $version   = $this->engine->getVersion(); + +        if (   version_compare($version, '1.0.5', 'ge') +            && version_compare($version, '1.0.7', 'lt') +        ) { +            $arguments[] = '--allow-secret-key-import'; +        } + +        $this->engine->reset(); +        $this->engine->addStatusHandler( +            array($this, 'handleImportKeyStatus'), +            array(&$result) +        ); + +        $this->engine->setOperation('--import', $arguments); +        $this->engine->setInput($input); +        $this->engine->run(); + +        if ($isFile) { +            fclose($input); +        } + +        $code = $this->engine->getErrorCode(); + +        switch ($code) { +        case Crypt_GPG::ERROR_DUPLICATE_KEY: +        case Crypt_GPG::ERROR_NONE: +            // ignore duplicate key import errors +            break; +        case Crypt_GPG::ERROR_NO_DATA: +            throw new Crypt_GPG_NoDataException( +                'No valid GPG key data found.', $code); +        default: +            throw new Crypt_GPG_Exception( +                'Unknown error importing GPG key. Please use the \'debug\' ' . +                'option when creating the Crypt_GPG object, and file a bug ' . +                'report at ' . self::BUG_URI, $code); +        } + +        return $result; +    } + +    // }}} +    // {{{ _encrypt() + +    /** +     * Encrypts data +     * +     * @param string  $data       the data to encrypt. +     * @param boolean $isFile     whether or not the data is a filename. +     * @param string  $outputFile the filename of the file in which to store +     *                            the encrypted data. If null, the encrypted +     *                            data is returned as a string. +     * @param boolean $armor      if true, ASCII armored data is returned; +     *                            otherwise, binary data is returned. +     * +     * @return void|string if the <kbd>$outputFile</kbd> parameter is null, a +     *                     string containing the encrypted data is returned. +     * +     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified. +     *         See {@link Crypt_GPG::addEncryptKey()}. +     * +     * @throws Crypt_GPG_FileException if the output file is not writeable or +     *         if the input file is not readable. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    private function _encrypt($data, $isFile, $outputFile, $armor) +    { +        if (count($this->encryptKeys) === 0) { +            throw new Crypt_GPG_KeyNotFoundException( +                'No encryption keys specified.'); +        } + +        if ($isFile) { +            $input = @fopen($data, 'rb'); +            if ($input === false) { +                throw new Crypt_GPG_FileException('Could not open input file "' . +                    $data . '" for encryption.', 0, $data); +            } +        } else { +            $input = strval($data); +        } + +        if ($outputFile === null) { +            $output = ''; +        } else { +            $output = @fopen($outputFile, 'wb'); +            if ($output === false) { +                if ($isFile) { +                    fclose($input); +                } +                throw new Crypt_GPG_FileException('Could not open output ' . +                    'file "' . $outputFile . '" for storing encrypted data.', +                    0, $outputFile); +            } +        } + +        $arguments = ($armor) ? array('--armor') : array(); +        foreach ($this->encryptKeys as $key) { +            $arguments[] = '--recipient ' . escapeshellarg($key['fingerprint']); +        } + +        $this->engine->reset(); +        $this->engine->setInput($input); +        $this->engine->setOutput($output); +        $this->engine->setOperation('--encrypt', $arguments); +        $this->engine->run(); + +        if ($isFile) { +            fclose($input); +        } + +        if ($outputFile !== null) { +            fclose($output); +        } + +        $code = $this->engine->getErrorCode(); + +        if ($code !== Crypt_GPG::ERROR_NONE) { +            throw new Crypt_GPG_Exception( +                'Unknown error encrypting data. Please use the \'debug\' ' . +                'option when creating the Crypt_GPG object, and file a bug ' . +                'report at ' . self::BUG_URI, $code); +        } + +        if ($outputFile === null) { +            return $output; +        } +    } + +    // }}} +    // {{{ _decrypt() + +    /** +     * Decrypts data +     * +     * @param string  $data       the data to be decrypted. +     * @param boolean $isFile     whether or not the data is a filename. +     * @param string  $outputFile the name of the file to which the decrypted +     *                            data should be written. If null, the decrypted +     *                            data is returned as a string. +     * +     * @return void|string if the <kbd>$outputFile</kbd> parameter is null, a +     *                     string containing the decrypted data is returned. +     * +     * @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_FileException if the output file is not writeable or +     *         if the input file is not readable. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    private function _decrypt($data, $isFile, $outputFile) +    { +        if ($isFile) { +            $input = @fopen($data, 'rb'); +            if ($input === false) { +                throw new Crypt_GPG_FileException('Could not open input file "' . +                    $data . '" for decryption.', 0, $data); +            } +        } else { +            $input = strval($data); +            if ($input == '') { +                throw new Crypt_GPG_NoDataException( +                    'Cannot decrypt data. No PGP encrypted data was found in '. +                    'the provided data.', Crypt_GPG::ERROR_NO_DATA); +            } +        } + +        if ($outputFile === null) { +            $output = ''; +        } else { +            $output = @fopen($outputFile, 'wb'); +            if ($output === false) { +                if ($isFile) { +                    fclose($input); +                } +                throw new Crypt_GPG_FileException('Could not open output ' . +                    'file "' . $outputFile . '" for storing decrypted data.', +                    0, $outputFile); +            } +        } + +        $handler = new Crypt_GPG_DecryptStatusHandler($this->engine, +            $this->decryptKeys); + +        $this->engine->reset(); +        $this->engine->addStatusHandler(array($handler, 'handle')); +        $this->engine->setOperation('--decrypt'); +        $this->engine->setInput($input); +        $this->engine->setOutput($output); +        $this->engine->run(); + +        if ($isFile) { +            fclose($input); +        } + +        if ($outputFile !== null) { +            fclose($output); +        } + +        // if there was any problem decrypting the data, the handler will +        // deal with it here. +        $handler->throwException(); + +        if ($outputFile === null) { +            return $output; +        } +    } + +    // }}} +    // {{{ _sign() + +    /** +     * Signs data +     * +     * @param string  $data       the data to be signed. +     * @param boolean $isFile     whether or not the data is a filename. +     * @param string  $outputFile the name of the file in which the signed data +     *                            should be stored. If null, the signed data is +     *                            returned as a string. +     * @param boolean $mode       the data signing mode to use. Should be one of +     *                            {@link Crypt_GPG::SIGN_MODE_NORMAL}, +     *                            {@link Crypt_GPG::SIGN_MODE_CLEAR} or +     *                            {@link Crypt_GPG::SIGN_MODE_DETACHED}. +     * @param boolean $armor      if true, ASCII armored data is returned; +     *                            otherwise, binary data is returned. This has +     *                            no effect if the mode +     *                            <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is +     *                            used. +     * @param boolean $textmode   if true, line-breaks in signed data be +     *                            normalized. Use this option when signing +     *                            e-mail, or for greater compatibility between +     *                            systems with different line-break formats. +     *                            Defaults to false. This has no effect if the +     *                            mode <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is +     *                            used as clear-signing always uses textmode. +     * +     * @return void|string if the <kbd>$outputFile</kbd> parameter is null, a +     *                     string containing the signed data (or the signature +     *                     data if a detached signature is requested) is +     *                     returned. +     * +     * @throws Crypt_GPG_KeyNotFoundException if no signing key is specified. +     *         See {@link Crypt_GPG::addSignKey()}. +     * +     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is +     *         incorrect or if a required passphrase is not specified. +     * +     * @throws Crypt_GPG_FileException if the output file is not writeable or +     *         if the input file is not readable. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    private function _sign($data, $isFile, $outputFile, $mode, $armor, +        $textmode +    ) { +        if (count($this->signKeys) === 0) { +            throw new Crypt_GPG_KeyNotFoundException( +                'No signing keys specified.'); +        } + +        if ($isFile) { +            $input = @fopen($data, 'rb'); +            if ($input === false) { +                throw new Crypt_GPG_FileException('Could not open input ' . +                    'file "' . $data . '" for signing.', 0, $data); +            } +        } else { +            $input = strval($data); +        } + +        if ($outputFile === null) { +            $output = ''; +        } else { +            $output = @fopen($outputFile, 'wb'); +            if ($output === false) { +                if ($isFile) { +                    fclose($input); +                } +                throw new Crypt_GPG_FileException('Could not open output ' . +                    'file "' . $outputFile . '" for storing signed ' . +                    'data.', 0, $outputFile); +            } +        } + +        switch ($mode) { +        case Crypt_GPG::SIGN_MODE_DETACHED: +            $operation = '--detach-sign'; +            break; +        case Crypt_GPG::SIGN_MODE_CLEAR: +            $operation = '--clearsign'; +            break; +        case Crypt_GPG::SIGN_MODE_NORMAL: +        default: +            $operation = '--sign'; +            break; +        } + +        $arguments  = array(); + +        if ($armor) { +            $arguments[] = '--armor'; +        } +        if ($textmode) { +            $arguments[] = '--textmode'; +        } + +        foreach ($this->signKeys as $key) { +            $arguments[] = '--local-user ' . +                escapeshellarg($key['fingerprint']); +        } + +        $this->engine->reset(); +        $this->engine->addStatusHandler(array($this, 'handleSignStatus')); +        $this->engine->setInput($input); +        $this->engine->setOutput($output); +        $this->engine->setOperation($operation, $arguments); +        $this->engine->run(); + +        if ($isFile) { +            fclose($input); +        } + +        if ($outputFile !== null) { +            fclose($output); +        } + +        $code = $this->engine->getErrorCode(); + +        switch ($code) { +        case Crypt_GPG::ERROR_NONE: +            break; +        case Crypt_GPG::ERROR_KEY_NOT_FOUND: +            throw new Crypt_GPG_KeyNotFoundException( +                'Cannot sign data. Private key not found. Import the '. +                'private key before trying to sign data.', $code, +                $this->engine->getErrorKeyId()); +        case Crypt_GPG::ERROR_BAD_PASSPHRASE: +            throw new Crypt_GPG_BadPassphraseException( +                'Cannot sign data. Incorrect passphrase provided.', $code); +        case Crypt_GPG::ERROR_MISSING_PASSPHRASE: +            throw new Crypt_GPG_BadPassphraseException( +                'Cannot sign data. No passphrase provided.', $code); +        default: +            throw new Crypt_GPG_Exception( +                'Unknown error signing data. Please use the \'debug\' option ' . +                'when creating the Crypt_GPG object, and file a bug report ' . +                'at ' . self::BUG_URI, $code); +        } + +        if ($outputFile === null) { +            return $output; +        } +    } + +    // }}} +    // {{{ _encryptAndSign() + +    /** +     * Encrypts and signs data +     * +     * @param string  $data       the data to be encrypted and signed. +     * @param boolean $isFile     whether or not the data is a filename. +     * @param string  $outputFile the name of the file in which the encrypted, +     *                            signed data should be stored. If null, the +     *                            encrypted, signed data is returned as a +     *                            string. +     * @param boolean $armor      if true, ASCII armored data is returned; +     *                            otherwise, binary data is returned. +     * +     * @return void|string if the <kbd>$outputFile</kbd> parameter is null, a +     *                     string containing the encrypted, signed data is +     *                     returned. +     * +     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified +     *         or if no signing key is specified. See +     *         {@link Crypt_GPG::addEncryptKey()} and +     *         {@link Crypt_GPG::addSignKey()}. +     * +     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is +     *         incorrect or if a required passphrase is not specified. +     * +     * @throws Crypt_GPG_FileException if the output file is not writeable or +     *         if the input file is not readable. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     */ +    private function _encryptAndSign($data, $isFile, $outputFile, $armor) +    { +        if (count($this->signKeys) === 0) { +            throw new Crypt_GPG_KeyNotFoundException( +                'No signing keys specified.'); +        } + +        if (count($this->encryptKeys) === 0) { +            throw new Crypt_GPG_KeyNotFoundException( +                'No encryption keys specified.'); +        } + + +        if ($isFile) { +            $input = @fopen($data, 'rb'); +            if ($input === false) { +                throw new Crypt_GPG_FileException('Could not open input ' . +                    'file "' . $data . '" for encrypting and signing.', 0, +                    $data); +            } +        } else { +            $input = strval($data); +        } + +        if ($outputFile === null) { +            $output = ''; +        } else { +            $output = @fopen($outputFile, 'wb'); +            if ($output === false) { +                if ($isFile) { +                    fclose($input); +                } +                throw new Crypt_GPG_FileException('Could not open output ' . +                    'file "' . $outputFile . '" for storing encrypted, ' . +                    'signed data.', 0, $outputFile); +            } +        } + +        $arguments  = ($armor) ? array('--armor') : array(); + +        foreach ($this->signKeys as $key) { +            $arguments[] = '--local-user ' . +                escapeshellarg($key['fingerprint']); +        } + +        foreach ($this->encryptKeys as $key) { +            $arguments[] = '--recipient ' . escapeshellarg($key['fingerprint']); +        } + +        $this->engine->reset(); +        $this->engine->addStatusHandler(array($this, 'handleSignStatus')); +        $this->engine->setInput($input); +        $this->engine->setOutput($output); +        $this->engine->setOperation('--encrypt --sign', $arguments); +        $this->engine->run(); + +        if ($isFile) { +            fclose($input); +        } + +        if ($outputFile !== null) { +            fclose($output); +        } + +        $code = $this->engine->getErrorCode(); + +        switch ($code) { +        case Crypt_GPG::ERROR_NONE: +            break; +        case Crypt_GPG::ERROR_KEY_NOT_FOUND: +            throw new Crypt_GPG_KeyNotFoundException( +                'Cannot sign encrypted data. Private key not found. Import '. +                'the private key before trying to sign the encrypted data.', +                $code, $this->engine->getErrorKeyId()); +        case Crypt_GPG::ERROR_BAD_PASSPHRASE: +            throw new Crypt_GPG_BadPassphraseException( +                'Cannot sign encrypted data. Incorrect passphrase provided.', +                $code); +        case Crypt_GPG::ERROR_MISSING_PASSPHRASE: +            throw new Crypt_GPG_BadPassphraseException( +                'Cannot sign encrypted data. No passphrase provided.', $code); +        default: +            throw new Crypt_GPG_Exception( +                'Unknown error encrypting and signing data. Please use the ' . +                '\'debug\' option when creating the Crypt_GPG object, and ' . +                'file a bug report at ' . self::BUG_URI, $code); +        } + +        if ($outputFile === null) { +            return $output; +        } +    } + +    // }}} +    // {{{ _verify() + +    /** +     * Verifies data +     * +     * @param string  $data      the signed data to be verified. +     * @param boolean $isFile    whether or not the data is a filename. +     * @param string  $signature if verifying a file signed using a detached +     *                           signature, this must be the detached signature +     *                           data. Otherwise, specify ''. +     * +     * @return array an array of {@link Crypt_GPG_Signature} objects for the +     *               signed data. +     * +     * @throws Crypt_GPG_NoDataException if the provided data is not signed +     *         data. +     * +     * @throws Crypt_GPG_FileException if the input file is not readable. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     * +     * @see Crypt_GPG_Signature +     */ +    private function _verify($data, $isFile, $signature) +    { +        if ($signature == '') { +            $operation = '--verify'; +            $arguments = array(); +        } else { +            // Signed data goes in FD_MESSAGE, detached signature data goes in +            // FD_INPUT. +            $operation = '--verify - "-&' . Crypt_GPG_Engine::FD_MESSAGE. '"'; +            $arguments = array('--enable-special-filenames'); +        } + +        $handler = new Crypt_GPG_VerifyStatusHandler(); + +        if ($isFile) { +            $input = @fopen($data, 'rb'); +            if ($input === false) { +                throw new Crypt_GPG_FileException('Could not open input ' . +                    'file "' . $data . '" for verifying.', 0, $data); +            } +        } else { +            $input = strval($data); +            if ($input == '') { +                throw new Crypt_GPG_NoDataException( +                    'No valid signature data found.', Crypt_GPG::ERROR_NO_DATA); +            } +        } + +        $this->engine->reset(); +        $this->engine->addStatusHandler(array($handler, 'handle')); + +        if ($signature == '') { +            // signed or clearsigned data +            $this->engine->setInput($input); +        } else { +            // detached signature +            $this->engine->setInput($signature); +            $this->engine->setMessage($input); +        } + +        $this->engine->setOperation($operation, $arguments); +        $this->engine->run(); + +        if ($isFile) { +            fclose($input); +        } + +        $code = $this->engine->getErrorCode(); + +        switch ($code) { +        case Crypt_GPG::ERROR_NONE: +        case Crypt_GPG::ERROR_BAD_SIGNATURE: +            break; +        case Crypt_GPG::ERROR_NO_DATA: +            throw new Crypt_GPG_NoDataException( +                'No valid signature data found.', $code); +        case Crypt_GPG::ERROR_KEY_NOT_FOUND: +            throw new Crypt_GPG_KeyNotFoundException( +                'Public key required for data verification not in keyring.', +                $code, $this->engine->getErrorKeyId()); +        default: +            throw new Crypt_GPG_Exception( +                'Unknown error validating signature details. Please use the ' . +                '\'debug\' option when creating the Crypt_GPG object, and ' . +                'file a bug report at ' . self::BUG_URI, $code); +        } + +        return $handler->getSignatures(); +    } + +    // }}} +    // {{{ _decryptAndVerify() + +    /** +     * Decrypts and verifies encrypted, signed data +     * +     * @param string  $data       the encrypted signed data to be decrypted and +     *                            verified. +     * @param boolean $isFile     whether or not the data is a filename. +     * @param string  $outputFile the name of the file to which the decrypted +     *                            data should be written. If null, the decrypted +     *                            data is returned in the results array. +     * +     * @return array two element array. The array has an element 'data' +     *               containing the decrypted data and an element +     *               'signatures' containing an array of +     *               {@link Crypt_GPG_Signature} objects for the signed data. +     *               If the decrypted data is written to a file, the 'data' +     *               element is null. +     * +     * @throws Crypt_GPG_KeyNotFoundException if the private key needed to +     *         decrypt the data is not in the user's keyring or it the public +     *         key needed for verification is not in the user's keyring. +     * +     * @throws Crypt_GPG_NoDataException if specified data does not contain +     *         GPG signed, 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_FileException if the output file is not writeable or +     *         if the input file is not readable. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     * +     * @see Crypt_GPG_Signature +     */ +    private function _decryptAndVerify($data, $isFile, $outputFile) +    { +        if ($isFile) { +            $input = @fopen($data, 'rb'); +            if ($input === false) { +                throw new Crypt_GPG_FileException('Could not open input ' . +                    'file "' . $data . '" for decrypting and verifying.', 0, +                    $data); +            } +        } else { +            $input = strval($data); +            if ($input == '') { +                throw new Crypt_GPG_NoDataException( +                    'No valid encrypted signed data found.', +                    Crypt_GPG::ERROR_NO_DATA); +            } +        } + +        if ($outputFile === null) { +            $output = ''; +        } else { +            $output = @fopen($outputFile, 'wb'); +            if ($output === false) { +                if ($isFile) { +                    fclose($input); +                } +                throw new Crypt_GPG_FileException('Could not open output ' . +                    'file "' . $outputFile . '" for storing decrypted data.', +                    0, $outputFile); +            } +        } + +        $verifyHandler = new Crypt_GPG_VerifyStatusHandler(); + +        $decryptHandler = new Crypt_GPG_DecryptStatusHandler($this->engine, +            $this->decryptKeys); + +        $this->engine->reset(); +        $this->engine->addStatusHandler(array($verifyHandler, 'handle')); +        $this->engine->addStatusHandler(array($decryptHandler, 'handle')); +        $this->engine->setInput($input); +        $this->engine->setOutput($output); +        $this->engine->setOperation('--decrypt'); +        $this->engine->run(); + +        if ($isFile) { +            fclose($input); +        } + +        if ($outputFile !== null) { +            fclose($output); +        } + +        $return = array( +            'data'       => null, +            'signatures' => $verifyHandler->getSignatures() +        ); + +        // if there was any problem decrypting the data, the handler will +        // deal with it here. +        try { +            $decryptHandler->throwException(); +        } catch (Exception $e) { +            if ($e instanceof Crypt_GPG_KeyNotFoundException) { +                throw new Crypt_GPG_KeyNotFoundException( +                    'Public key required for data verification not in ', +                    'the keyring. Either no suitable private decryption key ' . +                    'is in the keyring or the public key required for data ' . +                    'verification is not in the keyring. Import a suitable ' . +                    'key before trying to decrypt and verify this data.', +                    self::ERROR_KEY_NOT_FOUND, $this->engine->getErrorKeyId()); +            } + +            if ($e instanceof Crypt_GPG_NoDataException) { +                throw new Crypt_GPG_NoDataException( +                    'Cannot decrypt and verify data. No PGP encrypted data ' . +                    'was found in the provided data.', self::ERROR_NO_DATA); +            } + +            throw $e; +        } + +        if ($outputFile === null) { +            $return['data'] = $output; +        } + +        return $return; +    } + +    // }}} +} + +// }}} + +?> diff --git a/plugins/enigma/lib/Crypt/GPG/DecryptStatusHandler.php b/plugins/enigma/lib/Crypt/GPG/DecryptStatusHandler.php new file mode 100644 index 000000000..40e8d50ed --- /dev/null +++ b/plugins/enigma/lib/Crypt/GPG/DecryptStatusHandler.php @@ -0,0 +1,336 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Crypt_GPG is a package to use GPG from PHP + * + * This file contains an object that handles GPG'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-2009 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version   CVS: $Id: DecryptStatusHandler.php 302814 2010-08-26 15:43:07Z gauthierm $ + * @link      http://pear.php.net/package/Crypt_GPG + * @link      http://www.gnupg.org/ + */ + +/** + * Crypt_GPG base class + */ +require_once 'Crypt/GPG.php'; + +/** + * 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/ GPG distribution} for detailed + * information on GPG'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 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); +        } +    } + +    // }}} +} + +?> diff --git a/plugins/enigma/lib/Crypt/GPG/Engine.php b/plugins/enigma/lib/Crypt/GPG/Engine.php new file mode 100644 index 000000000..081be8e21 --- /dev/null +++ b/plugins/enigma/lib/Crypt/GPG/Engine.php @@ -0,0 +1,1758 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Crypt_GPG is a package to use GPG from PHP + * + * This file contains an engine that handles GPG subprocess control and I/O. + * PHP's process manipulation functions are used to handle the GPG subprocess. + * + * 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    Nathan Fredrickson <nathan@silverorange.com> + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2005-2010 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version   CVS: $Id: Engine.php 302822 2010-08-26 17:30:57Z gauthierm $ + * @link      http://pear.php.net/package/Crypt_GPG + * @link      http://www.gnupg.org/ + */ + +/** + * Crypt_GPG base class. + */ +require_once 'Crypt/GPG.php'; + +/** + * GPG exception classes. + */ +require_once 'Crypt/GPG/Exceptions.php'; + +/** + * Standard PEAR exception is used if GPG binary is not found. + */ +require_once 'PEAR/Exception.php'; + +// {{{ class Crypt_GPG_Engine + +/** + * Native PHP Crypt_GPG I/O engine + * + * 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 engine uses PHP's native process control functions to directly control + * the GPG process. The GPG executable is required to be on the system. + * + * All data is passed to the GPG subprocess using file descriptors. This is the + * most secure method of passing data to the GPG subprocess. + * + * @category  Encryption + * @package   Crypt_GPG + * @author    Nathan Fredrickson <nathan@silverorange.com> + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2005-2010 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_Engine +{ +    // {{{ constants + +    /** +     * Size of data chunks that are sent to and retrieved from the IPC pipes. +     * +     * PHP reads 8192 bytes. If this is set to less than 8192, PHP reads 8192 +     * and buffers the rest so we might as well just read 8192. +     * +     * Using values other than 8192 also triggers PHP bugs. +     * +     * @see http://bugs.php.net/bug.php?id=35224 +     */ +    const CHUNK_SIZE = 8192; + +    /** +     * Standard input file descriptor. This is used to pass data to the GPG +     * process. +     */ +    const FD_INPUT = 0; + +    /** +     * Standard output file descriptor. This is used to receive normal output +     * from the GPG process. +     */ +    const FD_OUTPUT = 1; + +    /** +     * Standard output file descriptor. This is used to receive error output +     * from the GPG process. +     */ +    const FD_ERROR = 2; + +    /** +     * GPG status output file descriptor. The status file descriptor outputs +     * detailed information for many GPG commands. See the second section of +     * the file <b>doc/DETAILS</b> in the +     * {@link http://www.gnupg.org/download/ GPG package} for a detailed +     * description of GPG's status output. +     */ +    const FD_STATUS = 3; + +    /** +     * Command input file descriptor. This is used for methods requiring +     * passphrases. +     */ +    const FD_COMMAND = 4; + +    /** +     * Extra message input file descriptor. This is used for passing signed +     * data when verifying a detached signature. +     */ +    const FD_MESSAGE = 5; + +    /** +     * Minimum version of GnuPG that is supported. +     */ +    const MIN_VERSION = '1.0.2'; + +    // }}} +    // {{{ private class properties + +    /** +     * Whether or not to use debugging mode +     * +     * When set to true, every GPG command is echoed before it is run. Sensitive +     * data is always handled using pipes and is not specified as part of the +     * command. As a result, sensitive data is never displayed when debug is +     * enabled. Sensitive data includes private key data and passphrases. +     * +     * Debugging is off by default. +     * +     * @var boolean +     * @see Crypt_GPG_Engine::__construct() +     */ +    private $_debug = false; + +    /** +     * Location of GPG binary +     * +     * @var string +     * @see Crypt_GPG_Engine::__construct() +     * @see Crypt_GPG_Engine::_getBinary() +     */ +    private $_binary = ''; + +    /** +     * Directory containing the GPG key files +     * +     * This property only contains the path when the <i>homedir</i> option +     * is specified in the constructor. +     * +     * @var string +     * @see Crypt_GPG_Engine::__construct() +     */ +    private $_homedir = ''; + +    /** +     * File path of the public keyring +     * +     * This property only contains the file path when the <i>public_keyring</i> +     * option is specified in the constructor. +     * +     * If the specified file path starts with <kbd>~/</kbd>, the path is +     * relative to the <i>homedir</i> if specified, otherwise to +     * <kbd>~/.gnupg</kbd>. +     * +     * @var string +     * @see Crypt_GPG_Engine::__construct() +     */ +    private $_publicKeyring = ''; + +    /** +     * File path of the private (secret) keyring +     * +     * This property only contains the file path when the <i>private_keyring</i> +     * option is specified in the constructor. +     * +     * If the specified file path starts with <kbd>~/</kbd>, the path is +     * relative to the <i>homedir</i> if specified, otherwise to +     * <kbd>~/.gnupg</kbd>. +     * +     * @var string +     * @see Crypt_GPG_Engine::__construct() +     */ +    private $_privateKeyring = ''; + +    /** +     * File path of the trust database +     * +     * This property only contains the file path when the <i>trust_db</i> +     * option is specified in the constructor. +     * +     * If the specified file path starts with <kbd>~/</kbd>, the path is +     * relative to the <i>homedir</i> if specified, otherwise to +     * <kbd>~/.gnupg</kbd>. +     * +     * @var string +     * @see Crypt_GPG_Engine::__construct() +     */ +    private $_trustDb = ''; + +    /** +     * Array of pipes used for communication with the GPG binary +     * +     * This is an array of file descriptor resources. +     * +     * @var array +     */ +    private $_pipes = array(); + +    /** +     * Array of currently opened pipes +     * +     * This array is used to keep track of remaining opened pipes so they can +     * be closed when the GPG subprocess is finished. This array is a subset of +     * the {@link Crypt_GPG_Engine::$_pipes} array and contains opened file +     * descriptor resources. +     * +     * @var array +     * @see Crypt_GPG_Engine::_closePipe() +     */ +    private $_openPipes = array(); + +    /** +     * A handle for the GPG process +     * +     * @var resource +     */ +    private $_process = null; + +    /** +     * Whether or not the operating system is Darwin (OS X) +     * +     * @var boolean +     */ +    private $_isDarwin = false; + +    /** +     * Commands to be sent to GPG's command input stream +     * +     * @var string +     * @see Crypt_GPG_Engine::sendCommand() +     */ +    private $_commandBuffer = ''; + +    /** +     * Array of status line handlers +     * +     * @var array +     * @see Crypt_GPG_Engine::addStatusHandler() +     */ +    private $_statusHandlers = array(); + +    /** +     * Array of error line handlers +     * +     * @var array +     * @see Crypt_GPG_Engine::addErrorHandler() +     */ +    private $_errorHandlers = array(); + +    /** +     * The error code of the current operation +     * +     * @var integer +     * @see Crypt_GPG_Engine::getErrorCode() +     */ +    private $_errorCode = Crypt_GPG::ERROR_NONE; + +    /** +     * File related to the error code of the current operation +     * +     * @var string +     * @see Crypt_GPG_Engine::getErrorFilename() +     */ +    private $_errorFilename = ''; + +    /** +     * Key id related to the error code of the current operation +     * +     * @var string +     * @see Crypt_GPG_Engine::getErrorKeyId() +     */ +    private $_errorkeyId = ''; + +    /** +     * The number of currently needed passphrases +     * +     * If this is not zero when the GPG command is completed, the error code is +     * set to {@link Crypt_GPG::ERROR_MISSING_PASSPHRASE}. +     * +     * @var integer +     */ +    private $_needPassphrase = 0; + +    /** +     * The input source +     * +     * This is data to send to GPG. Either a string or a stream resource. +     * +     * @var string|resource +     * @see Crypt_GPG_Engine::setInput() +     */ +    private $_input = null; + +    /** +     * The extra message input source +     * +     * Either a string or a stream resource. +     * +     * @var string|resource +     * @see Crypt_GPG_Engine::setMessage() +     */ +    private $_message = null; + +    /** +     * The output location +     * +     * This is where the output from GPG is sent. Either a string or a stream +     * resource. +     * +     * @var string|resource +     * @see Crypt_GPG_Engine::setOutput() +     */ +    private $_output = ''; + +    /** +     * The GPG operation to execute +     * +     * @var string +     * @see Crypt_GPG_Engine::setOperation() +     */ +    private $_operation; + +    /** +     * Arguments for the current operation +     * +     * @var array +     * @see Crypt_GPG_Engine::setOperation() +     */ +    private $_arguments = array(); + +    /** +     * The version number of the GPG binary +     * +     * @var string +     * @see Crypt_GPG_Engine::getVersion() +     */ +    private $_version = ''; + +    /** +     * Cached value indicating whether or not mbstring function overloading is +     * on for strlen +     * +     * This is cached for optimal performance inside the I/O loop. +     * +     * @var boolean +     * @see Crypt_GPG_Engine::_byteLength() +     * @see Crypt_GPG_Engine::_byteSubstring() +     */ +    private static $_mbStringOverload = null; + +    // }}} +    // {{{ __construct() + +    /** +     * Creates a new GPG engine +     * +     * Available options are: +     * +     * - <kbd>string  homedir</kbd>        - the directory where the GPG +     *                                       keyring files are stored. If not +     *                                       specified, Crypt_GPG uses the +     *                                       default of <kbd>~/.gnupg</kbd>. +     * - <kbd>string  publicKeyring</kbd>  - the file path of the public +     *                                       keyring. Use this if the public +     *                                       keyring is not in the homedir, or +     *                                       if the keyring is in a directory +     *                                       not writable by the process +     *                                       invoking GPG (like Apache). Then +     *                                       you can specify the path to the +     *                                       keyring with this option +     *                                       (/foo/bar/pubring.gpg), and specify +     *                                       a writable directory (like /tmp) +     *                                       using the <i>homedir</i> option. +     * - <kbd>string  privateKeyring</kbd> - the file path of the private +     *                                       keyring. Use this if the private +     *                                       keyring is not in the homedir, or +     *                                       if the keyring is in a directory +     *                                       not writable by the process +     *                                       invoking GPG (like Apache). Then +     *                                       you can specify the path to the +     *                                       keyring with this option +     *                                       (/foo/bar/secring.gpg), and specify +     *                                       a writable directory (like /tmp) +     *                                       using the <i>homedir</i> option. +     * - <kbd>string  trustDb</kbd>        - the file path of the web-of-trust +     *                                       database. Use this if the trust +     *                                       database is not in the homedir, or +     *                                       if the database is in a directory +     *                                       not writable by the process +     *                                       invoking GPG (like Apache). Then +     *                                       you can specify the path to the +     *                                       trust database with this option +     *                                       (/foo/bar/trustdb.gpg), and specify +     *                                       a writable directory (like /tmp) +     *                                       using the <i>homedir</i> option. +     * - <kbd>string  binary</kbd>         - the location of the GPG binary. If +     *                                       not specified, the driver attempts +     *                                       to auto-detect the GPG binary +     *                                       location using a list of known +     *                                       default locations for the current +     *                                       operating system. The option +     *                                       <kbd>gpgBinary</kbd> is a +     *                                       deprecated alias for this option. +     * - <kbd>boolean debug</kbd>          - whether or not to use debug mode. +     *                                       When debug mode is on, all +     *                                       communication to and from the GPG +     *                                       subprocess is logged. This can be +     *                                       useful to diagnose errors when +     *                                       using Crypt_GPG. +     * +     * @param array $options optional. An array of options used to create the +     *                       GPG object. All options are optional and are +     *                       represented as key-value pairs. +     * +     * @throws Crypt_GPG_FileException if the <kbd>homedir</kbd> does not exist +     *         and cannot be created. This can happen if <kbd>homedir</kbd> is +     *         not specified, Crypt_GPG is run as the web user, and the web +     *         user has no home directory. This exception is also thrown if any +     *         of the options <kbd>publicKeyring</kbd>, +     *         <kbd>privateKeyring</kbd> or <kbd>trustDb</kbd> options are +     *         specified but the files do not exist or are are not readable. +     *         This can happen if the user running the Crypt_GPG process (for +     *         example, the Apache user) does not have permission to read the +     *         files. +     * +     * @throws PEAR_Exception if the provided <kbd>binary</kbd> is invalid, or +     *         if no <kbd>binary</kbd> is provided and no suitable binary could +     *         be found. +     */ +    public function __construct(array $options = array()) +    { +        $this->_isDarwin = (strncmp(strtoupper(PHP_OS), 'DARWIN', 6) === 0); + +        // populate mbstring overloading cache if not set +        if (self::$_mbStringOverload === null) { +            self::$_mbStringOverload = (extension_loaded('mbstring') +                && (ini_get('mbstring.func_overload') & 0x02) === 0x02); +        } + +        // get homedir +        if (array_key_exists('homedir', $options)) { +            $this->_homedir = (string)$options['homedir']; +        } else { +            // note: this requires the package OS dep exclude 'windows' +            $info = posix_getpwuid(posix_getuid()); +            $this->_homedir = $info['dir'].'/.gnupg'; +        } + +        // attempt to create homedir if it does not exist +        if (!is_dir($this->_homedir)) { +            if (@mkdir($this->_homedir, 0777, true)) { +                // Set permissions on homedir. Parent directories are created +                // with 0777, homedir is set to 0700. +                chmod($this->_homedir, 0700); +            } else { +                throw new Crypt_GPG_FileException('The \'homedir\' "' . +                    $this->_homedir . '" is not readable or does not exist '. +                    'and cannot be created. This can happen if \'homedir\' '. +                    'is not specified in the Crypt_GPG options, Crypt_GPG is '. +                    'run as the web user, and the web user has no home '. +                    'directory.', +                    0, $this->_homedir); +            } +        } + +        // get binary +        if (array_key_exists('binary', $options)) { +            $this->_binary = (string)$options['binary']; +        } elseif (array_key_exists('gpgBinary', $options)) { +            // deprecated alias +            $this->_binary = (string)$options['gpgBinary']; +        } else { +            $this->_binary = $this->_getBinary(); +        } + +        if ($this->_binary == '' || !is_executable($this->_binary)) { +            throw new PEAR_Exception('GPG binary not found. If you are sure '. +                'the GPG binary is installed, please specify the location of '. +                'the GPG binary using the \'binary\' driver option.'); +        } + +        /* +         * Note: +         * +         * Normally, GnuPG expects keyrings to be in the homedir and expects +         * to be able to write temporary files in the homedir. Sometimes, +         * keyrings are not in the homedir, or location of the keyrings does +         * not allow writing temporary files. In this case, the <i>homedir</i> +         * option by itself is not enough to specify the keyrings because GnuPG +         * can not write required temporary files. Additional options are +         * provided so you can specify the location of the keyrings separately +         * from the homedir. +         */ + +        // get public keyring +        if (array_key_exists('publicKeyring', $options)) { +            $this->_publicKeyring = (string)$options['publicKeyring']; +            if (!is_readable($this->_publicKeyring)) { +                 throw new Crypt_GPG_FileException('The \'publicKeyring\' "' . +                    $this->_publicKeyring . '" does not exist or is ' . +                    'not readable. Check the location and ensure the file ' . +                    'permissions are correct.', 0, $this->_publicKeyring); +            } +        } + +        // get private keyring +        if (array_key_exists('privateKeyring', $options)) { +            $this->_privateKeyring = (string)$options['privateKeyring']; +            if (!is_readable($this->_privateKeyring)) { +                 throw new Crypt_GPG_FileException('The \'privateKeyring\' "' . +                    $this->_privateKeyring . '" does not exist or is ' . +                    'not readable. Check the location and ensure the file ' . +                    'permissions are correct.', 0, $this->_privateKeyring); +            } +        } + +        // get trust database +        if (array_key_exists('trustDb', $options)) { +            $this->_trustDb = (string)$options['trustDb']; +            if (!is_readable($this->_trustDb)) { +                 throw new Crypt_GPG_FileException('The \'trustDb\' "' . +                    $this->_trustDb . '" does not exist or is not readable. ' . +                    'Check the location and ensure the file permissions are ' . +                    'correct.', 0, $this->_trustDb); +            } +        } + +        if (array_key_exists('debug', $options)) { +            $this->_debug = (boolean)$options['debug']; +        } +    } + +    // }}} +    // {{{ __destruct() + +    /** +     * Closes open GPG subprocesses when this object is destroyed +     * +     * Subprocesses should never be left open by this class unless there is +     * an unknown error and unexpected script termination occurs. +     */ +    public function __destruct() +    { +        $this->_closeSubprocess(); +    } + +    // }}} +    // {{{ addErrorHandler() + +    /** +     * Adds an error handler method +     * +     * The method is run every time a new error line is received from the GPG +     * subprocess. The handler method must accept the error line to be handled +     * as its first parameter. +     * +     * @param callback $callback the callback method to use. +     * @param array    $args     optional. Additional arguments to pass as +     *                           parameters to the callback method. +     * +     * @return void +     */ +    public function addErrorHandler($callback, array $args = array()) +    { +        $this->_errorHandlers[] = array( +            'callback' => $callback, +            'args'     => $args +        ); +    } + +    // }}} +    // {{{ addStatusHandler() + +    /** +     * Adds a status handler method +     * +     * The method is run every time a new status line is received from the +     * GPG subprocess. The handler method must accept the status line to be +     * handled as its first parameter. +     * +     * @param callback $callback the callback method to use. +     * @param array    $args     optional. Additional arguments to pass as +     *                           parameters to the callback method. +     * +     * @return void +     */ +    public function addStatusHandler($callback, array $args = array()) +    { +        $this->_statusHandlers[] = array( +            'callback' => $callback, +            'args'     => $args +        ); +    } + +    // }}} +    // {{{ sendCommand() + +    /** +     * Sends a command to the GPG subprocess over the command file-descriptor +     * pipe +     * +     * @param string $command the command to send. +     * +     * @return void +     * +     * @sensitive $command +     */ +    public function sendCommand($command) +    { +        if (array_key_exists(self::FD_COMMAND, $this->_openPipes)) { +            $this->_commandBuffer .= $command . PHP_EOL; +        } +    } + +    // }}} +    // {{{ reset() + +    /** +     * Resets the GPG engine, preparing it for a new operation +     * +     * @return void +     * +     * @see Crypt_GPG_Engine::run() +     * @see Crypt_GPG_Engine::setOperation() +     */ +    public function reset() +    { +        $this->_operation      = ''; +        $this->_arguments      = array(); +        $this->_input          = null; +        $this->_message        = null; +        $this->_output         = ''; +        $this->_errorCode      = Crypt_GPG::ERROR_NONE; +        $this->_needPassphrase = 0; +        $this->_commandBuffer  = ''; + +        $this->_statusHandlers = array(); +        $this->_errorHandlers  = array(); + +        $this->addStatusHandler(array($this, '_handleErrorStatus')); +        $this->addErrorHandler(array($this, '_handleErrorError')); + +        if ($this->_debug) { +            $this->addStatusHandler(array($this, '_handleDebugStatus')); +            $this->addErrorHandler(array($this, '_handleDebugError')); +        } +    } + +    // }}} +    // {{{ run() + +    /** +     * Runs the current GPG operation +     * +     * This creates and manages the GPG subprocess. +     * +     * The operation must be set with {@link Crypt_GPG_Engine::setOperation()} +     * before this method is called. +     * +     * @return void +     * +     * @throws Crypt_GPG_InvalidOperationException if no operation is specified. +     * +     * @see Crypt_GPG_Engine::reset() +     * @see Crypt_GPG_Engine::setOperation() +     */ +    public function run() +    { +        if ($this->_operation === '') { +            throw new Crypt_GPG_InvalidOperationException('No GPG operation ' . +                'specified. Use Crypt_GPG_Engine::setOperation() before ' . +                'calling Crypt_GPG_Engine::run().'); +        } + +        $this->_openSubprocess(); +        $this->_process(); +        $this->_closeSubprocess(); +    } + +    // }}} +    // {{{ getErrorCode() + +    /** +     * Gets the error code of the last executed operation +     * +     * This value is only meaningful after {@link Crypt_GPG_Engine::run()} has +     * been executed. +     * +     * @return integer the error code of the last executed operation. +     */ +    public function getErrorCode() +    { +        return $this->_errorCode; +    } + +    // }}} +    // {{{ getErrorFilename() + +    /** +     * Gets the file related to the error code of the last executed operation +     * +     * This value is only meaningful after {@link Crypt_GPG_Engine::run()} has +     * been executed. If there is no file related to the error, an empty string +     * is returned. +     * +     * @return string the file related to the error code of the last executed +     *                operation. +     */ +    public function getErrorFilename() +    { +        return $this->_errorFilename; +    } + +    // }}} +    // {{{ getErrorKeyId() + +    /** +     * Gets the key id related to the error code of the last executed operation +     * +     * This value is only meaningful after {@link Crypt_GPG_Engine::run()} has +     * been executed. If there is no key id related to the error, an empty +     * string is returned. +     * +     * @return string the key id related to the error code of the last executed +     *                operation. +     */ +    public function getErrorKeyId() +    { +        return $this->_errorKeyId; +    } + +    // }}} +    // {{{ setInput() + +    /** +     * Sets the input source for the current GPG operation +     * +     * @param string|resource &$input either a reference to the string +     *                                containing the input data or an open +     *                                stream resource containing the input +     *                                data. +     * +     * @return void +     */ +    public function setInput(&$input) +    { +        $this->_input =& $input; +    } + +    // }}} +    // {{{ setMessage() + +    /** +     * Sets the message source for the current GPG operation +     * +     * Detached signature data should be specified here. +     * +     * @param string|resource &$message either a reference to the string +     *                                  containing the message data or an open +     *                                  stream resource containing the message +     *                                  data. +     * +     * @return void +     */ +    public function setMessage(&$message) +    { +        $this->_message =& $message; +    } + +    // }}} +    // {{{ setOutput() + +    /** +     * Sets the output destination for the current GPG operation +     * +     * @param string|resource &$output either a reference to the string in +     *                                 which to store GPG output or an open +     *                                 stream resource to which the output data +     *                                 should be written. +     * +     * @return void +     */ +    public function setOutput(&$output) +    { +        $this->_output =& $output; +    } + +    // }}} +    // {{{ setOperation() + +    /** +     * Sets the operation to perform +     * +     * @param string $operation the operation to perform. This should be one +     *                          of GPG's operations. For example, +     *                          <kbd>--encrypt</kbd>, <kbd>--decrypt</kbd>, +     *                          <kbd>--sign</kbd>, etc. +     * @param array  $arguments optional. Additional arguments for the GPG +     *                          subprocess. See the GPG manual for specific +     *                          values. +     * +     * @return void +     * +     * @see Crypt_GPG_Engine::reset() +     * @see Crypt_GPG_Engine::run() +     */ +    public function setOperation($operation, array $arguments = array()) +    { +        $this->_operation = $operation; +        $this->_arguments = $arguments; +    } + +    // }}} +    // {{{ getVersion() + +    /** +     * Gets the version of the GnuPG binary +     * +     * @return string a version number string containing the version of GnuPG +     *                being used. This value is suitable to use with PHP's +     *                version_compare() function. +     * +     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs. +     *         Use the <kbd>debug</kbd> option and file a bug report if these +     *         exceptions occur. +     * +     * @throws Crypt_GPG_UnsupportedException if the provided binary is not +     *         GnuPG or if the GnuPG version is less than 1.0.2. +     */ +    public function getVersion() +    { +        if ($this->_version == '') { + +            $options = array( +                'homedir' => $this->_homedir, +                'binary'  => $this->_binary, +                'debug'   => $this->_debug +            ); + +            $engine = new self($options); +            $info   = ''; + +            // Set a garbage version so we do not end up looking up the version +            // recursively. +            $engine->_version = '1.0.0'; + +            $engine->reset(); +            $engine->setOutput($info); +            $engine->setOperation('--version'); +            $engine->run(); + +            $code = $this->getErrorCode(); + +            if ($code !== Crypt_GPG::ERROR_NONE) { +                throw new Crypt_GPG_Exception( +                    'Unknown error getting GnuPG version information. Please ' . +                    'use the \'debug\' option when creating the Crypt_GPG ' . +                    'object, and file a bug report at ' . Crypt_GPG::BUG_URI, +                    $code); +            } + +            $matches    = array(); +            $expression = '/gpg \(GnuPG\) (\S+)/'; + +            if (preg_match($expression, $info, $matches) === 1) { +                $this->_version = $matches[1]; +            } else { +                throw new Crypt_GPG_Exception( +                    'No GnuPG version information provided by the binary "' . +                    $this->_binary . '". Are you sure it is GnuPG?'); +            } + +            if (version_compare($this->_version, self::MIN_VERSION, 'lt')) { +                throw new Crypt_GPG_Exception( +                    'The version of GnuPG being used (' . $this->_version . +                    ') is not supported by Crypt_GPG. The minimum version ' . +                    'required by Crypt_GPG is ' . self::MIN_VERSION); +            } +        } + + +        return $this->_version; +    } + +    // }}} +    // {{{ _handleErrorStatus() + +    /** +     * Handles error values in the status output from GPG +     * +     * This method is responsible for setting the +     * {@link Crypt_GPG_Engine::$_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 +     */ +    private function _handleErrorStatus($line) +    { +        $tokens = explode(' ', $line); +        switch ($tokens[0]) { +        case 'BAD_PASSPHRASE': +            $this->_errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE; +            break; + +        case 'MISSING_PASSPHRASE': +            $this->_errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE; +            break; + +        case 'NODATA': +            $this->_errorCode = Crypt_GPG::ERROR_NO_DATA; +            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_RES': +            if ($tokens[12] > 0) { +                $this->_errorCode = Crypt_GPG::ERROR_DUPLICATE_KEY; +            } +            break; + +        case 'NO_PUBKEY': +        case 'NO_SECKEY': +            $this->_errorKeyId = $tokens[1]; +            $this->_errorCode  = Crypt_GPG::ERROR_KEY_NOT_FOUND; +            break; + +        case 'NEED_PASSPHRASE': +            $this->_needPassphrase++; +            break; + +        case 'GOOD_PASSPHRASE': +            $this->_needPassphrase--; +            break; + +        case 'EXPSIG': +        case 'EXPKEYSIG': +        case 'REVKEYSIG': +        case 'BADSIG': +            $this->_errorCode = Crypt_GPG::ERROR_BAD_SIGNATURE; +            break; + +        } +    } + +    // }}} +    // {{{ _handleErrorError() + +    /** +     * Handles error values in the error output from GPG +     * +     * This method is responsible for setting the +     * {@link Crypt_GPG_Engine::$_errorCode}. +     * +     * @param string $line the error line to handle. +     * +     * @return void +     */ +    private function _handleErrorError($line) +    { +        if ($this->_errorCode === Crypt_GPG::ERROR_NONE) { +            $pattern = '/no valid OpenPGP data found/'; +            if (preg_match($pattern, $line) === 1) { +                $this->_errorCode = Crypt_GPG::ERROR_NO_DATA; +            } +        } + +        if ($this->_errorCode === Crypt_GPG::ERROR_NONE) { +            $pattern = '/No secret key|secret key not available/'; +            if (preg_match($pattern, $line) === 1) { +                $this->_errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND; +            } +        } + +        if ($this->_errorCode === Crypt_GPG::ERROR_NONE) { +            $pattern = '/No public key|public key not found/'; +            if (preg_match($pattern, $line) === 1) { +                $this->_errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND; +            } +        } + +        if ($this->_errorCode === Crypt_GPG::ERROR_NONE) { +            $matches = array(); +            $pattern = '/can\'t (?:access|open) `(.*?)\'/'; +            if (preg_match($pattern, $line, $matches) === 1) { +                $this->_errorFilename = $matches[1]; +                $this->_errorCode = Crypt_GPG::ERROR_FILE_PERMISSIONS; +            } +        } +    } + +    // }}} +    // {{{ _handleDebugStatus() + +    /** +     * Displays debug output for status lines +     * +     * @param string $line the status line to handle. +     * +     * @return void +     */ +    private function _handleDebugStatus($line) +    { +        $this->_debug('STATUS: ' . $line); +    } + +    // }}} +    // {{{ _handleDebugError() + +    /** +     * Displays debug output for error lines +     * +     * @param string $line the error line to handle. +     * +     * @return void +     */ +    private function _handleDebugError($line) +    { +        $this->_debug('ERROR: ' . $line); +    } + +    // }}} +    // {{{ _process() + +    /** +     * Performs internal streaming operations for the subprocess using either +     * strings or streams as input / output points +     * +     * This is the main I/O loop for streaming to and from the GPG subprocess. +     * +     * The implementation of this method is verbose mainly for performance +     * reasons. Adding streams to a lookup array and looping the array inside +     * the main I/O loop would be siginficantly slower for large streams. +     * +     * @return void +     * +     * @throws Crypt_GPG_Exception if there is an error selecting streams for +     *         reading or writing. If this occurs, please file a bug report at +     *         http://pear.php.net/bugs/report.php?package=Crypt_GPG. +     */ +    private function _process() +    { +        $this->_debug('BEGIN PROCESSING'); + +        $this->_commandBuffer = '';    // buffers input to GPG +        $messageBuffer        = '';    // buffers input to GPG +        $inputBuffer          = '';    // buffers input to GPG +        $outputBuffer         = '';    // buffers output from GPG +        $statusBuffer         = '';    // buffers output from GPG +        $errorBuffer          = '';    // buffers output from GPG +        $inputComplete        = false; // input stream is completely buffered +        $messageComplete      = false; // message stream is completely buffered + +        if (is_string($this->_input)) { +            $inputBuffer   = $this->_input; +            $inputComplete = true; +        } + +        if (is_string($this->_message)) { +            $messageBuffer   = $this->_message; +            $messageComplete = true; +        } + +        if (is_string($this->_output)) { +            $outputBuffer =& $this->_output; +        } + +        // convenience variables +        $fdInput   = $this->_pipes[self::FD_INPUT]; +        $fdOutput  = $this->_pipes[self::FD_OUTPUT]; +        $fdError   = $this->_pipes[self::FD_ERROR]; +        $fdStatus  = $this->_pipes[self::FD_STATUS]; +        $fdCommand = $this->_pipes[self::FD_COMMAND]; +        $fdMessage = $this->_pipes[self::FD_MESSAGE]; + +        while (true) { + +            $inputStreams     = array(); +            $outputStreams    = array(); +            $exceptionStreams = array(); + +            // set up input streams +            if (is_resource($this->_input) && !$inputComplete) { +                if (feof($this->_input)) { +                    $inputComplete = true; +                } else { +                    $inputStreams[] = $this->_input; +                } +            } + +            // close GPG input pipe if there is no more data +            if ($inputBuffer == '' && $inputComplete) { +                $this->_debug('=> closing GPG input pipe'); +                $this->_closePipe(self::FD_INPUT); +            } + +            if (is_resource($this->_message) && !$messageComplete) { +                if (feof($this->_message)) { +                    $messageComplete = true; +                } else { +                    $inputStreams[] = $this->_message; +                } +            } + +            // close GPG message pipe if there is no more data +            if ($messageBuffer == '' && $messageComplete) { +                $this->_debug('=> closing GPG message pipe'); +                $this->_closePipe(self::FD_MESSAGE); +            } + +            if (!feof($fdOutput)) { +                $inputStreams[] = $fdOutput; +            } + +            if (!feof($fdStatus)) { +                $inputStreams[] = $fdStatus; +            } + +            if (!feof($fdError)) { +                $inputStreams[] = $fdError; +            } + +            // set up output streams +            if ($outputBuffer != '' && is_resource($this->_output)) { +                $outputStreams[] = $this->_output; +            } + +            if ($this->_commandBuffer != '') { +                $outputStreams[] = $fdCommand; +            } + +            if ($messageBuffer != '') { +                $outputStreams[] = $fdMessage; +            } + +            if ($inputBuffer != '') { +                $outputStreams[] = $fdInput; +            } + +            // no streams left to read or write, we're all done +            if (count($inputStreams) === 0 && count($outputStreams) === 0) { +                break; +            } + +            $this->_debug('selecting streams'); + +            $ready = stream_select( +                $inputStreams, +                $outputStreams, +                $exceptionStreams, +                null +            ); + +            $this->_debug('=> got ' . $ready); + +            if ($ready === false) { +                throw new Crypt_GPG_Exception( +                    'Error selecting stream for communication with GPG ' . +                    'subprocess. Please file a bug report at: ' . +                    'http://pear.php.net/bugs/report.php?package=Crypt_GPG'); +            } + +            if ($ready === 0) { +                throw new Crypt_GPG_Exception( +                    'stream_select() returned 0. This can not happen! Please ' . +                    'file a bug report at: ' . +                    'http://pear.php.net/bugs/report.php?package=Crypt_GPG'); +            } + +            // write input (to GPG) +            if (in_array($fdInput, $outputStreams)) { +                $this->_debug('GPG is ready for input'); + +                $chunk = self::_byteSubstring( +                    $inputBuffer, +                    0, +                    self::CHUNK_SIZE +                ); + +                $length = self::_byteLength($chunk); + +                $this->_debug( +                    '=> about to write ' . $length . ' bytes to GPG input' +                ); + +                $length = fwrite($fdInput, $chunk, $length); + +                $this->_debug('=> wrote ' . $length . ' bytes'); + +                $inputBuffer = self::_byteSubstring( +                    $inputBuffer, +                    $length +                ); +            } + +            // read input (from PHP stream) +            if (in_array($this->_input, $inputStreams)) { +                $this->_debug('input stream is ready for reading'); +                $this->_debug( +                    '=> about to read ' . self::CHUNK_SIZE . +                    ' bytes from input stream' +                ); + +                $chunk        = fread($this->_input, self::CHUNK_SIZE); +                $length       = self::_byteLength($chunk); +                $inputBuffer .= $chunk; + +                $this->_debug('=> read ' . $length . ' bytes'); +            } + +            // write message (to GPG) +            if (in_array($fdMessage, $outputStreams)) { +                $this->_debug('GPG is ready for message data'); + +                $chunk = self::_byteSubstring( +                    $messageBuffer, +                    0, +                    self::CHUNK_SIZE +                ); + +                $length = self::_byteLength($chunk); + +                $this->_debug( +                    '=> about to write ' . $length . ' bytes to GPG message' +                ); + +                $length = fwrite($fdMessage, $chunk, $length); +                $this->_debug('=> wrote ' . $length . ' bytes'); + +                $messageBuffer = self::_byteSubstring($messageBuffer, $length); +            } + +            // read message (from PHP stream) +            if (in_array($this->_message, $inputStreams)) { +                $this->_debug('message stream is ready for reading'); +                $this->_debug( +                    '=> about to read ' . self::CHUNK_SIZE . +                    ' bytes from message stream' +                ); + +                $chunk          = fread($this->_message, self::CHUNK_SIZE); +                $length         = self::_byteLength($chunk); +                $messageBuffer .= $chunk; + +                $this->_debug('=> read ' . $length . ' bytes'); +            } + +            // read output (from GPG) +            if (in_array($fdOutput, $inputStreams)) { +                $this->_debug('GPG output stream ready for reading'); +                $this->_debug( +                    '=> about to read ' . self::CHUNK_SIZE . +                    ' bytes from GPG output' +                ); + +                $chunk         = fread($fdOutput, self::CHUNK_SIZE); +                $length        = self::_byteLength($chunk); +                $outputBuffer .= $chunk; + +                $this->_debug('=> read ' . $length . ' bytes'); +            } + +            // write output (to PHP stream) +            if (in_array($this->_output, $outputStreams)) { +                $this->_debug('output stream is ready for data'); + +                $chunk = self::_byteSubstring( +                    $outputBuffer, +                    0, +                    self::CHUNK_SIZE +                ); + +                $length = self::_byteLength($chunk); + +                $this->_debug( +                    '=> about to write ' . $length . ' bytes to output stream' +                ); + +                $length = fwrite($this->_output, $chunk, $length); + +                $this->_debug('=> wrote ' . $length . ' bytes'); + +                $outputBuffer = self::_byteSubstring($outputBuffer, $length); +            } + +            // read error (from GPG) +            if (in_array($fdError, $inputStreams)) { +                $this->_debug('GPG error stream ready for reading'); +                $this->_debug( +                    '=> about to read ' . self::CHUNK_SIZE . +                    ' bytes from GPG error' +                ); + +                $chunk        = fread($fdError, self::CHUNK_SIZE); +                $length       = self::_byteLength($chunk); +                $errorBuffer .= $chunk; + +                $this->_debug('=> read ' . $length . ' bytes'); + +                // pass lines to error handlers +                while (($pos = strpos($errorBuffer, PHP_EOL)) !== false) { +                    $line = self::_byteSubstring($errorBuffer, 0, $pos); +                    foreach ($this->_errorHandlers as $handler) { +                        array_unshift($handler['args'], $line); +                        call_user_func_array( +                            $handler['callback'], +                            $handler['args'] +                        ); + +                        array_shift($handler['args']); +                    } +                    $errorBuffer = self::_byteSubString( +                        $errorBuffer, +                        $pos + self::_byteLength(PHP_EOL) +                    ); +                } +            } + +            // read status (from GPG) +            if (in_array($fdStatus, $inputStreams)) { +                $this->_debug('GPG status stream ready for reading'); +                $this->_debug( +                    '=> about to read ' . self::CHUNK_SIZE . +                    ' bytes from GPG status' +                ); + +                $chunk         = fread($fdStatus, self::CHUNK_SIZE); +                $length        = self::_byteLength($chunk); +                $statusBuffer .= $chunk; + +                $this->_debug('=> read ' . $length . ' bytes'); + +                // pass lines to status handlers +                while (($pos = strpos($statusBuffer, PHP_EOL)) !== false) { +                    $line = self::_byteSubstring($statusBuffer, 0, $pos); +                    // only pass lines beginning with magic prefix +                    if (self::_byteSubstring($line, 0, 9) == '[GNUPG:] ') { +                        $line = self::_byteSubstring($line, 9); +                        foreach ($this->_statusHandlers as $handler) { +                            array_unshift($handler['args'], $line); +                            call_user_func_array( +                                $handler['callback'], +                                $handler['args'] +                            ); + +                            array_shift($handler['args']); +                        } +                    } +                    $statusBuffer = self::_byteSubString( +                        $statusBuffer, +                        $pos + self::_byteLength(PHP_EOL) +                    ); +                } +            } + +            // write command (to GPG) +            if (in_array($fdCommand, $outputStreams)) { +                $this->_debug('GPG is ready for command data'); + +                // send commands +                $chunk = self::_byteSubstring( +                    $this->_commandBuffer, +                    0, +                    self::CHUNK_SIZE +                ); + +                $length = self::_byteLength($chunk); + +                $this->_debug( +                    '=> about to write ' . $length . ' bytes to GPG command' +                ); + +                $length = fwrite($fdCommand, $chunk, $length); + +                $this->_debug('=> wrote ' . $length); + +                $this->_commandBuffer = self::_byteSubstring( +                    $this->_commandBuffer, +                    $length +                ); +            } + +        } // end loop while streams are open + +        $this->_debug('END PROCESSING'); +    } + +    // }}} +    // {{{ _openSubprocess() + +    /** +     * Opens an internal GPG subprocess for the current operation +     * +     * Opens a GPG subprocess, then connects the subprocess to some pipes. Sets +     * the private class property {@link Crypt_GPG_Engine::$_process} to +     * the new subprocess. +     * +     * @return void +     * +     * @throws Crypt_GPG_OpenSubprocessException if the subprocess could not be +     *         opened. +     * +     * @see Crypt_GPG_Engine::setOperation() +     * @see Crypt_GPG_Engine::_closeSubprocess() +     * @see Crypt_GPG_Engine::$_process +     */ +    private function _openSubprocess() +    { +        $version = $this->getVersion(); + +        $env = $_ENV; + +        // Newer versions of GnuPG return localized results. Crypt_GPG only +        // works with English, so set the locale to 'C' for the subprocess. +        $env['LC_ALL'] = 'C'; + +        $commandLine = $this->_binary; + +        $defaultArguments = array( +            '--status-fd ' . escapeshellarg(self::FD_STATUS), +            '--command-fd ' . escapeshellarg(self::FD_COMMAND), +            '--no-secmem-warning', +            '--no-tty', +            '--no-default-keyring', // ignored if keying files are not specified +            '--no-options'          // prevent creation of ~/.gnupg directory +        ); + +        if (version_compare($version, '1.0.7', 'ge')) { +            if (version_compare($version, '2.0.0', 'lt')) { +                $defaultArguments[] = '--no-use-agent'; +            } +            $defaultArguments[] = '--no-permission-warning'; +        } + +        if (version_compare($version, '1.4.2', 'ge')) { +            $defaultArguments[] = '--exit-on-status-write-error'; +        } + +        if (version_compare($version, '1.3.2', 'ge')) { +            $defaultArguments[] = '--trust-model always'; +        } else { +            $defaultArguments[] = '--always-trust'; +        } + +        $arguments = array_merge($defaultArguments, $this->_arguments); + +        if ($this->_homedir) { +            $arguments[] = '--homedir ' . escapeshellarg($this->_homedir); + +            // the random seed file makes subsequent actions faster so only +            // disable it if we have to. +            if (!is_writeable($this->_homedir)) { +                $arguments[] = '--no-random-seed-file'; +            } +        } + +        if ($this->_publicKeyring) { +            $arguments[] = '--keyring ' . escapeshellarg($this->_publicKeyring); +        } + +        if ($this->_privateKeyring) { +            $arguments[] = '--secret-keyring ' . +                escapeshellarg($this->_privateKeyring); +        } + +        if ($this->_trustDb) { +            $arguments[] = '--trustdb-name ' . escapeshellarg($this->_trustDb); +        } + +        $commandLine .= ' ' . implode(' ', $arguments) . ' ' . +            $this->_operation; + +        // Binary operations will not work on Windows with PHP < 5.2.6. This is +        // in case stream_select() ever works on Windows. +        $rb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'r' : 'rb'; +        $wb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'w' : 'wb'; + +        $descriptorSpec = array( +            self::FD_INPUT   => array('pipe', $rb), // stdin +            self::FD_OUTPUT  => array('pipe', $wb), // stdout +            self::FD_ERROR   => array('pipe', $wb), // stderr +            self::FD_STATUS  => array('pipe', $wb), // status +            self::FD_COMMAND => array('pipe', $rb), // command +            self::FD_MESSAGE => array('pipe', $rb)  // message +        ); + +        $this->_debug('OPENING SUBPROCESS WITH THE FOLLOWING COMMAND:'); +        $this->_debug($commandLine); + +        $this->_process = proc_open( +            $commandLine, +            $descriptorSpec, +            $this->_pipes, +            null, +            $env, +            array('binary_pipes' => true) +        ); + +        if (!is_resource($this->_process)) { +            throw new Crypt_GPG_OpenSubprocessException( +                'Unable to open GPG subprocess.', 0, $commandLine); +        } + +        $this->_openPipes = $this->_pipes; +        $this->_errorCode = Crypt_GPG::ERROR_NONE; +    } + +    // }}} +    // {{{ _closeSubprocess() + +    /** +     * Closes a the internal GPG subprocess +     * +     * Closes the internal GPG subprocess. Sets the private class property +     * {@link Crypt_GPG_Engine::$_process} to null. +     * +     * @return void +     * +     * @see Crypt_GPG_Engine::_openSubprocess() +     * @see Crypt_GPG_Engine::$_process +     */ +    private function _closeSubprocess() +    { +        if (is_resource($this->_process)) { +            $this->_debug('CLOSING SUBPROCESS'); + +            // close remaining open pipes +            foreach (array_keys($this->_openPipes) as $pipeNumber) { +                $this->_closePipe($pipeNumber); +            } + +            $exitCode = proc_close($this->_process); + +            if ($exitCode != 0) { +                $this->_debug( +                    '=> subprocess returned an unexpected exit code: ' . +                    $exitCode +                ); + +                if ($this->_errorCode === Crypt_GPG::ERROR_NONE) { +                    if ($this->_needPassphrase > 0) { +                        $this->_errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE; +                    } else { +                        $this->_errorCode = Crypt_GPG::ERROR_UNKNOWN; +                    } +                } +            } + +            $this->_process = null; +            $this->_pipes   = array(); +        } +    } + +    // }}} +    // {{{ _closePipe() + +    /** +     * Closes an opened pipe used to communicate with the GPG subprocess +     * +     * If the pipe is already closed, it is ignored. If the pipe is open, it +     * is flushed and then closed. +     * +     * @param integer $pipeNumber the file descriptor number of the pipe to +     *                            close. +     * +     * @return void +     */ +    private function _closePipe($pipeNumber) +    { +        $pipeNumber = intval($pipeNumber); +        if (array_key_exists($pipeNumber, $this->_openPipes)) { +            fflush($this->_openPipes[$pipeNumber]); +            fclose($this->_openPipes[$pipeNumber]); +            unset($this->_openPipes[$pipeNumber]); +        } +    } + +    // }}} +    // {{{ _getBinary() + +    /** +     * Gets the name of the GPG binary for the current operating system +     * +     * This method is called if the '<kbd>binary</kbd>' option is <i>not</i> +     * specified when creating this driver. +     * +     * @return string the name of the GPG binary for the current operating +     *                system. If no suitable binary could be found, an empty +     *                string is returned. +     */ +    private function _getBinary() +    { +        $binary = ''; + +        if ($this->_isDarwin) { +            $binaryFiles = array( +                '/opt/local/bin/gpg', // MacPorts +                '/usr/local/bin/gpg', // Mac GPG +                '/sw/bin/gpg',        // Fink +                '/usr/bin/gpg' +            ); +        } else { +            $binaryFiles = array( +                '/usr/bin/gpg', +                '/usr/local/bin/gpg' +            ); +        } + +        foreach ($binaryFiles as $binaryFile) { +            if (is_executable($binaryFile)) { +                $binary = $binaryFile; +                break; +            } +        } + +        return $binary; +    } + +    // }}} +    // {{{ _debug() + +    /** +     * Displays debug text if debugging is turned on +     * +     * Debugging text is prepended with a debug identifier and echoed to stdout. +     * +     * @param string $text the debugging text to display. +     * +     * @return void +     */ +    private function _debug($text) +    { +        if ($this->_debug) { +            if (array_key_exists('SHELL', $_ENV)) { +                foreach (explode(PHP_EOL, $text) as $line) { +                    echo "Crypt_GPG DEBUG: ", $line, PHP_EOL; +                } +            } else { +                // running on a web server, format debug output nicely +                foreach (explode(PHP_EOL, $text) as $line) { +                    echo "Crypt_GPG DEBUG: <strong>", $line, +                        '</strong><br />', PHP_EOL; +                } +            } +        } +    } + +    // }}} +    // {{{ _byteLength() + +    /** +     * Gets the length of a string in bytes even if mbstring function +     * overloading is turned on +     * +     * This is used for stream-based communication with the GPG subprocess. +     * +     * @param string $string the string for which to get the length. +     * +     * @return integer the length of the string in bytes. +     * +     * @see Crypt_GPG_Engine::$_mbStringOverload +     */ +    private static function _byteLength($string) +    { +        if (self::$_mbStringOverload) { +            return mb_strlen($string, '8bit'); +        } + +        return strlen((binary)$string); +    } + +    // }}} +    // {{{ _byteSubstring() + +    /** +     * Gets the substring of a string in bytes even if mbstring function +     * overloading is turned on +     * +     * This is used for stream-based communication with the GPG subprocess. +     * +     * @param string  $string the input string. +     * @param integer $start  the starting point at which to get the substring. +     * @param integer $length optional. The length of the substring. +     * +     * @return string the extracted part of the string. Unlike the default PHP +     *                <kbd>substr()</kbd> function, the returned value is +     *                always a string and never false. +     * +     * @see Crypt_GPG_Engine::$_mbStringOverload +     */ +    private static function _byteSubstring($string, $start, $length = null) +    { +        if (self::$_mbStringOverload) { +            if ($length === null) { +                return mb_substr( +                    $string, +                    $start, +                    self::_byteLength($string) - $start, '8bit' +                ); +            } + +            return mb_substr($string, $start, $length, '8bit'); +        } + +        if ($length === null) { +            return (string)substr((binary)$string, $start); +        } + +        return (string)substr((binary)$string, $start, $length); +    } + +    // }}} +} + +// }}} + +?> diff --git a/plugins/enigma/lib/Crypt/GPG/Exceptions.php b/plugins/enigma/lib/Crypt/GPG/Exceptions.php new file mode 100644 index 000000000..744acf5d4 --- /dev/null +++ b/plugins/enigma/lib/Crypt/GPG/Exceptions.php @@ -0,0 +1,473 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Various exception handling classes for Crypt_GPG + * + * Crypt_GPG provides an object oriented interface to GNU Privacy + * Guard (GPG). It requires the GPG executable to be on the system. + * + * This file contains various exception classes used by the Crypt_GPG package. + * + * 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    Nathan Fredrickson <nathan@silverorange.com> + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2005 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version   CVS: $Id: Exceptions.php 273745 2009-01-18 05:24:25Z gauthierm $ + * @link      http://pear.php.net/package/Crypt_GPG + */ + +/** + * PEAR Exception handler and base class + */ +require_once 'PEAR/Exception.php'; + +// {{{ class Crypt_GPG_Exception + +/** + * An exception thrown by the Crypt_GPG package + * + * @category  Encryption + * @package   Crypt_GPG + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2005 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @link      http://pear.php.net/package/Crypt_GPG + */ +class Crypt_GPG_Exception extends PEAR_Exception +{ +} + +// }}} +// {{{ class Crypt_GPG_FileException + +/** + * An exception thrown when a file is used in ways it cannot be used + * + * For example, if an output file is specified and the file is not writeable, or + * if an input file is specified and the file is not readable, this exception + * is thrown. + * + * @category  Encryption + * @package   Crypt_GPG + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2007-2008 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @link      http://pear.php.net/package/Crypt_GPG + */ +class Crypt_GPG_FileException extends Crypt_GPG_Exception +{ +    // {{{ private class properties + +    /** +     * The name of the file that caused this exception +     * +     * @var string +     */ +    private $_filename = ''; + +    // }}} +    // {{{ __construct() + +    /** +     * Creates a new Crypt_GPG_FileException +     * +     * @param string  $message  an error message. +     * @param integer $code     a user defined error code. +     * @param string  $filename the name of the file that caused this exception. +     */ +    public function __construct($message, $code = 0, $filename = '') +    { +        $this->_filename = $filename; +        parent::__construct($message, $code); +    } + +    // }}} +    // {{{ getFilename() + +    /** +     * Returns the filename of the file that caused this exception +     * +     * @return string the filename of the file that caused this exception. +     * +     * @see Crypt_GPG_FileException::$_filename +     */ +    public function getFilename() +    { +        return $this->_filename; +    } + +    // }}} +} + +// }}} +// {{{ class Crypt_GPG_OpenSubprocessException + +/** + * An exception thrown when the GPG subprocess cannot be opened + * + * This exception is thrown when the {@link Crypt_GPG_Engine} tries to open a + * new subprocess and fails. + * + * @category  Encryption + * @package   Crypt_GPG + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2005 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @link      http://pear.php.net/package/Crypt_GPG + */ +class Crypt_GPG_OpenSubprocessException extends Crypt_GPG_Exception +{ +    // {{{ private class properties + +    /** +     * The command used to try to open the subprocess +     * +     * @var string +     */ +    private $_command = ''; + +    // }}} +    // {{{ __construct() + +    /** +     * Creates a new Crypt_GPG_OpenSubprocessException +     * +     * @param string  $message an error message. +     * @param integer $code    a user defined error code. +     * @param string  $command the command that was called to open the +     *                         new subprocess. +     * +     * @see Crypt_GPG::_openSubprocess() +     */ +    public function __construct($message, $code = 0, $command = '') +    { +        $this->_command = $command; +        parent::__construct($message, $code); +    } + +    // }}} +    // {{{ getCommand() + +    /** +     * Returns the contents of the internal _command property +     * +     * @return string the command used to open the subprocess. +     * +     * @see Crypt_GPG_OpenSubprocessException::$_command +     */ +    public function getCommand() +    { +        return $this->_command; +    } + +    // }}} +} + +// }}} +// {{{ class Crypt_GPG_InvalidOperationException + +/** + * An exception thrown when an invalid GPG operation is attempted + * + * @category  Encryption + * @package   Crypt_GPG + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2008 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @link      http://pear.php.net/package/Crypt_GPG + */ +class Crypt_GPG_InvalidOperationException extends Crypt_GPG_Exception +{ +    // {{{ private class properties + +    /** +     * The attempted operation +     * +     * @var string +     */ +    private $_operation = ''; + +    // }}} +    // {{{ __construct() + +    /** +     * Creates a new Crypt_GPG_OpenSubprocessException +     * +     * @param string  $message   an error message. +     * @param integer $code      a user defined error code. +     * @param string  $operation the operation. +     */ +    public function __construct($message, $code = 0, $operation = '') +    { +        $this->_operation = $operation; +        parent::__construct($message, $code); +    } + +    // }}} +    // {{{ getOperation() + +    /** +     * Returns the contents of the internal _operation property +     * +     * @return string the attempted operation. +     * +     * @see Crypt_GPG_InvalidOperationException::$_operation +     */ +    public function getOperation() +    { +        return $this->_operation; +    } + +    // }}} +} + +// }}} +// {{{ class Crypt_GPG_KeyNotFoundException + +/** + * An exception thrown when Crypt_GPG fails to find the key for various + * operations + * + * @category  Encryption + * @package   Crypt_GPG + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2005 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @link      http://pear.php.net/package/Crypt_GPG + */ +class Crypt_GPG_KeyNotFoundException extends Crypt_GPG_Exception +{ +    // {{{ private class properties + +    /** +     * The key identifier that was searched for +     * +     * @var string +     */ +    private $_keyId = ''; + +    // }}} +    // {{{ __construct() + +    /** +     * Creates a new Crypt_GPG_KeyNotFoundException +     * +     * @param string  $message an error message. +     * @param integer $code    a user defined error code. +     * @param string  $keyId   the key identifier of the key. +     */ +    public function __construct($message, $code = 0, $keyId= '') +    { +        $this->_keyId = $keyId; +        parent::__construct($message, $code); +    } + +    // }}} +    // {{{ getKeyId() + +    /** +     * Gets the key identifier of the key that was not found +     * +     * @return string the key identifier of the key that was not found. +     */ +    public function getKeyId() +    { +        return $this->_keyId; +    } + +    // }}} +} + +// }}} +// {{{ class Crypt_GPG_NoDataException + +/** + * An exception thrown when Crypt_GPG cannot find valid data for various + * operations + * + * @category  Encryption + * @package   Crypt_GPG + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2006 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @link      http://pear.php.net/package/Crypt_GPG + */ +class Crypt_GPG_NoDataException extends Crypt_GPG_Exception +{ +} + +// }}} +// {{{ class Crypt_GPG_BadPassphraseException + +/** + * An exception thrown when a required passphrase is incorrect or missing + * + * @category  Encryption + * @package   Crypt_GPG + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2006-2008 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @link      http://pear.php.net/package/Crypt_GPG + */ +class Crypt_GPG_BadPassphraseException extends Crypt_GPG_Exception +{ +    // {{{ private class properties + +    /** +     * Keys for which the passhprase is missing +     * +     * This contains primary user ids indexed by sub-key id. +     * +     * @var array +     */ +    private $_missingPassphrases = array(); + +    /** +     * Keys for which the passhprase is incorrect +     * +     * This contains primary user ids indexed by sub-key id. +     * +     * @var array +     */ +    private $_badPassphrases = array(); + +    // }}} +    // {{{ __construct() + +    /** +     * Creates a new Crypt_GPG_BadPassphraseException +     * +     * @param string  $message            an error message. +     * @param integer $code               a user defined error code. +     * @param string  $badPassphrases     an array containing user ids of keys +     *                                    for which the passphrase is incorrect. +     * @param string  $missingPassphrases an array containing user ids of keys +     *                                    for which the passphrase is missing. +     */ +    public function __construct($message, $code = 0, +        array $badPassphrases = array(), array $missingPassphrases = array() +    ) { +        $this->_badPassphrases     = $badPassphrases; +        $this->_missingPassphrases = $missingPassphrases; + +        parent::__construct($message, $code); +    } + +    // }}} +    // {{{ getBadPassphrases() + +    /** +     * Gets keys for which the passhprase is incorrect +     * +     * @return array an array of keys for which the passphrase is incorrect. +     *               The array contains primary user ids indexed by the sub-key +     *               id. +     */ +    public function getBadPassphrases() +    { +        return $this->_badPassphrases; +    } + +    // }}} +    // {{{ getMissingPassphrases() + +    /** +     * Gets keys for which the passhprase is missing  +     * +     * @return array an array of keys for which the passphrase is missing. +     *               The array contains primary user ids indexed by the sub-key +     *               id. +     */ +    public function getMissingPassphrases() +    { +        return $this->_missingPassphrases; +    } + +    // }}} +} + +// }}} +// {{{ class Crypt_GPG_DeletePrivateKeyException + +/** + * An exception thrown when an attempt is made to delete public key that has an + * associated private key on the keyring + * + * @category  Encryption + * @package   Crypt_GPG + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2008 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @link      http://pear.php.net/package/Crypt_GPG + */ +class Crypt_GPG_DeletePrivateKeyException extends Crypt_GPG_Exception +{ +    // {{{ private class properties + +    /** +     * The key identifier the deletion attempt was made upon +     * +     * @var string +     */ +    private $_keyId = ''; + +    // }}} +    // {{{ __construct() + +    /** +     * Creates a new Crypt_GPG_DeletePrivateKeyException +     * +     * @param string  $message an error message. +     * @param integer $code    a user defined error code. +     * @param string  $keyId   the key identifier of the public key that was +     *                         attempted to delete. +     * +     * @see Crypt_GPG::deletePublicKey() +     */ +    public function __construct($message, $code = 0, $keyId = '') +    { +        $this->_keyId = $keyId; +        parent::__construct($message, $code); +    } + +    // }}} +    // {{{ getKeyId() + +    /** +     * Gets the key identifier of the key that was not found +     * +     * @return string the key identifier of the key that was not found. +     */ +    public function getKeyId() +    { +        return $this->_keyId; +    } + +    // }}} +} + +// }}} + +?> diff --git a/plugins/enigma/lib/Crypt/GPG/Key.php b/plugins/enigma/lib/Crypt/GPG/Key.php new file mode 100644 index 000000000..67a4b9c7d --- /dev/null +++ b/plugins/enigma/lib/Crypt/GPG/Key.php @@ -0,0 +1,223 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Contains a class representing GPG keys + * + * 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-2010 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version   CVS: $Id: Key.php 295621 2010-03-01 04:18:54Z gauthierm $ + * @link      http://pear.php.net/package/Crypt_GPG + */ + +/** + * Sub-key class definition + */ +require_once 'Crypt/GPG/SubKey.php'; + +/** + * User id class definition + */ +require_once 'Crypt/GPG/UserId.php'; + +// {{{ class Crypt_GPG_Key + +/** + * A data class for GPG key information + * + * This class is used to store the results of the {@link Crypt_GPG::getKeys()} + * method. + * + * @category  Encryption + * @package   Crypt_GPG + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2008-2010 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @link      http://pear.php.net/package/Crypt_GPG + * @see       Crypt_GPG::getKeys() + */ +class Crypt_GPG_Key +{ +    // {{{ class properties + +    /** +     * The user ids associated with this key +     * +     * This is an array of {@link Crypt_GPG_UserId} objects. +     * +     * @var array +     * +     * @see Crypt_GPG_Key::addUserId() +     * @see Crypt_GPG_Key::getUserIds() +     */ +    private $_userIds = array(); + +    /** +     * The subkeys of this key +     * +     * This is an array of {@link Crypt_GPG_SubKey} objects. +     * +     * @var array +     * +     * @see Crypt_GPG_Key::addSubKey() +     * @see Crypt_GPG_Key::getSubKeys() +     */ +    private $_subKeys = array(); + +    // }}} +    // {{{ getSubKeys() + +    /** +     * Gets the sub-keys of this key +     * +     * @return array the sub-keys of this key. +     * +     * @see Crypt_GPG_Key::addSubKey() +     */ +    public function getSubKeys() +    { +        return $this->_subKeys; +    } + +    // }}} +    // {{{ getUserIds() + +    /** +     * Gets the user ids of this key +     * +     * @return array the user ids of this key. +     * +     * @see Crypt_GPG_Key::addUserId() +     */ +    public function getUserIds() +    { +        return $this->_userIds; +    } + +    // }}} +    // {{{ getPrimaryKey() + +    /** +     * Gets the primary sub-key of this key +     * +     * The primary key is the first added sub-key. +     * +     * @return Crypt_GPG_SubKey the primary sub-key of this key. +     */ +    public function getPrimaryKey() +    { +        $primary_key = null; +        if (count($this->_subKeys) > 0) { +            $primary_key = $this->_subKeys[0]; +        } +        return $primary_key; +    } + +    // }}} +    // {{{ canSign() + +    /** +     * Gets whether or not this key can sign data +     * +     * This key can sign data if any sub-key of this key can sign data. +     * +     * @return boolean true if this key can sign data and false if this key +     *                 cannot sign data. +     */ +    public function canSign() +    { +        $canSign = false; +        foreach ($this->_subKeys as $subKey) { +            if ($subKey->canSign()) { +                $canSign = true; +                break; +            } +        } +        return $canSign; +    } + +    // }}} +    // {{{ canEncrypt() + +    /** +     * Gets whether or not this key can encrypt data +     * +     * This key can encrypt data if any sub-key of this key can encrypt data. +     * +     * @return boolean true if this key can encrypt data and false if this +     *                 key cannot encrypt data. +     */ +    public function canEncrypt() +    { +        $canEncrypt = false; +        foreach ($this->_subKeys as $subKey) { +            if ($subKey->canEncrypt()) { +                $canEncrypt = true; +                break; +            } +        } +        return $canEncrypt; +    } + +    // }}} +    // {{{ addSubKey() + +    /** +     * Adds a sub-key to this key +     * +     * The first added sub-key will be the primary key of this key. +     * +     * @param Crypt_GPG_SubKey $subKey the sub-key to add. +     * +     * @return Crypt_GPG_Key the current object, for fluent interface. +     */ +    public function addSubKey(Crypt_GPG_SubKey $subKey) +    { +        $this->_subKeys[] = $subKey; +        return $this; +    } + +    // }}} +    // {{{ addUserId() + +    /** +     * Adds a user id to this key +     * +     * @param Crypt_GPG_UserId $userId the user id to add. +     * +     * @return Crypt_GPG_Key the current object, for fluent interface. +     */ +    public function addUserId(Crypt_GPG_UserId $userId) +    { +        $this->_userIds[] = $userId; +        return $this; +    } + +    // }}} +} + +// }}} + +?> diff --git a/plugins/enigma/lib/Crypt/GPG/Signature.php b/plugins/enigma/lib/Crypt/GPG/Signature.php new file mode 100644 index 000000000..03ab44c53 --- /dev/null +++ b/plugins/enigma/lib/Crypt/GPG/Signature.php @@ -0,0 +1,428 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * A class representing GPG signatures + * + * This file contains a data class representing a GPG signature. + * + * 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    Nathan Fredrickson <nathan@silverorange.com> + * @copyright 2005-2010 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version   CVS: $Id: Signature.php 302773 2010-08-25 14:16:28Z gauthierm $ + * @link      http://pear.php.net/package/Crypt_GPG + */ + +/** + * User id class definition + */ +require_once 'Crypt/GPG/UserId.php'; + +// {{{ class Crypt_GPG_Signature + +/** + * A class for GPG signature information + * + * This class is used to store the results of the Crypt_GPG::verify() method. + * + * @category  Encryption + * @package   Crypt_GPG + * @author    Nathan Fredrickson <nathan@silverorange.com> + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2005-2010 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @link      http://pear.php.net/package/Crypt_GPG + * @see       Crypt_GPG::verify() + */ +class Crypt_GPG_Signature +{ +    // {{{ class properties + +    /** +     * A base64-encoded string containing a unique id for this signature if +     * this signature has been verified as ok +     * +     * This id is used to prevent replay attacks and is not present for all +     * types of signatures. +     * +     * @var string +     */ +    private $_id = ''; + +    /** +     * The fingerprint of the key used to create the signature +     * +     * @var string +     */ +    private $_keyFingerprint = ''; + +    /** +     * The id of the key used to create the signature +     * +     * @var string +     */ +    private $_keyId = ''; + +    /** +     * The creation date of this signature +     * +     * This is a Unix timestamp. +     * +     * @var integer +     */ +    private $_creationDate = 0; + +    /** +     * The expiration date of the signature +     * +     * This is a Unix timestamp. If this signature does not expire, this will +     * be zero. +     * +     * @var integer +     */ +    private $_expirationDate = 0; + +    /** +     * The user id associated with this signature +     * +     * @var Crypt_GPG_UserId +     */ +    private $_userId = null; + +    /** +     * Whether or not this signature is valid +     * +     * @var boolean +     */ +    private $_isValid = false; + +    // }}} +    // {{{ __construct() + +    /** +     * Creates a new signature +     * +     * Signatures can be initialized from an array of named values. Available +     * names are: +     * +     * - <kbd>string  id</kbd>          - the unique id of this signature. +     * - <kbd>string  fingerprint</kbd> - the fingerprint of the key used to +     *                                    create the signature. The fingerprint +     *                                    should not contain formatting +     *                                    characters. +     * - <kbd>string  keyId</kbd>       - the id of the key used to create the +     *                                    the signature. +     * - <kbd>integer creation</kbd>    - the date the signature was created. +     *                                    This is a UNIX timestamp. +     * - <kbd>integer expiration</kbd>  - the date the signature expired. This +     *                                    is a UNIX timestamp. If the signature +     *                                    does not expire, use 0. +     * - <kbd>boolean valid</kbd>       - whether or not the signature is valid. +     * - <kbd>string  userId</kbd>      - the user id associated with the +     *                                    signature. This may also be a +     *                                    {@link Crypt_GPG_UserId} object. +     * +     * @param Crypt_GPG_Signature|array $signature optional. Either an existing +     *        signature object, which is copied; or an array of initial values. +     */ +    public function __construct($signature = null) +    { +        // copy from object +        if ($signature instanceof Crypt_GPG_Signature) { +            $this->_id             = $signature->_id; +            $this->_keyFingerprint = $signature->_keyFingerprint; +            $this->_keyId          = $signature->_keyId; +            $this->_creationDate   = $signature->_creationDate; +            $this->_expirationDate = $signature->_expirationDate; +            $this->_isValid        = $signature->_isValid; + +            if ($signature->_userId instanceof Crypt_GPG_UserId) { +                $this->_userId = clone $signature->_userId; +            } else { +                $this->_userId = $signature->_userId; +            } +        } + +        // initialize from array +        if (is_array($signature)) { +            if (array_key_exists('id', $signature)) { +                $this->setId($signature['id']); +            } + +            if (array_key_exists('fingerprint', $signature)) { +                $this->setKeyFingerprint($signature['fingerprint']); +            } + +            if (array_key_exists('keyId', $signature)) { +                $this->setKeyId($signature['keyId']); +            } + +            if (array_key_exists('creation', $signature)) { +                $this->setCreationDate($signature['creation']); +            } + +            if (array_key_exists('expiration', $signature)) { +                $this->setExpirationDate($signature['expiration']); +            } + +            if (array_key_exists('valid', $signature)) { +                $this->setValid($signature['valid']); +            } + +            if (array_key_exists('userId', $signature)) { +                $userId = new Crypt_GPG_UserId($signature['userId']); +                $this->setUserId($userId); +            } +        } +    } + +    // }}} +    // {{{ getId() + +    /** +     * Gets the id of this signature +     * +     * @return string a base64-encoded string containing a unique id for this +     *                signature. This id is used to prevent replay attacks and +     *                is not present for all types of signatures. +     */ +    public function getId() +    { +        return $this->_id; +    } + +    // }}} +    // {{{ getKeyFingerprint() + +    /** +     * Gets the fingerprint of the key used to create this signature +     * +     * @return string the fingerprint of the key used to create this signature. +     */ +    public function getKeyFingerprint() +    { +        return $this->_keyFingerprint; +    } + +    // }}} +    // {{{ getKeyId() + +    /** +     * Gets the id of the key used to create this signature +     * +     * Whereas the fingerprint of the signing key may not always be available +     * (for example if the signature is bad), the id should always be +     * available. +     * +     * @return string the id of the key used to create this signature. +     */ +    public function getKeyId() +    { +        return $this->_keyId; +    } + +    // }}} +    // {{{ getCreationDate() + +    /** +     * Gets the creation date of this signature +     * +     * @return integer the creation date of this signature. This is a Unix +     *                 timestamp. +     */ +    public function getCreationDate() +    { +        return $this->_creationDate; +    } + +    // }}} +    // {{{ getExpirationDate() + +    /** +     * Gets the expiration date of the signature +     * +     * @return integer the expiration date of this signature. This is a Unix +     *                 timestamp. If this signature does not expire, this will +     *                 be zero. +     */ +    public function getExpirationDate() +    { +        return $this->_expirationDate; +    } + +    // }}} +    // {{{ getUserId() + +    /** +     * Gets the user id associated with this signature +     * +     * @return Crypt_GPG_UserId the user id associated with this signature. +     */ +    public function getUserId() +    { +        return $this->_userId; +    } + +    // }}} +    // {{{ isValid() + +    /** +     * Gets whether or no this signature is valid +     * +     * @return boolean true if this signature is valid and false if it is not. +     */ +    public function isValid() +    { +        return $this->_isValid; +    } + +    // }}} +    // {{{ setId() + +    /** +     * Sets the id of this signature +     * +     * @param string $id a base64-encoded string containing a unique id for +     *                   this signature. +     * +     * @return Crypt_GPG_Signature the current object, for fluent interface. +     * +     * @see Crypt_GPG_Signature::getId() +     */ +    public function setId($id) +    { +        $this->_id = strval($id); +        return $this; +    } + +    // }}} +    // {{{ setKeyFingerprint() + +    /** +     * Sets the key fingerprint of this signature +     * +     * @param string $fingerprint the key fingerprint of this signature. This +     *                            is the fingerprint of the primary key used to +     *                            create this signature. +     * +     * @return Crypt_GPG_Signature the current object, for fluent interface. +     */ +    public function setKeyFingerprint($fingerprint) +    { +        $this->_keyFingerprint = strval($fingerprint); +        return $this; +    } + +    // }}} +    // {{{ setKeyId() + +    /** +     * Sets the key id of this signature +     * +     * @param string $id the key id of this signature. This is the id of the +     *                   primary key used to create this signature. +     * +     * @return Crypt_GPG_Signature the current object, for fluent interface. +     */ +    public function setKeyId($id) +    { +        $this->_keyId = strval($id); +        return $this; +    } + +    // }}} +    // {{{ setCreationDate() + +    /** +     * Sets the creation date of this signature +     * +     * @param integer $creationDate the creation date of this signature. This +     *                              is a Unix timestamp. +     * +     * @return Crypt_GPG_Signature the current object, for fluent interface. +     */ +    public function setCreationDate($creationDate) +    { +        $this->_creationDate = intval($creationDate); +        return $this; +    } + +    // }}} +    // {{{ setExpirationDate() + +    /** +     * Sets the expiration date of this signature +     * +     * @param integer $expirationDate the expiration date of this signature. +     *                                This is a Unix timestamp. Specify zero if +     *                                this signature does not expire. +     * +     * @return Crypt_GPG_Signature the current object, for fluent interface. +     */ +    public function setExpirationDate($expirationDate) +    { +        $this->_expirationDate = intval($expirationDate); +        return $this; +    } + +    // }}} +    // {{{ setUserId() + +    /** +     * Sets the user id associated with this signature +     * +     * @param Crypt_GPG_UserId $userId the user id associated with this +     *                                 signature. +     * +     * @return Crypt_GPG_Signature the current object, for fluent interface. +     */ +    public function setUserId(Crypt_GPG_UserId $userId) +    { +        $this->_userId = $userId; +        return $this; +    } + +    // }}} +    // {{{ setValid() + +    /** +     * Sets whether or not this signature is valid +     * +     * @param boolean $isValid true if this signature is valid and false if it +     *                         is not. +     * +     * @return Crypt_GPG_Signature the current object, for fluent interface. +     */ +    public function setValid($isValid) +    { +        $this->_isValid = ($isValid) ? true : false; +        return $this; +    } + +    // }}} +} + +// }}} + +?> diff --git a/plugins/enigma/lib/Crypt/GPG/SubKey.php b/plugins/enigma/lib/Crypt/GPG/SubKey.php new file mode 100644 index 000000000..b6316e99f --- /dev/null +++ b/plugins/enigma/lib/Crypt/GPG/SubKey.php @@ -0,0 +1,649 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Contains a class representing GPG sub-keys and constants for GPG algorithms + * + * 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> + * @author    Nathan Fredrickson <nathan@silverorange.com> + * @copyright 2005-2010 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version   CVS: $Id: SubKey.php 302768 2010-08-25 13:45:52Z gauthierm $ + * @link      http://pear.php.net/package/Crypt_GPG + */ + +// {{{ class Crypt_GPG_SubKey + +/** + * A class for GPG sub-key information + * + * This class is used to store the results of the {@link Crypt_GPG::getKeys()} + * method. Sub-key objects are members of a {@link Crypt_GPG_Key} object. + * + * @category  Encryption + * @package   Crypt_GPG + * @author    Michael Gauthier <mike@silverorange.com> + * @author    Nathan Fredrickson <nathan@silverorange.com> + * @copyright 2005-2010 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @link      http://pear.php.net/package/Crypt_GPG + * @see       Crypt_GPG::getKeys() + * @see       Crypt_GPG_Key::getSubKeys() + */ +class Crypt_GPG_SubKey +{ +    // {{{ class constants + +    /** +     * RSA encryption algorithm. +     */ +    const ALGORITHM_RSA = 1; + +    /** +     * Elgamal encryption algorithm (encryption only). +     */ +    const ALGORITHM_ELGAMAL_ENC = 16; + +    /** +     * DSA encryption algorithm (sometimes called DH, sign only). +     */ +    const ALGORITHM_DSA = 17; + +    /** +     * Elgamal encryption algorithm (signage and encryption - should not be +     * used). +     */ +    const ALGORITHM_ELGAMAL_ENC_SGN = 20; + +    // }}} +    // {{{ class properties + +    /** +     * The id of this sub-key +     * +     * @var string +     */ +    private $_id = ''; + +    /** +     * The algorithm used to create this sub-key +     * +     * The value is one of the Crypt_GPG_SubKey::ALGORITHM_* constants. +     * +     * @var integer +     */ +    private $_algorithm = 0; + +    /** +     * The fingerprint of this sub-key +     * +     * @var string +     */ +    private $_fingerprint = ''; + +    /** +     * Length of this sub-key in bits +     * +     * @var integer +     */ +    private $_length = 0; + +    /** +     * Date this sub-key was created +     * +     * This is a Unix timestamp. +     * +     * @var integer +     */ +    private $_creationDate = 0; + +    /** +     * Date this sub-key expires +     * +     * This is a Unix timestamp. If this sub-key does not expire, this will be +     * zero. +     * +     * @var integer +     */ +    private $_expirationDate = 0; + +    /** +     * Whether or not this sub-key can sign data +     * +     * @var boolean +     */ +    private $_canSign = false; + +    /** +     * Whether or not this sub-key can encrypt data +     * +     * @var boolean +     */ +    private $_canEncrypt = false; + +    /** +     * Whether or not the private key for this sub-key exists in the keyring +     * +     * @var boolean +     */ +    private $_hasPrivate = false; + +    /** +     * Whether or not this sub-key is revoked +     * +     * @var boolean +     */ +    private $_isRevoked = false; + +    // }}} +    // {{{ __construct() + +    /** +     * Creates a new sub-key object +     * +     * Sub-keys can be initialized from an array of named values. Available +     * names are: +     * +     * - <kbd>string  id</kbd>          - the key id of the sub-key. +     * - <kbd>integer algorithm</kbd>   - the encryption algorithm of the +     *                                    sub-key. +     * - <kbd>string  fingerprint</kbd> - the fingerprint of the sub-key. The +     *                                    fingerprint should not contain +     *                                    formatting characters. +     * - <kbd>integer length</kbd>      - the length of the sub-key in bits. +     * - <kbd>integer creation</kbd>    - the date the sub-key was created. +     *                                    This is a UNIX timestamp. +     * - <kbd>integer expiration</kbd>  - the date the sub-key expires. This +     *                                    is a UNIX timestamp. If the sub-key +     *                                    does not expire, use 0. +     * - <kbd>boolean canSign</kbd>     - whether or not the sub-key can be +     *                                    used to sign data. +     * - <kbd>boolean canEncrypt</kbd>  - whether or not the sub-key can be +     *                                    used to encrypt data. +     * - <kbd>boolean hasPrivate</kbd>  - whether or not the private key for +     *                                    the sub-key exists in the keyring. +     * - <kbd>boolean isRevoked</kbd>   - whether or not this sub-key is +     *                                    revoked. +     * +     * @param Crypt_GPG_SubKey|string|array $key optional. Either an existing +     *        sub-key object, which is copied; a sub-key string, which is +     *        parsed; or an array of initial values. +     */ +    public function __construct($key = null) +    { +        // parse from string +        if (is_string($key)) { +            $key = self::parse($key); +        } + +        // copy from object +        if ($key instanceof Crypt_GPG_SubKey) { +            $this->_id             = $key->_id; +            $this->_algorithm      = $key->_algorithm; +            $this->_fingerprint    = $key->_fingerprint; +            $this->_length         = $key->_length; +            $this->_creationDate   = $key->_creationDate; +            $this->_expirationDate = $key->_expirationDate; +            $this->_canSign        = $key->_canSign; +            $this->_canEncrypt     = $key->_canEncrypt; +            $this->_hasPrivate     = $key->_hasPrivate; +            $this->_isRevoked      = $key->_isRevoked; +        } + +        // initialize from array +        if (is_array($key)) { +            if (array_key_exists('id', $key)) { +                $this->setId($key['id']); +            } + +            if (array_key_exists('algorithm', $key)) { +                $this->setAlgorithm($key['algorithm']); +            } + +            if (array_key_exists('fingerprint', $key)) { +                $this->setFingerprint($key['fingerprint']); +            } + +            if (array_key_exists('length', $key)) { +                $this->setLength($key['length']); +            } + +            if (array_key_exists('creation', $key)) { +                $this->setCreationDate($key['creation']); +            } + +            if (array_key_exists('expiration', $key)) { +                $this->setExpirationDate($key['expiration']); +            } + +            if (array_key_exists('canSign', $key)) { +                $this->setCanSign($key['canSign']); +            } + +            if (array_key_exists('canEncrypt', $key)) { +                $this->setCanEncrypt($key['canEncrypt']); +            } + +            if (array_key_exists('hasPrivate', $key)) { +                $this->setHasPrivate($key['hasPrivate']); +            } + +            if (array_key_exists('isRevoked', $key)) { +                $this->setRevoked($key['isRevoked']); +            } +        } +    } + +    // }}} +    // {{{ getId() + +    /** +     * Gets the id of this sub-key +     * +     * @return string the id of this sub-key. +     */ +    public function getId() +    { +        return $this->_id; +    } + +    // }}} +    // {{{ getAlgorithm() + +    /** +     * Gets the algorithm used by this sub-key +     * +     * The algorithm should be one of the Crypt_GPG_SubKey::ALGORITHM_* +     * constants. +     * +     * @return integer the algorithm used by this sub-key. +     */ +    public function getAlgorithm() +    { +        return $this->_algorithm; +    } + +    // }}} +    // {{{ getCreationDate() + +    /** +     * Gets the creation date of this sub-key +     * +     * This is a Unix timestamp. +     * +     * @return integer the creation date of this sub-key. +     */ +    public function getCreationDate() +    { +        return $this->_creationDate; +    } + +    // }}} +    // {{{ getExpirationDate() + +    /** +     * Gets the date this sub-key expires +     * +     * This is a Unix timestamp. If this sub-key does not expire, this will be +     * zero. +     * +     * @return integer the date this sub-key expires. +     */ +    public function getExpirationDate() +    { +        return $this->_expirationDate; +    } + +    // }}} +    // {{{ getFingerprint() + +    /** +     * Gets the fingerprint of this sub-key +     * +     * @return string the fingerprint of this sub-key. +     */ +    public function getFingerprint() +    { +        return $this->_fingerprint; +    } + +    // }}} +    // {{{ getLength() + +    /** +     * Gets the length of this sub-key in bits +     * +     * @return integer the length of this sub-key in bits. +     */ +    public function getLength() +    { +        return $this->_length; +    } + +    // }}} +    // {{{ canSign() + +    /** +     * Gets whether or not this sub-key can sign data +     * +     * @return boolean true if this sub-key can sign data and false if this +     *                 sub-key can not sign data. +     */ +    public function canSign() +    { +        return $this->_canSign; +    } + +    // }}} +    // {{{ canEncrypt() + +    /** +     * Gets whether or not this sub-key can encrypt data +     * +     * @return boolean true if this sub-key can encrypt data and false if this +     *                 sub-key can not encrypt data. +     */ +    public function canEncrypt() +    { +        return $this->_canEncrypt; +    } + +    // }}} +    // {{{ hasPrivate() + +    /** +     * Gets whether or not the private key for this sub-key exists in the +     * keyring +     * +     * @return boolean true the private key for this sub-key exists in the +     *                 keyring and false if it does not. +     */ +    public function hasPrivate() +    { +        return $this->_hasPrivate; +    } + +    // }}} +    // {{{ isRevoked() + +    /** +     * Gets whether or not this sub-key is revoked +     * +     * @return boolean true if this sub-key is revoked and false if it is not. +     */ +    public function isRevoked() +    { +        return $this->_isRevoked; +    } + +    // }}} +    // {{{ setCreationDate() + +    /** +     * Sets the creation date of this sub-key +     * +     * The creation date is a Unix timestamp. +     * +     * @param integer $creationDate the creation date of this sub-key. +     * +     * @return Crypt_GPG_SubKey the current object, for fluent interface. +     */ +    public function setCreationDate($creationDate) +    { +        $this->_creationDate = intval($creationDate); +        return $this; +    } + +    // }}} +    // {{{ setExpirationDate() + +    /** +     * Sets the expiration date of this sub-key +     * +     * The expiration date is a Unix timestamp. Specify zero if this sub-key +     * does not expire. +     * +     * @param integer $expirationDate the expiration date of this sub-key. +     * +     * @return Crypt_GPG_SubKey the current object, for fluent interface. +     */ +    public function setExpirationDate($expirationDate) +    { +        $this->_expirationDate = intval($expirationDate); +        return $this; +    } + +    // }}} +    // {{{ setId() + +    /** +     * Sets the id of this sub-key +     * +     * @param string $id the id of this sub-key. +     * +     * @return Crypt_GPG_SubKey the current object, for fluent interface. +     */ +    public function setId($id) +    { +        $this->_id = strval($id); +        return $this; +    } + +    // }}} +    // {{{ setAlgorithm() + +    /** +     * Sets the algorithm used by this sub-key +     * +     * @param integer $algorithm the algorithm used by this sub-key. +     * +     * @return Crypt_GPG_SubKey the current object, for fluent interface. +     */ +    public function setAlgorithm($algorithm) +    { +        $this->_algorithm = intval($algorithm); +        return $this; +    } + +    // }}} +    // {{{ setFingerprint() + +    /** +     * Sets the fingerprint of this sub-key +     * +     * @param string $fingerprint the fingerprint of this sub-key. +     * +     * @return Crypt_GPG_SubKey the current object, for fluent interface. +     */ +    public function setFingerprint($fingerprint) +    { +        $this->_fingerprint = strval($fingerprint); +        return $this; +    } + +    // }}} +    // {{{ setLength() + +    /** +     * Sets the length of this sub-key in bits +     * +     * @param integer $length the length of this sub-key in bits. +     * +     * @return Crypt_GPG_SubKey the current object, for fluent interface. +     */ +    public function setLength($length) +    { +        $this->_length = intval($length); +        return $this; +    } + +    // }}} +    // {{{ setCanSign() + +    /** +     * Sets whether of not this sub-key can sign data +     * +     * @param boolean $canSign true if this sub-key can sign data and false if +     *                         it can not. +     * +     * @return Crypt_GPG_SubKey the current object, for fluent interface. +     */ +    public function setCanSign($canSign) +    { +        $this->_canSign = ($canSign) ? true : false; +        return $this; +    } + +    // }}} +    // {{{ setCanEncrypt() + +    /** +     * Sets whether of not this sub-key can encrypt data +     * +     * @param boolean $canEncrypt true if this sub-key can encrypt data and +     *                            false if it can not. +     * +     * @return Crypt_GPG_SubKey the current object, for fluent interface. +     */ +    public function setCanEncrypt($canEncrypt) +    { +        $this->_canEncrypt = ($canEncrypt) ? true : false; +        return $this; +    } + +    // }}} +    // {{{ setHasPrivate() + +    /** +     * Sets whether of not the private key for this sub-key exists in the +     * keyring +     * +     * @param boolean $hasPrivate true if the private key for this sub-key +     *                            exists in the keyring and false if it does +     *                            not. +     * +     * @return Crypt_GPG_SubKey the current object, for fluent interface. +     */ +    public function setHasPrivate($hasPrivate) +    { +        $this->_hasPrivate = ($hasPrivate) ? true : false; +        return $this; +    } + +    // }}} +    // {{{ setRevoked() + +    /** +     * Sets whether or not this sub-key is revoked +     * +     * @param boolean $isRevoked whether or not this sub-key is revoked. +     * +     * @return Crypt_GPG_SubKey the current object, for fluent interface. +     */ +    public function setRevoked($isRevoked) +    { +        $this->_isRevoked = ($isRevoked) ? true : false; +        return $this; +    } + +    // }}} +    // {{{ parse() + +    /** +     * Parses a sub-key object from a sub-key string +     * +     * See <b>doc/DETAILS</b> in the +     * {@link http://www.gnupg.org/download/ GPG distribution} for information +     * on how the sub-key string is parsed. +     * +     * @param string $string the string containing the sub-key. +     * +     * @return Crypt_GPG_SubKey the sub-key object parsed from the string. +     */ +    public static function parse($string) +    { +        $tokens = explode(':', $string); + +        $subKey = new Crypt_GPG_SubKey(); + +        $subKey->setId($tokens[4]); +        $subKey->setLength($tokens[2]); +        $subKey->setAlgorithm($tokens[3]); +        $subKey->setCreationDate(self::_parseDate($tokens[5])); +        $subKey->setExpirationDate(self::_parseDate($tokens[6])); + +        if ($tokens[1] == 'r') { +            $subKey->setRevoked(true); +        } + +        if (strpos($tokens[11], 's') !== false) { +            $subKey->setCanSign(true); +        } + +        if (strpos($tokens[11], 'e') !== false) { +            $subKey->setCanEncrypt(true); +        } + +        return $subKey; +    } + +    // }}} +    // {{{ _parseDate() + +    /** +     * Parses a date string as provided by GPG into a UNIX timestamp +     * +     * @param string $string the date string. +     * +     * @return integer the UNIX timestamp corresponding to the provided date +     *                 string. +     */ +    private static function _parseDate($string) +    { +        if ($string == '') { +            $timestamp = 0; +        } else { +            // all times are in UTC according to GPG documentation +            $timeZone = new DateTimeZone('UTC'); + +            if (strpos($string, 'T') === false) { +                // interpret as UNIX timestamp +                $string = '@' . $string; +            } + +            $date = new DateTime($string, $timeZone); + +            // convert to UNIX timestamp +            $timestamp = intval($date->format('U')); +        } + +        return $timestamp; +    } + +    // }}} +} + +// }}} + +?> diff --git a/plugins/enigma/lib/Crypt/GPG/UserId.php b/plugins/enigma/lib/Crypt/GPG/UserId.php new file mode 100644 index 000000000..04435708c --- /dev/null +++ b/plugins/enigma/lib/Crypt/GPG/UserId.php @@ -0,0 +1,373 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Contains a data class representing a GPG user id + * + * 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-2010 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version   CVS: $Id: UserId.php 295621 2010-03-01 04:18:54Z gauthierm $ + * @link      http://pear.php.net/package/Crypt_GPG + */ + +// {{{ class Crypt_GPG_UserId + +/** + * A class for GPG user id information + * + * This class is used to store the results of the {@link Crypt_GPG::getKeys()} + * method. User id objects are members of a {@link Crypt_GPG_Key} object. + * + * @category  Encryption + * @package   Crypt_GPG + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2008-2010 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @link      http://pear.php.net/package/Crypt_GPG + * @see       Crypt_GPG::getKeys() + * @see       Crypt_GPG_Key::getUserIds() + */ +class Crypt_GPG_UserId +{ +    // {{{ class properties + +    /** +     * The name field of this user id +     * +     * @var string +     */ +    private $_name = ''; + +    /** +     * The comment field of this user id +     * +     * @var string +     */ +    private $_comment = ''; + +    /** +     * The email field of this user id +     * +     * @var string +     */ +    private $_email = ''; + +    /** +     * Whether or not this user id is revoked +     * +     * @var boolean +     */ +    private $_isRevoked = false; + +    /** +     * Whether or not this user id is valid +     * +     * @var boolean +     */ +    private $_isValid = true; + +    // }}} +    // {{{ __construct() + +    /** +     * Creates a new user id +     * +     * User ids can be initialized from an array of named values. Available +     * names are: +     * +     * - <kbd>string  name</kbd>    - the name field of the user id. +     * - <kbd>string  comment</kbd> - the comment field of the user id. +     * - <kbd>string  email</kbd>   - the email field of the user id. +     * - <kbd>boolean valid</kbd>   - whether or not the user id is valid. +     * - <kbd>boolean revoked</kbd> - whether or not the user id is revoked. +     * +     * @param Crypt_GPG_UserId|string|array $userId optional. Either an +     *        existing user id object, which is copied; a user id string, which +     *        is parsed; or an array of initial values. +     */ +    public function __construct($userId = null) +    { +        // parse from string +        if (is_string($userId)) { +            $userId = self::parse($userId); +        } + +        // copy from object +        if ($userId instanceof Crypt_GPG_UserId) { +            $this->_name      = $userId->_name; +            $this->_comment   = $userId->_comment; +            $this->_email     = $userId->_email; +            $this->_isRevoked = $userId->_isRevoked; +            $this->_isValid   = $userId->_isValid; +        } + +        // initialize from array +        if (is_array($userId)) { +            if (array_key_exists('name', $userId)) { +                $this->setName($userId['name']); +            } + +            if (array_key_exists('comment', $userId)) { +                $this->setComment($userId['comment']); +            } + +            if (array_key_exists('email', $userId)) { +                $this->setEmail($userId['email']); +            } + +            if (array_key_exists('revoked', $userId)) { +                $this->setRevoked($userId['revoked']); +            } + +            if (array_key_exists('valid', $userId)) { +                $this->setValid($userId['valid']); +            } +        } +    } + +    // }}} +    // {{{ getName() + +    /** +     * Gets the name field of this user id +     * +     * @return string the name field of this user id. +     */ +    public function getName() +    { +        return $this->_name; +    } + +    // }}} +    // {{{ getComment() + +    /** +     * Gets the comments field of this user id +     * +     * @return string the comments field of this user id. +     */ +    public function getComment() +    { +        return $this->_comment; +    } + +    // }}} +    // {{{ getEmail() + +    /** +     * Gets the email field of this user id +     * +     * @return string the email field of this user id. +     */ +    public function getEmail() +    { +        return $this->_email; +    } + +    // }}} +    // {{{ isRevoked() + +    /** +     * Gets whether or not this user id is revoked +     * +     * @return boolean true if this user id is revoked and false if it is not. +     */ +    public function isRevoked() +    { +        return $this->_isRevoked; +    } + +    // }}} +    // {{{ isValid() + +    /** +     * Gets whether or not this user id is valid +     * +     * @return boolean true if this user id is valid and false if it is not. +     */ +    public function isValid() +    { +        return $this->_isValid; +    } + +    // }}} +    // {{{ __toString() + +    /** +     * Gets a string representation of this user id +     * +     * The string is formatted as: +     * <b><kbd>name (comment) <email-address></kbd></b>. +     * +     * @return string a string representation of this user id. +     */ +    public function __toString() +    { +        $components = array(); + +        if (strlen($this->_name) > 0) { +            $components[] = $this->_name; +        } + +        if (strlen($this->_comment) > 0) { +            $components[] = '(' . $this->_comment . ')'; +        } + +        if (strlen($this->_email) > 0) { +            $components[] = '<' . $this->_email. '>'; +        } + +        return implode(' ', $components); +    } + +    // }}} +    // {{{ setName() + +    /** +     * Sets the name field of this user id +     * +     * @param string $name the name field of this user id. +     * +     * @return Crypt_GPG_UserId the current object, for fluent interface. +     */ +    public function setName($name) +    { +        $this->_name = strval($name); +        return $this; +    } + +    // }}} +    // {{{ setComment() + +    /** +     * Sets the comment field of this user id +     * +     * @param string $comment the comment field of this user id. +     * +     * @return Crypt_GPG_UserId the current object, for fluent interface. +     */ +    public function setComment($comment) +    { +        $this->_comment = strval($comment); +        return $this; +    } + +    // }}} +    // {{{ setEmail() + +    /** +     * Sets the email field of this user id +     * +     * @param string $email the email field of this user id. +     * +     * @return Crypt_GPG_UserId the current object, for fluent interface. +     */ +    public function setEmail($email) +    { +        $this->_email = strval($email); +        return $this; +    } + +    // }}} +    // {{{ setRevoked() + +    /** +     * Sets whether or not this user id is revoked +     * +     * @param boolean $isRevoked whether or not this user id is revoked. +     * +     * @return Crypt_GPG_UserId the current object, for fluent interface. +     */ +    public function setRevoked($isRevoked) +    { +        $this->_isRevoked = ($isRevoked) ? true : false; +        return $this; +    } + +    // }}} +    // {{{ setValid() + +    /** +     * Sets whether or not this user id is valid +     * +     * @param boolean $isValid whether or not this user id is valid. +     * +     * @return Crypt_GPG_UserId the current object, for fluent interface. +     */ +    public function setValid($isValid) +    { +        $this->_isValid = ($isValid) ? true : false; +        return $this; +    } + +    // }}} +    // {{{ parse() + +    /** +     * Parses a user id object from a user id string +     * +     * A user id string is of the form: +     * <b><kbd>name (comment) <email-address></kbd></b> with the <i>comment</i> +     * and <i>email-address</i> fields being optional. +     * +     * @param string $string the user id string to parse. +     * +     * @return Crypt_GPG_UserId the user id object parsed from the string. +     */ +    public static function parse($string) +    { +        $userId  = new Crypt_GPG_UserId(); +        $email   = ''; +        $comment = ''; + +        // get email address from end of string if it exists +        $matches = array(); +        if (preg_match('/^(.+?) <([^>]+)>$/', $string, $matches) === 1) { +            $string = $matches[1]; +            $email  = $matches[2]; +        } + +        // get comment from end of string if it exists +        $matches = array(); +        if (preg_match('/^(.+?) \(([^\)]+)\)$/', $string, $matches) === 1) { +            $string  = $matches[1]; +            $comment = $matches[2]; +        } + +        $name = $string; + +        $userId->setName($name); +        $userId->setComment($comment); +        $userId->setEmail($email); + +        return $userId; +    } + +    // }}} +} + +// }}} + +?> diff --git a/plugins/enigma/lib/Crypt/GPG/VerifyStatusHandler.php b/plugins/enigma/lib/Crypt/GPG/VerifyStatusHandler.php new file mode 100644 index 000000000..083bd3012 --- /dev/null +++ b/plugins/enigma/lib/Crypt/GPG/VerifyStatusHandler.php @@ -0,0 +1,216 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Crypt_GPG is a package to use GPG from PHP + * + * This file contains an object that handles GPG's status output for the verify + * 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 silverorange + * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version   CVS: $Id: VerifyStatusHandler.php 302908 2010-08-31 03:56:54Z gauthierm $ + * @link      http://pear.php.net/package/Crypt_GPG + * @link      http://www.gnupg.org/ + */ + +/** + * Signature object class definition + */ +require_once 'Crypt/GPG/Signature.php'; + +/** + * Status line handler for the verify 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 building signature objects that are returned + * by the {@link Crypt_GPG::verify()} method. See <b>doc/DETAILS</b> in the + * {@link http://www.gnupg.org/download/ GPG distribution} for detailed + * information on GPG's status output for the verify operation. + * + * @category  Encryption + * @package   Crypt_GPG + * @author    Michael Gauthier <mike@silverorange.com> + * @copyright 2008 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_VerifyStatusHandler +{ +    // {{{ protected properties + +    /** +     * The current signature id +     * +     * Ths signature id is emitted by GPG before the new signature line so we +     * must remember it temporarily. +     * +     * @var string +     */ +    protected $signatureId = ''; + +    /** +     * List of parsed {@link Crypt_GPG_Signature} objects +     * +     * @var array +     */ +    protected $signatures = array(); + +    /** +     * Array index of the current signature +     * +     * @var integer +     */ +    protected $index = -1; + +    // }}} +    // {{{ 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 'GOODSIG': +        case 'EXPSIG': +        case 'EXPKEYSIG': +        case 'REVKEYSIG': +        case 'BADSIG': +            $signature = new Crypt_GPG_Signature(); + +            // if there was a signature id, set it on the new signature +            if ($this->signatureId != '') { +                $signature->setId($this->signatureId); +                $this->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 (strlen($tokens[1]) > 16) { +                $signature->setKeyFingerprint($tokens[1]); +                $signature->setKeyId(substr($tokens[1], -16)); +            } else { +                $signature->setKeyId($tokens[1]); +            } + +            // get user id string +            $string = implode(' ', array_splice($tokens, 2)); +            $string = rawurldecode($string); + +            $signature->setUserId(Crypt_GPG_UserId::parse($string)); + +            $this->index++; +            $this->signatures[$this->index] = $signature; +            break; + +        case 'ERRSIG': +            $signature = new Crypt_GPG_Signature(); + +            // if there was a signature id, set it on the new signature +            if ($this->signatureId != '') { +                $signature->setId($this->signatureId); +                $this->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 (strlen($tokens[1]) > 16) { +                $signature->setKeyFingerprint($tokens[1]); +                $signature->setKeyId(substr($tokens[1], -16)); +            } else { +                $signature->setKeyId($tokens[1]); +            } + +            $this->index++; +            $this->signatures[$this->index] = $signature; + +            break; + +        case 'VALIDSIG': +            if (!array_key_exists($this->index, $this->signatures)) { +                break; +            } + +            $signature = $this->signatures[$this->index]; + +            $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 'SIG_ID': +            // note: signature id comes before new signature line and may not +            // exist for some signature types +            $this->signatureId = $tokens[1]; +            break; +        } +    } + +    // }}} +    // {{{ getSignatures() + +    /** +     * Gets the {@link Crypt_GPG_Signature} objects parsed by this handler +     * +     * @return array the signature objects parsed by this handler. +     */ +    public function getSignatures() +    { +        return $this->signatures; +    } + +    // }}} +} + +?> diff --git a/plugins/enigma/lib/enigma_driver.php b/plugins/enigma/lib/enigma_driver.php new file mode 100644 index 000000000..a9a3e4715 --- /dev/null +++ b/plugins/enigma/lib/enigma_driver.php @@ -0,0 +1,106 @@ +<?php +/* + +-------------------------------------------------------------------------+ + | Abstract driver for the Enigma Plugin                                   | + |                                                                         | + | This program is free software; you can redistribute it and/or modify    | + | it under the terms of the GNU General Public License version 2          | + | as published by the Free Software Foundation.                           | + |                                                                         | + | This program 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 General Public License for more details.                            | + |                                                                         | + | You should have received a copy of the GNU General Public License along | + | with this program; if not, write to the Free Software Foundation, Inc., | + | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             | + |                                                                         | + +-------------------------------------------------------------------------+ + | Author: Aleksander Machniak <alec@alec.pl>                              | + +-------------------------------------------------------------------------+ +*/ + +abstract class enigma_driver +{ +    /** +     * Class constructor. +     * +     * @param string User name (email address) +     */ +    abstract function __construct($user); + +    /** +     * Driver initialization. +     * +     * @return mixed NULL on success, enigma_error on failure +     */ +    abstract function init(); + +    /** +     * Encryption. +     */ +    abstract function encrypt($text, $keys); + +    /** +     * Decryption.. +     */ +    abstract function decrypt($text, $key, $passwd); + +    /** +     * Signing. +     */ +    abstract function sign($text, $key, $passwd); + +    /** +     * Signature verification. +     * +     * @param string Message body +     * @param string Signature, if message is of type PGP/MIME and body doesn't contain it +     * +     * @return mixed Signature information (enigma_signature) or enigma_error +     */ +    abstract function verify($text, $signature); + +    /** +     * Key/Cert file import. +     * +     * @param string  File name or file content +     * @param bollean True if first argument is a filename +     * +     * @return mixed Import status array or enigma_error +     */ +    abstract function import($content, $isfile=false); + +    /** +     * Keys listing. +     * +     * @param string Optional pattern for key ID, user ID or fingerprint +     * +     * @return mixed Array of enigma_key objects or enigma_error +     */ +    abstract function list_keys($pattern=''); +     +    /** +     * Single key information. +     * +     * @param string Key ID, user ID or fingerprint +     * +     * @return mixed Key (enigma_key) object or enigma_error +     */ +    abstract function get_key($keyid); + +    /** +     * Key pair generation. +     * +     * @param array Key/User data +     * +     * @return mixed Key (enigma_key) object or enigma_error +     */ +    abstract function gen_key($data); +     +    /** +     * Key deletion. +     */ +    abstract function del_key($keyid); +} diff --git a/plugins/enigma/lib/enigma_driver_gnupg.php b/plugins/enigma/lib/enigma_driver_gnupg.php new file mode 100644 index 000000000..5aa32217e --- /dev/null +++ b/plugins/enigma/lib/enigma_driver_gnupg.php @@ -0,0 +1,305 @@ +<?php +/* + +-------------------------------------------------------------------------+ + | GnuPG (PGP) driver for the Enigma Plugin                                | + |                                                                         | + | This program is free software; you can redistribute it and/or modify    | + | it under the terms of the GNU General Public License version 2          | + | as published by the Free Software Foundation.                           | + |                                                                         | + | This program 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 General Public License for more details.                            | + |                                                                         | + | You should have received a copy of the GNU General Public License along | + | with this program; if not, write to the Free Software Foundation, Inc., | + | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             | + |                                                                         | + +-------------------------------------------------------------------------+ + | Author: Aleksander Machniak <alec@alec.pl>                              | + +-------------------------------------------------------------------------+ +*/ + +require_once 'Crypt/GPG.php'; + +class enigma_driver_gnupg extends enigma_driver +{ +    private $rc; +    private $gpg; +    private $homedir; +    private $user; + +    function __construct($user) +    { +        $rcmail = rcmail::get_instance(); +        $this->rc = $rcmail; +        $this->user = $user; +    } + +    /** +     * Driver initialization and environment checking. +     * Should only return critical errors. +     * +     * @return mixed NULL on success, enigma_error on failure +     */ +    function init() +    { +        $homedir = $this->rc->config->get('enigma_pgp_homedir', INSTALL_PATH . '/plugins/enigma/home'); + +        if (!$homedir) +            return new enigma_error(enigma_error::E_INTERNAL, +                "Option 'enigma_pgp_homedir' not specified"); + +        // check if homedir exists (create it if not) and is readable +        if (!file_exists($homedir)) +            return new enigma_error(enigma_error::E_INTERNAL, +                "Keys directory doesn't exists: $homedir"); +        if (!is_writable($homedir)) +            return new enigma_error(enigma_error::E_INTERNAL, +                "Keys directory isn't writeable: $homedir"); + +        $homedir = $homedir . '/' . $this->user; + +        // check if user's homedir exists (create it if not) and is readable +        if (!file_exists($homedir)) +            mkdir($homedir, 0700); + +        if (!file_exists($homedir)) +            return new enigma_error(enigma_error::E_INTERNAL, +                "Unable to create keys directory: $homedir"); +        if (!is_writable($homedir)) +            return new enigma_error(enigma_error::E_INTERNAL, +                "Unable to write to keys directory: $homedir"); + +        $this->homedir = $homedir; + +        // Create Crypt_GPG object +        try { +	        $this->gpg = new Crypt_GPG(array( +                'homedir'   => $this->homedir, +//                'debug'     => true, +          )); +        } +        catch (Exception $e) { +            return $this->get_error_from_exception($e); +        } +    } + +    function encrypt($text, $keys) +    { +/* +	    foreach ($keys as $key) { +		    $this->gpg->addEncryptKey($key); +	    } +	    $enc = $this->gpg->encrypt($text); +	    return $enc; +*/ +    } + +    function decrypt($text, $key, $passwd) +    { +//	    $this->gpg->addDecryptKey($key, $passwd); +        try { +    	    $dec = $this->gpg->decrypt($text); +    	    return $dec; +        } +        catch (Exception $e) { +            return $this->get_error_from_exception($e); +        } +    } + +    function sign($text, $key, $passwd) +    { +/* +	    $this->gpg->addSignKey($key, $passwd); +	    $signed = $this->gpg->sign($text, Crypt_GPG::SIGN_MODE_DETACHED); +	    return $signed; +*/ +    } + +    function verify($text, $signature) +    { +        try { +    	    $verified = $this->gpg->verify($text, $signature); +      	    return $this->parse_signature($verified[0]); +        } +        catch (Exception $e) { +            return $this->get_error_from_exception($e); +        } +    } + +    public function import($content, $isfile=false) +    { +        try { +            if ($isfile) +                return $this->gpg->importKeyFile($content); +            else +                return $this->gpg->importKey($content); +        } +        catch (Exception $e) { +            return $this->get_error_from_exception($e); +        } +    } +     +    public function list_keys($pattern='') +    { +        try { +    	    $keys = $this->gpg->getKeys($pattern); +            $result = array(); +//print_r($keys); +            foreach ($keys as $idx => $key) { +                $result[] = $this->parse_key($key); +                unset($keys[$idx]); +            } +//print_r($result); +      	    return $result; +        } +        catch (Exception $e) { +            return $this->get_error_from_exception($e); +        } +    } +     +    public function get_key($keyid) +    { +        $list = $this->list_keys($keyid); + +        if (is_array($list)) +            return array_shift($list); + +        // error         +        return $list; +    } + +    public function gen_key($data) +    { +    } + +    public function del_key($keyid) +    { +//        $this->get_key($keyid); +         +         +    } +     +    public function del_privkey($keyid) +    { +        try { +    	    $this->gpg->deletePrivateKey($keyid); +            return true; +        } +        catch (Exception $e) { +            return $this->get_error_from_exception($e); +        } +    } + +    public function del_pubkey($keyid) +    { +        try { +    	    $this->gpg->deletePublicKey($keyid); +            return true; +        } +        catch (Exception $e) { +            return $this->get_error_from_exception($e); +        } +    } +     +    /** +     * Converts Crypt_GPG exception into Enigma's error object +     * +     * @param mixed Exception object +     * +     * @return enigma_error Error object +     */ +    private function get_error_from_exception($e) +    { +        $data = array(); + +        if ($e instanceof Crypt_GPG_KeyNotFoundException) { +            $error = enigma_error::E_KEYNOTFOUND; +            $data['id'] = $e->getKeyId(); +        } +        else if ($e instanceof Crypt_GPG_BadPassphraseException) { +            $error = enigma_error::E_BADPASS; +            $data['bad']     = $e->getBadPassphrases(); +            $data['missing'] = $e->getMissingPassphrases(); +        } +        else if ($e instanceof Crypt_GPG_NoDataException) +            $error = enigma_error::E_NODATA; +        else if ($e instanceof Crypt_GPG_DeletePrivateKeyException) +            $error = enigma_error::E_DELKEY; +        else +            $error = enigma_error::E_INTERNAL; + +        $msg = $e->getMessage(); + +        return new enigma_error($error, $msg, $data); +    } + +    /** +     * Converts Crypt_GPG_Signature object into Enigma's signature object +     * +     * @param Crypt_GPG_Signature Signature object +     * +     * @return enigma_signature Signature object +     */ +    private function parse_signature($sig) +    { +        $user = $sig->getUserId(); + +        $data = new enigma_signature(); +        $data->id          = $sig->getId(); +        $data->valid       = $sig->isValid(); +        $data->fingerprint = $sig->getKeyFingerprint(); +        $data->created     = $sig->getCreationDate(); +        $data->expires     = $sig->getExpirationDate(); +        $data->name        = $user->getName(); +        $data->comment     = $user->getComment(); +        $data->email       = $user->getEmail(); + +        return $data; +    } + +    /** +     * Converts Crypt_GPG_Key object into Enigma's key object +     * +     * @param Crypt_GPG_Key Key object +     * +     * @return enigma_key Key object +     */ +    private function parse_key($key) +    { +        $ekey = new enigma_key(); + +        foreach ($key->getUserIds() as $idx => $user) { +            $id = new enigma_userid(); +            $id->name    = $user->getName(); +            $id->comment = $user->getComment(); +            $id->email   = $user->getEmail(); +            $id->valid   = $user->isValid(); +            $id->revoked = $user->isRevoked(); + +            $ekey->users[$idx] = $id; +        } +         +        $ekey->name = trim($ekey->users[0]->name . ' <' . $ekey->users[0]->email . '>'); + +        foreach ($key->getSubKeys() as $idx => $subkey) { +                $skey = new enigma_subkey(); +                $skey->id          = $subkey->getId(); +                $skey->revoked     = $subkey->isRevoked(); +                $skey->created     = $subkey->getCreationDate(); +                $skey->expires     = $subkey->getExpirationDate(); +                $skey->fingerprint = $subkey->getFingerprint(); +                $skey->has_private = $subkey->hasPrivate(); +                $skey->can_sign    = $subkey->canSign(); +                $skey->can_encrypt = $subkey->canEncrypt(); + +                $ekey->subkeys[$idx] = $skey; +        }; +         +        $ekey->id = $ekey->subkeys[0]->id; +         +        return $ekey; +    } +} diff --git a/plugins/enigma/lib/enigma_engine.php b/plugins/enigma/lib/enigma_engine.php new file mode 100644 index 000000000..59ae1202c --- /dev/null +++ b/plugins/enigma/lib/enigma_engine.php @@ -0,0 +1,547 @@ +<?php +/* + +-------------------------------------------------------------------------+ + | Engine of the Enigma Plugin                                             | + |                                                                         | + | This program is free software; you can redistribute it and/or modify    | + | it under the terms of the GNU General Public License version 2          | + | as published by the Free Software Foundation.                           | + |                                                                         | + | This program 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 General Public License for more details.                            | + |                                                                         | + | You should have received a copy of the GNU General Public License along | + | with this program; if not, write to the Free Software Foundation, Inc., | + | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             | + |                                                                         | + +-------------------------------------------------------------------------+ + | Author: Aleksander Machniak <alec@alec.pl>                              | + +-------------------------------------------------------------------------+ + +*/ + +/* +    RFC2440: OpenPGP Message Format +    RFC3156: MIME Security with OpenPGP +    RFC3851: S/MIME +*/ + +class enigma_engine +{ +    private $rc; +    private $enigma; +    private $pgp_driver; +    private $smime_driver; + +    public $decryptions = array(); +    public $signatures = array(); +    public $signed_parts = array(); + + +    /** +     * Plugin initialization. +     */ +    function __construct($enigma) +    { +        $rcmail = rcmail::get_instance(); +        $this->rc = $rcmail;     +        $this->enigma = $enigma; +    } + +    /** +     * PGP driver initialization. +     */ +    function load_pgp_driver() +    { +        if ($this->pgp_driver) +            return; + +        $driver = 'enigma_driver_' . $this->rc->config->get('enigma_pgp_driver', 'gnupg'); +        $username = $this->rc->user->get_username(); + +        // Load driver +        $this->pgp_driver = new $driver($username); + +        if (!$this->pgp_driver) { +            raise_error(array( +                'code' => 600, 'type' => 'php', +                'file' => __FILE__, 'line' => __LINE__, +                'message' => "Enigma plugin: Unable to load PGP driver: $driver" +            ), true, true); +        } + +        // Initialise driver +        $result = $this->pgp_driver->init(); + +        if ($result instanceof enigma_error) { +            raise_error(array( +                'code' => 600, 'type' => 'php', +                'file' => __FILE__, 'line' => __LINE__, +                'message' => "Enigma plugin: ".$result->getMessage() +            ), true, true); +        } +    } + +    /** +     * S/MIME driver initialization. +     */ +    function load_smime_driver() +    { +        if ($this->smime_driver) +            return; + +        // NOT IMPLEMENTED! +        return; + +        $driver = 'enigma_driver_' . $this->rc->config->get('enigma_smime_driver', 'phpssl'); +        $username = $this->rc->user->get_username(); + +        // Load driver +        $this->smime_driver = new $driver($username); + +        if (!$this->smime_driver) { +            raise_error(array( +                'code' => 600, 'type' => 'php', +                'file' => __FILE__, 'line' => __LINE__, +                'message' => "Enigma plugin: Unable to load S/MIME driver: $driver" +            ), true, true); +        } + +        // Initialise driver +        $result = $this->smime_driver->init(); + +        if ($result instanceof enigma_error) { +            raise_error(array( +                'code' => 600, 'type' => 'php', +                'file' => __FILE__, 'line' => __LINE__, +                'message' => "Enigma plugin: ".$result->getMessage() +            ), true, true); +        } +    } + +    /** +     * Handler for plain/text message. +     * +     * @param array Reference to hook's parameters +     */ +    function parse_plain(&$p) +    { +        $part = $p['structure']; + +        // Get message body from IMAP server +        $this->set_part_body($part, $p['object']->uid); + +        // @TODO: big message body can be a file resource +        // PGP signed message +        if (preg_match('/^-----BEGIN PGP SIGNED MESSAGE-----/', $part->body)) { +            $this->parse_plain_signed($p); +        } +        // PGP encrypted message +        else if (preg_match('/^-----BEGIN PGP MESSAGE-----/', $part->body)) { +            $this->parse_plain_encrypted($p); +        } +    } + +    /** +     * Handler for multipart/signed message. +     * +     * @param array Reference to hook's parameters +     */ +    function parse_signed(&$p) +    { +        $struct = $p['structure']; + +        // S/MIME +        if ($struct->parts[1] && $struct->parts[1]->mimetype == 'application/pkcs7-signature') { +            $this->parse_smime_signed($p); +        } +        // PGP/MIME: +        // The multipart/signed body MUST consist of exactly two parts. +        // The first part contains the signed data in MIME canonical format, +        // including a set of appropriate content headers describing the data. +        // The second body MUST contain the PGP digital signature.  It MUST be +        // labeled with a content type of "application/pgp-signature". +        else if ($struct->parts[1] && $struct->parts[1]->mimetype == 'application/pgp-signature') { +            $this->parse_pgp_signed($p); +        } +    } + +    /** +     * Handler for multipart/encrypted message. +     * +     * @param array Reference to hook's parameters +     */ +    function parse_encrypted(&$p) +    { +        $struct = $p['structure']; + +        // S/MIME +        if ($struct->mimetype == 'application/pkcs7-mime') { +            $this->parse_smime_encrypted($p); +        } +        // PGP/MIME: +        // The multipart/encrypted MUST consist of exactly two parts.  The first +        // MIME body part must have a content type of "application/pgp-encrypted". +        // This body contains the control information. +        // The second MIME body part MUST contain the actual encrypted data.  It +        // must be labeled with a content type of "application/octet-stream". +        else if ($struct->parts[0] && $struct->parts[0]->mimetype == 'application/pgp-encrypted' && +            $struct->parts[1] && $struct->parts[1]->mimetype == 'application/octet-stream' +        ) { +            $this->parse_pgp_encrypted($p); +        } +    } + +    /** +     * Handler for plain signed message. +     * Excludes message and signature bodies and verifies signature. +     * +     * @param array Reference to hook's parameters +     */ +    private function parse_plain_signed(&$p) +    { +        $this->load_pgp_driver(); +        $part = $p['structure']; + +        // Verify signature +        if ($this->rc->action == 'show' || $this->rc->action == 'preview') { +            $sig = $this->pgp_verify($part->body); +        } + +        // @TODO: Handle big bodies using (temp) files + +        // In this way we can use fgets on string as on file handle +        $fh = fopen('php://memory', 'br+'); +        // @TODO: fopen/fwrite errors handling +        if ($fh) { +            fwrite($fh, $part->body); +            rewind($fh); +        } +        $part->body = null; + +        // Extract body (and signature?) +        while (!feof($fh)) { +            $line = fgets($fh, 1024); + +            if ($part->body === null) +                $part->body = ''; +            else if (preg_match('/^-----BEGIN PGP SIGNATURE-----/', $line)) +                break; +            else +                $part->body .= $line; +        } + +        // Remove "Hash" Armor Headers +        $part->body = preg_replace('/^.*\r*\n\r*\n/', '', $part->body); +        // de-Dash-Escape (RFC2440) +        $part->body = preg_replace('/(^|\n)- -/', '\\1-', $part->body); + +        // Store signature data for display +        if (!empty($sig)) { +            $this->signed_parts[$part->mime_id] = $part->mime_id; +            $this->signatures[$part->mime_id] = $sig; +        } + +        fclose($fh); +    } +     +    /** +     * Handler for PGP/MIME signed message. +     * Verifies signature. +     * +     * @param array Reference to hook's parameters +     */ +    private function parse_pgp_signed(&$p) +    { +        $this->load_pgp_driver(); +        $struct = $p['structure']; +         +        // Verify signature +        if ($this->rc->action == 'show' || $this->rc->action == 'preview') { +            $msg_part = $struct->parts[0]; +            $sig_part = $struct->parts[1]; +         +            // Get bodies +            $this->set_part_body($msg_part, $p['object']->uid); +            $this->set_part_body($sig_part, $p['object']->uid); + +            // Verify +            $sig = $this->pgp_verify($msg_part->body, $sig_part->body); + +            // Store signature data for display +            $this->signatures[$struct->mime_id] = $sig; + +            // Message can be multipart (assign signature to each subpart) +            if (!empty($msg_part->parts)) { +                foreach ($msg_part->parts as $part) +                    $this->signed_parts[$part->mime_id] = $struct->mime_id; +            } +            else +                $this->signed_parts[$msg_part->mime_id] = $struct->mime_id; + +            // Remove signature file from attachments list +            unset($struct->parts[1]); +        } +    } + +    /** +     * Handler for S/MIME signed message. +     * Verifies signature. +     * +     * @param array Reference to hook's parameters +     */ +    private function parse_smime_signed(&$p) +    { +        $this->load_smime_driver(); +    } + +    /** +     * Handler for plain encrypted message. +     * +     * @param array Reference to hook's parameters +     */ +    private function parse_plain_encrypted(&$p) +    { +        $this->load_pgp_driver(); +        $part = $p['structure']; +         +        // Get body +        $this->set_part_body($part, $p['object']->uid); + +        // Decrypt  +        $result = $this->pgp_decrypt($part->body); +         +        // Store decryption status +        $this->decryptions[$part->mime_id] = $result; +         +        // Parse decrypted message +        if ($result === true) { +            // @TODO +        } +    } +     +    /** +     * Handler for PGP/MIME encrypted message. +     * +     * @param array Reference to hook's parameters +     */ +    private function parse_pgp_encrypted(&$p) +    { +        $this->load_pgp_driver(); +        $struct = $p['structure']; +        $part = $struct->parts[1]; +         +        // Get body +        $this->set_part_body($part, $p['object']->uid); + +        // Decrypt +        $result = $this->pgp_decrypt($part->body); + +        $this->decryptions[$part->mime_id] = $result; +//print_r($part); +        // Parse decrypted message +        if ($result === true) { +            // @TODO +        } +        else { +            // Make sure decryption status message will be displayed +            $part->type = 'content'; +            $p['object']->parts[] = $part; +        } +    } + +    /** +     * Handler for S/MIME encrypted message. +     * +     * @param array Reference to hook's parameters +     */ +    private function parse_smime_encrypted(&$p) +    { +        $this->load_smime_driver(); +    } + +    /** +     * PGP signature verification. +     * +     * @param mixed Message body +     * @param mixed Signature body (for MIME messages) +     * +     * @return mixed enigma_signature or enigma_error +     */ +    private function pgp_verify(&$msg_body, $sig_body=null) +    { +        // @TODO: Handle big bodies using (temp) files +        // @TODO: caching of verification result +         +         $sig = $this->pgp_driver->verify($msg_body, $sig_body); + +         if (($sig instanceof enigma_error) && $sig->getCode() != enigma_error::E_KEYNOTFOUND) +             raise_error(array( +                'code' => 600, 'type' => 'php', +                'file' => __FILE__, 'line' => __LINE__, +                'message' => "Enigma plugin: " . $error->getMessage() +                ), true, false); + +//print_r($sig); +        return $sig; +    } + +    /** +     * PGP message decryption. +     * +     * @param mixed Message body +     * +     * @return mixed True or enigma_error +     */ +    private function pgp_decrypt(&$msg_body) +    { +        // @TODO: Handle big bodies using (temp) files +        // @TODO: caching of verification result +         +        $result = $this->pgp_driver->decrypt($msg_body, $key, $pass); + +//print_r($result); + +        if ($result instanceof enigma_error) { +            $err_code = $result->getCode(); +            if (!in_array($err_code, array(enigma_error::E_KEYNOTFOUND, enigma_error::E_BADPASS))) +                raise_error(array( +                    'code' => 600, 'type' => 'php', +                    'file' => __FILE__, 'line' => __LINE__, +                    'message' => "Enigma plugin: " . $result->getMessage() +                    ), true, false); +            return $result; +        } + +//        $msg_body = $result; +        return true; +    } + +    /** +     * PGP keys listing. +     * +     * @param mixed Key ID/Name pattern +     * +     * @return mixed Array of keys or enigma_error +     */ +    function list_keys($pattern='') +    { +        $this->load_pgp_driver(); +        $result = $this->pgp_driver->list_keys($pattern); +     +        if ($result instanceof enigma_error) { +            raise_error(array( +                'code' => 600, 'type' => 'php', +                'file' => __FILE__, 'line' => __LINE__, +                'message' => "Enigma plugin: " . $result->getMessage() +                ), true, false); +        } +         +        return $result; +    } + +    /** +     * PGP key details. +     * +     * @param mixed Key ID +     * +     * @return mixed enigma_key or enigma_error +     */ +    function get_key($keyid) +    { +        $this->load_pgp_driver(); +        $result = $this->pgp_driver->get_key($keyid); +     +        if ($result instanceof enigma_error) { +            raise_error(array( +                'code' => 600, 'type' => 'php', +                'file' => __FILE__, 'line' => __LINE__, +                'message' => "Enigma plugin: " . $result->getMessage() +                ), true, false); +        } +         +        return $result; +    } + +    /** +     * PGP keys/certs importing. +     * +     * @param mixed   Import file name or content +     * @param boolean True if first argument is a filename +     * +     * @return mixed Import status data array or enigma_error +     */ +    function import_key($content, $isfile=false) +    { +        $this->load_pgp_driver(); +        $result = $this->pgp_driver->import($content, $isfile); + +        if ($result instanceof enigma_error) { +            raise_error(array( +                'code' => 600, 'type' => 'php', +                'file' => __FILE__, 'line' => __LINE__, +                'message' => "Enigma plugin: " . $result->getMessage() +                ), true, false); +        } +        else { +            $result['imported'] = $result['public_imported'] + $result['private_imported']; +            $result['unchanged'] = $result['public_unchanged'] + $result['private_unchanged']; +        } + +        return $result; +    } + +    /** +     * Handler for keys/certs import request action +     */ +    function import_file() +    { +        $uid = get_input_value('_uid', RCUBE_INPUT_POST); +        $mbox = get_input_value('_mbox', RCUBE_INPUT_POST); +        $mime_id = get_input_value('_part', RCUBE_INPUT_POST); + +        if ($uid && $mime_id) { +            $part = $this->rc->imap->get_message_part($uid, $mime_id); +        } + +        if ($part && is_array($result = $this->import_key($part))) { +            $this->rc->output->show_message('enigma.keysimportsuccess', 'confirmation', +                array('new' => $result['imported'], 'old' => $result['unchanged'])); +        } +        else +            $this->rc->output->show_message('enigma.keysimportfailed', 'error'); +     +        $this->rc->output->send(); +    } + +    /** +     * Checks if specified message part contains body data. +     * If body is not set it will be fetched from IMAP server. +     * +     * @param rcube_message_part Message part object +     * @param integer            Message UID +     */ +    private function set_part_body($part, $uid) +    { +        // @TODO: Create such function in core +        // @TODO: Handle big bodies using file handles +        if (!isset($part->body)) { +            $part->body = $this->rc->imap->get_message_part( +                $uid, $part->mime_id, $part); +        } +    } + +    /** +     * Adds CSS style file to the page header. +     */ +    private function add_css() +    { +        $skin = $this->rc->config->get('skin'); +        if (!file_exists($this->home . "/skins/$skin/enigma.css")) +            $skin = 'default'; + +        $this->include_stylesheet("skins/$skin/enigma.css");                                                 +    } +} diff --git a/plugins/enigma/lib/enigma_error.php b/plugins/enigma/lib/enigma_error.php new file mode 100644 index 000000000..9f424dc2b --- /dev/null +++ b/plugins/enigma/lib/enigma_error.php @@ -0,0 +1,62 @@ +<?php +/* + +-------------------------------------------------------------------------+ + | Error class for the Enigma Plugin                                       | + |                                                                         | + | This program is free software; you can redistribute it and/or modify    | + | it under the terms of the GNU General Public License version 2          | + | as published by the Free Software Foundation.                           | + |                                                                         | + | This program 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 General Public License for more details.                            | + |                                                                         | + | You should have received a copy of the GNU General Public License along | + | with this program; if not, write to the Free Software Foundation, Inc., | + | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             | + |                                                                         | + +-------------------------------------------------------------------------+ + | Author: Aleksander Machniak <alec@alec.pl>                              | + +-------------------------------------------------------------------------+ +*/ + +class enigma_error +{ +    private $code; +    private $message; +    private $data = array(); + +    // error codes +    const E_OK = 0; +    const E_INTERNAL = 1; +    const E_NODATA = 2; +    const E_KEYNOTFOUND = 3; +    const E_DELKEY = 4; +    const E_BADPASS = 5; +     +    function __construct($code = null, $message = '', $data = array()) +    { +        $this->code = $code; +        $this->message = $message; +        $this->data = $data; +    } + +    function getCode() +    { +        return $this->code; +    } + +    function getMessage() +    { +        return $this->message; +    } + +    function getData($name) +    { +        if ($name) +            return $this->data[$name]; +        else +            return $this->data; +    } +} diff --git a/plugins/enigma/lib/enigma_key.php b/plugins/enigma/lib/enigma_key.php new file mode 100644 index 000000000..520c36b0b --- /dev/null +++ b/plugins/enigma/lib/enigma_key.php @@ -0,0 +1,129 @@ +<?php +/* + +-------------------------------------------------------------------------+ + | Key class for the Enigma Plugin                                         | + |                                                                         | + | This program is free software; you can redistribute it and/or modify    | + | it under the terms of the GNU General Public License version 2          | + | as published by the Free Software Foundation.                           | + |                                                                         | + | This program 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 General Public License for more details.                            | + |                                                                         | + | You should have received a copy of the GNU General Public License along | + | with this program; if not, write to the Free Software Foundation, Inc., | + | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             | + |                                                                         | + +-------------------------------------------------------------------------+ + | Author: Aleksander Machniak <alec@alec.pl>                              | + +-------------------------------------------------------------------------+ +*/ + +class enigma_key +{ +    public $id; +    public $name; +    public $users = array(); +    public $subkeys = array(); + +    const TYPE_UNKNOWN = 0; +    const TYPE_KEYPAIR = 1; +    const TYPE_PUBLIC = 2; + +    /** +     * Keys list sorting callback for usort() +     */ +    static function cmp($a, $b) +    { +        return strcmp($a->name, $b->name); +    } + +    /** +     * Returns key type +     */ +    function get_type() +    { +        if ($this->subkeys[0]->has_private) +            return enigma_key::TYPE_KEYPAIR; +        else if (!empty($this->subkeys[0])) +            return enigma_key::TYPE_PUBLIC; + +        return enigma_key::TYPE_UNKNOWN; +    } + +    /** +     * Returns true if all user IDs are revoked +     */     +    function is_revoked() +    { +        foreach ($this->subkeys as $subkey) +            if (!$subkey->revoked) +                return false; + +        return true; +    } + +    /** +     * Returns true if any user ID is valid +     */     +    function is_valid() +    { +        foreach ($this->users as $user) +            if ($user->valid) +                return true; + +        return false; +    } +     +    /** +     * Returns true if any of subkeys is not expired +     */     +    function is_expired() +    { +        $now = time(); +         +        foreach ($this->subkeys as $subkey) +            if (!$subkey->expires || $subkey->expires > $now) +                return true; +     +        return false; +    } + +    /** +     * Converts long ID or Fingerprint to short ID +     * Crypt_GPG uses internal, but e.g. Thunderbird's Enigmail displays short ID +     * +     * @param string Key ID or fingerprint +     * @return string Key short ID +     */ +    static function format_id($id) +    { +        // E.g. 04622F2089E037A5 => 89E037A5 +         +        return substr($id, -8); +    } + +    /** +     * Formats fingerprint string +     * +     * @param string Key fingerprint +     * +     * @return string Formatted fingerprint (with spaces) +     */ +    static function format_fingerprint($fingerprint) +    { +        if (!$fingerprint) +            return ''; +     +        $result = ''; +        for ($i=0; $i<40; $i++) { +            if ($i % 4 == 0) +                $result .= ' '; +            $result .= $fingerprint[$i]; +        } +        return $result; +    } + +} diff --git a/plugins/enigma/lib/enigma_signature.php b/plugins/enigma/lib/enigma_signature.php new file mode 100644 index 000000000..65990903b --- /dev/null +++ b/plugins/enigma/lib/enigma_signature.php @@ -0,0 +1,34 @@ +<?php +/* + +-------------------------------------------------------------------------+ + | Signature class for the Enigma Plugin                                   | + |                                                                         | + | This program is free software; you can redistribute it and/or modify    | + | it under the terms of the GNU General Public License version 2          | + | as published by the Free Software Foundation.                           | + |                                                                         | + | This program 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 General Public License for more details.                            | + |                                                                         | + | You should have received a copy of the GNU General Public License along | + | with this program; if not, write to the Free Software Foundation, Inc., | + | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             | + |                                                                         | + +-------------------------------------------------------------------------+ + | Author: Aleksander Machniak <alec@alec.pl>                              | + +-------------------------------------------------------------------------+ +*/ + +class enigma_signature +{ +    public $id; +    public $valid; +    public $fingerprint; +    public $created; +    public $expires; +    public $name; +    public $comment; +    public $email; +} diff --git a/plugins/enigma/lib/enigma_subkey.php b/plugins/enigma/lib/enigma_subkey.php new file mode 100644 index 000000000..1b9fb95ad --- /dev/null +++ b/plugins/enigma/lib/enigma_subkey.php @@ -0,0 +1,57 @@ +<?php +/* + +-------------------------------------------------------------------------+ + | SubKey class for the Enigma Plugin                                      | + |                                                                         | + | This program is free software; you can redistribute it and/or modify    | + | it under the terms of the GNU General Public License version 2          | + | as published by the Free Software Foundation.                           | + |                                                                         | + | This program 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 General Public License for more details.                            | + |                                                                         | + | You should have received a copy of the GNU General Public License along | + | with this program; if not, write to the Free Software Foundation, Inc., | + | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             | + |                                                                         | + +-------------------------------------------------------------------------+ + | Author: Aleksander Machniak <alec@alec.pl>                              | + +-------------------------------------------------------------------------+ +*/ + +class enigma_subkey +{ +    public $id; +    public $fingerprint; +    public $expires; +    public $created; +    public $revoked; +    public $has_private; +    public $can_sign; +    public $can_encrypt; +     +    /** +     * Converts internal ID to short ID +     * Crypt_GPG uses internal, but e.g. Thunderbird's Enigmail displays short ID +     * +     * @return string Key ID +     */ +    function get_short_id() +    { +        // E.g. 04622F2089E037A5 => 89E037A5 +        return enigma_key::format_id($this->id); +    } + +    /** +     * Getter for formatted fingerprint +     * +     * @return string Formatted fingerprint +     */ +    function get_fingerprint() +    { +        return enigma_key::format_fingerprint($this->fingerprint); +    } + +} diff --git a/plugins/enigma/lib/enigma_ui.php b/plugins/enigma/lib/enigma_ui.php new file mode 100644 index 000000000..b9ccff53d --- /dev/null +++ b/plugins/enigma/lib/enigma_ui.php @@ -0,0 +1,459 @@ +<?php +/* + +-------------------------------------------------------------------------+ + | User Interface for the Enigma Plugin                                    | + |                                                                         | + | This program is free software; you can redistribute it and/or modify    | + | it under the terms of the GNU General Public License version 2          | + | as published by the Free Software Foundation.                           | + |                                                                         | + | This program 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 General Public License for more details.                            | + |                                                                         | + | You should have received a copy of the GNU General Public License along | + | with this program; if not, write to the Free Software Foundation, Inc., | + | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             | + |                                                                         | + +-------------------------------------------------------------------------+ + | Author: Aleksander Machniak <alec@alec.pl>                              | + +-------------------------------------------------------------------------+ +*/ + +class enigma_ui +{ +    private $rc; +    private $enigma; +    private $home; +    private $css_added; +    private $data; + + +    function __construct($enigma_plugin, $home='') +    { +        $this->enigma = $enigma_plugin; +        $this->rc = $enigma_plugin->rc; +        // we cannot use $enigma_plugin->home here +        $this->home = $home; +    } + +    /** +     * UI initialization and requests handlers. +     * +     * @param string Preferences section +     */ +    function init($section='') +    { +        $this->enigma->include_script('enigma.js'); + +        // Enigma actions +        if ($this->rc->action == 'plugin.enigma') { +            $action = get_input_value('_a', RCUBE_INPUT_GPC); + +            switch ($action) { +                case 'keyedit': +                    $this->key_edit(); +                    break; +                case 'keyimport': +                    $this->key_import(); +                    break; +                case 'keysearch': +                case 'keylist': +                    $this->key_list(); +                    break; +                case 'keyinfo': +                default: +                    $this->key_info(); +            } +        } +        // Message composing UI +        else if ($this->rc->action == 'compose') { +            $this->compose_ui(); +        } +        // Preferences UI +        else { // if ($this->rc->action == 'edit-prefs') { +            if ($section == 'enigmacerts') { +                $this->rc->output->add_handlers(array( +                    'keyslist' => array($this, 'tpl_certs_list'), +                    'keyframe' => array($this, 'tpl_cert_frame'), +                    'countdisplay' => array($this, 'tpl_certs_rowcount'), +                    'searchform' => array($this->rc->output, 'search_form'), +                )); +                $this->rc->output->set_pagetitle($this->enigma->gettext('enigmacerts')); +                $this->rc->output->send('enigma.certs');  +            } +            else { +                $this->rc->output->add_handlers(array( +                    'keyslist' => array($this, 'tpl_keys_list'), +                    'keyframe' => array($this, 'tpl_key_frame'), +                    'countdisplay' => array($this, 'tpl_keys_rowcount'), +                    'searchform' => array($this->rc->output, 'search_form'), +                )); +                $this->rc->output->set_pagetitle($this->enigma->gettext('enigmakeys')); +                $this->rc->output->send('enigma.keys');  +            } +        } +    } + +   /** +     * Adds CSS style file to the page header. +     */ +    function add_css() +    { +        if ($this->css_loaded) +            return; + +        $skin = $this->rc->config->get('skin'); +        if (!file_exists($this->home . "/skins/$skin/enigma.css")) +            $skin = 'default'; + +        $this->enigma->include_stylesheet("skins/$skin/enigma.css"); +        $this->css_added = true; +    } + +    /** +     * Template object for key info/edit frame. +     * +     * @param array Object attributes +     * +     * @return string HTML output +     */ +    function tpl_key_frame($attrib) +    { +        if (!$attrib['id']) { +            $attrib['id'] = 'rcmkeysframe'; +        } + +        $attrib['name'] = $attrib['id']; + +        $this->rc->output->set_env('contentframe', $attrib['name']); +        $this->rc->output->set_env('blankpage', $attrib['src'] ?  +            $this->rc->output->abs_url($attrib['src']) : 'program/blank.gif'); + +        return html::tag('iframe', $attrib); +    } + +    /** +     * Template object for list of keys. +     * +     * @param array Object attributes +     * +     * @return string HTML content +     */ +    function tpl_keys_list($attrib) +    { +        // add id to message list table if not specified +        if (!strlen($attrib['id'])) { +            $attrib['id'] = 'rcmenigmakeyslist'; +        } + +        // define list of cols to be displayed +        $a_show_cols = array('name'); + +        // create XHTML table +        $out = rcube_table_output($attrib, array(), $a_show_cols, 'id'); + +        // set client env +        $this->rc->output->add_gui_object('keyslist', $attrib['id']); +        $this->rc->output->include_script('list.js'); + +        // add some labels to client +        $this->rc->output->add_label('enigma.keyconfirmdelete'); + +        return $out; +    } + +    /** +     * Key listing (and searching) request handler +     */ +    private function key_list() +    { +        $this->enigma->load_engine(); + +        $pagesize = $this->rc->config->get('pagesize', 100); +        $page     = max(intval(get_input_value('_p', RCUBE_INPUT_GPC)), 1); +        $search   = get_input_value('_q', RCUBE_INPUT_GPC); + +        // define list of cols to be displayed +        $a_show_cols = array('name'); +        $result = array(); + +        // Get the list +        $list = $this->enigma->engine->list_keys($search); + +        if ($list && ($list instanceof enigma_error)) +            $this->rc->output->show_message('enigma.keylisterror', 'error'); +        else if (empty($list)) +            $this->rc->output->show_message('enigma.nokeysfound', 'notice'); +        else { +            if (is_array($list)) { +                // Save the size +                $listsize = count($list); + +                // Sort the list by key (user) name +                usort($list, array('enigma_key', 'cmp')); + +                // Slice current page +                $list = array_slice($list, ($page - 1) * $pagesize, $pagesize); + +                $size = count($list); + +                // Add rows +                foreach($list as $idx => $key) { +                    $this->rc->output->command('enigma_add_list_row', +                        array('name' => Q($key->name), 'id' => $key->id)); +                } +            } +        } + +        $this->rc->output->set_env('search_request', $search); +        $this->rc->output->set_env('pagecount', ceil($listsize/$pagesize)); +        $this->rc->output->set_env('current_page', $page); +        $this->rc->output->command('set_rowcount', +            $this->get_rowcount_text($listsize, $size, $page)); + +        $this->rc->output->send(); +    } + +    /** +     * Template object for list records counter. +     * +     * @param array Object attributes +     * +     * @return string HTML output +     */ +    function tpl_keys_rowcount($attrib) +    { +        if (!$attrib['id']) +            $attrib['id'] = 'rcmcountdisplay'; + +        $this->rc->output->add_gui_object('countdisplay', $attrib['id']); + +        return html::span($attrib, $this->get_rowcount_text()); +    } + +    /** +     * Returns text representation of list records counter +     */ +    private function get_rowcount_text($all=0, $curr_count=0, $page=1) +    { +        if (!$curr_count) +            $out = $this->enigma->gettext('nokeysfound'); +        else { +            $pagesize = $this->rc->config->get('pagesize', 100); +            $first = ($page - 1) * $pagesize; + +            $out = $this->enigma->gettext(array( +                'name' => 'keysfromto', +                'vars' => array( +                    'from'  => $first + 1, +                    'to'    => $first + $curr_count, +                    'count' => $all) +            )); +        } + +        return $out; +    } + +    /** +     * Key information page handler +     */ +    private function key_info() +    { +        $id = get_input_value('_id', RCUBE_INPUT_GET); + +        $this->enigma->load_engine(); +        $res = $this->enigma->engine->get_key($id); + +        if ($res instanceof enigma_key) +            $this->data = $res; +        else { // error +            $this->rc->output->show_message('enigma.keyopenerror', 'error'); +            $this->rc->output->command('parent.enigma_loadframe'); +            $this->rc->output->send('iframe'); +        } + +        $this->rc->output->add_handlers(array( +            'keyname' => array($this, 'tpl_key_name'), +            'keydata' => array($this, 'tpl_key_data'), +        )); + +        $this->rc->output->set_pagetitle($this->enigma->gettext('keyinfo')); +        $this->rc->output->send('enigma.keyinfo'); +    } + +    /** +     * Template object for key name +     */ +    function tpl_key_name($attrib) +    { +        return Q($this->data->name); +    } + +    /** +     * Template object for key information page content +     */ +    function tpl_key_data($attrib) +    { +        $out = ''; +        $table = new html_table(array('cols' => 2));  + +        // Key user ID +        $table->add('title', $this->enigma->gettext('keyuserid')); +        $table->add(null, Q($this->data->name)); +        // Key ID +        $table->add('title', $this->enigma->gettext('keyid')); +        $table->add(null, $this->data->subkeys[0]->get_short_id()); +        // Key type +        $keytype = $this->data->get_type(); +        if ($keytype == enigma_key::TYPE_KEYPAIR) +            $type = $this->enigma->gettext('typekeypair'); +        else if ($keytype == enigma_key::TYPE_PUBLIC) +            $type = $this->enigma->gettext('typepublickey'); +        $table->add('title', $this->enigma->gettext('keytype')); +        $table->add(null, $type); +        // Key fingerprint +        $table->add('title', $this->enigma->gettext('fingerprint')); +        $table->add(null, $this->data->subkeys[0]->get_fingerprint()); + +        $out .= html::tag('fieldset', null, +            html::tag('legend', null, +                $this->enigma->gettext('basicinfo')) . $table->show($attrib)); + +        // Subkeys +        $table = new html_table(array('cols' => 6));  +        // Columns: Type, ID, Algorithm, Size, Created, Expires + +        $out .= html::tag('fieldset', null, +            html::tag('legend', null,  +                $this->enigma->gettext('subkeys')) . $table->show($attrib)); + +        // Additional user IDs +        $table = new html_table(array('cols' => 2)); +        // Columns: User ID, Validity + +        $out .= html::tag('fieldset', null, +            html::tag('legend', null,  +                $this->enigma->gettext('userids')) . $table->show($attrib)); + +        return $out; +    } + +    /** +     * Key import page handler +     */ +    private function key_import() +    { +        // Import process +        if ($_FILES['_file']['tmp_name'] && is_uploaded_file($_FILES['_file']['tmp_name'])) { +            $this->enigma->load_engine(); +            $result = $this->enigma->engine->import_key($_FILES['_file']['tmp_name'], true); + +            if (is_array($result)) { +                // reload list if any keys has been added +                if ($result['imported']) { +                    $this->rc->output->command('parent.enigma_list', 1); +                } +                else +                    $this->rc->output->command('parent.enigma_loadframe'); + +                $this->rc->output->show_message('enigma.keysimportsuccess', 'confirmation', +                    array('new' => $result['imported'], 'old' => $result['unchanged'])); + +                $this->rc->output->send('iframe'); +            } +            else +                $this->rc->output->show_message('enigma.keysimportfailed', 'error'); +        } +        else if ($err = $_FILES['_file']['error']) { +            if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) { +                $this->rc->output->show_message('filesizeerror', 'error', +                    array('size' => show_bytes(parse_bytes(ini_get('upload_max_filesize'))))); +            } else { +                $this->rc->output->show_message('fileuploaderror', 'error'); +            } +        } + +        $this->rc->output->add_handlers(array( +            'importform' => array($this, 'tpl_key_import_form'), +        )); + +        $this->rc->output->set_pagetitle($this->enigma->gettext('keyimport')); +        $this->rc->output->send('enigma.keyimport'); +    } + +    /** +     * Template object for key import (upload) form +     */ +    function tpl_key_import_form($attrib) +    { +        $attrib += array('id' => 'rcmKeyImportForm'); + +        $upload = new html_inputfield(array('type' => 'file', 'name' => '_file', +            'id' => 'rcmimportfile', 'size' => 30)); + +        $form = html::p(null, +            Q($this->enigma->gettext('keyimporttext'), 'show') +            . html::br() . html::br() . $upload->show() +        ); + +        $this->rc->output->add_label('selectimportfile', 'importwait'); +        $this->rc->output->add_gui_object('importform', $attrib['id']); + +        $out = $this->rc->output->form_tag(array( +            'action' => $this->rc->url(array('action' => 'plugin.enigma', 'a' => 'keyimport')), +            'method' => 'post', +            'enctype' => 'multipart/form-data') + $attrib, +            $form); + +        return $out; +    } + +    private function compose_ui() +    { +        if (!is_array($_SESSION['compose']) || $_SESSION['compose']['id'] != get_input_value('_id', RCUBE_INPUT_GET)) +            return; + +        // Options menu button +        // @TODO: make this work with non-default skins +        $this->enigma->add_button(array( +            'name' => 'enigmamenu', +            'imagepas' => 'skins/default/enigma.png', +            'imageact' => 'skins/default/enigma.png', +            'onclick' => "rcmail_ui.show_popup('enigmamenu', true); return false", +            'title' => 'securityoptions', +            'domain' => 'enigma', +            ), 'toolbar'); + +        // Options menu contents +        $this->enigma->add_hook('render_page', array($this, 'compose_menu')); +    } + +    function compose_menu($p) +    { +        $menu = new html_table(array('cols' => 2)); +        $chbox = new html_checkbox(array('value' => 1)); + +        $menu->add(null, html::label(array('for' => 'enigmadefaultopt'), +            Q($this->enigma->gettext('identdefault')))); +        $menu->add(null, $chbox->show(1, array('name' => '_enigma_default', 'id' => 'enigmadefaultopt'))); + +        $menu->add(null, html::label(array('for' => 'enigmasignopt'), +            Q($this->enigma->gettext('signmsg')))); +        $menu->add(null, $chbox->show(1, array('name' => '_enigma_sign', 'id' => 'enigmasignopt'))); + +        $menu->add(null, html::label(array('for' => 'enigmacryptopt'), +            Q($this->enigma->gettext('encryptmsg')))); +        $menu->add(null, $chbox->show(1, array('name' => '_enigma_crypt', 'id' => 'enigmacryptopt'))); + +        $menu = html::div(array('id' => 'enigmamenu', 'class' => 'popupmenu'), +            $menu->show()); + +        $p['content'] = preg_replace('/(<form name="form"[^>]+>)/i', '\\1'."\n$menu", $p['content']); + +        return $p; + +    } + +} diff --git a/plugins/enigma/lib/enigma_userid.php b/plugins/enigma/lib/enigma_userid.php new file mode 100644 index 000000000..36185e718 --- /dev/null +++ b/plugins/enigma/lib/enigma_userid.php @@ -0,0 +1,31 @@ +<?php +/* + +-------------------------------------------------------------------------+ + | User ID class for the Enigma Plugin                                     | + |                                                                         | + | This program is free software; you can redistribute it and/or modify    | + | it under the terms of the GNU General Public License version 2          | + | as published by the Free Software Foundation.                           | + |                                                                         | + | This program 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 General Public License for more details.                            | + |                                                                         | + | You should have received a copy of the GNU General Public License along | + | with this program; if not, write to the Free Software Foundation, Inc., | + | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             | + |                                                                         | + +-------------------------------------------------------------------------+ + | Author: Aleksander Machniak <alec@alec.pl>                              | + +-------------------------------------------------------------------------+ +*/ + +class enigma_userid +{ +    public $revoked; +    public $valid; +    public $name; +    public $comment; +    public $email; +} | 
