1<?php
2
3/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5/**
6 * Crypt_GPG is a package to use GPG from PHP
7 *
8 * This file contains an object that handles GnuPG key generation.
9 *
10 * LICENSE:
11 *
12 * This library is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU Lesser General Public License as
14 * published by the Free Software Foundation; either version 2.1 of the
15 * License, or (at your option) any later version.
16 *
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this library; if not, see
24 * <http://www.gnu.org/licenses/>
25 *
26 * @category  Encryption
27 * @package   Crypt_GPG
28 * @author    Michael Gauthier <mike@silverorange.com>
29 * @copyright 2011-2013 silverorange
30 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
31 * @link      http://pear.php.net/package/Crypt_GPG
32 * @link      http://www.gnupg.org/
33 */
34
35/**
36 * Base class for GPG methods
37 */
38require_once 'Crypt/GPGAbstract.php';
39
40/**
41 * GnuPG key generator
42 *
43 * This class provides an object oriented interface for generating keys with
44 * the GNU Privacy Guard (GPG).
45 *
46 * Secure key generation requires true random numbers, and as such can be slow.
47 * If the operating system runs out of entropy, key generation will block until
48 * more entropy is available.
49 *
50 * If quick key generation is important, a hardware entropy generator, or an
51 * entropy gathering daemon may be installed. For example, administrators of
52 * Debian systems may want to install the 'randomsound' package.
53 *
54 * This class uses the experimental automated key generation support available
55 * in GnuPG. See <b>doc/DETAILS</b> in the
56 * {@link http://www.gnupg.org/download/ GPG distribution} for detailed
57 * information on the key generation format.
58 *
59 * @category  Encryption
60 * @package   Crypt_GPG
61 * @author    Nathan Fredrickson <nathan@silverorange.com>
62 * @author    Michael Gauthier <mike@silverorange.com>
63 * @copyright 2005-2013 silverorange
64 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
65 * @link      http://pear.php.net/package/Crypt_GPG
66 * @link      http://www.gnupg.org/
67 */
68class Crypt_GPG_KeyGenerator extends Crypt_GPGAbstract
69{
70    /**
71     * The expiration date of generated keys
72     *
73     * @var integer
74     *
75     * @see Crypt_GPG_KeyGenerator::setExpirationDate()
76     */
77    protected $expirationDate = 0;
78
79    /**
80     * The passphrase of generated keys
81     *
82     * @var string
83     *
84     * @see Crypt_GPG_KeyGenerator::setPassphrase()
85     */
86    protected $passphrase = '';
87
88    /**
89     * The algorithm for generated primary keys
90     *
91     * @var integer
92     *
93     * @see Crypt_GPG_KeyGenerator::setKeyParams()
94     */
95    protected $keyAlgorithm = Crypt_GPG_SubKey::ALGORITHM_DSA;
96
97    /**
98     * The size of generated primary keys
99     *
100     * @var integer
101     *
102     * @see Crypt_GPG_KeyGenerator::setKeyParams()
103     */
104    protected $keySize = 1024;
105
106    /**
107     * The usages of generated primary keys
108     *
109     * This is a bitwise combination of the usage constants in
110     * {@link Crypt_GPG_SubKey}.
111     *
112     * @var integer
113     *
114     * @see Crypt_GPG_KeyGenerator::setKeyParams()
115     */
116    protected $keyUsage = 6; // USAGE_SIGN | USAGE_CERTIFY
117
118    /**
119     * The algorithm for generated sub-keys
120     *
121     * @var integer
122     *
123     * @see Crypt_GPG_KeyGenerator::setSubKeyParams()
124     */
125    protected $subKeyAlgorithm = Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC;
126
127    /**
128     * The size of generated sub-keys
129     *
130     * @var integer
131     *
132     * @see Crypt_GPG_KeyGenerator::setSubKeyParams()
133     */
134    protected $subKeySize = 2048;
135
136    /**
137     * The usages of generated sub-keys
138     *
139     * This is a bitwise combination of the usage constants in
140     * {@link Crypt_GPG_SubKey}.
141     *
142     * @var integer
143     *
144     * @see Crypt_GPG_KeyGenerator::setSubKeyParams()
145     */
146    protected $subKeyUsage = Crypt_GPG_SubKey::USAGE_ENCRYPT;
147
148    /**
149     * Creates a new GnuPG key generator
150     *
151     * @param array $options An array of options used to create the object.
152     *                       All options are optional and are represented as key-value
153     *                       pairs. See Crypt_GPGAbstract::__construct() for more info.
154     *
155     * @throws Crypt_GPG_FileException if the <kbd>homedir</kbd> does not exist
156     *         and cannot be created. This can happen if <kbd>homedir</kbd> is
157     *         not specified, Crypt_GPG is run as the web user, and the web
158     *         user has no home directory. This exception is also thrown if any
159     *         of the options <kbd>publicKeyring</kbd>,
160     *         <kbd>privateKeyring</kbd> or <kbd>trustDb</kbd> options are
161     *         specified but the files do not exist or are are not readable.
162     *         This can happen if the user running the Crypt_GPG process (for
163     *         example, the Apache user) does not have permission to read the
164     *         files.
165     *
166     * @throws PEAR_Exception if the provided <kbd>binary</kbd> is invalid, or
167     *         if no <kbd>binary</kbd> is provided and no suitable binary could
168     *         be found.
169     *
170     * @throws PEAR_Exception if the provided <kbd>agent</kbd> is invalid, or
171     *         if no <kbd>agent</kbd> is provided and no suitable gpg-agent
172     *         could be found.
173     */
174    public function __construct(array $options = array())
175    {
176        parent::__construct($options);
177    }
178
179    /**
180     * Sets the expiration date of generated keys
181     *
182     * @param string|integer $date either a string that may be parsed by
183     *                             PHP's strtotime() function, or an integer
184     *                             timestamp representing the number of seconds
185     *                             since the UNIX epoch. This date must be at
186     *                             least one date in the future. Keys that
187     *                             expire in the past may not be generated. Use
188     *                             an expiration date of 0 for keys that do not
189     *                             expire.
190     *
191     * @throws InvalidArgumentException if the date is not a valid format, or
192     *                                  if the date is not at least one day in
193     *                                  the future, or if the date is greater
194     *                                  than 2038-01-19T03:14:07.
195     *
196     * @return Crypt_GPG_KeyGenerator the current object, for fluent interface.
197     */
198    public function setExpirationDate($date)
199    {
200        if (is_int($date) || ctype_digit(strval($date))) {
201            $expirationDate = intval($date);
202        } else {
203            $expirationDate = strtotime($date);
204        }
205
206        if ($expirationDate === false) {
207            throw new InvalidArgumentException(
208                sprintf(
209                    'Invalid expiration date format: "%s". Please use a ' .
210                    'format compatible with PHP\'s strtotime().',
211                    $date
212                )
213            );
214        }
215
216        if ($expirationDate !== 0 && $expirationDate < time() + 86400) {
217            throw new InvalidArgumentException(
218                'Expiration date must be at least a day in the future.'
219            );
220        }
221
222        // GnuPG suffers from the 2038 bug
223        if ($expirationDate > 2147483647) {
224            throw new InvalidArgumentException(
225                'Expiration date must not be greater than 2038-01-19T03:14:07.'
226            );
227        }
228
229        $this->expirationDate = $expirationDate;
230
231        return $this;
232    }
233
234    /**
235     * Sets the passphrase of generated keys
236     *
237     * @param string $passphrase the passphrase to use for generated keys. Use
238     *                           null or an empty string for no passphrase.
239     *
240     * @return Crypt_GPG_KeyGenerator the current object, for fluent interface.
241     */
242    public function setPassphrase($passphrase)
243    {
244        $this->passphrase = strval($passphrase);
245        return $this;
246    }
247
248    /**
249     * Sets the parameters for the primary key of generated key-pairs
250     *
251     * @param integer $algorithm the algorithm used by the key. This should be
252     *                           one of the Crypt_GPG_SubKey::ALGORITHM_*
253     *                           constants.
254     * @param integer $size      optional. The size of the key. Different
255     *                           algorithms have different size requirements.
256     *                           If not specified, the default size for the
257     *                           specified algorithm will be used. If an
258     *                           invalid key size is used, GnuPG will do its
259     *                           best to round it to a valid size.
260     * @param integer $usage     optional. A bitwise combination of key usages.
261     *                           If not specified, the primary key will be used
262     *                           only to sign and certify. This is the default
263     *                           behavior of GnuPG in interactive mode. Use
264     *                           the Crypt_GPG_SubKey::USAGE_* constants here.
265     *                           The primary key may be used to certify even
266     *                           if the certify usage is not specified.
267     *
268     * @return Crypt_GPG_KeyGenerator the current object, for fluent interface.
269     */
270    public function setKeyParams($algorithm, $size = 0, $usage = 0)
271    {
272        $algorithm = intval($algorithm);
273
274        if ($algorithm === Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC) {
275            throw new Crypt_GPG_InvalidKeyParamsException(
276                'Primary key algorithm must be capable of signing. The ' .
277                'Elgamal algorithm can only encrypt.',
278                0,
279                $algorithm,
280                $size,
281                $usage
282            );
283        }
284
285        if ($size != 0) {
286            $size = intval($size);
287        }
288
289        if ($usage != 0) {
290            $usage = intval($usage);
291        }
292
293        $usageEncrypt = Crypt_GPG_SubKey::USAGE_ENCRYPT;
294
295        if ($algorithm === Crypt_GPG_SubKey::ALGORITHM_DSA
296            && ($usage & $usageEncrypt) === $usageEncrypt
297        ) {
298            throw new Crypt_GPG_InvalidKeyParamsException(
299                'The DSA algorithm is not capable of encrypting. Please ' .
300                'specify a different algorithm or do not include encryption ' .
301                'as a usage for the primary key.',
302                0,
303                $algorithm,
304                $size,
305                $usage
306            );
307        }
308
309        $this->keyAlgorithm = $algorithm;
310
311        if ($size != 0) {
312            $this->keySize = $size;
313        }
314
315        if ($usage != 0) {
316            $this->keyUsage = $usage;
317        }
318
319        return $this;
320    }
321
322    /**
323     * Sets the parameters for the sub-key of generated key-pairs
324     *
325     * @param integer $algorithm the algorithm used by the key. This should be
326     *                           one of the Crypt_GPG_SubKey::ALGORITHM_*
327     *                           constants.
328     * @param integer $size      optional. The size of the key. Different
329     *                           algorithms have different size requirements.
330     *                           If not specified, the default size for the
331     *                           specified algorithm will be used. If an
332     *                           invalid key size is used, GnuPG will do its
333     *                           best to round it to a valid size.
334     * @param integer $usage     optional. A bitwise combination of key usages.
335     *                           If not specified, the sub-key will be used
336     *                           only to encrypt. This is the default behavior
337     *                           of GnuPG in interactive mode. Use the
338     *                           Crypt_GPG_SubKey::USAGE_* constants here.
339     *
340     * @return Crypt_GPG_KeyGenerator the current object, for fluent interface.
341     */
342    public function setSubKeyParams($algorithm, $size = '', $usage = 0)
343    {
344        $algorithm = intval($algorithm);
345
346        if ($size != 0) {
347            $size = intval($size);
348        }
349
350        if ($usage != 0) {
351            $usage = intval($usage);
352        }
353
354        $usageSign = Crypt_GPG_SubKey::USAGE_SIGN;
355
356        if ($algorithm === Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC
357            && ($usage & $usageSign) === $usageSign
358        ) {
359            throw new Crypt_GPG_InvalidKeyParamsException(
360                'The Elgamal algorithm is not capable of signing. Please ' .
361                'specify a different algorithm or do not include signing ' .
362                'as a usage for the sub-key.',
363                0,
364                $algorithm,
365                $size,
366                $usage
367            );
368        }
369
370        $usageEncrypt = Crypt_GPG_SubKey::USAGE_ENCRYPT;
371
372        if ($algorithm === Crypt_GPG_SubKey::ALGORITHM_DSA
373            && ($usage & $usageEncrypt) === $usageEncrypt
374        ) {
375            throw new Crypt_GPG_InvalidKeyParamsException(
376                'The DSA algorithm is not capable of encrypting. Please ' .
377                'specify a different algorithm or do not include encryption ' .
378                'as a usage for the sub-key.',
379                0,
380                $algorithm,
381                $size,
382                $usage
383            );
384        }
385
386        $this->subKeyAlgorithm = $algorithm;
387
388        if ($size != 0) {
389            $this->subKeySize = $size;
390        }
391
392        if ($usage != 0) {
393            $this->subKeyUsage = $usage;
394        }
395
396        return $this;
397    }
398
399    /**
400     * Generates a new key-pair in the current keyring
401     *
402     * Secure key generation requires true random numbers, and as such can be
403     * solw. If the operating system runs out of entropy, key generation will
404     * block until more entropy is available.
405     *
406     * If quick key generation is important, a hardware entropy generator, or
407     * an entropy gathering daemon may be installed. For example,
408     * administrators of Debian systems may want to install the 'randomsound'
409     * package.
410     *
411     * @param string|Crypt_GPG_UserId $name    either a {@link Crypt_GPG_UserId}
412     *                                         object, or a string containing
413     *                                         the name of the user id.
414     * @param string                  $email   optional. If <i>$name</i> is
415     *                                         specified as a string, this is
416     *                                         the email address of the user id.
417     * @param string                  $comment optional. If <i>$name</i> is
418     *                                         specified as a string, this is
419     *                                         the comment of the user id.
420     *
421     * @return Crypt_GPG_Key the newly generated key.
422     *
423     * @throws Crypt_GPG_KeyNotCreatedException if the key parameters are
424     *         incorrect, if an unknown error occurs during key generation, or
425     *         if the newly generated key is not found in the keyring.
426     *
427     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
428     *         Use the <kbd>debug</kbd> option and file a bug report if these
429     *         exceptions occur.
430     */
431    public function generateKey($name, $email = '', $comment = '')
432    {
433        $handle = uniqid('key', true);
434
435        $userId = $this->getUserId($name, $email, $comment);
436
437        $keyParams = array(
438            'Key-Type'      => $this->keyAlgorithm,
439            'Key-Length'    => $this->keySize,
440            'Key-Usage'     => $this->getUsage($this->keyUsage),
441            'Subkey-Type'   => $this->subKeyAlgorithm,
442            'Subkey-Length' => $this->subKeySize,
443            'Subkey-Usage'  => $this->getUsage($this->subKeyUsage),
444            'Handle'        => $handle,
445        );
446
447        if ($this->expirationDate != 0) {
448            // GnuPG only accepts granularity of days
449            $expirationDate = date('Y-m-d', $this->expirationDate);
450            $keyParams['Expire-Date'] = $expirationDate;
451        }
452
453        if (strlen($this->passphrase)) {
454            $keyParams['Passphrase'] = $this->passphrase;
455        }
456
457        $name    = $userId->getName();
458        $email   = $userId->getEmail();
459        $comment = $userId->getComment();
460
461        if (strlen($name) > 0) {
462            $keyParams['Name-Real'] = $name;
463        }
464
465        if (strlen($email) > 0) {
466            $keyParams['Name-Email'] = $email;
467        }
468
469        if (strlen($comment) > 0) {
470            $keyParams['Name-Comment'] = $comment;
471        }
472
473        $keyParamsFormatted = array();
474        foreach ($keyParams as $name => $value) {
475            $keyParamsFormatted[] = $name . ': ' . $value;
476        }
477
478        // This is required in GnuPG 2.1
479        if (!strlen($this->passphrase)) {
480            $keyParamsFormatted[] = '%no-protection';
481        }
482
483        $input = implode("\n", $keyParamsFormatted) . "\n%commit\n";
484
485        $this->engine->reset();
486        $this->engine->setProcessData('Handle', $handle);
487        $this->engine->setInput($input);
488        $this->engine->setOutput($output);
489        $this->engine->setOperation('--gen-key', array('--batch'));
490
491        try {
492            $this->engine->run();
493        } catch (Crypt_GPG_InvalidKeyParamsException $e) {
494            switch ($this->engine->getProcessData('LineNumber')) {
495            case 1:
496                throw new Crypt_GPG_InvalidKeyParamsException(
497                    'Invalid primary key algorithm specified.',
498                    0,
499                    $this->keyAlgorithm,
500                    $this->keySize,
501                    $this->keyUsage
502                );
503            case 4:
504                throw new Crypt_GPG_InvalidKeyParamsException(
505                    'Invalid sub-key algorithm specified.',
506                    0,
507                    $this->subKeyAlgorithm,
508                    $this->subKeySize,
509                    $this->subKeyUsage
510                );
511            default:
512                throw $e;
513            }
514        }
515
516        $fingerprint = $this->engine->getProcessData('KeyCreated');
517        $keys        = $this->_getKeys($fingerprint);
518
519        if (count($keys) === 0) {
520            throw new Crypt_GPG_KeyNotCreatedException(
521                sprintf(
522                    'Newly created key "%s" not found in keyring.',
523                    $fingerprint
524                )
525            );
526        }
527
528        return $keys[0];
529    }
530
531    /**
532     * Builds a GnuPG key usage string suitable for key generation
533     *
534     * See <b>doc/DETAILS</b> in the
535     * {@link http://www.gnupg.org/download/ GPG distribution} for detailed
536     * information on the key usage format.
537     *
538     * @param integer $usage a bitwise combination of the key usages. This is
539     *                       a combination of the Crypt_GPG_SubKey::USAGE_*
540     *                       constants.
541     *
542     * @return string the key usage string.
543     */
544    protected function getUsage($usage)
545    {
546        $map = array(
547            Crypt_GPG_SubKey::USAGE_ENCRYPT        => 'encrypt',
548            Crypt_GPG_SubKey::USAGE_SIGN           => 'sign',
549            Crypt_GPG_SubKey::USAGE_CERTIFY        => 'cert',
550            Crypt_GPG_SubKey::USAGE_AUTHENTICATION => 'auth',
551        );
552
553        // cert is always used for primary keys and does not need to be
554        // specified
555        $usage &= ~Crypt_GPG_SubKey::USAGE_CERTIFY;
556
557        $usageArray = array();
558
559        foreach ($map as $key => $value) {
560            if (($usage & $key) === $key) {
561                $usageArray[] = $value;
562            }
563        }
564
565        return implode(',', $usageArray);
566    }
567
568    /**
569     * Gets a user id object from parameters
570     *
571     * @param string|Crypt_GPG_UserId $name    either a {@link Crypt_GPG_UserId}
572     *                                         object, or a string containing
573     *                                         the name of the user id.
574     * @param string                  $email   optional. If <i>$name</i> is
575     *                                         specified as a string, this is
576     *                                         the email address of the user id.
577     * @param string                  $comment optional. If <i>$name</i> is
578     *                                         specified as a string, this is
579     *                                         the comment of the user id.
580     *
581     * @return Crypt_GPG_UserId a user id object for the specified parameters.
582     */
583    protected function getUserId($name, $email = '', $comment = '')
584    {
585        if ($name instanceof Crypt_GPG_UserId) {
586            $userId = $name;
587        } else {
588            $userId = new Crypt_GPG_UserId();
589            $userId->setName($name)->setEmail($email)->setComment($comment);
590        }
591
592        return $userId;
593    }
594}
595