Show spam aliases #

This commit is contained in:
andryyy
2017-02-21 22:27:11 +01:00
parent 76426b65b2
commit 0eb932b3ab
2737 changed files with 357639 additions and 22 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,597 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Various exception handling classes for Crypt_GPG
*
* Crypt_GPG provides an object oriented interface to GNU Privacy
* Guard (GPG). It requires the GPG executable to be on the system.
*
* This file contains various exception classes used by the Crypt_GPG package.
*
* PHP version 5
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005-2011 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
/**
* PEAR Exception handler and base class
*/
require_once 'PEAR/Exception.php';
// {{{ class Crypt_GPG_Exception
/**
* An exception thrown by the Crypt_GPG package
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_Exception extends PEAR_Exception
{
}
// }}}
// {{{ class Crypt_GPG_FileException
/**
* An exception thrown when a file is used in ways it cannot be used
*
* For example, if an output file is specified and the file is not writeable, or
* if an input file is specified and the file is not readable, this exception
* is thrown.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2007-2008 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_FileException extends Crypt_GPG_Exception
{
// {{{ private class properties
/**
* The name of the file that caused this exception
*
* @var string
*/
private $_filename = '';
// }}}
// {{{ __construct()
/**
* Creates a new Crypt_GPG_FileException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $filename the name of the file that caused this exception.
*/
public function __construct($message, $code = 0, $filename = '')
{
$this->_filename = $filename;
parent::__construct($message, $code);
}
// }}}
// {{{ getFilename()
/**
* Returns the filename of the file that caused this exception
*
* @return string the filename of the file that caused this exception.
*
* @see Crypt_GPG_FileException::$_filename
*/
public function getFilename()
{
return $this->_filename;
}
// }}}
}
// }}}
// {{{ class Crypt_GPG_OpenSubprocessException
/**
* An exception thrown when the GPG subprocess cannot be opened
*
* This exception is thrown when the {@link Crypt_GPG_Engine} tries to open a
* new subprocess and fails.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_OpenSubprocessException extends Crypt_GPG_Exception
{
// {{{ private class properties
/**
* The command used to try to open the subprocess
*
* @var string
*/
private $_command = '';
// }}}
// {{{ __construct()
/**
* Creates a new Crypt_GPG_OpenSubprocessException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $command the command that was called to open the
* new subprocess.
*
* @see Crypt_GPG::_openSubprocess()
*/
public function __construct($message, $code = 0, $command = '')
{
$this->_command = $command;
parent::__construct($message, $code);
}
// }}}
// {{{ getCommand()
/**
* Returns the contents of the internal _command property
*
* @return string the command used to open the subprocess.
*
* @see Crypt_GPG_OpenSubprocessException::$_command
*/
public function getCommand()
{
return $this->_command;
}
// }}}
}
// }}}
// {{{ class Crypt_GPG_InvalidOperationException
/**
* An exception thrown when an invalid GPG operation is attempted
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_InvalidOperationException extends Crypt_GPG_Exception
{
// {{{ private class properties
/**
* The attempted operation
*
* @var string
*/
private $_operation = '';
// }}}
// {{{ __construct()
/**
* Creates a new Crypt_GPG_OpenSubprocessException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $operation the operation.
*/
public function __construct($message, $code = 0, $operation = '')
{
$this->_operation = $operation;
parent::__construct($message, $code);
}
// }}}
// {{{ getOperation()
/**
* Returns the contents of the internal _operation property
*
* @return string the attempted operation.
*
* @see Crypt_GPG_InvalidOperationException::$_operation
*/
public function getOperation()
{
return $this->_operation;
}
// }}}
}
// }}}
// {{{ class Crypt_GPG_KeyNotFoundException
/**
* An exception thrown when Crypt_GPG fails to find the key for various
* operations
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_KeyNotFoundException extends Crypt_GPG_Exception
{
// {{{ private class properties
/**
* The key identifier that was searched for
*
* @var string
*/
private $_keyId = '';
// }}}
// {{{ __construct()
/**
* Creates a new Crypt_GPG_KeyNotFoundException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $keyId the key identifier of the key.
*/
public function __construct($message, $code = 0, $keyId= '')
{
$this->_keyId = $keyId;
parent::__construct($message, $code);
}
// }}}
// {{{ getKeyId()
/**
* Gets the key identifier of the key that was not found
*
* @return string the key identifier of the key that was not found.
*/
public function getKeyId()
{
return $this->_keyId;
}
// }}}
}
// }}}
// {{{ class Crypt_GPG_NoDataException
/**
* An exception thrown when Crypt_GPG cannot find valid data for various
* operations
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2006 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_NoDataException extends Crypt_GPG_Exception
{
}
// }}}
// {{{ class Crypt_GPG_BadPassphraseException
/**
* An exception thrown when a required passphrase is incorrect or missing
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2006-2008 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_BadPassphraseException extends Crypt_GPG_Exception
{
// {{{ private class properties
/**
* Keys for which the passhprase is missing
*
* This contains primary user ids indexed by sub-key id.
*
* @var array
*/
private $_missingPassphrases = array();
/**
* Keys for which the passhprase is incorrect
*
* This contains primary user ids indexed by sub-key id.
*
* @var array
*/
private $_badPassphrases = array();
// }}}
// {{{ __construct()
/**
* Creates a new Crypt_GPG_BadPassphraseException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param array $badPassphrases an array containing user ids of keys
* for which the passphrase is incorrect.
* @param array $missingPassphrases an array containing user ids of keys
* for which the passphrase is missing.
*/
public function __construct($message, $code = 0,
array $badPassphrases = array(), array $missingPassphrases = array()
) {
$this->_badPassphrases = (array) $badPassphrases;
$this->_missingPassphrases = (array) $missingPassphrases;
parent::__construct($message, $code);
}
// }}}
// {{{ getBadPassphrases()
/**
* Gets keys for which the passhprase is incorrect
*
* @return array an array of keys for which the passphrase is incorrect.
* The array contains primary user ids indexed by the sub-key
* id.
*/
public function getBadPassphrases()
{
return $this->_badPassphrases;
}
// }}}
// {{{ getMissingPassphrases()
/**
* Gets keys for which the passhprase is missing
*
* @return array an array of keys for which the passphrase is missing.
* The array contains primary user ids indexed by the sub-key
* id.
*/
public function getMissingPassphrases()
{
return $this->_missingPassphrases;
}
// }}}
}
// }}}
// {{{ class Crypt_GPG_DeletePrivateKeyException
/**
* An exception thrown when an attempt is made to delete public key that has an
* associated private key on the keyring
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
class Crypt_GPG_DeletePrivateKeyException extends Crypt_GPG_Exception
{
// {{{ private class properties
/**
* The key identifier the deletion attempt was made upon
*
* @var string
*/
private $_keyId = '';
// }}}
// {{{ __construct()
/**
* Creates a new Crypt_GPG_DeletePrivateKeyException
*
* @param string $message an error message.
* @param integer $code a user defined error code.
* @param string $keyId the key identifier of the public key that was
* attempted to delete.
*
* @see Crypt_GPG::deletePublicKey()
*/
public function __construct($message, $code = 0, $keyId = '')
{
$this->_keyId = $keyId;
parent::__construct($message, $code);
}
// }}}
// {{{ getKeyId()
/**
* Gets the key identifier of the key that was not found
*
* @return string the key identifier of the key that was not found.
*/
public function getKeyId()
{
return $this->_keyId;
}
// }}}
}
// }}}
// {{{ 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;
}
// }}}
}
// }}}
?>

View File

