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