plugins/enigma/README
@@ -12,6 +12,7 @@ - Handling of PGP keys files attached to incoming messages - PGP encrypted messages decryption (started) - PGP keys management UI (started) - S/MIME signatures verification (started) * TODO (must have): plugins/enigma/enigma.php
@@ -179,10 +179,11 @@ { // add labels $this->add_texts('localization/'); /* $p['list']['enigmasettings'] = array( 'id' => 'enigmasettings', 'section' => $this->gettext('enigmasettings'), ); */ $p['list']['enigmacerts'] = array( 'id' => 'enigmacerts', 'section' => $this->gettext('enigmacerts'), ); @@ -203,11 +204,13 @@ */ function preferences_list($p) { /* if ($p['section'] == 'enigmasettings') { // This makes that section is not removed from the list $p['blocks']['dummy']['options']['dummy'] = array(); } else if ($p['section'] == 'enigmacerts') { else */ if ($p['section'] == 'enigmacerts') { // This makes that section is not removed from the list $p['blocks']['dummy']['options']['dummy'] = array(); } @@ -313,18 +316,24 @@ $attrib['id'] = 'enigma-message'; if ($sig instanceof enigma_signature) { if ($sig->valid) { $attrib['class'] = 'enigmanotice'; $sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>'; if ($sig->valid === enigma_error::E_UNVERIFIED) { $attrib['class'] = 'enigmawarning'; $msg = str_replace('$sender', $sender, $this->gettext('sigunverified')); $msg = str_replace('$keyid', $sig->id, $msg); $msg = rcube::Q($msg); } else if ($sig->valid) { $attrib['class'] = 'enigmanotice'; $msg = rcube::Q(str_replace('$sender', $sender, $this->gettext('sigvalid'))); } else { $attrib['class'] = 'enigmawarning'; $sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>'; $msg = rcube::Q(str_replace('$sender', $sender, $this->gettext('siginvalid'))); } } else if ($sig->getCode() == enigma_error::E_KEYNOTFOUND) { else if ($sig && $sig->getCode() == enigma_error::E_KEYNOTFOUND) { $attrib['class'] = 'enigmawarning'; $msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($sig->getData('id')), $this->gettext('signokey'))); plugins/enigma/lib/enigma_driver_gnupg.php
@@ -178,8 +178,6 @@ public function del_key($keyid) { // $this->get_key($keyid); } public function del_privkey($keyid) plugins/enigma/lib/enigma_driver_phpssl.php
New file @@ -0,0 +1,238 @@ <?php /* +-------------------------------------------------------------------------+ | S/MIME 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> | +-------------------------------------------------------------------------+ */ class enigma_driver_phpssl 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_smime_homedir', INSTALL_PATH . '/plugins/enigma/home'); if (!$homedir) return new enigma_error(enigma_error::E_INTERNAL, "Option 'enigma_smime_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; } function encrypt($text, $keys) { } function decrypt($text, $key, $passwd) { } function sign($text, $key, $passwd) { } function verify($struct, $message) { // use common temp dir $temp_dir = $this->rc->config->get('temp_dir'); $msg_file = tempnam($temp_dir, 'rcmMsg'); $cert_file = tempnam($temp_dir, 'rcmCert'); $fh = fopen($msg_file, "w"); if ($struct->mime_id) { $message->get_part_content($struct->mime_id, $fh, true, 0, false); } else { $this->rc->storage->get_raw_body($message->uid, $fh); } fclose($fh); // @TODO: use stored certificates // try with certificate verification $sig = openssl_pkcs7_verify($msg_file, 0, $cert_file); $validity = true; if ($sig !== true) { // try without certificate verification $sig = openssl_pkcs7_verify($msg_file, PKCS7_NOVERIFY, $cert_file); $validity = enigma_error::E_UNVERIFIED; } if ($sig === true) { $sig = $this->parse_sig_cert($cert_file, $validity); } else { $errorstr = $this->get_openssl_error(); $sig = new enigma_error(enigma_error::E_INTERNAL, $errorstr); } // remove temp files @unlink($msg_file); @unlink($cert_file); return $sig; } public function import($content, $isfile=false) { } public function list_keys($pattern='') { } public function get_key($keyid) { } public function gen_key($data) { } public function del_key($keyid) { } public function del_privkey($keyid) { } public function del_pubkey($keyid) { } /** * 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; */ } private function get_openssl_error() { $tmp = array(); while ($errorstr = openssl_error_string()) { $tmp[] = $errorstr; } return join("\n", array_values($tmp)); } private function parse_sig_cert($file, $validity) { $cert = openssl_x509_parse(file_get_contents($file)); if (empty($cert) || empty($cert['subject'])) { $errorstr = $this->get_openssl_error(); return new enigma_error(enigm_error::E_INTERNAL, $errorstr); } $data = new enigma_signature(); $data->id = $cert['hash']; //? $data->valid = $validity; $data->fingerprint = $cert['serialNumber']; $data->created = $cert['validFrom_time_t']; $data->expires = $cert['validTo_time_t']; $data->name = $cert['subject']['CN']; // $data->comment = ''; $data->email = $cert['subject']['emailAddress']; return $data; } } plugins/enigma/lib/enigma_engine.php
@@ -92,9 +92,6 @@ 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(); @@ -255,11 +252,11 @@ */ private function parse_pgp_signed(&$p) { // Verify signature if ($this->rc->action == 'show' || $this->rc->action == 'preview') { $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]; @@ -294,7 +291,31 @@ */ private function parse_smime_signed(&$p) { // Verify signature if ($this->rc->action == 'show' || $this->rc->action == 'preview') { $this->load_smime_driver(); $struct = $p['structure']; $msg_part = $struct->parts[0]; // Verify $sig = $this->smime_driver->verify($struct, $p['object']); // 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]); } } /** @@ -359,7 +380,7 @@ */ private function parse_smime_encrypted(&$p) { $this->load_smime_driver(); // $this->load_smime_driver(); } /** plugins/enigma/lib/enigma_error.php
@@ -34,6 +34,8 @@ const E_KEYNOTFOUND = 3; const E_DELKEY = 4; const E_BADPASS = 5; const E_EXPIRED = 6; const E_UNVERIFIED = 7; function __construct($code = null, $message = '', $data = array()) { plugins/enigma/localization/en_US.inc
@@ -1,9 +1,9 @@ <?php $labels = array(); $labels['enigmasettings'] = 'Enigma: Settings'; $labels['enigmacerts'] = 'Enigma: Certificates (S/MIME)'; $labels['enigmakeys'] = 'Enigma: Keys (PGP)'; $labels['enigmasettings'] = 'Enigma Settings'; $labels['enigmacerts'] = 'S/MIME Certificates'; $labels['enigmakeys'] = 'PGP Keys'; $labels['keysfromto'] = 'Keys $from to $to of $count'; $labels['keyname'] = 'Name'; $labels['keyid'] = 'Key ID'; @@ -36,6 +36,7 @@ $messages = array(); $messages['sigvalid'] = 'Verified signature from $sender.'; $messages['siginvalid'] = 'Invalid signature from $sender.'; $messages['sigunverified'] = 'Unverified signature. Certificate not verified. Certificate ID: $keyid.'; $messages['signokey'] = 'Unverified signature. Public key not found. Key ID: $keyid.'; $messages['sigerror'] = 'Unverified signature. Internal error.'; $messages['decryptok'] = 'Message decrypted.'; program/lib/Crypt/GPG.php
@@ -47,13 +47,18 @@ * @package Crypt_GPG * @author Nathan Fredrickson <nathan@silverorange.com> * @author Michael Gauthier <mike@silverorange.com> * @copyright 2005-2010 silverorange * @copyright 2005-2013 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 $ * @version CVS: $Id$ * @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/ */ /** * Base class for GPG methods */ require_once 'Crypt/GPGAbstract.php'; /** * Signature handler class @@ -64,31 +69,6 @@ * 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 @@ -104,82 +84,13 @@ * @package Crypt_GPG * @author Nathan Fredrickson <nathan@silverorange.com> * @author Michael Gauthier <mike@silverorange.com> * @copyright 2005-2010 silverorange * @copyright 2005-2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @link http://pear.php.net/package/Crypt_GPG * @link http://www.gnupg.org/ */ class Crypt_GPG class Crypt_GPG extends Crypt_GPGAbstract { // {{{ 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 /** @@ -249,12 +160,27 @@ const FORMAT_X509 = 3; // }}} // {{{ other class constants // {{{ class constants for boolean options /** * URI at which package bugs may be reported. * Use to specify ASCII armored mode for returned data */ const BUG_URI = 'http://pear.php.net/bugs/report.php?package=Crypt_GPG'; const ARMOR_ASCII = true; /** * Use to specify binary mode for returned data */ const ARMOR_BINARY = false; /** * Use to specify that line breaks in signed text should be normalized */ const TEXT_NORMALIZED = true; /** * Use to specify that line breaks in signed text should not be normalized */ const TEXT_RAW = false; // }}} // {{{ protected class properties @@ -324,88 +250,6 @@ * @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() @@ -520,7 +364,9 @@ if ($fingerprint === null) { throw new Crypt_GPG_KeyNotFoundException( 'Public key not found: ' . $keyId, Crypt_GPG::ERROR_KEY_NOT_FOUND, $keyId); self::ERROR_KEY_NOT_FOUND, $keyId ); } $keyData = ''; @@ -534,11 +380,13 @@ $code = $this->engine->getErrorCode(); if ($code !== Crypt_GPG::ERROR_NONE) { if ($code !== self::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); 'file a bug report at ' . self::BUG_URI, $code ); } return $keyData; @@ -583,7 +431,9 @@ if ($fingerprint === null) { throw new Crypt_GPG_KeyNotFoundException( 'Public key not found: ' . $keyId, Crypt_GPG::ERROR_KEY_NOT_FOUND, $keyId); self::ERROR_KEY_NOT_FOUND, $keyId ); } $operation = '--delete-key ' . escapeshellarg($fingerprint); @@ -599,17 +449,22 @@ $code = $this->engine->getErrorCode(); switch ($code) { case Crypt_GPG::ERROR_NONE: case self::ERROR_NONE: break; case Crypt_GPG::ERROR_DELETE_PRIVATE_KEY: case self::ERROR_DELETE_PRIVATE_KEY: throw new Crypt_GPG_DeletePrivateKeyException( 'Private key must be deleted before public key can be ' . 'deleted.', $code, $keyId); '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); 'file a bug report at ' . self::BUG_URI, $code ); } } @@ -647,7 +502,9 @@ if ($fingerprint === null) { throw new Crypt_GPG_KeyNotFoundException( 'Private key not found: ' . $keyId, Crypt_GPG::ERROR_KEY_NOT_FOUND, $keyId); self::ERROR_KEY_NOT_FOUND, $keyId ); } $operation = '--delete-secret-key ' . escapeshellarg($fingerprint); @@ -663,17 +520,21 @@ $code = $this->engine->getErrorCode(); switch ($code) { case Crypt_GPG::ERROR_NONE: case self::ERROR_NONE: break; case Crypt_GPG::ERROR_KEY_NOT_FOUND: case self::ERROR_KEY_NOT_FOUND: throw new Crypt_GPG_KeyNotFoundException( 'Private key not found: ' . $keyId, $code, $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); 'file a bug report at ' . self::BUG_URI, $code ); } } @@ -705,161 +566,7 @@ */ 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; return parent::_getKeys($keyId); } // }}} @@ -895,7 +602,7 @@ * Use the <kbd>debug</kbd> option and file a bug report if these * exceptions occur. */ public function getFingerprint($keyId, $format = Crypt_GPG::FORMAT_NONE) public function getFingerprint($keyId, $format = self::FORMAT_NONE) { $output = ''; $operation = '--list-keys ' . escapeshellarg($keyId); @@ -912,15 +619,17 @@ $code = $this->engine->getErrorCode(); switch ($code) { case Crypt_GPG::ERROR_NONE: case Crypt_GPG::ERROR_KEY_NOT_FOUND: case self::ERROR_NONE: case self::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); 'file a bug report at ' . self::BUG_URI, $code ); } $fingerprint = null; @@ -932,13 +641,13 @@ $fingerprint = $lineExp[9]; switch ($format) { case Crypt_GPG::FORMAT_CANONICAL: case self::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: case self::FORMAT_X509: $fingerprintExp = str_split($fingerprint, 2); $fingerprint = implode(':', $fingerprintExp); break; @@ -976,7 +685,7 @@ * * @sensitive $data */ public function encrypt($data, $armor = true) public function encrypt($data, $armor = self::ARMOR_ASCII) { return $this->_encrypt($data, false, null, $armor); } @@ -1012,8 +721,11 @@ * Use the <kbd>debug</kbd> option and file a bug report if these * exceptions occur. */ public function encryptFile($filename, $encryptedFile = null, $armor = true) { public function encryptFile( $filename, $encryptedFile = null, $armor = self::ARMOR_ASCII ) { return $this->_encrypt($filename, true, $encryptedFile, $armor); } @@ -1052,7 +764,7 @@ * * @see Crypt_GPG::decryptAndVerify() */ public function encryptAndSign($data, $armor = true) public function encryptAndSign($data, $armor = self::ARMOR_ASCII) { return $this->_encryptAndSign($data, false, null, $armor); } @@ -1103,8 +815,10 @@ * * @see Crypt_GPG::decryptAndVerifyFile() */ public function encryptAndSignFile($filename, $signedFile = null, $armor = true public function encryptAndSignFile( $filename, $signedFile = null, $armor = self::ARMOR_ASCII ) { return $this->_encryptAndSign($filename, true, $signedFile, $armor); } @@ -1315,8 +1029,11 @@ * 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 public function sign( $data, $mode = self::SIGN_MODE_NORMAL, $armor = self::ARMOR_ASCII, $textmode = self::TEXT_RAW ) { return $this->_sign($data, false, null, $mode, $armor, $textmode); } @@ -1376,8 +1093,12 @@ * 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 public function signFile( $filename, $signedFile = null, $mode = self::SIGN_MODE_NORMAL, $armor = self::ARMOR_ASCII, $textmode = self::TEXT_RAW ) { return $this->_sign( $filename, @@ -1472,7 +1193,7 @@ * @param string $passphrase optional. The passphrase of the key required * for decryption. * * @return void * @return Crypt_GPG the current object, for fluent interface. * * @see Crypt_GPG::decrypt() * @see Crypt_GPG::decryptFile() @@ -1485,6 +1206,7 @@ public function addDecryptKey($key, $passphrase = null) { $this->_addKey($this->decryptKeys, true, false, $key, $passphrase); return $this; } // }}} @@ -1498,7 +1220,7 @@ * {@link Crypt_GPG_SubKey}. The key must be able to * encrypt. * * @return void * @return Crypt_GPG the current object, for fluent interface. * * @see Crypt_GPG::encrypt() * @see Crypt_GPG::encryptFile() @@ -1508,6 +1230,7 @@ public function addEncryptKey($key) { $this->_addKey($this->encryptKeys, true, false, $key); return $this; } // }}} @@ -1523,7 +1246,7 @@ * @param string $passphrase optional. The passphrase of the key required * for signing. * * @return void * @return Crypt_GPG the current object, for fluent interface. * * @see Crypt_GPG::sign() * @see Crypt_GPG::signFile() @@ -1536,6 +1259,7 @@ public function addSignKey($key, $passphrase = null) { $this->_addKey($this->signKeys, false, true, $key, $passphrase); return $this; } // }}} @@ -1544,7 +1268,7 @@ /** * Clears all decryption keys * * @return void * @return Crypt_GPG the current object, for fluent interface. * * @see Crypt_GPG::decrypt() * @see Crypt_GPG::addDecryptKey() @@ -1552,6 +1276,7 @@ public function clearDecryptKeys() { $this->decryptKeys = array(); return $this; } // }}} @@ -1560,7 +1285,7 @@ /** * Clears all encryption keys * * @return void * @return Crypt_GPG the current object, for fluent interface. * * @see Crypt_GPG::encrypt() * @see Crypt_GPG::addEncryptKey() @@ -1568,6 +1293,7 @@ public function clearEncryptKeys() { $this->encryptKeys = array(); return $this; } // }}} @@ -1576,7 +1302,7 @@ /** * Clears all signing keys * * @return void * @return Crypt_GPG the current object, for fluent interface. * * @see Crypt_GPG::sign() * @see Crypt_GPG::addSignKey() @@ -1584,6 +1310,7 @@ public function clearSignKeys() { $this->signKeys = array(); return $this; } // }}} @@ -1658,24 +1385,6 @@ } // }}} // {{{ 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() /** @@ -1698,7 +1407,7 @@ * * @sensitive $passphrase */ private function _addKey(array &$array, $encrypt, $sign, $key, protected function _addKey(array &$array, $encrypt, $sign, $key, $passphrase = null ) { $subKeys = array(); @@ -1707,7 +1416,10 @@ $keys = $this->getKeys($key); if (count($keys) == 0) { throw new Crypt_GPG_KeyNotFoundException( 'Key "' . $key . '" not found.', 0, $key); 'Key "' . $key . '" not found.', 0, $key ); } $key = $keys[0]; } @@ -1715,12 +1427,14 @@ if ($key instanceof Crypt_GPG_Key) { if ($encrypt && !$key->canEncrypt()) { throw new InvalidArgumentException( 'Key "' . $key . '" cannot encrypt.'); 'Key "' . $key . '" cannot encrypt.' ); } if ($sign && !$key->canSign()) { throw new InvalidArgumentException( 'Key "' . $key . '" cannot sign.'); 'Key "' . $key . '" cannot sign.' ); } foreach ($key->getSubKeys() as $subKey) { @@ -1741,18 +1455,21 @@ if (count($subKeys) === 0) { throw new InvalidArgumentException( 'Key "' . $key . '" is not in a recognized format.'); 'Key "' . $key . '" is not in a recognized format.' ); } foreach ($subKeys as $subKey) { if ($encrypt && !$subKey->canEncrypt()) { throw new InvalidArgumentException( 'Key "' . $key . '" cannot encrypt.'); 'Key "' . $key . '" cannot encrypt.' ); } if ($sign && !$subKey->canSign()) { throw new InvalidArgumentException( 'Key "' . $key . '" cannot sign.'); 'Key "' . $key . '" cannot sign.' ); } $array[$subKey->getId()] = array( @@ -1760,6 +1477,37 @@ 'passphrase' => $passphrase ); } } // }}} // {{{ _setPinEntryEnv() /** * Sets the PINENTRY_USER_DATA environment variable with the currently * added keys and passphrases * * Keys and pasphrases are stored as an indexed array of associative * arrays that is JSON encoded to a flat string. * * For GnuPG 2.x this is how passphrases are passed. For GnuPG 1.x the * environment variable is set but not used. * * @param array $keys the internal key array to use. * * @return void */ protected function _setPinEntryEnv(array $keys) { $envKeys = array(); foreach ($keys as $id => $key) { $envKeys[] = array( 'keyId' => $id, 'fingerprint' => $key['fingerprint'], 'passphrase' => $key['passphrase'] ); } $envKeys = json_encode($envKeys); $_ENV['PINENTRY_USER_DATA'] = $envKeys; } // }}} @@ -1792,21 +1540,26 @@ * Use the <kbd>debug</kbd> option and file a bug report if these * exceptions occur. */ private function _importKey($key, $isFile) protected 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); 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); 'No valid GPG key data found.', self::ERROR_NO_DATA ); } } @@ -1836,18 +1589,22 @@ $code = $this->engine->getErrorCode(); switch ($code) { case Crypt_GPG::ERROR_DUPLICATE_KEY: case Crypt_GPG::ERROR_NONE: case self::ERROR_DUPLICATE_KEY: case self::ERROR_NONE: // ignore duplicate key import errors break; case Crypt_GPG::ERROR_NO_DATA: case self::ERROR_NO_DATA: throw new Crypt_GPG_NoDataException( 'No valid GPG key data found.', $code); '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); 'report at ' . self::BUG_URI, $code ); } return $result; @@ -1880,18 +1637,23 @@ * Use the <kbd>debug</kbd> option and file a bug report if these * exceptions occur. */ private function _encrypt($data, $isFile, $outputFile, $armor) protected function _encrypt($data, $isFile, $outputFile, $armor) { if (count($this->encryptKeys) === 0) { throw new Crypt_GPG_KeyNotFoundException( 'No encryption keys specified.'); '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); throw new Crypt_GPG_FileException( 'Could not open input file "' . $data . '" for encryption.', 0, $data ); } } else { $input = strval($data); @@ -1905,9 +1667,12 @@ if ($isFile) { fclose($input); } throw new Crypt_GPG_FileException('Could not open output ' . 'file "' . $outputFile . '" for storing encrypted data.', 0, $outputFile); throw new Crypt_GPG_FileException( 'Could not open output file "' . $outputFile . '" for storing encrypted data.', 0, $outputFile ); } } @@ -1932,11 +1697,13 @@ $code = $this->engine->getErrorCode(); if ($code !== Crypt_GPG::ERROR_NONE) { if ($code !== self::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); 'report at ' . self::BUG_URI, $code ); } if ($outputFile === null) { @@ -1976,20 +1743,26 @@ * Use the <kbd>debug</kbd> option and file a bug report if these * exceptions occur. */ private function _decrypt($data, $isFile, $outputFile) protected 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); 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); 'the provided data.', self::ERROR_NO_DATA ); } } @@ -2001,14 +1774,22 @@ if ($isFile) { fclose($input); } throw new Crypt_GPG_FileException('Could not open output ' . 'file "' . $outputFile . '" for storing decrypted data.', 0, $outputFile); 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); $handler = new Crypt_GPG_DecryptStatusHandler( $this->engine, $this->decryptKeys ); // If using gpg-agent, set the decrypt pins used by the pinentry $this->_setPinEntryEnv($this->decryptKeys); $this->engine->reset(); $this->engine->addStatusHandler(array($handler, 'handle')); @@ -2080,19 +1861,23 @@ * Use the <kbd>debug</kbd> option and file a bug report if these * exceptions occur. */ private function _sign($data, $isFile, $outputFile, $mode, $armor, protected function _sign($data, $isFile, $outputFile, $mode, $armor, $textmode ) { if (count($this->signKeys) === 0) { throw new Crypt_GPG_KeyNotFoundException( 'No signing keys specified.'); '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); throw new Crypt_GPG_FileException( 'Could not open input file "' . $data . '" for signing.', 0, $data ); } } else { $input = strval($data); @@ -2106,20 +1891,23 @@ if ($isFile) { fclose($input); } throw new Crypt_GPG_FileException('Could not open output ' . 'file "' . $outputFile . '" for storing signed ' . 'data.', 0, $outputFile); 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: case self::SIGN_MODE_DETACHED: $operation = '--detach-sign'; break; case Crypt_GPG::SIGN_MODE_CLEAR: case self::SIGN_MODE_CLEAR: $operation = '--clearsign'; break; case Crypt_GPG::SIGN_MODE_NORMAL: case self::SIGN_MODE_NORMAL: default: $operation = '--sign'; break; @@ -2139,6 +1927,9 @@ escapeshellarg($key['fingerprint']); } // If using gpg-agent, set the sign pins used by the pinentry $this->_setPinEntryEnv($this->signKeys); $this->engine->reset(); $this->engine->addStatusHandler(array($this, 'handleSignStatus')); $this->engine->setInput($input); @@ -2157,24 +1948,32 @@ $code = $this->engine->getErrorCode(); switch ($code) { case Crypt_GPG::ERROR_NONE: case self::ERROR_NONE: break; case Crypt_GPG::ERROR_KEY_NOT_FOUND: case self::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: 'private key before trying to sign data.', $code, $this->engine->getErrorKeyId() ); case self::ERROR_BAD_PASSPHRASE: throw new Crypt_GPG_BadPassphraseException( 'Cannot sign data. Incorrect passphrase provided.', $code); case Crypt_GPG::ERROR_MISSING_PASSPHRASE: 'Cannot sign data. Incorrect passphrase provided.', $code ); case self::ERROR_MISSING_PASSPHRASE: throw new Crypt_GPG_BadPassphraseException( 'Cannot sign data. No passphrase provided.', $code); '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); 'at ' . self::BUG_URI, $code ); } if ($outputFile === null) { @@ -2216,25 +2015,30 @@ * Use the <kbd>debug</kbd> option and file a bug report if these * exceptions occur. */ private function _encryptAndSign($data, $isFile, $outputFile, $armor) protected function _encryptAndSign($data, $isFile, $outputFile, $armor) { if (count($this->signKeys) === 0) { throw new Crypt_GPG_KeyNotFoundException( 'No signing keys specified.'); 'No signing keys specified.' ); } if (count($this->encryptKeys) === 0) { throw new Crypt_GPG_KeyNotFoundException( 'No encryption keys specified.'); '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); throw new Crypt_GPG_FileException( 'Could not open input file "' . $data . '" for encrypting and signing.', 0, $data ); } } else { $input = strval($data); @@ -2248,9 +2052,12 @@ if ($isFile) { fclose($input); } throw new Crypt_GPG_FileException('Could not open output ' . 'file "' . $outputFile . '" for storing encrypted, ' . 'signed data.', 0, $outputFile); throw new Crypt_GPG_FileException( 'Could not open output file "' . $outputFile . '" for storing encrypted, signed data.', 0, $outputFile ); } } @@ -2260,6 +2067,9 @@ $arguments[] = '--local-user ' . escapeshellarg($key['fingerprint']); } // If using gpg-agent, set the sign pins used by the pinentry $this->_setPinEntryEnv($this->signKeys); foreach ($this->encryptKeys as $key) { $arguments[] = '--recipient ' . escapeshellarg($key['fingerprint']); @@ -2283,25 +2093,32 @@ $code = $this->engine->getErrorCode(); switch ($code) { case Crypt_GPG::ERROR_NONE: case self::ERROR_NONE: break; case Crypt_GPG::ERROR_KEY_NOT_FOUND: case self::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: $code, $this->engine->getErrorKeyId() ); case self::ERROR_BAD_PASSPHRASE: throw new Crypt_GPG_BadPassphraseException( 'Cannot sign encrypted data. Incorrect passphrase provided.', $code); case Crypt_GPG::ERROR_MISSING_PASSPHRASE: $code ); case self::ERROR_MISSING_PASSPHRASE: throw new Crypt_GPG_BadPassphraseException( 'Cannot sign encrypted data. No passphrase provided.', $code); '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); 'file a bug report at ' . self::BUG_URI, $code ); } if ($outputFile === null) { @@ -2335,7 +2152,7 @@ * * @see Crypt_GPG_Signature */ private function _verify($data, $isFile, $signature) protected function _verify($data, $isFile, $signature) { if ($signature == '') { $operation = '--verify'; @@ -2352,14 +2169,19 @@ if ($isFile) { $input = @fopen($data, 'rb'); if ($input === false) { throw new Crypt_GPG_FileException('Could not open input ' . 'file "' . $data . '" for verifying.', 0, $data); 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); 'No valid signature data found.', self::ERROR_NO_DATA ); } } @@ -2385,21 +2207,27 @@ $code = $this->engine->getErrorCode(); switch ($code) { case Crypt_GPG::ERROR_NONE: case Crypt_GPG::ERROR_BAD_SIGNATURE: case self::ERROR_NONE: case self::ERROR_BAD_SIGNATURE: break; case Crypt_GPG::ERROR_NO_DATA: case self::ERROR_NO_DATA: throw new Crypt_GPG_NoDataException( 'No valid signature data found.', $code); case Crypt_GPG::ERROR_KEY_NOT_FOUND: 'No valid signature data found.', $code ); case self::ERROR_KEY_NOT_FOUND: throw new Crypt_GPG_KeyNotFoundException( 'Public key required for data verification not in keyring.', $code, $this->engine->getErrorKeyId()); $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); 'file a bug report at ' . self::BUG_URI, $code ); } return $handler->getSignatures(); @@ -2445,21 +2273,25 @@ * * @see Crypt_GPG_Signature */ private function _decryptAndVerify($data, $isFile, $outputFile) protected 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); 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); self::ERROR_NO_DATA ); } } @@ -2471,16 +2303,24 @@ if ($isFile) { fclose($input); } throw new Crypt_GPG_FileException('Could not open output ' . 'file "' . $outputFile . '" for storing decrypted data.', 0, $outputFile); 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); $decryptHandler = new Crypt_GPG_DecryptStatusHandler( $this->engine, $this->decryptKeys ); // If using gpg-agent, set the decrypt pins used by the pinentry $this->_setPinEntryEnv($this->decryptKeys); $this->engine->reset(); $this->engine->addStatusHandler(array($verifyHandler, 'handle')); @@ -2515,13 +2355,17 @@ '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()); 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); 'was found in the provided data.', self::ERROR_NO_DATA ); } throw $e; program/lib/Crypt/GPG/ByteUtils.php
New file @@ -0,0 +1,105 @@ <?php /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ /** * A class for performing byte-wise string operations * * GPG I/O streams are managed using bytes rather than characters. * * 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 2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @version CVS: $Id$ * @link http://pear.php.net/package/Crypt_GPG */ // {{{ class Crypt_GPG_ByteUtils /** * A class for performing byte-wise string operations * * GPG I/O streams are managed using bytes rather than characters. This class * requires the mbstring extension to be available. * * @category Encryption * @package Crypt_GPG * @author Michael Gauthier <mike@silverorange.com> * @copyright 2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @link http://pear.php.net/package/Crypt_GPG * @link http://php.net/mbstring */ class Crypt_GPG_ByteUtils { // {{{ strlen() /** * Gets the length of a string in bytes * * 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. */ public static function strlen($string) { return mb_strlen($string, '8bit'); } // }}} // {{{ substr() /** * Gets the substring of a string in bytes * * 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. */ public static function substr($string, $start, $length = null) { if ($length === null) { return mb_substr( $string, $start, self::strlen($string) - $start, '8bit' ); } return mb_substr($string, $start, $length, '8bit'); } // }}} } // }}} ?> program/lib/Crypt/GPG/DecryptStatusHandler.php
@@ -3,9 +3,9 @@ /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ /** * Crypt_GPG is a package to use GPG from PHP * Crypt_GPG is a package to use GnuPG from PHP * * This file contains an object that handles GPG's status output for the * This file contains an object that handles GnuPG's status output for the * decrypt operation. * * PHP version 5 @@ -29,9 +29,9 @@ * @category Encryption * @package Crypt_GPG * @author Michael Gauthier <mike@silverorange.com> * @copyright 2008-2009 silverorange * @copyright 2008-2013 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 $ * @version CVS: $Id$ * @link http://pear.php.net/package/Crypt_GPG * @link http://www.gnupg.org/ */ @@ -42,7 +42,7 @@ require_once 'Crypt/GPG.php'; /** * GPG exception classes * Crypt_GPG exception classes */ require_once 'Crypt/GPG/Exceptions.php'; @@ -55,8 +55,8 @@ * * 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. * {@link http://www.gnupg.org/download/ GnuPG distribution} for detailed * information on GnuPG's status output for the decrypt operation. * * This class is also responsible for parsing error status and throwing a * meaningful exception in the event that decryption fails. @@ -64,7 +64,7 @@ * @category Encryption * @package Crypt_GPG * @author Michael Gauthier <mike@silverorange.com> * @copyright 2008 silverorange * @copyright 2008-2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @link http://pear.php.net/package/Crypt_GPG * @link http://www.gnupg.org/ @@ -293,8 +293,10 @@ 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); 'decrypt this data.', $code, $keyId ); case Crypt_GPG::ERROR_BAD_PASSPHRASE: $badPassphrases = array_diff_key( $this->badPassphrases, @@ -316,17 +318,23 @@ implode('", "', $badPassphrases) . '".'; } throw new Crypt_GPG_BadPassphraseException($message, $code, $badPassphrases, $missingPassphrases); 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); 'the provided data.', $code ); default: throw new Crypt_GPG_Exception( 'Unknown error decrypting data.', $code); 'Unknown error decrypting data.', $code ); } } program/lib/Crypt/GPG/Engine.php
@@ -30,9 +30,9 @@ * @package Crypt_GPG * @author Nathan Fredrickson <nathan@silverorange.com> * @author Michael Gauthier <mike@silverorange.com> * @copyright 2005-2010 silverorange * @copyright 2005-2013 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 $ * @version CVS: $Id$ * @link http://pear.php.net/package/Crypt_GPG * @link http://www.gnupg.org/ */ @@ -46,6 +46,16 @@ * GPG exception classes. */ require_once 'Crypt/GPG/Exceptions.php'; /** * Byte string operations. */ require_once 'Crypt/GPG/ByteUtils.php'; /** * Process control methods. */ require_once 'Crypt/GPG/ProcessControl.php'; /** * Standard PEAR exception is used if GPG binary is not found. @@ -70,7 +80,7 @@ * @package Crypt_GPG * @author Nathan Fredrickson <nathan@silverorange.com> * @author Michael Gauthier <mike@silverorange.com> * @copyright 2005-2010 silverorange * @copyright 2005-2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @link http://pear.php.net/package/Crypt_GPG * @link http://www.gnupg.org/ @@ -163,6 +173,17 @@ private $_binary = ''; /** * Location of GnuPG agent binary * * Only used for GnuPG 2.x * * @var string * @see Crypt_GPG_Engine::__construct() * @see Crypt_GPG_Engine::_getAgent() */ private $_agent = ''; /** * Directory containing the GPG key files * * This property only contains the path when the <i>homedir</i> option @@ -228,6 +249,15 @@ private $_pipes = array(); /** * Array of pipes used for communication with the gpg-agent binary * * This is an array of file descriptor resources. * * @var array */ private $_agentPipes = array(); /** * Array of currently opened pipes * * This array is used to keep track of remaining opened pipes so they can @@ -246,6 +276,20 @@ * @var resource */ private $_process = null; /** * A handle for the gpg-agent process * * @var resource */ private $_agentProcess = null; /** * GPG agent daemon socket and PID for running gpg-agent * * @var string */ private $_agentInfo = null; /** * Whether or not the operating system is Darwin (OS X) @@ -367,18 +411,6 @@ */ 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() @@ -432,6 +464,14 @@ * operating system. The option * <kbd>gpgBinary</kbd> is a * deprecated alias for this option. * - <kbd>string agent</kbd> - the location of the GnuPG agent * binary. The gpg-agent is only * used for GnuPG 2.x. If not * specified, the engine attempts * to auto-detect the gpg-agent * binary location using a list of * know default locations for the * current operating system. * - <kbd>boolean debug</kbd> - whether or not to use debug mode. * When debug mode is on, all * communication to and from the GPG @@ -457,24 +497,38 @@ * @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. * * @throws PEAR_Exception if the provided <kbd>agent</kbd> is invalid, or * if no <kbd>agent</kbd> is provided and no suitable gpg-agent * cound 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 { if (extension_loaded('posix')) { // note: this requires the package OS dep exclude 'windows' $info = posix_getpwuid(posix_getuid()); $this->_homedir = $info['dir'].'/.gnupg'; } else { if (isset($_SERVER['HOME'])) { $this->_homedir = $_SERVER['HOME']; } else { $this->_homedir = getenv('HOME'); } } if ($this->_homedir === false) { throw new Crypt_GPG_FileException( 'Could not locate homedir. Please specify the homedir ' . 'to use with the \'homedir\' option when instantiating ' . 'the Crypt_GPG object.' ); } } // attempt to create homedir if it does not exist @@ -484,14 +538,38 @@ // 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); 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 ); } } // check homedir permissions (See Bug #19833) if (!is_executable($this->_homedir)) { throw new Crypt_GPG_FileException( 'The \'homedir\' "' . $this->_homedir . '" is not enterable ' . 'by the current user. Please check the permissions on your ' . 'homedir and make sure the current user can both enter and ' . 'write to the directory.', 0, $this->_homedir ); } if (!is_writeable($this->_homedir)) { throw new Crypt_GPG_FileException( 'The \'homedir\' "' . $this->_homedir . '" is not writable ' . 'by the current user. Please check the permissions on your ' . 'homedir and make sure the current user can both enter and ' . 'write to the directory.', 0, $this->_homedir ); } // get binary @@ -505,9 +583,26 @@ } 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.'); 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.' ); } // get agent if (array_key_exists('agent', $options)) { $this->_agent = (string)$options['agent']; } else { $this->_agent = $this->_getAgent(); } if ($this->_agent == '' || !is_executable($this->_agent)) { throw new PEAR_Exception( 'gpg-agent binary not found. If you are sure the gpg-agent ' . 'is installed, please specify the location of the gpg-agent ' . 'binary using the \'agent\' driver option.' ); } /* @@ -891,7 +986,7 @@ } $matches = array(); $expression = '/gpg \(GnuPG\) (\S+)/'; $expression = '#gpg \(GnuPG[A-Za-z0-9/]*?\) (\S+)#'; if (preg_match($expression, $info, $matches) === 1) { $this->_version = $matches[1]; @@ -1114,6 +1209,9 @@ $fdCommand = $this->_pipes[self::FD_COMMAND]; $fdMessage = $this->_pipes[self::FD_MESSAGE]; // select loop delay in milliseconds $delay = 0; while (true) { $inputStreams = array(); @@ -1166,15 +1264,15 @@ $outputStreams[] = $this->_output; } if ($this->_commandBuffer != '') { if ($this->_commandBuffer != '' && is_resource($fdCommand)) { $outputStreams[] = $fdCommand; } if ($messageBuffer != '') { if ($messageBuffer != '' && is_resource($fdMessage)) { $outputStreams[] = $fdMessage; } if ($inputBuffer != '') { if ($inputBuffer != '' && is_resource($fdInput)) { $outputStreams[] = $fdInput; } @@ -1209,33 +1307,41 @@ } // write input (to GPG) if (in_array($fdInput, $outputStreams)) { if (in_array($fdInput, $outputStreams, true)) { $this->_debug('GPG is ready for input'); $chunk = self::_byteSubstring( $chunk = Crypt_GPG_ByteUtils::substr( $inputBuffer, 0, self::CHUNK_SIZE ); $length = self::_byteLength($chunk); $length = Crypt_GPG_ByteUtils::strlen($chunk); $this->_debug( '=> about to write ' . $length . ' bytes to GPG input' ); $length = fwrite($fdInput, $chunk, $length); if ($length === 0) { // If we wrote 0 bytes it was either EAGAIN or EPIPE. Since // the pipe was seleted for writing, we assume it was EPIPE. // There's no way to get the actual erorr code in PHP. See // PHP Bug #39598. https://bugs.php.net/bug.php?id=39598 $this->_debug('=> broken pipe on GPG input'); $this->_debug('=> closing pipe GPG input'); $this->_closePipe(self::FD_INPUT); } else { $this->_debug('=> wrote ' . $length . ' bytes'); $inputBuffer = self::_byteSubstring( $inputBuffer = Crypt_GPG_ByteUtils::substr( $inputBuffer, $length ); } } // read input (from PHP stream) if (in_array($this->_input, $inputStreams)) { if (in_array($this->_input, $inputStreams, true)) { $this->_debug('input stream is ready for reading'); $this->_debug( '=> about to read ' . self::CHUNK_SIZE . @@ -1243,36 +1349,48 @@ ); $chunk = fread($this->_input, self::CHUNK_SIZE); $length = self::_byteLength($chunk); $length = Crypt_GPG_ByteUtils::strlen($chunk); $inputBuffer .= $chunk; $this->_debug('=> read ' . $length . ' bytes'); } // write message (to GPG) if (in_array($fdMessage, $outputStreams)) { if (in_array($fdMessage, $outputStreams, true)) { $this->_debug('GPG is ready for message data'); $chunk = self::_byteSubstring( $chunk = Crypt_GPG_ByteUtils::substr( $messageBuffer, 0, self::CHUNK_SIZE ); $length = self::_byteLength($chunk); $length = Crypt_GPG_ByteUtils::strlen($chunk); $this->_debug( '=> about to write ' . $length . ' bytes to GPG message' ); $length = fwrite($fdMessage, $chunk, $length); if ($length === 0) { // If we wrote 0 bytes it was either EAGAIN or EPIPE. Since // the pipe was seleted for writing, we assume it was EPIPE. // There's no way to get the actual erorr code in PHP. See // PHP Bug #39598. https://bugs.php.net/bug.php?id=39598 $this->_debug('=> broken pipe on GPG message'); $this->_debug('=> closing pipe GPG message'); $this->_closePipe(self::FD_MESSAGE); } else { $this->_debug('=> wrote ' . $length . ' bytes'); $messageBuffer = self::_byteSubstring($messageBuffer, $length); $messageBuffer = Crypt_GPG_ByteUtils::substr( $messageBuffer, $length ); } } // read message (from PHP stream) if (in_array($this->_message, $inputStreams)) { if (in_array($this->_message, $inputStreams, true)) { $this->_debug('message stream is ready for reading'); $this->_debug( '=> about to read ' . self::CHUNK_SIZE . @@ -1280,14 +1398,14 @@ ); $chunk = fread($this->_message, self::CHUNK_SIZE); $length = self::_byteLength($chunk); $length = Crypt_GPG_ByteUtils::strlen($chunk); $messageBuffer .= $chunk; $this->_debug('=> read ' . $length . ' bytes'); } // read output (from GPG) if (in_array($fdOutput, $inputStreams)) { if (in_array($fdOutput, $inputStreams, true)) { $this->_debug('GPG output stream ready for reading'); $this->_debug( '=> about to read ' . self::CHUNK_SIZE . @@ -1295,23 +1413,23 @@ ); $chunk = fread($fdOutput, self::CHUNK_SIZE); $length = self::_byteLength($chunk); $length = Crypt_GPG_ByteUtils::strlen($chunk); $outputBuffer .= $chunk; $this->_debug('=> read ' . $length . ' bytes'); } // write output (to PHP stream) if (in_array($this->_output, $outputStreams)) { if (in_array($this->_output, $outputStreams, true)) { $this->_debug('output stream is ready for data'); $chunk = self::_byteSubstring( $chunk = Crypt_GPG_ByteUtils::substr( $outputBuffer, 0, self::CHUNK_SIZE ); $length = self::_byteLength($chunk); $length = Crypt_GPG_ByteUtils::strlen($chunk); $this->_debug( '=> about to write ' . $length . ' bytes to output stream' @@ -1321,11 +1439,14 @@ $this->_debug('=> wrote ' . $length . ' bytes'); $outputBuffer = self::_byteSubstring($outputBuffer, $length); $outputBuffer = Crypt_GPG_ByteUtils::substr( $outputBuffer, $length ); } // read error (from GPG) if (in_array($fdError, $inputStreams)) { if (in_array($fdError, $inputStreams, true)) { $this->_debug('GPG error stream ready for reading'); $this->_debug( '=> about to read ' . self::CHUNK_SIZE . @@ -1333,14 +1454,14 @@ ); $chunk = fread($fdError, self::CHUNK_SIZE); $length = self::_byteLength($chunk); $length = Crypt_GPG_ByteUtils::strlen($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); $line = Crypt_GPG_ByteUtils::substr($errorBuffer, 0, $pos); foreach ($this->_errorHandlers as $handler) { array_unshift($handler['args'], $line); call_user_func_array( @@ -1350,15 +1471,15 @@ array_shift($handler['args']); } $errorBuffer = self::_byteSubString( $errorBuffer = Crypt_GPG_ByteUtils::substr( $errorBuffer, $pos + self::_byteLength(PHP_EOL) $pos + Crypt_GPG_ByteUtils::strlen(PHP_EOL) ); } } // read status (from GPG) if (in_array($fdStatus, $inputStreams)) { if (in_array($fdStatus, $inputStreams, true)) { $this->_debug('GPG status stream ready for reading'); $this->_debug( '=> about to read ' . self::CHUNK_SIZE . @@ -1366,17 +1487,17 @@ ); $chunk = fread($fdStatus, self::CHUNK_SIZE); $length = self::_byteLength($chunk); $length = Crypt_GPG_ByteUtils::strlen($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); $line = Crypt_GPG_ByteUtils::substr($statusBuffer, 0, $pos); // only pass lines beginning with magic prefix if (self::_byteSubstring($line, 0, 9) == '[GNUPG:] ') { $line = self::_byteSubstring($line, 9); if (Crypt_GPG_ByteUtils::substr($line, 0, 9) == '[GNUPG:] ') { $line = Crypt_GPG_ByteUtils::substr($line, 9); foreach ($this->_statusHandlers as $handler) { array_unshift($handler['args'], $line); call_user_func_array( @@ -1387,38 +1508,60 @@ array_shift($handler['args']); } } $statusBuffer = self::_byteSubString( $statusBuffer = Crypt_GPG_ByteUtils::substr( $statusBuffer, $pos + self::_byteLength(PHP_EOL) $pos + Crypt_GPG_ByteUtils::strlen(PHP_EOL) ); } } // write command (to GPG) if (in_array($fdCommand, $outputStreams)) { if (in_array($fdCommand, $outputStreams, true)) { $this->_debug('GPG is ready for command data'); // send commands $chunk = self::_byteSubstring( $chunk = Crypt_GPG_ByteUtils::substr( $this->_commandBuffer, 0, self::CHUNK_SIZE ); $length = self::_byteLength($chunk); $length = Crypt_GPG_ByteUtils::strlen($chunk); $this->_debug( '=> about to write ' . $length . ' bytes to GPG command' ); $length = fwrite($fdCommand, $chunk, $length); if ($length === 0) { // If we wrote 0 bytes it was either EAGAIN or EPIPE. Since // the pipe was seleted for writing, we assume it was EPIPE. // There's no way to get the actual erorr code in PHP. See // PHP Bug #39598. https://bugs.php.net/bug.php?id=39598 $this->_debug('=> broken pipe on GPG command'); $this->_debug('=> closing pipe GPG command'); $this->_closePipe(self::FD_COMMAND); } else { $this->_debug('=> wrote ' . $length); $this->_commandBuffer = self::_byteSubstring( $this->_commandBuffer = Crypt_GPG_ByteUtils::substr( $this->_commandBuffer, $length ); } } if (count($outputStreams) === 0 || count($inputStreams) === 0) { // we have an I/O imbalance, increase the select loop delay // to smooth things out $delay += 10; } else { // things are running smoothly, decrease the delay $delay -= 8; $delay = max(0, $delay); } if ($delay > 0) { usleep($delay); } } // end loop while streams are open @@ -1449,11 +1592,82 @@ { $version = $this->getVersion(); // 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'; $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'; // If using GnuPG 2.x start the gpg-agent if (version_compare($version, '2.0.0', 'ge')) { $agentCommandLine = $this->_agent; $agentArguments = array( '--options /dev/null', // ignore any saved options '--csh', // output is easier to parse '--keep-display', // prevent passing --display to pinentry '--no-grab', '--ignore-cache-for-signing', '--pinentry-touch-file /dev/null', '--disable-scdaemon', '--no-use-standard-socket', '--pinentry-program ' . escapeshellarg($this->_getPinEntry()) ); if ($this->_homedir) { $agentArguments[] = '--homedir ' . escapeshellarg($this->_homedir); } $agentCommandLine .= ' ' . implode(' ', $agentArguments) . ' --daemon'; $agentDescriptorSpec = array( self::FD_INPUT => array('pipe', $rb), // stdin self::FD_OUTPUT => array('pipe', $wb), // stdout self::FD_ERROR => array('pipe', $wb) // stderr ); $this->_debug('OPENING GPG-AGENT SUBPROCESS WITH THE FOLLOWING COMMAND:'); $this->_debug($agentCommandLine); $this->_agentProcess = proc_open( $agentCommandLine, $agentDescriptorSpec, $this->_agentPipes, null, $env, array('binary_pipes' => true) ); if (!is_resource($this->_agentProcess)) { throw new Crypt_GPG_OpenSubprocessException( 'Unable to open gpg-agent subprocess.', 0, $agentCommandLine ); } // Get GPG_AGENT_INFO and set environment variable for gpg process. // This is a blocking read, but is only 1 line. $agentInfo = fread( $this->_agentPipes[self::FD_OUTPUT], self::CHUNK_SIZE ); $agentInfo = explode(' ', $agentInfo, 3); $this->_agentInfo = $agentInfo[2]; $env['GPG_AGENT_INFO'] = $this->_agentInfo; // gpg-agent daemon is started, we can close the launching process $this->_closeAgentLaunchProcess(); } $commandLine = $this->_binary; @@ -1511,11 +1725,6 @@ $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 @@ -1525,7 +1734,7 @@ self::FD_MESSAGE => array('pipe', $rb) // message ); $this->_debug('OPENING SUBPROCESS WITH THE FOLLOWING COMMAND:'); $this->_debug('OPENING GPG SUBPROCESS WITH THE FOLLOWING COMMAND:'); $this->_debug($commandLine); $this->_process = proc_open( @@ -1540,6 +1749,11 @@ if (!is_resource($this->_process)) { throw new Crypt_GPG_OpenSubprocessException( 'Unable to open GPG subprocess.', 0, $commandLine); } // Set streams as non-blocking. See Bug #18618. foreach ($this->_pipes as $pipe) { stream_set_blocking($pipe, 0); } $this->_openPipes = $this->_pipes; @@ -1562,8 +1776,11 @@ */ private function _closeSubprocess() { // clear PINs from environment if they were set $_ENV['PINENTRY_USER_DATA'] = null; if (is_resource($this->_process)) { $this->_debug('CLOSING SUBPROCESS'); $this->_debug('CLOSING GPG SUBPROCESS'); // close remaining open pipes foreach (array_keys($this->_openPipes) as $pipeNumber) { @@ -1590,9 +1807,55 @@ $this->_process = null; $this->_pipes = array(); } $this->_closeAgentLaunchProcess(); if ($this->_agentInfo !== null) { $this->_debug('STOPPING GPG-AGENT DAEMON'); $parts = explode(':', $this->_agentInfo, 3); $pid = $parts[1]; $process = new Crypt_GPG_ProcessControl($pid); // terminate agent daemon $process->terminate(); while ($process->isRunning()) { usleep(10000); // 10 ms $process->terminate(); } $this->_agentInfo = null; $this->_debug('GPG-AGENT DAEMON STOPPED'); } } // }}} // {{ _closeAgentLaunchProcess() private function _closeAgentLaunchProcess() { if (is_resource($this->_agentProcess)) { $this->_debug('CLOSING GPG-AGENT LAUNCH PROCESS'); // close agent pipes foreach ($this->_agentPipes as $pipe) { fflush($pipe); fclose($pipe); } // close agent launching process proc_close($this->_agentProcess); $this->_agentProcess = null; $this->_agentPipes = array(); $this->_debug('GPG-AGENT LAUNCH PROCESS CLOSED'); } } // }} // {{{ _closePipe() /** @@ -1658,6 +1921,55 @@ } // }}} // {{ _getAgent() private function _getAgent() { $agent = ''; if ($this->_isDarwin) { $agentFiles = array( '/opt/local/bin/gpg-agent', // MacPorts '/usr/local/bin/gpg-agent', // Mac GPG '/sw/bin/gpg-agent', // Fink '/usr/bin/gpg-agent' ); } else { $agentFiles = array( '/usr/bin/gpg-agent', '/usr/local/bin/gpg-agent' ); } foreach ($agentFiles as $agentFile) { if (is_executable($agentFile)) { $agent = $agentFile; break; } } return $agent; } // }} // {{ _getPinEntry() private function _getPinEntry() { // Check if we're running directly from git or if we're using a // PEAR-packaged version $pinEntry = '@bin-dir@' . DIRECTORY_SEPARATOR . 'crypt-gpg-pinentry'; if ($pinEntry[0] === '@') { $pinEntry = dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'crypt-gpg-pinentry'; } return $pinEntry; } // }} // {{{ _debug() /** @@ -1672,7 +1984,7 @@ private function _debug($text) { if ($this->_debug) { if (array_key_exists('SHELL', $_ENV)) { if (php_sapi_name() === 'cli') { foreach (explode(PHP_EOL, $text) as $line) { echo "Crypt_GPG DEBUG: ", $line, PHP_EOL; } @@ -1684,70 +1996,6 @@ } } } } // }}} // {{{ _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); } // }}} program/lib/Crypt/GPG/Exceptions.php
@@ -32,9 +32,9 @@ * @package Crypt_GPG * @author Nathan Fredrickson <nathan@silverorange.com> * @author Michael Gauthier <mike@silverorange.com> * @copyright 2005 silverorange * @copyright 2005-2011 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 $ * @version CVS: $Id$ * @link http://pear.php.net/package/Crypt_GPG */ @@ -469,5 +469,130 @@ } // }}} // {{{ class Crypt_GPG_KeyNotCreatedException /** * An exception thrown when an attempt is made to generate a key and the * attempt fails * * @category Encryption * @package Crypt_GPG * @author Michael Gauthier <mike@silverorange.com> * @copyright 2011 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @link http://pear.php.net/package/Crypt_GPG */ class Crypt_GPG_KeyNotCreatedException extends Crypt_GPG_Exception { } // }}} // {{{ class Crypt_GPG_InvalidKeyParamsException /** * An exception thrown when an attempt is made to generate a key and the * key parameters set on the key generator are invalid * * @category Encryption * @package Crypt_GPG * @author Michael Gauthier <mike@silverorange.com> * @copyright 2011 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @link http://pear.php.net/package/Crypt_GPG */ class Crypt_GPG_InvalidKeyParamsException extends Crypt_GPG_Exception { // {{{ private class properties /** * The key algorithm * * @var integer */ private $_algorithm = 0; /** * The key size * * @var integer */ private $_size = 0; /** * The key usage * * @var integer */ private $_usage = 0; // }}} // {{{ __construct() /** * Creates a new Crypt_GPG_InvalidKeyParamsException * * @param string $message an error message. * @param integer $code a user defined error code. * @param string $algorithm the key algorithm. * @param string $size the key size. * @param string $usage the key usage. */ public function __construct( $message, $code = 0, $algorithm = 0, $size = 0, $usage = 0 ) { parent::__construct($message, $code); $this->_algorithm = $algorithm; $this->_size = $size; $this->_usage = $usage; } // }}} // {{{ getAlgorithm() /** * Gets the key algorithm * * @return integer the key algorithm. */ public function getAlgorithm() { return $this->_algorithm; } // }}} // {{{ getSize() /** * Gets the key size * * @return integer the key size. */ public function getSize() { return $this->_size; } // }}} // {{{ getUsage() /** * Gets the key usage * * @return integer the key usage. */ public function getUsage() { return $this->_usage; } // }}} } // }}} ?> program/lib/Crypt/GPG/Key.php
@@ -28,7 +28,7 @@ * @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 $ * @version CVS: $Id$ * @link http://pear.php.net/package/Crypt_GPG */ program/lib/Crypt/GPG/KeyGenerator.php
New file @@ -0,0 +1,790 @@ <?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 GnuPG key generation. * * 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 2011-2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @version CVS: $Id:$ * @link http://pear.php.net/package/Crypt_GPG * @link http://www.gnupg.org/ */ /** * Base class for GPG methods */ require_once 'Crypt/GPGAbstract.php'; /** * Status output handler for key generation */ require_once 'Crypt/GPG/KeyGeneratorStatusHandler.php'; /** * Error output handler for key generation */ require_once 'Crypt/GPG/KeyGeneratorErrorHandler.php'; // {{{ class Crypt_GPG_KeyGenerator /** * GnuPG key generator * * This class provides an object oriented interface for generating keys with * the GNU Privacy Guard (GPG). * * Secure key generation requires true random numbers, and as such can be slow. * If the operating system runs out of entropy, key generation will block until * more entropy is available. * * If quick key generation is important, a hardware entropy generator, or an * entropy gathering daemon may be installed. For example, administrators of * Debian systems may want to install the 'randomsound' package. * * This class uses the experimental automated key generation support available * in GnuPG. See <b>doc/DETAILS</b> in the * {@link http://www.gnupg.org/download/ GPG distribution} for detailed * information on the key generation format. * * @category Encryption * @package Crypt_GPG * @author Nathan Fredrickson <nathan@silverorange.com> * @author Michael Gauthier <mike@silverorange.com> * @copyright 2005-2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @link http://pear.php.net/package/Crypt_GPG * @link http://www.gnupg.org/ */ class Crypt_GPG_KeyGenerator extends Crypt_GPGAbstract { // {{{ protected properties /** * The expiration date of generated keys * * @var integer * * @see Crypt_GPG_KeyGenerator::setExpirationDate() */ protected $expirationDate = 0; /** * The passphrase of generated keys * * @var string * * @see Crypt_GPG_KeyGenerator::setPassphrase() */ protected $passphrase = ''; /** * The algorithm for generated primary keys * * @var integer * * @see Crypt_GPG_KeyGenerator::setKeyParams() */ protected $keyAlgorithm = Crypt_GPG_SubKey::ALGORITHM_DSA; /** * The size of generated primary keys * * @var integer * * @see Crypt_GPG_KeyGenerator::setKeyParams() */ protected $keySize = 1024; /** * The usages of generated primary keys * * This is a bitwise combination of the usage constants in * {@link Crypt_GPG_SubKey}. * * @var integer * * @see Crypt_GPG_KeyGenerator::setKeyParams() */ protected $keyUsage = 6; // USAGE_SIGN | USAGE_CERTIFY /** * The algorithm for generated sub-keys * * @var integer * * @see Crypt_GPG_KeyGenerator::setSubKeyParams() */ protected $subKeyAlgorithm = Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC; /** * The size of generated sub-keys * * @var integer * * @see Crypt_GPG_KeyGenerator::setSubKeyParams() */ protected $subKeySize = 2048; /** * The usages of generated sub-keys * * This is a bitwise combination of the usage constants in * {@link Crypt_GPG_SubKey}. * * @var integer * * @see Crypt_GPG_KeyGenerator::setSubKeyParams() */ protected $subKeyUsage = Crypt_GPG_SubKey::USAGE_ENCRYPT; /** * The GnuPG status handler to use for key generation * * @var Crypt_GPG_KeyGeneratorStatusHandler * * @see Crypt_GPG_KeyGenerator::setStatusHandler() */ protected $statusHandler = null; /** * The GnuPG error handler to use for key generation * * @var Crypt_GPG_KeyGeneratorErrorHandler * * @see Crypt_GPG_KeyGenerator::setErrorHandler() */ protected $errorHandler = null; // }}} // {{{ __construct() /** * Creates a new GnuPG key generator * * 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>string agent</kbd> - the location of the GnuPG agent * binary. The gpg-agent is only * used for GnuPG 2.x. If not * specified, the engine attempts * to auto-detect the gpg-agent * binary location using a list of * know default locations for the * current operating system. * - <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. * * @throws PEAR_Exception if the provided <kbd>agent</kbd> is invalid, or * if no <kbd>agent</kbd> is provided and no suitable gpg-agent * cound be found. */ public function __construct(array $options = array()) { parent::__construct($options); $this->statusHandler = new Crypt_GPG_KeyGeneratorStatusHandler(); $this->errorHandler = new Crypt_GPG_KeyGeneratorErrorHandler(); } // }}} // {{{ setExpirationDate() /** * Sets the expiration date of generated keys * * @param string|integer $date either a string that may be parsed by * PHP's strtotime() function, or an integer * timestamp representing the number of seconds * since the UNIX epoch. This date must be at * least one date in the future. Keys that * expire in the past may not be generated. Use * an expiration date of 0 for keys that do not * expire. * * @throws InvalidArgumentException if the date is not a valid format, or * if the date is not at least one day in * the future, or if the date is greater * than 2038-01-19T03:14:07. * * @return Crypt_GPG_KeyGenerator the current object, for fluent interface. */ public function setExpirationDate($date) { if (is_int($date) || ctype_digit(strval($date))) { $expirationDate = intval($date); } else { $expirationDate = strtotime($date); } if ($expirationDate === false) { throw new InvalidArgumentException( sprintf( 'Invalid expiration date format: "%s". Please use a ' . 'format compatible with PHP\'s strtotime().', $date ) ); } if ($expirationDate !== 0 && $expirationDate < time() + 86400) { throw new InvalidArgumentException( 'Expiration date must be at least a day in the future.' ); } // GnuPG suffers from the 2038 bug if ($expirationDate > 2147483647) { throw new InvalidArgumentException( 'Expiration date must not be greater than 2038-01-19T03:14:07.' ); } $this->expirationDate = $expirationDate; return $this; } // }}} // {{{ setPassphrase() /** * Sets the passphrase of generated keys * * @param string $passphrase the passphrase to use for generated keys. Use * null or an empty string for no passphrase. * * @return Crypt_GPG_KeyGenerator the current object, for fluent interface. */ public function setPassphrase($passphrase) { $this->passphrase = strval($passphrase); return $this; } // }}} // {{{ setKeyParams() /** * Sets the parameters for the primary key of generated key-pairs * * @param integer $algorithm the algorithm used by the key. This should be * one of the Crypt_GPG_SubKey::ALGORITHM_* * constants. * @param integer $size optional. The size of the key. Different * algorithms have different size requirements. * If not specified, the default size for the * specified algorithm will be used. If an * invalid key size is used, GnuPG will do its * best to round it to a valid size. * @param integer $usage optional. A bitwise combination of key usages. * If not specified, the primary key will be used * only to sign and certify. This is the default * behavior of GnuPG in interactive mode. Use * the Crypt_GPG_SubKey::USAGE_* constants here. * The primary key may be used to certify even * if the certify usage is not specified. * * @return Crypt_GPG_KeyGenerator the current object, for fluent interface. */ public function setKeyParams($algorithm, $size = 0, $usage = 0) { $apgorithm = intval($algorithm); if ($algorithm === Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC) { throw new Crypt_GPG_InvalidKeyParamsException( 'Primary key algorithm must be capable of signing. The ' . 'Elgamal algorithm can only encrypt.', 0, $algorithm, $size, $usage ); } if ($size != 0) { $size = intval($size); } if ($usage != 0) { $usage = intval($usage); } $usageEncrypt = Crypt_GPG_SubKey::USAGE_ENCRYPT; if ( $algorithm === Crypt_GPG_SubKey::ALGORITHM_DSA && ($usage & $usageEncrypt) === $usageEncrypt ) { throw new Crypt_GPG_InvalidKeyParamsException( 'The DSA algorithm is not capable of encrypting. Please ' . 'specify a different algorithm or do not include encryption ' . 'as a usage for the primary key.', 0, $algorithm, $size, $usage ); } $this->keyAlgorithm = $algorithm; if ($size != 0) { $this->keySize = $size; } if ($usage != 0) { $this->keyUsage = $usage; } return $this; } // }}} // {{{ setSubKeyParams() /** * Sets the parameters for the sub-key of generated key-pairs * * @param integer $algorithm the algorithm used by the key. This should be * one of the Crypt_GPG_SubKey::ALGORITHM_* * constants. * @param integer $size optional. The size of the key. Different * algorithms have different size requirements. * If not specified, the default size for the * specified algorithm will be used. If an * invalid key size is used, GnuPG will do its * best to round it to a valid size. * @param integer $usage optional. A bitwise combination of key usages. * If not specified, the sub-key will be used * only to encrypt. This is the default behavior * of GnuPG in interactive mode. Use the * Crypt_GPG_SubKey::USAGE_* constants here. * * @return Crypt_GPG_KeyGenerator the current object, for fluent interface. */ public function setSubKeyParams($algorithm, $size = '', $usage = 0) { $apgorithm = intval($algorithm); if ($size != 0) { $size = intval($size); } if ($usage != 0) { $usage = intval($usage); } $usageSign = Crypt_GPG_SubKey::USAGE_SIGN; if ( $algorithm === Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC && ($usage & $usageSign) === $usageSign ) { throw new Crypt_GPG_InvalidKeyParamsException( 'The Elgamal algorithm is not capable of signing. Please ' . 'specify a different algorithm or do not include signing ' . 'as a usage for the sub-key.', 0, $algorithm, $size, $usage ); } $usageEncrypt = Crypt_GPG_SubKey::USAGE_ENCRYPT; if ( $algorithm === Crypt_GPG_SubKey::ALGORITHM_DSA && ($usage & $usageEncrypt) === $usageEncrypt ) { throw new Crypt_GPG_InvalidKeyParamsException( 'The DSA algorithm is not capable of encrypting. Please ' . 'specify a different algorithm or do not include encryption ' . 'as a usage for the sub-key.', 0, $algorithm, $size, $usage ); } $this->subKeyAlgorithm = $algorithm; if ($size != 0) { $this->subKeySize = $size; } if ($usage != 0) { $this->subKeyUsage = $usage; } return $this; } // }}} // {{{ setStatusHandler() /** * Sets the status handler to use for key generation * * Normally this method does not need to be used. It provides a means for * dependency injection. * * @param Crypt_GPG_KeyStatusHandler $handler the key status handler to * use. * * @return Crypt_GPG_KeyGenerator the current object, for fluent interface. */ public function setStatusHandler( Crypt_GPG_KeyGeneratorStatusHandler $handler ) { $this->statusHandler = $handler; return $this; } // }}} // {{{ setErrorHandler() /** * Sets the error handler to use for key generation * * Normally this method does not need to be used. It provides a means for * dependency injection. * * @param Crypt_GPG_KeyErrorHandler $handler the key error handler to * use. * * @return Crypt_GPG_KeyGenerator the current object, for fluent interface. */ public function setErrorHandler( Crypt_GPG_KeyGeneratorErrorHandler $handler ) { $this->errorHandler = $handler; return $this; } // }}} // {{{ generateKey() /** * Generates a new key-pair in the current keyring * * Secure key generation requires true random numbers, and as such can be * solw. If the operating system runs out of entropy, key generation will * block until more entropy is available. * * If quick key generation is important, a hardware entropy generator, or * an entropy gathering daemon may be installed. For example, * administrators of Debian systems may want to install the 'randomsound' * package. * * @param string|Crypt_GPG_UserId $name either a {@link Crypt_GPG_UserId} * object, or a string containing * the name of the user id. * @param string $email optional. If <i>$name</i> is * specified as a string, this is * the email address of the user id. * @param string $comment optional. If <i>$name</i> is * specified as a string, this is * the comment of the user id. * * @return Crypt_GPG_Key the newly generated key. * * @throws Crypt_GPG_KeyNotCreatedException if the key parameters are * incorrect, if an unknown error occurs during key generation, or * if the newly generated key is not found in the keyring. * * @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 generateKey($name, $email = '', $comment = '') { $handle = uniqid('key', true); $userId = $this->getUserId($name, $email, $comment); $keyParams = array( 'Key-Type' => $this->keyAlgorithm, 'Key-Length' => $this->keySize, 'Key-Usage' => $this->getUsage($this->keyUsage), 'Subkey-Type' => $this->subKeyAlgorithm, 'Subkey-Length' => $this->subKeySize, 'Subkey-Usage' => $this->getUsage($this->subKeyUsage), 'Name-Real' => $userId->getName(), 'Handle' => $handle, ); if ($this->expirationDate != 0) { // GnuPG only accepts granularity of days $expirationDate = date('Y-m-d', $this->expirationDate); $keyParams['Expire-Date'] = $expirationDate; } if ($this->passphrase != '') { $keyParams['Passphrase'] = $this->passphrase; } if ($userId->getEmail() != '') { $keyParams['Name-Email'] = $userId->getEmail(); } if ($userId->getComment() != '') { $keyParams['Name-Comment'] = $userId->getComment(); } $keyParamsFormatted = array(); foreach ($keyParams as $name => $value) { $keyParamsFormatted[] = $name . ': ' . $value; } $input = implode("\n", $keyParamsFormatted) . "\n%commit\n"; $statusHandler = clone $this->statusHandler; $statusHandler->setHandle($handle); $errorHandler = clone $this->errorHandler; $this->engine->reset(); $this->engine->addStatusHandler(array($statusHandler, 'handle')); $this->engine->addErrorHandler(array($errorHandler, 'handle')); $this->engine->setInput($input); $this->engine->setOutput($output); $this->engine->setOperation('--gen-key', array('--batch')); $this->engine->run(); $code = $errorHandler->getErrorCode(); switch ($code) { case self::ERROR_BAD_KEY_PARAMS: switch ($errorHandler->getLineNumber()) { case 1: throw new Crypt_GPG_InvalidKeyParamsException( 'Invalid primary key algorithm specified.', 0, $this->keyAlgorithm, $this->keySize, $this->keyUsage ); case 4: throw new Crypt_GPG_InvalidKeyParamsException( 'Invalid sub-key algorithm specified.', 0, $this->subKeyAlgorithm, $this->subKeySize, $this->subKeyUsage ); default: throw new Crypt_GPG_InvalidKeyParamsException( 'Invalid key algorithm specified.' ); } } $code = $this->engine->getErrorCode(); switch ($code) { case self::ERROR_NONE: break; default: throw new Crypt_GPG_Exception( 'Unknown error generating key-pair. Please use the \'debug\' ' . 'option when creating the Crypt_GPG object, and file a bug ' . 'report at ' . self::BUG_URI, $code ); } $code = $statusHandler->getErrorCode(); switch ($code) { case self::ERROR_NONE: break; case self::ERROR_KEY_NOT_CREATED: throw new Crypt_GPG_KeyNotCreatedException( 'Unable to create new key-pair. Invalid key parameters. ' . 'Make sure the specified key algorithms and sizes are ' . 'correct.', $code ); } $fingerprint = $statusHandler->getKeyFingerprint(); $keys = $this->_getKeys($fingerprint); if (count($keys) === 0) { throw new Crypt_GPG_KeyNotCreatedException( sprintf( 'Newly created key "%s" not found in keyring.', $fingerprint ) ); } return $keys[0]; } // }}} // {{{ getUsage() /** * Builds a GnuPG key usage string suitable for key generation * * See <b>doc/DETAILS</b> in the * {@link http://www.gnupg.org/download/ GPG distribution} for detailed * information on the key usage format. * * @param integer $usage a bitwise combination of the key usages. This is * a combination of the Crypt_GPG_SubKey::USAGE_* * constants. * * @return string the key usage string. */ protected function getUsage($usage) { $map = array( Crypt_GPG_SubKey::USAGE_ENCRYPT => 'encrypt', Crypt_GPG_SubKey::USAGE_SIGN => 'sign', Crypt_GPG_SubKey::USAGE_CERTIFY => 'cert', Crypt_GPG_SubKey::USAGE_AUTHENTICATION => 'auth', ); // cert is always used for primary keys and does not need to be // specified $usage &= ~Crypt_GPG_SubKey::USAGE_CERTIFY; $usageArray = array(); foreach ($map as $key => $value) { if (($usage & $key) === $key) { $usageArray[] = $value; } } return implode(',', $usageArray); } // }}} // {{{ getUserId() /** * Gets a user id object from parameters * * @param string|Crypt_GPG_UserId $name either a {@link Crypt_GPG_UserId} * object, or a string containing * the name of the user id. * @param string $email optional. If <i>$name</i> is * specified as a string, this is * the email address of the user id. * @param string $comment optional. If <i>$name</i> is * specified as a string, this is * the comment of the user id. * * @return Crypt_GPG_UserId a user id object for the specified parameters. */ protected function getUserId($name, $email = '', $comment = '') { if ($name instanceof Crypt_GPG_UserId) { $userId = $name; } else { $userId = new Crypt_GPG_UserId(); $userId->setName($name)->setEmail($email)->setComment($comment); } return $userId; } // }}} } // }}} ?> program/lib/Crypt/GPG/KeyGeneratorErrorHandler.php
New file @@ -0,0 +1,121 @@ <?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 error output for the * key generation 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 2011-2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @version CVS: $Id:$ * @link http://pear.php.net/package/Crypt_GPG * @link http://www.gnupg.org/ */ /** * Error line handler for the key generation 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. * * @category Encryption * @package Crypt_GPG * @author Michael Gauthier <mike@silverorange.com> * @copyright 2011-2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @link http://pear.php.net/package/Crypt_GPG * @link http://www.gnupg.org/ */ class Crypt_GPG_KeyGeneratorErrorHandler { // {{{ protected properties /** * Error code (if any) caused by key generation * * @var integer */ protected $errorCode = Crypt_GPG::ERROR_NONE; /** * Line number at which the error occurred * * @var integer */ protected $lineNumber = null; // }}} // {{{ handle() /** * Handles an error line * * @param string $line the error line to handle. * * @return void */ public function handle($line) { $matches = array(); $pattern = '/:([0-9]+): invalid algorithm$/'; if (preg_match($pattern, $line, $matches) === 1) { $this->errorCode = Crypt_GPG::ERROR_BAD_KEY_PARAMS; $this->lineNumber = intval($matches[1]); } } // }}} // {{{ getErrorCode() /** * Gets the error code resulting from key gneration * * @return integer the error code resulting from key generation. */ public function getErrorCode() { return $this->errorCode; } // }}} // {{{ getLineNumber() /** * Gets the line number at which the error occurred * * @return integer the line number at which the error occurred. Null if * no error occurred. */ public function getLineNumber() { return $this->lineNumber; } // }}} } ?> program/lib/Crypt/GPG/KeyGeneratorStatusHandler.php
New file @@ -0,0 +1,173 @@ <?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 * key generation 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 2011-2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @version CVS: $Id:$ * @link http://pear.php.net/package/Crypt_GPG * @link http://www.gnupg.org/ */ /** * Status line handler for the key generation 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 parsing the final key fingerprint from the * status output and for updating the key generation progress file. 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 batch key generation operation. * * @category Encryption * @package Crypt_GPG * @author Michael Gauthier <mike@silverorange.com> * @copyright 2011-2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @link http://pear.php.net/package/Crypt_GPG * @link http://www.gnupg.org/ */ class Crypt_GPG_KeyGeneratorStatusHandler { // {{{ protected properties /** * The key fingerprint * * Ths key fingerprint is emitted by GPG after the key generation is * complete. * * @var string */ protected $keyFingerprint = ''; /** * The unique key handle used by this handler * * The key handle is used to track GPG status output for a particular key * before the key has its own identifier. * * @var string * * @see Crypt_GPG_KeyGeneratorStatusHandler::setHandle() */ protected $handle = ''; /** * Error code (if any) caused by key generation * * @var integer */ protected $errorCode = Crypt_GPG::ERROR_NONE; // }}} // {{{ setHandle() /** * Sets the unique key handle used by this handler * * The key handle is used to track GPG status output for a particular key * before the key has its own identifier. * * @param string $handle the key handle this status handle will use. * * @return Crypt_GPG_KeyGeneratorStatusHandler the current object, for * fluent interface. */ public function setHandle($handle) { $this->handle = strval($handle); return $this; } // }}} // {{{ 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 'KEY_CREATED': if ($tokens[3] == $this->handle) { $this->keyFingerprint = $tokens[2]; } break; case 'KEY_NOT_CREATED': if ($tokens[1] == $this->handle) { $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_CREATED; } break; case 'PROGRESS': // todo: at some point, support reporting status async break; } } // }}} // {{{ getKeyFingerprint() /** * Gets the key fingerprint parsed by this handler * * @return array the key fingerprint parsed by this handler. */ public function getKeyFingerprint() { return $this->keyFingerprint; } // }}} // {{{ getErrorCode() /** * Gets the error code resulting from key gneration * * @return integer the error code resulting from key generation. */ public function getErrorCode() { return $this->errorCode; } // }}} } ?> program/lib/Crypt/GPG/PinEntry.php
New file @@ -0,0 +1,875 @@ <?php /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ /** * Contains a class implementing automatic pinentry for gpg-agent * * 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 2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @version CVS: $Id$ * @link http://pear.php.net/package/Crypt_GPG */ /** * CLI user-interface and parser. */ require_once 'Console/CommandLine.php'; // {{{ class Crypt_GPG_PinEntry /** * A command-line dummy pinentry program for use with gpg-agent and Crypt_GPG * * This pinentry receives passphrases through en environment variable and * automatically enters the PIN in response to gpg-agent requests. No user- * interaction required. * * Thie pinentry can be run independently for testing and debugging with the * following syntax: * * <pre> * Usage: * crypt-gpg-pinentry [options] * * Options: * -l log, --log=log Optional location to log pinentry activity. * -v, --verbose Sets verbosity level. Use multiples for more detail * (e.g. "-vv"). * -h, --help show this help message and exit * --version show the program version and exit * </pre> * * @category Encryption * @package Crypt_GPG * @author Michael Gauthier <mike@silverorange.com> * @copyright 2013 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_PinEntry { // {{{ class constants /** * Verbosity level for showing no output. */ const VERBOSITY_NONE = 0; /** * Verbosity level for showing error output. */ const VERBOSITY_ERRORS = 1; /** * Verbosity level for showing all output, including Assuan protocol * messages. */ const VERBOSITY_ALL = 2; /** * Length of buffer for reading lines from the Assuan server. * * 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; // }}} // {{{ protected properties /** * File handle for the input stream * * @var resource */ protected $stdin = null; /** * File handle for the output stream * * @var resource */ protected $stdout = null; /** * File handle for the log file if a log file is used * * @var resource */ protected $logFile = null; /** * Whether or not this pinentry is finished and is exiting * * @var boolean */ protected $moribund = false; /** * Verbosity level * * One of: * - {@link Crypt_GPG_PinEntry::VERBOSITY_NONE}, * - {@link Crypt_GPG_PinEntry::VERBOSITY_ERRORS}, or * - {@link Crypt_GPG_PinEntry::VERBOSITY_ALL} * * @var integer */ protected $verbosity = self::VERBOSITY_NONE; /** * The command-line interface parser for this pinentry * * @var Console_CommandLine * * @see Crypt_GPG_PinEntry::getParser() */ protected $parser = null; /** * PINs to be entered by this pinentry * * An indexed array of associative arrays in the form: * <code> * <?php * array( * array( * 'keyId' => $keyId, * 'passphrase' => $passphrase * ), * ... * ); * ?> * </code> * * This array is parsed from the environment variable * <kbd>PINENTRY_USER_DATA</kbd>. * * @var array * * @see Crypt_GPG_PinEntry::initPinsFromENV() */ protected $pins = array(); /** * PINs that have been tried for the current PIN * * This is an associative array indexed by the key identifier with * values being the same as elements in the {@link Crypt_GPG_PinEntry::$pins} * array. * * @var array */ protected $triedPins = array(); /** * The PIN currently being requested by the Assuan server * * If set, this is an associative array in the form: * <code> * <?php * array( * 'keyId' => $shortKeyId, * 'userId' => $userIdString * ); * ?> * </code> * * @var array|null */ protected $currentPin = null; // }}} // {{{ __invoke() /** * Runs this pinentry * * @return void */ public function __invoke() { $this->parser = $this->getCommandLineParser(); try { $result = $this->parser->parse(); $this->setVerbosity($result->options['verbose']); $this->setLogFilename($result->options['log']); $this->connect(); $this->initPinsFromENV(); while (($line = fgets($this->stdin, self::CHUNK_SIZE)) !== false) { $this->parseCommand(mb_substr($line, 0, -1, '8bit')); if ($this->moribund) { break; } } $this->disconnect(); } catch (Console_CommandLineException $e) { $this->log($e->getMessage() . PHP_EOL, slf::VERBOSITY_ERRORS); exit(1); } catch (Exception $e) { $this->log($e->getMessage() . PHP_EOL, self::VERBOSITY_ERRORS); $this->log($e->getTraceAsString() . PHP_EOL, self::VERBOSITY_ERRORS); exit(1); } } // }}} // {{{ setVerbosity() /** * Sets the verbosity of logging for this pinentry * * Verbosity levels are: * * - {@link Crypt_GPG_PinEntry::VERBOSITY_NONE} - no logging. * - {@link Crypt_GPG_PinEntry::VERBOSITY_ERRORS} - log errors only. * - {@link Crypt_GPG_PinEntry::VERBOSITY_ALL} - log everything, including * the assuan protocol. * * @param integer $verbosity the level of verbosity of this pinentry. * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ public function setVerbosity($verbosity) { $this->verbosity = (integer)$verbosity; return $this; } // }}} // {{{ setLogFilename() /** * Sets the log file location * * @param string $filename the new log filename to use. If an empty string * is used, file-based logging is disabled. * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ public function setLogFilename($filename) { if (is_resource($this->logFile)) { fflush($this->logFile); fclose($this->logFile); $this->logFile = null; } if ($filename != '') { if (($this->logFile = fopen($filename, 'w')) === false) { $this->log( 'Unable to open log file "' . $filename . '" ' . 'for writing.' . PHP_EOL, self::VERBOSITY_ERRORS ); exit(1); } else { stream_set_write_buffer($this->logFile, 0); } } return $this; } // }}} // {{{ getUIXML() /** * Gets the CLI user-interface definition for this pinentry * * Detects whether or not this package is PEAR-installed and appropriately * locates the XML UI definition. * * @return string the location of the CLI user-interface definition XML. */ protected function getUIXML() { $dir = '@data-dir@' . DIRECTORY_SEPARATOR . '@package-name@' . DIRECTORY_SEPARATOR . 'data'; // Check if we're running directly from a git checkout or if we're // running from a PEAR-packaged version. if ($dir[0] == '@') { $dir = dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'data'; } return $dir . DIRECTORY_SEPARATOR . 'pinentry-cli.xml'; } // }}} // {{{ getCommandLineParser() /** * Gets the CLI parser for this pinentry * * @return Console_CommandLine the CLI parser for this pinentry. */ protected function getCommandLineParser() { return Console_CommandLine::fromXmlFile($this->getUIXML()); } // }}} // {{{ log() /** * Logs a message at the specified verbosity level * * If a log file is used, the message is written to the log. Otherwise, * the message is sent to STDERR. * * @param string $data the message to log. * @param integer $level the verbosity level above which the message should * be logged. * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function log($data, $level) { if ($this->verbosity >= $level) { if (is_resource($this->logFile)) { fwrite($this->logFile, $data); fflush($this->logFile); } else { $this->parser->outputter->stderr($data); } } return $this; } // }}} // {{{ connect() /** * Connects this pinentry to the assuan server * * Opens I/O streams and sends initial handshake. * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function connect() { // Binary operations will not work on Windows with PHP < 5.2.6. $rb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'r' : 'rb'; $wb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'w' : 'wb'; $this->stdin = fopen('php://stdin', $rb); $this->stdout = fopen('php://stdout', $wb); if (function_exists('stream_set_read_buffer')) { stream_set_read_buffer($this->stdin, 0); } stream_set_write_buffer($this->stdout, 0); // initial handshake $this->send($this->getOK('Crypt_GPG pinentry ready and waiting')); return $this; } // }}} // {{{ parseCommand() /** * Parses an assuan command and performs the appropriate action * * Documentation of the assuan commands for pinentry is limited to * non-existent. Most of these commands were taken from the C source code * to gpg-agent and pinentry. * * Additional context was provided by using strace -f when calling the * gpg-agent. * * @param string $line the assuan command line to parse * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function parseCommand($line) { $this->log('<- ' . $line . PHP_EOL, self::VERBOSITY_ALL); $parts = explode(' ', $line, 2); $command = $parts[0]; if (count($parts) === 2) { $data = $parts[1]; } else { $data = null; } switch ($command) { case 'SETDESC': return $this->sendSetDescription($data); case 'SETPROMPT': case 'SETERROR': case 'SETOK': case 'SETNOTOK': case 'SETCANCEL': case 'SETQUALITYBAR': case 'SETQUALITYBAR_TT': case 'OPTION': return $this->sendNotImplementedOK(); case 'MESSAGE': return $this->sendMessage(); case 'CONFIRM': return $this->sendConfirm(); case 'GETINFO': return $this->sendGetInfo($data); case 'GETPIN': return $this->sendGetPin($data); case 'RESET': return $this->sendReset(); case 'BYE': return $this->sendBye(); } } // }}} // {{{ initPinsFromENV() /** * Initializes the PINs to be entered by this pinentry from the environment * variable PINENTRY_USER_DATA * * The PINs are parsed from a JSON-encoded string. * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function initPinsFromENV() { if (($userData = getenv('PINENTRY_USER_DATA')) !== false) { $pins = json_decode($userData, true); if ($pins === null) { $this->log( '-- failed to parse user data' . PHP_EOL, self::VERBOSITY_ERRORS ); } else { $this->pins = $pins; $this->log( '-- got user data [not showing passphrases]' . PHP_EOL, self::VERBOSITY_ALL ); } } return $this; } // }}} // {{{ disconnect() /** * Disconnects this pinentry from the Assuan server * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function disconnect() { $this->log('-- disconnecting' . PHP_EOL, self::VERBOSITY_ALL); fflush($this->stdout); fclose($this->stdout); fclose($this->stdin); $this->stdin = null; $this->stdout = null; $this->log('-- disconnected' . PHP_EOL, self::VERBOSITY_ALL); if (is_resource($this->logFile)) { fflush($this->logFile); fclose($this->logFile); $this->logFile = null; } return $this; } // }}} // {{{ sendNotImplementedOK() /** * Sends an OK response for a not implemented feature * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function sendNotImplementedOK() { return $this->send($this->getOK()); } // }}} // {{{ sendSetDescription() /** * Parses the currently requested key identifier and user identifier from * the description passed to this pinentry * * @param string $text the raw description sent from gpg-agent. * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function sendSetDescription($text) { $text = rawurldecode($text); $matches = array(); // TODO: handle user id with quotation marks $exp = '/\n"(.+)"\n.*\sID ([A-Z0-9]+),\n/mu'; if (preg_match($exp, $text, $matches) === 1) { $userId = $matches[1]; $keyId = $matches[2]; // only reset tried pins for new requested pin if ( $this->currentPin === null || $this->currentPin['keyId'] !== $keyId ) { $this->currentPin = array( 'userId' => $userId, 'keyId' => $keyId ); $this->triedPins = array(); $this->log( '-- looking for PIN for ' . $keyId . PHP_EOL, self::VERBOSITY_ALL ); } } return $this->send($this->getOK()); } // }}} // {{{ sendConfirm() /** * Tells the assuan server the PIN entry was confirmed (not cancelled) * by pressing the fake 'close' button * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function sendConfirm() { return $this->sendButtonInfo('close'); } // }}} // {{{ sendMessage() /** * Tells the assuan server that any requested pop-up messages were confirmed * by pressing the fake 'close' button * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function sendMessage() { return $this->sendButtonInfo('close'); } // }}} // {{{ sendButtonInfo() /** * Sends information about pressed buttons to the assuan server * * This is used to fake a user-interface for this pinentry. * * @param string $text the button status to send. * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function sendButtonInfo($text) { return $this->send('BUTTON_INFO ' . $text . "\n"); } // }}} // {{{ sendGetPin() /** * Sends the PIN value for the currently requested key * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function sendGetPin() { $foundPin = ''; if (is_array($this->currentPin)) { $keyIdLength = mb_strlen($this->currentPin['keyId'], '8bit'); // search for the pin foreach ($this->pins as $pin) { // only check pins we haven't tried if (!isset($this->triedPins[$pin['keyId']])) { // get last X characters of key identifier to compare $keyId = mb_substr( $pin['keyId'], -$keyIdLength, mb_strlen($pin['keyId'], '8bit'), '8bit' ); if ($keyId === $this->currentPin['keyId']) { $foundPin = $pin['passphrase']; $this->triedPins[$pin['keyId']] = $pin; break; } } } } return $this ->send($this->getData($foundPin)) ->send($this->getOK()); } // }}} // {{{ sendGetInfo() /** * Sends information about this pinentry * * @param string $data the information requested by the assuan server. * Currently only 'pid' is supported. Other requests * return no information. * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function sendGetInfo($data) { $parts = explode(' ', $data, 2); $command = reset($parts); switch ($command) { case 'pid': return $this->sendGetInfoPID(); default: return $this->send($this->getOK()); } return $this; } // }}} // {{{ sendGetInfoPID() /** * Sends the PID of this pinentry to the assuan server * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function sendGetInfoPID() { return $this ->send($this->getData(getmypid())) ->send($this->getOK()); } // }}} // {{{ sendBye() /** * Flags this pinentry for disconnection and sends an OK response * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function sendBye() { $return = $this->send($this->getOK('closing connection')); $this->moribund = true; return $return; } // }}} // {{{ sendReset() /** * Resets this pinentry and sends an OK response * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function sendReset() { $this->currentPin = null; $this->triedPins = array(); return $this->send($this->getOK()); } // }}} // {{{ getOK() /** * Gets an OK response to send to the assuan server * * @param string $data an optional message to include with the OK response. * * @return string the OK response. */ protected function getOK($data = null) { $return = 'OK'; if ($data) { $return .= ' ' . $data; } return $return . "\n"; } // }}} // {{{ getData() /** * Gets data ready to send to the assuan server * * Data is appropriately escaped and long lines are wrapped. * * @param string $data the data to send to the assuan server. * * @return string the properly escaped, formatted data. * * @see http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html */ protected function getData($data) { // Escape data. Only %, \n and \r need to be escaped but other // values are allowed to be escaped. See // http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html $data = rawurlencode($data); $data = $this->getWordWrappedData($data, 'D'); return $data; } // }}} // {{{ getComment() /** * Gets a comment ready to send to the assuan server * * @param string $data the comment to send to the assuan server. * * @return string the properly formatted comment. * * @see http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html */ protected function getComment($data) { return $this->getWordWrappedData($data, '#'); } // }}} // {{{ getWordWrappedData() /** * Wraps strings at 1,000 bytes without splitting UTF-8 multibyte * characters * * Each line is prepended with the specified line prefix. Wrapped lines * are automatically appended with \ characters. * * Protocol strings are UTF-8 but maximum line length is 1,000 bytes. * <kbd>mb_strcut()</kbd> is used so we can limit line length by bytes * and not split characters across multiple lines. * * @param string $data the data to wrap. * @param string $prefix a single character to use as the line prefix. For * example, 'D' or '#'. * * @return string the word-wrapped, prefixed string. * * @see http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html */ protected function getWordWrappedData($data, $prefix) { $lines = array(); do { if (mb_strlen($data, '8bit') > 997) { $line = $prefix . ' ' . mb_strcut($data, 0, 996, 'utf-8') . "\\\n"; $lines[] = $line; $lineLength = mb_strlen($line, '8bit') - 1; $dataLength = mb_substr($data, '8bit'); $data = mb_substr( $data, $lineLength, $dataLength - $lineLength, '8bit' ); } else { $lines[] = $prefix . ' ' . $data . "\n"; $data = ''; } } while ($data != ''); return implode('', $lines); } // }}} // {{{ send() /** * Sends raw data to the assuan server * * @param string $data the data to send. * * @return Crypt_GPG_PinEntry the current object, for fluent interface. */ protected function send($data) { $this->log('-> ' . $data, self::VERBOSITY_ALL); fwrite($this->stdout, $data); fflush($this->stdout); return $this; } // }}} } // }}} ?> program/lib/Crypt/GPG/ProcessControl.php
New file @@ -0,0 +1,150 @@ <?php /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ /** * A class for monitoring and terminating processes * * 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 2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @version CVS: $Id$ * @link http://pear.php.net/package/Crypt_GPG */ // {{{ class Crypt_GPG_ProcessControl /** * A class for monitoring and terminating processes by PID * * This is used to safely terminate the gpg-agent for GnuPG 2.x. This class * is limited in its abilities and can only check if a PID is running and * send a PID SIGTERM. * * @category Encryption * @package Crypt_GPG * @author Michael Gauthier <mike@silverorange.com> * @copyright 2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @link http://pear.php.net/package/Crypt_GPG */ class Crypt_GPG_ProcessControl { // {{{ protected properties /** * The PID (process identifier) being monitored * * @var integer */ protected $pid; // }}} // {{{ __construct() /** * Creates a new process controller from the given PID (process identifier) * * @param integer $pid the PID (process identifier). */ public function __construct($pid) { $this->pid = $pid; } // }}} // {{{ public function getPid() /** * Gets the PID (process identifier) being controlled * * @return integer the PID being controlled. */ public function getPid() { return $this->pid; } // }}} // {{{ isRunning() /** * Checks if the process is running * * Uses <kbd>ps</kbd> on UNIX-like systems and <kbd>tasklist</kbd> on * Windows. * * @return boolean true if the process is running, false if not. */ public function isRunning() { $running = false; if (PHP_OS === 'WINNT') { $command = 'tasklist /fo csv /nh /fi ' . escapeshellarg('PID eq ' . $this->pid); $result = exec($command); $parts = explode(',', $result); $running = (count($parts) > 1 && trim($parts[1], '"') == $this->pid); } else { $result = exec('ps -p ' . escapeshellarg($this->pid) . ' -o pid='); $running = (trim($result) == $this->pid); } return $running; } // }}} // {{{ terminate() /** * Ends the process gracefully * * The signal SIGTERM is sent to the process. The gpg-agent process will * end gracefully upon receiving the SIGTERM signal. Upon 3 consecutive * SIGTERM signals the gpg-agent will forcefully shut down. * * If the <kbd>posix</kbd> extension is available, <kbd>posix_kill()</kbd> * is used. Otherwise <kbd>kill</kbd> is used on UNIX-like systems and * <kbd>taskkill</kbd> is used in Windows. * * @return void */ public function terminate() { if (function_exists('posix_kill')) { posix_kill($this->pid, 15); } elseif (PHP_OS === 'WINNT') { exec('taskkill /PID ' . escapeshellarg($this->pid)); } else { exec('kill -15 ' . escapeshellarg($this->pid)); } } // }}} } // }}} ?> program/lib/Crypt/GPG/Signature.php
@@ -28,9 +28,10 @@ * @category Encryption * @package Crypt_GPG * @author Nathan Fredrickson <nathan@silverorange.com> * @copyright 2005-2010 silverorange * @author Michael Gauthier <mike@silverorange.com> * @copyright 2005-2013 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 $ * @version CVS: $Id$ * @link http://pear.php.net/package/Crypt_GPG */ @@ -50,7 +51,7 @@ * @package Crypt_GPG * @author Nathan Fredrickson <nathan@silverorange.com> * @author Michael Gauthier <mike@silverorange.com> * @copyright 2005-2010 silverorange * @copyright 2005-2013 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() @@ -159,8 +160,6 @@ if ($signature->_userId instanceof Crypt_GPG_UserId) { $this->_userId = clone $signature->_userId; } else { $this->_userId = $signature->_userId; } } program/lib/Crypt/GPG/SubKey.php
@@ -29,7 +29,7 @@ * @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 $ * @version CVS: $Id$ * @link http://pear.php.net/package/Crypt_GPG */ @@ -53,7 +53,7 @@ */ class Crypt_GPG_SubKey { // {{{ class constants // {{{ algorithm class constants /** * RSA encryption algorithm. @@ -77,6 +77,29 @@ const ALGORITHM_ELGAMAL_ENC_SGN = 20; // }}} // {{{ usage class constants /** * Key can be used to encrypt */ const USAGE_ENCRYPT = 1; /** * Key can be used to sign */ const USAGE_SIGN = 2; /** * Key can be used to certify other keys */ const USAGE_CERTIFY = 4; /** * Key can be used for authentication */ const USAGE_AUTHENTICATION = 8; // }}} // {{{ class properties /** program/lib/Crypt/GPG/UserId.php
@@ -28,7 +28,7 @@ * @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 $ * @version CVS: $Id$ * @link http://pear.php.net/package/Crypt_GPG */ program/lib/Crypt/GPG/VerifyStatusHandler.php
@@ -31,7 +31,7 @@ * @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 $ * @version CVS: $Id$ * @link http://pear.php.net/package/Crypt_GPG * @link http://www.gnupg.org/ */ program/lib/Crypt/GPGAbstract.php
New file @@ -0,0 +1,508 @@ <?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 an abstract implementation of a user of the * {@link Crypt_GPG_Engine} class. * * 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-2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @version CVS: $Id: GPG.php 305428 2010-11-17 02:47:56Z 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/ */ /** * 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_GPGAbstract /** * Base class for implementing a user of {@link Crypt_GPG_Engine} * * @category Encryption * @package Crypt_GPG * @author Nathan Fredrickson <nathan@silverorange.com> * @author Michael Gauthier <mike@silverorange.com> * @copyright 2005-2013 silverorange * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @link http://pear.php.net/package/Crypt_GPG * @link http://www.gnupg.org/ */ abstract class Crypt_GPGAbstract { // {{{ 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; /** * Error code returned when a key could not be created. */ const ERROR_KEY_NOT_CREATED = 12; /** * Error code returned when bad key parameters are used during key * generation. */ const ERROR_BAD_KEY_PARAMS = 13; // }}} // {{{ 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_GPGAbstract::setEngine() */ protected $engine = null; // }}} // {{{ __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>string agent</kbd> - the location of the GnuPG agent * binary. The gpg-agent is only * used for GnuPG 2.x. If not * specified, the engine attempts * to auto-detect the gpg-agent * binary location using a list of * know default locations for the * current operating system. * - <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. * * @throws PEAR_Exception if the provided <kbd>agent</kbd> is invalid, or * if no <kbd>agent</kbd> is provided and no suitable gpg-agent * cound be found. */ public function __construct(array $options = array()) { $this->setEngine(new Crypt_GPG_Engine($options)); } // }}} // {{{ 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 Crypt_GPGAbstract the current object, for fluent interface. */ public function setEngine(Crypt_GPG_Engine $engine) { $this->engine = $engine; return $this; } // }}} // {{{ _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 */ protected 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 self::ERROR_NONE: case self::ERROR_KEY_NOT_FOUND: // ignore not found key errors break; case self::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 self::ERROR_NONE: case self::ERROR_KEY_NOT_FOUND: // ignore not found key errors break; case self::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; } // }}} } // }}} ?>