@@ -0,0 +1,241 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Contains a class representing GPG keys
*
* PHP version 5
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
/**
* Sub-key class definition
*/
require_once 'Crypt/GPG/SubKey.php';
/**
* User id class definition
*/
require_once 'Crypt/GPG/UserId.php';
// {{{ class Crypt_GPG_Key
/**
* A data class for GPG key information
*
* This class is used to store the results of the {@link Crypt_GPG::getKeys()}
* method.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @see Crypt_GPG::getKeys()
*/
class Crypt_GPG_Key
{
// {{{ class properties
/**
* The user ids associated with this key
*
* This is an array of {@link Crypt_GPG_UserId} objects.
*
* @var array
*
* @see Crypt_GPG_Key::addUserId()
* @see Crypt_GPG_Key::getUserIds()
*/
private $_userIds = array();
/**
* The subkeys of this key
*
* This is an array of {@link Crypt_GPG_SubKey} objects.
*
* @var array
*
* @see Crypt_GPG_Key::addSubKey()
* @see Crypt_GPG_Key::getSubKeys()
*/
private $_subKeys = array();
// }}}
// {{{ getSubKeys()
/**
* Gets the sub-keys of this key
*
* @return array the sub-keys of this key.
*
* @see Crypt_GPG_Key::addSubKey()
*/
public function getSubKeys()
{
return $this->_subKeys;
}
// }}}
// {{{ getUserIds()
/**
* Gets the user ids of this key
*
* @return array the user ids of this key.
*
* @see Crypt_GPG_Key::addUserId()
*/
public function getUserIds()
{
return $this->_userIds;
}
// }}}
// {{{ getPrimaryKey()
/**
* Gets the primary sub-key of this key
*
* The primary key is the first added sub-key.
*
* @return Crypt_GPG_SubKey the primary sub-key of this key.
*/
public function getPrimaryKey()
{
$primary_key = null;
if (count($this->_subKeys) > 0) {
$primary_key = $this->_subKeys[0];
}
return $primary_key;
}
// }}}
// {{{ canSign()
/**
* Gets whether or not this key can sign data
*
* This key can sign data if any sub-key of this key can sign data.
*
* @return boolean true if this key can sign data and false if this key
* cannot sign data.
*/
public function canSign()
{
$canSign = false;
foreach ($this->_subKeys as $subKey) {
if ($subKey->canSign()) {
$canSign = true;
break;
}
}
return $canSign;
}
// }}}
// {{{ canEncrypt()
/**
* Gets whether or not this key can encrypt data
*
* This key can encrypt data if any sub-key of this key can encrypt data.
*
* @return boolean true if this key can encrypt data and false if this
* key cannot encrypt data.
*/
public function canEncrypt()
{
$canEncrypt = false;
foreach ($this->_subKeys as $subKey) {
if ($subKey->canEncrypt()) {
$canEncrypt = true;
break;
}
}
return $canEncrypt;
}
// }}}
// {{{ addSubKey()
/**
* Adds a sub-key to this key
*
* The first added sub-key will be the primary key of this key.
*
* @param Crypt_GPG_SubKey $subKey the sub-key to add.
*
* @return Crypt_GPG_Key the current object, for fluent interface.
*/
public function addSubKey(Crypt_GPG_SubKey $subKey)
{
$this->_subKeys[] = $subKey;
return $this;
}
// }}}
// {{{ addUserId()
/**
* Adds a user id to this key
*
* @param Crypt_GPG_UserId $userId the user id to add.
*
* @return Crypt_GPG_Key the current object, for fluent interface.
*/
public function addUserId(Crypt_GPG_UserId $userId)
{
$this->_userIds[] = $userId;
return $this;
}
// }}}
// {{{ __toString()
/**
* String representation of the key
*
* @return string The key ID.
*/
public function __toString()
{
foreach ($this->_subKeys as $subKey) {
if ($id = $subKey->getId()) {
return $id;
}
}
return '';
}
// }}}
}
// }}}
?>

View File

@@ -0,0 +1,683 @@
<?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, see
* <http://www.gnu.org/licenses/>
*
* @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/
*/
/**
* Base class for GPG methods
*/
require_once 'Crypt/GPGAbstract.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;
// }}}
// {{{ __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>mixed 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);
}
// }}}
// {{{ 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)
{
$algorithm = 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)
{
$algorithm = 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;
}
// }}}
// {{{ 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 (strlen($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;
}
// This is required in GnuPG 2.1
if (!strlen($this->passphrase)) {
$keyParamsFormatted[] = '%no-protection';
}
$input = implode("\n", $keyParamsFormatted) . "\n%commit\n";
$this->engine->reset();
$this->engine->setProcessData('Handle', $handle);
$this->engine->setInput($input);
$this->engine->setOutput($output);
$this->engine->setOperation('--gen-key', array('--batch'));
try {
$this->engine->run();
} catch (Crypt_GPG_InvalidKeyParamsException $e) {
switch ($this->engine->getProcessData('LineNumber')) {
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 $e;
}
}
$fingerprint = $this->engine->getProcessData('KeyCreated');
$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;
}
// }}}
}
// }}}
?>

View File

@@ -0,0 +1,853 @@
<?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, see
* <http://www.gnu.org/licenses/>
*
* @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
*/
/**
* 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();
/**
* 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()
{
// Find PinEntry config depending on the way how the package is installed
$ds = DIRECTORY_SEPARATOR;
$root = __DIR__ . $ds . '..' . $ds . '..' . $ds;
$paths = array(
'/www/roundcube/releases/roundcubemail-1.3-beta/vendor/pear-pear.php.net/Crypt_GPG/data' . $ds . '@package-name@' . $ds . 'data', // PEAR
$root . 'data', // Git
$root . 'data' . $ds . 'Crypt_GPG' . $ds . 'data', // Composer
);
foreach ($paths as $path) {
if (file_exists($path . $ds . 'pinentry-cli.xml')) {
return $path . $ds . '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 '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();
default:
return $this->sendNotImplementedOK();
}
}
// }}}
// {{{ 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];
if ($this->currentPin === null || $this->currentPin['keyId'] !== $keyId) {
$this->currentPin = array(
'userId' => $userId,
'keyId' => $keyId
);
$this->log(
'-- looking for PIN for ' . $keyId . PHP_EOL,
self::VERBOSITY_ALL
);
}
}
return $this->send($this->getOK());
}
// }}}
// {{{ sendConfirm()
/**
* Tells the assuan server to confirm the operation
*
* @return Crypt_GPG_PinEntry the current object, for fluent interface.
*/
protected function sendConfirm()
{
return $this->send($this->getOK());
}
// }}}
// {{{ 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 $_keyId => $pin) {
// Warning: GnuPG 2.1 asks 3 times for passphrase if it is invalid
$keyId = $this->currentPin['keyId'];
$_keyIdLength = mb_strlen($_keyId, '8bit');
// Get last X characters of key identifier to compare
// Most GnuPG versions use 8 characters, but recent ones can use 16,
// We support 8 for backward compatibility
if ($keyIdLength < $_keyIdLength) {
$_keyId = mb_substr($_keyId, -$keyIdLength, $keyIdLength, '8bit');
} else if ($keyIdLength > $_keyIdLength) {
$keyId = mb_substr($keyId, -$_keyIdLength, $_keyIdLength, '8bit');
}
if ($_keyId === $keyId) {
$foundPin = $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;
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;
}
// }}}
}
// }}}
?>

View File

