From 3e98f8be718578644bb15ee6a992a875f6468e8f Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 27 Dec 2013 13:14:40 +0100 Subject: Add some code for S/MIME signatures verification, update Crypt_GPG package --- program/lib/Crypt/GPG/KeyGenerator.php | 790 +++++++++++++++++++++++++++++++++ 1 file changed, 790 insertions(+) create mode 100644 program/lib/Crypt/GPG/KeyGenerator.php (limited to 'program/lib/Crypt/GPG/KeyGenerator.php') diff --git a/program/lib/Crypt/GPG/KeyGenerator.php b/program/lib/Crypt/GPG/KeyGenerator.php new file mode 100644 index 000000000..f59c0ee3a --- /dev/null +++ b/program/lib/Crypt/GPG/KeyGenerator.php @@ -0,0 +1,790 @@ + + * @copyright 2011-2013 silverorange + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version CVS: $Id:$ + * @link http://pear.php.net/package/Crypt_GPG + * @link http://www.gnupg.org/ + */ + +/** + * Base class for GPG methods + */ +require_once 'Crypt/GPGAbstract.php'; + +/** + * Status output handler for key generation + */ +require_once 'Crypt/GPG/KeyGeneratorStatusHandler.php'; + +/** + * Error output handler for key generation + */ +require_once 'Crypt/GPG/KeyGeneratorErrorHandler.php'; + +// {{{ class Crypt_GPG_KeyGenerator + +/** + * GnuPG key generator + * + * This class provides an object oriented interface for generating keys with + * the GNU Privacy Guard (GPG). + * + * Secure key generation requires true random numbers, and as such can be slow. + * If the operating system runs out of entropy, key generation will block until + * more entropy is available. + * + * If quick key generation is important, a hardware entropy generator, or an + * entropy gathering daemon may be installed. For example, administrators of + * Debian systems may want to install the 'randomsound' package. + * + * This class uses the experimental automated key generation support available + * in GnuPG. See doc/DETAILS 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 + * @author Michael Gauthier + * @copyright 2005-2013 silverorange + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @link http://pear.php.net/package/Crypt_GPG + * @link http://www.gnupg.org/ + */ +class Crypt_GPG_KeyGenerator extends Crypt_GPGAbstract +{ + // {{{ protected properties + + /** + * The expiration date of generated keys + * + * @var integer + * + * @see Crypt_GPG_KeyGenerator::setExpirationDate() + */ + protected $expirationDate = 0; + + /** + * The passphrase of generated keys + * + * @var string + * + * @see Crypt_GPG_KeyGenerator::setPassphrase() + */ + protected $passphrase = ''; + + /** + * The algorithm for generated primary keys + * + * @var integer + * + * @see Crypt_GPG_KeyGenerator::setKeyParams() + */ + protected $keyAlgorithm = Crypt_GPG_SubKey::ALGORITHM_DSA; + + /** + * The size of generated primary keys + * + * @var integer + * + * @see Crypt_GPG_KeyGenerator::setKeyParams() + */ + protected $keySize = 1024; + + /** + * The usages of generated primary keys + * + * This is a bitwise combination of the usage constants in + * {@link Crypt_GPG_SubKey}. + * + * @var integer + * + * @see Crypt_GPG_KeyGenerator::setKeyParams() + */ + protected $keyUsage = 6; // USAGE_SIGN | USAGE_CERTIFY + + /** + * The algorithm for generated sub-keys + * + * @var integer + * + * @see Crypt_GPG_KeyGenerator::setSubKeyParams() + */ + protected $subKeyAlgorithm = Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC; + + /** + * The size of generated sub-keys + * + * @var integer + * + * @see Crypt_GPG_KeyGenerator::setSubKeyParams() + */ + protected $subKeySize = 2048; + + /** + * The usages of generated sub-keys + * + * This is a bitwise combination of the usage constants in + * {@link Crypt_GPG_SubKey}. + * + * @var integer + * + * @see Crypt_GPG_KeyGenerator::setSubKeyParams() + */ + protected $subKeyUsage = Crypt_GPG_SubKey::USAGE_ENCRYPT; + + /** + * The GnuPG status handler to use for key generation + * + * @var Crypt_GPG_KeyGeneratorStatusHandler + * + * @see Crypt_GPG_KeyGenerator::setStatusHandler() + */ + protected $statusHandler = null; + + /** + * The GnuPG error handler to use for key generation + * + * @var Crypt_GPG_KeyGeneratorErrorHandler + * + * @see Crypt_GPG_KeyGenerator::setErrorHandler() + */ + protected $errorHandler = null; + + // }}} + // {{{ __construct() + + /** + * Creates a new GnuPG key generator + * + * Available options are: + * + * - string homedir - the directory where the GPG + * keyring files are stored. If not + * specified, Crypt_GPG uses the + * default of ~/.gnupg. + * - string publicKeyring - 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 homedir option. + * - string privateKeyring - 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 homedir option. + * - string trustDb - 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 homedir option. + * - string binary - 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 + * gpgBinary is a + * deprecated alias for this option. + * - string agent - 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. + * - boolean debug - 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 homedir does not exist + * and cannot be created. This can happen if homedir 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 publicKeyring, + * privateKeyring or trustDb 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 binary is invalid, or + * if no binary is provided and no suitable binary could + * be found. + * + * @throws PEAR_Exception if the provided agent is invalid, or + * if no agent is provided and no suitable gpg-agent + * cound be found. + */ + public function __construct(array $options = array()) + { + parent::__construct($options); + + $this->statusHandler = new Crypt_GPG_KeyGeneratorStatusHandler(); + $this->errorHandler = new Crypt_GPG_KeyGeneratorErrorHandler(); + } + + // }}} + // {{{ setExpirationDate() + + /** + * Sets the expiration date of generated keys + * + * @param string|integer $date either a string that may be parsed by + * PHP's strtotime() function, or an integer + * timestamp representing the number of seconds + * since the UNIX epoch. This date must be at + * least one date in the future. Keys that + * expire in the past may not be generated. Use + * an expiration date of 0 for keys that do not + * expire. + * + * @throws InvalidArgumentException if the date is not a valid format, or + * if the date is not at least one day in + * the future, or if the date is greater + * than 2038-01-19T03:14:07. + * + * @return Crypt_GPG_KeyGenerator the current object, for fluent interface. + */ + public function setExpirationDate($date) + { + if (is_int($date) || ctype_digit(strval($date))) { + $expirationDate = intval($date); + } else { + $expirationDate = strtotime($date); + } + + if ($expirationDate === false) { + throw new InvalidArgumentException( + sprintf( + 'Invalid expiration date format: "%s". Please use a ' . + 'format compatible with PHP\'s strtotime().', + $date + ) + ); + } + + if ($expirationDate !== 0 && $expirationDate < time() + 86400) { + throw new InvalidArgumentException( + 'Expiration date must be at least a day in the future.' + ); + } + + // GnuPG suffers from the 2038 bug + if ($expirationDate > 2147483647) { + throw new InvalidArgumentException( + 'Expiration date must not be greater than 2038-01-19T03:14:07.' + ); + } + + $this->expirationDate = $expirationDate; + + return $this; + } + + // }}} + // {{{ setPassphrase() + + /** + * Sets the passphrase of generated keys + * + * @param string $passphrase the passphrase to use for generated keys. Use + * null or an empty string for no passphrase. + * + * @return Crypt_GPG_KeyGenerator the current object, for fluent interface. + */ + public function setPassphrase($passphrase) + { + $this->passphrase = strval($passphrase); + return $this; + } + + // }}} + // {{{ setKeyParams() + + /** + * Sets the parameters for the primary key of generated key-pairs + * + * @param integer $algorithm the algorithm used by the key. This should be + * one of the Crypt_GPG_SubKey::ALGORITHM_* + * constants. + * @param integer $size optional. The size of the key. Different + * algorithms have different size requirements. + * If not specified, the default size for the + * specified algorithm will be used. If an + * invalid key size is used, GnuPG will do its + * best to round it to a valid size. + * @param integer $usage optional. A bitwise combination of key usages. + * If not specified, the primary key will be used + * only to sign and certify. This is the default + * behavior of GnuPG in interactive mode. Use + * the Crypt_GPG_SubKey::USAGE_* constants here. + * The primary key may be used to certify even + * if the certify usage is not specified. + * + * @return Crypt_GPG_KeyGenerator the current object, for fluent interface. + */ + public function setKeyParams($algorithm, $size = 0, $usage = 0) + { + $apgorithm = intval($algorithm); + + if ($algorithm === Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC) { + throw new Crypt_GPG_InvalidKeyParamsException( + 'Primary key algorithm must be capable of signing. The ' . + 'Elgamal algorithm can only encrypt.', + 0, + $algorithm, + $size, + $usage + ); + } + + if ($size != 0) { + $size = intval($size); + } + + if ($usage != 0) { + $usage = intval($usage); + } + + $usageEncrypt = Crypt_GPG_SubKey::USAGE_ENCRYPT; + + if ( $algorithm === Crypt_GPG_SubKey::ALGORITHM_DSA + && ($usage & $usageEncrypt) === $usageEncrypt + ) { + throw new Crypt_GPG_InvalidKeyParamsException( + 'The DSA algorithm is not capable of encrypting. Please ' . + 'specify a different algorithm or do not include encryption ' . + 'as a usage for the primary key.', + 0, + $algorithm, + $size, + $usage + ); + } + + $this->keyAlgorithm = $algorithm; + + if ($size != 0) { + $this->keySize = $size; + } + + if ($usage != 0) { + $this->keyUsage = $usage; + } + + return $this; + } + + // }}} + // {{{ setSubKeyParams() + + /** + * Sets the parameters for the sub-key of generated key-pairs + * + * @param integer $algorithm the algorithm used by the key. This should be + * one of the Crypt_GPG_SubKey::ALGORITHM_* + * constants. + * @param integer $size optional. The size of the key. Different + * algorithms have different size requirements. + * If not specified, the default size for the + * specified algorithm will be used. If an + * invalid key size is used, GnuPG will do its + * best to round it to a valid size. + * @param integer $usage optional. A bitwise combination of key usages. + * If not specified, the sub-key will be used + * only to encrypt. This is the default behavior + * of GnuPG in interactive mode. Use the + * Crypt_GPG_SubKey::USAGE_* constants here. + * + * @return Crypt_GPG_KeyGenerator the current object, for fluent interface. + */ + public function setSubKeyParams($algorithm, $size = '', $usage = 0) + { + $apgorithm = intval($algorithm); + + if ($size != 0) { + $size = intval($size); + } + + if ($usage != 0) { + $usage = intval($usage); + } + + $usageSign = Crypt_GPG_SubKey::USAGE_SIGN; + + if ( $algorithm === Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC + && ($usage & $usageSign) === $usageSign + ) { + throw new Crypt_GPG_InvalidKeyParamsException( + 'The Elgamal algorithm is not capable of signing. Please ' . + 'specify a different algorithm or do not include signing ' . + 'as a usage for the sub-key.', + 0, + $algorithm, + $size, + $usage + ); + } + + $usageEncrypt = Crypt_GPG_SubKey::USAGE_ENCRYPT; + + if ( $algorithm === Crypt_GPG_SubKey::ALGORITHM_DSA + && ($usage & $usageEncrypt) === $usageEncrypt + ) { + throw new Crypt_GPG_InvalidKeyParamsException( + 'The DSA algorithm is not capable of encrypting. Please ' . + 'specify a different algorithm or do not include encryption ' . + 'as a usage for the sub-key.', + 0, + $algorithm, + $size, + $usage + ); + } + + $this->subKeyAlgorithm = $algorithm; + + if ($size != 0) { + $this->subKeySize = $size; + } + + if ($usage != 0) { + $this->subKeyUsage = $usage; + } + + return $this; + } + + // }}} + // {{{ setStatusHandler() + + /** + * Sets the status handler to use for key generation + * + * Normally this method does not need to be used. It provides a means for + * dependency injection. + * + * @param Crypt_GPG_KeyStatusHandler $handler the key status handler to + * use. + * + * @return Crypt_GPG_KeyGenerator the current object, for fluent interface. + */ + public function setStatusHandler( + Crypt_GPG_KeyGeneratorStatusHandler $handler + ) { + $this->statusHandler = $handler; + return $this; + } + + // }}} + // {{{ setErrorHandler() + + /** + * Sets the error handler to use for key generation + * + * Normally this method does not need to be used. It provides a means for + * dependency injection. + * + * @param Crypt_GPG_KeyErrorHandler $handler the key error handler to + * use. + * + * @return Crypt_GPG_KeyGenerator the current object, for fluent interface. + */ + public function setErrorHandler( + Crypt_GPG_KeyGeneratorErrorHandler $handler + ) { + $this->errorHandler = $handler; + return $this; + } + + // }}} + // {{{ generateKey() + + /** + * Generates a new key-pair in the current keyring + * + * Secure key generation requires true random numbers, and as such can be + * solw. If the operating system runs out of entropy, key generation will + * block until more entropy is available. + * + * If quick key generation is important, a hardware entropy generator, or + * an entropy gathering daemon may be installed. For example, + * administrators of Debian systems may want to install the 'randomsound' + * package. + * + * @param string|Crypt_GPG_UserId $name either a {@link Crypt_GPG_UserId} + * object, or a string containing + * the name of the user id. + * @param string $email optional. If $name is + * specified as a string, this is + * the email address of the user id. + * @param string $comment optional. If $name 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 debug option and file a bug report if these + * exceptions occur. + */ + public function generateKey($name, $email = '', $comment = '') + { + $handle = uniqid('key', true); + + $userId = $this->getUserId($name, $email, $comment); + + $keyParams = array( + 'Key-Type' => $this->keyAlgorithm, + 'Key-Length' => $this->keySize, + 'Key-Usage' => $this->getUsage($this->keyUsage), + 'Subkey-Type' => $this->subKeyAlgorithm, + 'Subkey-Length' => $this->subKeySize, + 'Subkey-Usage' => $this->getUsage($this->subKeyUsage), + 'Name-Real' => $userId->getName(), + 'Handle' => $handle, + ); + + if ($this->expirationDate != 0) { + // GnuPG only accepts granularity of days + $expirationDate = date('Y-m-d', $this->expirationDate); + $keyParams['Expire-Date'] = $expirationDate; + } + + if ($this->passphrase != '') { + $keyParams['Passphrase'] = $this->passphrase; + } + + if ($userId->getEmail() != '') { + $keyParams['Name-Email'] = $userId->getEmail(); + } + + if ($userId->getComment() != '') { + $keyParams['Name-Comment'] = $userId->getComment(); + } + + + $keyParamsFormatted = array(); + foreach ($keyParams as $name => $value) { + $keyParamsFormatted[] = $name . ': ' . $value; + } + + $input = implode("\n", $keyParamsFormatted) . "\n%commit\n"; + + $statusHandler = clone $this->statusHandler; + $statusHandler->setHandle($handle); + + $errorHandler = clone $this->errorHandler; + + $this->engine->reset(); + $this->engine->addStatusHandler(array($statusHandler, 'handle')); + $this->engine->addErrorHandler(array($errorHandler, 'handle')); + $this->engine->setInput($input); + $this->engine->setOutput($output); + $this->engine->setOperation('--gen-key', array('--batch')); + $this->engine->run(); + + $code = $errorHandler->getErrorCode(); + switch ($code) { + case self::ERROR_BAD_KEY_PARAMS: + switch ($errorHandler->getLineNumber()) { + case 1: + throw new Crypt_GPG_InvalidKeyParamsException( + 'Invalid primary key algorithm specified.', + 0, + $this->keyAlgorithm, + $this->keySize, + $this->keyUsage + ); + case 4: + throw new Crypt_GPG_InvalidKeyParamsException( + 'Invalid sub-key algorithm specified.', + 0, + $this->subKeyAlgorithm, + $this->subKeySize, + $this->subKeyUsage + ); + default: + throw new Crypt_GPG_InvalidKeyParamsException( + 'Invalid key algorithm specified.' + ); + } + } + + $code = $this->engine->getErrorCode(); + + switch ($code) { + case self::ERROR_NONE: + break; + default: + throw new Crypt_GPG_Exception( + 'Unknown error generating key-pair. Please use the \'debug\' ' . + 'option when creating the Crypt_GPG object, and file a bug ' . + 'report at ' . self::BUG_URI, + $code + ); + } + + $code = $statusHandler->getErrorCode(); + + switch ($code) { + case self::ERROR_NONE: + break; + case self::ERROR_KEY_NOT_CREATED: + throw new Crypt_GPG_KeyNotCreatedException( + 'Unable to create new key-pair. Invalid key parameters. ' . + 'Make sure the specified key algorithms and sizes are ' . + 'correct.', + $code + ); + } + + $fingerprint = $statusHandler->getKeyFingerprint(); + $keys = $this->_getKeys($fingerprint); + + if (count($keys) === 0) { + throw new Crypt_GPG_KeyNotCreatedException( + sprintf( + 'Newly created key "%s" not found in keyring.', + $fingerprint + ) + ); + } + + return $keys[0]; + } + + // }}} + // {{{ getUsage() + + /** + * Builds a GnuPG key usage string suitable for key generation + * + * See doc/DETAILS 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 $name is + * specified as a string, this is + * the email address of the user id. + * @param string $comment optional. If $name 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; + } + + // }}} +} + +// }}} + +?> -- cgit v1.2.3