From cc32cd2e95b18a0c0118bbf1928327675c9e64a9 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sun, 19 May 2019 15:35:29 -0500 Subject: [PATCH] make RSA / DSA / ECDSA immutable and add support to SSH2 / X509 --- phpseclib/Crypt/Common/AsymmetricKey.php | 538 ++--- phpseclib/Crypt/Common/Fingerprint.php | 65 + phpseclib/Crypt/Common/Keys/PKCS8.php | 4 +- phpseclib/Crypt/Common/PasswordProtected.php | 51 + phpseclib/Crypt/Common/PrivateKey.php | 30 + phpseclib/Crypt/Common/PublicKey.php | 29 + phpseclib/Crypt/DSA.php | 443 +---- phpseclib/Crypt/DSA/Parameters.php | 39 + phpseclib/Crypt/DSA/PrivateKey.php | 159 ++ phpseclib/Crypt/DSA/PublicKey.php | 91 + phpseclib/Crypt/ECDSA.php | 634 ++---- phpseclib/Crypt/ECDSA/Parameters.php | 39 + phpseclib/Crypt/ECDSA/PrivateKey.php | 214 ++ phpseclib/Crypt/ECDSA/PublicKey.php | 170 ++ phpseclib/Crypt/PublicKeyLoader.php | 74 + phpseclib/Crypt/RSA.php | 1755 +++-------------- phpseclib/Crypt/RSA/Keys/XML.php | 9 + phpseclib/Crypt/RSA/PrivateKey.php | 561 ++++++ phpseclib/Crypt/RSA/PublicKey.php | 496 +++++ .../Exception/UnsupportedFormatException.php | 26 + phpseclib/File/X509.php | 385 ++-- phpseclib/Math/BigInteger/Engines/OpenSSL.php | 11 +- phpseclib/Net/SSH2.php | 157 +- phpseclib/System/SSH/Agent.php | 6 +- phpseclib/System/SSH/Agent/Identity.php | 65 +- tests/Unit/Crypt/DSA/CreateKeyTest.php | 32 +- tests/Unit/Crypt/DSA/LoadKeyTest.php | 82 +- tests/Unit/Crypt/DSA/SignatureTest.php | 65 +- tests/Unit/Crypt/ECDSA/CurveTest.php | 132 +- tests/Unit/Crypt/ECDSA/KeyTest.php | 130 +- tests/Unit/Crypt/RSA/CreateKeyTest.php | 28 +- tests/Unit/Crypt/RSA/LoadKeyTest.php | 278 +-- tests/Unit/Crypt/RSA/ModeTest.php | 136 +- tests/Unit/File/X509/CSRTest.php | 4 +- tests/Unit/File/X509/SPKACTest.php | 2 +- tests/Unit/File/X509/X509Test.php | 45 +- 36 files changed, 3413 insertions(+), 3572 deletions(-) create mode 100644 phpseclib/Crypt/Common/Fingerprint.php create mode 100644 phpseclib/Crypt/Common/PasswordProtected.php create mode 100644 phpseclib/Crypt/Common/PrivateKey.php create mode 100644 phpseclib/Crypt/Common/PublicKey.php create mode 100644 phpseclib/Crypt/DSA/Parameters.php create mode 100644 phpseclib/Crypt/DSA/PrivateKey.php create mode 100644 phpseclib/Crypt/DSA/PublicKey.php create mode 100644 phpseclib/Crypt/ECDSA/Parameters.php create mode 100644 phpseclib/Crypt/ECDSA/PrivateKey.php create mode 100644 phpseclib/Crypt/ECDSA/PublicKey.php create mode 100644 phpseclib/Crypt/PublicKeyLoader.php create mode 100644 phpseclib/Crypt/RSA/PrivateKey.php create mode 100644 phpseclib/Crypt/RSA/PublicKey.php create mode 100644 phpseclib/Exception/UnsupportedFormatException.php diff --git a/phpseclib/Crypt/Common/AsymmetricKey.php b/phpseclib/Crypt/Common/AsymmetricKey.php index a95f7e80..df6187cb 100644 --- a/phpseclib/Crypt/Common/AsymmetricKey.php +++ b/phpseclib/Crypt/Common/AsymmetricKey.php @@ -15,11 +15,13 @@ namespace phpseclib\Crypt\Common; +use phpseclib\Exception\UnsupportedFormatException; +use phpseclib\Exception\NoKeyLoadedException; use phpseclib\Math\BigInteger; use phpseclib\Crypt\Hash; -use ParagonIE\ConstantTime\Base64; -use phpseclib\Exception\UnsupportedOperationException; -use phpseclib\Exception\FileNotFoundException; +use phpseclib\Crypt\RSA; +use phpseclib\Crypt\DSA; +use phpseclib\Crypt\ECDSA; /** * Base Class for all stream cipher classes @@ -46,15 +48,36 @@ abstract class AsymmetricKey protected static $one; /** - * OpenSSL configuration file name. + * Format of the loaded key * - * Set to null to use system configuration file. - * - * @see self::createKey() - * @var mixed - * @access public + * @var string + * @access private */ - protected static $configFile; + protected $format; + + /** + * Hash function + * + * @var \phpseclib\Crypt\Hash + * @access private + */ + protected $hash; + + /** + * HMAC function + * + * @var \phpseclib\Crypt\Hash + * @access private + */ + private $hmac; + + /** + * Enable Blinding? + * + * @var bool + * @access private + */ + protected static $enableBlinding = true; /** * Supported plugins (lower case) @@ -92,73 +115,6 @@ abstract class AsymmetricKey */ private static $signatureFileFormats = []; - /** - * Password - * - * @var string - * @access private - */ - protected $password = false; - - /** - * Loaded File Format - * - * @var string - * @access private - */ - protected $format = false; - - /** - * Private Key Format - * - * @var string - * @access private - */ - protected $privateKeyFormat = 'PKCS8'; - - /** - * Public Key Format - * - * @var string - * @access private - */ - protected $publicKeyFormat = 'PKCS8'; - - /** - * Parameters Format - * - * No setParametersFormat method exists because PKCS1 is the only format that supports - * parameters in both DSA and ECDSA (RSA doesn't have an analog) - * - * @var string - * @access private - */ - protected $parametersFormat = 'PKCS1'; - - /** - * Hash function - * - * @var \phpseclib\Crypt\Hash - * @access private - */ - protected $hash; - - /** - * HMAC function - * - * @var \phpseclib\Crypt\Hash - * @access private - */ - private $hmac; - - /** - * Hash manually set? - * - * @var bool - * @access private - */ - protected $hashManuallySet = false; - /** * Available Engines * @@ -169,10 +125,8 @@ abstract class AsymmetricKey /** * The constructor - * - * @access public */ - public function __construct() + protected function __construct() { self::initialize_static_variables(); @@ -180,51 +134,14 @@ abstract class AsymmetricKey $this->hmac = new Hash('sha256'); } - /** - * Tests engine validity - * - * @access public - * @param int $val - */ - public static function useBestEngine() - { - static::$engines = [ - 'PHP' => true, - 'OpenSSL' => extension_loaded('openssl') && file_exists(self::$configFile), - // this test can be satisfied by either of the following: - // http://php.net/manual/en/book.sodium.php - // https://github.com/paragonie/sodium_compat - 'libsodium' => function_exists('sodium_crypto_sign_keypair') - ]; - - return static::$engines; - } - - /** - * Flag to use internal engine only (useful for unit testing) - * - * @access public - */ - public static function useInternalEngine() - { - static::$engines = [ - 'PHP' => true, - 'OpenSSL' => false, - 'libsodium' => false - ]; - } - /** * Initialize static variables - * - * @access private */ protected static function initialize_static_variables() { if (!isset(self::$zero)) { self::$zero= new BigInteger(0); self::$one = new BigInteger(1); - self::$configFile = __DIR__ . '/../../openssl.cnf'; } self::loadPlugins('Keys'); @@ -233,6 +150,70 @@ abstract class AsymmetricKey } } + /** + * Load the key + * + * @param string $key + * @param string $type + * @param string $password + * @return array|bool + */ + protected static function load($key, $type, $password) + { + self::initialize_static_variables(); + + $components = false; + if ($type === false) { + foreach (self::$plugins[static::ALGORITHM]['Keys'] as $format) { + try { + $components = $format::load($key, $password); + } catch (\Exception $e) { + $components = false; + } + if ($components !== false) { + break; + } + } + } else { + $format = strtolower($type); + if (isset(self::$plugins[static::ALGORITHM]['Keys'][$format])) { + $format = self::$plugins[static::ALGORITHM]['Keys'][$format]; + $components = $format::load($key, $password); + } + } + + if ($components === false) { + throw new NoKeyLoadedException('Unable to read key'); + } + + $components['format'] = $format; + + return $components; + } + + /** + * Validate Plugin + * + * @access private + * @param string $format + * @param string $type + * @param string $method optional + * @return mixed + */ + protected static function validatePlugin($format, $type, $method = NULL) + { + $type = strtolower($type); + if (!isset(self::$plugins[static::ALGORITHM][$format][$type])) { + throw new UnsupportedFormatException("$type is not a supported format"); + } + $type = self::$plugins[static::ALGORITHM][$format][$type]; + if (isset($method) && !method_exists($type, $method)) { + throw new UnsupportedFormatException("$type does not implement $method"); + } + + return $type; + } + /** * Load Plugins * @@ -259,118 +240,6 @@ abstract class AsymmetricKey } } - /** - * Validate Plugin - * - * @access private - * @param string $format - * @param string $type - * @param string $method optional - * @return mixed - */ - protected static function validatePlugin($format, $type, $method = NULL) - { - $type = strtolower($type); - if (!isset(self::$plugins[static::ALGORITHM][$format][$type])) { - return false; - } - $type = self::$plugins[static::ALGORITHM][$format][$type]; - if (isset($method) && !method_exists($type, $method)) { - return false; - } - - return $type; - } - - /** - * Load the key - * - * @access private - * @param string $key - * @param string $type - * @return array|bool - */ - protected function load($key, $type) - { - if ($key instanceof self) { - $this->hmac = $key->hmac; - - return; - } - - $components = false; - if ($type === false) { - foreach (self::$plugins[static::ALGORITHM]['Keys'] as $format) { - try { - $components = $format::load($key, $this->password); - } catch (\Exception $e) { - $components = false; - } - if ($components !== false) { - break; - } - } - } else { - $format = strtolower($type); - if (isset(self::$plugins[static::ALGORITHM]['Keys'][$format])) { - $format = self::$plugins[static::ALGORITHM]['Keys'][$format]; - $components = $format::load($key, $this->password); - } - } - - if ($components === false) { - $this->format = false; - return false; - } - - $this->format = $format; - - return $components; - } - - /** - * Load the public key - * - * @access private - * @param string $key - * @param string $type - * @return array - */ - protected function setPublicKey($key, $type) - { - $components = false; - if ($type === false) { - foreach (self::$plugins[static::ALGORITHM]['Keys'] as $format) { - if (!method_exists($format, 'savePublicKey')) { - continue; - } - try { - $components = $format::load($key, $this->password); - } catch (\Exception $e) { - $components = false; - } - if ($components !== false) { - break; - } - } - } else { - $format = strtolower($type); - if (isset(self::$plugins[static::ALGORITHM]['Keys'][$format])) { - $format = self::$plugins[static::ALGORITHM]['Keys'][$format]; - $components = $format::load($key, $this->password); - } - } - - if ($components === false) { - $this->format = false; - return false; - } - - $this->format = $format; - - return $components; - } - /** * Returns a list of supported formats. * @@ -407,161 +276,6 @@ abstract class AsymmetricKey } } - /** - * Returns the public key's fingerprint - * - * The public key's fingerprint is returned, which is equivalent to running `ssh-keygen -lf rsa.pub`. If there is - * no public key currently loaded, false is returned. - * Example output (md5): "c1:b1:30:29:d7:b8:de:6c:97:77:10:d7:46:41:63:87" (as specified by RFC 4716) - * - * @access public - * @param string $algorithm The hashing algorithm to be used. Valid options are 'md5' and 'sha256'. False is returned - * for invalid values. - * @return mixed - */ - public function getPublicKeyFingerprint($algorithm = 'md5') - { - $type = self::validatePlugin('Keys', 'OpenSSH', 'getBinaryOutput'); - if ($type === false) { - return false; - } - - $status = $type::getBinaryOutput(); - $type::setBinaryOutput(true); - - $key = $this->getPublicKey('OpenSSH'); - if ($key === false) { - return false; - } - - $type::setBinaryOutput($status); - - switch ($algorithm) { - case 'sha256': - $hash = new Hash('sha256'); - $base = Base64::encode($hash->hash($key)); - return substr($base, 0, strlen($base) - 1); - case 'md5': - return substr(chunk_split(md5($key), 2, ':'), 0, -1); - default: - return false; - } - } - - /** - * __toString() magic method - * - * @access public - * @return string - */ - public function __toString() - { - try { - $key = $this->getPrivateKey($this->privateKeyFormat); - if (is_string($key)) { - return $key; - } - $key = $this->getPublicKey($this->publicKeyFormat); - if (is_string($key)) { - return $key; - } - - if (!method_exists($this, 'getParameters')) { - return ''; - } - - $key = $this->getParameters($this->parametersFormat); - return is_string($key) ? $key : ''; - } catch (\Exception $e) { - return ''; - } - } - - /** - * __clone() magic method - * - * @access public - * @return static - */ - public function __clone() - { - $key = new static(); - $key->load($this); - return $key; - } - - /** - * Determines the private key format - * - * @see self::__toString() - * @access public - * @param string $format - */ - public function setPrivateKeyFormat($format) - { - $type = self::validatePlugin('Keys', $format); - if ($type === false) { - throw new FileNotFoundException('Plugin not found'); - } - - $type = self::validatePlugin('Keys', $format, 'savePrivateKey'); - if ($type === false) { - throw new UnsupportedOperationException('Plugin does not support private keys'); - } - - $this->privateKeyFormat = $format; - } - - /** - * Determines the public key format - * - * @see self::__toString() - * @access public - * @param string $format - */ - public function setPublicKeyFormat($format) - { - $type = self::validatePlugin('Keys', $format); - if ($type === false) { - throw new FileNotFoundException('Plugin not found'); - } - - $type = self::validatePlugin('Keys', $format, 'savePublicKey'); - if ($type === false) { - throw new UnsupportedOperationException('Plugin does not support public keys'); - } - - $this->publicKeyFormat = $format; - } - - /** - * Determines the key format - * - * Sets both the public key and private key formats to the specified format if those formats support - * the key type - * - * @see self::__toString() - * @access public - * @param string $format - */ - public function setKeyFormat($format) - { - $type = self::validatePlugin('Keys', $format); - if ($type === false) { - throw new FileNotFoundException('Plugin not found'); - } - - try { - $this->setPrivateKeyFormat($format); - } catch (\Exception $e) { - } - - try { - $this->setPublicKeyFormat($format); - } catch (\Exception $e) { - } - } - /** * Returns the format of the loaded key. * @@ -583,19 +297,47 @@ abstract class AsymmetricKey } /** - * Sets the password + * Tests engine validity * - * Private keys can be encrypted with a password. To unset the password, pass in the empty string or false. - * Or rather, pass in $password such that empty($password) && !is_string($password) is true. - * - * @see self::createKey() - * @see self::load() * @access public - * @param string|boolean $password + * @param int $val */ - public function setPassword($password = false) + public static function useBestEngine() { - $this->password = $password; + static::$engines = [ + 'PHP' => true, + 'OpenSSL' => extension_loaded('openssl'), + // this test can be satisfied by either of the following: + // http://php.net/manual/en/book.sodium.php + // https://github.com/paragonie/sodium_compat + 'libsodium' => function_exists('sodium_crypto_sign_keypair') + ]; + + return static::$engines; + } + + /** + * Flag to use internal engine only (useful for unit testing) + * + * @access public + */ + public static function useInternalEngine() + { + static::$engines = [ + 'PHP' => true, + 'OpenSSL' => false, + 'libsodium' => false + ]; + } + + /** + * __toString() magic method + * + * @return string + */ + public function __toString() + { + return $this->toString('PKCS8'); } /** @@ -604,12 +346,14 @@ abstract class AsymmetricKey * @access public * @param string $hash */ - public function setHash($hash) + public function withHash($hash) { - $this->hash = new Hash($hash); - $this->hmac = new Hash($hash); + $new = clone $this; - $this->hashManuallySet = true; + $new->hash = new Hash($hash); + $new->hmac = new Hash($hash); + + return $new; } /** diff --git a/phpseclib/Crypt/Common/Fingerprint.php b/phpseclib/Crypt/Common/Fingerprint.php new file mode 100644 index 00000000..4a81ee9d --- /dev/null +++ b/phpseclib/Crypt/Common/Fingerprint.php @@ -0,0 +1,65 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\Common; + +use phpseclib\Crypt\Hash; + +/** + * Fingerprint Trait for Private Keys + * + * @package Common + * @author Jim Wigginton + * @access public + */ +trait Fingerprint +{ + /** + * Returns the public key's fingerprint + * + * The public key's fingerprint is returned, which is equivalent to running `ssh-keygen -lf rsa.pub`. If there is + * no public key currently loaded, false is returned. + * Example output (md5): "c1:b1:30:29:d7:b8:de:6c:97:77:10:d7:46:41:63:87" (as specified by RFC 4716) + * + * @access public + * @param string $algorithm The hashing algorithm to be used. Valid options are 'md5' and 'sha256'. False is returned + * for invalid values. + * @return mixed + */ + public function getFingerprint($algorithm = 'md5') + { + $type = self::validatePlugin('Keys', 'OpenSSH', 'getBinaryOutput'); + if ($type === false) { + return false; + } + $status = $type::getBinaryOutput(); + $type::setBinaryOutput(true); + $key = $this->toString('OpenSSH'); + if ($key === false) { + return false; + } + $type::setBinaryOutput($status); + switch ($algorithm) { + case 'sha256': + $hash = new Hash('sha256'); + $base = base64_encode($hash->hash($key)); + return substr($base, 0, strlen($base) - 1); + case 'md5': + return substr(chunk_split(md5($key), 2, ':'), 0, -1); + default: + return false; + } + } +} \ No newline at end of file diff --git a/phpseclib/Crypt/Common/Keys/PKCS8.php b/phpseclib/Crypt/Common/Keys/PKCS8.php index 73d6eeab..f350ca6d 100644 --- a/phpseclib/Crypt/Common/Keys/PKCS8.php +++ b/phpseclib/Crypt/Common/Keys/PKCS8.php @@ -479,7 +479,7 @@ abstract class PKCS8 extends PKCS } if (isset($private['publicKey'])) { if ($private['publicKey'][0] != "\0") { - throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($val)); + throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($private['publicKey'][0])); } $private['publicKey'] = substr($private['publicKey'], 1); } @@ -494,7 +494,7 @@ abstract class PKCS8 extends PKCS if (is_array($public)) { if ($public['publicKey'][0] != "\0") { - throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($val)); + throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($public['publicKey'][0])); } if (is_array(static::OID_NAME)) { if (!in_array($public['publicKeyAlgorithm']['algorithm'], static::OID_NAME)) { diff --git a/phpseclib/Crypt/Common/PasswordProtected.php b/phpseclib/Crypt/Common/PasswordProtected.php new file mode 100644 index 00000000..614114ca --- /dev/null +++ b/phpseclib/Crypt/Common/PasswordProtected.php @@ -0,0 +1,51 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\Common; + +/** + * Password Protected Trait for Private Keys + * + * @package Common + * @author Jim Wigginton + * @access public + */ +trait PasswordProtected +{ + /** + * Password + * + * @var string|bool + */ + private $password = false; + + /** + * Sets the password + * + * Private keys can be encrypted with a password. To unset the password, pass in the empty string or false. + * Or rather, pass in $password such that empty($password) && !is_string($password) is true. + * + * @see self::createKey() + * @see self::load() + * @access public + * @param string|boolean $password + */ + public function withPassword($password = false) + { + $new = clone $this; + $new->password = $password; + return $new; + } +} \ No newline at end of file diff --git a/phpseclib/Crypt/Common/PrivateKey.php b/phpseclib/Crypt/Common/PrivateKey.php new file mode 100644 index 00000000..c726f3ea --- /dev/null +++ b/phpseclib/Crypt/Common/PrivateKey.php @@ -0,0 +1,30 @@ + + * @copyright 2009 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\Common; + +/** + * PrivateKey interface + * + * @package Common + * @author Jim Wigginton + * @access public + */ +interface PrivateKey +{ + public function sign($message); + //public function decrypt($ciphertext); + public function getPublicKey(); + public function toString($type); + public function withPassword($string); +} diff --git a/phpseclib/Crypt/Common/PublicKey.php b/phpseclib/Crypt/Common/PublicKey.php new file mode 100644 index 00000000..c1102f71 --- /dev/null +++ b/phpseclib/Crypt/Common/PublicKey.php @@ -0,0 +1,29 @@ + + * @copyright 2009 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\Common; + +/** + * PublicKey interface + * + * @package Common + * @author Jim Wigginton + * @access public + */ +interface PublicKey +{ + public function verify($message, $signature); + //public function encrypt($plaintext); + public function toString($type); + public function getFingerprint($algorithm); +} diff --git a/phpseclib/Crypt/DSA.php b/phpseclib/Crypt/DSA.php index 1802c679..8cec196e 100644 --- a/phpseclib/Crypt/DSA.php +++ b/phpseclib/Crypt/DSA.php @@ -10,13 +10,14 @@ * getPublicKey(); * * $plaintext = 'terrafrost'; * - * $signature = $privatekey->sign($plaintext, 'ASN1'); + * $signature = $private->sign($plaintext); * - * echo $publickey->verify($plaintext, $signature) ? 'verified' : 'unverified'; + * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified'; * ?> * * @@ -30,14 +31,11 @@ namespace phpseclib\Crypt; -use ParagonIE\ConstantTime\Base64; -use phpseclib\File\ASN1; -use phpseclib\Math\BigInteger; use phpseclib\Crypt\Common\AsymmetricKey; -use phpseclib\Math\PrimeField; -use phpseclib\Crypt\ECDSA\Signature\ASN1 as ASN1Signature; -use phpseclib\Exception\UnsupportedOperationException; -use phpseclib\Exception\NoKeyLoadedException; +use phpseclib\Crypt\DSA\PrivateKey; +use phpseclib\Crypt\DSA\PublicKey; +use phpseclib\Crypt\DSA\Parameters; +use phpseclib\Math\BigInteger; use phpseclib\Exception\InsufficientSetupException; /** @@ -47,7 +45,7 @@ use phpseclib\Exception\InsufficientSetupException; * @author Jim Wigginton * @access public */ -class DSA extends AsymmetricKey +abstract class DSA extends AsymmetricKey { /** * Algorithm Name @@ -63,7 +61,7 @@ class DSA extends AsymmetricKey * @var \phpseclib\Math\BigInteger * @access private */ - private $p; + protected $p; /** * DSA Group Order q @@ -81,15 +79,7 @@ class DSA extends AsymmetricKey * @var \phpseclib\Math\BigInteger * @access private */ - private $g; - - /** - * DSA secret exponent x - * - * @var \phpseclib\Math\BigInteger - * @access private - */ - protected $x; + protected $g; /** * DSA public key value y @@ -97,7 +87,23 @@ class DSA extends AsymmetricKey * @var \phpseclib\Math\BigInteger * @access private */ - private $y; + protected $y; + + /** + * Signature Format + * + * @var string + * @access private + */ + protected $format; + + /** + * Signature Format (Short) + * + * @var string + * @access private + */ + protected $shortFormat; /** * Create DSA parameters @@ -133,7 +139,7 @@ class DSA extends AsymmetricKey case $L == 3072 && $N == 256: break; default: - return false; + throw new \InvalidArgumentException('Invalid values for N and L'); } $two = new BigInteger(2); @@ -163,7 +169,7 @@ class DSA extends AsymmetricKey $h = $h->add(self::$one); } - $dsa = new DSA(); + $dsa = new Parameters; $dsa->p = $p; $dsa->q = $q; $dsa->g = $g; @@ -174,17 +180,14 @@ class DSA extends AsymmetricKey /** * Create public / private key pair. * - * This method is a bit polymorphic. It can take a DSA object (eg. pre-loaded with parameters), - * L / N as two distinct parameters or no parameters (at which point L and N will be generated - * with this method) + * This method is a bit polymorphic. It can take a DSA/Parameters object, L / N as two distinct parameters or + * no parameters (at which point L and N will be generated with this method) * - * Returns an array with the following two elements: - * - 'privatekey': The private key. - * - 'publickey': The public key. + * Returns the private key, from which the publickey can be extracted * * @param $args[] * @access public - * @return array|DSA + * @return DSA\PrivateKey */ public static function createKey(...$args) { @@ -195,22 +198,29 @@ class DSA extends AsymmetricKey } if (count($args) == 2 && is_int($args[0]) && is_int($args[1])) { - $private = self::createParameters($args[0], $args[1]); - } else if (count($args) == 1 && $args[0] instanceof DSA) { - $private = clone $args[0]; + $params = self::createParameters($args[0], $args[1]); + } else if (count($args) == 1 && $args[0] instanceof Parameters) { + $params = $args[0]; } else if (!count($args)) { - $private = self::createParameters(); + $params = self::createParameters(); } else { throw new InsufficientSetupException('Valid parameters are either two integers (L and N), a single DSA object or no parameters at all.'); } + $private = new PrivateKey; + $private->p = $params->p; + $private->q = $params->q; + $private->g = $params->g; + $private->x = BigInteger::randomRange(self::$one, $private->q->subtract(self::$one)); $private->y = $private->g->powMod($private->x, $private->p); - $public = clone $private; - unset($public->x); + //$public = clone $private; + //unset($public->x); - return ['privatekey' => $private, 'publickey' => $public]; + return $private + ->withHash($params->hash->getHash()) + ->withSignatureFormat($params->shortFormat); } /** @@ -220,9 +230,10 @@ class DSA extends AsymmetricKey * @return bool * @access public * @param string $key - * @param int|bool $type optional + * @param string $type optional + * @param string $password optional */ - public function load($key, $type = false) + public static function load($key, $type = false, $password = false) { self::initialize_static_variables(); @@ -230,55 +241,38 @@ class DSA extends AsymmetricKey self::useBestEngine(); } - if ($key instanceof DSA) { - $this->privateKeyFormat = $key->privateKeyFormat; - $this->publicKeyFormat = $key->publicKeyFormat; - $this->format = $key->format; - $this->p = $key->p; - $this->q = $key->q; - $this->g = $key->g; - $this->x = $key->x; - $this->y = $key->y; - $this->parametersFormat = $key->parametersFormat; - - return true; + $components = parent::load($key, $type, $password); + if (!isset($components['x']) && !isset($components['y'])) { + $new = new Parameters; + } else if (isset($components['x'])) { + $new = new PrivateKey; + $new->x = $components['x']; + } else { + $new = new PublicKey; } - $components = parent::load($key, $type); - if ($components === false) { - $this->format = null; - $this->p = null; - $this->q = null; - $this->g = null; - $this->x = null; - $this->y = null; - - return false; - } - - if (isset($components['p'])) { - switch (true) { - case isset($this->p) && !$this->p->equals($components['p']): - case isset($this->q) && !$this->q->equals($components['q']): - case isset($this->g) && !$this->g->equals($components['g']): - $this->x = $this->y = null; - } - - $this->p = $components['p']; - $this->q = $components['q']; - $this->g = $components['g']; - } - - $this->x = isset($components['x']) ? $components['x'] : null; + $new->p = $components['p']; + $new->q = $components['q']; + $new->g = $components['g']; if (isset($components['y'])) { - $this->y = $components['y']; + $new->y = $components['y']; } - //} else if (isset($components['x'])) { - // $this->y = $this->g->powMod($this->x, $this->p); - //} - return true; + return $new; + } + + /** + * Constructor + * + * PublicKey and PrivateKey objects can only be created from abstract RSA class + */ + protected function __construct() + { + $this->format = self::validatePlugin('Signature', 'ASN1'); + $this->shortFormat = 'ASN1'; + + parent::__construct(); } /** @@ -291,140 +285,7 @@ class DSA extends AsymmetricKey */ public function getLength() { - return isset($this->p) ? - ['L' => $this->p->getLength(), 'N' => $this->q->getLength()] : - ['L' => 0, 'N' => 0]; - } - - /** - * Returns the private key - * - * PKCS1 DSA private keys contain x and y. PKCS8 DSA private keys just contain x - * but y can be derived from x. - * - * @see self::getPublicKey() - * @access public - * @param string $type optional - * @return mixed - */ - public function getPrivateKey($type = 'PKCS8') - { - $type = self::validatePlugin('Keys', $type, 'savePrivateKey'); - if ($type === false) { - return false; - } - - if (!isset($this->x)) { - return false; - } - - if (!isset($this->y)) { - $this->y = $this->g->powMod($this->x, $this->p); - } - - return $type::savePrivateKey($this->p, $this->q, $this->g, $this->y, $this->x, $this->password); - } - - /** - * Is the key a private key? - * - * @access public - * @return bool - */ - public function isPrivateKey() - { - return isset($this->x); - } - - /** - * Is the key a public key? - * - * @access public - * @return bool - */ - public function isPublicKey() - { - return isset($this->p); - } - - /** - * Returns the public key - * - * If you do "openssl rsa -in private.rsa -pubout -outform PEM" you get a PKCS8 formatted key - * that contains a publicKeyAlgorithm AlgorithmIdentifier and a publicKey BIT STRING. - * An AlgorithmIdentifier contains an OID and a parameters field. With RSA public keys this - * parameters field is NULL. With DSA PKCS8 public keys it is not - it contains the p, q and g - * variables. The publicKey BIT STRING contains, simply, the y variable. This can be verified - * by getting a DSA PKCS8 public key: - * - * "openssl dsa -in private.dsa -pubout -outform PEM" - * - * ie. just swap out rsa with dsa in the rsa command above. - * - * A PKCS1 public key corresponds to the publicKey portion of the PKCS8 key. In the case of RSA - * the publicKey portion /is/ the key. In the case of DSA it is not. You cannot verify a signature - * without the parameters and the PKCS1 DSA public key format does not include the parameters. - * - * @see self::getPrivateKey() - * @access public - * @param string $type optional - * @return mixed - */ - public function getPublicKey($type = null) - { - $returnObj = false; - if ($type === null) { - $returnObj = true; - $type = 'PKCS8'; - } - - $type = self::validatePlugin('Keys', $type, 'savePublicKey'); - if ($type === false) { - return false; - } - - if (!isset($this->y)) { - if (!isset($this->x) || !isset($this->p)) { - return false; - } - $this->y = $this->g->powMod($this->x, $this->p); - } - - $key = $type::savePublicKey($this->p, $this->q, $this->g, $this->y); - if (!$returnObj) { - return $key; - } - - $public = clone $this; - $public->load($key, 'PKCS8'); - - return $public; - } - - /** - * Returns the parameters - * - * A public / private key is only returned if the currently loaded "key" contains an x or y - * value. - * - * @see self::getPublicKey() - * @see self::getPrivateKey() - * @access public - * @param string $type optional - * @return mixed - */ - public function getParameters($type = 'PKCS1') - { - $type = self::validatePlugin('Keys', $type, 'saveParameters'); - if ($type === false) { - return false; - } - - if (!isset($this->p) || !isset($this->q) || !isset($this->g)) { - return false; - } - - return $type::saveParameters($this->p, $this->q, $this->g); + return ['L' => $this->p->getLength(), 'N' => $this->q->getLength()]; } /** @@ -442,145 +303,39 @@ class DSA extends AsymmetricKey } /** - * Create a signature + * Returns the parameters * - * @see self::verify() + * A public / private key is only returned if the currently loaded "key" contains an x or y + * value. + * + * @see self::getPublicKey() * @access public - * @param string $message - * @param string $format optional + * @param string $type optional * @return mixed */ - public function sign($message, $format = 'ASN1') + public function getParameters() { - $shortFormat = $format; - $format = self::validatePlugin('Signature', $format); - if ($format === false) { - return false; - } + $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters'); - if (empty($this->x)) { - if (empty($this->y)) { - throw new NoKeyLoadedException('No key has been loaded'); - } - throw new UnsupportedOperationException('A public key cannot be used to sign data'); - } - - if (empty($this->p)) { - throw new InsufficientSetupException('DSA Prime P is not set'); - } - - if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) { - $signature = ''; - $result = openssl_sign($message, $signature, $this->getPrivateKey(), $this->hash->getHash()); - - if ($result) { - if ($shortFormat == 'ASN1') { - return $signature; - } - - extract(ASN1Signature::load($signature)); - - return $format::save($r, $s); - } - } - - $h = $this->hash->hash($message); - $h = $this->bits2int($h); - - while (true) { - $k = BigInteger::randomRange(self::$one, $this->q->subtract(self::$one)); - $r = $this->g->powMod($k, $this->p); - list(, $r) = $r->divide($this->q); - if ($r->equals(self::$zero)) { - continue; - } - $kinv = $k->modInverse($this->q); - $temp = $h->add($this->x->multiply($r)); - $temp = $kinv->multiply($temp); - list(, $s) = $temp->divide($this->q); - if (!$s->equals(self::$zero)) { - break; - } - } - - // the following is an RFC6979 compliant implementation of deterministic DSA - // it's unused because it's mainly intended for use when a good CSPRNG isn't - // available. if phpseclib's CSPRNG isn't good then even key generation is - // suspect - /* - $h1 = $this->hash->hash($message); - $k = $this->computek($h1); - $r = $this->g->powMod($k, $this->p); - list(, $r) = $r->divide($this->q); - $kinv = $k->modInverse($this->q); - $h1 = $this->bits2int($h1); - $temp = $h1->add($this->x->multiply($r)); - $temp = $kinv->multiply($temp); - list(, $s) = $temp->divide($this->q); - */ - - return $format::save($r, $s); + $key = $type::saveParameters($this->p, $this->q, $this->g); + return DSA::load($key, 'PKCS1') + ->withHash($this->hash->getHash()) + ->withSignatureFormat($this->shortFormat); } /** - * Verify a signature + * Determines the signature padding mode + * + * Valid values are: ASN1, SSH2, Raw * - * @see self::verify() * @access public - * @param string $message - * @param string $signature - * @param string $format optional - * @return mixed + * @param string $padding */ - public function verify($message, $signature, $format = 'ASN1') + public function withSignatureFormat($format) { - $format = self::validatePlugin('Signature', $format); - if ($format === false) { - return false; - } - - $params = $format::load($signature); - if ($params === false || count($params) != 2) { - return false; - } - extract($params); - - if (empty($this->y)) { - if (empty($this->x)) { - throw new NoKeyLoadedException('No key has been loaded'); - } - throw new UnsupportedOperationException('A private key cannot be used to sign data'); - } - - if (empty($this->p)) { - throw new InsufficientSetupException('DSA Prime P is not set'); - } - - if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) { - $sig = $format != 'ASN1' ? ASN1Signature::save($r, $s) : $signature; - - $result = openssl_verify($message, $sig, $this->getPublicKey(), $this->hash->getHash()); - - if ($result != -1) { - return (bool) $result; - } - } - - $q_1 = $this->q->subtract(self::$one); - if (!$r->between(self::$one, $q_1) || !$s->between(self::$one, $q_1)) { - return false; - } - - $w = $s->modInverse($this->q); - $h = $this->hash->hash($message); - $h = $this->bits2int($h); - list(, $u1) = $h->multiply($w)->divide($this->q); - list(, $u2) = $r->multiply($w)->divide($this->q); - $v1 = $this->g->powMod($u1, $this->p); - $v2 = $this->y->powMod($u2, $this->p); - list(, $v) = $v1->multiply($v2)->divide($this->p); - list(, $v) = $v->divide($this->q); - - return $v->equals($r); + $new = clone $this; + $new->shortFormat = $format; + $new->format = self::validatePlugin('Signature', $format); + return $new; } } \ No newline at end of file diff --git a/phpseclib/Crypt/DSA/Parameters.php b/phpseclib/Crypt/DSA/Parameters.php new file mode 100644 index 00000000..7a26b29a --- /dev/null +++ b/phpseclib/Crypt/DSA/Parameters.php @@ -0,0 +1,39 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\DSA; + +use phpseclib\Crypt\DSA; + +/** + * DSA Parameters + * + * @package DSA + * @author Jim Wigginton + * @access public + */ +class Parameters extends DSA +{ + /** + * Returns the public key + * + * @param string $type + * @return string + */ + public function toString($type = 'PKCS1') + { + $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters'); + + return $type::saveParameters($this->p, $this->q, $this->g); + } +} diff --git a/phpseclib/Crypt/DSA/PrivateKey.php b/phpseclib/Crypt/DSA/PrivateKey.php new file mode 100644 index 00000000..7434ca9e --- /dev/null +++ b/phpseclib/Crypt/DSA/PrivateKey.php @@ -0,0 +1,159 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\DSA; + +use phpseclib\Crypt\DSA; +use phpseclib\Crypt\ECDSA\Signature\ASN1 as ASN1Signature; +use phpseclib\Math\BigInteger; +use phpseclib\Crypt\Common; + +/** + * DSA Private Key + * + * @package DSA + * @author Jim Wigginton + * @access public + */ +class PrivateKey extends DSA implements Common\PrivateKey +{ + use Common\PasswordProtected; + + /** + * DSA secret exponent x + * + * @var \phpseclib\Math\BigInteger + * @access private + */ + protected $x; + + /** + * Returns the public key + * + * If you do "openssl rsa -in private.rsa -pubout -outform PEM" you get a PKCS8 formatted key + * that contains a publicKeyAlgorithm AlgorithmIdentifier and a publicKey BIT STRING. + * An AlgorithmIdentifier contains an OID and a parameters field. With RSA public keys this + * parameters field is NULL. With DSA PKCS8 public keys it is not - it contains the p, q and g + * variables. The publicKey BIT STRING contains, simply, the y variable. This can be verified + * by getting a DSA PKCS8 public key: + * + * "openssl dsa -in private.dsa -pubout -outform PEM" + * + * ie. just swap out rsa with dsa in the rsa command above. + * + * A PKCS1 public key corresponds to the publicKey portion of the PKCS8 key. In the case of RSA + * the publicKey portion /is/ the key. In the case of DSA it is not. You cannot verify a signature + * without the parameters and the PKCS1 DSA public key format does not include the parameters. + * + * @see self::getPrivateKey() + * @access public + * @param string $type optional + * @return mixed + */ + public function getPublicKey() + { + $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey'); + + if (!isset($this->y)) { + $this->y = $this->g->powMod($this->x, $this->p); + } + + $key = $type::savePublicKey($this->p, $this->q, $this->g, $this->y); + + return DSA::load($key, 'PKCS8') + ->withHash($this->hash->getHash()) + ->withSignatureFormat($this->shortFormat); + } + + /** + * Create a signature + * + * @see self::verify() + * @access public + * @param string $message + * @return mixed + */ + public function sign($message) + { + $format = $this->format; + + if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) { + $signature = ''; + $result = openssl_sign($message, $signature, $this->toString('PKCS8'), $this->hash->getHash()); + + if ($result) { + if ($this->shortFormat == 'ASN1') { + return $signature; + } + + extract(ASN1Signature::load($signature)); + + return $format::save($r, $s); + } + } + + $h = $this->hash->hash($message); + $h = $this->bits2int($h); + + while (true) { + $k = BigInteger::randomRange(self::$one, $this->q->subtract(self::$one)); + $r = $this->g->powMod($k, $this->p); + list(, $r) = $r->divide($this->q); + if ($r->equals(self::$zero)) { + continue; + } + $kinv = $k->modInverse($this->q); + $temp = $h->add($this->x->multiply($r)); + $temp = $kinv->multiply($temp); + list(, $s) = $temp->divide($this->q); + if (!$s->equals(self::$zero)) { + break; + } + } + + // the following is an RFC6979 compliant implementation of deterministic DSA + // it's unused because it's mainly intended for use when a good CSPRNG isn't + // available. if phpseclib's CSPRNG isn't good then even key generation is + // suspect + /* + $h1 = $this->hash->hash($message); + $k = $this->computek($h1); + $r = $this->g->powMod($k, $this->p); + list(, $r) = $r->divide($this->q); + $kinv = $k->modInverse($this->q); + $h1 = $this->bits2int($h1); + $temp = $h1->add($this->x->multiply($r)); + $temp = $kinv->multiply($temp); + list(, $s) = $temp->divide($this->q); + */ + + return $format::save($r, $s); + } + + /** + * Returns the private key + * + * @param string $type + * @return string + */ + public function toString($type) + { + $type = self::validatePlugin('Keys', $type, 'savePrivateKey'); + + if (!isset($this->y)) { + $this->y = $this->g->powMod($this->x, $this->p); + } + + return $type::savePrivateKey($this->p, $this->q, $this->g, $this->y, $this->x, $this->password); + } +} diff --git a/phpseclib/Crypt/DSA/PublicKey.php b/phpseclib/Crypt/DSA/PublicKey.php new file mode 100644 index 00000000..e8aca903 --- /dev/null +++ b/phpseclib/Crypt/DSA/PublicKey.php @@ -0,0 +1,91 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\DSA; + +use phpseclib\Crypt\DSA; +use phpseclib\Crypt\ECDSA\Signature\ASN1 as ASN1Signature; +use phpseclib\Crypt\Common; + +/** + * DSA Public Key + * + * @package DSA + * @author Jim Wigginton + * @access public + */ +class PublicKey extends DSA implements Common\PublicKey +{ + use Common\Fingerprint; + + /** + * Verify a signature + * + * @see self::verify() + * @access public + * @param string $message + * @param string $signature + * @param string $format optional + * @return mixed + */ + public function verify($message, $signature) + { + $format = $this->format; + + $params = $format::load($signature); + if ($params === false || count($params) != 2) { + return false; + } + extract($params); + + if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) { + $sig = $format != 'ASN1' ? ASN1Signature::save($r, $s) : $signature; + + $result = openssl_verify($message, $sig, $this->toString('PKCS8'), $this->hash->getHash()); + + if ($result != -1) { + return (bool) $result; + } + } + + $q_1 = $this->q->subtract(self::$one); + if (!$r->between(self::$one, $q_1) || !$s->between(self::$one, $q_1)) { + return false; + } + + $w = $s->modInverse($this->q); + $h = $this->hash->hash($message); + $h = $this->bits2int($h); + list(, $u1) = $h->multiply($w)->divide($this->q); + list(, $u2) = $r->multiply($w)->divide($this->q); + $v1 = $this->g->powMod($u1, $this->p); + $v2 = $this->y->powMod($u2, $this->p); + list(, $v) = $v1->multiply($v2)->divide($this->p); + list(, $v) = $v->divide($this->q); + + return $v->equals($r); + } + + /** + * Returns the public key + * + * @param string $type + * @return string + */ + public function toString($type) + { + $type = self::validatePlugin('Keys', $type, 'savePublicKey'); + + return $type::savePublicKey($this->p, $this->q, $this->g, $this->y); + } +} diff --git a/phpseclib/Crypt/ECDSA.php b/phpseclib/Crypt/ECDSA.php index d18b404f..1587c583 100644 --- a/phpseclib/Crypt/ECDSA.php +++ b/phpseclib/Crypt/ECDSA.php @@ -10,13 +10,14 @@ * getPublicKey(); * * $plaintext = 'terrafrost'; * - * $signature = $privatekey->sign($plaintext, 'ASN1'); + * $signature = $private->sign($plaintext); * - * echo $publickey->verify($plaintext, $signature) ? 'verified' : 'unverified'; + * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified'; * ?> * * @@ -30,21 +31,18 @@ namespace phpseclib\Crypt; -use phpseclib\Math\BigInteger; use phpseclib\Crypt\Common\AsymmetricKey; -use phpseclib\Exception\UnsupportedCurveException; -use phpseclib\Exception\UnsupportedOperationException; -use phpseclib\Exception\UnsupportedAlgorithmException; -use phpseclib\Exception\NoKeyLoadedException; -use phpseclib\Exception\InsufficientSetupException; -use phpseclib\File\ASN1; -use phpseclib\File\ASN1\Maps\ECParameters; +use phpseclib\Crypt\ECDSA\PrivateKey; +use phpseclib\Crypt\ECDSA\PublicKey; +use phpseclib\Crypt\ECDSA\Parameters; use phpseclib\Crypt\ECDSA\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; use phpseclib\Crypt\ECDSA\Curves\Ed25519; use phpseclib\Crypt\ECDSA\Curves\Ed448; use phpseclib\Crypt\ECDSA\Keys\PKCS1; -use phpseclib\Crypt\ECDSA\Keys\PKCS8; -use phpseclib\Crypt\ECDSA\Signature\ASN1 as ASN1Signature; +use phpseclib\File\ASN1\Maps\ECParameters; +use phpseclib\File\ASN1; +use phpseclib\Exception\UnsupportedCurveException; +use phpseclib\Exception\UnsupportedAlgorithmException; /** * Pure-PHP implementation of ECDSA. @@ -53,7 +51,7 @@ use phpseclib\Crypt\ECDSA\Signature\ASN1 as ASN1Signature; * @author Jim Wigginton * @access public */ -class ECDSA extends AsymmetricKey +abstract class ECDSA extends AsymmetricKey { /** * Algorithm Name @@ -63,30 +61,35 @@ class ECDSA extends AsymmetricKey */ const ALGORITHM = 'ECDSA'; - /** - * Private Key dA - * - * sign() converts this to a BigInteger so one might wonder why this is a FiniteFieldInteger instead of - * a BigInteger. That's because a FiniteFieldInteger, when converted to a byte string, is null padded by - * a certain amount whereas a BigInteger isn't. - * - * @var object - */ - private $dA; - /** * Public Key QA * * @var object[] */ - private $QA; + protected $QA; /** * Curve * * @var \phpseclib\Crypt\ECDSA\BaseCurves\Base */ - private $curve; + protected $curve; + + /** + * Signature Format + * + * @var string + * @access private + */ + protected $format; + + /** + * Signature Format (Short) + * + * @var string + * @access private + */ + protected $shortFormat; /** * Curve Name @@ -96,44 +99,18 @@ class ECDSA extends AsymmetricKey private $curveName; /** - * Curve Order + * Context * - * Used for deterministic ECDSA - * - * @var \phpseclib\Math\BigInteger + * @var string */ - protected $q; - - /** - * Alias for the private key - * - * Used for deterministic ECDSA. AsymmetricKey expects $x. I don't like x because - * with x you have x * the base point yielding an (x, y)-coordinate that is the - * public key. But the x is different depending on which side of the equal sign - * you're on. It's less ambiguous if you do dA * base point = (x, y)-coordinate. - * - * @var \phpseclib\Math\BigInteger - */ - protected $x; - - /** - * Alias for the private key - * - * Used for deterministic ECDSA. AsymmetricKey expects $x. I don't like x because - * with x you have x * the base point yielding an (x, y)-coordinate that is the - * public key. But the x is different depending on which side of the equal sign - * you're on. It's less ambiguous if you do dA * base point = (x, y)-coordinate. - * - * @var \phpseclib\Math\BigInteger - */ - private $context; + protected $context; /** * Create public / private key pair. * * @access public * @param string $curve - * @return \phpseclib\Crypt\ECDSA[] + * @return \phpseclib\Crypt\ECDSA\PrivateKey */ public static function createKey($curve) { @@ -143,51 +120,62 @@ class ECDSA extends AsymmetricKey self::useBestEngine(); } - if (self::$engines['libsodium'] && $curve == 'Ed25519' && function_exists('sodium_crypto_sign_keypair')) { + $curve = strtolower($curve); + if (self::$engines['libsodium'] && $curve == 'ed25519' && function_exists('sodium_crypto_sign_keypair')) { $kp = sodium_crypto_sign_keypair(); - $privatekey = new static(); - $privatekey->load(sodium_crypto_sign_secretkey($kp)); + $privatekey = ECDSA::load(sodium_crypto_sign_secretkey($kp), 'libsodium'); + //$publickey = ECDSA::load(sodium_crypto_sign_publickey($kp), 'libsodium'); - $publickey = new static(); - $publickey->load(sodium_crypto_sign_publickey($kp)); + $privatekey->curveName = 'Ed25519'; + //$publickey->curveName = $curve; - $publickey->curveName = $privatekey->curveName = $curve; - - return compact('privatekey', 'publickey'); + return $privatekey; } - $privatekey = new static(); + $privatekey = new PrivateKey; $curveName = $curve; $curve = '\phpseclib\Crypt\ECDSA\Curves\\' . $curve; if (!class_exists($curve)) { - throw new UnsupportedCurveException('Named Curve of ' . $curve . ' is not supported'); + throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported'); } + + $reflect = new \ReflectionClass($curve); + $curveName = $reflect->isFinal() ? + $reflect->getParentClass()->getShortName() : + $reflect->getShortName(); + $curve = new $curve(); $privatekey->dA = $dA = $curve->createRandomMultiplier(); $privatekey->QA = $curve->multiplyPoint($curve->getBasePoint(), $dA); $privatekey->curve = $curve; - $publickey = clone $privatekey; - unset($publickey->dA); - unset($publickey->x); + //$publickey = clone $privatekey; + //unset($publickey->dA); + //unset($publickey->x); - $publickey->curveName = $privatekey->curveName = $curveName; + $privatekey->curveName = $curveName; + //$publickey->curveName = $curveName; - return compact('privatekey', 'publickey'); + if ($privatekey->curve instanceof TwistedEdwardsCurve) { + return $privatekey->withHash($curve::HASH); + } + + return $privatekey; } /** * Loads a public or private key * * Returns true on success and false on failure (ie. an incorrect password was provided or the key was malformed) - * + * @return bool * @access public * @param string $key - * @param int $type optional + * @param string $type optional + * @param string $password optional */ - public function load($key, $type = false) + public static function load($key, $type = false, $password = false) { self::initialize_static_variables(); @@ -195,54 +183,42 @@ class ECDSA extends AsymmetricKey self::useBestEngine(); } - if ($key instanceof ECDSA) { - $this->privateKeyFormat = $key->privateKeyFormat; - $this->publicKeyFormat = $key->publicKeyFormat; - $this->format = $key->format; - $this->dA = isset($key->dA) ? $key->dA : null; - $this->QA = $key->QA; - $this->curve = $key->curve; - $this->parametersFormat = $key->parametersFormat; - $this->hash = $key->hash; + $components = parent::load($key, $type, $password); - parent::load($key, false); - - return true; + if (!isset($components['dA']) && !isset($components['QA'])) { + $new = new Parameters; + $new->curve = $components['curve']; + return $new; } - $components = parent::load($key, $type); - if ($components === false) { - $this->clearKey(); - return false; + $new = isset($components['dA']) ? + new PrivateKey : + new PublicKey; + $new->curve = $components['curve']; + $new->QA = $components['QA']; + + if (isset($components['dA'])) { + $new->dA = $components['dA']; } - if ($components['curve'] instanceof Ed25519 && $this->hashManuallySet && $this->hash->getHash() != 'sha512') { - $this->clearKey(); - throw new UnsupportedAlgorithmException('Ed25519 only supports sha512 as a hash'); - } - if ($components['curve'] instanceof Ed448 && $this->hashManuallySet && $this->hash->getHash() != 'shake256-912') { - $this->clearKey(); - throw new UnsupportedAlgorithmException('Ed448 only supports shake256 with a length of 114 bytes'); + if ($new->curve instanceof TwistedEdwardsCurve) { + return $new->withHash($components['curve']::HASH); } - $this->curve = $components['curve']; - $this->QA = $components['QA']; - $this->dA = isset($components['dA']) ? $components['dA'] : null; - - return true; + return $new; } /** - * Removes a key + * Constructor * - * @access private + * PublicKey and PrivateKey objects can only be created from abstract RSA class */ - private function clearKey() + protected function __construct() { - $this->format = null; - $this->dA = null; - $this->QA = null; - $this->curve = null; + $this->format = self::validatePlugin('Signature', 'ASN1'); + $this->shortFormat = 'ASN1'; + + parent::__construct(); } /** @@ -306,117 +282,9 @@ class ECDSA extends AsymmetricKey */ public function getLength() { - if (!isset($this->QA)) { - return 0; - } - return $this->curve->getLength(); } - /** - * Is the key a private key? - * - * @access public - * @return bool - */ - public function isPrivateKey() - { - return isset($this->dA); - } - - /** - * Is the key a public key? - * - * @access public - * @return bool - */ - public function isPublicKey() - { - return isset($this->QA); - } - - /** - * Returns the private key - * - * @see self::getPublicKey() - * @access public - * @param string $type optional - * @return mixed - */ - public function getPrivateKey($type = 'PKCS8') - { - $type = self::validatePlugin('Keys', $type, 'savePrivateKey'); - if ($type === false) { - return false; - } - - if (!isset($this->dA)) { - return false; - } - - return $type::savePrivateKey($this->dA, $this->curve, $this->QA, $this->password); - } - - /** - * Returns the public key - * - * @see self::getPrivateKey() - * @access public - * @param string $type optional - * @return mixed - */ - public function getPublicKey($type = null) - { - $returnObj = false; - if ($type === null) { - $returnObj = true; - $type = 'PKCS8'; - } - $type = self::validatePlugin('Keys', $type, 'savePublicKey'); - if ($type === false) { - return false; - } - - if (!isset($this->QA)) { - return false; - } - - $key = $type::savePublicKey($this->curve, $this->QA); - if ($returnObj) { - $public = clone $this; - $public->load($key, 'PKCS8'); - - return $public; - } - return $key; - } - - /** - * Returns the parameters - * - * A public / private key is only returned if the currently loaded "key" contains an x or y - * value. - * - * @see self::getPublicKey() - * @see self::getPrivateKey() - * @access public - * @param string $type optional - * @return mixed - */ - public function getParameters($type = 'PKCS1') - { - $type = self::validatePlugin('Keys', $type, 'saveParameters'); - if ($type === false) { - return false; - } - - if (!isset($this->curve) || $this->curve instanceof TwistedEdwardsCurve) { - return false; - } - - return $type::saveParameters($this->curve); - } - /** * Returns the current engine being used * @@ -427,10 +295,6 @@ class ECDSA extends AsymmetricKey */ public function getEngine() { - if (!isset($this->curve)) { - throw new InsufficientSetupException('getEngine should not be called until after a key has been loaded'); - } - if ($this->curve instanceof TwistedEdwardsCurve) { return $this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context) ? 'libsodium' : 'PHP'; @@ -440,6 +304,41 @@ class ECDSA extends AsymmetricKey 'OpenSSL' : 'PHP'; } + /** + * Returns the parameters + * + * @see self::getPublicKey() + * @access public + * @param string $type optional + * @return mixed + */ + public function getParameters($type = 'PKCS1') + { + $type = self::validatePlugin('Keys', $type, 'saveParameters'); + + $key = $type::saveParameters($this->curve); + + return ECDSA::load($key, 'PKCS1') + ->withHash($this->hash->getHash()) + ->withSignatureFormat($this->shortFormat); + } + + /** + * Determines the signature padding mode + * + * Valid values are: ASN1, SSH2, Raw + * + * @access public + * @param string $padding + */ + public function withSignatureFormat($format) + { + $new = clone $this; + $new->shortFormat = $format; + $new->format = self::validatePlugin('Signature', $format); + return $new; + } + /** * Sets the context * @@ -450,11 +349,16 @@ class ECDSA extends AsymmetricKey * @access public * @param string $context optional */ - public function setContext($context = null) + public function withContext($context = null) { + if (!$this->curve instanceof TwistedEdwardsCurve) { + throw new UnsupportedCurveException('Only Ed25519 and Ed448 support contexts'); + } + + $new = clone $this; if (!isset($context)) { - $this->context = null; - return; + $new->context = null; + return $new; } if (!is_string($context)) { throw new \InvalidArgumentException('setContext expects a string'); @@ -462,7 +366,8 @@ class ECDSA extends AsymmetricKey if (strlen($context) > 255) { throw new \LengthException('The context is supposed to be, at most, 255 bytes long'); } - $this->context = $context; + $new->context = $context; + return $new; } /** @@ -471,7 +376,7 @@ class ECDSA extends AsymmetricKey * @access public * @param string $hash */ - public function setHash($hash) + public function withHash($hash) { if ($this->curve instanceof Ed25519 && $hash != 'sha512') { throw new UnsupportedAlgorithmException('Ed25519 only supports sha512 as a hash'); @@ -480,279 +385,6 @@ class ECDSA extends AsymmetricKey throw new UnsupportedAlgorithmException('Ed448 only supports shake256 with a length of 114 bytes'); } - parent::setHash($hash); - } - - /** - * Create a signature - * - * @see self::verify() - * @access public - * @param string $message - * @param string $format optional - * @return mixed - */ - public function sign($message, $format = 'ASN1') - { - if (!isset($this->dA)) { - if (!isset($this->QA)) { - throw new NoKeyLoadedException('No key has been loaded'); - } - throw new UnsupportedOperationException('A public key cannot be used to sign data'); - } - - $dA = $this->dA->toBigInteger(); - - $order = $this->curve->getOrder(); - - if ($this->curve instanceof TwistedEdwardsCurve) { - if ($this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context)) { - return sodium_crypto_sign_detached($message, $this->getPrivateKey('libsodium')); - } - - // contexts (Ed25519ctx) are supported but prehashing (Ed25519ph) is not. - // quoting https://tools.ietf.org/html/rfc8032#section-8.5 , - // "The Ed25519ph and Ed448ph variants ... SHOULD NOT be used" - $A = $this->curve->encodePoint($this->QA); - $curve = $this->curve; - $hash = new Hash($curve::HASH); - - $secret = substr($hash->hash($this->dA->secret), $curve::SIZE); - - if ($curve instanceof Ed25519) { - $dom = !isset($this->context) ? '' : - 'SigEd25519 no Ed25519 collisions' . "\0" . chr(strlen($this->context)) . $this->context; - } else { - $context = isset($this->context) ? $this->context : ''; - $dom = 'SigEd448' . "\0" . chr(strlen($context)) . $context; - } - // SHA-512(dom2(F, C) || prefix || PH(M)) - $r = $hash->hash($dom . $secret . $message); - $r = strrev($r); - $r = new BigInteger($r, 256); - list(, $r) = $r->divide($order); - $R = $curve->multiplyPoint($curve->getBasePoint(), $curve->convertInteger($r)); - $R = $curve->encodePoint($R); - $k = $hash->hash($dom . $R . $A . $message); - $k = strrev($k); - $k = new BigInteger($k, 256); - list(, $k) = $k->divide($order); - $S = $k->multiply($dA)->add($r); - list(, $S) = $S->divide($order); - $S = str_pad(strrev($S->toBytes()), $curve::SIZE, "\0"); - return $R . $S; - } - - $shortFormat = $format; - $format = self::validatePlugin('Signature', $format); - if ($format === false) { - return false; - } - - if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) { - $namedCurves = PKCS8::isUsingNamedCurves(); - - // use specified curves to avoid issues with OpenSSL possibly not supporting a given named curve; - // doing this may mean some curve-specific optimizations can't be used but idk if OpenSSL even - // has curve-specific optimizations - PKCS8::useSpecifiedCurve(); - - $signature = ''; - // altho PHP's OpenSSL bindings only supported ECDSA key creation in PHP 7.1 they've long - // supported signing / verification - $result = openssl_sign($message, $signature, $this->getPrivateKey(), $this->hash->getHash()); - - if ($namedCurves) { - PKCS8::useNamedCurve(); - } - - if ($result) { - if ($shortFormat == 'ASN1') { - return $signature; - } - - extract(ASN1Signature::load($signature)); - - return $shortFormat == 'SSH2' ? $format::save($r, $s, $this->getCurve()) : $format::save($r, $s); - } - } - - $e = $this->hash->hash($message); - $e = new BigInteger($e, 256); - - $Ln = $this->hash->getLength() - $order->getLength(); - $z = $Ln > 0 ? $e->bitwise_rightShift($Ln) : $e; - - while (true) { - $k = BigInteger::randomRange(self::$one, $order->subtract(self::$one)); - list($x, $y) = $this->curve->multiplyPoint($this->curve->getBasePoint(), $this->curve->convertInteger($k)); - $x = $x->toBigInteger(); - list(, $r) = $x->divide($order); - if ($r->equals(self::$zero)) { - continue; - } - $kinv = $k->modInverse($order); - $temp = $z->add($dA->multiply($r)); - $temp = $kinv->multiply($temp); - list(, $s) = $temp->divide($order); - if (!$s->equals(self::$zero)) { - break; - } - } - - // the following is an RFC6979 compliant implementation of deterministic ECDSA - // it's unused because it's mainly intended for use when a good CSPRNG isn't - // available. if phpseclib's CSPRNG isn't good then even key generation is - // suspect - /* - // if this were actually being used it'd probably be better if this lived in load() and createKey() - $this->q = $this->curve->getOrder(); - $dA = $this->dA->toBigInteger(); - $this->x = $dA; - - $h1 = $this->hash->hash($message); - $k = $this->computek($h1); - list($x, $y) = $this->curve->multiplyPoint($this->curve->getBasePoint(), $this->curve->convertInteger($k)); - $x = $x->toBigInteger(); - list(, $r) = $x->divide($this->q); - $kinv = $k->modInverse($this->q); - $h1 = $this->bits2int($h1); - $temp = $h1->add($dA->multiply($r)); - $temp = $kinv->multiply($temp); - list(, $s) = $temp->divide($this->q); - */ - - return $shortFormat == 'SSH2' ? $format::save($r, $s, $this->getCurve()) : $format::save($r, $s); - } - - /** - * Verify a signature - * - * @see self::verify() - * @access public - * @param string $message - * @param string $format optional - * @return mixed - */ - public function verify($message, $signature, $format = 'ASN1') - { - if (!isset($this->QA)) { - if (!isset($this->dA)) { - throw new NoKeyLoadedException('No key has been loaded'); - } - throw new UnsupportedOperationException('A private key cannot be used to verify data'); - } - - $order = $this->curve->getOrder(); - - if ($this->curve instanceof TwistedEdwardsCurve) { - if ($this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context)) { - return sodium_crypto_sign_verify_detached($signature, $message, $this->getPublicKey('libsodium')); - } - - $curve = $this->curve; - if (strlen($signature) != 2 * $curve::SIZE) { - return false; - } - - $R = substr($signature, 0, $curve::SIZE); - $S = substr($signature, $curve::SIZE); - - try { - $R = PKCS1::extractPoint($R, $curve); - $R = $this->curve->convertToInternal($R); - } catch (\Exception $e) { - return false; - } - - $S = strrev($S); - $S = new BigInteger($S, 256); - - if ($S->compare($order) >= 0) { - return false; - } - - $A = $curve->encodePoint($this->QA); - - if ($curve instanceof Ed25519) { - $dom2 = !isset($this->context) ? '' : - 'SigEd25519 no Ed25519 collisions' . "\0" . chr(strlen($this->context)) . $this->context; - } else { - $context = isset($this->context) ? $this->context : ''; - $dom2 = 'SigEd448' . "\0" . chr(strlen($context)) . $context; - } - - $hash = new Hash($curve::HASH); - $k = $hash->hash($dom2 . substr($signature, 0, $curve::SIZE) . $A . $message); - $k = strrev($k); - $k = new BigInteger($k, 256); - list(, $k) = $k->divide($order); - - $qa = $curve->convertToInternal($this->QA); - - $lhs = $curve->multiplyPoint($curve->getBasePoint(), $curve->convertInteger($S)); - $rhs = $curve->multiplyPoint($qa, $curve->convertInteger($k)); - $rhs = $curve->addPoint($rhs, $R); - $rhs = $curve->convertToAffine($rhs); - - return $lhs[0]->equals($rhs[0]) && $lhs[1]->equals($rhs[1]); - } - - $format = self::validatePlugin('Signature', $format); - if ($format === false) { - return false; - } - - $params = $format::load($signature); - if ($params === false || count($params) != 2) { - return false; - } - extract($params); - - if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) { - $namedCurves = PKCS8::isUsingNamedCurves(); - - PKCS8::useSpecifiedCurve(); - - $sig = $format != 'ASN1' ? ASN1Signature::save($r, $s) : $signature; - - $result = openssl_verify($message, $sig, $this->getPublicKey(), $this->hash->getHash()); - - if ($namedCurves) { - PKCS8::useNamedCurve(); - } - - if ($result != -1) { - return (bool) $result; - } - } - - $n_1 = $order->subtract(self::$one); - if (!$r->between(self::$one, $n_1) || !$s->between(self::$one, $n_1)) { - return false; - } - - $e = $this->hash->hash($message); - $e = new BigInteger($e, 256); - - $Ln = $this->hash->getLength() - $order->getLength(); - $z = $Ln > 0 ? $e->bitwise_rightShift($Ln) : $e; - - $w = $s->modInverse($order); - list(, $u1) = $z->multiply($w)->divide($order); - list(, $u2) = $r->multiply($w)->divide($order); - - $u1 = $this->curve->convertInteger($u1); - $u2 = $this->curve->convertInteger($u2); - - list($x1, $y1) = $this->curve->multiplyAddPoints( - [$this->curve->getBasePoint(), $this->QA], - [$u1, $u2] - ); - - $x1 = $x1->toBigInteger(); - list(, $x1) = $x1->divide($order); - - return $x1->equals($r); + return parent::withHash($hash); } } \ No newline at end of file diff --git a/phpseclib/Crypt/ECDSA/Parameters.php b/phpseclib/Crypt/ECDSA/Parameters.php new file mode 100644 index 00000000..040633ca --- /dev/null +++ b/phpseclib/Crypt/ECDSA/Parameters.php @@ -0,0 +1,39 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\ECDSA; + +use phpseclib\Crypt\ECDSA; + +/** + * ECDSA Parameters + * + * @package ECDSA + * @author Jim Wigginton + * @access public + */ +class Parameters extends ECDSA +{ + /** + * Returns the public key + * + * @param string $type + * @return string + */ + public function toString($type = 'PKCS1') + { + $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters'); + + return $type::saveParameters($this->curve); + } +} diff --git a/phpseclib/Crypt/ECDSA/PrivateKey.php b/phpseclib/Crypt/ECDSA/PrivateKey.php new file mode 100644 index 00000000..f79dabdb --- /dev/null +++ b/phpseclib/Crypt/ECDSA/PrivateKey.php @@ -0,0 +1,214 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\ECDSA; + +use phpseclib\Crypt\ECDSA; +use phpseclib\Crypt\ECDSA\Signature\ASN1 as ASN1Signature; +use phpseclib\Math\BigInteger; +use phpseclib\Crypt\ECDSA\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib\Crypt\Hash; +use phpseclib\Crypt\ECDSA\Curves\Ed25519; +use phpseclib\Crypt\ECDSA\Keys\PKCS8; +use phpseclib\Crypt\Common; + +/** + * ECDSA Private Key + * + * @package ECDSA + * @author Jim Wigginton + * @access public + */ +class PrivateKey extends ECDSA implements Common\PrivateKey +{ + use Common\PasswordProtected; + + /** + * Private Key dA + * + * sign() converts this to a BigInteger so one might wonder why this is a FiniteFieldInteger instead of + * a BigInteger. That's because a FiniteFieldInteger, when converted to a byte string, is null padded by + * a certain amount whereas a BigInteger isn't. + * + * @var object + */ + protected $dA; + + /** + * Create a signature + * + * @see self::verify() + * @access public + * @param string $message + * @return mixed + */ + public function sign($message) + { + $dA = $this->dA->toBigInteger(); + + $order = $this->curve->getOrder(); + + if ($this->curve instanceof TwistedEdwardsCurve) { + if ($this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context)) { + return sodium_crypto_sign_detached($message, $this->toString('libsodium')); + } + + // contexts (Ed25519ctx) are supported but prehashing (Ed25519ph) is not. + // quoting https://tools.ietf.org/html/rfc8032#section-8.5 , + // "The Ed25519ph and Ed448ph variants ... SHOULD NOT be used" + $A = $this->curve->encodePoint($this->QA); + $curve = $this->curve; + $hash = new Hash($curve::HASH); + + $secret = substr($hash->hash($this->dA->secret), $curve::SIZE); + + if ($curve instanceof Ed25519) { + $dom = !isset($this->context) ? '' : + 'SigEd25519 no Ed25519 collisions' . "\0" . chr(strlen($this->context)) . $this->context; + } else { + $context = isset($this->context) ? $this->context : ''; + $dom = 'SigEd448' . "\0" . chr(strlen($context)) . $context; + } + // SHA-512(dom2(F, C) || prefix || PH(M)) + $r = $hash->hash($dom . $secret . $message); + $r = strrev($r); + $r = new BigInteger($r, 256); + list(, $r) = $r->divide($order); + $R = $curve->multiplyPoint($curve->getBasePoint(), $curve->convertInteger($r)); + $R = $curve->encodePoint($R); + $k = $hash->hash($dom . $R . $A . $message); + $k = strrev($k); + $k = new BigInteger($k, 256); + list(, $k) = $k->divide($order); + $S = $k->multiply($dA)->add($r); + list(, $S) = $S->divide($order); + $S = str_pad(strrev($S->toBytes()), $curve::SIZE, "\0"); + return $R . $S; + } + + $shortFormat = $this->shortFormat; + $format = $this->format; + if ($format === false) { + return false; + } + + if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) { + $namedCurves = PKCS8::isUsingNamedCurves(); + + // use specified curves to avoid issues with OpenSSL possibly not supporting a given named curve; + // doing this may mean some curve-specific optimizations can't be used but idk if OpenSSL even + // has curve-specific optimizations + PKCS8::useSpecifiedCurve(); + + $signature = ''; + // altho PHP's OpenSSL bindings only supported ECDSA key creation in PHP 7.1 they've long + // supported signing / verification + $result = openssl_sign($message, $signature, $this->toString('PKCS8'), $this->hash->getHash()); + + if ($namedCurves) { + PKCS8::useNamedCurve(); + } + + if ($result) { + if ($shortFormat == 'ASN1') { + return $signature; + } + + extract(ASN1Signature::load($signature)); + + return $shortFormat == 'SSH2' ? $format::save($r, $s, $this->getCurve()) : $format::save($r, $s); + } + } + + $e = $this->hash->hash($message); + $e = new BigInteger($e, 256); + + $Ln = $this->hash->getLength() - $order->getLength(); + $z = $Ln > 0 ? $e->bitwise_rightShift($Ln) : $e; + + while (true) { + $k = BigInteger::randomRange(self::$one, $order->subtract(self::$one)); + list($x, $y) = $this->curve->multiplyPoint($this->curve->getBasePoint(), $this->curve->convertInteger($k)); + $x = $x->toBigInteger(); + list(, $r) = $x->divide($order); + if ($r->equals(self::$zero)) { + continue; + } + $kinv = $k->modInverse($order); + $temp = $z->add($dA->multiply($r)); + $temp = $kinv->multiply($temp); + list(, $s) = $temp->divide($order); + if (!$s->equals(self::$zero)) { + break; + } + } + + // the following is an RFC6979 compliant implementation of deterministic ECDSA + // it's unused because it's mainly intended for use when a good CSPRNG isn't + // available. if phpseclib's CSPRNG isn't good then even key generation is + // suspect + /* + // if this were actually being used it'd probably be better if this lived in load() and createKey() + $this->q = $this->curve->getOrder(); + $dA = $this->dA->toBigInteger(); + $this->x = $dA; + + $h1 = $this->hash->hash($message); + $k = $this->computek($h1); + list($x, $y) = $this->curve->multiplyPoint($this->curve->getBasePoint(), $this->curve->convertInteger($k)); + $x = $x->toBigInteger(); + list(, $r) = $x->divide($this->q); + $kinv = $k->modInverse($this->q); + $h1 = $this->bits2int($h1); + $temp = $h1->add($dA->multiply($r)); + $temp = $kinv->multiply($temp); + list(, $s) = $temp->divide($this->q); + */ + + return $shortFormat == 'SSH2' ? $format::save($r, $s, $this->getCurve()) : $format::save($r, $s); + } + + /** + * Returns the private key + * + * @param string $type + * @return string + */ + public function toString($type) + { + $type = self::validatePlugin('Keys', $type, 'savePrivateKey'); + + return $type::savePrivateKey($this->dA, $this->curve, $this->QA, $this->password); + } + + /** + * Returns the public key + * + * @see self::getPrivateKey() + * @access public + * @return mixed + */ + public function getPublicKey() + { + $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey'); + + $key = $type::savePublicKey($this->curve, $this->QA); + $key = ECDSA::load($key, 'PKCS8') + ->withHash($this->hash->getHash()) + ->withSignatureFormat($this->shortFormat); + if ($this->curve instanceof TwistedEdwardsCurve) { + $key = $key->withContext($this->context); + } + return $key; + } +} diff --git a/phpseclib/Crypt/ECDSA/PublicKey.php b/phpseclib/Crypt/ECDSA/PublicKey.php new file mode 100644 index 00000000..ef09a97e --- /dev/null +++ b/phpseclib/Crypt/ECDSA/PublicKey.php @@ -0,0 +1,170 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\ECDSA; + +use phpseclib\Crypt\ECDSA; +use phpseclib\Crypt\Hash; +use phpseclib\Math\BigInteger; +use phpseclib\Crypt\ECDSA\Signature\ASN1 as ASN1Signature; +use phpseclib\Crypt\ECDSA\BaseCurves\TwistedEdwards as TwistedEdwardsCurve; +use phpseclib\Crypt\ECDSA\Curves\Ed25519; +use phpseclib\Crypt\ECDSA\Keys\PKCS1; +use phpseclib\Crypt\ECDSA\Keys\PKCS8; +use phpseclib\Crypt\Common; + +/** + * ECDSA Public Key + * + * @package ECDSA + * @author Jim Wigginton + * @access public + */ +class PublicKey extends ECDSA implements Common\PublicKey +{ + use Common\Fingerprint; + + /** + * Verify a signature + * + * @see self::verify() + * @access public + * @param string $message + * @param string $signature + * @return mixed + */ + public function verify($message, $signature) + { + $order = $this->curve->getOrder(); + + if ($this->curve instanceof TwistedEdwardsCurve) { + if ($this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context)) { + return sodium_crypto_sign_verify_detached($signature, $message, $this->toString('libsodium')); + } + + $curve = $this->curve; + if (strlen($signature) != 2 * $curve::SIZE) { + return false; + } + + $R = substr($signature, 0, $curve::SIZE); + $S = substr($signature, $curve::SIZE); + + try { + $R = PKCS1::extractPoint($R, $curve); + $R = $this->curve->convertToInternal($R); + } catch (\Exception $e) { + return false; + } + + $S = strrev($S); + $S = new BigInteger($S, 256); + + if ($S->compare($order) >= 0) { + return false; + } + + $A = $curve->encodePoint($this->QA); + + if ($curve instanceof Ed25519) { + $dom2 = !isset($this->context) ? '' : + 'SigEd25519 no Ed25519 collisions' . "\0" . chr(strlen($this->context)) . $this->context; + } else { + $context = isset($this->context) ? $this->context : ''; + $dom2 = 'SigEd448' . "\0" . chr(strlen($context)) . $context; + } + + $hash = new Hash($curve::HASH); + $k = $hash->hash($dom2 . substr($signature, 0, $curve::SIZE) . $A . $message); + $k = strrev($k); + $k = new BigInteger($k, 256); + list(, $k) = $k->divide($order); + + $qa = $curve->convertToInternal($this->QA); + + $lhs = $curve->multiplyPoint($curve->getBasePoint(), $curve->convertInteger($S)); + $rhs = $curve->multiplyPoint($qa, $curve->convertInteger($k)); + $rhs = $curve->addPoint($rhs, $R); + $rhs = $curve->convertToAffine($rhs); + + return $lhs[0]->equals($rhs[0]) && $lhs[1]->equals($rhs[1]); + } + + $format = $this->format; + + $params = $format::load($signature); + if ($params === false || count($params) != 2) { + return false; + } + extract($params); + + if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) { + $namedCurves = PKCS8::isUsingNamedCurves(); + + PKCS8::useSpecifiedCurve(); + + $sig = $format != 'ASN1' ? ASN1Signature::save($r, $s) : $signature; + + $result = openssl_verify($message, $sig, $this->toString('PKCS8'), $this->hash->getHash()); + + if ($namedCurves) { + PKCS8::useNamedCurve(); + } + + if ($result != -1) { + return (bool) $result; + } + } + + $n_1 = $order->subtract(self::$one); + if (!$r->between(self::$one, $n_1) || !$s->between(self::$one, $n_1)) { + return false; + } + + $e = $this->hash->hash($message); + $e = new BigInteger($e, 256); + + $Ln = $this->hash->getLength() - $order->getLength(); + $z = $Ln > 0 ? $e->bitwise_rightShift($Ln) : $e; + + $w = $s->modInverse($order); + list(, $u1) = $z->multiply($w)->divide($order); + list(, $u2) = $r->multiply($w)->divide($order); + + $u1 = $this->curve->convertInteger($u1); + $u2 = $this->curve->convertInteger($u2); + + list($x1, $y1) = $this->curve->multiplyAddPoints( + [$this->curve->getBasePoint(), $this->QA], + [$u1, $u2] + ); + + $x1 = $x1->toBigInteger(); + list(, $x1) = $x1->divide($order); + + return $x1->equals($r); + } + + /** + * Returns the public key + * + * @param string $type + * @return string + */ + public function toString($type) + { + $type = self::validatePlugin('Keys', $type, 'savePublicKey'); + + return $type::savePublicKey($this->curve, $this->QA); + } +} diff --git a/phpseclib/Crypt/PublicKeyLoader.php b/phpseclib/Crypt/PublicKeyLoader.php new file mode 100644 index 00000000..6dfc6def --- /dev/null +++ b/phpseclib/Crypt/PublicKeyLoader.php @@ -0,0 +1,74 @@ + + * @copyright 2009 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt; + +use phpseclib\Exception\NoKeyLoadedException; +use phpseclib\Crypt\Common\PrivateKey; +use phpseclib\File\X509; + +/** + * PublicKeyLoader + * + * @package Common + * @author Jim Wigginton + * @access public + */ +abstract class PublicKeyLoader +{ + /** + * Loads a public or private key + * + * @return AsymmetricKey + * @access public + * @param string $key + * @param string $password optional + */ + public static function load($key, $password = false) + { + try { + $new = ECDSA::load($key, false, $password); + } catch (\Exception $e) {} + + if (!isset($new)) { + try { + $new = RSA::load($key, false, $password); + } catch (\Exception $e) {} + } + + if (!isset($new)) { + try { + $new = DSA::load($key, false, $password); + } catch (\Exception $e) {} + } + + if (isset($new)) { + return $new instanceof PrivateKey ? + $new->withPassword($password) : + $new; + } + + try { + $x509 = new X509(); + $x509->loadX509($key); + $key = $x509->getPublicKey(); + if ($key) { + return $key; + } + } catch (\Exception $e) {} + + throw new NoKeyLoadedException('Unable to read key'); + } +} diff --git a/phpseclib/Crypt/RSA.php b/phpseclib/Crypt/RSA.php index 7dd637b7..096972bf 100644 --- a/phpseclib/Crypt/RSA.php +++ b/phpseclib/Crypt/RSA.php @@ -10,13 +10,14 @@ * getPublicKey(); * * $plaintext = 'terrafrost'; * - * $ciphertext = $publickey->encrypt($plaintext); + * $ciphertext = $public->encrypt($plaintext); * - * echo $privatekey->decrypt($ciphertext); + * echo $private->decrypt($ciphertext); * ?> * * @@ -25,13 +26,14 @@ * getPublicKey(); * * $plaintext = 'terrafrost'; * - * $signature = $privatekey->sign($plaintext); + * $signature = $private->sign($plaintext); * - * echo $publickey->verify($plaintext, $signature) ? 'verified' : 'unverified'; + * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified'; * ?> * * @@ -45,14 +47,12 @@ namespace phpseclib\Crypt; -use phpseclib\File\ASN1; -use phpseclib\Math\BigInteger; -use phpseclib\Common\Functions\Strings; -use phpseclib\File\ASN1\Maps\DigestInfo; use phpseclib\Crypt\Common\AsymmetricKey; -use phpseclib\Exception\UnsupportedAlgorithmException; -use phpseclib\Exception\UnsupportedOperationException; -use phpseclib\Exception\NoKeyLoadedException; +use phpseclib\Crypt\RSA\PrivateKey; +use phpseclib\Crypt\RSA\PublicKey; +use phpseclib\Math\BigInteger; +use phpseclib\Exceptions\UnsupportedAlgorithmException; +use phpseclib\Exceptions\InconsistentSetupException; /** * Pure-PHP PKCS#1 compliant implementation of RSA. @@ -61,7 +61,7 @@ use phpseclib\Exception\NoKeyLoadedException; * @author Jim Wigginton * @access public */ -class RSA extends AsymmetricKey +abstract class RSA extends AsymmetricKey { /** * Algorithm Name @@ -85,27 +85,27 @@ class RSA extends AsymmetricKey * @see self::setHash() * @see self::setMGFHash() */ - const PADDING_OAEP = 1; + const ENCRYPTION_OAEP = 1; /** * Use PKCS#1 padding. * * Although self::PADDING_OAEP / self::PADDING_PSS offers more security, including PKCS#1 padding is necessary for purposes of backwards * compatibility with protocols (like SSH-1) written before OAEP's introduction. */ - const PADDING_PKCS1 = 2; + const ENCRYPTION_PKCS1 = 2; /** * Do not use any padding * * Although this method is not recommended it can none-the-less sometimes be useful if you're trying to decrypt some legacy * stuff, if you're trying to diagnose why an encrypted message isn't decrypting, etc. */ - const PADDING_NONE = 3; + const ENCRYPTION_NONE = 4; /** * Use PKCS#1 padding with PKCS1 v1.5 compatibility * * A PKCS1 v2.1 encrypted message may not successfully decrypt with a PKCS1 v1.5 implementation (such as OpenSSL). */ - const PADDING_PKCS15_COMPAT = 6; + const ENCRYPTION_PKCS15_COMPAT = 8; /**#@-*/ /**#@+ @@ -123,68 +123,32 @@ class RSA extends AsymmetricKey * @see self::setMGFHash() * @see self::setHash() */ - const PADDING_PSS = 4; + const SIGNATURE_PSS = 16; /** * Use a relaxed version of PKCS#1 padding for signature verification */ - const PADDING_RELAXED_PKCS1 = 5; + const SIGNATURE_RELAXED_PKCS1 = 32; + /** + * Use PKCS#1 padding for signature verification + */ + const SIGNATURE_PKCS1 = 64; /**#@-*/ /** - * Modulus (ie. n) + * Encryption padding mode * - * @var \phpseclib\Math\BigInteger + * @var int * @access private */ - private $modulus; + protected $encryptionPadding = self::ENCRYPTION_OAEP; /** - * Modulus length + * Signature padding mode * - * @var \phpseclib\Math\BigInteger + * @var int * @access private */ - private $k; - - /** - * Exponent (ie. e or d) - * - * @var \phpseclib\Math\BigInteger - * @access private - */ - private $exponent; - - /** - * Primes for Chinese Remainder Theorem (ie. p and q) - * - * @var array - * @access private - */ - private $primes; - - /** - * Exponents for Chinese Remainder Theorem (ie. dP and dQ) - * - * @var array - * @access private - */ - private $exponents; - - /** - * Coefficients for Chinese Remainder Theorem (ie. qInv) - * - * @var array - * @access private - */ - private $coefficients; - - /** - * Hash name - * - * @var string - * @access private - */ - private $hashName; + protected $signaturePadding = self::SIGNATURE_PSS; /** * Length of hash function output @@ -192,7 +156,7 @@ class RSA extends AsymmetricKey * @var int * @access private */ - private $hLen; + protected $hLen; /** * Length of salt @@ -200,7 +164,15 @@ class RSA extends AsymmetricKey * @var int * @access private */ - private $sLen; + protected $sLen; + + /** + * Label + * + * @var string + * @access private + */ + protected $label = ''; /** * Hash function for the Mask Generation Function @@ -208,7 +180,7 @@ class RSA extends AsymmetricKey * @var \phpseclib\Crypt\Hash * @access private */ - private $mgfHash; + protected $mgfHash; /** * Length of MGF hash function output @@ -216,18 +188,34 @@ class RSA extends AsymmetricKey * @var int * @access private */ - private $mgfHLen; + protected $mgfHLen; /** - * Public Exponent + * Modulus (ie. n) * - * @var mixed + * @var \phpseclib\Math\BigInteger * @access private */ - private $publicExponent = false; + protected $modulus; /** - * Public exponent + * Modulus length + * + * @var \phpseclib\Math\BigInteger + * @access private + */ + protected $k; + + /** + * Exponent (ie. e or d) + * + * @var \phpseclib\Math\BigInteger + * @access private + */ + protected $exponent; + + /** + * Default public exponent * * @var int * @link http://en.wikipedia.org/wiki/65537_%28number%29 @@ -235,14 +223,6 @@ class RSA extends AsymmetricKey */ private static $defaultExponent = 65537; - /** - * Is the loaded key a public key? - * - * @var bool - * @access private - */ - private $isPublic = false; - /** * Smallest Prime * @@ -259,36 +239,7 @@ class RSA extends AsymmetricKey private static $smallestPrime = 4096; /** - * Enable Blinding? - * - * @var bool - * @access private - */ - private static $enableBlinding = true; - - /** - * The constructor - * - * If you want to make use of the openssl extension, you'll need to set the mode manually, yourself. The reason - * \phpseclib\Crypt\RSA doesn't do it is because OpenSSL doesn't fail gracefully. openssl_pkey_new(), in particular, requires - * openssl.cnf be present somewhere and, unfortunately, the only real way to find out is too late. - * - * @return \phpseclib\Crypt\RSA - * @access public - */ - public function __construct() - { - parent::__construct(); - - //$this->hash = new Hash('sha256'); - $this->hLen = $this->hash->getLengthInBytes(); - $this->hashName = 'sha256'; - $this->mgfHash = new Hash('sha256'); - $this->mgfHLen = $this->mgfHash->getLengthInBytes(); - } - - /** - * Sets the public exponent + * Sets the public exponent for key generation * * This will be 65537 unless changed. * @@ -301,7 +252,7 @@ class RSA extends AsymmetricKey } /** - * Sets the smallest prime number in bits + * Sets the smallest prime number in bits. Used for key generation * * This will be 4096 unless changed. * @@ -314,13 +265,11 @@ class RSA extends AsymmetricKey } /** - * Create public / private key pair + * Create a private key * - * Returns an array with the following two elements: - * - 'privatekey': The private key. - * - 'publickey': The public key. + * The public key can be extracted from the private key * - * @return array + * @return RSA * @access public * @param int $bits */ @@ -406,7 +355,7 @@ class RSA extends AsymmetricKey // coefficient INTEGER, -- (inverse of q) mod p // otherPrimeInfos OtherPrimeInfos OPTIONAL // } - $privatekey = new RSA(); + $privatekey = new PrivateKey; $privatekey->modulus = $n; $privatekey->k = $bits >> 3; $privatekey->publicExponent = $e; @@ -416,14 +365,16 @@ class RSA extends AsymmetricKey $privatekey->exponents = $exponents; $privatekey->coefficients = $coefficients; - $publickey = new RSA(); + /* + $publickey = new PublicKey; $publickey->modulus = $n; $publickey->k = $bits >> 3; $publickey->exponent = $e; $publickey->publicExponent = $e; $publickey->isPublic = true; + */ - return compact('privatekey', 'publickey'); + return $privatekey; } /** @@ -433,450 +384,53 @@ class RSA extends AsymmetricKey * * @return bool * @access public - * @param string|RSA|array $key - * @param int|bool $type optional + * @param string $key + * @param string $type optional + * @param string $password optional */ - public function load($key, $type = false) + public static function load($key, $type = false, $password = false) { - if ($key instanceof RSA) { - $this->privateKeyFormat = $key->privateKeyFormat; - $this->publicKeyFormat = $key->publicKeyFormat; - $this->format = $key->format; - $this->k = $key->k; - $this->hLen = $key->hLen; - $this->sLen = $key->sLen; - $this->mgfHLen = $key->mgfHLen; - $this->password = $key->password; - $this->isPublic = $key->isPublic; + self::initialize_static_variables(); - if (is_object($key->hash)) { - $this->hashName = $key->hash->getHash(); - $this->hash = new Hash($this->hashName); - } - if (is_object($key->mgfHash)) { - $this->mgfHash = new Hash($key->mgfHash->getHash()); - } + $components = parent::load($key, $type, $password); - if (is_object($key->modulus)) { - $this->modulus = clone $key->modulus; - } - if (is_object($key->exponent)) { - $this->exponent = clone $key->exponent; - } - if (is_object($key->publicExponent)) { - $this->publicExponent = clone $key->publicExponent; - } + $key = $components['isPublicKey'] ? + new PublicKey : + new PrivateKey; - $this->primes = []; - $this->exponents = []; - $this->coefficients = []; - - foreach ($this->primes as $prime) { - $this->primes[] = clone $prime; - } - foreach ($this->exponents as $exponent) { - $this->exponents[] = clone $exponent; - } - foreach ($this->coefficients as $coefficient) { - $this->coefficients[] = clone $coefficient; - } - - return true; - } - - $components = parent::load($key, $type); - if ($components === false) { - $this->comment = null; - $this->modulus = null; - $this->k = null; - $this->exponent = null; - $this->primes = null; - $this->exponents = null; - $this->coefficients = null; - $this->publicExponent = null; - $this->isPublic = false; - - return false; - } - - $this->isPublic = false; - $this->modulus = $components['modulus']; - $this->k = $this->modulus->getLengthInBytes(); - $this->exponent = isset($components['privateExponent']) ? $components['privateExponent'] : $components['publicExponent']; - if (isset($components['primes'])) { - $this->primes = $components['primes']; - $this->exponents = $components['exponents']; - $this->coefficients = $components['coefficients']; - $this->publicExponent = $components['publicExponent']; - } else { - $this->primes = []; - $this->exponents = []; - $this->coefficients = []; - $this->publicExponent = false; - } + $key->format = $components['format']; + $key->modulus = $components['modulus']; + $key->publicExponent = $components['publicExponent']; + $key->k = $key->modulus->getLengthInBytes(); if ($components['isPublicKey']) { - $this->setPublicKey(); - } - - return true; - } - - /** - * Returns the private key - * - * The private key is only returned if the currently loaded key contains the constituent prime numbers. - * - * @see self::getPublicKey() - * @access public - * @param string $type optional - * @return mixed - */ - public function getPrivateKey($type = 'PKCS8') - { - $type = self::validatePlugin('Keys', $type, 'savePrivateKey'); - if ($type === false) { - return false; - } - - if (empty($this->primes)) { - return false; - } - - return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password); - - /* - $key = $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password); - if ($key !== false || count($this->primes) == 2) { + $key->exponent = $key->publicExponent; return $key; } - $nSize = $this->getSize() >> 1; + $key->privateExponent = $components['privateExponent']; + $key->exponent = $key->privateExponent; + $key->primes = $components['primes']; + $key->exponents = $components['exponents']; + $key->coefficients = $components['coefficients']; - $primes = [1 => clone self::$one, clone self::$one]; - $i = 1; - foreach ($this->primes as $prime) { - $primes[$i] = $primes[$i]->multiply($prime); - if ($primes[$i]->getLength() >= $nSize) { - $i++; - } - } - - $exponents = []; - $coefficients = [2 => $primes[2]->modInverse($primes[1])]; - - foreach ($primes as $i => $prime) { - $temp = $prime->subtract(self::$one); - $exponents[$i] = $this->modulus->modInverse($temp); - } - - return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $primes, $exponents, $coefficients, $this->password); - */ + return $key; } /** - * Returns a minimalistic private key + * Constructor * - * Returns the private key without the prime number constituents. Structurally identical to a public key that - * hasn't been set as the public key - * - * @see self::getPrivateKey() - * @access private - * @param string $type optional - * @return mixed + * PublicKey and PrivateKey objects can only be created from abstract RSA class */ - protected function getPrivatePublicKey($type = 'PKCS8') + protected function __construct() { - $type = self::validatePlugin('Keys', $type, 'savePublicKey'); - if ($type === false) { - return false; - } + parent::__construct(); - if (empty($this->modulus) || empty($this->exponent)) { - return false; - } - - $oldFormat = $this->publicKeyFormat; - $this->publicKeyFormat = $type; - $temp = $type::savePublicKey($this->modulus, $this->exponent); - $this->publicKeyFormat = $oldFormat; - return $temp; - } - - /** - * Returns the key size - * - * More specifically, this returns the size of the modulo in bits. - * - * @access public - * @return int - */ - public function getLength() - { - return !isset($this->modulus) ? 0 : $this->modulus->getLength(); - } - - /** - * Returns the current engine being used - * - * @see self::useInternalEngine() - * @see self::useBestEngine() - * @access public - * @return string - */ - public function getEngine() - { - return 'PHP'; - } - - /** - * Defines the public key - * - * Some private key formats define the public exponent and some don't. Those that don't define it are problematic when - * used in certain contexts. For example, in SSH-2, RSA authentication works by sending the public key along with a - * message signed by the private key to the server. The SSH-2 server looks the public key up in an index of public keys - * and if it's present then proceeds to verify the signature. Problem is, if your private key doesn't include the public - * exponent this won't work unless you manually add the public exponent. phpseclib tries to guess if the key being used - * is the public key but in the event that it guesses incorrectly you might still want to explicitly set the key as being - * public. - * - * Do note that when a new key is loaded the index will be cleared. - * - * Returns true on success, false on failure - * - * @see self::getPublicKey() - * @access public - * @param string|bool $key optional - * @param int|bool $type optional - * @return bool - */ - public function setPublicKey($key = false, $type = false) - { - // if a public key has already been loaded return false - if (!empty($this->publicExponent)) { - return false; - } - - if ($key === false && !empty($this->modulus)) { - $this->isPublic = true; - $this->publicExponent = $this->exponent; - return true; - } - - $components = parent::setPublicKey($key, $type); - if ($components === false) { - return false; - } - - if (empty($this->modulus) || !$this->modulus->equals($components['modulus'])) { - $this->modulus = $components['modulus']; - $this->exponent = $this->publicExponent = $components['publicExponent']; - $this->isPublic = true; - return true; - } - - $this->publicExponent = $components['publicExponent']; - - return true; - } - - /** - * Is the key a public key? - * - * @access public - * @return bool - */ - public function isPublicKey() - { - return $this->isPublic; - } - - /** - * Is the key a private key? - * - * @access public - * @return bool - */ - public function isPrivateKey() - { - return !$this->isPublic && isset($this->modulus); - } - - /** - * Defines the private key - * - * If phpseclib guessed a private key was a public key and loaded it as such it might be desirable to force - * phpseclib to treat the key as a private key. This function will do that. - * - * Do note that when a new key is loaded the index will be cleared. - * - * Returns true on success, false on failure - * - * @see self::getPublicKey() - * @access public - * @param string|bool $key optional - * @param int|bool $type optional - * @return bool - */ - public function setPrivateKey($key = false, $type = false) - { - if ($key === false && !empty($this->publicExponent)) { - $this->publicExponent = false; - $this->isPublic = false; - return true; - } - - $rsa = new RSA(); - if (!$rsa->load($key, $type)) { - return false; - } - $rsa->publicExponent = false; - $rsa->isPublic = false; - - // don't overwrite the old key if the new key is invalid - $this->load($rsa); - return true; - } - - /** - * Returns the public key - * - * The public key is only returned under two circumstances - if the private key had the public key embedded within it - * or if the public key was set via setPublicKey(). If the currently loaded key is supposed to be the public key this - * function won't return it since this library, for the most part, doesn't distinguish between public and private keys. - * - * @see self::getPrivateKey() - * @access public - * @param string $type optional - * @return mixed - */ - public function getPublicKey($type = null) - { - $returnObj = false; - if ($type === null) { - $returnObj = true; - $type = 'PKCS8'; - } - - $type = self::validatePlugin('Keys', $type, 'savePublicKey'); - if ($type === false) { - return false; - } - - if (empty($this->modulus) || empty($this->publicExponent)) { - return false; - } - - $key = $type::savePublicKey($this->modulus, $this->publicExponent); - if (!$returnObj) { - return $key; - } - - $public = clone $this; - $public->load($key, 'PKCS8'); - - return $public; - } - - /** - * __toString() magic method - * - * @access public - * @return string - */ - public function __toString() - { - try { - $key = $this->getPrivateKey($this->privateKeyFormat); - if (is_string($key)) { - return $key; - } - $key = $this->getPrivatePublicKey($this->publicKeyFormat); - return is_string($key) ? $key : ''; - } catch (\Exception $e) { - return ''; - } - } - - /** - * Determines which hashing function should be used - * - * Used with signature production / verification and (if the encryption mode is self::PADDING_OAEP) encryption and - * decryption. - * - * @access public - * @param string $hash - */ - public function setHash($hash) - { - // \phpseclib\Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. - switch (strtolower($hash)) { - case 'md2': - case 'md5': - case 'sha1': - case 'sha256': - case 'sha384': - case 'sha512': - case 'sha224': - case 'sha512/224': - case 'sha512/256': - $this->hash = new Hash($hash); - $this->hashName = $hash; - break; - default: - throw new UnsupportedAlgorithmException( - 'The only supported hash algorithms are: md2, md5, sha1, sha256, sha384, sha512, sha224, sha512/224, sha512/256' - ); - } $this->hLen = $this->hash->getLengthInBytes(); - } - - /** - * Determines which hashing function should be used for the mask generation function - * - * The mask generation function is used by self::PADDING_OAEP and self::PADDING_PSS and although it's - * best if Hash and MGFHash are set to the same thing this is not a requirement. - * - * @access public - * @param string $hash - */ - public function setMGFHash($hash) - { - // \phpseclib\Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. - switch ($hash) { - case 'md2': - case 'md5': - case 'sha1': - case 'sha256': - case 'sha384': - case 'sha512': - case 'sha224': - case 'sha512/224': - case 'sha512/256': - $this->mgfHash = new Hash($hash); - break; - default: - $this->mgfHash = new Hash('sha256'); - } + $this->mgfHash = new Hash('sha256'); $this->mgfHLen = $this->mgfHash->getLengthInBytes(); } - /** - * Determines the salt length - * - * To quote from {@link http://tools.ietf.org/html/rfc3447#page-38 RFC3447#page-38}: - * - * Typical salt lengths in octets are hLen (the length of the output - * of the hash function Hash) and 0. - * - * @access public - * @param int $sLen - */ - public function setSaltLength($sLen) - { - $this->sLen = $sLen; - } - /** * Integer-to-Octet-String primitive * @@ -887,7 +441,7 @@ class RSA extends AsymmetricKey * @param int $xLen * @return bool|string */ - private function i2osp($x, $xLen) + protected function i2osp($x, $xLen) { if ($x === false) { return false; @@ -908,624 +462,11 @@ class RSA extends AsymmetricKey * @param string $x * @return \phpseclib\Math\BigInteger */ - private function os2ip($x) + protected function os2ip($x) { return new BigInteger($x, 256); } - /** - * Exponentiate with or without Chinese Remainder Theorem - * - * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.2}. - * - * @access private - * @param \phpseclib\Math\BigInteger $x - * @return \phpseclib\Math\BigInteger - */ - private function exponentiate($x) - { - switch (true) { - case empty($this->primes): - case $this->primes[1]->equals(self::$zero): - case empty($this->coefficients): - case $this->coefficients[2]->equals(self::$zero): - case empty($this->exponents): - case $this->exponents[1]->equals(self::$zero): - return $x->modPow($this->exponent, $this->modulus); - } - - $num_primes = count($this->primes); - - if (!static::$enableBlinding) { - $m_i = [ - 1 => $x->modPow($this->exponents[1], $this->primes[1]), - 2 => $x->modPow($this->exponents[2], $this->primes[2]) - ]; - $h = $m_i[1]->subtract($m_i[2]); - $h = $h->multiply($this->coefficients[2]); - list(, $h) = $h->divide($this->primes[1]); - $m = $m_i[2]->add($h->multiply($this->primes[2])); - - $r = $this->primes[1]; - for ($i = 3; $i <= $num_primes; $i++) { - $m_i = $x->modPow($this->exponents[$i], $this->primes[$i]); - - $r = $r->multiply($this->primes[$i - 1]); - - $h = $m_i->subtract($m); - $h = $h->multiply($this->coefficients[$i]); - list(, $h) = $h->divide($this->primes[$i]); - - $m = $m->add($r->multiply($h)); - } - } else { - $smallest = $this->primes[1]; - for ($i = 2; $i <= $num_primes; $i++) { - if ($smallest->compare($this->primes[$i]) > 0) { - $smallest = $this->primes[$i]; - } - } - - $r = BigInteger::randomRange(self::$one, $smallest->subtract(self::$one)); - - $m_i = [ - 1 => $this->blind($x, $r, 1), - 2 => $this->blind($x, $r, 2) - ]; - $h = $m_i[1]->subtract($m_i[2]); - $h = $h->multiply($this->coefficients[2]); - list(, $h) = $h->divide($this->primes[1]); - $m = $m_i[2]->add($h->multiply($this->primes[2])); - - $r = $this->primes[1]; - for ($i = 3; $i <= $num_primes; $i++) { - $m_i = $this->blind($x, $r, $i); - - $r = $r->multiply($this->primes[$i - 1]); - - $h = $m_i->subtract($m); - $h = $h->multiply($this->coefficients[$i]); - list(, $h) = $h->divide($this->primes[$i]); - - $m = $m->add($r->multiply($h)); - } - } - - return $m; - } - - /** - * Enable RSA Blinding - * - * @access public - */ - public static function enableBlinding() - { - static::$enableBlinding = true; - } - - /** - * Disable RSA Blinding - * - * @access public - */ - public static function disableBlinding() - { - static::$enableBlinding = false; - } - - /** - * Performs RSA Blinding - * - * Protects against timing attacks by employing RSA Blinding. - * Returns $x->modPow($this->exponents[$i], $this->primes[$i]) - * - * @access private - * @param \phpseclib\Math\BigInteger $x - * @param \phpseclib\Math\BigInteger $r - * @param int $i - * @return \phpseclib\Math\BigInteger - */ - private function blind($x, $r, $i) - { - $x = $x->multiply($r->modPow($this->publicExponent, $this->primes[$i])); - $x = $x->modPow($this->exponents[$i], $this->primes[$i]); - - $r = $r->modInverse($this->primes[$i]); - $x = $x->multiply($r); - list(, $x) = $x->divide($this->primes[$i]); - - return $x; - } - - /** - * RSAEP - * - * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}. - * - * @access private - * @param \phpseclib\Math\BigInteger $m - * @return bool|\phpseclib\Math\BigInteger - */ - private function rsaep($m) - { - if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) { - return false; - } - return $this->exponentiate($m); - } - - /** - * RSADP - * - * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.2 RFC3447#section-5.1.2}. - * - * @access private - * @param \phpseclib\Math\BigInteger $c - * @return bool|\phpseclib\Math\BigInteger - */ - private function rsadp($c) - { - if ($c->compare(self::$zero) < 0 || $c->compare($this->modulus) > 0) { - return false; - } - return $this->exponentiate($c); - } - - /** - * RSASP1 - * - * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.1 RFC3447#section-5.2.1}. - * - * @access private - * @param \phpseclib\Math\BigInteger $m - * @return bool|\phpseclib\Math\BigInteger - */ - private function rsasp1($m) - { - if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) { - return false; - } - return $this->exponentiate($m); - } - - /** - * RSAVP1 - * - * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}. - * - * @access private - * @param \phpseclib\Math\BigInteger $s - * @return bool|\phpseclib\Math\BigInteger - */ - private function rsavp1($s) - { - if ($s->compare(self::$zero) < 0 || $s->compare($this->modulus) > 0) { - return false; - } - return $this->exponentiate($s); - } - - /** - * MGF1 - * - * See {@link http://tools.ietf.org/html/rfc3447#appendix-B.2.1 RFC3447#appendix-B.2.1}. - * - * @access private - * @param string $mgfSeed - * @param int $maskLen - * @return string - */ - private function mgf1($mgfSeed, $maskLen) - { - // if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output. - - $t = ''; - $count = ceil($maskLen / $this->mgfHLen); - for ($i = 0; $i < $count; $i++) { - $c = pack('N', $i); - $t.= $this->mgfHash->hash($mgfSeed . $c); - } - - return substr($t, 0, $maskLen); - } - - /** - * RSAES-OAEP-ENCRYPT - * - * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.1 RFC3447#section-7.1.1} and - * {http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding OAES}. - * - * @access private - * @param string $m - * @param string $l - * @throws \LengthException if strlen($m) > $this->k - 2 * $this->hLen - 2 - * @return string - */ - private function rsaes_oaep_encrypt($m, $l = '') - { - $mLen = strlen($m); - - // Length checking - - // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error - // be output. - - if ($mLen > $this->k - 2 * $this->hLen - 2) { - throw new \LengthException('Message too long'); - } - - // EME-OAEP encoding - - $lHash = $this->hash->hash($l); - $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2); - $db = $lHash . $ps . chr(1) . $m; - $seed = Random::string($this->hLen); - $dbMask = $this->mgf1($seed, $this->k - $this->hLen - 1); - $maskedDB = $db ^ $dbMask; - $seedMask = $this->mgf1($maskedDB, $this->hLen); - $maskedSeed = $seed ^ $seedMask; - $em = chr(0) . $maskedSeed . $maskedDB; - - // RSA encryption - - $m = $this->os2ip($em); - $c = $this->rsaep($m); - $c = $this->i2osp($c, $this->k); - - // Output the ciphertext C - - return $c; - } - - /** - * RSAES-OAEP-DECRYPT - * - * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.2 RFC3447#section-7.1.2}. The fact that the error - * messages aren't distinguishable from one another hinders debugging, but, to quote from RFC3447#section-7.1.2: - * - * Note. Care must be taken to ensure that an opponent cannot - * distinguish the different error conditions in Step 3.g, whether by - * error message or timing, or, more generally, learn partial - * information about the encoded message EM. Otherwise an opponent may - * be able to obtain useful information about the decryption of the - * ciphertext C, leading to a chosen-ciphertext attack such as the one - * observed by Manger [36]. - * - * As for $l... to quote from {@link http://tools.ietf.org/html/rfc3447#page-17 RFC3447#page-17}: - * - * Both the encryption and the decryption operations of RSAES-OAEP take - * the value of a label L as input. In this version of PKCS #1, L is - * the empty string; other uses of the label are outside the scope of - * this document. - * - * @access private - * @param string $c - * @param string $l - * @return bool|string - */ - private function rsaes_oaep_decrypt($c, $l = '') - { - // Length checking - - // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error - // be output. - - if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) { - return false; - } - - // RSA decryption - - $c = $this->os2ip($c); - $m = $this->rsadp($c); - $em = $this->i2osp($m, $this->k); - if ($em === false) { - return false; - } - - // EME-OAEP decoding - - $lHash = $this->hash->hash($l); - $y = ord($em[0]); - $maskedSeed = substr($em, 1, $this->hLen); - $maskedDB = substr($em, $this->hLen + 1); - $seedMask = $this->mgf1($maskedDB, $this->hLen); - $seed = $maskedSeed ^ $seedMask; - $dbMask = $this->mgf1($seed, $this->k - $this->hLen - 1); - $db = $maskedDB ^ $dbMask; - $lHash2 = substr($db, 0, $this->hLen); - $m = substr($db, $this->hLen); - $hashesMatch = hash_equals($lHash, $lHash2); - $leadingZeros = 1; - $patternMatch = 0; - $offset = 0; - for ($i = 0; $i < strlen($m); $i++) { - $patternMatch|= $leadingZeros & ($m[$i] === "\1"); - $leadingZeros&= $m[$i] === "\0"; - $offset+= $patternMatch ? 0 : 1; - } - - // we do & instead of && to avoid https://en.wikipedia.org/wiki/Short-circuit_evaluation - // to protect against timing attacks - if (!$hashesMatch & !$patternMatch) { - return false; - } - - // Output the message M - - return substr($m, $offset + 1); - } - - /** - * Raw Encryption / Decryption - * - * Doesn't use padding and is not recommended. - * - * @access private - * @param string $m - * @return bool|string - * @throws \LengthException if strlen($m) > $this->k - */ - private function raw_encrypt($m) - { - if (strlen($m) > $this->k) { - throw new \LengthException('Message too long'); - } - - $temp = $this->os2ip($m); - $temp = $this->rsaep($temp); - return $this->i2osp($temp, $this->k); - } - - /** - * RSAES-PKCS1-V1_5-ENCRYPT - * - * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}. - * - * @access private - * @param string $m - * @param bool $pkcs15_compat optional - * @throws \LengthException if strlen($m) > $this->k - 11 - * @return bool|string - */ - private function rsaes_pkcs1_v1_5_encrypt($m, $pkcs15_compat = false) - { - $mLen = strlen($m); - - // Length checking - - if ($mLen > $this->k - 11) { - throw new \LengthException('Message too long'); - } - - // EME-PKCS1-v1_5 encoding - - $psLen = $this->k - $mLen - 3; - $ps = ''; - while (strlen($ps) != $psLen) { - $temp = Random::string($psLen - strlen($ps)); - $temp = str_replace("\x00", '', $temp); - $ps.= $temp; - } - $type = 2; - // see the comments of _rsaes_pkcs1_v1_5_decrypt() to understand why this is being done - if ($pkcs15_compat && (!isset($this->publicExponent) || $this->exponent !== $this->publicExponent)) { - $type = 1; - // "The padding string PS shall consist of k-3-||D|| octets. ... for block type 01, they shall have value FF" - $ps = str_repeat("\xFF", $psLen); - } - $em = chr(0) . chr($type) . $ps . chr(0) . $m; - - // RSA encryption - $m = $this->os2ip($em); - $c = $this->rsaep($m); - $c = $this->i2osp($c, $this->k); - - // Output the ciphertext C - - return $c; - } - - /** - * RSAES-PKCS1-V1_5-DECRYPT - * - * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.2 RFC3447#section-7.2.2}. - * - * For compatibility purposes, this function departs slightly from the description given in RFC3447. - * The reason being that RFC2313#section-8.1 (PKCS#1 v1.5) states that ciphertext's encrypted by the - * private key should have the second byte set to either 0 or 1 and that ciphertext's encrypted by the - * public key should have the second byte set to 2. In RFC3447 (PKCS#1 v2.1), the second byte is supposed - * to be 2 regardless of which key is used. For compatibility purposes, we'll just check to make sure the - * second byte is 2 or less. If it is, we'll accept the decrypted string as valid. - * - * As a consequence of this, a private key encrypted ciphertext produced with \phpseclib\Crypt\RSA may not decrypt - * with a strictly PKCS#1 v1.5 compliant RSA implementation. Public key encrypted ciphertext's should but - * not private key encrypted ciphertext's. - * - * @access private - * @param string $c - * @return bool|string - */ - private function rsaes_pkcs1_v1_5_decrypt($c) - { - // Length checking - - if (strlen($c) != $this->k) { // or if k < 11 - return false; - } - - // RSA decryption - - $c = $this->os2ip($c); - $m = $this->rsadp($c); - $em = $this->i2osp($m, $this->k); - if ($em === false) { - return false; - } - - // EME-PKCS1-v1_5 decoding - - if (ord($em[0]) != 0 || ord($em[1]) > 2) { - return false; - } - - $ps = substr($em, 2, strpos($em, chr(0), 2) - 2); - $m = substr($em, strlen($ps) + 3); - - if (strlen($ps) < 8) { - return false; - } - - // Output M - - return $m; - } - - /** - * EMSA-PSS-ENCODE - * - * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.1 RFC3447#section-9.1.1}. - * - * @return string - * @access private - * @param string $m - * @throws \RuntimeException on encoding error - * @param int $emBits - */ - private function emsa_pss_encode($m, $emBits) - { - // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error - // be output. - - $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8) - $sLen = $this->sLen !== null ? $this->sLen : $this->hLen; - - $mHash = $this->hash->hash($m); - if ($emLen < $this->hLen + $sLen + 2) { - return false; - } - - $salt = Random::string($sLen); - $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; - $h = $this->hash->hash($m2); - $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2); - $db = $ps . chr(1) . $salt; - $dbMask = $this->mgf1($h, $emLen - $this->hLen - 1); - $maskedDB = $db ^ $dbMask; - $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0]; - $em = $maskedDB . $h . chr(0xBC); - - return $em; - } - - /** - * EMSA-PSS-VERIFY - * - * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}. - * - * @access private - * @param string $m - * @param string $em - * @param int $emBits - * @return string - */ - private function emsa_pss_verify($m, $em, $emBits) - { - // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error - // be output. - - $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8); - $sLen = $this->sLen !== null ? $this->sLen : $this->hLen; - - $mHash = $this->hash->hash($m); - if ($emLen < $this->hLen + $sLen + 2) { - return false; - } - - if ($em[strlen($em) - 1] != chr(0xBC)) { - return false; - } - - $maskedDB = substr($em, 0, -$this->hLen - 1); - $h = substr($em, -$this->hLen - 1, $this->hLen); - $temp = chr(0xFF << ($emBits & 7)); - if ((~$maskedDB[0] & $temp) != $temp) { - return false; - } - $dbMask = $this->mgf1($h, $emLen - $this->hLen - 1); - $db = $maskedDB ^ $dbMask; - $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0]; - $temp = $emLen - $this->hLen - $sLen - 2; - if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) { - return false; - } - $salt = substr($db, $temp + 1); // should be $sLen long - $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; - $h2 = $this->hash->hash($m2); - return hash_equals($h, $h2); - } - - /** - * RSASSA-PSS-SIGN - * - * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.1 RFC3447#section-8.1.1}. - * - * @access private - * @param string $m - * @return bool|string - */ - private function rsassa_pss_sign($m) - { - // EMSA-PSS encoding - - $em = $this->emsa_pss_encode($m, 8 * $this->k - 1); - - // RSA signature - - $m = $this->os2ip($em); - $s = $this->rsasp1($m); - $s = $this->i2osp($s, $this->k); - - // Output the signature S - - return $s; - } - - /** - * RSASSA-PSS-VERIFY - * - * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}. - * - * @access private - * @param string $m - * @param string $s - * @return bool|string - */ - private function rsassa_pss_verify($m, $s) - { - // Length checking - - if (strlen($s) != $this->k) { - return false; - } - - // RSA verification - - $modBits = 8 * $this->k; - - $s2 = $this->os2ip($s); - $m2 = $this->rsavp1($s2); - $em = $this->i2osp($m2, $modBits >> 3); - if ($em === false) { - return false; - } - - // EMSA-PSS verification - - return $this->emsa_pss_verify($m, $em, $modBits - 1); - } - /** * EMSA-PKCS1-V1_5-ENCODE * @@ -1537,12 +478,12 @@ class RSA extends AsymmetricKey * @throws \LengthException if the intended encoded message length is too short * @return string */ - private function emsa_pkcs1_v1_5_encode($m, $emLen) + protected function emsa_pkcs1_v1_5_encode($m, $emLen) { $h = $this->hash->hash($m); // see http://tools.ietf.org/html/rfc3447#page-43 - switch ($this->hashName) { + switch ($this->hash->getHash()) { case 'md2': $t = "\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x02\x05\x00\x04\x10"; break; @@ -1586,293 +527,239 @@ class RSA extends AsymmetricKey } /** - * RSASSA-PKCS1-V1_5-SIGN + * MGF1 * - * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.1 RFC3447#section-8.2.1}. + * See {@link http://tools.ietf.org/html/rfc3447#appendix-B.2.1 RFC3447#appendix-B.2.1}. * * @access private - * @param string $m - * @throws \LengthException if the RSA modulus is too short - * @return bool|string - */ - private function rsassa_pkcs1_v1_5_sign($m) - { - // EMSA-PKCS1-v1_5 encoding - - // If the encoding operation outputs "intended encoded message length too short," output "RSA modulus - // too short" and stop. - try { - $em = $this->emsa_pkcs1_v1_5_encode($m, $this->k); - } catch (\LengthException $e) { - throw new \LengthException('RSA modulus too short'); - } - - // RSA signature - - $m = $this->os2ip($em); - $s = $this->rsasp1($m); - $s = $this->i2osp($s, $this->k); - - // Output the signature S - - return $s; - } - - /** - * RSASSA-PKCS1-V1_5-VERIFY - * - * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}. - * - * @access private - * @param string $m - * @param string $s - * @throws \LengthException if the RSA modulus is too short - * @return bool - */ - private function rsassa_pkcs1_v1_5_verify($m, $s) - { - // Length checking - - if (strlen($s) != $this->k) { - return false; - } - - // RSA verification - - $s = $this->os2ip($s); - $m2 = $this->rsavp1($s); - $em = $this->i2osp($m2, $this->k); - if ($em === false) { - return false; - } - - // EMSA-PKCS1-v1_5 encoding - - // If the encoding operation outputs "intended encoded message length too short," output "RSA modulus - // too short" and stop. - try { - $em2 = $this->emsa_pkcs1_v1_5_encode($m, $this->k); - } catch (\LengthException $e) { - throw new \LengthException('RSA modulus too short'); - } - - // Compare - return hash_equals($em, $em2); - } - - /** - * RSASSA-PKCS1-V1_5-VERIFY (relaxed matching) - * - * Per {@link http://tools.ietf.org/html/rfc3447#page-43 RFC3447#page-43} PKCS1 v1.5 - * specified the use BER encoding rather than DER encoding that PKCS1 v2.0 specified. - * This means that under rare conditions you can have a perfectly valid v1.5 signature - * that fails to validate with _rsassa_pkcs1_v1_5_verify(). PKCS1 v2.1 also recommends - * that if you're going to validate these types of signatures you "should indicate - * whether the underlying BER encoding is a DER encoding and hence whether the signature - * is valid with respect to the specification given in [PKCS1 v2.0+]". so if you do - * $rsa->getLastPadding() and get RSA::PADDING_RELAXED_PKCS1 back instead of - * RSA::PADDING_PKCS1... that means BER encoding was used. - * - * @access private - * @param string $m - * @param string $s - * @return bool - */ - private function rsassa_pkcs1_v1_5_relaxed_verify($m, $s) - { - // Length checking - - if (strlen($s) != $this->k) { - return false; - } - - // RSA verification - - $s = $this->os2ip($s); - $m2 = $this->rsavp1($s); - if ($m2 === false) { - return false; - } - $em = $this->i2osp($m2, $this->k); - if ($em === false) { - return false; - } - - if (Strings::shift($em, 2) != "\0\1") { - return false; - } - - $em = ltrim($em, "\xFF"); - if (Strings::shift($em) != "\0") { - return false; - } - - $decoded = ASN1::decodeBER($em); - if (!is_array($decoded) || empty($decoded[0]) || strlen($em) > $decoded[0]['length']) { - return false; - } - - static $oids; - if (!isset($oids)) { - $oids = [ - 'md2' => '1.2.840.113549.2.2', - 'md4' => '1.2.840.113549.2.4', // from PKCS1 v1.5 - 'md5' => '1.2.840.113549.2.5', - 'id-sha1' => '1.3.14.3.2.26', - 'id-sha256' => '2.16.840.1.101.3.4.2.1', - 'id-sha384' => '2.16.840.1.101.3.4.2.2', - 'id-sha512' => '2.16.840.1.101.3.4.2.3', - // from PKCS1 v2.2 - 'id-sha224' => '2.16.840.1.101.3.4.2.4', - 'id-sha512/224' => '2.16.840.1.101.3.4.2.5', - 'id-sha512/256' => '2.16.840.1.101.3.4.2.6', - ]; - ASN1::loadOIDs($oids); - } - - $decoded = ASN1::asn1map($decoded[0], DigestInfo::MAP); - if (!isset($decoded) || $decoded === false) { - return false; - } - - if (!isset($oids[$decoded['digestAlgorithm']['algorithm']])) { - return false; - } - - $hash = $decoded['digestAlgorithm']['algorithm']; - $hash = substr($hash, 0, 3) == 'id-' ? - substr($hash, 3) : - $hash; - $hash = new Hash($hash); - $em = $hash->hash($m); - $em2 = $decoded['digest']; - - return hash_equals($em, $em2); - } - - /** - * Encryption - * - * Both self::PADDING_OAEP and self::PADDING_PKCS1 both place limits on how long $plaintext can be. - * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will - * be concatenated together. - * - * @see self::decrypt() - * @access public - * @param string $plaintext - * @param int $padding optional - * @return bool|string - * @throws \LengthException if the RSA modulus is too short - */ - public function encrypt($plaintext, $padding = self::PADDING_OAEP) - { - if (empty($this->modulus) || empty($this->exponent)) { - throw new NoKeyLoadedException('No key has been loaded'); - } - - if (!$this->isPublic) { - throw new UnsupportedOperationException('phpseclib does not allow the use of private keys to encrypt data'); - } - - switch ($padding) { - case self::PADDING_NONE: - return $this->raw_encrypt($plaintext); - case self::PADDING_PKCS15_COMPAT: - case self::PADDING_PKCS1: - return $this->rsaes_pkcs1_v1_5_encrypt($plaintext, $padding == self::PADDING_PKCS15_COMPAT); - //case self::PADDING_OAEP: - default: - return $this->rsaes_oaep_encrypt($plaintext); - } - } - - /** - * Decryption - * - * @see self::encrypt() - * @access public - * @param string $ciphertext - * @param int $padding optional - * @return bool|string - */ - public function decrypt($ciphertext, $padding = self::PADDING_OAEP) - { - if (empty($this->modulus) || empty($this->exponent)) { - throw new NoKeyLoadedException('No key has been loaded'); - } - - if ($this->isPublic) { - throw new UnsupportedOperationException('phpseclib does not allow the use of public keys to decrypt data'); - } - - switch ($padding) { - case self::PADDING_NONE: - return $this->raw_encrypt($ciphertext); - case self::PADDING_PKCS1: - return $this->rsaes_pkcs1_v1_5_decrypt($ciphertext); - //case self::PADDING_OAEP: - default: - return $this->rsaes_oaep_decrypt($ciphertext); - } - } - - /** - * Create a signature - * - * @see self::verify() - * @access public - * @param string $message - * @param int $padding optional + * @param string $mgfSeed + * @param int $maskLen * @return string */ - public function sign($message, $padding = self::PADDING_PSS) + protected function mgf1($mgfSeed, $maskLen) { - if (empty($this->modulus) || empty($this->exponent)) { - throw new NoKeyLoadedException('No key has been loaded'); + // if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output. + + $t = ''; + $count = ceil($maskLen / $this->mgfHLen); + for ($i = 0; $i < $count; $i++) { + $c = pack('N', $i); + $t.= $this->mgfHash->hash($mgfSeed . $c); } - if ($this->isPublic) { - throw new UnsupportedOperationException('phpseclib does not allow the use of public keys to sign data'); - } - - switch ($padding) { - case self::PADDING_PKCS1: - case self::PADDING_RELAXED_PKCS1: - return $this->rsassa_pkcs1_v1_5_sign($message); - //case self::PADDING_PSS: - default: - return $this->rsassa_pss_sign($message); - } + return substr($t, 0, $maskLen); } /** - * Verifies a signature + * Returns the key size + * + * More specifically, this returns the size of the modulo in bits. * - * @see self::sign() * @access public - * @param string $message - * @param string $signature - * @param int $padding optional - * @return bool + * @return int */ - public function verify($message, $signature, $padding = self::PADDING_PSS) + public function getLength() { - if (empty($this->modulus) || empty($this->exponent)) { - throw new NoKeyLoadedException('No key has been loaded'); - } - - if (!$this->isPublic) { - throw new UnsupportedOperationException('phpseclib does not allow the use of private keys to verify data'); - } - - switch ($padding) { - case self::PADDING_RELAXED_PKCS1: - return $this->rsassa_pkcs1_v1_5_relaxed_verify($message, $signature); - case self::PADDING_PKCS1: - return $this->rsassa_pkcs1_v1_5_verify($message, $signature); - //case self::PADDING_PSS: - default: - return $this->rsassa_pss_verify($message, $signature); - } + return !isset($this->modulus) ? 0 : $this->modulus->getLength(); } -} + + /** + * Determines which hashing function should be used + * + * Used with signature production / verification and (if the encryption mode is self::PADDING_OAEP) encryption and + * decryption. + * + * @access public + * @param string $hash + */ + public function withHash($hash) + { + $new = clone $this; + + // \phpseclib\Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. + switch (strtolower($hash)) { + case 'md2': + case 'md5': + case 'sha1': + case 'sha256': + case 'sha384': + case 'sha512': + case 'sha224': + case 'sha512/224': + case 'sha512/256': + $new->hash = new Hash($hash); + break; + default: + throw new UnsupportedAlgorithmException( + 'The only supported hash algorithms are: md2, md5, sha1, sha256, sha384, sha512, sha224, sha512/224, sha512/256' + ); + } + $new->hLen = $new->hash->getLengthInBytes(); + + return $new; + } + + /** + * Determines which hashing function should be used for the mask generation function + * + * The mask generation function is used by self::PADDING_OAEP and self::PADDING_PSS and although it's + * best if Hash and MGFHash are set to the same thing this is not a requirement. + * + * @access public + * @param string $hash + */ + public function withMGFHash($hash) + { + $new = clone $this; + + // \phpseclib\Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. + switch (strtolower($hash)) { + case 'md2': + case 'md5': + case 'sha1': + case 'sha256': + case 'sha384': + case 'sha512': + case 'sha224': + case 'sha512/224': + case 'sha512/256': + $new->mgfHash = new Hash($hash); + break; + default: + throw new UnsupportedAlgorithmException( + 'The only supported hash algorithms are: md2, md5, sha1, sha256, sha384, sha512, sha224, sha512/224, sha512/256' + ); + } + $new->mgfHLen = $new->mgfHash->getLengthInBytes(); + + return $new; + } + + /** + * Determines the salt length + * + * Used by RSA::PADDING_PSS + * + * To quote from {@link http://tools.ietf.org/html/rfc3447#page-38 RFC3447#page-38}: + * + * Typical salt lengths in octets are hLen (the length of the output + * of the hash function Hash) and 0. + * + * @access public + * @param int $sLen + */ + public function withSaltLength($sLen) + { + $new = clone $this; + $new->sLen = $sLen; + return $new; + } + + /** + * Determines the label + * + * Used by RSA::PADDING_OAEP + * + * To quote from {@link http://tools.ietf.org/html/rfc3447#page-17 RFC3447#page-17}: + * + * Both the encryption and the decryption operations of RSAES-OAEP take + * the value of a label L as input. In this version of PKCS #1, L is + * the empty string; other uses of the label are outside the scope of + * this document. + * + * @access public + * @param string $label + */ + public function withLabel($label) + { + $new = clone $this; + $new->label = $label; + return $new; + } + + /** + * Determines the padding modes + * + * Example: $key->withPadding(RSA::ENCRYPTION_PKCS1 | RSA::SIGNATURE_PKCS1); + * + * @access public + * @param string $label + */ + public function withPadding($padding) + { + $masks = [ + self::ENCRYPTION_OAEP, + self::ENCRYPTION_PKCS1, + self::ENCRYPTION_NONE, + self::ENCRYPTION_PKCS15_COMPAT + ]; + $numSelected = 0; + $selected = 0; + foreach ($masks as $mask) { + if ($padding & $mask) { + $selected = $mask; + $numSelected++; + } + } + if ($numSelected > 1) { + throw new InconsistentSetupException('Multiple encryption padding modes have been selected; at most only one should be selected'); + } + $encryptionPadding = $selected; + + $masks = [ + self::SIGNATURE_PSS, + self::SIGNATURE_RELAXED_PKCS1, + self::SIGNATURE_PKCS1 + ]; + $numSelected = 0; + $selected = 0; + foreach ($masks as $mask) { + if ($padding & $mask) { + $selected = $mask; + $numSelected++; + } + } + if ($numSelected > 1) { + throw new InconsistentSetupException('Multiple signature padding modes have been selected; at most only one should be selected'); + } + $signaturePadding = $selected; + + $new = clone $this; + $new->encryptionPadding = $encryptionPadding; + $new->signaturePadding = $signaturePadding; + return $new; + } + + /** + * Returns the current engine being used + * + * @see self::useInternalEngine() + * @see self::useBestEngine() + * @access public + * @return string + */ + public function getEngine() + { + return 'PHP'; + } + + /** + * Enable RSA Blinding + * + * @access public + */ + public static function enableBlinding() + { + static::$enableBlinding = true; + } + + /** + * Disable RSA Blinding + * + * @access public + */ + public static function disableBlinding() + { + static::$enableBlinding = false; + } +} \ No newline at end of file diff --git a/phpseclib/Crypt/RSA/Keys/XML.php b/phpseclib/Crypt/RSA/Keys/XML.php index c8caf4b5..966112bc 100644 --- a/phpseclib/Crypt/RSA/Keys/XML.php +++ b/phpseclib/Crypt/RSA/Keys/XML.php @@ -102,7 +102,16 @@ abstract class XML libxml_use_internal_errors($use_errors); + foreach ($components as $key => $value) { + if (is_array($value) && !count($value)) { + unset($components[$key]); + } + } + if (isset($components['modulus']) && isset($components['publicExponent'])) { + if (count($components) == 3) { + $components['isPublicKey'] = true; + } return $components; } diff --git a/phpseclib/Crypt/RSA/PrivateKey.php b/phpseclib/Crypt/RSA/PrivateKey.php new file mode 100644 index 00000000..526fedf6 --- /dev/null +++ b/phpseclib/Crypt/RSA/PrivateKey.php @@ -0,0 +1,561 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\RSA; + +use phpseclib\Crypt\RSA; +use phpseclib\Math\BigInteger; +use phpseclib\File\ASN1; +use phpseclib\Common\Functions\Strings; +use phpseclib\Crypt\Hash; +use phpseclib\Exceptions\NoKeyLoadedException; +use phpseclib\Exception\UnsupportedFormatException; +use phpseclib\Crypt\Random; +use phpseclib\Crypt\Common; + +/** + * Raw RSA Key Handler + * + * @package RSA + * @author Jim Wigginton + * @access public + */ +class PrivateKey extends RSA implements Common\PrivateKey +{ + use Common\PasswordProtected; + + /** + * Primes for Chinese Remainder Theorem (ie. p and q) + * + * @var array + * @access private + */ + protected $primes; + + /** + * Exponents for Chinese Remainder Theorem (ie. dP and dQ) + * + * @var array + * @access private + */ + protected $exponents; + + /** + * Coefficients for Chinese Remainder Theorem (ie. qInv) + * + * @var array + * @access private + */ + protected $coefficients; + + /** + * Public Exponent + * + * @var mixed + * @access private + */ + protected $publicExponent = false; + + /** + * RSADP + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.2 RFC3447#section-5.1.2}. + * + * @access private + * @param \phpseclib\Math\BigInteger $c + * @return bool|\phpseclib\Math\BigInteger + */ + private function rsadp($c) + { + if ($c->compare(self::$zero) < 0 || $c->compare($this->modulus) > 0) { + return false; + } + return $this->exponentiate($c); + } + + /** + * RSASP1 + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.1 RFC3447#section-5.2.1}. + * + * @access private + * @param \phpseclib\Math\BigInteger $m + * @return bool|\phpseclib\Math\BigInteger + */ + private function rsasp1($m) + { + if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) { + return false; + } + return $this->exponentiate($m); + } + + /** + * Exponentiate + * + * @param \phpseclib\Math\BigInteger $x + * @return \phpseclib\Math\BigInteger + */ + protected function exponentiate(BigInteger $x) + { + switch (true) { + case empty($this->primes): + case $this->primes[1]->equals(self::$zero): + case empty($this->coefficients): + case $this->coefficients[2]->equals(self::$zero): + case empty($this->exponents): + case $this->exponents[1]->equals(self::$zero): + return $x->modPow($this->exponent, $this->modulus); + } + + $num_primes = count($this->primes); + + if (!static::$enableBlinding) { + $m_i = [ + 1 => $x->modPow($this->exponents[1], $this->primes[1]), + 2 => $x->modPow($this->exponents[2], $this->primes[2]) + ]; + $h = $m_i[1]->subtract($m_i[2]); + $h = $h->multiply($this->coefficients[2]); + list(, $h) = $h->divide($this->primes[1]); + $m = $m_i[2]->add($h->multiply($this->primes[2])); + + $r = $this->primes[1]; + for ($i = 3; $i <= $num_primes; $i++) { + $m_i = $x->modPow($this->exponents[$i], $this->primes[$i]); + + $r = $r->multiply($this->primes[$i - 1]); + + $h = $m_i->subtract($m); + $h = $h->multiply($this->coefficients[$i]); + list(, $h) = $h->divide($this->primes[$i]); + + $m = $m->add($r->multiply($h)); + } + } else { + $smallest = $this->primes[1]; + for ($i = 2; $i <= $num_primes; $i++) { + if ($smallest->compare($this->primes[$i]) > 0) { + $smallest = $this->primes[$i]; + } + } + + $r = BigInteger::randomRange(self::$one, $smallest->subtract(self::$one)); + + $m_i = [ + 1 => $this->blind($x, $r, 1), + 2 => $this->blind($x, $r, 2) + ]; + $h = $m_i[1]->subtract($m_i[2]); + $h = $h->multiply($this->coefficients[2]); + list(, $h) = $h->divide($this->primes[1]); + $m = $m_i[2]->add($h->multiply($this->primes[2])); + + $r = $this->primes[1]; + for ($i = 3; $i <= $num_primes; $i++) { + $m_i = $this->blind($x, $r, $i); + + $r = $r->multiply($this->primes[$i - 1]); + + $h = $m_i->subtract($m); + $h = $h->multiply($this->coefficients[$i]); + list(, $h) = $h->divide($this->primes[$i]); + + $m = $m->add($r->multiply($h)); + } + } + + return $m; + } + + /** + * Performs RSA Blinding + * + * Protects against timing attacks by employing RSA Blinding. + * Returns $x->modPow($this->exponents[$i], $this->primes[$i]) + * + * @access private + * @param \phpseclib\Math\BigInteger $x + * @param \phpseclib\Math\BigInteger $r + * @param int $i + * @return \phpseclib\Math\BigInteger + */ + private function blind($x, $r, $i) + { + $x = $x->multiply($r->modPow($this->publicExponent, $this->primes[$i])); + $x = $x->modPow($this->exponents[$i], $this->primes[$i]); + + $r = $r->modInverse($this->primes[$i]); + $x = $x->multiply($r); + list(, $x) = $x->divide($this->primes[$i]); + + return $x; + } + + /** + * EMSA-PSS-ENCODE + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.1 RFC3447#section-9.1.1}. + * + * @return string + * @access private + * @param string $m + * @throws \RuntimeException on encoding error + * @param int $emBits + */ + private function emsa_pss_encode($m, $emBits) + { + // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8) + $sLen = $this->sLen !== null ? $this->sLen : $this->hLen; + + $mHash = $this->hash->hash($m); + if ($emLen < $this->hLen + $sLen + 2) { + return false; + } + + $salt = Random::string($sLen); + $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; + $h = $this->hash->hash($m2); + $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2); + $db = $ps . chr(1) . $salt; + $dbMask = $this->mgf1($h, $emLen - $this->hLen - 1); + $maskedDB = $db ^ $dbMask; + $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0]; + $em = $maskedDB . $h . chr(0xBC); + + return $em; + } + + /** + * RSASSA-PSS-SIGN + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.1 RFC3447#section-8.1.1}. + * + * @access private + * @param string $m + * @return bool|string + */ + private function rsassa_pss_sign($m) + { + // EMSA-PSS encoding + + $em = $this->emsa_pss_encode($m, 8 * $this->k - 1); + + // RSA signature + + $m = $this->os2ip($em); + $s = $this->rsasp1($m); + $s = $this->i2osp($s, $this->k); + + // Output the signature S + + return $s; + } + + /** + * RSASSA-PKCS1-V1_5-SIGN + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.1 RFC3447#section-8.2.1}. + * + * @access private + * @param string $m + * @throws \LengthException if the RSA modulus is too short + * @return bool|string + */ + private function rsassa_pkcs1_v1_5_sign($m) + { + // EMSA-PKCS1-v1_5 encoding + + // If the encoding operation outputs "intended encoded message length too short," output "RSA modulus + // too short" and stop. + try { + $em = $this->emsa_pkcs1_v1_5_encode($m, $this->k); + } catch (\LengthException $e) { + throw new \LengthException('RSA modulus too short'); + } + + // RSA signature + + $m = $this->os2ip($em); + $s = $this->rsasp1($m); + $s = $this->i2osp($s, $this->k); + + // Output the signature S + + return $s; + } + + /** + * Create a signature + * + * @see self::verify() + * @access public + * @param string $message + * @return string + */ + public function sign($message) + { + switch ($this->signaturePadding) { + case self::SIGNATURE_PKCS1: + case self::SIGNATURE_RELAXED_PKCS1: + return $this->rsassa_pkcs1_v1_5_sign($message); + //case self::SIGNATURE_PSS: + default: + return $this->rsassa_pss_sign($message); + } + } + + /** + * RSAES-PKCS1-V1_5-DECRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.2 RFC3447#section-7.2.2}. + * + * For compatibility purposes, this function departs slightly from the description given in RFC3447. + * The reason being that RFC2313#section-8.1 (PKCS#1 v1.5) states that ciphertext's encrypted by the + * private key should have the second byte set to either 0 or 1 and that ciphertext's encrypted by the + * public key should have the second byte set to 2. In RFC3447 (PKCS#1 v2.1), the second byte is supposed + * to be 2 regardless of which key is used. For compatibility purposes, we'll just check to make sure the + * second byte is 2 or less. If it is, we'll accept the decrypted string as valid. + * + * As a consequence of this, a private key encrypted ciphertext produced with \phpseclib\Crypt\RSA may not decrypt + * with a strictly PKCS#1 v1.5 compliant RSA implementation. Public key encrypted ciphertext's should but + * not private key encrypted ciphertext's. + * + * @access private + * @param string $c + * @return bool|string + */ + private function rsaes_pkcs1_v1_5_decrypt($c) + { + // Length checking + + if (strlen($c) != $this->k) { // or if k < 11 + return false; + } + + // RSA decryption + + $c = $this->os2ip($c); + $m = $this->rsadp($c); + $em = $this->i2osp($m, $this->k); + if ($em === false) { + return false; + } + + // EME-PKCS1-v1_5 decoding + + if (ord($em[0]) != 0 || ord($em[1]) > 2) { + return false; + } + + $ps = substr($em, 2, strpos($em, chr(0), 2) - 2); + $m = substr($em, strlen($ps) + 3); + + if (strlen($ps) < 8) { + return false; + } + + // Output M + + return $m; + } + + /** + * RSAES-OAEP-DECRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.2 RFC3447#section-7.1.2}. The fact that the error + * messages aren't distinguishable from one another hinders debugging, but, to quote from RFC3447#section-7.1.2: + * + * Note. Care must be taken to ensure that an opponent cannot + * distinguish the different error conditions in Step 3.g, whether by + * error message or timing, or, more generally, learn partial + * information about the encoded message EM. Otherwise an opponent may + * be able to obtain useful information about the decryption of the + * ciphertext C, leading to a chosen-ciphertext attack such as the one + * observed by Manger [36]. + * + * @access private + * @param string $c + * @return bool|string + */ + private function rsaes_oaep_decrypt($c) + { + // Length checking + + // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) { + return false; + } + + // RSA decryption + + $c = $this->os2ip($c); + $m = $this->rsadp($c); + $em = $this->i2osp($m, $this->k); + if ($em === false) { + return false; + } + + // EME-OAEP decoding + + $lHash = $this->hash->hash($this->label); + $y = ord($em[0]); + $maskedSeed = substr($em, 1, $this->hLen); + $maskedDB = substr($em, $this->hLen + 1); + $seedMask = $this->mgf1($maskedDB, $this->hLen); + $seed = $maskedSeed ^ $seedMask; + $dbMask = $this->mgf1($seed, $this->k - $this->hLen - 1); + $db = $maskedDB ^ $dbMask; + $lHash2 = substr($db, 0, $this->hLen); + $m = substr($db, $this->hLen); + $hashesMatch = hash_equals($lHash, $lHash2); + $leadingZeros = 1; + $patternMatch = 0; + $offset = 0; + for ($i = 0; $i < strlen($m); $i++) { + $patternMatch|= $leadingZeros & ($m[$i] === "\1"); + $leadingZeros&= $m[$i] === "\0"; + $offset+= $patternMatch ? 0 : 1; + } + + // we do & instead of && to avoid https://en.wikipedia.org/wiki/Short-circuit_evaluation + // to protect against timing attacks + if (!$hashesMatch & !$patternMatch) { + return false; + } + + // Output the message M + + return substr($m, $offset + 1); + } + + /** + * Raw Encryption / Decryption + * + * Doesn't use padding and is not recommended. + * + * @access private + * @param string $m + * @return bool|string + * @throws \LengthException if strlen($m) > $this->k + */ + private function raw_encrypt($m) + { + if (strlen($m) > $this->k) { + throw new \LengthException('Message too long'); + } + + $temp = $this->os2ip($m); + $temp = $this->rsadp($temp); + return $this->i2osp($temp, $this->k); + } + + /** + * Decryption + * + * @see self::encrypt() + * @access public + * @param string $ciphertext + * @param int $padding optional + * @return bool|string + */ + public function decrypt($ciphertext) + { + switch ($this->encryptionPadding) { + case self::ENCRYPTION_NONE: + return $this->raw_encrypt($ciphertext); + case self::ENCRYPTION_PKCS1: + return $this->rsaes_pkcs1_v1_5_decrypt($ciphertext); + //case self::ENCRYPTION_OAEP: + default: + return $this->rsaes_oaep_decrypt($ciphertext); + } + } + + /** + * Returns the public key + * + * @access public + * @param string $type optional + * @return mixed + */ + public function getPublicKey() + { + $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey'); + if (empty($this->modulus) || empty($this->publicExponent)) { + return false; + } + + $key = $type::savePublicKey($this->modulus, $this->publicExponent); + return RSA::load($key, 'PKCS8') + ->withHash($this->hash->getHash()) + ->withMGFHash($this->mgfHash->getHash()) + ->withSaltLength($this->sLen) + ->withLabel($this->label) + ->withPadding($this->signaturePadding | $this->encryptionPadding); + } + + /** + * Returns the private key + * + * @param string $type + * @return string + */ + public function toString($type) + { + $type = self::validatePlugin( + 'Keys', + $type, + empty($this->primes) ? 'savePublicKey' : 'savePrivateKey' + ); + + if (empty($this->primes)) { + return $type::savePublicKey($this->modulus, $this->exponent); + } + + return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password); + + /* + $key = $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password); + if ($key !== false || count($this->primes) == 2) { + return $key; + } + + $nSize = $this->getSize() >> 1; + + $primes = [1 => clone self::$one, clone self::$one]; + $i = 1; + foreach ($this->primes as $prime) { + $primes[$i] = $primes[$i]->multiply($prime); + if ($primes[$i]->getLength() >= $nSize) { + $i++; + } + } + + $exponents = []; + $coefficients = [2 => $primes[2]->modInverse($primes[1])]; + + foreach ($primes as $i => $prime) { + $temp = $prime->subtract(self::$one); + $exponents[$i] = $this->modulus->modInverse($temp); + } + + return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $primes, $exponents, $coefficients, $this->password); + */ + } +} diff --git a/phpseclib/Crypt/RSA/PublicKey.php b/phpseclib/Crypt/RSA/PublicKey.php new file mode 100644 index 00000000..f602c346 --- /dev/null +++ b/phpseclib/Crypt/RSA/PublicKey.php @@ -0,0 +1,496 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\RSA; + +use phpseclib\Crypt\RSA; +use phpseclib\Math\BigInteger; +use phpseclib\File\ASN1; +use phpseclib\Common\Functions\Strings; +use phpseclib\Crypt\Hash; +use phpseclib\Exceptions\NoKeyLoadedException; +use phpseclib\Crypt\Random; +use phpseclib\Crypt\Common; +use phpseclib\File\ASN1\Maps\DigestInfo; + +/** + * Raw RSA Key Handler + * + * @package RSA + * @author Jim Wigginton + * @access public + */ +class PublicKey extends RSA implements Common\PublicKey +{ + use Common\Fingerprint; + + /** + * Exponentiate + * + * @param \phpseclib\Math\BigInteger $x + * @return \phpseclib\Math\BigInteger + */ + private function exponentiate(BigInteger $x) + { + return $x->modPow($this->exponent, $this->modulus); + } + + /** + * RSAVP1 + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}. + * + * @access private + * @param \phpseclib\Math\BigInteger $s + * @return bool|\phpseclib\Math\BigInteger + */ + private function rsavp1($s) + { + if ($s->compare(self::$zero) < 0 || $s->compare($this->modulus) > 0) { + return false; + } + return $this->exponentiate($s); + } + + /** + * RSASSA-PKCS1-V1_5-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}. + * + * @access private + * @param string $m + * @param string $s + * @throws \LengthException if the RSA modulus is too short + * @return bool + */ + private function rsassa_pkcs1_v1_5_verify($m, $s) + { + // Length checking + + if (strlen($s) != $this->k) { + return false; + } + + // RSA verification + + $s = $this->os2ip($s); + $m2 = $this->rsavp1($s); + $em = $this->i2osp($m2, $this->k); + if ($em === false) { + return false; + } + + // EMSA-PKCS1-v1_5 encoding + + // If the encoding operation outputs "intended encoded message length too short," output "RSA modulus + // too short" and stop. + try { + $em2 = $this->emsa_pkcs1_v1_5_encode($m, $this->k); + } catch (\LengthException $e) { + throw new \LengthException('RSA modulus too short'); + } + + // Compare + return hash_equals($em, $em2); + } + + /** + * RSASSA-PKCS1-V1_5-VERIFY (relaxed matching) + * + * Per {@link http://tools.ietf.org/html/rfc3447#page-43 RFC3447#page-43} PKCS1 v1.5 + * specified the use BER encoding rather than DER encoding that PKCS1 v2.0 specified. + * This means that under rare conditions you can have a perfectly valid v1.5 signature + * that fails to validate with _rsassa_pkcs1_v1_5_verify(). PKCS1 v2.1 also recommends + * that if you're going to validate these types of signatures you "should indicate + * whether the underlying BER encoding is a DER encoding and hence whether the signature + * is valid with respect to the specification given in [PKCS1 v2.0+]". so if you do + * $rsa->getLastPadding() and get RSA::PADDING_RELAXED_PKCS1 back instead of + * RSA::PADDING_PKCS1... that means BER encoding was used. + * + * @access private + * @param string $m + * @param string $s + * @return bool + */ + private function rsassa_pkcs1_v1_5_relaxed_verify($m, $s) + { + // Length checking + + if (strlen($s) != $this->k) { + return false; + } + + // RSA verification + + $s = $this->os2ip($s); + $m2 = $this->rsavp1($s); + if ($m2 === false) { + return false; + } + $em = $this->i2osp($m2, $this->k); + if ($em === false) { + return false; + } + + if (Strings::shift($em, 2) != "\0\1") { + return false; + } + + $em = ltrim($em, "\xFF"); + if (Strings::shift($em) != "\0") { + return false; + } + + $decoded = ASN1::decodeBER($em); + if (!is_array($decoded) || empty($decoded[0]) || strlen($em) > $decoded[0]['length']) { + return false; + } + + static $oids; + if (!isset($oids)) { + $oids = [ + 'md2' => '1.2.840.113549.2.2', + 'md4' => '1.2.840.113549.2.4', // from PKCS1 v1.5 + 'md5' => '1.2.840.113549.2.5', + 'id-sha1' => '1.3.14.3.2.26', + 'id-sha256' => '2.16.840.1.101.3.4.2.1', + 'id-sha384' => '2.16.840.1.101.3.4.2.2', + 'id-sha512' => '2.16.840.1.101.3.4.2.3', + // from PKCS1 v2.2 + 'id-sha224' => '2.16.840.1.101.3.4.2.4', + 'id-sha512/224' => '2.16.840.1.101.3.4.2.5', + 'id-sha512/256' => '2.16.840.1.101.3.4.2.6', + ]; + ASN1::loadOIDs($oids); + } + + $decoded = ASN1::asn1map($decoded[0], DigestInfo::MAP); + if (!isset($decoded) || $decoded === false) { + return false; + } + + if (!isset($oids[$decoded['digestAlgorithm']['algorithm']])) { + return false; + } + + $hash = $decoded['digestAlgorithm']['algorithm']; + $hash = substr($hash, 0, 3) == 'id-' ? + substr($hash, 3) : + $hash; + $hash = new Hash($hash); + $em = $hash->hash($m); + $em2 = $decoded['digest']; + + return hash_equals($em, $em2); + } + + /** + * EMSA-PSS-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}. + * + * @access private + * @param string $m + * @param string $em + * @param int $emBits + * @return string + */ + private function emsa_pss_verify($m, $em, $emBits) + { + // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8); + $sLen = $this->sLen !== null ? $this->sLen : $this->hLen; + + $mHash = $this->hash->hash($m); + if ($emLen < $this->hLen + $sLen + 2) { + return false; + } + + if ($em[strlen($em) - 1] != chr(0xBC)) { + return false; + } + + $maskedDB = substr($em, 0, -$this->hLen - 1); + $h = substr($em, -$this->hLen - 1, $this->hLen); + $temp = chr(0xFF << ($emBits & 7)); + if ((~$maskedDB[0] & $temp) != $temp) { + return false; + } + $dbMask = $this->mgf1($h, $emLen - $this->hLen - 1); + $db = $maskedDB ^ $dbMask; + $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0]; + $temp = $emLen - $this->hLen - $sLen - 2; + if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) { + return false; + } + $salt = substr($db, $temp + 1); // should be $sLen long + $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; + $h2 = $this->hash->hash($m2); + return hash_equals($h, $h2); + } + + /** + * RSASSA-PSS-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}. + * + * @access private + * @param string $m + * @param string $s + * @return bool|string + */ + private function rsassa_pss_verify($m, $s) + { + // Length checking + + if (strlen($s) != $this->k) { + return false; + } + + // RSA verification + + $modBits = 8 * $this->k; + + $s2 = $this->os2ip($s); + $m2 = $this->rsavp1($s2); + $em = $this->i2osp($m2, $modBits >> 3); + if ($em === false) { + return false; + } + + // EMSA-PSS verification + + return $this->emsa_pss_verify($m, $em, $modBits - 1); + } + + /** + * Verifies a signature + * + * @see self::sign() + * @param string $message + * @param string $signature + * @return bool + */ + public function verify($message, $signature) + { + switch ($this->signaturePadding) { + case self::SIGNATURE_RELAXED_PKCS1: + return $this->rsassa_pkcs1_v1_5_relaxed_verify($message, $signature); + case self::SIGNATURE_PKCS1: + return $this->rsassa_pkcs1_v1_5_verify($message, $signature); + //case self::SIGNATURE_PSS: + default: + return $this->rsassa_pss_verify($message, $signature); + } + } + + /** + * RSAES-PKCS1-V1_5-ENCRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}. + * + * @access private + * @param string $m + * @param bool $pkcs15_compat optional + * @throws \LengthException if strlen($m) > $this->k - 11 + * @return bool|string + */ + private function rsaes_pkcs1_v1_5_encrypt($m, $pkcs15_compat = false) + { + $mLen = strlen($m); + + // Length checking + + if ($mLen > $this->k - 11) { + throw new \LengthException('Message too long'); + } + + // EME-PKCS1-v1_5 encoding + + $psLen = $this->k - $mLen - 3; + $ps = ''; + while (strlen($ps) != $psLen) { + $temp = Random::string($psLen - strlen($ps)); + $temp = str_replace("\x00", '', $temp); + $ps.= $temp; + } + $type = 2; + // see the comments of _rsaes_pkcs1_v1_5_decrypt() to understand why this is being done + if ($pkcs15_compat && (!isset($this->publicExponent) || $this->exponent !== $this->publicExponent)) { + $type = 1; + // "The padding string PS shall consist of k-3-||D|| octets. ... for block type 01, they shall have value FF" + $ps = str_repeat("\xFF", $psLen); + } + $em = chr(0) . chr($type) . $ps . chr(0) . $m; + + // RSA encryption + $m = $this->os2ip($em); + $c = $this->rsaep($m); + $c = $this->i2osp($c, $this->k); + + // Output the ciphertext C + + return $c; + } + + /** + * RSAES-OAEP-ENCRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.1 RFC3447#section-7.1.1} and + * {http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding OAES}. + * + * @access private + * @param string $m + * @throws \LengthException if strlen($m) > $this->k - 2 * $this->hLen - 2 + * @return string + */ + private function rsaes_oaep_encrypt($m) + { + $mLen = strlen($m); + + // Length checking + + // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + if ($mLen > $this->k - 2 * $this->hLen - 2) { + throw new \LengthException('Message too long'); + } + + // EME-OAEP encoding + + $lHash = $this->hash->hash($this->label); + $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2); + $db = $lHash . $ps . chr(1) . $m; + $seed = Random::string($this->hLen); + $dbMask = $this->mgf1($seed, $this->k - $this->hLen - 1); + $maskedDB = $db ^ $dbMask; + $seedMask = $this->mgf1($maskedDB, $this->hLen); + $maskedSeed = $seed ^ $seedMask; + $em = chr(0) . $maskedSeed . $maskedDB; + + // RSA encryption + + $m = $this->os2ip($em); + $c = $this->rsaep($m); + $c = $this->i2osp($c, $this->k); + + // Output the ciphertext C + + return $c; + } + + /** + * RSAEP + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}. + * + * @access private + * @param \phpseclib\Math\BigInteger $m + * @return bool|\phpseclib\Math\BigInteger + */ + private function rsaep($m) + { + if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) { + return false; + } + return $this->exponentiate($m); + } + + /** + * Raw Encryption / Decryption + * + * Doesn't use padding and is not recommended. + * + * @access private + * @param string $m + * @return bool|string + * @throws \LengthException if strlen($m) > $this->k + */ + private function raw_encrypt($m) + { + if (strlen($m) > $this->k) { + throw new \LengthException('Message too long'); + } + + $temp = $this->os2ip($m); + $temp = $this->rsaep($temp); + return $this->i2osp($temp, $this->k); + } + + /** + * Encryption + * + * Both self::PADDING_OAEP and self::PADDING_PKCS1 both place limits on how long $plaintext can be. + * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will + * be concatenated together. + * + * @see self::decrypt() + * @access public + * @param string $plaintext + * @return bool|string + * @throws \LengthException if the RSA modulus is too short + */ + public function encrypt($plaintext) + { + switch ($this->encryptionPadding) { + case self::ENCRYPTION_NONE: + return $this->raw_encrypt($plaintext); + case self::ENCRYPTION_PKCS15_COMPAT: + case self::ENCRYPTION_PKCS1: + return $this->rsaes_pkcs1_v1_5_encrypt($plaintext, $padding == self::ENCRYPTION_PKCS15_COMPAT); + //case self::ENCRYPTION_OAEP: + default: + return $this->rsaes_oaep_encrypt($plaintext); + } + } + + /** + * Returns the public key + * + * The public key is only returned under two circumstances - if the private key had the public key embedded within it + * or if the public key was set via setPublicKey(). If the currently loaded key is supposed to be the public key this + * function won't return it since this library, for the most part, doesn't distinguish between public and private keys. + * + * @param string $type + * @return mixed + */ + public function toString($type) + { + $type = self::validatePlugin('Keys', $type, 'savePublicKey'); + + return $type::savePublicKey($this->modulus, $this->publicExponent); + } + + /** + * Converts a public key to a private key + * + * @return RSA + */ + public function asPrivateKey() + { + $new = new PrivateKey; + $new->exponent = $this->exponent; + $new->modulus = $this->modulus; + $new->k = $this->k; + $new->format = $this->format; + return $new + ->withHash($this->hash->getHash()) + ->withMGFHash($this->mgfHash->getHash()) + ->withSaltLength($this->sLen) + ->withLabel($this->label) + ->withPadding($this->signaturePadding | $this->encryptionPadding); + } +} diff --git a/phpseclib/Exception/UnsupportedFormatException.php b/phpseclib/Exception/UnsupportedFormatException.php new file mode 100644 index 00000000..439163a5 --- /dev/null +++ b/phpseclib/Exception/UnsupportedFormatException.php @@ -0,0 +1,26 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Exception; + +/** + * UnsupportedFormatException + * + * @package UnsupportedFormatException + * @author Jim Wigginton + */ +class UnsupportedFormatException extends \RuntimeException +{ +} diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index d0c6c77f..a9ca1f90 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -31,6 +31,10 @@ use ParagonIE\ConstantTime\Hex; use phpseclib\Crypt\Hash; use phpseclib\Crypt\Random; use phpseclib\Crypt\RSA; +use phpseclib\Crypt\DSA; +use phpseclib\Crypt\ECDSA; +use phpseclib\Crypt\Common\PublicKey; +use phpseclib\Crypt\Common\PrivateKey; use phpseclib\Exception\UnsupportedAlgorithmException; use phpseclib\File\ASN1\Element; use phpseclib\Math\BigInteger; @@ -273,18 +277,18 @@ class X509 if (!self::$oidsLoaded) { // OIDs from RFC5280 and those RFCs mentioned in RFC5280#section-4.1.1.2 ASN1::loadOIDs([ - 'id-pkix' => '1.3.6.1.5.5.7', - 'id-pe' => '1.3.6.1.5.5.7.1', - 'id-qt' => '1.3.6.1.5.5.7.2', - 'id-kp' => '1.3.6.1.5.5.7.3', - 'id-ad' => '1.3.6.1.5.5.7.48', + //'id-pkix' => '1.3.6.1.5.5.7', + //'id-pe' => '1.3.6.1.5.5.7.1', + //'id-qt' => '1.3.6.1.5.5.7.2', + //'id-kp' => '1.3.6.1.5.5.7.3', + //'id-ad' => '1.3.6.1.5.5.7.48', 'id-qt-cps' => '1.3.6.1.5.5.7.2.1', 'id-qt-unotice' => '1.3.6.1.5.5.7.2.2', 'id-ad-ocsp' =>'1.3.6.1.5.5.7.48.1', 'id-ad-caIssuers' => '1.3.6.1.5.5.7.48.2', 'id-ad-timeStamping' => '1.3.6.1.5.5.7.48.3', 'id-ad-caRepository' => '1.3.6.1.5.5.7.48.5', - 'id-at' => '2.5.4', + //'id-at' => '2.5.4', 'id-at-name' => '2.5.4.41', 'id-at-surname' => '2.5.4.4', 'id-at-givenName' => '2.5.4.42', @@ -307,18 +311,19 @@ class X509 'id-at-role' => '2.5.4.72', 'id-at-postalAddress' => '2.5.4.16', - 'id-domainComponent' => '0.9.2342.19200300.100.1.25', - 'pkcs-9' => '1.2.840.113549.1.9', + //'id-domainComponent' => '0.9.2342.19200300.100.1.25', + //'pkcs-9' => '1.2.840.113549.1.9', 'pkcs-9-at-emailAddress' => '1.2.840.113549.1.9.1', - 'id-ce' => '2.5.29', + //'id-ce' => '2.5.29', 'id-ce-authorityKeyIdentifier' => '2.5.29.35', 'id-ce-subjectKeyIdentifier' => '2.5.29.14', 'id-ce-keyUsage' => '2.5.29.15', 'id-ce-privateKeyUsagePeriod' => '2.5.29.16', 'id-ce-certificatePolicies' => '2.5.29.32', - 'anyPolicy' => '2.5.29.32.0', + //'anyPolicy' => '2.5.29.32.0', 'id-ce-policyMappings' => '2.5.29.33', + 'id-ce-subjectAltName' => '2.5.29.17', 'id-ce-issuerAltName' => '2.5.29.18', 'id-ce-subjectDirectoryAttributes' => '2.5.29.9', @@ -327,7 +332,7 @@ class X509 'id-ce-policyConstraints' => '2.5.29.36', 'id-ce-cRLDistributionPoints' => '2.5.29.31', 'id-ce-extKeyUsage' => '2.5.29.37', - 'anyExtendedKeyUsage' => '2.5.29.37.0', + //'anyExtendedKeyUsage' => '2.5.29.37.0', 'id-kp-serverAuth' => '1.3.6.1.5.5.7.3.1', 'id-kp-clientAuth' => '1.3.6.1.5.5.7.3.2', 'id-kp-codeSigning' => '1.3.6.1.5.5.7.3.3', @@ -344,82 +349,47 @@ class X509 'id-ce-cRLReasons' => '2.5.29.21', 'id-ce-certificateIssuer' => '2.5.29.29', 'id-ce-holdInstructionCode' => '2.5.29.23', - 'holdInstruction' => '1.2.840.10040.2', + //'holdInstruction' => '1.2.840.10040.2', 'id-holdinstruction-none' => '1.2.840.10040.2.1', 'id-holdinstruction-callissuer' => '1.2.840.10040.2.2', 'id-holdinstruction-reject' => '1.2.840.10040.2.3', 'id-ce-invalidityDate' => '2.5.29.24', - 'md2' => '1.2.840.113549.2.2', - 'md5' => '1.2.840.113549.2.5', - 'id-sha1' => '1.3.14.3.2.26', - 'id-dsa' => '1.2.840.10040.4.1', - 'id-dsa-with-sha1' => '1.2.840.10040.4.3', - 'pkcs-1' => '1.2.840.113549.1.1', 'rsaEncryption' => '1.2.840.113549.1.1.1', 'md2WithRSAEncryption' => '1.2.840.113549.1.1.2', 'md5WithRSAEncryption' => '1.2.840.113549.1.1.4', 'sha1WithRSAEncryption' => '1.2.840.113549.1.1.5', - 'dhpublicnumber' => '1.2.840.10046.2.1', - 'id-keyExchangeAlgorithm' => '2.16.840.1.101.2.1.1.22', - 'ansi-X9-62' => '1.2.840.10045', - 'id-ecSigType' => '1.2.840.10045.4', - 'ecdsa-with-SHA1' => '1.2.840.10045.4.1', - 'id-fieldType' => '1.2.840.10045.1', - 'prime-field' => '1.2.840.10045.1.1', - 'characteristic-two-field' => '1.2.840.10045.1.2', - 'id-characteristic-two-basis' => '1.2.840.10045.1.2.3', - 'gnBasis' => '1.2.840.10045.1.2.3.1', - 'tpBasis' => '1.2.840.10045.1.2.3.2', - 'ppBasis' => '1.2.840.10045.1.2.3.3', - 'id-publicKeyType' => '1.2.840.10045.2', - 'id-ecPublicKey' => '1.2.840.10045.2.1', - 'ellipticCurve' => '1.2.840.10045.3', - 'c-TwoCurve' => '1.2.840.10045.3.0', - 'c2pnb163v1' => '1.2.840.10045.3.0.1', - 'c2pnb163v2' => '1.2.840.10045.3.0.2', - 'c2pnb163v3' => '1.2.840.10045.3.0.3', - 'c2pnb176w1' => '1.2.840.10045.3.0.4', - 'c2pnb191v1' => '1.2.840.10045.3.0.5', - 'c2pnb191v2' => '1.2.840.10045.3.0.6', - 'c2pnb191v3' => '1.2.840.10045.3.0.7', - 'c2pnb191v4' => '1.2.840.10045.3.0.8', - 'c2pnb191v5' => '1.2.840.10045.3.0.9', - 'c2pnb208w1' => '1.2.840.10045.3.0.10', - 'c2pnb239v1' => '1.2.840.10045.3.0.11', - 'c2pnb239v2' => '1.2.840.10045.3.0.12', - 'c2pnb239v3' => '1.2.840.10045.3.0.13', - 'c2pnb239v4' => '1.2.840.10045.3.0.14', - 'c2pnb239v5' => '1.2.840.10045.3.0.15', - 'c2pnb272w1' => '1.2.840.10045.3.0.16', - 'c2pnb304w1' => '1.2.840.10045.3.0.17', - 'c2pnb359v1' => '1.2.840.10045.3.0.18', - 'c2pnb368w1' => '1.2.840.10045.3.0.19', - 'c2pnb431r1' => '1.2.840.10045.3.0.20', - 'primeCurve' => '1.2.840.10045.3.1', - 'prime192v1' => '1.2.840.10045.3.1.1', - 'prime192v2' => '1.2.840.10045.3.1.2', - 'prime192v3' => '1.2.840.10045.3.1.3', - 'prime239v1' => '1.2.840.10045.3.1.4', - 'prime239v2' => '1.2.840.10045.3.1.5', - 'prime239v3' => '1.2.840.10045.3.1.6', - 'prime256v1' => '1.2.840.10045.3.1.7', - 'id-RSAES-OAEP' => '1.2.840.113549.1.1.7', - 'id-pSpecified' => '1.2.840.113549.1.1.9', - 'id-RSASSA-PSS' => '1.2.840.113549.1.1.10', - 'id-mgf1' => '1.2.840.113549.1.1.8', 'sha224WithRSAEncryption' => '1.2.840.113549.1.1.14', 'sha256WithRSAEncryption' => '1.2.840.113549.1.1.11', 'sha384WithRSAEncryption' => '1.2.840.113549.1.1.12', 'sha512WithRSAEncryption' => '1.2.840.113549.1.1.13', - 'id-sha224' => '2.16.840.1.101.3.4.2.4', - 'id-sha256' => '2.16.840.1.101.3.4.2.1', - 'id-sha384' => '2.16.840.1.101.3.4.2.2', - 'id-sha512' => '2.16.840.1.101.3.4.2.3', - 'id-GostR3411-94-with-GostR3410-94' => '1.2.643.2.2.4', - 'id-GostR3411-94-with-GostR3410-2001' => '1.2.643.2.2.3', - 'id-GostR3410-2001' => '1.2.643.2.2.20', - 'id-GostR3410-94' => '1.2.643.2.2.19', + + 'id-ecPublicKey' => '1.2.840.10045.2.1', + 'ecdsa-with-SHA1' => '1.2.840.10045.4.1', + // from https://tools.ietf.org/html/rfc5758#section-3.2 + 'ecdsa-with-SHA224' => '1.2.840.10045.4.3.1', + 'ecdsa-with-SHA256' => '1.2.840.10045.4.3.2', + 'ecdsa-with-SHA384' => '1.2.840.10045.4.3.3', + 'ecdsa-with-SHA512' => '1.2.840.10045.4.3.4', + + 'id-dsa' => '1.2.840.10040.4.1', + 'id-dsa-with-sha1' => '1.2.840.10040.4.3', + // from https://tools.ietf.org/html/rfc5758#section-3.1 + 'id-dsa-with-sha224' => '2.16.840.1.101.3.4.3.1', + 'id-dsa-with-sha256' => '2.16.840.1.101.3.4.3.2', + + // from https://tools.ietf.org/html/rfc8410: + 'id-Ed25519' => '1.3.101.112', + 'id-Ed448' => '1.3.101.113', + + //'id-sha224' => '2.16.840.1.101.3.4.2.4', + //'id-sha256' => '2.16.840.1.101.3.4.2.1', + //'id-sha384' => '2.16.840.1.101.3.4.2.2', + //'id-sha512' => '2.16.840.1.101.3.4.2.3', + //'id-GostR3411-94-with-GostR3410-94' => '1.2.643.2.2.4', + //'id-GostR3411-94-with-GostR3410-2001' => '1.2.643.2.2.3', + //'id-GostR3410-2001' => '1.2.643.2.2.20', + //'id-GostR3410-94' => '1.2.643.2.2.19', // Netscape Object Identifiers from "Netscape Certificate Extensions" 'netscape' => '2.16.840.1.113730', 'netscape-cert-extension' => '2.16.840.1.113730.1', @@ -499,8 +469,12 @@ class X509 $this->mapInDNs($x509, 'tbsCertificate/issuer/rdnSequence'); $this->mapInDNs($x509, 'tbsCertificate/subject/rdnSequence'); - $key = &$x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']; - $key = $this->reformatKey($x509['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $key); + $key = $x509['tbsCertificate']['subjectPublicKeyInfo']; + $key = ASN1::encodeDER($key, Maps\SubjectPublicKeyInfo::MAP); + $x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'] = + "-----BEGIN PUBLIC KEY-----\r\n" . + chunk_split(base64_encode($key), 64) . + "-----END PUBLIC KEY-----"; $this->currentCert = $x509; $this->dn = $x509['tbsCertificate']['subject']; @@ -531,21 +505,14 @@ class X509 case is_object($cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']): break; default: - switch ($algorithm) { - case 'rsaEncryption': - $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'] - = "\0" . Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'])); - /* "[For RSA keys] the parameters field MUST have ASN.1 type NULL for this algorithm identifier." - -- https://tools.ietf.org/html/rfc3279#section-2.3.1 + $cert['tbsCertificate']['subjectPublicKeyInfo'] = new Element( + base64_decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'])) + ); + } - given that and the fact that RSA keys appear to be the only key type for which the parameters field can be blank, - it seems like perhaps the ASN.1 description ought not say the parameters field is OPTIONAL, but whatever. - */ - $cert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = null; - // https://tools.ietf.org/html/rfc3279#section-2.2.1 - $cert['signatureAlgorithm']['parameters'] = null; - $cert['tbsCertificate']['signature']['parameters'] = null; - } + if ($algorithm == 'rsaEncryption') { + $cert['signatureAlgorithm']['parameters'] = null; + $cert['tbsCertificate']['signature']['parameters'] = null; } $filters = []; @@ -1389,9 +1356,7 @@ class X509 { switch ($publicKeyAlgorithm) { case 'rsaEncryption': - $rsa = new RSA(); - $rsa->load($publicKey); - + $key = RSA::load($publicKey, 'PKCS8'); switch ($signatureAlgorithm) { case 'md2WithRSAEncryption': case 'md5WithRSAEncryption': @@ -1400,10 +1365,41 @@ class X509 case 'sha256WithRSAEncryption': case 'sha384WithRSAEncryption': case 'sha512WithRSAEncryption': - $rsa->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)); - if (!@$rsa->verify($signatureSubject, $signature, RSA::PADDING_PKCS1)) { - return false; - } + $key = $key + ->withHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)) + ->withPadding(RSA::SIGNATURE_PKCS1); + break; + default: + throw new UnsupportedAlgorithmException('Signature algorithm unsupported'); + } + break; + case 'id-Ed25519': + case 'id-Ed448': + $key = ECDSA::load($publicKey, 'PKCS8'); + break; + case 'id-ecPublicKey': + $key = ECDSA::load($publicKey, 'PKCS8'); + switch ($signatureAlgorithm) { + case 'ecdsa-with-SHA1': + case 'ecdsa-with-SHA224': + case 'ecdsa-with-SHA256': + case 'ecdsa-with-SHA384': + case 'ecdsa-with-SHA512': + $key = $key + ->withHash(preg_replace('#^ecdsa-with-#', '', strtolower($signatureAlgorithm))); + break; + default: + throw new UnsupportedAlgorithmException('Signature algorithm unsupported'); + } + break; + case 'id-dsa': + $key = DSA::load($publicKey, 'PKCS8'); + switch ($signatureAlgorithm) { + case 'id-dsa-with-sha1': + case 'id-dsa-with-sha224': + case 'id-dsa-with-sha256': + $key = $key + ->withHash(preg_replace('#^id-dsa-with-#', '', strtolower($signatureAlgorithm))); break; default: throw new UnsupportedAlgorithmException('Signature algorithm unsupported'); @@ -1413,7 +1409,7 @@ class X509 throw new UnsupportedAlgorithmException('Public key algorithm unsupported'); } - return true; + return $key->verify($signatureSubject, $signature); } /** @@ -1451,32 +1447,6 @@ class X509 self::$disable_url_fetch = false; } - /** - * Reformat public keys - * - * Reformats a public key to a format supported by phpseclib (if applicable) - * - * @param string $algorithm - * @param string $key - * @access private - * @return string - */ - private function reformatKey($algorithm, $key) - { - switch ($algorithm) { - case 'rsaEncryption': - return - "-----BEGIN RSA PUBLIC KEY-----\r\n" . - // subjectPublicKey is stored as a bit string in X.509 certs. the first byte of a bit string represents how many bits - // in the last byte should be ignored. the following only supports non-zero stuff but as none of the X.509 certs Firefox - // uses as a cert authority actually use a non-zero bit I think it's safe to assume that none do. - chunk_split(Base64::encode(substr($key, 1)), 64) . - '-----END RSA PUBLIC KEY-----'; - default: - return $key; - } - } - /** * Decodes an IP address * @@ -2053,9 +2023,8 @@ class X509 * @access public * @return bool */ - public function setPublicKey($key) + public function setPublicKey(PublicKey $key) { - $key->setPublicKey(); $this->publicKey = $key; } @@ -2067,7 +2036,7 @@ class X509 * @param object $key * @access public */ - public function setPrivateKey($key) + public function setPrivateKey(PrivateKey $key) { $this->privateKey = $key; } @@ -2115,15 +2084,16 @@ class X509 switch ($keyinfo['algorithm']['algorithm']) { case 'rsaEncryption': - $publicKey = new RSA(); - $publicKey->load($key); - $publicKey->setPublicKey(); - break; - default: - return false; + return RSA::load($key, 'PKCS8'); + case 'id-ecPublicKey': + case 'id-Ed25519': + case 'id-Ed448': + return ECDSA::load($key, 'PKCS8'); + case 'id-dsa': + return DSA::load($key, 'PKCS8'); } - return $publicKey; + return false; } /** @@ -2185,19 +2155,15 @@ class X509 $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); - $algorithm = &$csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm']; - $key = &$csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']; - $key = $this->reformatKey($algorithm, $key); + $key = $csr['certificationRequestInfo']['subjectPKInfo']; + $key = ASN1::encodeDER($key, Maps\SubjectPublicKeyInfo::MAP); + $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'] = + "-----BEGIN PUBLIC KEY-----\r\n" . + chunk_split(base64_encode($key), 64) . + "-----END PUBLIC KEY-----"; - switch ($algorithm) { - case 'rsaEncryption': - $this->publicKey = new RSA(); - $this->publicKey->load($key); - $this->publicKey->setPublicKey(); - break; - default: - $this->publicKey = null; - } + $this->publicKey = null; + $this->publicKey = $this->getPublicKey(); $this->currentKeyIdentifier = null; $this->currentCert = $csr; @@ -2224,14 +2190,9 @@ class X509 case is_object($csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']): break; default: - switch ($algorithm) { - case 'rsaEncryption': - $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'] - = "\0" . Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'])); - $csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['parameters'] = null; - $csr['signatureAlgorithm']['parameters'] = null; - $csr['certificationRequestInfo']['signature']['parameters'] = null; - } + $csr['certificationRequestInfo']['subjectPKInfo'] = new Element( + base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'])) + ); } $filters = []; @@ -2305,18 +2266,15 @@ class X509 $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); - $algorithm = &$spkac['publicKeyAndChallenge']['spki']['algorithm']['algorithm']; - $key = &$spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']; - $key = $this->reformatKey($algorithm, $key); + $key = $spkac['publicKeyAndChallenge']['spki']; + $key = ASN1::encodeDER($key, Maps\SubjectPublicKeyInfo::MAP); + $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'] = + "-----BEGIN PUBLIC KEY-----\r\n" . + chunk_split(base64_encode($key), 64) . + "-----END PUBLIC KEY-----"; - switch ($algorithm) { - case 'rsaEncryption': - $this->publicKey = new RSA(); - $this->publicKey->load($key); - break; - default: - $this->publicKey = null; - } + $this->publicKey = null; + $this->publicKey = $this->getPublicKey(); $this->currentKeyIdentifier = null; $this->currentCert = $spkac; @@ -2344,11 +2302,9 @@ class X509 case is_object($spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']): break; default: - switch ($algorithm) { - case 'rsaEncryption': - $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'] - = "\0" . Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'])); - } + $spkac['publicKeyAndChallenge']['spki'] = new Element( + base64_decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'])) + ); } $spkac = ASN1::encodeDER($spkac, Maps\SignedPublicKeyAndChallenge::MAP); @@ -2535,7 +2491,7 @@ class X509 } $currentCert = isset($this->currentCert) ? $this->currentCert : null; - $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null; + $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null; if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) { $this->currentCert = $subject->currentCert; @@ -2713,17 +2669,12 @@ class X509 } $origPublicKey = $this->publicKey; - $class = get_class($this->privateKey); - $this->publicKey = new $class(); - $this->publicKey->load($this->privateKey->getPublicKey()); - $this->publicKey->setPublicKey(); - if (!($publicKey = $this->formatSubjectPublicKey())) { - return false; - } + $this->publicKey = $this->privateKey->getPublicKey(); + $publicKey = $this->formatSubjectPublicKey(); $this->publicKey = $origPublicKey; $currentCert = isset($this->currentCert) ? $this->currentCert : null; - $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null; + $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null; if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['certificationRequestInfo'])) { $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; @@ -2772,18 +2723,12 @@ class X509 } $origPublicKey = $this->publicKey; - $class = get_class($this->privateKey); - $this->publicKey = new $class(); - $this->publicKey->load($this->privateKey->getPublicKey()); - $this->publicKey->setPublicKey(); + $this->publicKey = $this->privateKey->getPublicKey(); $publicKey = $this->formatSubjectPublicKey(); - if (!$publicKey) { - return false; - } $this->publicKey = $origPublicKey; $currentCert = isset($this->currentCert) ? $this->currentCert : null; - $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null; + $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null; // re-signing a SPKAC seems silly but since everything else supports re-signing why not? if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['publicKeyAndChallenge'])) { @@ -2966,7 +2911,7 @@ class X509 * @throws \phpseclib\Exception\UnsupportedAlgorithmException if the algorithm is unsupported * @return mixed */ - private function signHelper($key, $signatureAlgorithm) + private function signHelper(PrivateKey $key, $signatureAlgorithm) { if ($key instanceof RSA) { switch ($signatureAlgorithm) { @@ -2977,9 +2922,52 @@ class X509 case 'sha256WithRSAEncryption': case 'sha384WithRSAEncryption': case 'sha512WithRSAEncryption': - $key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)); + $key = $key + ->withHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)) + ->withPadding(RSA::SIGNATURE_PKCS1); + $this->currentCert['signature'] = "\0" . $key->sign($this->signatureSubject); + return $this->currentCert; + default: + throw new UnsupportedAlgorithmException('Signature algorithm unsupported'); + } + } - $this->currentCert['signature'] = "\0" . $key->sign($this->signatureSubject, RSA::PADDING_PKCS1); + if ($key instanceof DSA) { + switch ($signatureAlgorithm) { + case 'id-dsa-with-sha1': + case 'id-dsa-with-sha224': + case 'id-dsa-with-sha256': + $key = $key + ->withHash(preg_replace('#^id-dsa-with-#', '', strtolower($signatureAlgorithm))); + $this->currentCert['signature'] = "\0" . $key->sign($this->signatureSubject); + return $this->currentCert; + default: + throw new UnsupportedAlgorithmException('Signature algorithm unsupported'); + } + } + + if ($key instanceof ECDSA) { + switch ($signatureAlgorithm) { + case 'id-Ed25519': + if ($key->getCurve() !== 'Ed25519') { + throw new UnsupportedAlgorithmException('Loaded ECDSA does not use the Ed25519 key and yet that is the signature algorithm that has been chosen'); + } + $this->currentCert['signature'] = "\0" . $key->sign($this->signatureSubject); + return $this->currentCert; + case 'id-Ed448': + if ($key->getCurve() !== 'Ed448') { + throw new UnsupportedAlgorithmException('Loaded ECDSA does not use the Ed448 key and yet that is the signature algorithm that has been chosen'); + } + $this->currentCert['signature'] = "\0" . $key->sign($this->signatureSubject); + return $this->currentCert; + case 'ecdsa-with-SHA1': + case 'ecdsa-with-SHA224': + case 'ecdsa-with-SHA256': + case 'ecdsa-with-SHA384': + case 'ecdsa-with-SHA512': + $key = $key + ->withHash(preg_replace('#^ecdsa-with-#', '', strtolower($signatureAlgorithm))); + $this->currentCert['signature'] = "\0" . $key->sign($this->signatureSubject); return $this->currentCert; default: throw new UnsupportedAlgorithmException('Signature algorithm unsupported'); @@ -3669,17 +3657,14 @@ class X509 */ private function formatSubjectPublicKey() { - if ($this->publicKey instanceof RSA) { - // the following two return statements do the same thing. i dunno.. i just prefer the later for some reason. - // the former is a good example of how to do fuzzing on the public key - //return new Element(preg_replace('#-.+-|[\r\n]#', '', $this->publicKey->getPublicKey())); - return [ - 'algorithm' => ['algorithm' => 'rsaEncryption'], - 'subjectPublicKey' => $this->publicKey->getPublicKey('PKCS1') - ]; - } + $publicKey = base64_decode(preg_replace('#-.+-|[\r\n]#', '', $this->publicKey)); - return false; + $decoded = ASN1::decodeBER($publicKey); + $mapped = ASN1::asn1map($decoded[0], Maps\SubjectPublicKeyInfo::MAP); + + $mapped['subjectPublicKey'] = (string) $this->publicKey; + + return $mapped; } /** diff --git a/phpseclib/Math/BigInteger/Engines/OpenSSL.php b/phpseclib/Math/BigInteger/Engines/OpenSSL.php index 6a1aed52..0d7af804 100644 --- a/phpseclib/Math/BigInteger/Engines/OpenSSL.php +++ b/phpseclib/Math/BigInteger/Engines/OpenSSL.php @@ -16,6 +16,7 @@ namespace phpseclib\Math\BigInteger\Engines; use phpseclib\Crypt\RSA; +use phpseclib\Crypt\RSA\Keys\PKCS8; use phpseclib\Math\BigInteger; /** @@ -51,11 +52,11 @@ abstract class OpenSSL throw new \OutOfRangeException('Only modulo between 31 and 16384 bits are accepted'); } - $rsa = new RSA(); - $rsa->load([ - 'e' => new BigInteger($e), - 'n' => new BigInteger($n) - ]); + $key = PKCS8::savePublicKey( + new BigInteger($n), + new BigInteger($e) + ); + $rsa = RSA::load($key); //$rsa->setPublicKeyFormat('PKCS1'); $plaintext = str_pad($x->toBytes(), strlen($n->toBytes(true)) - 1, "\0", STR_PAD_LEFT); diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index 6d738c53..5c159700 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -24,7 +24,7 @@ * setPassword('whatever'); * $key->load(file_get_contents('privatekey')); * @@ -49,12 +49,12 @@ namespace phpseclib\Net; -use ParagonIE\ConstantTime\Base64; use phpseclib\Crypt\Blowfish; use phpseclib\Crypt\Hash; use phpseclib\Crypt\Random; use phpseclib\Crypt\RC4; use phpseclib\Crypt\Rijndael; +use phpseclib\Crypt\Common\PrivateKey; use phpseclib\Crypt\RSA; use phpseclib\Crypt\DSA; use phpseclib\Crypt\ECDSA; @@ -66,6 +66,7 @@ use phpseclib\System\SSH\Agent; use phpseclib\System\SSH\Agent\Identity as AgentIdentity; use phpseclib\Exception\NoSupportedAlgorithmsException; use phpseclib\Exception\UnsupportedAlgorithmException; +use phpseclib\Exception\UnsupportedCurveException; use phpseclib\Common\Functions\Strings; /** @@ -2112,9 +2113,11 @@ class SSH2 return !is_string($password) && !is_array($password) ? false : $this->keyboard_interactive_process($password); } - if ($password instanceof RSA) { + if ($password instanceof PrivateKey) { return $this->privatekey_login($username, $password); - } elseif ($password instanceof Agent) { + } + + if ($password instanceof Agent) { return $this->ssh_agent_login($username, $password); } @@ -2126,6 +2129,10 @@ class SSH2 return false; } + if (!is_string($password)) { + throw new \UnexpectedValueException('$password needs to either be an instance of \phpseclib\Crypt\Common\PrivateKey, \System\SSH\Agent, an array or a string'); + } + if (!isset($password)) { $packet = Strings::packSSH2( 'Cs3', @@ -2361,7 +2368,7 @@ class SSH2 * @return bool * @access private */ - private function ssh_agent_login($username, $agent) + private function ssh_agent_login($username, Agent $agent) { $this->agent = $agent; $keys = $agent->requestIdentities(); @@ -2378,42 +2385,69 @@ class SSH2 * Login with an RSA private key * * @param string $username - * @param \phpseclib\Crypt\RSA $password + * @param \phpseclib\Crypt\Common\PrivateKey $privatekey * @return bool * @throws \RuntimeException on connection error * @access private * @internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis} * by sending dummy SSH_MSG_IGNORE messages. */ - private function privatekey_login($username, $privatekey) + private function privatekey_login($username, PrivateKey $privatekey) { - // see http://tools.ietf.org/html/rfc4253#page-15 - $publickey = $privatekey->getPublicKey('Raw'); - if ($publickey === false) { - return false; + $publickey = $privatekey->getPublicKey(); + + if ($publickey instanceof RSA) { + $privatekey = $privatekey->withPadding(RSA::SIGNATURE_PKCS1); + switch ($this->signature_format) { + case 'rsa-sha2-512': + $hash = 'sha512'; + $signatureType = 'rsa-sha2-512'; + break; + case 'rsa-sha2-256': + $hash = 'sha256'; + $signatureType = 'rsa-sha2-256'; + break; + //case 'ssh-rsa': + default: + $hash = 'sha1'; + $signatureType = 'ssh-rsa'; + } + } else if ($publickey instanceof ECDSA) { + $privatekey = $privatekey->withSignatureFormat('SSH2'); + $curveName = $privatekey->getCurve(); + switch ($curveName) { + case 'Ed25519': + $hash = 'sha512'; + $signatureType = 'ssh-ed25519'; + break; + case 'secp256r1': // nistp256 + $hash = 'sha256'; + $signatureType = 'ecdsa-sha2-nistp256'; + break; + case 'secp384r1': // nistp384 + $hash = 'sha384'; + $signatureType = 'ecdsa-sha2-nistp384'; + break; + case 'secp521r1': // nistp521 + $hash = 'sha512'; + $signatureType = 'ecdsa-sha2-nistp521'; + break; + default: + if (is_array($curveName)) { + throw new UnsupportedCurveException('Specified Curves are not supported by SSH2'); + } + throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported by phpseclib\'s SSH2 implementation'); + } + } else if ($publickey instanceof DSA) { + $privatekey = $privatekey->withSignatureFormat('SSH2'); + $hash = 'sha1'; + $signatureType = 'ssh-dss'; + } else { + throw new UnsupportedAlgorithmException('Please use either an RSA key, an ECDSA one or a DSA key'); } - $publickey = Strings::packSSH2( - 'sii', - 'ssh-rsa', - $publickey['e'], - $publickey['n'] - ); - - switch ($this->signature_format) { - case 'rsa-sha2-512': - $hash = 'sha512'; - $signatureType = 'rsa-sha2-512'; - break; - case 'rsa-sha2-256': - $hash = 'sha256'; - $signatureType = 'rsa-sha2-256'; - break; - //case 'ssh-rsa': - default: - $hash = 'sha1'; - $signatureType = 'ssh-rsa'; - } + $publickeyStr = $publickey->toString('OpenSSH'); + $publickeyStr = base64_decode(preg_replace('#(^.*? )|( .*?)$#', '', $publickeyStr)); $part1 = Strings::packSSH2( 'Csss', @@ -2422,7 +2456,7 @@ class SSH2 'ssh-connection', 'publickey' ); - $part2 = Strings::packSSH2('ss', $signatureType, $publickey); + $part2 = Strings::packSSH2('ss', $signatureType, $publickeyStr); $packet = $part1 . chr(0) . $part2; $this->send_binary_packet($packet); @@ -2438,7 +2472,6 @@ class SSH2 case NET_SSH2_MSG_USERAUTH_FAILURE: list($message) = Strings::unpackSSH2('s', $response); $this->errors[] = 'SSH_MSG_USERAUTH_FAILURE: ' . $message; - return false; case NET_SSH2_MSG_USERAUTH_PK_OK: // we'll just take it on faith that the public key blob and the public key algorithm name are as @@ -2453,9 +2486,11 @@ class SSH2 } $packet = $part1 . chr(1) . $part2; - $privatekey->setHash($hash); - $signature = $privatekey->sign(Strings::packSSH2('s', $this->session_id) . $packet, RSA::PADDING_PKCS1); - $signature = Strings::packSSH2('ss', $signatureType, $signature); + $privatekey = $privatekey->withHash($hash); + $signature = $privatekey->sign(Strings::packSSH2('s', $this->session_id) . $packet); + if ($publickey instanceof RSA) { + $signature = Strings::packSSH2('ss', $signatureType, $signature); + } $packet.= Strings::packSSH2('s', $signature); $this->send_binary_packet($packet); @@ -4258,6 +4293,8 @@ class SSH2 return [ 'ssh-ed25519', // https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-02 'ecdsa-sha2-nistp256', // RFC 5656 + 'ecdsa-sha2-nistp384', // RFC 5656 + 'ecdsa-sha2-nistp521', // RFC 5656 'rsa-sha2-256', // RFC 8332 'rsa-sha2-512', // RFC 8332 'ssh-rsa', // RECOMMENDED sign Raw RSA Key @@ -4553,7 +4590,7 @@ class SSH2 if ($this->signature_validated) { return $this->bitmap ? - $this->signature_format . ' ' . Base64::encode($this->server_public_host_key) : + $this->signature_format . ' ' . $server_public_host_key : false; } @@ -4562,27 +4599,30 @@ class SSH2 switch ($this->signature_format) { case 'ssh-ed25519': case 'ecdsa-sha2-nistp256': - $ec = new ECDSA(); - $ec->load($server_public_host_key, 'OpenSSH'); + case 'ecdsa-sha2-nistp384': + case 'ecdsa-sha2-nistp521': + $key = ECDSA::load($server_public_host_key, 'OpenSSH') + ->withSignatureFormat('SSH2'); switch ($this->signature_format) { case 'ssh-ed25519': - //$ec->setHash('sha512'); Strings::shift($signature, 4 + strlen('ssh-ed25519') + 4); + $hash = 'sha512'; break; case 'ecdsa-sha2-nistp256': - $ec->setHash('sha256'); + $hash = 'sha256'; + break; + case 'ecdsa-sha2-nistp384': + $hash = 'sha384'; + break; + case 'ecdsa-sha2-nistp521': + $hash = 'sha512'; } - if (!$ec->verify($this->exchange_hash, $signature, 'SSH2')) { - return $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); - }; + $key = $key->withHash($hash); break; case 'ssh-dss': - $dsa = new DSA(); - $dsa->load($server_public_host_key, 'OpenSSH'); - $dsa->setHash('sha1'); - if (!$dsa->verify($this->exchange_hash, $signature, 'SSH2')) { - return $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); - }; + $key = DSA::load($server_public_host_key, 'OpenSSH') + ->withSignatureFormat('SSH2') + ->withHash('sha1'); break; case 'ssh-rsa': case 'rsa-sha2-256': @@ -4594,8 +4634,8 @@ class SSH2 $temp = unpack('Nlength', Strings::shift($signature, 4)); $signature = Strings::shift($signature, $temp['length']); - $rsa = new RSA(); - $rsa->load($server_public_host_key, 'OpenSSH'); + $key = RSA::load($server_public_host_key, 'OpenSSH') + ->withPadding(RSA::SIGNATURE_PKCS1); switch ($this->signature_format) { case 'rsa-sha2-512': $hash = 'sha512'; @@ -4607,17 +4647,18 @@ class SSH2 default: $hash = 'sha1'; } - $rsa->setHash($hash); - if (!$rsa->verify($this->exchange_hash, $signature, RSA::PADDING_PKCS1)) { - return $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); - } + $key = $key->withHash($hash); break; default: $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); throw new NoSupportedAlgorithmsException('Unsupported signature format'); } - return $this->signature_format . ' ' . Base64::encode($this->server_public_host_key); + if (!$key->verify($this->exchange_hash, $signature)) { + return $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); + }; + + return $this->signature_format . ' ' . $server_public_host_key; } /** diff --git a/phpseclib/System/SSH/Agent.php b/phpseclib/System/SSH/Agent.php index 38d92907..1acb4ec2 100644 --- a/phpseclib/System/SSH/Agent.php +++ b/phpseclib/System/SSH/Agent.php @@ -38,6 +38,7 @@ use phpseclib\Crypt\RSA; use phpseclib\Exception\BadConfigurationException; use phpseclib\System\SSH\Agent\Identity; use phpseclib\Common\Functions\Strings; +use phpseclib\Crypt\PublicKeyLoader; /** * Pure-PHP ssh-agent client identity factory @@ -198,9 +199,8 @@ class Agent $temp = $key_blob; list($key_type) = Strings::unpackSSH2('s', $temp); switch ($key_type) { - case 'ssh-rsa': - $key = new RSA(); - $key->load($key_str); + case 'ssh-rsa': + $key = PublicKeyLoader::load(base64_encode($key_blob)); break; case 'ssh-dss': // not currently supported diff --git a/phpseclib/System/SSH/Agent/Identity.php b/phpseclib/System/SSH/Agent/Identity.php index 7f35a821..b1c102f4 100644 --- a/phpseclib/System/SSH/Agent/Identity.php +++ b/phpseclib/System/SSH/Agent/Identity.php @@ -20,6 +20,8 @@ use phpseclib\Crypt\RSA; use phpseclib\Exception\UnsupportedAlgorithmException; use phpseclib\System\SSH\Agent; use phpseclib\Common\Functions\Strings; +use phpseclib\Crypt\Common\PrivateKey; + /** * Pure-PHP ssh-agent client identity object @@ -34,7 +36,7 @@ use phpseclib\Common\Functions\Strings; * @author Jim Wigginton * @access internal */ -class Identity +class Identity implements PrivateKey { /**@+ * Signature Flags @@ -107,7 +109,6 @@ class Identity public function setPublicKey($key) { $this->key = $key; - $this->key->setPublicKey(); } /** @@ -135,32 +136,48 @@ class Identity */ public function getPublicKey($type = 'PKCS8') { - return $this->key->getPublicKey($type); + return $this->key; } /** * Sets the hash * - * ssh-agent only supports signatures with sha1 hashes but to maintain BC with RSA.php this function exists - * - * @param string $hash optional + * @param string $hash * @access public */ - public function setHash($hash) + public function withHash($hash) { - $this->flags = 0; + $new = clone $this; + $new->flags = 0; switch ($hash) { case 'sha1': break; case 'sha256': - $this->flags = self::SSH_AGENT_RSA2_256; + $new->flags = self::SSH_AGENT_RSA2_256; break; case 'sha512': - $this->flags = self::SSH_AGENT_RSA2_512; + $new->flags = self::SSH_AGENT_RSA2_512; break; default: throw new UnsupportedAlgorithmException('The only supported hashes for RSA are sha1, sha256 and sha512'); } + return $new; + } + + /** + * Sets the padding + * + * Only PKCS1 padding is supported + * + * @param string $padding + * @access public + */ + public function withPadding($padding = RSA::SIGNATURE_PKCS1) + { + if ($padding != RSA::SIGNATURE_PKCS1 && $padding != RSA::SIGNATURE_RELAXED_PKCS1) { + throw new UnsupportedAlgorithmException('ssh-agent can only create PKCS1 signatures'); + } + return $this; } /** @@ -175,12 +192,8 @@ class Identity * @throws \phpseclib\Exception\UnsupportedAlgorithmException if the algorithm is unsupported * @access public */ - public function sign($message, $padding = RSA::PADDING_PKCS1) + public function sign($message) { - if ($padding != RSA::PADDING_PKCS1 && $padding != RSA::PADDING_RELAXED_PKCS1) { - throw new UnsupportedAlgorithmException('ssh-agent can only create PKCS1 signatures'); - } - // the last parameter (currently 0) is for flags and ssh-agent only defines one flag (for ssh-dss): SSH_AGENT_OLD_SIGNATURE $packet = Strings::packSSH2( 'CssN', @@ -206,4 +219,26 @@ class Identity return $signature_blob; } + + /** + * Returns the private key + * + * @param string $type + * @return string + */ + public function toString($type) + { + throw new \RuntimeException('ssh-agent does not provide a mechanism to get the private key'); + } + + /** + * Sets the password + * + * @access public + * @param string|boolean $password + */ + public function withPassword($password = false) + { + throw new \RuntimeException('ssh-agent does not provide a mechanism to get the private key'); + } } diff --git a/tests/Unit/Crypt/DSA/CreateKeyTest.php b/tests/Unit/Crypt/DSA/CreateKeyTest.php index 3e65ad87..409b849a 100644 --- a/tests/Unit/Crypt/DSA/CreateKeyTest.php +++ b/tests/Unit/Crypt/DSA/CreateKeyTest.php @@ -7,6 +7,9 @@ */ use phpseclib\Crypt\DSA; +use phpseclib\Crypt\DSA\Parameters; +use phpseclib\Crypt\DSA\PublicKey; +use phpseclib\Crypt\DSA\PrivateKey; /** * @requires PHP 7.0 @@ -16,14 +19,17 @@ class Unit_Crypt_DSA_CreateKeyTest extends PhpseclibTestCase public function testCreateParameters() { $dsa = DSA::createParameters(); - $this->assertInstanceOf('\phpseclib\Crypt\DSA', $dsa); + $this->assertInstanceOf(Parameters::class, $dsa); $this->assertRegexp('#BEGIN DSA PARAMETERS#', "$dsa"); - $dsa = DSA::createParameters(100, 100); - $this->assertFalse($dsa); + try { + $dsa = DSA::createParameters(100, 100); + } catch (Exception $e) { + $this->assertInstanceOf(Exception::class, $e); + } $dsa = DSA::createParameters(512, 160); - $this->assertInstanceOf('\phpseclib\Crypt\DSA', $dsa); + $this->assertInstanceOf(Parameters::class, $dsa); $this->assertRegexp('#BEGIN DSA PARAMETERS#', "$dsa"); return $dsa; @@ -34,17 +40,17 @@ class Unit_Crypt_DSA_CreateKeyTest extends PhpseclibTestCase */ public function testCreateKey($params) { - extract(DSA::createKey()); - $this->assertInstanceOf('\phpseclib\Crypt\DSA', $privatekey); - $this->assertInstanceOf('\phpseclib\Crypt\DSA', $publickey); + $privatekey = DSA::createKey(); + $this->assertInstanceOf(PrivateKey::class, $privatekey); + $this->assertInstanceOf(PublicKey::class, $privatekey->getPublicKey()); - extract(DSA::createKey($params)); - $this->assertInstanceOf('\phpseclib\Crypt\DSA', $privatekey); - $this->assertInstanceOf('\phpseclib\Crypt\DSA', $publickey); + $privatekey = DSA::createKey($params); + $this->assertInstanceOf(PrivateKey::class, $privatekey); + $this->assertInstanceOf(PublicKey::class, $privatekey->getPublicKey()); - extract(DSA::createKey(512, 160)); - $this->assertInstanceOf('\phpseclib\Crypt\DSA', $privatekey); - $this->assertInstanceOf('\phpseclib\Crypt\DSA', $publickey); + $privatekey = DSA::createKey(512, 160); + $this->assertInstanceOf(PrivateKey::class, $privatekey); + $this->assertInstanceOf(PublicKey::class, $privatekey->getPublicKey()); } } diff --git a/tests/Unit/Crypt/DSA/LoadKeyTest.php b/tests/Unit/Crypt/DSA/LoadKeyTest.php index 996b00f5..7397317e 100644 --- a/tests/Unit/Crypt/DSA/LoadKeyTest.php +++ b/tests/Unit/Crypt/DSA/LoadKeyTest.php @@ -5,7 +5,10 @@ * @license http://www.opensource.org/licenses/mit-license.html MIT License */ -use phpseclib\Crypt\DSA; +use phpseclib\Crypt\PublicKeyLoader; +use phpseclib\Crypt\DSA\PrivateKey; +use phpseclib\Crypt\DSA\PublicKey; +use phpseclib\Crypt\DSA\Parameters; use phpseclib\Crypt\DSA\Keys\PKCS1; use phpseclib\Crypt\DSA\Keys\PKCS8; use phpseclib\Crypt\DSA\Keys\PuTTY; @@ -13,18 +16,17 @@ use phpseclib\Math\BigInteger; class Unit_Crypt_DSA_LoadKeyTest extends PhpseclibTestCase { + /** + * @expectedException \phpseclib\Exception\NoKeyLoadedException + */ public function testBadKey() { - $dsa = new DSA(); - $key = 'zzzzzzzzzzzzzz'; - - $this->assertFalse($dsa->load($key)); + PublicKeyLoader::load($key); } + public function testPuTTYKey() { - $dsa = new DSA(); - $key = 'PuTTY-User-Key-File-2: ssh-dss Encryption: none Comment: dsa-key-20161223 @@ -52,23 +54,19 @@ AAAAFFMy7BG9rPXwzqZzIY/lqsHEILNf Private-MAC: 62b92ddd8b341b9414d640c24ba6ae929a78e039 '; - $dsa->setPrivateKeyFormat('PuTTY'); - $dsa->setPublicKeyFormat('PuTTY'); + $dsa = PublicKeyLoader::load($key); - $this->assertTrue($dsa->load($key)); + $this->assertInstanceOf(PrivateKey::class, $dsa); $this->assertInternalType('string', "$dsa"); - $this->assertSame("$dsa", $dsa->getPrivateKey('PuTTY')); - $this->assertInternalType('string', $dsa->getPublicKey('PuTTY')); - $this->assertInternalType('string', $dsa->getParameters()); + $this->assertInternalType('string', $dsa->getPublicKey()->toString('PuTTY')); + $this->assertInternalType('string', $dsa->getParameters()->toString('PuTTY')); - $dsa->setPassword('password'); + $dsa = $dsa->withPassword('password'); $this->assertGreaterThan(0, strlen("$dsa")); } public function testPKCS1Key() { - $dsa = new DSA(); - $key = '-----BEGIN DSA PRIVATE KEY----- MIIDPQIBAAKCAQEAiwfUDxLuCgQSd5boP/MleHXPKllGUqXDu81onvJeL2+pSQqd NJcr2VHj+djLhJVNxUCljSwRTZFIOuJ0tPLjRl4w8Csf6zFHuUJJnYC42r2xDG7p @@ -90,20 +88,16 @@ yVFGWdP2B4Gyj85IXCm3r+JNVoV5tVX9IUBTXnUor7YfWNncwWn56Lc+RQIUUzLs Eb2s9fDOpnMhj+WqwcQgs18= -----END DSA PRIVATE KEY-----'; - $dsa->setPrivateKeyFormat('PKCS1'); - $dsa->setPublicKeyFormat('PKCS1'); + $dsa = PublicKeyLoader::load($key); - $this->assertTrue($dsa->load($key)); + $this->assertInstanceOf(PrivateKey::class, $dsa); $this->assertInternalType('string', "$dsa"); - $this->assertSame("$dsa", $dsa->getPrivateKey('PKCS1')); - $this->assertInternalType('string', $dsa->getPublicKey('PKCS1')); - $this->assertInternalType('string', $dsa->getParameters()); + $this->assertInternalType('string', $dsa->getPublicKey()->toString('PKCS1')); + $this->assertInternalType('string', (string) $dsa->getParameters()); } public function testParameters() { - $dsa = new DSA(); - $key = '-----BEGIN DSA PARAMETERS----- MIIBHgKBgQDandMycPZNOEwDXpIDSdFODWOQVO5tlnt38wK0X33TJh4wQdqOSiVF I+g+X8reP43ag3TEHu5bstrk6Znm7y1htTTvXQVTEwp6X3YHXbJG4Faul3g08Vud @@ -114,16 +108,15 @@ L1cwyXx0KMaaampd34MzOIHbC44SHY+cE3aVVUsnmt6Ur1nQaVYVszl+AO6m8bPm 4Vg= -----END DSA PARAMETERS-----'; $key = str_replace(["\n", "\r"], '', $key); + $dsa = PublicKeyLoader::load($key); - $this->assertTrue($dsa->load($key)); + $this->assertInstanceOf(Parameters::class, $dsa); $this->assertSame($key, str_replace(["\n", "\r"], '', "$dsa")); - $this->assertSame($key, str_replace(["\n", "\r"], '', $dsa->getParameters())); + $this->assertSame($key, str_replace(["\n", "\r"], '', (string) $dsa->getParameters())); } public function testPKCS8Public() { - $dsa = new DSA(); - $key = '-----BEGIN PUBLIC KEY----- MIIBtjCCASsGByqGSM44BAEwggEeAoGBANqd0zJw9k04TANekgNJ0U4NY5BU7m2W e3fzArRffdMmHjBB2o5KJUUj6D5fyt4/jdqDdMQe7luy2uTpmebvLWG1NO9dBVMT @@ -137,14 +130,14 @@ ZpmyOpXM/0opRMIRdmqVW4ardBFNokmlqngwcbaptfRnk9W2cQtx0lmKy6X/vnis 3AElwP86TYgBhw== -----END PUBLIC KEY-----'; - $this->assertTrue($dsa->load($key)); + $dsa = PublicKeyLoader::load($key); + + $this->assertInstanceOf(PublicKey::class, $dsa); $this->assertInternalType('string', "$dsa"); } public function testPKCS8Private() { - $dsa = new DSA(); - $key = '-----BEGIN PRIVATE KEY----- MIIBSgIBADCCASsGByqGSM44BAEwggEeAoGBANqd0zJw9k04TANekgNJ0U4NY5BU 7m2We3fzArRffdMmHjBB2o5KJUUj6D5fyt4/jdqDdMQe7luy2uTpmebvLWG1NO9d @@ -155,20 +148,19 @@ rgPJisERm7NDMd6J9o7qUG8NI18vVzDJfHQoxppqal3fgzM4gdsLjhIdj5wTdpVV Syea3pSvWdBpVhWzOX4A7qbxs+bhWAQWAhQiF7sFfCtZ7oOgCb2aJ9ySC9sTug== -----END PRIVATE KEY-----'; - $this->assertTrue($dsa->load($key)); + $dsa = PublicKeyLoader::load($key); + + $this->assertInstanceOf(PrivateKey::class, $dsa); $this->assertInternalType('string', "$dsa"); - $this->assertSame("$dsa", $dsa->getPrivateKey()); - $this->assertInstanceOf(DSA::class, $dsa->getPublicKey()); - $this->assertInternalType('string', $dsa->getParameters()); + $this->assertInstanceOf(PublicKey::class, $dsa->getPublicKey()); + $this->assertInstanceOf(Parameters::class, $dsa->getParameters()); } /** - * @expectedException \UnexpectedValueException + * @expectedException \phpseclib\Exception\NoKeyLoadedException */ public function testPuTTYBadMAC() { - $dsa = new DSA(); - $key = 'PuTTY-User-Key-File-2: ssh-dss Encryption: none Comment: dsa-key-20161223 @@ -196,14 +188,11 @@ AAAAFFMy7BG9rPXwzqZzIY/lqsHEILNf Private-MAC: aaaaaadd8b341b9414d640c24ba6ae929a78e039 '; - $this->assertFalse($dsa->load($key)); - $dsa->load($key, 'PuTTY'); + PublicKeyLoader::load($key); } public function testXML() { - $dsa = new DSA(); - $key = '-----BEGIN PUBLIC KEY----- MIIBtjCCASsGByqGSM44BAEwggEeAoGBANqd0zJw9k04TANekgNJ0U4NY5BU7m2W e3fzArRffdMmHjBB2o5KJUUj6D5fyt4/jdqDdMQe7luy2uTpmebvLWG1NO9dBVMT @@ -217,13 +206,12 @@ ZpmyOpXM/0opRMIRdmqVW4ardBFNokmlqngwcbaptfRnk9W2cQtx0lmKy6X/vnis 3AElwP86TYgBhw== -----END PUBLIC KEY-----'; - $dsa->load($key); - $xml = $dsa->getPublicKey('XML'); + $dsa = PublicKeyLoader::load($key); + $xml = $dsa->toString('XML'); $this->assertContains('DSAKeyValue', $xml); - $dsa = new DSA(); - $dsa->load($xml); - $pkcs8 = $dsa->getPublicKey('PKCS8'); + $dsa = PublicKeyLoader::load($xml); + $pkcs8 = $dsa->toString('PKCS8'); $this->assertSame( strtolower(preg_replace('#\s#', '', $pkcs8)), diff --git a/tests/Unit/Crypt/DSA/SignatureTest.php b/tests/Unit/Crypt/DSA/SignatureTest.php index 8e59a786..29fbbece 100644 --- a/tests/Unit/Crypt/DSA/SignatureTest.php +++ b/tests/Unit/Crypt/DSA/SignatureTest.php @@ -7,6 +7,7 @@ */ use phpseclib\Crypt\DSA; +use phpseclib\Crypt\PublicKeyLoader; class Unit_Crypt_DSA_SignatureTest extends PhpseclibTestCase { @@ -14,9 +15,7 @@ class Unit_Crypt_DSA_SignatureTest extends PhpseclibTestCase { $message = 'hello, world!'; - $dsa = new DSA(); - - $dsa->load('-----BEGIN DSA PRIVATE KEY----- + $dsa = PublicKeyLoader::load('-----BEGIN DSA PRIVATE KEY----- MIIBvAIBAAKBgQDsGAHAM16bsPlwl7jaec4QMynYa0YLiLiOZC4mvH4UW/tRJxTz aV7eH1EtnP9D9J78x/07wKYs8zJEWCXmuq0UluQfjA47+pb68b/ucQTNeZHboNN9 5oEi+8BCSK0y8G3uf3Y89qHvqa9Si6rP374MinEMrbVFm+UpsGflFcd83wIVALtJ @@ -27,10 +26,11 @@ CCBGBQJRAoGBALnHTAZlpoLJZuSBVtnMuRM3cSX43IkE9w9FveDV1jX5mmfK7yBV pQFV8eVJfk91ERQ4Dn6ePLUv2dRIt4a0S0qHqadgzyoFyqkmmUi1kNLyixtRqh+m 2gXx0t63HEpZDbEPppdpnlppZquVQh7TyrKSXW9MTzUkQjFI9UY7kZeKAhQXiJgI kBniZHdFBAZBTE14YJUBkw== ------END DSA PRIVATE KEY-----'); - $signature = $dsa->sign($message, 'ASN1'); +-----END DSA PRIVATE KEY-----') + ->withSignatureFormat('ASN1'); + $signature = $dsa->sign($message); - $dsa->load('-----BEGIN PUBLIC KEY----- + $dsa = PublicKeyLoader::load('-----BEGIN PUBLIC KEY----- MIIBuDCCASwGByqGSM44BAEwggEfAoGBAOwYAcAzXpuw+XCXuNp5zhAzKdhrRguI uI5kLia8fhRb+1EnFPNpXt4fUS2c/0P0nvzH/TvApizzMkRYJea6rRSW5B+MDjv6 lvrxv+5xBM15kdug033mgSL7wEJIrTLwbe5/djz2oe+pr1KLqs/fvgyKcQyttUWb @@ -41,37 +41,36 @@ jhGOrO+kJcZBxUSxINgIIEYFAlEDgYUAAoGBALnHTAZlpoLJZuSBVtnMuRM3cSX4 3IkE9w9FveDV1jX5mmfK7yBVpQFV8eVJfk91ERQ4Dn6ePLUv2dRIt4a0S0qHqadg zyoFyqkmmUi1kNLyixtRqh+m2gXx0t63HEpZDbEPppdpnlppZquVQh7TyrKSXW9M TzUkQjFI9UY7kZeK ------END PUBLIC KEY-----'); +-----END PUBLIC KEY-----') + ->withSignatureFormat('ASN1'); - $this->assertTrue($dsa->verify($message, $signature, 'ASN1')); - $this->assertFalse($dsa->verify('foozbar', $signature, 'ASN1')); + $this->assertTrue($dsa->verify($message, $signature)); + $this->assertFalse($dsa->verify('foozbar', $signature)); // openssl dgst -dss1 -sign dsa_priv.pem foo.txt > sigfile.bin $signature = '302c021456d7e7da10d1538a6cd45dcb2b0ce15c28bac03402147e973a4de1e92e8a87ed5218c797952a3f854df5'; $signature = pack('H*', $signature); - $dsa->setHash('sha1'); + $dsa = $dsa->withHash('sha1'); - $this->assertTrue($dsa->verify("foobar\n", $signature, 'ASN1')); - $this->assertFalse($dsa->verify('foozbar', $signature, 'ASN1')); + $this->assertTrue($dsa->verify("foobar\n", $signature)); + $this->assertFalse($dsa->verify('foozbar', $signature)); // openssl dgst -sha256 -sign dsa_priv.pem foo.txt > sigfile.bin $signature = '302e021500b131ec2682c4c0be13e6558ba3d64929ebc0ac420215009946300a03561cef50c0a51d0cd0a2c835e798fc'; $signature = pack('H*', $signature); - $dsa->setHash('sha256'); + $dsa = $dsa->withHash('sha256'); - $this->assertTrue($dsa->verify('abcdefghijklmnopqrstuvwxyz', $signature, 'ASN1')); - $this->assertFalse($dsa->verify('zzzz', $signature, 'ASN1')); + $this->assertTrue($dsa->verify('abcdefghijklmnopqrstuvwxyz', $signature)); + $this->assertFalse($dsa->verify('zzzz', $signature)); } public function testRandomSignature() { $message = 'hello, world!'; - $dsa = new DSA(); - - $dsa->load('-----BEGIN DSA PRIVATE KEY----- + $dsa = PublicKeyLoader::load('-----BEGIN DSA PRIVATE KEY----- MIIBvAIBAAKBgQDsGAHAM16bsPlwl7jaec4QMynYa0YLiLiOZC4mvH4UW/tRJxTz aV7eH1EtnP9D9J78x/07wKYs8zJEWCXmuq0UluQfjA47+pb68b/ucQTNeZHboNN9 5oEi+8BCSK0y8G3uf3Y89qHvqa9Si6rP374MinEMrbVFm+UpsGflFcd83wIVALtJ @@ -82,9 +81,11 @@ CCBGBQJRAoGBALnHTAZlpoLJZuSBVtnMuRM3cSX43IkE9w9FveDV1jX5mmfK7yBV pQFV8eVJfk91ERQ4Dn6ePLUv2dRIt4a0S0qHqadgzyoFyqkmmUi1kNLyixtRqh+m 2gXx0t63HEpZDbEPppdpnlppZquVQh7TyrKSXW9MTzUkQjFI9UY7kZeKAhQXiJgI kBniZHdFBAZBTE14YJUBkw== ------END DSA PRIVATE KEY-----'); - $signature1 = $dsa->sign($message, 'ASN1'); - $signature2 = $dsa->sign($message, 'ASN1'); +-----END DSA PRIVATE KEY-----') + ->withSignatureFormat('ASN1'); + $public = $dsa->getPublicKey(); + $signature1 = $dsa->sign($message); + $signature2 = $dsa->sign($message); // phpseclib's DSA implementation uses a CSPRNG to generate the k parameter. // used correctly this should result in different signatures every time. @@ -93,31 +94,31 @@ kBniZHdFBAZBTE14YJUBkw== // unit test would need to be updated $this->assertNotEquals($signature1, $signature2); - $this->assertTrue($dsa->verify($message, $signature1, 'ASN1')); - $this->assertTrue($dsa->verify($message, $signature2, 'ASN1')); + $this->assertTrue($public->verify($message, $signature1)); + $this->assertTrue($public->verify($message, $signature2)); - $signature = $dsa->sign($message, 'SSH2'); + $dsa = $dsa->withSignatureFormat('SSH2'); + $public = $public->withSignatureFormat('SSH2'); - $pubKey = $dsa->getPublicKey(); + $signature = $dsa->sign($message); - $dsa = new DSA(); - $dsa->load($pubKey); - $this->assertTrue($dsa->verify($message, $signature, 'SSH2')); + $this->assertTrue($public->verify($message, $signature)); } public function testSSHSignature() { - $dsa = new DSA(); - $dsa->setHash('sha1'); - $dsa->load('AAAAB3NzaC1kc3MAAACBAPyzZzm4oqmY12lxmHwNcfYDNyXr38M1lU6xy9I792U1YSKgX27nUW9eXdJ8Mrn63Le5rrBRfg2Niycx' . + $dsa = PublicKeyLoader::load('AAAAB3NzaC1kc3MAAACBAPyzZzm4oqmY12lxmHwNcfYDNyXr38M1lU6xy9I792U1YSKgX27nUW9eXdJ8Mrn63Le5rrBRfg2Niycx' . 'JF2IwDpwCi7YpIv79uwT3RtA0chQDS4vx8qi8BWBzy7PZC9hmqY62+mgfj8ooga1sr+JpMh+8r4j3KjPM+wE37khkgkvAAAAFQDn' . '19pBng6TajI/vdg7GPnxsitCqQAAAIEA6Pl1Z/TVdkc+HpfkAvcg2Q+yNtnVq7+26RCbRDO3b9Ocr+tZA9u23qnO3KDYeygzaLnI' . 'gpErp61Bj70iIUldhXy2LFGZFEC9XiKmt/tQxSDKiBbj3bS3wKfHrAlElgjhqxiRh+GixgSsmCj96eJFXcsxPjQU81HR+WJ0ALV1' . 'UnMAAACABRdNuqqe1Y68es8TIflV71P0J7Ci2BbbqAXRwYYKc9/7DrygwaN2UIbMXyOLuojeZgQPPoM9nkzd6QZo8M9apawVKKwD' . 'GAUj2of+F9WVRxhE0ohTQBzD/3HqT80pQsX+rYcxuSx1cCtdMp4oLrrfKO2J4EiWUkaoSB7SdCaj+vU='); + $dsa = $dsa + ->withHash('sha1') + ->withSignatureFormat('SSH2'); $message = pack('H*', '8bfc69a222c12ddf6bc6bf33c9cadc106af04feb'); $signature = pack('H*', '000000077373682d64737300000028a7a2e55dc43e5e6145aa94daa0552ea479d1139d6d6ba50650b489e24e976593e73f76557813d6bc'); - $this->assertTrue($dsa->verify($message, $signature, 'SSH2')); + $this->assertTrue($dsa->verify($message, $signature)); } } diff --git a/tests/Unit/Crypt/ECDSA/CurveTest.php b/tests/Unit/Crypt/ECDSA/CurveTest.php index fb252d72..b9be6a25 100644 --- a/tests/Unit/Crypt/ECDSA/CurveTest.php +++ b/tests/Unit/Crypt/ECDSA/CurveTest.php @@ -10,6 +10,7 @@ use phpseclib\Crypt\ECDSA; use phpseclib\File\ASN1; use phpseclib\Crypt\ECDSA\Curves\Ed448; use phpseclib\Math\BigInteger; +use phpseclib\Crypt\PublicKeyLoader; class Ed448PublicKey { @@ -167,7 +168,8 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $plaintext = 'zzz'; ECDSA::useInternalEngine(); - extract(ECDSA::createKey($name)); + $privatekey = ECDSA::createKey($name); + $publickey = $privatekey->getPublicKey(); $sig = $privatekey->sign($plaintext); ECDSA::useBestEngine(); @@ -189,7 +191,8 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $plaintext = 'zzz'; ECDSA::useBestEngine(); - extract(ECDSA::createKey($name)); + $privatekey = ECDSA::createKey($name); + $publickey = $privatekey->getPublicKey(); $sig = $privatekey->sign($plaintext); ECDSA::useInternalEngine(); @@ -207,11 +210,8 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $private = pack('H*', '6c82a562cb808d10d632be89c8513ebf6c929f34ddfa8c9f63c9960ef6e348a3528c8a3fcc2f044e39a3fc5b94492f8f032e7549a20098f95b'); $public = pack('H*', '5fd7449b59b461fd2ce787ec616ad46a1da1342485a70e1f8a0ea75d80e96778edf124769b46c7061bd6783df1e50f6cd1fa1abeafe8256180'); - $privateKey = new ECDSA(); - $privateKey->load($private); - - $publicKey = new ECDSA(); - $publicKey->load($public); + $privateKey = PublicKeyLoader::load($private); + $publicKey = PublicKeyLoader::load($public); $expected = '533a37f6bbe457251f023c0d88f976ae2dfb504a843e34d2074fd823d41a591f2b233f034f628281f2fd7a22ddd47d7828c59bd0a21bfd3980' . 'ff0d2028d4b18a9df63e006c5d1c2d345b925d8dc00b4104852db99ac5c7cdda8530a113a0f4dbb61149f05a7363268c71d95808ff2e652600'; @@ -221,19 +221,16 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $private = pack('H*', 'c4eab05d357007c632f3dbb48489924d552b08fe0c353a0d4a1f00acda2c463afbea67c5e8d2877c5e3bc397a659949ef8021e954e0a12274e'); $public = pack('H*', '43ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c0866aea01eb00742802b8438ea4cb82169c235160627b4c3a9480'); - $privateKey = new ECDSA(); - $privateKey->load($private); - - $publicKey = new ECDSA(); - $publicKey->load($public); + $privateKey = PublicKeyLoader::load($private); + $publicKey = PublicKeyLoader::load($public); $expected = '26b8f91727bd62897af15e41eb43c377efb9c610d48f2335cb0bd0087810f4352541b143c4b981b7e18f62de8ccdf633fc1bf037ab7cd77980' . '5e0dbcc0aae1cbcee1afb2e027df36bc04dcecbf154336c19f0af7e0a6472905e799f1953d2a0ff3348ab21aa4adafd1d234441cf807c03a00'; $this->assertSame($expected, bin2hex($sig = $privateKey->sign("\x03"))); $this->assertTrue($publicKey->verify("\x03", $sig)); - $publicKey->setContext(pack('H*', '666f6f')); - $privateKey->setContext(pack('H*', '666f6f')); + $publicKey = $publicKey->withContext(pack('H*', '666f6f')); + $privateKey = $privateKey->withContext(pack('H*', '666f6f')); $expected = 'd4f8f6131770dd46f40867d6fd5d5055de43541f8c5e35abbcd001b32a89f7d2151f7647f11d8ca2ae279fb842d607217fce6e042f6815ea00' . '0c85741de5c8da1144a6a1aba7f96de42505d7a7298524fda538fccbbb754f578c1cad10d54d0d5428407e85dcbc98a49155c13764e66c3c00'; @@ -243,11 +240,8 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $private = pack('H*', '258cdd4ada32ed9c9ff54e63756ae582fb8fab2ac721f2c8e676a72768513d939f63dddb55609133f29adf86ec9929dccb52c1c5fd2ff7e21b'); $public = pack('H*', '3ba16da0c6f2cc1f30187740756f5e798d6bc5fc015d7c63cc9510ee3fd44adc24d8e968b6e46e6f94d19b945361726bd75e149ef09817f580'); - $privateKey = new ECDSA(); - $privateKey->load($private); - - $publicKey = new ECDSA(); - $publicKey->load($public); + $privateKey = PublicKeyLoader::load($private); + $publicKey = PublicKeyLoader::load($public); $message = pack('H*', '64a65f3cdedcdd66811e2915'); @@ -259,11 +253,8 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $private = pack('H*', '7ef4e84544236752fbb56b8f31a23a10e42814f5f55ca037cdcc11c64c9a3b2949c1bb60700314611732a6c2fea98eebc0266a11a93970100e'); $public = pack('H*', 'b3da079b0aa493a5772029f0467baebee5a8112d9d3a22532361da294f7bb3815c5dc59e176b4d9f381ca0938e13c6c07b174be65dfa578e80'); - $privateKey = new ECDSA(); - $privateKey->load($private); - - $publicKey = new ECDSA(); - $publicKey->load($public); + $privateKey = PublicKeyLoader::load($private); + $publicKey = PublicKeyLoader::load($public); $message = pack('H*', '64a65f3cdedcdd66811e2915e7'); @@ -275,11 +266,8 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $private = pack('H*', 'd65df341ad13e008567688baedda8e9dcdc17dc024974ea5b4227b6530e339bff21f99e68ca6968f3cca6dfe0fb9f4fab4fa135d5542ea3f01'); $public = pack('H*', 'df9705f58edbab802c7f8363cfe5560ab1c6132c20a9f1dd163483a26f8ac53a39d6808bf4a1dfbd261b099bb03b3fb50906cb28bd8a081f00'); - $privateKey = new ECDSA(); - $privateKey->load($private); - - $publicKey = new ECDSA(); - $publicKey->load($public); + $privateKey = PublicKeyLoader::load($private); + $publicKey = PublicKeyLoader::load($public); $message = 'bd0f6a3747cd561bdddf4640a332461a4a30a12a434cd0bf40d766d9c6d458e5512204a30c17d1f50b5079631f64eb3112182da3005835461113718d1a5ef944'; $message = pack('H*', $message); @@ -292,11 +280,8 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $private = pack('H*', '2ec5fe3c17045abdb136a5e6a913e32ab75ae68b53d2fc149b77e504132d37569b7e766ba74a19bd6162343a21c8590aa9cebca9014c636df5'); $public = pack('H*', '79756f014dcfe2079f5dd9e718be4171e2ef2486a08f25186f6bff43a9936b9bfe12402b08ae65798a3d81e22e9ec80e7690862ef3d4ed3a00'); - $privateKey = new ECDSA(); - $privateKey->load($private); - - $publicKey = new ECDSA(); - $publicKey->load($public); + $privateKey = PublicKeyLoader::load($private); + $publicKey = PublicKeyLoader::load($public); $message = '15777532b0bdd0d1389f636c5f6b9ba734c90af572877e2d272dd078aa1e567cfa80e12928bb542330e8409f3174504107ecd5efac61ae7504dabe2a602ede89e5cca6257a7c77e27a702b3ae39fc769fc54f2395ae6a1178cab4738e543072fc1c177fe71e92e25bf03e4ecb72f47b64d0465aaea4c7fad372536c8ba516a60' . '39c3c2a39f0e4d832be432dfa9a706a6e5c7e19f397964ca4258002f7c0541b590316dbc5622b6b2a6fe7a4abffd96105eca76ea7b98816af0748c10df048ce012d901015a51f189f3888145c03650aa23ce894c3bd889e030d565071c59f409a9981b51878fd6fc110624dcbcde0bf7a69ccce38fabdf86f3bef6044819de11'; @@ -310,11 +295,8 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $private = pack('H*', '872d093780f5d3730df7c212664b37b8a0f24f56810daa8382cd4fa3f77634ec44dc54f1c2ed9bea86fafb7632d8be199ea165f5ad55dd9ce8'); $public = pack('H*', 'a81b2e8a70a5ac94ffdbcc9badfc3feb0801f258578bb114ad44ece1ec0e799da08effb81c5d685c0c56f64eecaef8cdf11cc38737838cf400'); - $privateKey = new ECDSA(); - $privateKey->load($private); - - $publicKey = new ECDSA(); - $publicKey->load($public); + $privateKey = PublicKeyLoader::load($private); + $publicKey = PublicKeyLoader::load($public); $message = '6ddf802e1aae4986935f7f981ba3f0351d6273c0a0c22c9c0e8339168e675412a3debfaf435ed651558007db4384b650fcc07e3b586a27a4f7a00ac8a6fec2cd86ae4bf1570c41e6a40c931db27b2faa15a8cedd52cff7362c4e6e23daec0fbc3a79b6806e316efcc7b68119bf46bc76a26067a53f296dafdbdc11c77f7777e9' . '72660cf4b6a9b369a6665f02e0cc9b6edfad136b4fabe723d2813db3136cfde9b6d044322fee2947952e031b73ab5c603349b307bdc27bc6cb8b8bbd7bd323219b8033a581b59eadebb09b3c4f3d2277d4f0343624acc817804728b25ab797172b4c5c21a22f9c7839d64300232eb66e53f31c723fa37fe387c7d3e50bdf9813' . @@ -342,12 +324,8 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $private = pack('H*', '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'); $public = pack('H*', 'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'); - $privateKey = new ECDSA(); - // libsodium format - $privateKey->load($private . $public); - - $publicKey = new ECDSA(); - $publicKey->load($public); + $privateKey = PublicKeyLoader::load($private . $public); // libsodium format + $publicKey = PublicKeyLoader::load($public); $expected = 'e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155' . '5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b'; @@ -357,11 +335,8 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $private = pack('H*', '4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb'); $public = pack('H*', '3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c'); - $privateKey = new ECDSA(); - $privateKey->load($private . $public); - - $publicKey = new ECDSA(); - $publicKey->load($public); + $privateKey = PublicKeyLoader::load($private . $public); + $publicKey = PublicKeyLoader::load($public); $expected = '92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da' . '085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00'; @@ -371,11 +346,8 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $private = pack('H*', 'c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7'); $public = pack('H*', 'fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025'); - $privateKey = new ECDSA(); - $privateKey->load($private . $public); - - $publicKey = new ECDSA(); - $publicKey->load($public); + $privateKey = PublicKeyLoader::load($private . $public); // libsodium format + $publicKey = PublicKeyLoader::load($public); $expected = '6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac' . '18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a'; @@ -385,11 +357,8 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $private = pack('H*', 'f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5'); $public = pack('H*', '278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e'); - $privateKey = new ECDSA(); - $privateKey->load($private . $public); - - $publicKey = new ECDSA(); - $publicKey->load($public); + $privateKey = PublicKeyLoader::load($private . $public); // libsodium format + $publicKey = PublicKeyLoader::load($public); $message = '08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98' . 'fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d8' . @@ -433,11 +402,8 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $private = pack('H*', '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42'); $public = pack('H*', 'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf'); - $privateKey = new ECDSA(); - $privateKey->load($private . $public); - - $publicKey = new ECDSA(); - $publicKey->load($public); + $privateKey = PublicKeyLoader::load($private . $public); + $publicKey = PublicKeyLoader::load($public); $message = 'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a' . '2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f'; @@ -451,14 +417,11 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $private = pack('H*', '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6'); $public = pack('H*', 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292'); - $privateKey = new ECDSA(); - $privateKey->load($private . $public); + $privateKey = PublicKeyLoader::load($private . $public); + $publicKey = PublicKeyLoader::load($public); - $publicKey = new ECDSA(); - $publicKey->load($public); - - $privateKey->setContext("\x62\x61\x72"); - $publicKey->setContext("\x62\x61\x72"); + $privateKey = $privateKey->withContext("\x62\x61\x72"); + $publicKey = $publicKey->withContext("\x62\x61\x72"); $message = 'f726936d19c800494e3fdaff20b276a8'; $message = pack('H*', $message); @@ -471,14 +434,11 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $private = pack('H*', '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6'); $public = pack('H*', 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292'); - $privateKey = new ECDSA(); - $privateKey->load($private . $public); + $privateKey = PublicKeyLoader::load($private . $public); + $publicKey = PublicKeyLoader::load($public); - $publicKey = new ECDSA(); - $publicKey->load($public); - - $privateKey->setContext("\x66\x6f\x6f"); - $publicKey->setContext("\x66\x6f\x6f"); + $privateKey = $privateKey->withContext("\x66\x6f\x6f"); + $publicKey = $publicKey->withContext("\x66\x6f\x6f"); $message = '508e9e6882b979fea900f62adceaca35'; $message = pack('H*', $message); @@ -491,14 +451,11 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase $private = pack('H*', 'ab9c2853ce297ddab85c993b3ae14bcad39b2c682beabc27d6d4eb20711d6560'); $public = pack('H*', '0f1d1274943b91415889152e893d80e93275a1fc0b65fd71b4b0dda10ad7d772'); - $privateKey = new ECDSA(); - $privateKey->load($private . $public); + $privateKey = PublicKeyLoader::load($private . $public); + $publicKey = PublicKeyLoader::load($public); - $publicKey = new ECDSA(); - $publicKey->load($public); - - $privateKey->setContext("\x66\x6f\x6f"); - $publicKey->setContext("\x66\x6f\x6f"); + $privateKey = $privateKey->withContext("\x66\x6f\x6f"); + $publicKey = $publicKey->withContext("\x66\x6f\x6f"); $message = 'f726936d19c800494e3fdaff20b276a8'; $message = pack('H*', $message); @@ -512,8 +469,7 @@ class Unit_Crypt_ECDSA_CurveTest extends PhpseclibTestCase public function testRandomSignature() { $message = 'hello, world!'; - $private = new ECDSA(); - $private->load('PuTTY-User-Key-File-2: ecdsa-sha2-nistp256 + $private = PublicKeyLoader::load('PuTTY-User-Key-File-2: ecdsa-sha2-nistp256 Encryption: none Comment: ecdsa-key-20181105 Public-Lines: 3 diff --git a/tests/Unit/Crypt/ECDSA/KeyTest.php b/tests/Unit/Crypt/ECDSA/KeyTest.php index 47fbbc2c..2da73b0d 100644 --- a/tests/Unit/Crypt/ECDSA/KeyTest.php +++ b/tests/Unit/Crypt/ECDSA/KeyTest.php @@ -11,28 +11,27 @@ use phpseclib\Crypt\ECDSA\Keys\PKCS8; use phpseclib\Crypt\ECDSA\Keys\PuTTY; use phpseclib\Crypt\ECDSA\Keys\OpenSSH; use phpseclib\Crypt\ECDSA\Keys\XML; +use phpseclib\Crypt\PublicKeyLoader; class Unit_Crypt_ECDSA_LoadKeyTest extends PhpseclibTestCase { // openssl ecparam -name secp256k1 -genkey -noout -out secp256k1.pem public function testPKCS1PrivateKey() { - $key = new ECDSA; - $key->load($expected = '-----BEGIN EC PRIVATE KEY----- + $key = PublicKeyLoader::load($expected = '-----BEGIN EC PRIVATE KEY----- MHQCAQEEIEzUawcXqUsQhaEQ51JLeOIY0ddzlO2nNgwDk32ETqwkoAcGBSuBBAAK oUQDQgAEFuVcVb9iCUhg2cknHPE+BouHGhQ39ORjMaMI3T4RfRxr6dj5HAXdEqVZ 1W94KMe30ndmTndcJ8BPeT1Dd15FdQ== -----END EC PRIVATE KEY-----'); $this->assertSame('secp256k1', $key->getCurve()); //PKCS1::useNamedCurve(); - $this->assertSame($expected, $key->getPrivateKey('PKCS1')); + $this->assertSame($expected, $key->toString('PKCS1')); } // openssl ecparam -name secp256k1 -genkey -noout -out secp256k1.pem -param_enc explicit public function testPKCS1PrivateKeySpecifiedCurve() { - $key = new ECDSA; - $key->load('-----BEGIN EC PRIVATE KEY----- + $key = PublicKeyLoader::load('-----BEGIN EC PRIVATE KEY----- MIIBEwIBAQQgFr6TF5meGfgCXDqVxoSEltGI+T94G42PPbA6/ibq+ouggaUwgaIC AQEwLAYHKoZIzj0BAQIhAP////////////////////////////////////7///wv MAYEAQAEAQcEQQR5vmZ++dy7rFWgYpXOhwsHApv82y3OKNlZ8oFbFvgXmEg62ncm @@ -61,29 +60,27 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAABwRBBHm+Zn753LusVaBilc6HCwcCm/zbLc4o E5w= -----END EC PRIVATE KEY-----'; PKCS1::useSpecifiedCurve(); - $this->assertSame($expected, $key->getPrivateKey('PKCS1')); + $this->assertSame($expected, $key->toString('PKCS1')); } // openssl ecparam -name secp256k1 -genkey -noout -out secp256k1.pem // openssl pkcs8 -topk8 -nocrypt -in secp256k1.pem -out secp256k1-2.pem public function testPKCS8PrivateKey() { - $key = new ECDSA; - $key->load($expected = '-----BEGIN PRIVATE KEY----- + $key = PublicKeyLoader::load($expected = '-----BEGIN PRIVATE KEY----- MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgAYCXwnhqMT6fCIKIkQ0w cac7QqHrn4TCQMF9a+im74WhRANCAATwCjyGuP8xQbvVjznqazL36oeAnD32I+X2 +wscW3OmyTDpk41HaWYPh+j+BoufsSkCwf8dBRGEQbCieZbbZogy -----END PRIVATE KEY-----'); $this->assertSame('secp256k1', $key->getCurve()); - $this->assertSame($expected, $key->getPrivateKey('PKCS8')); + $this->assertSame($expected, $key->toString('PKCS8')); } // openssl ecparam -name secp256k1 -genkey -noout -out secp256k1.pem -param_enc explicit // openssl pkcs8 -topk8 -nocrypt -in secp256k1.pem -out secp256k1-2.pem public function testPKCS8PrivateKeySpecifiedCurve() { - $key = new ECDSA; - $key->load('-----BEGIN PRIVATE KEY----- + $key = PublicKeyLoader::load('-----BEGIN PRIVATE KEY----- MIIBIwIBADCBrgYHKoZIzj0CATCBogIBATAsBgcqhkjOPQEBAiEA//////////// /////////////////////////v///C8wBgQBAAQBBwRBBHm+Zn753LusVaBilc6H CwcCm/zbLc4o2VnygVsW+BeYSDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj/sQ @@ -107,28 +104,26 @@ AASdfrr5QLNRbdP9+QsYgh9mMmblsgzABXzkukOibaEjjjUlHH79bhaq0a5b4H8s AFLpken6rN6lOEIeyNLdD097 -----END PRIVATE KEY-----'; PKCS8::useSpecifiedCurve(); - $this->assertSame($expected, $key->getPrivateKey('PKCS8')); + $this->assertSame($expected, $key->toString('PKCS8')); } // openssl ecparam -name sect113r1 -genkey -noout -out sect113r1.pem public function testBinaryPKCS1PrivateKey() { - $key = new ECDSA; - $key->load($expected = '-----BEGIN EC PRIVATE KEY----- + $key = PublicKeyLoader::load($expected = '-----BEGIN EC PRIVATE KEY----- MEECAQEEDwBZdP4eSzKk/uQa6jdtfKAHBgUrgQQABKEiAyAABAHqCoNb++mK5qvE c4rCzQEuI19czqvXpEPcAWSXew== -----END EC PRIVATE KEY-----'); $this->assertSame('sect113r1', $key->getCurve()); PKCS1::useNamedCurve(); - $this->assertSame($expected, $key->getPrivateKey('PKCS1')); + $this->assertSame($expected, $key->toString('PKCS1')); } // openssl ecparam -name sect113r1 -genkey -noout -out sect113r1.pem -param_enc explicit public function testBinaryPKCS1PrivateKeySpecifiedCurve() { - $key = new ECDSA; - $key->load('-----BEGIN EC PRIVATE KEY----- + $key = PublicKeyLoader::load('-----BEGIN EC PRIVATE KEY----- MIHNAgEBBA8AuSc4BeeyYTq9rbSDuL2ggZIwgY8CAQEwHAYHKoZIzj0BAjARAgFx BgkqhkjOPQECAwICAQkwNwQOMIglDKbnx/5knOhYIPcEDui+5NPiJgdEGIvg6ccj AxUAEOcjqxTWluZ2h1YVF1b+v4/LSakEHwQAnXNhbzX0qxQH1zViwQ8ApSgwJ3lY @@ -149,7 +144,7 @@ BACdc2FvNfSrFAfXNWLBDwClKDAneVjuhNExXtMYhgIPAQAAAAAAAADZzOyKOeVv oSIDIAAEAULtznTLu7D6K4d4wK1bAKko0FRxV6IeZ7rT0O/+ -----END EC PRIVATE KEY-----'; PKCS1::useSpecifiedCurve(); - $this->assertSame($expected, $key->getPrivateKey('PKCS1')); + $this->assertSame($expected, $key->toString('PKCS1')); } // openssl ecparam -name sect113r1 -genkey -noout -out sect113r1.pem @@ -157,23 +152,21 @@ oSIDIAAEAULtznTLu7D6K4d4wK1bAKko0FRxV6IeZ7rT0O/+ // sect113r1's reduction polynomial is a trinomial public function testBinaryPKCS8PrivateKey() { - $key = new ECDSA; - $key->load($expected = '-----BEGIN PRIVATE KEY----- + $key = PublicKeyLoader::load($expected = '-----BEGIN PRIVATE KEY----- MFECAQAwEAYHKoZIzj0CAQYFK4EEAAQEOjA4AgEBBA8A5OuqAY8HYoFOaz9mE6mh IgMgAAQASF3rOTPXvH0QdRBvsrMBdLMf27yd8AWABrZTxvI= -----END PRIVATE KEY-----'); $this->assertSame('sect113r1', $key->getCurve()); PKCS8::useNamedCurve(); - $this->assertSame($expected, $key->getPrivateKey('PKCS8')); + $this->assertSame($expected, $key->toString('PKCS8')); } // openssl ecparam -name sect113r1 -genkey -noout -out sect113r1.pem -param_enc explicit // openssl pkcs8 -topk8 -nocrypt -in sect113r1.pem -out sect113r1-2.pem public function testBinaryPKCS8PrivateKeySpecifiedCurve() { - $key = new ECDSA; - $key->load('-----BEGIN PRIVATE KEY----- + $key = PublicKeyLoader::load('-----BEGIN PRIVATE KEY----- MIHdAgEAMIGbBgcqhkjOPQIBMIGPAgEBMBwGByqGSM49AQIwEQIBcQYJKoZIzj0B AgMCAgEJMDcEDjCIJQym58f+ZJzoWCD3BA7ovuTT4iYHRBiL4OnHIwMVABDnI6sU 1pbmdodWFRdW/r+Py0mpBB8EAJ1zYW819KsUB9c1YsEPAKUoMCd5WO6E0TFe0xiG @@ -193,15 +186,14 @@ BA8AXtfDMRsRTx8snPbWHquhIgMgAAQA9xdWGJ6vV23+vkdq0C8BLJVg5E3amMyf /5keGa4= -----END PRIVATE KEY-----'; PKCS8::useSpecifiedCurve(); - $this->assertSame($expected, $key->getPrivateKey('PKCS8')); + $this->assertSame($expected, $key->toString('PKCS8')); } // openssl ecparam -name sect131r1 -genkey -noout -out sect131r1.pem -param_enc explicit // sect131r1's reduction polynomial is a pentanomial public function testBinaryPentanomialPKCS1PrivateKey() { - $key = new ECDSA; - $key->load('-----BEGIN EC PRIVATE KEY----- + $key = PublicKeyLoader::load('-----BEGIN EC PRIVATE KEY----- MIHoAgEBBBECPEK9NCISWf2riBsORoTM+6CBpzCBpAIBATAlBgcqhkjOPQECMBoC AgCDBgkqhkjOPQECAwMwCQIBAgIBAwIBCDA9BBEHoRsJp2tWIURBj/P/jCVwuAQR AhfAVhCIS2O5xscpFnj500EDFQBNaW5naHVhUXWYW9OtutohtDqX4gQjBACBuvkf @@ -221,14 +213,13 @@ SxtO+eFQAhEEAAAAAAAAAAIxI5U6lGS1TaEmAyQABARCKJRo6OZZ7GKjWoKmDzmh BjoJZJZQztmlj7Qep/sf1l8= -----END EC PRIVATE KEY-----'; PKCS1::useSpecifiedCurve(); - $this->assertSame($expected, $key->getPrivateKey('PKCS1')); + $this->assertSame($expected, $key->toString('PKCS1')); } // from https://tools.ietf.org/html/draft-ietf-curdle-pkix-07#section-10.1 public function testEd25519PublicKey() { - $key = new ECDSA; - $key->load('-----BEGIN PUBLIC KEY----- + $key = PublicKeyLoader::load('-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= -----END PUBLIC KEY-----'); $this->assertSame('Ed25519', $key->getCurve()); @@ -240,23 +231,21 @@ MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= $expected = '-----BEGIN PUBLIC KEY----- MCwwBwYDK2VwBQADIQAZv0QJaYTN/oVBusFn3DuWyFCGqjC2tssMXDitcDFm4Q== -----END PUBLIC KEY-----'; - $this->assertSame($expected, $key->getPublicKey('PKCS8')); + $this->assertSame($expected, $key->toString('PKCS8')); } // from https://tools.ietf.org/html/draft-ietf-curdle-pkix-07#section-10.3 public function testEd25519PrivateKey() { // without public key (public key should be derived) - $key = new ECDSA; - $key->load('-----BEGIN PRIVATE KEY----- + $key = PublicKeyLoader::load('-----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC -----END PRIVATE KEY-----'); $this->assertSame('Ed25519', $key->getCurve()); $this->assertSame('Ed25519', $key->getPublicKey()->getCurve()); // with public key - $key = new ECDSA; - $key->load('-----BEGIN PRIVATE KEY----- + $key = PublicKeyLoader::load('-----BEGIN PRIVATE KEY----- MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB Z9w7lshQhqowtrbLDFw4rXAxZuE= @@ -271,13 +260,12 @@ Z9w7lshQhqowtrbLDFw4rXAxZuE= MFICAQEwBwYDK2VwBQAEIgQg1O5y2/kTWErVttjx92n4rTr+fCjL8dT74Jeoj0R1 WEKBIBm/RAlphM3+hUG6wWfcO5bIUIaqMLa2ywxcOK1wMWbh -----END PRIVATE KEY-----'; - $this->assertSame($expected, $key->getPrivateKey('PKCS8')); + $this->assertSame($expected, $key->toString('PKCS8')); } public function testPuTTYnistp256() { - $key = new ECDSA; - $key->load($expected = 'PuTTY-User-Key-File-2: ecdsa-sha2-nistp256 + $key = PublicKeyLoader::load($expected = 'PuTTY-User-Key-File-2: ecdsa-sha2-nistp256 Encryption: none Comment: ecdsa-key-20181105 Public-Lines: 3 @@ -291,20 +279,18 @@ Private-MAC: b85ca0eb7c612df5d18af85128821bd53faaa3ef $this->assertSame('nistp256', $key->getCurve()); PuTTY::setComment('ecdsa-key-20181105'); - $this->assertSame($expected, $key->getPrivateKey('PuTTY')); + $this->assertSame($expected, $key->toString('PuTTY')); - $key = new ECDSA; - $key->load($expected = 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJEXCsWA8s18m25MJlVE1urbXPYFi4q8oMbb2H0kE2f5WPxizsKXRmb1J68paXQizryL9fC4FTqICJ1+UnaPfk0= ecdsa-key-20181105'); + $key = PublicKeyLoader::load($expected = 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJEXCsWA8s18m25MJlVE1urbXPYFi4q8oMbb2H0kE2f5WPxizsKXRmb1J68paXQizryL9fC4FTqICJ1+UnaPfk0= ecdsa-key-20181105'); $this->assertSame('nistp256', $key->getCurve()); OpenSSH::setComment('ecdsa-key-20181105'); - $this->assertSame($expected, $key->getPublicKey('OpenSSH')); + $this->assertSame($expected, $key->toString('OpenSSH')); } public function testPuTTYnistp384() { - $key = new ECDSA; - $key->load($expected = 'PuTTY-User-Key-File-2: ecdsa-sha2-nistp384 + $key = PublicKeyLoader::load($expected = 'PuTTY-User-Key-File-2: ecdsa-sha2-nistp384 Encryption: none Comment: ecdsa-key-20181105 Public-Lines: 3 @@ -319,21 +305,19 @@ Private-MAC: 97a990a3d5f6b8f268d4be9c4ab9ebfd8fa79849 $this->assertSame('nistp384', $key->getCurve()); PuTTY::setComment('ecdsa-key-20181105'); - $this->assertSame($expected, $key->getPrivateKey('PuTTY')); + $this->assertSame($expected, $key->toString('PuTTY')); - $key = new ECDSA; - $key->load($expected = 'ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBOI53wHG3CdcAJZq5PXWZAEAxxsNVFQlQgOX9toWEOgqQF5LbK2nWLKRvaHMzocUXaTYZDccSS0ATZFPT3j1Er1LU9cu4PHpyS07v262jdzkxIvKCPcAeISuV80MC7rHog== ecdsa-key-20181105'); + $key = PublicKeyLoader::load($expected = 'ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBOI53wHG3CdcAJZq5PXWZAEAxxsNVFQlQgOX9toWEOgqQF5LbK2nWLKRvaHMzocUXaTYZDccSS0ATZFPT3j1Er1LU9cu4PHpyS07v262jdzkxIvKCPcAeISuV80MC7rHog== ecdsa-key-20181105'); $this->assertSame('nistp384', $key->getCurve()); OpenSSH::setComment('ecdsa-key-20181105'); - $this->assertSame($expected, $key->getPublicKey('OpenSSH')); + $this->assertSame($expected, $key->toString('OpenSSH')); } public function testPuTTYnistp521() { - $key = new ECDSA; - $key->load($expected = 'PuTTY-User-Key-File-2: ecdsa-sha2-nistp521 + $key = PublicKeyLoader::load($expected = 'PuTTY-User-Key-File-2: ecdsa-sha2-nistp521 Encryption: none Comment: ecdsa-key-20181105 Public-Lines: 4 @@ -349,20 +333,18 @@ Private-MAC: 6d49ce289b85549a43d74422dd8bb3ba8798c72c $this->assertSame('nistp521', $key->getCurve()); PuTTY::setComment('ecdsa-key-20181105'); - $this->assertSame($expected, $key->getPrivateKey('PuTTY')); + $this->assertSame($expected, $key->toString('PuTTY')); - $key = new ECDSA; - $key->load($expected = 'ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAF1Eg0MjaJwooFj6HCNh4RWbvmQRY+sdczJyBdT3EaTc/6IUcCfW7w7rAeRp2CDdE9RlAVD8IuLqW7DJH06Xeov8wBO5G6jUqXu0rlHsOSiC6VcCxBJuWVNB1IorHnS7PX0f6HdLlIEme73P77drqpn5YY0XLtP6hFrF7H5XfCxpNyaJA== ecdsa-key-20181105'); + $key = PublicKeyLoader::load($expected = 'ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAF1Eg0MjaJwooFj6HCNh4RWbvmQRY+sdczJyBdT3EaTc/6IUcCfW7w7rAeRp2CDdE9RlAVD8IuLqW7DJH06Xeov8wBO5G6jUqXu0rlHsOSiC6VcCxBJuWVNB1IorHnS7PX0f6HdLlIEme73P77drqpn5YY0XLtP6hFrF7H5XfCxpNyaJA== ecdsa-key-20181105'); $this->assertSame('nistp521', $key->getCurve()); OpenSSH::setComment('ecdsa-key-20181105'); - $this->assertSame($expected, $key->getPublicKey('OpenSSH')); + $this->assertSame($expected, $key->toString('OpenSSH')); } public function testPuTTYed25519() { - $key = new ECDSA; - $key->load($expected = 'PuTTY-User-Key-File-2: ssh-ed25519 + $key = PublicKeyLoader::load($expected = 'PuTTY-User-Key-File-2: ssh-ed25519 Encryption: none Comment: ed25519-key-20181105 Public-Lines: 2 @@ -375,14 +357,13 @@ Private-MAC: 8a06821a1c8b8b40fc40f876e543c4ea3fb81bb9 $this->assertSame('Ed25519', $key->getCurve()); PuTTY::setComment('ed25519-key-20181105'); - $this->assertSame($expected, $key->getPrivateKey('PuTTY')); + $this->assertSame($expected, $key->toString('PuTTY')); - $key = new ECDSA; - $key->load($expected = 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC6I6RyYAqtBcWXws9EDqGbhFtc5rKG4NMn/G7temQtu ed25519-key-20181105'); + $key = PublicKeyLoader::load($expected = 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC6I6RyYAqtBcWXws9EDqGbhFtc5rKG4NMn/G7temQtu ed25519-key-20181105'); $this->assertSame('Ed25519', $key->getCurve()); OpenSSH::setComment('ed25519-key-20181105'); - $this->assertSame($expected, $key->getPublicKey('OpenSSH')); + $this->assertSame($expected, $key->toString('OpenSSH')); } public function testlibsodium() @@ -393,22 +374,19 @@ Private-MAC: 8a06821a1c8b8b40fc40f876e543c4ea3fb81bb9 $kp = sodium_crypto_sign_keypair(); - $key = new ECDSA; - $key->load($expected = sodium_crypto_sign_secretkey($kp)); + $key = PublicKeyLoader::load($expected = sodium_crypto_sign_secretkey($kp)); $this->assertSame('Ed25519', $key->getCurve()); - $this->assertSame($expected, $key->getPrivateKey('libsodium')); + $this->assertSame($expected, $key->toString('libsodium')); - $key = new ECDSA; - $key->load($expected = sodium_crypto_sign_publickey($kp)); + $key = PublicKeyLoader::load($expected = sodium_crypto_sign_publickey($kp)); $this->assertSame('Ed25519', $key->getCurve()); - $this->assertSame($expected, $key->getPublicKey('libsodium')); + $this->assertSame($expected, $key->toString('libsodium')); } // ssh-keygen -t ed25519 public function testOpenSSHPrivateKey() { - $key = new ECDSA; - $key->load('-----BEGIN OPENSSH PRIVATE KEY----- + $key = PublicKeyLoader::load('-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACCpm7dS1/WDTW+uuhp2+aFLPKaJle6+oJqDGLXhlQAX4AAAAJg8TmN5PE5j eQAAAAtzc2gtZWQyNTUxOQAAACCpm7dS1/WDTW+uuhp2+aFLPKaJle6+oJqDGLXhlQAX4A @@ -424,16 +402,14 @@ pomV7r6gmoMYteGVABfgAAAAD3ZhZ3JhbnRAdmFncmFudAECAwQFBg== // support encrypted keys // none-the-less, because of the randomized component we can't easily // see if the key string is equal to another known string - $key2 = new ECDSA; - $key2->load($key->getPrivateKey('OpenSSH')); + $key2 = PublicKeyLoader::load($key->toString('OpenSSH')); $this->assertSame('Ed25519', $key2->getCurve()); } // from https://www.w3.org/TR/xmldsig-core/#sec-RFC4050Compat public function testXMLKey() { - $key = new ECDSA; - $key->load($orig = ' + $key = PublicKeyLoader::load($orig = ' @@ -453,22 +429,12 @@ pomV7r6gmoMYteGVABfgAAAAD3ZhZ3JhbnRAdmFncmFudAECAwQFBg== //$dom = new DOMDocument(); //$dom->preserveWhiteSpace = false; - $dom->loadXML($key->getPublicKey('XML')); + $dom->loadXML($key->toString('XML')); $actual = $dom->C14N(); $this->assertSame($expected, $actual); } - public function testToPublicKey() - { - $key = new ECDSA; - $key->load('-----BEGIN PRIVATE KEY----- -MFICAQEwBwYDK2VwBQAEIgQgS5tTLrcNRaml4g5CgGeMvptuXuSrcrFbl+zVSxHD -H76BIDXmiVv2hLjr5MhZENlKIuz0ak1hUO8MdZ2vgY/nGcUV ------END PRIVATE KEY-----'); - $this->assertInternalType('string', (string) $key->getPublicKey()); - } - public static function assertSame($expected, $actual, $message = '') { $expected = str_replace("\r\n", "\n", $expected); diff --git a/tests/Unit/Crypt/RSA/CreateKeyTest.php b/tests/Unit/Crypt/RSA/CreateKeyTest.php index 761ed4e5..c9e8009c 100644 --- a/tests/Unit/Crypt/RSA/CreateKeyTest.php +++ b/tests/Unit/Crypt/RSA/CreateKeyTest.php @@ -8,14 +8,17 @@ use phpseclib\Crypt\RSA; use phpseclib\Crypt\RSA\Keys\PKCS1; +use phpseclib\Crypt\RSA\PrivateKey; +use phpseclib\Crypt\RSA\PublicKey; class Unit_Crypt_RSA_CreateKeyTest extends PhpseclibTestCase { public function testCreateKey() { - extract(RSA::createKey(768)); - $this->assertInstanceOf('\phpseclib\Crypt\RSA', $privatekey); - $this->assertInstanceOf('\phpseclib\Crypt\RSA', $publickey); + $privatekey = RSA::createKey(768); + $publickey = $privatekey->getPublicKey(); + $this->assertInstanceOf(PrivateKey::class, $privatekey); + $this->assertInstanceOf(PublicKey::class, $publickey); $this->assertNotEmpty("$privatekey"); $this->assertNotEmpty("$publickey"); $this->assertSame($privatekey->getLength(), 768); @@ -40,15 +43,15 @@ class Unit_Crypt_RSA_CreateKeyTest extends PhpseclibTestCase { RSA::useInternalEngine(); RSA::setSmallestPrime(256); - extract(RSA::createKey(1024)); - $this->assertInstanceOf('\phpseclib\Crypt\RSA', $privatekey); - $this->assertInstanceOf('\phpseclib\Crypt\RSA', $publickey); - $privatekey->setPrivateKeyFormat('PKCS1'); - $this->assertNotEmpty("$privatekey"); - $this->assertNotEmpty("$publickey"); + $privatekey = RSA::createKey(1024); + $publickey = $privatekey->getPublicKey(); + $this->assertInstanceOf(PrivateKey::class, $privatekey); + $this->assertInstanceOf(PublicKey::class, $publickey); + $this->assertNotEmpty($privatekey->toString('PKCS1')); + $this->assertNotEmpty($publickey->toString('PKCS1')); $this->assertSame($privatekey->getLength(), 1024); $this->assertSame($publickey->getLength(), 1024); - $r = PKCS1::load("$privatekey"); + $r = PKCS1::load($privatekey->toString('PKCS1')); $this->assertCount(4, $r['primes']); // the last prime number could be slightly over. eg. 99 * 99 == 9801 but 10 * 10 = 100. the more numbers you're // multiplying the less certain you are to have each of them multiply to an n-bit number @@ -56,10 +59,9 @@ class Unit_Crypt_RSA_CreateKeyTest extends PhpseclibTestCase $this->assertSame($prime->getLength(), 256); } - $rsa = new RSA(); - $rsa->load($privatekey->getPrivateKey()); + $rsa = RSA::load($privatekey->toString('PKCS1')); $signature = $rsa->sign('zzz'); - $rsa->load($rsa->getPublicKey()); + $rsa = RSA::load($rsa->getPublicKey()->toString('PKCS1')); $this->assertTrue($rsa->verify('zzz', $signature)); RSA::useBestEngine(); diff --git a/tests/Unit/Crypt/RSA/LoadKeyTest.php b/tests/Unit/Crypt/RSA/LoadKeyTest.php index 200145c7..1348506a 100644 --- a/tests/Unit/Crypt/RSA/LoadKeyTest.php +++ b/tests/Unit/Crypt/RSA/LoadKeyTest.php @@ -6,6 +6,9 @@ */ use phpseclib\Crypt\RSA; +use phpseclib\Crypt\PublicKeyLoader; +use phpseclib\Crypt\RSA\PrivateKey; +use phpseclib\Crypt\RSA\PublicKey; use phpseclib\Crypt\RSA\Keys\PKCS1; use phpseclib\Crypt\RSA\Keys\PKCS8; use phpseclib\Crypt\RSA\Keys\PuTTY; @@ -20,19 +23,17 @@ class Unit_Crypt_RSA_LoadKeyTest extends PhpseclibTestCase OpenSSH::setComment('phpseclib-generated-key'); } + /** + * @expectedException \phpseclib\Exception\NoKeyLoadedException + */ public function testBadKey() { - $rsa = new RSA(); - $key = 'zzzzzzzzzzzzzz'; - - $this->assertFalse($rsa->load($key)); + PublicKeyLoader::load($key); } public function testPKCS1Key() { - $rsa = new RSA(); - $key = '-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5 @@ -47,14 +48,14 @@ U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ 37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0= -----END RSA PRIVATE KEY-----'; - $this->assertTrue($rsa->load($key)); - $this->assertInternalType('string', $rsa->getPrivateKey()); + $rsa = PublicKeyLoader::load($key); + + $this->assertInstanceOf(PrivateKey::class, $rsa); + $this->assertInternalType('string', "$rsa"); } public function testPKCS1SpacesKey() { - $rsa = new RSA(); - $key = '-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5 @@ -70,14 +71,14 @@ U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ -----END RSA PRIVATE KEY-----'; $key = str_replace(["\r", "\n", "\r\n"], ' ', $key); - $this->assertTrue($rsa->load($key)); - $this->assertInternalType('string', $rsa->getPrivateKey()); + $rsa = PublicKeyLoader::load($key); + + $this->assertInstanceOf(PrivateKey::class, $rsa); + $this->assertInternalType('string', "$rsa"); } public function testPKCS1NoHeaderKey() { - $rsa = new RSA(); - $key = 'MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5 1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh @@ -90,14 +91,14 @@ X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ 37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0='; - $this->assertTrue($rsa->load($key)); - $this->assertInternalType('string', $rsa->getPrivateKey()); + $rsa = PublicKeyLoader::load($key); + + $this->assertInstanceOf(PrivateKey::class, $rsa); + $this->assertInternalType('string', "$rsa"); } public function testPKCS1NoWhitespaceNoHeaderKey() { - $rsa = new RSA(); - $key = 'MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp' . 'wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5' . '1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh' . @@ -110,14 +111,14 @@ U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ 'U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ' . '37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0='; - $this->assertTrue($rsa->load($key)); - $this->assertInternalType('string', $rsa->getPrivateKey()); + $rsa = PublicKeyLoader::load($key); + + $this->assertInstanceOf(PrivateKey::class, $rsa); + $this->assertInternalType('string', "$rsa"); } public function testRawPKCS1Key() { - $rsa = new RSA(); - $key = 'MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp' . 'wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5' . '1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh' . @@ -131,15 +132,14 @@ U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ '37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0='; $key = base64_decode($key); - $this->assertTrue($rsa->load($key)); - $this->assertInternalType('string', $rsa->getPrivateKey()); + $rsa = PublicKeyLoader::load($key); + + $this->assertInstanceOf(PrivateKey::class, $rsa); + $this->assertInternalType('string', "$rsa"); } public function testLoadPKCS8PrivateKey() { - $rsa = new RSA(); - $rsa->setPassword('password'); - $key = '-----BEGIN ENCRYPTED PRIVATE KEY----- MIIE6TAbBgkqhkiG9w0BBQMwDgQIcWWgZeQYPTcCAggABIIEyLoa5b3ktcPmy4VB hHkpHzVSEsKJPmQTUaQvUwIp6+hYZeuOk78EPehrYJ/QezwJRdyBoD51oOxqWCE2 @@ -170,14 +170,14 @@ GF/qoZyC1mbqdtyyeWgHtVbJVUORmpbNnXOII9duEqBUNDiO9VSZNn/8h/VsYeAB xryZaRDVmtMuf/OZBQ== -----END ENCRYPTED PRIVATE KEY-----'; - $this->assertTrue($rsa->load($key)); - $this->assertInternalType('string', $rsa->getPrivateKey()); + $rsa = PublicKeyLoader::load($key, 'password'); + + $this->assertInstanceOf(PrivateKey::class, $rsa); + $this->assertInternalType('string', "$rsa"); } public function testSavePKCS8PrivateKey() { - $rsa = new RSA(); - $key = '-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5 @@ -191,20 +191,19 @@ X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ 37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0= -----END RSA PRIVATE KEY-----'; - $rsa->setPassword('password'); - $this->assertTrue($rsa->load($key)); + $rsa = PublicKeyLoader::load($key, 'password'); - $key = $rsa->getPrivateKey('PKCS8'); - $this->assertInternalType('string', $key); + $this->assertInstanceOf(PrivateKey::class, $rsa); - $this->assertTrue($rsa->load($key)); + $key = (string) $rsa->withPassword('password'); + $rsa = PublicKeyLoader::load($key, 'password'); + + $this->assertInstanceOf(PrivateKey::class, $rsa); } public function testPubKey1() { - $rsa = new RSA(); - $key = '-----BEGIN RSA PUBLIC KEY----- MIIBCgKCAQEA61BjmfXGEvWmegnBGSuS+rU9soUg2FnODva32D1AqhwdziwHINFa D1MVlcrYG6XRKfkcxnaXGfFDWHLEvNBSEVCgJjtHAGZIm5GL/KA86KDp/CwDFMSw @@ -214,15 +213,12 @@ gPiUWOPatVkt7+Bs3h5Ramxh7XjBOXeulmCpGSynXNcpZ/06+vofGi/2MlpQZNhH Ao8eayMp6FcvNucIpUndo1X8dKMv3Y26ZQIDAQAB -----END RSA PUBLIC KEY-----'; - $this->assertTrue($rsa->load($key)); - $this->assertInstanceOf(RSA::class, $rsa->getPublicKey()); - $this->assertFalse($rsa->getPrivateKey()); + $rsa = PublicKeyLoader::load($key); + $this->assertInstanceOf(PublicKey::class, $rsa); } public function testPubKey2() { - $rsa = new RSA(); - $key = '-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA61BjmfXGEvWmegnBGSuS +rU9soUg2FnODva32D1AqhwdziwHINFaD1MVlcrYG6XRKfkcxnaXGfFDWHLEvNBS @@ -233,29 +229,23 @@ lmCpGSynXNcpZ/06+vofGi/2MlpQZNhHAo8eayMp6FcvNucIpUndo1X8dKMv3Y26 ZQIDAQAB -----END PUBLIC KEY-----'; - $this->assertTrue($rsa->load($key)); - $this->assertInstanceOf(RSA::class, $rsa->getPublicKey()); - $this->assertFalse($rsa->getPrivateKey()); + $rsa = PublicKeyLoader::load($key); + $this->assertInstanceOf(PublicKey::class, $rsa); } public function testSSHPubKey() { - $rsa = new RSA(); - $key = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4e' . 'CZ0FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMS' . 'GkVb1/3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZw== ' . 'phpseclib-generated-key'; - $this->assertTrue($rsa->load($key)); - $this->assertInstanceOf(RSA::class, $rsa->getPublicKey()); - $this->assertFalse($rsa->getPrivateKey()); + $rsa = PublicKeyLoader::load($key); + $this->assertInstanceOf(PublicKey::class, $rsa); } public function testSSHPubKeyFingerprint() { - $rsa = new RSA(); - $key = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD9K+ebJRMN10kGanhi6kDz6EYFqZttZWZh0'. 'YoEbIbbere9N2Yvfc7oIoCTHYowhXND9WSJaIs1E4bx0085CZnofWaqf4NbZTzAh18iZup08ec'. 'COB5gJVS1efpgVSviDF2L7jxMsBVoOBfqsmA8m0RwDDVezyWvw4y+STSuVzu2jI8EfwN7ZFGC6'. @@ -263,15 +253,14 @@ ZQIDAQAB 'b6wYtY/q/WtUFr3nK+x0lgOtokhnJfRR/6fnmC1CztPnIT4BWK81VGKWONAxuhMyQ5XChyu6S9'. 'mWG5tUlUI/5'; - $this->assertTrue($rsa->load($key)); - $this->assertSame($rsa->getPublicKeyFingerprint('md5'), 'bd:2c:2f:31:b9:ef:b8:f8:ad:fc:40:a6:94:4f:28:82'); - $this->assertSame($rsa->getPublicKeyFingerprint('sha256'), 'N9sV2uSNZEe8TITODku0pRI27l+Zk0IY0TrRTw3ozwM'); + $rsa = PublicKeyLoader::load($key, 'password'); + $this->assertInstanceOf(PublicKey::class, $rsa); + $this->assertSame($rsa->getFingerprint('md5'), 'bd:2c:2f:31:b9:ef:b8:f8:ad:fc:40:a6:94:4f:28:82'); + $this->assertSame($rsa->getFingerprint('sha256'), 'N9sV2uSNZEe8TITODku0pRI27l+Zk0IY0TrRTw3ozwM'); } public function testSetPrivate() { - $rsa = new RSA(); - $key = '-----BEGIN RSA PUBLIC KEY----- MIIBCgKCAQEA61BjmfXGEvWmegnBGSuS+rU9soUg2FnODva32D1AqhwdziwHINFa D1MVlcrYG6XRKfkcxnaXGfFDWHLEvNBSEVCgJjtHAGZIm5GL/KA86KDp/CwDFMSw @@ -281,29 +270,28 @@ gPiUWOPatVkt7+Bs3h5Ramxh7XjBOXeulmCpGSynXNcpZ/06+vofGi/2MlpQZNhH Ao8eayMp6FcvNucIpUndo1X8dKMv3Y26ZQIDAQAB -----END RSA PUBLIC KEY-----'; - $this->assertTrue($rsa->load($key)); - $this->assertTrue($rsa->setPrivateKey()); + $rsa = PublicKeyLoader::load($key); + $this->assertInstanceOf(PublicKey::class, $rsa); + $rsa = $rsa->asPrivateKey(); + $this->assertInstanceOf(PrivateKey::class, $rsa); $this->assertGreaterThanOrEqual(1, strlen("$rsa")); - $this->assertFalse($rsa->getPublicKey()); } /** * make phpseclib generated XML keys be unsigned. this may need to be reverted * if it is later learned that XML keys are, in fact, supposed to be signed + * * @group github468 */ public function testUnsignedXML() { - $rsa = new RSA(); - $key = ' v5OxcEgxPUfa701NpxnScCmlRkbwSGBiTWobHkIWZEB+AlRTHaVoZg/D8l6YzR7VdQidG6gF+nuUMjY75dBXgY/XcyVq0Hccf1jTfgARuNuq4GGG3hnCJVi2QsOgcf9R7TeXn+p1RKIhjQoWCiEQeEBTotNbJhcabNcPGSEJw+s= AQAB '; - $rsa->load($key); - $rsa->setPublicKey(); - $newkey = $rsa->getPublicKey('XML'); + $rsa = PublicKeyLoader::load($key); + $newkey = $rsa->toString('XML'); $this->assertSame(strtolower(preg_replace('#\s#', '', $key)), strtolower(preg_replace('#\s#', '', $newkey))); } @@ -313,8 +301,6 @@ Ao8eayMp6FcvNucIpUndo1X8dKMv3Y26ZQIDAQAB */ public function testSignedPKCS1() { - $rsa = new RSA(); - $key = '-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/k7FwSDE9R9rvTU2nGdJwKaVG RvBIYGJNahseQhZkQH4CVFMdpWhmD8PyXpjNHtV1CJ0bqAX6e5QyNjvl0FeBj9dz @@ -322,9 +308,8 @@ JWrQdxx/WNN+ABG426rgYYbeGcIlWLZCw6Bx/1HtN5ef6nVEoiGNChYKIRB4QFOi 01smFxps1w8ZIQnD6wIDAQAB -----END PUBLIC KEY-----'; - $rsa->load($key); - $rsa->setPublicKey(); - $newkey = $rsa->getPublicKey(); + $rsa = PublicKeyLoader::load($key); + $newkey = "$rsa"; $this->assertSame(preg_replace('#\s#', '', $key), preg_replace('#\s#', '', $newkey)); } @@ -334,8 +319,6 @@ JWrQdxx/WNN+ABG426rgYYbeGcIlWLZCw6Bx/1HtN5ef6nVEoiGNChYKIRB4QFOi */ public function testPKCS8Only() { - $rsa = new RSA(); - $key = '-----BEGIN PRIVATE KEY----- MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKB0yPMAbUHKqJxP 5sjG9AOrQSAYNDc34NsnZ1tsi7fZ9lHlBaKZ6gjm2U9q+/qCKv2BuGINxWo2CMJp @@ -353,15 +336,13 @@ qMnD/pkHR/NFcYSYShUJS0cHyryVl7/eCclsQlZTRdnVTtKF9xPGTQC8fK0G7BDN Z2sKniRCcDT1ZP4= -----END PRIVATE KEY-----'; - $result = $rsa->load($key, 'PKCS8'); + $rsa = RSA::load($key, false, 'PKCS8'); - $this->assertTrue($result); + $this->assertInstanceOf(PrivateKey::class, $rsa); } public function testPKCS1EncryptionChange() { - $rsa = new RSA(); - $key = 'PuTTY-User-Key-File-2: ssh-rsa Encryption: none Comment: phpseclib-generated-key @@ -382,44 +363,23 @@ Gpb88h5NBYZzWXGZ37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ Private-MAC: 03e2cb74e1d67652fbad063d2ed0478f31bdf256 '; $key = preg_replace('#(?assertTrue($rsa->load($key)); - - $rsa->setPrivateKeyFormat('PKCS1'); + $rsa = PublicKeyLoader::load($key); + $this->assertInstanceOf(PrivateKey::class, $rsa); PKCS1::setEncryptionAlgorithm('AES-256-CBC'); - $rsa->setPassword('demo'); - - $encryptedKey = (string) $rsa; + $encryptedKey = $rsa->withPassword('demo')->toString('PKCS1'); $this->assertRegExp('#AES-256-CBC#', $encryptedKey); - $rsa = new RSA(); - $rsa->setPassword('demo'); - $this->assertTrue($rsa->load($encryptedKey)); - $rsa->setPassword(); - $rsa->setPrivateKeyFormat('PuTTY'); - $key2 = (string) $rsa; + $rsa = PublicKeyLoader::load($key, 'demo'); + $this->assertInstanceOf(PrivateKey::class, $rsa); OpenSSH::setComment('ecdsa-key-20181105'); + $key2 = $rsa->withPassword()->toString('PuTTY'); + $this->assertSame($key, $key2); } - public function testRawKey() - { - $rsa = new RSA(); - - $key = [ - 'e' => new BigInteger('10001', 16), - 'n' => new BigInteger('aa18aba43b50deef38598faf87d2ab634e4571c130a9bca7b878267414faab8b471bd8965f5c9fc3' . - '818485eaf529c26246f3055064a8de19c8c338be5496cbaeb059dc0b358143b44a35449eb2641131' . - '21a455bd7fde3fac919e94b56fb9bb4f651cdb23ead439d6cd523eb08191e75b35fd13a7419b3090' . - 'f24787bd4f4e1967', 16) - ]; - $this->assertTrue($rsa->load($key)); - $rsa->setPublicKeyFormat('raw'); - $this->assertEmpty("$rsa"); - } - public function testRawComment() { $key = 'PuTTY-User-Key-File-2: ssh-rsa @@ -440,13 +400,10 @@ fM8VzC3ukvzzRh0pujUVTr/yQdmciASVFnZlt4xQy+ZEOVUAOfwjd//AFfXTvk6x EOpSeghXSs7IilJu8I6/sB1w5dakdeBSFkIynrlFXkO0uUw+QJJWjxY8SypzgIuP DzduF6XsQrCyo6dnIpGQCQ== Private-MAC: 35134b7434bf828b21404099861d455e660e8740'; + $raw = PuTTY::load($key, 'password'); $this->assertArrayHasKey('comment', $raw); $this->assertEquals($raw['comment'], 'phpseclib-generated-key'); - - $rsa = new RSA(); - $rsa->load($raw); - $this->assertGreaterThanOrEqual(1, strlen("$rsa")); } public function testPrivateMSBlob() @@ -465,16 +422,13 @@ Private-MAC: 35134b7434bf828b21404099861d455e660e8740'; $plaintext = 'zzz'; - $privKey = new RSA(); - $privKey->load($key); - + $privKey = PublicKeyLoader::load($key); + $this->assertInstanceOf(PrivateKey::class, $privKey); $this->assertSame($privKey->getLoadedFormat(), 'MSBLOB'); - $this->assertGreaterThanOrEqual(1, strlen("$privKey")); - $pubKey = new RSA(); - $pubKey->load($privKey->getPublicKey('msblob')); - + $pubKey = PublicKeyLoader::load($privKey->getPublicKey()->toString('msblob')); + $this->assertInstanceOf(PublicKey::class, $pubKey); $this->assertGreaterThanOrEqual(1, strlen("$pubKey")); $ciphertext = $pubKey->encrypt($plaintext); @@ -486,9 +440,8 @@ Private-MAC: 35134b7434bf828b21404099861d455e660e8740'; { $key = 'AAAAB3NzaC1yc2EAAAABIwAAAIEA/NcGSQFZ0ZgN1EbDusV6LLwLnQjs05ljKcVVP7Z6aKIJUyhUDHE30uJa5XfwPPBsZ3L3Q7S0yycVcuuHjdauugmpn9xx+gyoYs7UiV5G5rvxNcA/Tc+MofGhAMiTmNicorNAs5mv6fRoVbkpIONRXPz6WK0kjx/X04EV42Vm9Qk='; - $rsa = new RSA(); - $rsa->load($key); - + $rsa = PublicKeyLoader::load($key); + $this->assertInstanceOf(PublicKey::class, $rsa); $this->assertSame($rsa->getLoadedFormat(), 'OpenSSH'); $this->assertGreaterThanOrEqual(1, strlen("$rsa")); @@ -504,48 +457,13 @@ C/EwUYl8b0fAwEsEF3myb+ryzgA9ihY08Zs9NZdmt1Maa+I7lQcLX9F/65YdcAch ILaEujU= ---- END SSH2 PUBLIC KEY ----'; - $rsa = new RSA(); - $rsa->load($key); - + $rsa = PublicKeyLoader::load($key); + $this->assertInstanceOf(PublicKey::class, $rsa); $this->assertSame($rsa->getLoadedFormat(), 'PuTTY'); $this->assertGreaterThanOrEqual(1, strlen("$rsa")); } - /** - * @group github960 - */ - public function testSetLoad() - { - $key = 'PuTTY-User-Key-File-2: ssh-rsa -Encryption: aes256-cbc -Comment: phpseclib-generated-key -Public-Lines: 4 -AAAAB3NzaC1yc2EAAAADAQABAAAAgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4 -eCZ0FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RK -NUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDy -R4e9T04ZZw== -Private-Lines: 8 -llx04QMegql0/nE5RvcJSrGrodxt6ytuv/JX2caeZBUyQwQc2WBNYagLHyHPM9jI -9OUWz59FLhjFXZMDNMoUXxVmjwQpOAaVPYNxxFM9AF6/NXFji64K7huD9n4A+kLn -sHwMLWPR5a/tZA0r05DZNz9ULA3mQu7Hz4EQ8ifu3uTPJuTmL51x6RmudYKysb20 -fM8VzC3ukvzzRh0pujUVTr/yQdmciASVFnZlt4xQy+ZEOVUAOfwjd//AFfXTvk6x -7A45rNlU/uicHwLgoY1APvRHCFxw7F+uVW5L4mSX7NNzqBKkZ+1qpQTAfQvIfEIb -444+CXsgIyOpqt6VxJH2u6elAtE1wau3YaFR8Alm8m97rFYzRi3oDP5NZYkTCWSV -EOpSeghXSs7IilJu8I6/sB1w5dakdeBSFkIynrlFXkO0uUw+QJJWjxY8SypzgIuP -DzduF6XsQrCyo6dnIpGQCQ== -Private-MAC: 35134b7434bf828b21404099861d455e660e8740'; - - $rsa = new RSA(); - $rsa->setPrivateKey($key); - $rsa->load($key); - - $rsa = new RSA(); - $rsa->load($key); - $rsa->setPrivateKey(); - $rsa->load($rsa); - } - /** * @group github980 */ @@ -558,19 +476,17 @@ NNj0BDlf38hOtkhDzz/hkYb+EBYLLvldhgsD0OvRNy8yhz7EjaUqLCB0juIN4QIB AAIBAAIBAAIBAAIBAA== -----END RSA PRIVATE KEY-----'; - $rsa = new RSA(); - $rsa->load($key); - $rsa->setHash('md5'); - $rsa->setMGFHash('md5'); + $rsa = PublicKeyLoader::load($key) + ->withHash('md5') + ->withMGFHash('md5') + ->withPadding(RSA::SIGNATURE_PKCS1); - $rsa->sign('zzzz', RSA::PADDING_PKCS1); + $rsa->sign('zzzz'); } public function pkcs8tester($key, $pass) { - $rsa = new RSA(); - $rsa->setPassword($pass); - $rsa->load($key); + $rsa = PublicKeyLoader::load($key, $pass); $r = PKCS8::load($key, $pass); PKCS8::setEncryptionAlgorithm($r['meta']['algorithm']); if (isset($r['meta']['cipher'])) { @@ -590,15 +506,13 @@ AAIBAAIBAAIBAAIBAA== $this->assertSame($r['meta']['prf'], $r2['meta']['prf']); } - $rsa2 = new RSA(); - $rsa2->setPassword($pass); - $rsa2->load($newkey); + $rsa2 = PublicKeyLoader::load($newkey, $pass); // comparing $key to $newkey won't work since phpseclib randomly generates IV's and salt's // so we'll strip the encryption - $rsa->setPassword(); - $rsa2->setPassword(); + $rsa = $rsa->withPassword(); + $rsa2 = $rsa2->withPassword(); $this->assertSame("$rsa", "$rsa2"); } @@ -930,29 +844,8 @@ OFLPBrLe4Hw= $this->pkcs8tester($key, $pass); } - public function testGoodBad() - { - $rsa = new RSA(); - - $key = '-----BEGIN RSA PUBLIC KEY----- -MIIBCgKCAQEA61BjmfXGEvWmegnBGSuS+rU9soUg2FnODva32D1AqhwdziwHINFa -D1MVlcrYG6XRKfkcxnaXGfFDWHLEvNBSEVCgJjtHAGZIm5GL/KA86KDp/CwDFMSw -luowcXwDwoyinmeOY9eKyh6aY72xJh7noLBBq1N0bWi1e2i+83txOCg4yV2oVXhB -o8pYEJ8LT3el6Smxol3C1oFMVdwPgc0vTl25XucMcG/ALE/KNY6pqC2AQ6R2ERlV -gPiUWOPatVkt7+Bs3h5Ramxh7XjBOXeulmCpGSynXNcpZ/06+vofGi/2MlpQZNhH -Ao8eayMp6FcvNucIpUndo1X8dKMv3Y26ZQIDAQAB ------END RSA PUBLIC KEY-----'; - - $this->assertTrue($rsa->load($key)); - $this->assertInstanceOf(RSA::class, $rsa->getPublicKey()); - $this->assertFalse($rsa->load('zzz')); - $this->assertFalse($rsa->getPublicKey()); - } - public function testXMLDeclaration() { - $rsa = new RSA(); - $key = ' AKoYq6Q7UN7vOFmPr4fSq2NORXHBMKm8p7h4JnQU+quLRxvYll9cn8OBhIXq9SnCYkbzBVBkqN4ZyMM4vlSWy66wWdwLNYFDtEo1RJ6yZBExIaRVvX/eP6yRnpS1b7m7T2Uc2yPq1DnWzVI+sIGR51s1/ROnQZswkPJHh71PThln @@ -965,7 +858,8 @@ Ao8eayMp6FcvNucIpUndo1X8dKMv3Y26ZQIDAQAB Fijko56+qGyN8M0RVyaRAXz++xTqHBLh3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2pIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxE= '; - $this->assertTrue($rsa->load($key)); - $this->assertInstanceOf(RSA::class, $rsa->getPublicKey()); + $rsa = PublicKeyLoader::load($key); + $this->assertInstanceOf(PrivateKey::class, $rsa); + $this->assertInstanceOf(PublicKey::class, $rsa->getPublicKey()); } } diff --git a/tests/Unit/Crypt/RSA/ModeTest.php b/tests/Unit/Crypt/RSA/ModeTest.php index 526ad648..17ff7d8d 100644 --- a/tests/Unit/Crypt/RSA/ModeTest.php +++ b/tests/Unit/Crypt/RSA/ModeTest.php @@ -7,6 +7,8 @@ use phpseclib\Crypt\RSA; use phpseclib\Math\BigInteger; +use phpseclib\Crypt\PublicKeyLoader; +use phpseclib\Crypt\RSA\Keys\PKCS8; class Unit_Crypt_RSA_ModeTest extends PhpseclibTestCase { @@ -14,8 +16,6 @@ class Unit_Crypt_RSA_ModeTest extends PhpseclibTestCase { $plaintext = 'a'; - $rsa = new RSA(); - $privatekey = '-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5 @@ -29,19 +29,21 @@ X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ 37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0= -----END RSA PRIVATE KEY-----'; - $rsa->load($privatekey); - $rsa->load($rsa->getPublicKey()); + $rsa = PublicKeyLoader::load($privatekey); + $rsa = $rsa->getPublicKey() + ->withPadding(RSA::ENCRYPTION_NONE); $expected = '105b92f59a87a8ad4da52c128b8c99491790ef5a54770119e0819060032fb9e772ed6772828329567f3d7e9472154c1530f8156ba7fd732f52ca1c06' . '5a3f5ed8a96c442e4662e0464c97f133aed31262170201993085a589565d67cc9e727e0d087e3b225c8965203b271e38a499c92fc0d6502297eca712' . '4d04bd467f6f1e7c'; $expected = pack('H*', $expected); - $result = $rsa->encrypt($plaintext, RSA::PADDING_NONE); + $result = $rsa->encrypt($plaintext); $this->assertEquals($result, $expected); - $rsa->load($privatekey); - $this->assertEquals(trim($rsa->decrypt($result, RSA::PADDING_NONE), "\0"), $plaintext); + $rsa = PublicKeyLoader::load($privatekey) + ->withPadding(RSA::ENCRYPTION_NONE); + $this->assertEquals(trim($rsa->decrypt($result), "\0"), $plaintext); } /** @@ -49,15 +51,14 @@ U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ */ public function testPSSSigs() { - $rsa = new RSA(); - $rsa->setHash('sha1'); - $rsa->setMGFHash('sha1'); - $rsa->load('-----BEGIN PUBLIC KEY----- + $rsa = PublicKeyLoader::load('-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVx wTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFnc CzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0T p0GbMJDyR4e9T04ZZwIDAQAB ------END PUBLIC KEY-----'); +-----END PUBLIC KEY-----') + ->withHash('sha1') + ->withMGFHash('sha1'); $sig = pack('H*', '1bd29a1d704a906cd7f726370ce1c63d8fb7b9a620871a05f3141a311c0d6e75fefb5d36dfb50d3ea2d37cd67992471419bfadd35da6e13b494' . '058ddc9b568d4cfea13ddc3c62b86a6256f5f296980d1131d3eaec6089069a3de79983f73eae20198a18721338b4a66e9cfe80e4f8e4fcef7a5bead5cbb' . @@ -72,22 +73,24 @@ p0GbMJDyR4e9T04ZZwIDAQAB public function testSmallModulo() { $plaintext = 'x'; - $n = new BigInteger(base64_decode('272435F22706FA96DE26E980D22DFF67'), 256); - $e = new BigInteger(base64_decode('158753FF2AF4D1E5BBAB574D5AE6B54D'), 256); - $rsa = new RSA(); - $rsa->load(['n' => $n, 'e' => $e]); + $key = PKCS8::savePublicKey( + new BigInteger(base64_decode('272435F22706FA96DE26E980D22DFF67'), 256), // n + new BigInteger(base64_decode('158753FF2AF4D1E5BBAB574D5AE6B54D'), 256) // e + ); + $rsa = PublicKeyLoader::load($key); + $rsa->encrypt($plaintext); } public function testPKCS1LooseVerify() { - $rsa = new RSA(); - $rsa->load('-----BEGIN RSA PUBLIC KEY----- + $rsa = PublicKeyLoader::load('-----BEGIN RSA PUBLIC KEY----- MIGJAoGBAMuqkz8ij+ESAaNvgocVGmapjlrIldmhRo4h2NX4e6IXiCLTSxASQtY4 iqRnmyxqQSfaan2okTfQ6sP95bl8Qz8lgneW3ClC6RXG/wpJgsx7TXQ2kodlcKBF m4k72G75QXhZ+I40ZG7cjBf1/9egakR0a0X0MpeOrKCzMBLv9+mpAgMBAAE= ------END RSA PUBLIC KEY-----'); +-----END RSA PUBLIC KEY-----') + ->withPadding(RSA::SIGNATURE_RELAXED_PKCS1); $message = base64_decode('MYIBLjAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNDA1MTUxNDM4MzRaMC8GCSqGSIb3DQEJBDEiBCBLzLIBGdOf0L2WRrIY' . '9KTwiHnReBW48S9C7LNRaPp5mDCBwgYLKoZIhvcNAQkQAi8xgbIwga8wgawwgakEIJDB9ZGwihf+TaiwrHQNkNHkqbN8Nuws0e77QNObkvFZMIGEMHCkbjBs' . @@ -97,16 +100,14 @@ m4k72G75QXhZ+I40ZG7cjBf1/9egakR0a0X0MpeOrKCzMBLv9+mpAgMBAAE= $sig = base64_decode('XDSZWw6IcUj8ICxRJf04HzF8stzoiFAZSR2a0Rw3ziZxTOT0/NVUYJO5+9TaaREXEgxuCLpgmA+6W2SWrrGoxbbNfaI90ZoKeOAws4IX+9RfiWuooibjKcvt' . 'GJYVVOCcjvQYxUUNbQ4EjCUonk3h7ECXfCCmWqbeq2LsyXeeYGE='); - $this->assertTrue($rsa->verify($message, $sig, RSA::PADDING_RELAXED_PKCS1)); + $this->assertTrue($rsa->verify($message, $sig)); } public function testZeroLengthSalt() { $plaintext = 'a'; - $rsa = new RSA(); - - $privatekey = '-----BEGIN RSA PRIVATE KEY----- + $rsa = PublicKeyLoader::load('-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5 1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh @@ -118,96 +119,17 @@ L0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ 37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0= ------END RSA PRIVATE KEY-----'; - $rsa->load($privatekey); - $rsa->setSaltLength(0); - $rsa->setHash('sha1'); - $rsa->setMGFHash('sha1'); +-----END RSA PRIVATE KEY-----') + ->withSaltLength(0) + ->withHash('sha1') + ->withMGFHash('sha1'); // Check we generate the correct signature. $sig = pack('H*', '0ddfc93548e21d015c0a289a640b3b79aecfdfae045f583c5925b91cc5c399bba181616ad6ae20d9662d966f0eb2fddb550f4733268e34d640f4c9dadcaf25b3c82c42130a5081c6ebad7883331c65b25b6a37ffa7c4233a468dae56180787e2718ed87c48d8d50b72f5850e4a40963b4f36710be250ecef6fe0bb91249261a3'); $this->assertEquals($sig, $rsa->sign($plaintext)); // Check we can verify the signature correctly. - $rsa->load($rsa->getPublicKey()); + $rsa = $rsa->getPublicKey(); $this->assertTrue($rsa->verify($plaintext, $sig)); } - - /** - * @expectedException \phpseclib\Exception\UnsupportedOperationException - */ - public function testPrivateEncrypt() - { - $rsa = new RSA(); - $privatekey = '-----BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp -wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5 -1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh -3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2 -pIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxECQQDeAw6fiIQX -GukBI4eMZZt4nscy2o12KyYner3VpoeE+Np2q+Z3pvAMd/aNzQ/W9WaI+NRfcxUJrmfPwIGm63il -AkEAxCL5HQb2bQr4ByorcMWm/hEP2MZzROV73yF41hPsRC9m66KrheO9HPTJuo3/9s5p+sqGxOlF -L0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k -X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl -U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ -37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0= ------END RSA PRIVATE KEY-----'; - $rsa->load($privatekey); - - $rsa->encrypt('hello, world!'); - } - - /** - * @expectedException \phpseclib\Exception\UnsupportedOperationException - */ - public function testPublicSign() - { - $rsa = new RSA(); - $rsa->load('-----BEGIN RSA PUBLIC KEY----- -MIGJAoGBAMuqkz8ij+ESAaNvgocVGmapjlrIldmhRo4h2NX4e6IXiCLTSxASQtY4 -iqRnmyxqQSfaan2okTfQ6sP95bl8Qz8lgneW3ClC6RXG/wpJgsx7TXQ2kodlcKBF -m4k72G75QXhZ+I40ZG7cjBf1/9egakR0a0X0MpeOrKCzMBLv9+mpAgMBAAE= ------END RSA PUBLIC KEY-----'); - - $rsa->sign('hello, world!'); - } - - /** - * @expectedException \phpseclib\Exception\UnsupportedOperationException - */ - public function testPublicDecrypt() - { - $rsa = new RSA(); - $rsa->load('-----BEGIN RSA PUBLIC KEY----- -MIGJAoGBAMuqkz8ij+ESAaNvgocVGmapjlrIldmhRo4h2NX4e6IXiCLTSxASQtY4 -iqRnmyxqQSfaan2okTfQ6sP95bl8Qz8lgneW3ClC6RXG/wpJgsx7TXQ2kodlcKBF -m4k72G75QXhZ+I40ZG7cjBf1/9egakR0a0X0MpeOrKCzMBLv9+mpAgMBAAE= ------END RSA PUBLIC KEY-----'); - - $rsa->decrypt('zzz'); - } - - /** - * @expectedException \phpseclib\Exception\UnsupportedOperationException - */ - public function testPrivateVerify() - { - $rsa = new RSA(); - $privatekey = '-----BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp -wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5 -1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh -3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2 -pIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxECQQDeAw6fiIQX -GukBI4eMZZt4nscy2o12KyYner3VpoeE+Np2q+Z3pvAMd/aNzQ/W9WaI+NRfcxUJrmfPwIGm63il -AkEAxCL5HQb2bQr4ByorcMWm/hEP2MZzROV73yF41hPsRC9m66KrheO9HPTJuo3/9s5p+sqGxOlF -L0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k -X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl -U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ -37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0= ------END RSA PRIVATE KEY-----'; - $rsa->load($privatekey); - - $rsa->verify('hello, world!', 'dummysignature'); - } } diff --git a/tests/Unit/File/X509/CSRTest.php b/tests/Unit/File/X509/CSRTest.php index a9dc47a9..a57ee17b 100644 --- a/tests/Unit/File/X509/CSRTest.php +++ b/tests/Unit/File/X509/CSRTest.php @@ -7,6 +7,7 @@ use phpseclib\File\X509; use phpseclib\Crypt\RSA; +use phpseclib\Crypt\PublicKeyLoader; class Unit_File_X509_CSRTest extends PhpseclibTestCase { @@ -98,10 +99,9 @@ draiRBZruwMPwPIP // on PHP 7.1, with older versions of phpseclib, this would produce a "A non-numeric value encountered" warning public function testNewCSR() { - $rsa = new RSA(); $x509 = new X509(); - $rsa->load('-----BEGIN RSA PRIVATE KEY----- + $rsa = PublicKeyLoader::load('-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5 1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh diff --git a/tests/Unit/File/X509/SPKACTest.php b/tests/Unit/File/X509/SPKACTest.php index 4f01181a..ebd0fe16 100644 --- a/tests/Unit/File/X509/SPKACTest.php +++ b/tests/Unit/File/X509/SPKACTest.php @@ -46,7 +46,7 @@ class Unit_File_X509_SPKACTest extends PhpseclibTestCase public function testSaveSPKAC() { - extract(RSA::createKey()); + $privatekey = RSA::createKey(); $x509 = new X509(); $x509->setPrivateKey($privatekey); diff --git a/tests/Unit/File/X509/X509Test.php b/tests/Unit/File/X509/X509Test.php index 2e513812..8ba0e575 100644 --- a/tests/Unit/File/X509/X509Test.php +++ b/tests/Unit/File/X509/X509Test.php @@ -9,6 +9,7 @@ use phpseclib\File\ASN1; use phpseclib\File\ASN1\Element; use phpseclib\File\X509; use phpseclib\Crypt\RSA; +use phpseclib\Crypt\PublicKeyLoader; class Unit_File_X509_X509Test extends PhpseclibTestCase { @@ -133,7 +134,7 @@ ulvKGQSy068Bsn5fFNum21K5mvMSf3yinDtvmX3qUA12IxL/92ZzKbeVCq3Yi7Le IOkKcGQRCMha8X2e7GmlpdWC1ycenlbN0nbVeSv3JUMcafC4+Q== -----END CERTIFICATE-----'); - $value = $this->_encodeOID('1.2.3.4'); + $value = ASN1::encodeOID('1.2.3.4'); $ext = chr(ASN1::TYPE_OBJECT_IDENTIFIER) . ASN1::encodeLength(strlen($value)) . $value; $value = 'zzzzzzzzz'; $ext.= chr(ASN1::TYPE_OCTET_STRING) . ASN1::encodeLength(strlen($value)) . $value; @@ -151,8 +152,7 @@ IOkKcGQRCMha8X2e7GmlpdWC1ycenlbN0nbVeSv3JUMcafC4+Q== */ public function testSaveNullRSAParam() { - $privKey = new RSA(); - $privKey->load('-----BEGIN RSA PRIVATE KEY----- + $privKey = PublicKeyLoader::load('-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDMswfEpAgnUDWA74zZw5XcPsWh1ly1Vk99tsqwoFDkLF7jvXy1 dDLHYfuquvfxCgcp8k/4fQhx4ubR8bbGgEq9B05YRnViK0R0iBB5Ui4IaxWYYhKE 8xqAEH2fL+/7nsqqNFKkEN9KeFwc7WbMY49U2adlMrpBdRjk1DqIEW3QTwIDAQAB @@ -168,9 +168,7 @@ aBtsWpliLSex/HHhtRW9AkBGcq67zKmEpJ9kXcYLEjJii3flFS+Ct/rNm+Hhm1l7 4vca9v/F2hGVJuHIMJ8mguwYlNYzh2NqoIDJTtgOkBmt -----END RSA PRIVATE KEY-----'); - $pubKey = new RSA(); - $pubKey->load($privKey->getPublicKey()); - $pubKey->setPublicKey(); + $pubKey = $privKey->getPublicKey(); $subject = new X509(); $subject->setDNProp('id-at-organizationName', 'phpseclib demo cert'); @@ -192,37 +190,12 @@ aBtsWpliLSex/HHhtRW9AkBGcq67zKmEpJ9kXcYLEjJii3flFS+Ct/rNm+Hhm1l7 $this->assertArrayHasKey('parameters', $cert['tbsCertificate']['signature']); } - private function _encodeOID($oid) - { - if ($oid === false) { - user_error('Invalid OID'); - return false; - } - $value = ''; - $parts = explode('.', $oid); - $value = chr(40 * $parts[0] + $parts[1]); - for ($i = 2; $i < count($parts); $i++) { - $temp = ''; - if (!$parts[$i]) { - $temp = "\0"; - } else { - while ($parts[$i]) { - $temp = chr(0x80 | ($parts[$i] & 0x7F)) . $temp; - $parts[$i] >>= 7; - } - $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F); - } - $value.= $temp; - } - return $value; - } - public function testGetOID() { // load the OIDs new X509(); - $this->assertEquals(ASN1::getOID('2.16.840.1.101.3.4.2.1'), '2.16.840.1.101.3.4.2.1'); - $this->assertEquals(ASN1::getOID('id-sha256'), '2.16.840.1.101.3.4.2.1'); + $this->assertEquals(ASN1::getOID('1.2.840.113549.1.1.5'), '1.2.840.113549.1.1.5'); + $this->assertEquals(ASN1::getOID('sha1WithRSAEncryption'), '1.2.840.113549.1.1.5'); $this->assertEquals(ASN1::getOID('zzz'), 'zzz'); } @@ -383,7 +356,8 @@ Mj93S // fixed by #1104 public function testMultipleDomainNames() { - extract(RSA::createKey(512)); + $privatekey = RSA::createKey(512); + $publickey = $privatekey->getPublicKey(); $subject = new X509(); $subject->setDomain('example.com', 'example.net'); @@ -578,8 +552,7 @@ keSg3sfr4VWT545guJlTe+6vvelxbPFIXCXnyVLoePBYZtEe8FQhIBxd3EQHsxuJ iSoMCxKCa8r5P1DrxKaJAkBBP87OdahRq0CBQjTFg0wmPs66PoTXA4hZvSxV77CO tMPj6Pas7Muejogm6JkmxXC/uT6Tzfknd0B3XSmtDzGL -----END RSA PRIVATE KEY-----'; - $cakey = new RSA(); - $cakey->load($pemcakey); + $cakey = PublicKeyLoader::load($pemcakey); $pemca = '-----BEGIN CERTIFICATE----- MIICADCCAWmgAwIBAgIUJXQulcz5xkTam8UGC/yn6iVaiWwwDQYJKoZIhvcNAQEF BQAwHDEaMBgGA1UECgwRcGhwc2VjbGliIGRlbW8gQ0EwHhcNMTgwMTIxMTc0NzM0