@@ -0,0 +1,152 @@
<?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, see
* <http://www.gnu.org/licenses/>
*
* @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
/**
* 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
*
* If the <kbd>posix</kbd> extension is available, <kbd>posix_getpgid()</kbd>
* is used. Otherwise <kbd>ps</kbd> is used 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 (function_exists('posix_getpgid')) {
$running = false !== posix_getpgid($this->pid);
} elseif (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));
}
}
// }}}
}
// }}}
?>

View File

@@ -0,0 +1,893 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Crypt_GPG is a package to use GPG from PHP
*
* This file contains handler for status and error pipes of GPG process.
*
* 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, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @author Aleksander Machniak <alec@alec.pl>
* @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/
*/
/**
* GPG exception classes.
*/
require_once 'Crypt/GPG/Exceptions.php';
/**
* Signature object class definition
*/
require_once 'Crypt/GPG/Signature.php';
// {{{ class Crypt_GPG_ProcessHandler
/**
* Status/Error handler for GPG process pipes.
*
* This class is used internally by Crypt_GPG_Engine and does not need to be used
* directly. See the {@link Crypt_GPG} class for end-user API.
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @author Aleksander Machniak <alec@alec.pl>
* @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_ProcessHandler
{
// {{{ protected class properties
/**
* Engine used to control the GPG subprocess
*
* @var Crypt_GPG_Engine
*/
protected $engine;
/**
* The error code of the current operation
*
* @var integer
*/
protected $errorCode = Crypt_GPG::ERROR_NONE;
/**
* The number of currently needed passphrases
*
* If this is not zero when the GPG command is completed, the error code is
* set to {@link Crypt_GPG::ERROR_MISSING_PASSPHRASE}.
*
* @var integer
*/
protected $needPassphrase = 0;
/**
* Some data collected while processing the operation
* or set for the operation
*
* @var array
* @see self::setData()
* @see self::getData()
*/
protected $data = array();
/**
* The name of the current operation
*
* @var string
* @see self::setOperation()
*/
protected $operation = null;
/**
* The value of the argument of current operation
*
* @var string
* @see self::setOperation()
*/
protected $operationArg = null;
// }}}
// {{{ __construct()
/**
* Creates a new instance
*
* @param Crypt_GPG_Engine $engine Engine object
*/
public function __construct($engine)
{
$this->engine = $engine;
}
// }}}
// {{{ setOperation()
/**
* Sets the operation that is being performed by the engine.
*
* @param string $operation The GPG operation to perform.
*
* @return void
*/
public function setOperation($operation)
{
$op = null;
$opArg = null;
// Regexp matching all GPG "operational" arguments
$regexp = '/--('
. 'version|import|list-public-keys|list-secret-keys'
. '|list-keys|delete-key|delete-secret-key|encrypt|sign|clearsign'
. '|detach-sign|decrypt|verify|export-secret-keys|export|gen-key'
. ')/';
if (strpos($operation, ' ') === false) {
$op = trim($operation, '- ');
} else if (preg_match($regexp, $operation, $matches, PREG_OFFSET_CAPTURE)) {
$op = trim($matches[0][0], '-');
$op_len = $matches[0][1] + mb_strlen($op, '8bit') + 3;
$command = mb_substr($operation, $op_len, null, '8bit');
// we really need the argument if it is a key ID/fingerprint or email
// address se we can use simplified regexp to "revert escapeshellarg()"
if (preg_match('/^[\'"]([a-zA-Z0-9:@._-]+)[\'"]/', $command, $matches)) {
$opArg = $matches[1];
}
}
$this->operation = $op;
$this->operationArg = $opArg;
}
// }}}
// {{{ handleStatus()
/**
* Handles error values in the status output from GPG
*
* This method is responsible for setting the
* {@link self::$errorCode}. See <b>doc/DETAILS</b> in the
* {@link http://www.gnupg.org/download/ GPG distribution} for detailed
* information on GPG's status output.
*
* @param string $line the status line to handle.
*
* @return void
*/
public function handleStatus($line)
{
$tokens = explode(' ', $line);
switch ($tokens[0]) {
case 'NODATA':
$this->errorCode = Crypt_GPG::ERROR_NO_DATA;
break;
case 'DECRYPTION_OKAY':
// If the message is encrypted, this is the all-clear signal.
$this->data['DecryptionOkay'] = true;
$this->errorCode = Crypt_GPG::ERROR_NONE;
break;
case 'DELETE_PROBLEM':
if ($tokens[1] == '1') {
$this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
break;
} elseif ($tokens[1] == '2') {
$this->errorCode = Crypt_GPG::ERROR_DELETE_PRIVATE_KEY;
break;
}
break;
case 'IMPORT_OK':
$this->data['Import']['fingerprint'] = $tokens[2];
if (empty($this->data['Import']['fingerprints'])) {
$this->data['Import']['fingerprints'] = array($tokens[2]);
} else if (!in_array($tokens[2], $this->data['Import']['fingerprints'])) {
$this->data['Import']['fingerprints'][] = $tokens[2];
}
break;
case 'IMPORT_RES':
$this->data['Import']['public_imported'] = intval($tokens[3]);
$this->data['Import']['public_unchanged'] = intval($tokens[5]);
$this->data['Import']['private_imported'] = intval($tokens[11]);
$this->data['Import']['private_unchanged'] = intval($tokens[12]);
break;
case 'NO_PUBKEY':
case 'NO_SECKEY':
$this->data['ErrorKeyId'] = $tokens[1];
if ($this->errorCode != Crypt_GPG::ERROR_MISSING_PASSPHRASE
&& $this->errorCode != Crypt_GPG::ERROR_BAD_PASSPHRASE
) {
$this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
}
// note: this message is also received if there are multiple
// recipients and a previous key had a correct passphrase.
$this->data['MissingKeys'][$tokens[1]] = $tokens[1];
// @FIXME: remove missing passphrase registered in ENC_TO handler
// This is for GnuPG 2.1
unset($this->data['MissingPassphrases'][$tokens[1]]);
break;
case 'KEY_CONSIDERED':
// In GnuPG 2.1.x exporting/importing a secret key requires passphrase
// However, no NEED_PASSPRASE is returned, https://bugs.gnupg.org/gnupg/issue2667
// So, handling KEY_CONSIDERED and GET_HIDDEN is needed.
if (!array_key_exists('KeyConsidered', $this->data)) {
$this->data['KeyConsidered'] = $tokens[1];
}
break;
case 'USERID_HINT':
// remember the user id for pretty exception messages
// GnuPG 2.1.15 gives me: "USERID_HINT 0000000000000000 [?]"
$keyId = $tokens[1];
if (strcspn($keyId, '0')) {
$username = implode(' ', array_splice($tokens, 2));
$this->data['BadPassphrases'][$keyId] = $username;
}
break;
case 'ENC_TO':
// Now we know the message is encrypted. Set flag to check if
// decryption succeeded.
$this->data['DecryptionOkay'] = false;
// this is the new key message
$this->data['CurrentSubKeyId'] = $keyId = $tokens[1];
// For some reason in GnuPG 2.1.11 I get only ENC_TO and no
// NEED_PASSPHRASE/MISSING_PASSPHRASE/USERID_HINT
// This is not needed for GnuPG 2.1.15
if (!empty($_ENV['PINENTRY_USER_DATA'])) {
$passphrases = json_decode($_ENV['PINENTRY_USER_DATA'], true);
} else {
$passphrases = array();
}
// @TODO: Get user name/email
$this->data['BadPassphrases'][$keyId] = $keyId;
if (empty($passphrases) || empty($passphrases[$keyId])) {
$this->data['MissingPassphrases'][$keyId] = $keyId;
}
break;
case 'GOOD_PASSPHRASE':
// if we got a good passphrase, remove the key from the list of
// bad passphrases.
if (isset($this->data['CurrentSubKeyId'])) {
unset($this->data['BadPassphrases'][$this->data['CurrentSubKeyId']]);
unset($this->data['MissingPassphrases'][$this->data['CurrentSubKeyId']]);
}
$this->needPassphrase--;
break;
case 'BAD_PASSPHRASE':
$this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
break;
case 'MISSING_PASSPHRASE':
if (isset($this->data['CurrentSubKeyId'])) {
$this->data['MissingPassphrases'][$this->data['CurrentSubKeyId']]
= $this->data['CurrentSubKeyId'];
}
$this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
break;
case 'GET_HIDDEN':
if ($tokens[1] == 'passphrase.enter' && isset($this->data['KeyConsidered'])) {
$tokens[1] = $this->data['KeyConsidered'];
} else {
break;
}
// no break
case 'NEED_PASSPHRASE':
$passphrase = $this->getPin($tokens[1]);
$this->engine->sendCommand($passphrase);
if ($passphrase === '') {
$this->needPassphrase++;
}
break;
case 'SIG_CREATED':
$this->data['SigCreated'] = $line;
break;
case 'SIG_ID':
// note: signature id comes before new signature line and may not
// exist for some signature types
$this->data['SignatureId'] = $tokens[1];
break;
case 'EXPSIG':
case 'EXPKEYSIG':
case 'REVKEYSIG':
case 'BADSIG':
case 'ERRSIG':
$this->errorCode = Crypt_GPG::ERROR_BAD_SIGNATURE;
// no break
case 'GOODSIG':
$signature = new Crypt_GPG_Signature();
// if there was a signature id, set it on the new signature
if (!empty($this->data['SignatureId'])) {
$signature->setId($this->data['SignatureId']);
$this->data['SignatureId'] = '';
}
// Detect whether fingerprint or key id was returned and set
// signature values appropriately. Key ids are strings of either
// 16 or 8 hexadecimal characters. Fingerprints are strings of 40
// hexadecimal characters. The key id is the last 16 characters of
// the key fingerprint.
if (mb_strlen($tokens[1], '8bit') > 16) {
$signature->setKeyFingerprint($tokens[1]);
$signature->setKeyId(mb_substr($tokens[1], -16, null, '8bit'));
} else {
$signature->setKeyId($tokens[1]);
}
// get user id string
if ($tokens[0] != 'ERRSIG') {
$string = implode(' ', array_splice($tokens, 2));
$string = rawurldecode($string);
$signature->setUserId(Crypt_GPG_UserId::parse($string));
}
$this->data['Signatures'][] = $signature;
break;
case 'VALIDSIG':
if (empty($this->data['Signatures'])) {
break;
}
$signature = end($this->data['Signatures']);
$signature->setValid(true);
$signature->setKeyFingerprint($tokens[1]);
if (strpos($tokens[3], 'T') === false) {
$signature->setCreationDate($tokens[3]);
} else {
$signature->setCreationDate(strtotime($tokens[3]));
}
if (array_key_exists(4, $tokens)) {
if (strpos($tokens[4], 'T') === false) {
$signature->setExpirationDate($tokens[4]);
} else {
$signature->setExpirationDate(strtotime($tokens[4]));
}
}
break;
case 'KEY_CREATED':
if (isset($this->data['Handle']) && $tokens[3] == $this->data['Handle']) {
$this->data['KeyCreated'] = $tokens[2];
}
break;
case 'KEY_NOT_CREATED':
if (isset($this->data['Handle']) && $tokens[1] == $this->data['Handle']) {
$this->errorCode = Crypt_GPG::ERROR_KEY_NOT_CREATED;
}
break;
case 'PROGRESS':
// todo: at some point, support reporting status async
break;
// GnuPG 2.1 uses FAILURE and ERROR responses
case 'FAILURE':
case 'ERROR':
$errnum = (int) $tokens[2];
$source = $errnum >> 24;
$errcode = $errnum & 0xFFFFFF;
switch ($errcode) {
case 11: // bad passphrase
case 87: // bad PIN
$this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
break;
case 177: // no passphrase
case 178: // no PIN
$this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
break;
case 58:
$this->errorCode = Crypt_GPG::ERROR_NO_DATA;
break;
}
break;
}
}
// }}}
// {{{ handleError()
/**
* Handles error values in the error output from GPG
*
* This method is responsible for setting the
* {@link Crypt_GPG_Engine::$_errorCode}.
*
* @param string $line the error line to handle.
*
* @return void
*/
public function handleError($line)
{
if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
$pattern = '/no valid OpenPGP data found/';
if (preg_match($pattern, $line) === 1) {
$this->errorCode = Crypt_GPG::ERROR_NO_DATA;
}
}
if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
$pattern = '/No secret key|secret key not available/';
if (preg_match($pattern, $line) === 1) {
$this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
}
}
if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
$pattern = '/No public key|public key not found/';
if (preg_match($pattern, $line) === 1) {
$this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
}
}
if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
$matches = array();
$pattern = '/can\'t (?:access|open) `(.*?)\'/';
if (preg_match($pattern, $line, $matches) === 1) {
$this->data['ErrorFilename'] = $matches[1];
$this->errorCode = Crypt_GPG::ERROR_FILE_PERMISSIONS;
}
}
// GnuPG 2.1: It should return MISSING_PASSPHRASE, but it does not
// we have to detect it this way. This happens e.g. on private key import
if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
$matches = array();
$pattern = '/key ([0-9A-F]+).* (Bad|No) passphrase/';
if (preg_match($pattern, $line, $matches) === 1) {
$keyId = $matches[1];
// @TODO: Get user name/email
if (empty($this->data['BadPassphrases'][$keyId])) {
$this->data['BadPassphrases'][$keyId] = $keyId;
}
if ($matches[2] == 'Bad') {
$this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
} else {
$this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
if (empty($this->data['MissingPassphrases'][$keyId])) {
$this->data['MissingPassphrases'][$keyId] = $keyId;
}
}
}
}
if ($this->errorCode === Crypt_GPG::ERROR_NONE && $this->operation == 'gen-key') {
$pattern = '/:([0-9]+): invalid algorithm$/';
if (preg_match($pattern, $line, $matches) === 1) {
$this->errorCode = Crypt_GPG::ERROR_BAD_KEY_PARAMS;
$this->data['LineNumber'] = intval($matches[1]);
}
}
}
// }}}
// {{{ throwException()
/**
* On error throws exception
*
* @param int $exitcode GPG process exit code
*
* @return void
* @throws Crypt_GPG_Exception
*/
public function throwException($exitcode = 0)
{
if ($exitcode != 0 && $this->errorCode === Crypt_GPG::ERROR_NONE) {
if ($this->needPassphrase > 0) {
$this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
} else if ($this->operation != 'import') {
$this->errorCode = Crypt_GPG::ERROR_UNKNOWN;
}
}
if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
return;
}
$code = $this->errorCode;
$note = "Please use the 'debug' option when creating the Crypt_GPG " .
"object, and file a bug report at " . Crypt_GPG::BUG_URI;
switch ($this->operation) {
case 'version':
throw new Crypt_GPG_Exception(
'Unknown error getting GnuPG version information. ' . $note,
$code
);
case 'list-secret-keys':
case 'list-public-keys':
case 'list-keys':
switch ($code) {
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
// ignore not found key errors
break;
case Crypt_GPG::ERROR_FILE_PERMISSIONS:
if (!empty($this->data['ErrorFilename'])) {
throw new Crypt_GPG_FileException(
sprintf(
'Error reading GnuPG data file \'%s\'. Check to make ' .
'sure it is readable by the current user.',
$this->data['ErrorFilename']
),
$code,
$this->data['ErrorFilename']
);
}
throw new Crypt_GPG_FileException(
'Error reading GnuPG data file. Check to make sure that ' .
'GnuPG data files are readable by the current user.',
$code
);
default:
throw new Crypt_GPG_Exception(
'Unknown error getting keys. ' . $note, $code
);
}
break;
case 'delete-key':
case 'delete-secret-key':
switch ($code) {
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
throw new Crypt_GPG_KeyNotFoundException(
'Key not found: ' . $this->operationArg,
$code,
$this->operationArg
);
case Crypt_GPG::ERROR_DELETE_PRIVATE_KEY:
throw new Crypt_GPG_DeletePrivateKeyException(
'Private key must be deleted before public key can be ' .
'deleted.',
$code,
$this->operationArg
);
default:
throw new Crypt_GPG_Exception(
'Unknown error deleting key. ' . $note, $code
);
}
break;
case 'import':
switch ($code) {
case Crypt_GPG::ERROR_NO_DATA:
throw new Crypt_GPG_NoDataException(
'No valid GPG key data found.', $code
);
case Crypt_GPG::ERROR_BAD_PASSPHRASE:
case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
throw $this->badPassException($code, 'Cannot import private key.');
default:
throw new Crypt_GPG_Exception(
'Unknown error importing GPG key. ' . $note, $code
);
}
break;
case 'export':
case 'export-secret-keys':
switch ($code) {
case Crypt_GPG::ERROR_BAD_PASSPHRASE:
case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
throw $this->badPassException($code, 'Cannot export private key.');
default:
throw new Crypt_GPG_Exception(
'Unknown error exporting a key. ' . $note, $code
);
}
break;
case 'encrypt':
case 'sign':
case 'clearsign':
case 'detach-sign':
switch ($code) {
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
throw new Crypt_GPG_KeyNotFoundException(
'Cannot sign data. Private key not found. Import the '.
'private key before trying to sign data.',
$code,
!empty($this->data['ErrorKeyId']) ? $this->data['ErrorKeyId'] : null
);
case Crypt_GPG::ERROR_BAD_PASSPHRASE:
throw new Crypt_GPG_BadPassphraseException(
'Cannot sign data. Incorrect passphrase provided.', $code
);
case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
throw new Crypt_GPG_BadPassphraseException(
'Cannot sign data. No passphrase provided.', $code
);
default:
throw new Crypt_GPG_Exception(
"Unknown error {$this->operation}ing data. $note", $code
);
}
break;
case 'verify':
switch ($code) {
case Crypt_GPG::ERROR_BAD_SIGNATURE:
// ignore bad signature errors
break;
case Crypt_GPG::ERROR_NO_DATA:
throw new Crypt_GPG_NoDataException(
'No valid signature data found.', $code
);
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
throw new Crypt_GPG_KeyNotFoundException(
'Public key required for data verification not in keyring.',
$code,
!empty($this->data['ErrorKeyId']) ? $this->data['ErrorKeyId'] : null
);
default:
throw new Crypt_GPG_Exception(
'Unknown error validating signature details. ' . $note,
$code
);
}
break;
case 'decrypt':
switch ($code) {
case Crypt_GPG::ERROR_BAD_SIGNATURE:
// ignore bad signature errors
break;
case Crypt_GPG::ERROR_KEY_NOT_FOUND:
if (!empty($this->data['MissingKeys'])) {
$keyId = reset($this->data['MissingKeys']);
} else {
$keyId = '';
}
throw new Crypt_GPG_KeyNotFoundException(
'Cannot decrypt data. No suitable private key is in the ' .
'keyring. Import a suitable private key before trying to ' .
'decrypt this data.',
$code,
$keyId
);
case Crypt_GPG::ERROR_BAD_PASSPHRASE:
case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
throw $this->badPassException($code, 'Cannot decrypt data.');
case Crypt_GPG::ERROR_NO_DATA:
throw new Crypt_GPG_NoDataException(
'Cannot decrypt data. No PGP encrypted data was found in '.
'the provided data.',
$code
);
default:
throw new Crypt_GPG_Exception(
'Unknown error decrypting data.', $code
);
}
break;
case 'gen-key':
switch ($code) {
case Crypt_GPG::ERROR_BAD_KEY_PARAMS:
throw new Crypt_GPG_InvalidKeyParamsException(
'Invalid key algorithm specified.', $code
);
default:
throw new Crypt_GPG_Exception(
'Unknown error generating key-pair. ' . $note, $code
);
}
}
}
// }}}
// {{{ getData()
/**
* Get data from the last process execution.
*
* @param string $name Data element name:
* - SigCreated: The last SIG_CREATED status.
* - KeyConsidered: The last KEY_CONSIDERED status identifier.
* - KeyCreated: The KEY_CREATED status (for specified Handle).
* - Signatures: Signatures data from verification process.
* - LineNumber: Number of the gen-key error line.
* - Import: Result of IMPORT_OK/IMPORT_RES
*
* @return mixed
*/
public function getData($name)
{
return isset($this->data[$name]) ? $this->data[$name] : null;
}
// }}}
// {{{ setData()
/**
* Set data for the process execution.
*
* @param string $name Data element name:
* - Handle: The unique key handle used by this handler
* The key handle is used to track GPG status output
* for a particular key on --gen-key command before
* the key has its own identifier.
* @param mixed $value Data element value
*
* @return void
*/
public function setData($name, $value)
{
switch ($name) {
case 'Handle':
$this->data[$name] = strval($value);
break;
}
}
// }}}
// {{{ setData()
/**
* Create Crypt_GPG_BadPassphraseException from operation data.
*
* @param int $code Error code
* @param string $message Error message
*
* @return Crypt_GPG_BadPassphraseException
*/
protected function badPassException($code, $message)
{
$badPassphrases = array_diff_key(
isset($this->data['BadPassphrases']) ? $this->data['BadPassphrases'] : array(),
isset($this->data['MissingPassphrases']) ? $this->data['MissingPassphrases'] : array()
);
$missingPassphrases = array_intersect_key(
isset($this->data['BadPassphrases']) ? $this->data['BadPassphrases'] : array(),
isset($this->data['MissingPassphrases']) ? $this->data['MissingPassphrases'] : array()
);
if (count($badPassphrases) > 0) {
$message .= ' Incorrect passphrase provided for keys: "' .
implode('", "', $badPassphrases) . '".';
}
if (count($missingPassphrases) > 0) {
$message .= ' No passphrase provided for keys: "' .
implode('", "', $missingPassphrases) . '".';
}
return new Crypt_GPG_BadPassphraseException(
$message,
$code,
$badPassphrases,
$missingPassphrases
);
}
// }}}
// {{{ getPin()
/**
* Get registered passphrase for specified key.
*
* @param string $key Key identifier
*
* @return string Passphrase
*/
protected function getPin($key)
{
$passphrase = '';
$keyIdLength = mb_strlen($key, '8bit');
if ($keyIdLength && !empty($_ENV['PINENTRY_USER_DATA'])) {
$passphrases = json_decode($_ENV['PINENTRY_USER_DATA'], true);
foreach ($passphrases as $_keyId => $pass) {
$keyId = $key;
$_keyIdLength = mb_strlen($_keyId, '8bit');
// Get last X characters of key identifier to compare
if ($keyIdLength < $_keyIdLength) {
$_keyId = mb_substr($_keyId, -$keyIdLength, null, '8bit');
} else if ($keyIdLength > $_keyIdLength) {
$keyId = mb_substr($keyId, -$_keyIdLength, null, '8bit');
}
if ($_keyId === $keyId) {
$passphrase = $pass;
break;
}
}
}
return $passphrase;
}
// }}}
}
// }}}
?>

