123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683 |
- <?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;
- }
-
- // }}}
- }
-
- // }}}
-
- ?>
|