View File

@@ -0,0 +1,426 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* A class representing GPG signatures
*
* This file contains a data class representing a GPG signature.
*
* PHP version 5
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @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
*/
/**
* User id class definition
*/
require_once 'Crypt/GPG/UserId.php';
// {{{ class Crypt_GPG_Signature
/**
* A class for GPG signature information
*
* This class is used to store the results of the Crypt_GPG::verify() method.
*
* @category Encryption
* @package Crypt_GPG
* @author Nathan Fredrickson <nathan@silverorange.com>
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2005-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()
*/
class Crypt_GPG_Signature
{
// {{{ class properties
/**
* A base64-encoded string containing a unique id for this signature if
* this signature has been verified as ok
*
* This id is used to prevent replay attacks and is not present for all
* types of signatures.
*
* @var string
*/
private $_id = '';
/**
* The fingerprint of the key used to create the signature
*
* @var string
*/
private $_keyFingerprint = '';
/**
* The id of the key used to create the signature
*
* @var string
*/
private $_keyId = '';
/**
* The creation date of this signature
*
* This is a Unix timestamp.
*
* @var integer
*/
private $_creationDate = 0;
/**
* The expiration date of the signature
*
* This is a Unix timestamp. If this signature does not expire, this will
* be zero.
*
* @var integer
*/
private $_expirationDate = 0;
/**
* The user id associated with this signature
*
* @var Crypt_GPG_UserId
*/
private $_userId = null;
/**
* Whether or not this signature is valid
*
* @var boolean
*/
private $_isValid = false;
// }}}
// {{{ __construct()
/**
* Creates a new signature
*
* Signatures can be initialized from an array of named values. Available
* names are:
*
* - <kbd>string id</kbd> - the unique id of this signature.
* - <kbd>string fingerprint</kbd> - the fingerprint of the key used to
* create the signature. The fingerprint
* should not contain formatting
* characters.
* - <kbd>string keyId</kbd> - the id of the key used to create the
* the signature.
* - <kbd>integer creation</kbd> - the date the signature was created.
* This is a UNIX timestamp.
* - <kbd>integer expiration</kbd> - the date the signature expired. This
* is a UNIX timestamp. If the signature
* does not expire, use 0.
* - <kbd>boolean valid</kbd> - whether or not the signature is valid.
* - <kbd>string userId</kbd> - the user id associated with the
* signature. This may also be a
* {@link Crypt_GPG_UserId} object.
*
* @param Crypt_GPG_Signature|array $signature optional. Either an existing
* signature object, which is copied; or an array of initial values.
*/
public function __construct($signature = null)
{
// copy from object
if ($signature instanceof Crypt_GPG_Signature) {
$this->_id = $signature->_id;
$this->_keyFingerprint = $signature->_keyFingerprint;
$this->_keyId = $signature->_keyId;
$this->_creationDate = $signature->_creationDate;
$this->_expirationDate = $signature->_expirationDate;
$this->_isValid = $signature->_isValid;
if ($signature->_userId instanceof Crypt_GPG_UserId) {
$this->_userId = clone $signature->_userId;
}
}
// initialize from array
if (is_array($signature)) {
if (array_key_exists('id', $signature)) {
$this->setId($signature['id']);
}
if (array_key_exists('fingerprint', $signature)) {
$this->setKeyFingerprint($signature['fingerprint']);
}
if (array_key_exists('keyId', $signature)) {
$this->setKeyId($signature['keyId']);
}
if (array_key_exists('creation', $signature)) {
$this->setCreationDate($signature['creation']);
}
if (array_key_exists('expiration', $signature)) {
$this->setExpirationDate($signature['expiration']);
}
if (array_key_exists('valid', $signature)) {
$this->setValid($signature['valid']);
}
if (array_key_exists('userId', $signature)) {
$userId = new Crypt_GPG_UserId($signature['userId']);
$this->setUserId($userId);
}
}
}
// }}}
// {{{ getId()
/**
* Gets the id of this signature
*
* @return string a base64-encoded string containing a unique id for this
* signature. This id is used to prevent replay attacks and
* is not present for all types of signatures.
*/
public function getId()
{
return $this->_id;
}
// }}}
// {{{ getKeyFingerprint()
/**
* Gets the fingerprint of the key used to create this signature
*
* @return string the fingerprint of the key used to create this signature.
*/
public function getKeyFingerprint()
{
return $this->_keyFingerprint;
}
// }}}
// {{{ getKeyId()
/**
* Gets the id of the key used to create this signature
*
* Whereas the fingerprint of the signing key may not always be available
* (for example if the signature is bad), the id should always be
* available.
*
* @return string the id of the key used to create this signature.
*/
public function getKeyId()
{
return $this->_keyId;
}
// }}}
// {{{ getCreationDate()
/**
* Gets the creation date of this signature
*
* @return integer the creation date of this signature. This is a Unix
* timestamp.
*/
public function getCreationDate()
{
return $this->_creationDate;
}
// }}}
// {{{ getExpirationDate()
/**
* Gets the expiration date of the signature
*
* @return integer the expiration date of this signature. This is a Unix
* timestamp. If this signature does not expire, this will
* be zero.
*/
public function getExpirationDate()
{
return $this->_expirationDate;
}
// }}}
// {{{ getUserId()
/**
* Gets the user id associated with this signature
*
* @return Crypt_GPG_UserId the user id associated with this signature.
*/
public function getUserId()
{
return $this->_userId;
}
// }}}
// {{{ isValid()
/**
* Gets whether or no this signature is valid
*
* @return boolean true if this signature is valid and false if it is not.
*/
public function isValid()
{
return $this->_isValid;
}
// }}}
// {{{ setId()
/**
* Sets the id of this signature
*
* @param string $id a base64-encoded string containing a unique id for
* this signature.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*
* @see Crypt_GPG_Signature::getId()
*/
public function setId($id)
{
$this->_id = strval($id);
return $this;
}
// }}}
// {{{ setKeyFingerprint()
/**
* Sets the key fingerprint of this signature
*
* @param string $fingerprint the key fingerprint of this signature. This
* is the fingerprint of the primary key used to
* create this signature.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setKeyFingerprint($fingerprint)
{
$this->_keyFingerprint = strval($fingerprint);
return $this;
}
// }}}
// {{{ setKeyId()
/**
* Sets the key id of this signature
*
* @param string $id the key id of this signature. This is the id of the
* primary key used to create this signature.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setKeyId($id)
{
$this->_keyId = strval($id);
return $this;
}
// }}}
// {{{ setCreationDate()
/**
* Sets the creation date of this signature
*
* @param integer $creationDate the creation date of this signature. This
* is a Unix timestamp.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setCreationDate($creationDate)
{
$this->_creationDate = intval($creationDate);
return $this;
}
// }}}
// {{{ setExpirationDate()
/**
* Sets the expiration date of this signature
*
* @param integer $expirationDate the expiration date of this signature.
* This is a Unix timestamp. Specify zero if
* this signature does not expire.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setExpirationDate($expirationDate)
{
$this->_expirationDate = intval($expirationDate);
return $this;
}
// }}}
// {{{ setUserId()
/**
* Sets the user id associated with this signature
*
* @param Crypt_GPG_UserId $userId the user id associated with this
* signature.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setUserId(Crypt_GPG_UserId $userId)
{
$this->_userId = $userId;
return $this;
}
// }}}
// {{{ setValid()
/**
* Sets whether or not this signature is valid
*
* @param boolean $isValid true if this signature is valid and false if it
* is not.
*
* @return Crypt_GPG_Signature the current object, for fluent interface.
*/
public function setValid($isValid)
{
$this->_isValid = ($isValid) ? true : false;
return $this;
}
// }}}
}
// }}}
?>

View File

@@ -0,0 +1,224 @@
<?php
/**
* Part of Crypt_GPG
*
* PHP version 5
*
* @category Encryption
* @package Crypt_GPG
* @author Christian Weiske <cweiske@php.net>
* @copyright 2015 PEAR
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @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/
*/
/**
* Information about a recently created signature.
*
* @category Encryption
* @package Crypt_GPG
* @author Christian Weiske <cweiske@php.net>
* @copyright 2015 PEAR
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @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/
*/
class Crypt_GPG_SignatureCreationInfo
{
/**
* One of the three signature types:
* - {@link Crypt_GPG::SIGN_MODE_NORMAL}
* - {@link Crypt_GPG::SIGN_MODE_CLEAR}
* - {@link Crypt_GPG::SIGN_MODE_DETACHED}
*
* @var integer
*/
protected $mode;
/**
* Public Key algorithm
*
* @var integer
*/
protected $pkAlgorithm;
/**
* Algorithm to hash the data
*
* @see RFC 2440 / 9.4. Hash Algorithm
* @var integer
*/
protected $hashAlgorithm;
/**
* OpenPGP signature class
*
* @var mixed
*/
protected $class;
/**
* Unix timestamp when the signature was created
*
* @var integer
*/
protected $timestamp;
/**
* Key fingerprint
*
* @var string
*/
protected $keyFingerprint;
/**
* If the line given to the constructor was valid
*
* @var boolean
*/
protected $valid;
/**
* Names for the hash algorithm IDs.
*
* Names taken from RFC 3156, without the leading "pgp-".
*
* @see RFC 2440 / 9.4. Hash Algorithm
* @see RFC 3156 / 5. OpenPGP signed data
* @var array
*/
protected static $hashAlgorithmNames = array(
1 => 'md5',
2 => 'sha1',
3 => 'ripemd160',
5 => 'md2',
6 => 'tiger192',
7 => 'haval-5-160',
);
/**
* Parse a SIG_CREATED line from gnupg
*
* @param string $sigCreatedLine Line beginning with "SIG_CREATED "
*/
public function __construct($sigCreatedLine = null)
{
if ($sigCreatedLine === null) {
$this->valid = false;
return;
}
$parts = explode(' ', $sigCreatedLine);
if (count($parts) !== 7) {
$this->valid = false;
return;
}
list(
$title, $mode, $pkAlgorithm, $hashAlgorithm,
$class, $timestamp, $keyFingerprint
) = $parts;
switch (strtoupper($mode[0])) {
case 'D':
$this->mode = Crypt_GPG::SIGN_MODE_DETACHED;
break;
case 'C':
$this->mode = Crypt_GPG::SIGN_MODE_CLEAR;
break;
case 'S':
$this->mode = Crypt_GPG::SIGN_MODE_NORMAL;
break;
}
$this->pkAlgorithm = (int) $pkAlgorithm;
$this->hashAlgorithm = (int) $hashAlgorithm;
$this->class = $class;
if (is_numeric($timestamp)) {
$this->timestamp = (int) $timestamp;
} else {
$this->timestamp = strtotime($timestamp);
}
$this->keyFingerprint = $keyFingerprint;
$this->valid = true;
}
/**
* Get the signature type
* - {@link Crypt_GPG::SIGN_MODE_NORMAL}
* - {@link Crypt_GPG::SIGN_MODE_CLEAR}
* - {@link Crypt_GPG::SIGN_MODE_DETACHED}
*
* @return integer
*/
public function getMode()
{
return $this->mode;
}
/**
* Return the public key algorithm used.
*
* @return integer
*/
public function getPkAlgorithm()
{
return $this->pkAlgorithm;
}
/**
* Return the hash algorithm used to hash the data to sign.
*
* @return integer
*/
public function getHashAlgorithm()
{
return $this->hashAlgorithm;
}
/**
* Get a name for the used hashing algorithm.
*
* @return string|null
*/
public function getHashAlgorithmName()
{
if (!isset(self::$hashAlgorithmNames[$this->hashAlgorithm])) {
return null;
}
return self::$hashAlgorithmNames[$this->hashAlgorithm];
}
/**
* Return the timestamp at which the signature was created
*
* @return integer
*/
public function getTimestamp()
{
return $this->timestamp;
}
/**
* Return the key's fingerprint
*
* @return string
*/
public function getKeyFingerprint()
{
return $this->keyFingerprint;
}
/**
* Tell if the fingerprint line given to the constructor was valid
*
* @return boolean
*/
public function isValid()
{
return $this->valid;
}
}
?>

View File

@@ -0,0 +1,715 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Contains a class representing GPG sub-keys and constants for GPG algorithms
*
* PHP version 5
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @author Nathan Fredrickson <nathan@silverorange.com>
* @copyright 2005-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
// {{{ class Crypt_GPG_SubKey
/**
* A class for GPG sub-key information
*
* This class is used to store the results of the {@link Crypt_GPG::getKeys()}
* method. Sub-key objects are members of a {@link Crypt_GPG_Key} object.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @author Nathan Fredrickson <nathan@silverorange.com>
* @copyright 2005-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @see Crypt_GPG::getKeys()
* @see Crypt_GPG_Key::getSubKeys()
*/
class Crypt_GPG_SubKey
{
// {{{ algorithm class constants
/**
* RSA encryption algorithm.
*/
const ALGORITHM_RSA = 1;
/**
* Elgamal encryption algorithm (encryption only).
*/
const ALGORITHM_ELGAMAL_ENC = 16;
/**
* DSA encryption algorithm (sometimes called DH, sign only).
*/
const ALGORITHM_DSA = 17;
/**
* Elgamal encryption algorithm (signage and encryption - should not be
* used).
*/
const ALGORITHM_ELGAMAL_ENC_SGN = 20;
// }}}
// {{{ 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
/**
* The id of this sub-key
*
* @var string
*/
private $_id = '';
/**
* The algorithm used to create this sub-key
*
* The value is one of the Crypt_GPG_SubKey::ALGORITHM_* constants.
*
* @var integer
*/
private $_algorithm = 0;
/**
* The fingerprint of this sub-key
*
* @var string
*/
private $_fingerprint = '';
/**
* Length of this sub-key in bits
*
* @var integer
*/
private $_length = 0;
/**
* Date this sub-key was created
*
* This is a Unix timestamp.
*
* @var integer
*/
private $_creationDate = 0;
/**
* Date this sub-key expires
*
* This is a Unix timestamp. If this sub-key does not expire, this will be
* zero.
*
* @var integer
*/
private $_expirationDate = 0;
/**
* Contains usage flags of this sub-key
*
* @var int
*/
private $_usage = 0;
/**
* Whether or not the private key for this sub-key exists in the keyring
*
* @var boolean
*/
private $_hasPrivate = false;
/**
* Whether or not this sub-key is revoked
*
* @var boolean
*/
private $_isRevoked = false;
// }}}
// {{{ __construct()
/**
* Creates a new sub-key object
*
* Sub-keys can be initialized from an array of named values. Available
* names are:
*
* - <kbd>string id</kbd> - the key id of the sub-key.
* - <kbd>integer algorithm</kbd> - the encryption algorithm of the
* sub-key.
* - <kbd>string fingerprint</kbd> - the fingerprint of the sub-key. The
* fingerprint should not contain
* formatting characters.
* - <kbd>integer length</kbd> - the length of the sub-key in bits.
* - <kbd>integer creation</kbd> - the date the sub-key was created.
* This is a UNIX timestamp.
* - <kbd>integer expiration</kbd> - the date the sub-key expires. This
* is a UNIX timestamp. If the sub-key
* does not expire, use 0.
* - <kbd>boolean canSign</kbd> - whether or not the sub-key can be
* used to sign data.
* - <kbd>boolean canEncrypt</kbd> - whether or not the sub-key can be
* used to encrypt data.
* - <kbd>integer usage</kbd> - the sub-key usage flags
* - <kbd>boolean hasPrivate</kbd> - whether or not the private key for
* the sub-key exists in the keyring.
* - <kbd>boolean isRevoked</kbd> - whether or not this sub-key is
* revoked.
*
* @param Crypt_GPG_SubKey|string|array $key optional. Either an existing
* sub-key object, which is copied; a sub-key string, which is
* parsed; or an array of initial values.
*/
public function __construct($key = null)
{
// parse from string
if (is_string($key)) {
$key = self::parse($key);
}
// copy from object
if ($key instanceof Crypt_GPG_SubKey) {
$this->_id = $key->_id;
$this->_algorithm = $key->_algorithm;
$this->_fingerprint = $key->_fingerprint;
$this->_length = $key->_length;
$this->_creationDate = $key->_creationDate;
$this->_expirationDate = $key->_expirationDate;
$this->_usage = $key->_usage;
$this->_hasPrivate = $key->_hasPrivate;
$this->_isRevoked = $key->_isRevoked;
}
// initialize from array
if (is_array($key)) {
if (array_key_exists('id', $key)) {
$this->setId($key['id']);
}
if (array_key_exists('algorithm', $key)) {
$this->setAlgorithm($key['algorithm']);
}
if (array_key_exists('fingerprint', $key)) {
$this->setFingerprint($key['fingerprint']);
}
if (array_key_exists('length', $key)) {
$this->setLength($key['length']);
}
if (array_key_exists('creation', $key)) {
$this->setCreationDate($key['creation']);
}
if (array_key_exists('expiration', $key)) {
$this->setExpirationDate($key['expiration']);
}
if (array_key_exists('usage', $key)) {
$this->setUsage($key['usage']);
}
if (array_key_exists('canSign', $key)) {
$this->setCanSign($key['canSign']);
}
if (array_key_exists('canEncrypt', $key)) {
$this->setCanEncrypt($key['canEncrypt']);
}
if (array_key_exists('hasPrivate', $key)) {
$this->setHasPrivate($key['hasPrivate']);
}
if (array_key_exists('isRevoked', $key)) {
$this->setRevoked($key['isRevoked']);
}
}
}
// }}}
// {{{ getId()
/**
* Gets the id of this sub-key
*
* @return string the id of this sub-key.
*/
public function getId()
{
return $this->_id;
}
// }}}
// {{{ getAlgorithm()
/**
* Gets the algorithm used by this sub-key
*
* The algorithm should be one of the Crypt_GPG_SubKey::ALGORITHM_*
* constants.
*
* @return integer the algorithm used by this sub-key.
*/
public function getAlgorithm()
{
return $this->_algorithm;
}
// }}}
// {{{ getCreationDate()
/**
* Gets the creation date of this sub-key
*
* This is a Unix timestamp.
*
* @return integer the creation date of this sub-key.
*/
public function getCreationDate()
{
return $this->_creationDate;
}
// }}}
// {{{ getExpirationDate()
/**
* Gets the date this sub-key expires
*
* This is a Unix timestamp. If this sub-key does not expire, this will be
* zero.
*
* @return integer the date this sub-key expires.
*/
public function getExpirationDate()
{
return $this->_expirationDate;
}
// }}}
// {{{ getFingerprint()
/**
* Gets the fingerprint of this sub-key
*
* @return string the fingerprint of this sub-key.
*/
public function getFingerprint()
{
return $this->_fingerprint;
}
// }}}
// {{{ getLength()
/**
* Gets the length of this sub-key in bits
*
* @return integer the length of this sub-key in bits.
*/
public function getLength()
{
return $this->_length;
}
// }}}
// {{{ canSign()
/**
* Gets whether or not this sub-key can sign data
*
* @return boolean true if this sub-key can sign data and false if this
* sub-key can not sign data.
*/
public function canSign()
{
return ($this->_usage & self::USAGE_SIGN) != 0;
}
// }}}
// {{{ canEncrypt()
/**
* Gets whether or not this sub-key can encrypt data
*
* @return boolean true if this sub-key can encrypt data and false if this
* sub-key can not encrypt data.
*/
public function canEncrypt()
{
return ($this->_usage & self::USAGE_ENCRYPT) != 0;
}
// }}}
// {{{ usage()
/**
* Gets usage flags of this sub-key
*
* @return int Sum of usage flags
*/
public function usage()
{
return $this->_usage;
}
// }}}
// {{{ hasPrivate()
/**
* Gets whether or not the private key for this sub-key exists in the
* keyring
*
* @return boolean true the private key for this sub-key exists in the
* keyring and false if it does not.
*/
public function hasPrivate()
{
return $this->_hasPrivate;
}
// }}}
// {{{ isRevoked()
/**
* Gets whether or not this sub-key is revoked
*
* @return boolean true if this sub-key is revoked and false if it is not.
*/
public function isRevoked()
{
return $this->_isRevoked;
}
// }}}
// {{{ setCreationDate()
/**
* Sets the creation date of this sub-key
*
* The creation date is a Unix timestamp.
*
* @param integer $creationDate the creation date of this sub-key.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setCreationDate($creationDate)
{
$this->_creationDate = intval($creationDate);
return $this;
}
// }}}
// {{{ setExpirationDate()
/**
* Sets the expiration date of this sub-key
*
* The expiration date is a Unix timestamp. Specify zero if this sub-key
* does not expire.
*
* @param integer $expirationDate the expiration date of this sub-key.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setExpirationDate($expirationDate)
{
$this->_expirationDate = intval($expirationDate);
return $this;
}
// }}}
// {{{ setId()
/**
* Sets the id of this sub-key
*
* @param string $id the id of this sub-key.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setId($id)
{
$this->_id = strval($id);
return $this;
}
// }}}
// {{{ setAlgorithm()
/**
* Sets the algorithm used by this sub-key
*
* @param integer $algorithm the algorithm used by this sub-key.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setAlgorithm($algorithm)
{
$this->_algorithm = intval($algorithm);
return $this;
}
// }}}
// {{{ setFingerprint()
/**
* Sets the fingerprint of this sub-key
*
* @param string $fingerprint the fingerprint of this sub-key.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setFingerprint($fingerprint)
{
$this->_fingerprint = strval($fingerprint);
return $this;
}
// }}}
// {{{ setLength()
/**
* Sets the length of this sub-key in bits
*
* @param integer $length the length of this sub-key in bits.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setLength($length)
{
$this->_length = intval($length);
return $this;
}
// }}}
// {{{ setCanSign()
/**
* Sets whether or not this sub-key can sign data
*
* @param boolean $canSign true if this sub-key can sign data and false if
* it can not.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setCanSign($canSign)
{
if ($canSign) {
$this->_usage |= self::USAGE_SIGN;
} else {
$this->_usage &= ~self::USAGE_SIGN;
}
return $this;
}
// }}}
// {{{ setCanEncrypt()
/**
* Sets whether or not this sub-key can encrypt data
*
* @param boolean $canEncrypt true if this sub-key can encrypt data and
* false if it can not.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setCanEncrypt($canEncrypt)
{
if ($canEncrypt) {
$this->_usage |= self::USAGE_ENCRYPT;
} else {
$this->_usage &= ~self::USAGE_ENCRYPT;
}
return $this;
}
// }}}
// {{{ setUsage()
/**
* Sets usage flags of the sub-key
*
* @param integer $usage Usage flags
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setUsage($usage)
{
$this->_usage = (int) $usage;
return $this;
}
// }}}
// {{{ setHasPrivate()
/**
* Sets whether of not the private key for this sub-key exists in the
* keyring
*
* @param boolean $hasPrivate true if the private key for this sub-key
* exists in the keyring and false if it does
* not.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setHasPrivate($hasPrivate)
{
$this->_hasPrivate = ($hasPrivate) ? true : false;
return $this;
}
// }}}
// {{{ setRevoked()
/**
* Sets whether or not this sub-key is revoked
*
* @param boolean $isRevoked whether or not this sub-key is revoked.
*
* @return Crypt_GPG_SubKey the current object, for fluent interface.
*/
public function setRevoked($isRevoked)
{
$this->_isRevoked = ($isRevoked) ? true : false;
return $this;
}
// }}}
// {{{ parse()
/**
* Parses a sub-key object from a sub-key string
*
* See <b>doc/DETAILS</b> in the
* {@link http://www.gnupg.org/download/ GPG distribution} for information
* on how the sub-key string is parsed.
*
* @param string $string the string containing the sub-key.
*
* @return Crypt_GPG_SubKey the sub-key object parsed from the string.
*/
public static function parse($string)
{
$tokens = explode(':', $string);
$subKey = new Crypt_GPG_SubKey();
$subKey->setId($tokens[4]);
$subKey->setLength($tokens[2]);
$subKey->setAlgorithm($tokens[3]);
$subKey->setCreationDate(self::_parseDate($tokens[5]));
$subKey->setExpirationDate(self::_parseDate($tokens[6]));
if ($tokens[1] == 'r') {
$subKey->setRevoked(true);
}
$usage = 0;
$usage_map = array(
'a' => self::USAGE_AUTHENTICATION,
'c' => self::USAGE_CERTIFY,
'e' => self::USAGE_ENCRYPT,
's' => self::USAGE_SIGN,
);
foreach ($usage_map as $key => $flag) {
if (strpos($tokens[11], $key) !== false) {
$usage |= $flag;
}
}
$subKey->setUsage($usage);
return $subKey;
}
// }}}
// {{{ _parseDate()
/**
* Parses a date string as provided by GPG into a UNIX timestamp
*
* @param string $string the date string.
*
* @return integer the UNIX timestamp corresponding to the provided date
* string.
*/
private static function _parseDate($string)
{
if ($string == '') {
$timestamp = 0;
} else {
// all times are in UTC according to GPG documentation
$timeZone = new DateTimeZone('UTC');
if (strpos($string, 'T') === false) {
// interpret as UNIX timestamp
$string = '@' . $string;
}
$date = new DateTime($string, $timeZone);
// convert to UNIX timestamp
$timestamp = intval($date->format('U'));
}
return $timestamp;
}
// }}}
}
// }}}
?>

View File

@@ -0,0 +1,378 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Contains a data class representing a GPG user id
*
* PHP version 5
*
* LICENSE:
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/>
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
*/
// {{{ class Crypt_GPG_UserId
/**
* A class for GPG user id information
*
* This class is used to store the results of the {@link Crypt_GPG::getKeys()}
* method. User id objects are members of a {@link Crypt_GPG_Key} object.
*
* @category Encryption
* @package Crypt_GPG
* @author Michael Gauthier <mike@silverorange.com>
* @copyright 2008-2010 silverorange
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
* @link http://pear.php.net/package/Crypt_GPG
* @see Crypt_GPG::getKeys()
* @see Crypt_GPG_Key::getUserIds()
*/
class Crypt_GPG_UserId
{
// {{{ class properties
/**
* The name field of this user id
*
* @var string
*/
private $_name = '';
/**
* The comment field of this user id
*
* @var string
*/
private $_comment = '';
/**
* The email field of this user id
*
* @var string
*/
private $_email = '';
/**
* Whether or not this user id is revoked
*
* @var boolean
*/
private $_isRevoked = false;
/**
* Whether or not this user id is valid
*
* @var boolean
*/
private $_isValid = true;
// }}}
// {{{ __construct()
/**
* Creates a new user id
*
* User ids can be initialized from an array of named values. Available
* names are:
*
* - <kbd>string name</kbd> - the name field of the user id.
* - <kbd>string comment</kbd> - the comment field of the user id.
* - <kbd>string email</kbd> - the email field of the user id.
* - <kbd>boolean valid</kbd> - whether or not the user id is valid.
* - <kbd>boolean revoked</kbd> - whether or not the user id is revoked.
*
* @param Crypt_GPG_UserId|string|array $userId optional. Either an
* existing user id object, which is copied; a user id string, which
* is parsed; or an array of initial values.
*/
public function __construct($userId = null)
{
// parse from string
if (is_string($userId)) {
$userId = self::parse($userId);
}
// copy from object
if ($userId instanceof Crypt_GPG_UserId) {
$this->_name = $userId->_name;
$this->_comment = $userId->_comment;
$this->_email = $userId->_email;
$this->_isRevoked = $userId->_isRevoked;
$this->_isValid = $userId->_isValid;
}
// initialize from array
if (is_array($userId)) {
if (array_key_exists('name', $userId)) {
$this->setName($userId['name']);
}
if (array_key_exists('comment', $userId)) {
$this->setComment($userId['comment']);
}
if (array_key_exists('email', $userId)) {
$this->setEmail($userId['email']);
}
if (array_key_exists('revoked', $userId)) {
$this->setRevoked($userId['revoked']);
}
if (array_key_exists('valid', $userId)) {
$this->setValid($userId['valid']);
}
}
}
// }}}
// {{{ getName()
/**
* Gets the name field of this user id
*
* @return string the name field of this user id.
*/
public function getName()
{
return $this->_name;
}
// }}}
// {{{ getComment()
/**
* Gets the comments field of this user id
*
* @return string the comments field of this user id.
*/
public function getComment()
{
return $this->_comment;
}
// }}}
// {{{ getEmail()
/**
* Gets the email field of this user id
*
* @return string the email field of this user id.
*/
public function getEmail()
{
return $this->_email;
}
// }}}
// {{{ isRevoked()
/**
* Gets whether or not this user id is revoked
*
* @return boolean true if this user id is revoked and false if it is not.
*/
public function isRevoked()
{
return $this->_isRevoked;
}
// }}}
// {{{ isValid()
/**
* Gets whether or not this user id is valid
*
* @return boolean true if this user id is valid and false if it is not.
*/
public function isValid()
{
return $this->_isValid;
}
// }}}
// {{{ __toString()
/**
* Gets a string representation of this user id
*
* The string is formatted as:
* <b><kbd>name (comment) <email-address></kbd></b>.
*
* @return string a string representation of this user id.
*/
public function __toString()
{
$components = array();
if (mb_strlen($this->_name, '8bit') > 0) {
$components[] = $this->_name;
}
if (mb_strlen($this->_comment, '8bit') > 0) {
$components[] = '(' . $this->_comment . ')';
}
if (mb_strlen($this->_email, '8bit') > 0) {
$components[] = '<' . $this->_email. '>';
}
return implode(' ', $components);
}
// }}}
// {{{ setName()
/**
* Sets the name field of this user id
*
* @param string $name the name field of this user id.
*
* @return Crypt_GPG_UserId the current object, for fluent interface.
*/
public function setName($name)
{
$this->_name = strval($name);
return $this;
}
// }}}
// {{{ setComment()
/**
* Sets the comment field of this user id
*
* @param string $comment the comment field of this user id.
*
* @return Crypt_GPG_UserId the current object, for fluent interface.
*/
public function setComment($comment)
{
$this->_comment = strval($comment);
return $this;
}
// }}}
// {{{ setEmail()
/**
* Sets the email field of this user id
*
* @param string $email the email field of this user id.
*
* @return Crypt_GPG_UserId the current object, for fluent interface.
*/
public function setEmail($email)
{
$this->_email = strval($email);
return $this;
}
// }}}
// {{{ setRevoked()
/**
* Sets whether or not this user id is revoked
*
* @param boolean $isRevoked whether or not this user id is revoked.
*
* @return Crypt_GPG_UserId the current object, for fluent interface.
*/
public function setRevoked($isRevoked)
{
$this->_isRevoked = ($isRevoked) ? true : false;
return $this;
}
// }}}
// {{{ setValid()
/**
* Sets whether or not this user id is valid
*
* @param boolean $isValid whether or not this user id is valid.
*
* @return Crypt_GPG_UserId the current object, for fluent interface.
*/
public function setValid($isValid)
{
$this->_isValid = ($isValid) ? true : false;
return $this;
}
// }}}
// {{{ parse()
/**
* Parses a user id object from a user id string
*
* A user id string is of the form:
* <b><kbd>name (comment) <email-address></kbd></b> with the <i>comment</i>
* and <i>email-address</i> fields being optional.
*
* @param string $string the user id string to parse.
*
* @return Crypt_GPG_UserId the user id object parsed from the string.
*/
public static function parse($string)
{
$userId = new Crypt_GPG_UserId();
$name = '';
$email = '';
$comment = '';
// get email address from end of string if it exists
$matches = array();
if (preg_match('/^(.*?)<([^>]+)>$/', $string, $matches) === 1) {
$string = trim($matches[1]);
$email = $matches[2];
}
// get comment from end of string if it exists
$matches = array();
if (preg_match('/^(.+?) \(([^\)]+)\)$/', $string, $matches) === 1) {
$string = $matches[1];
$comment = $matches[2];
}
// there can be an email without a name
if (!$email && preg_match('/^[\S]+@[\S]+$/', $string, $matches) === 1) {
$email = $string;
} else {
$name = $string;
}
$userId->setName($name);
$userId->setComment($comment);
$userId->setEmail($email);
return $userId;
}
// }}}
}
// }}}
?>

View File

@@ -0,0 +1,448 @@
<?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, see
* <http://www.gnu.org/licenses/>
*
* @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://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';
// {{{ 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;
}
// }}}
// {{{ getVersion()
/**
* Returns version of the engine (GnuPG) used for operation.
*
* @return string GnuPG version.
*
* @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 getVersion()
{
return $this->engine->getVersion();
}
// }}}
// {{{ _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 = '--utf8-strings --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();
$privateKeyFingerprints = array();
foreach (explode(PHP_EOL, $output) as $line) {
$lineExp = explode(':', $line);
if ($lineExp[0] == 'fpr') {
$privateKeyFingerprints[] = $lineExp[9];
}
}
// get public keys
if ($keyId == '') {
$operation = '--list-public-keys';
} else {
$operation = '--utf8-strings --list-public-keys ' . escapeshellarg($keyId);
}
$output = '';
$this->engine->reset();
$this->engine->setOutput($output);
$this->engine->setOperation($operation, $arguments);
$this->engine->run();
$keys = array();
$key = null; // current key
$subKey = null; // current sub-key
foreach (explode(PHP_EOL, $output) 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;
}
// }}}
}
// }}}
?>