diff --git a/README.md b/README.md index 29580c66..b6f96fad 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # phpseclib - PHP Secure Communications Library -[![Build Status](https://secure.travis-ci.org/phpseclib/phpseclib.png?branch=2.0)](http://travis-ci.org/phpseclib/phpseclib) +[![Build Status](https://secure.travis-ci.org/phpseclib/phpseclib.png?branch=master)](http://travis-ci.org/phpseclib/phpseclib) MIT-licensed pure-PHP implementations of an arbitrary-precision integer arithmetic library, fully PKCS#1 (v2.1) compliant RSA, DES, 3DES, RC4, Rijndael, @@ -8,7 +8,7 @@ AES, Blowfish, Twofish, SSH-1, SSH-2, SFTP, and X.509 * [Download (1.0.0)](http://sourceforge.net/projects/phpseclib/files/phpseclib1.0.0.zip/download) * [Browse Git](https://github.com/phpseclib/phpseclib) -* [Code Coverage Report](http://phpseclib.bantux.org/code_coverage/2.0/latest/) +* [Code Coverage Report](http://phpseclib.bantux.org/code_coverage/master/latest/) PEAR Channel PEAR Channel: [phpseclib.sourceforge.net](http://phpseclib.sourceforge.net/pear.htm) @@ -16,7 +16,7 @@ PEAR Channel: [phpseclib.sourceforge.net](http://phpseclib.sourceforge.net/pear. ## Documentation * [Documentation / Manual](http://phpseclib.sourceforge.net/) -* [API Documentation](http://phpseclib.bantux.org/api/2.0/) (generated by Sami) +* [API Documentation](http://phpseclib.bantux.org/api/master/) (generated by Sami) ## Support diff --git a/phpseclib/Crypt/Base.php b/phpseclib/Crypt/Base.php index 6b64aa99..024327d8 100644 --- a/phpseclib/Crypt/Base.php +++ b/phpseclib/Crypt/Base.php @@ -605,6 +605,7 @@ abstract class Base * @see Crypt/Hash.php * @param string $password * @param string $method + * @throws \LengthException if pbkdf1 is being used and the derived key length exceeds the hash length * @return bool * @access public * @internal Could, but not must, extend by the child Crypt_* class @@ -639,8 +640,7 @@ abstract class Base $hashObj = new Hash(); $hashObj->setHash($hash); if ($dkLen > $hashObj->getLength()) { - user_error('Derived key too long'); - return false; + throw new \LengthException('Derived key length cannot be longer than the hash length'); } $t = $password . $salt; for ($i = 0; $i < $count; ++$i) { @@ -1827,6 +1827,7 @@ abstract class Base * * @see \phpseclib\Crypt\Base::_unpad() * @param string $text + * @throws \LengthException if padding is disabled and the plaintext's length is not a multiple of the block size * @access private * @return string */ @@ -1838,8 +1839,7 @@ abstract class Base if ($length % $this->block_size == 0) { return $text; } else { - user_error("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size})"); - $this->padding = true; + throw new \LengthException("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size}). Try enabling padding."); } } @@ -1856,6 +1856,7 @@ abstract class Base * * @see \phpseclib\Crypt\Base::_pad() * @param string $text + * @throws \LengthException if the ciphertext's length is not a multiple of the block size * @access private * @return string */ @@ -1868,7 +1869,7 @@ abstract class Base $length = ord($text[strlen($text) - 1]); if (!$length || $length > $this->block_size) { - return false; + throw new \LengthException("The ciphertext has an invalid padding length ($length) compared to the block size ({$this->block_size})"); } return substr($text, 0, -$length); diff --git a/phpseclib/Crypt/Hash.php b/phpseclib/Crypt/Hash.php index c8ca476a..41afd6d9 100644 --- a/phpseclib/Crypt/Hash.php +++ b/phpseclib/Crypt/Hash.php @@ -1,26 +1,20 @@ * setKey('abcdefg'); * @@ -31,42 +25,25 @@ * @category Crypt * @package Hash * @author Jim Wigginton - * @copyright 2007 Jim Wigginton + * @copyright 2015 Jim Wigginton + * @author Andreas Fischer + * @copyright 2015 Andreas Fischer * @license http://www.opensource.org/licenses/mit-license.html MIT License * @link http://phpseclib.sourceforge.net */ namespace phpseclib\Crypt; -use phpseclib\Math\BigInteger; +use phpseclib\Exception\UnsupportedAlgorithmException; /** - * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions. - * * @package Hash * @author Jim Wigginton + * @author Andreas Fischer * @access public */ class Hash { - /**#@+ - * @access private - * @see \phpseclib\Crypt\Hash::__construct() - */ - /** - * Toggles the internal implementation - */ - const MODE_INTERNAL = 1; - /** - * Toggles the mhash() implementation, which has been deprecated on PHP 5.3.0+. - */ - const MODE_MHASH = 2; - /** - * Toggles the hash() implementation, which works on PHP 5.1.2+. - */ - const MODE_HASH = 3; - /**#@-*/ - /** * Hash Parameter * @@ -76,15 +53,6 @@ class Hash */ var $hashParam; - /** - * Byte-length of compression blocks / key (Internal HMAC) - * - * @see \phpseclib\Crypt\Hash::setAlgorithm() - * @var int - * @access private - */ - var $b; - /** * Byte-length of hash output (Internal HMAC) * @@ -92,7 +60,7 @@ class Hash * @var int * @access private */ - var $l = false; + var $length; /** * Hash Algorithm @@ -112,46 +80,14 @@ class Hash */ var $key = false; - /** - * Outer XOR (Internal HMAC) - * - * @see \phpseclib\Crypt\Hash::setKey() - * @var string - * @access private - */ - var $opad; - - /** - * Inner XOR (Internal HMAC) - * - * @see \phpseclib\Crypt\Hash::setKey() - * @var string - * @access private - */ - var $ipad; - /** * Default Constructor. * * @param string $hash - * @return \phpseclib\Crypt\Hash * @access public */ - function __construct($hash = 'sha1') + function __construct($hash = 'sha256') { - if (!defined('CRYPT_HASH_MODE')) { - switch (true) { - case extension_loaded('hash'): - define('CRYPT_HASH_MODE', self::MODE_HASH); - break; - case extension_loaded('mhash'): - define('CRYPT_HASH_MODE', self::MODE_MHASH); - break; - default: - define('CRYPT_HASH_MODE', self::MODE_INTERNAL); - } - } - $this->setHash($hash); } @@ -196,96 +132,47 @@ class Hash case 'sha256-96': case 'sha512-96': $hash = substr($hash, 0, -3); - $this->l = 12; // 96 / 8 = 12 + $this->length = 12; // 96 / 8 = 12 break; case 'md2': case 'md5': - $this->l = 16; + $this->length = 16; break; case 'sha1': - $this->l = 20; + $this->length = 20; break; case 'sha256': - $this->l = 32; + $this->length = 32; break; case 'sha384': - $this->l = 48; + $this->length = 48; break; case 'sha512': - $this->l = 64; - } - - switch ($hash) { - case 'md2': - $mode = CRYPT_HASH_MODE == self::MODE_HASH && in_array('md2', hash_algos()) ? - self::MODE_HASH : self::MODE_INTERNAL; - break; - case 'sha384': - case 'sha512': - $mode = CRYPT_HASH_MODE == self::MODE_MHASH ? self::MODE_INTERNAL : CRYPT_HASH_MODE; + $this->length = 64; break; default: - $mode = CRYPT_HASH_MODE; - } - - switch ($mode) { - case self::MODE_MHASH: - switch ($hash) { - case 'md5': - $this->hash = MHASH_MD5; - break; - case 'sha256': - $this->hash = MHASH_SHA256; - break; - case 'sha1': - default: - $this->hash = MHASH_SHA1; + // see if the hash isn't "officially" supported see if it can + // be "unofficially" supported and calculate the length + // accordingly. + if (in_array($hash, hash_algos())) { + $this->length = strlen(hash($hash, '', true)); + break; } - return; - case self::MODE_HASH: - switch ($hash) { - case 'md5': - $this->hash = 'md5'; - return; - case 'md2': - case 'sha256': - case 'sha384': - case 'sha512': - $this->hash = $hash; - return; - case 'sha1': - default: - $this->hash = 'sha1'; + // if the hash algorithm doens't exist maybe it's a truncated + // hash, e.g. whirlpool-12 or some such. + if (preg_match('#(-\d+)$#', $hash, $matches)) { + $hash = substr($hash, 0, -strlen($matches[1])); + if (in_array($hash, hash_algos())) { + $this->length = abs($matches[1]) >> 3; + break; + } } - return; + throw new UnsupportedAlgorithmException( + "$hash is not a supported algorithm" + ); } - switch ($hash) { - case 'md2': - $this->b = 16; - $this->hash = array($this, '_md2'); - break; - case 'md5': - $this->b = 64; - $this->hash = array($this, '_md5'); - break; - case 'sha256': - $this->b = 64; - $this->hash = array($this, '_sha256'); - break; - case 'sha384': - case 'sha512': - $this->b = 128; - $this->hash = array($this, '_sha512'); - break; - case 'sha1': - default: - $this->b = 64; - $this->hash = array($this, '_sha1'); - } - - $this->ipad = str_repeat(chr(0x36), $this->b); - $this->opad = str_repeat(chr(0x5C), $this->b); + $this->hash = $hash; } /** @@ -297,45 +184,13 @@ class Hash */ function hash($text) { - $mode = is_array($this->hash) ? self::MODE_INTERNAL : CRYPT_HASH_MODE; + $output = !empty($this->key) || is_string($this->key) ? + hash_hmac($this->hash, $text, $this->key, true) : + hash($this->hash, $text, true); - if (!empty($this->key) || is_string($this->key)) { - switch ($mode) { - case self::MODE_MHASH: - $output = mhash($this->hash, $text, $this->key); - break; - case self::MODE_HASH: - $output = hash_hmac($this->hash, $text, $this->key, true); - break; - case self::MODE_INTERNAL: - /* "Applications that use keys longer than B bytes will first hash the key using H and then use the - resultant L byte string as the actual key to HMAC." - - -- http://tools.ietf.org/html/rfc2104#section-2 */ - $key = strlen($this->key) > $this->b ? call_user_func($this->hash, $this->key) : $this->key; - - $key = str_pad($key, $this->b, chr(0)); // step 1 - $temp = $this->ipad ^ $key; // step 2 - $temp .= $text; // step 3 - $temp = call_user_func($this->hash, $temp); // step 4 - $output = $this->opad ^ $key; // step 5 - $output.= $temp; // step 6 - $output = call_user_func($this->hash, $output); // step 7 - } - } else { - switch ($mode) { - case self::MODE_MHASH: - $output = mhash($this->hash, $text); - break; - case self::MODE_HASH: - $output = hash($this->hash, $text, true); - break; - case self::MODE_INTERNAL: - $output = call_user_func($this->hash, $text); - } - } - - return substr($output, 0, $this->l); + return strlen($output) > $this->length + ? substr($output, 0, $this->length) + : $output; } /** @@ -346,480 +201,6 @@ class Hash */ function getLength() { - return $this->l; - } - - /** - * Wrapper for MD5 - * - * @access private - * @param string $m - */ - function _md5($m) - { - return pack('H*', md5($m)); - } - - /** - * Wrapper for SHA1 - * - * @access private - * @param string $m - */ - function _sha1($m) - { - return pack('H*', sha1($m)); - } - - /** - * Pure-PHP implementation of MD2 - * - * See {@link http://tools.ietf.org/html/rfc1319 RFC1319}. - * - * @access private - * @param string $m - */ - function _md2($m) - { - static $s = array( - 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, - 19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, - 76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, - 138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251, - 245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63, - 148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50, - 39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165, - 181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210, - 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157, - 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27, - 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15, - 85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197, - 234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65, - 129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123, - 8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233, - 203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228, - 166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237, - 31, 26, 219, 153, 141, 51, 159, 17, 131, 20 - ); - - // Step 1. Append Padding Bytes - $pad = 16 - (strlen($m) & 0xF); - $m.= str_repeat(chr($pad), $pad); - - $length = strlen($m); - - // Step 2. Append Checksum - $c = str_repeat(chr(0), 16); - $l = chr(0); - for ($i = 0; $i < $length; $i+= 16) { - for ($j = 0; $j < 16; $j++) { - // RFC1319 incorrectly states that C[j] should be set to S[c xor L] - //$c[$j] = chr($s[ord($m[$i + $j] ^ $l)]); - // per , however, C[j] should be set to S[c xor L] xor C[j] - $c[$j] = chr($s[ord($m[$i + $j] ^ $l)] ^ ord($c[$j])); - $l = $c[$j]; - } - } - $m.= $c; - - $length+= 16; - - // Step 3. Initialize MD Buffer - $x = str_repeat(chr(0), 48); - - // Step 4. Process Message in 16-Byte Blocks - for ($i = 0; $i < $length; $i+= 16) { - for ($j = 0; $j < 16; $j++) { - $x[$j + 16] = $m[$i + $j]; - $x[$j + 32] = $x[$j + 16] ^ $x[$j]; - } - $t = chr(0); - for ($j = 0; $j < 18; $j++) { - for ($k = 0; $k < 48; $k++) { - $x[$k] = $t = $x[$k] ^ chr($s[ord($t)]); - //$t = $x[$k] = $x[$k] ^ chr($s[ord($t)]); - } - $t = chr(ord($t) + $j); - } - } - - // Step 5. Output - return substr($x, 0, 16); - } - - /** - * Pure-PHP implementation of SHA256 - * - * See {@link http://en.wikipedia.org/wiki/SHA_hash_functions#SHA-256_.28a_SHA-2_variant.29_pseudocode SHA-256 (a SHA-2 variant) pseudocode - Wikipedia}. - * - * @access private - * @param string $m - */ - function _sha256($m) - { - if (extension_loaded('suhosin')) { - return pack('H*', sha256($m)); - } - - // Initialize variables - $hash = array( - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 - ); - // Initialize table of round constants - // (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311) - static $k = array( - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 - ); - - // Pre-processing - $length = strlen($m); - // to round to nearest 56 mod 64, we'll add 64 - (length + (64 - 56)) % 64 - $m.= str_repeat(chr(0), 64 - (($length + 8) & 0x3F)); - $m[$length] = chr(0x80); - // we don't support hashing strings 512MB long - $m.= pack('N2', 0, $length << 3); - - // Process the message in successive 512-bit chunks - $chunks = str_split($m, 64); - foreach ($chunks as $chunk) { - $w = array(); - for ($i = 0; $i < 16; $i++) { - extract(unpack('Ntemp', $this->_string_shift($chunk, 4))); - $w[] = $temp; - } - - // Extend the sixteen 32-bit words into sixty-four 32-bit words - for ($i = 16; $i < 64; $i++) { - // @codingStandardsIgnoreStart - $s0 = $this->_rightRotate($w[$i - 15], 7) ^ - $this->_rightRotate($w[$i - 15], 18) ^ - $this->_rightShift( $w[$i - 15], 3); - $s1 = $this->_rightRotate($w[$i - 2], 17) ^ - $this->_rightRotate($w[$i - 2], 19) ^ - $this->_rightShift( $w[$i - 2], 10); - // @codingStandardsIgnoreEnd - $w[$i] = $this->_add($w[$i - 16], $s0, $w[$i - 7], $s1); - - } - - // Initialize hash value for this chunk - list($a, $b, $c, $d, $e, $f, $g, $h) = $hash; - - // Main loop - for ($i = 0; $i < 64; $i++) { - $s0 = $this->_rightRotate($a, 2) ^ - $this->_rightRotate($a, 13) ^ - $this->_rightRotate($a, 22); - $maj = ($a & $b) ^ - ($a & $c) ^ - ($b & $c); - $t2 = $this->_add($s0, $maj); - - $s1 = $this->_rightRotate($e, 6) ^ - $this->_rightRotate($e, 11) ^ - $this->_rightRotate($e, 25); - $ch = ($e & $f) ^ - ($this->_not($e) & $g); - $t1 = $this->_add($h, $s1, $ch, $k[$i], $w[$i]); - - $h = $g; - $g = $f; - $f = $e; - $e = $this->_add($d, $t1); - $d = $c; - $c = $b; - $b = $a; - $a = $this->_add($t1, $t2); - } - - // Add this chunk's hash to result so far - $hash = array( - $this->_add($hash[0], $a), - $this->_add($hash[1], $b), - $this->_add($hash[2], $c), - $this->_add($hash[3], $d), - $this->_add($hash[4], $e), - $this->_add($hash[5], $f), - $this->_add($hash[6], $g), - $this->_add($hash[7], $h) - ); - } - - // Produce the final hash value (big-endian) - return pack('N8', $hash[0], $hash[1], $hash[2], $hash[3], $hash[4], $hash[5], $hash[6], $hash[7]); - } - - /** - * Pure-PHP implementation of SHA384 and SHA512 - * - * @access private - * @param string $m - */ - function _sha512($m) - { - static $init384, $init512, $k; - - if (!isset($k)) { - // Initialize variables - $init384 = array( // initial values for SHA384 - 'cbbb9d5dc1059ed8', '629a292a367cd507', '9159015a3070dd17', '152fecd8f70e5939', - '67332667ffc00b31', '8eb44a8768581511', 'db0c2e0d64f98fa7', '47b5481dbefa4fa4' - ); - $init512 = array( // initial values for SHA512 - '6a09e667f3bcc908', 'bb67ae8584caa73b', '3c6ef372fe94f82b', 'a54ff53a5f1d36f1', - '510e527fade682d1', '9b05688c2b3e6c1f', '1f83d9abfb41bd6b', '5be0cd19137e2179' - ); - - for ($i = 0; $i < 8; $i++) { - $init384[$i] = new BigInteger($init384[$i], 16); - $init384[$i]->setPrecision(64); - $init512[$i] = new BigInteger($init512[$i], 16); - $init512[$i]->setPrecision(64); - } - - // Initialize table of round constants - // (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409) - $k = array( - '428a2f98d728ae22', '7137449123ef65cd', 'b5c0fbcfec4d3b2f', 'e9b5dba58189dbbc', - '3956c25bf348b538', '59f111f1b605d019', '923f82a4af194f9b', 'ab1c5ed5da6d8118', - 'd807aa98a3030242', '12835b0145706fbe', '243185be4ee4b28c', '550c7dc3d5ffb4e2', - '72be5d74f27b896f', '80deb1fe3b1696b1', '9bdc06a725c71235', 'c19bf174cf692694', - 'e49b69c19ef14ad2', 'efbe4786384f25e3', '0fc19dc68b8cd5b5', '240ca1cc77ac9c65', - '2de92c6f592b0275', '4a7484aa6ea6e483', '5cb0a9dcbd41fbd4', '76f988da831153b5', - '983e5152ee66dfab', 'a831c66d2db43210', 'b00327c898fb213f', 'bf597fc7beef0ee4', - 'c6e00bf33da88fc2', 'd5a79147930aa725', '06ca6351e003826f', '142929670a0e6e70', - '27b70a8546d22ffc', '2e1b21385c26c926', '4d2c6dfc5ac42aed', '53380d139d95b3df', - '650a73548baf63de', '766a0abb3c77b2a8', '81c2c92e47edaee6', '92722c851482353b', - 'a2bfe8a14cf10364', 'a81a664bbc423001', 'c24b8b70d0f89791', 'c76c51a30654be30', - 'd192e819d6ef5218', 'd69906245565a910', 'f40e35855771202a', '106aa07032bbd1b8', - '19a4c116b8d2d0c8', '1e376c085141ab53', '2748774cdf8eeb99', '34b0bcb5e19b48a8', - '391c0cb3c5c95a63', '4ed8aa4ae3418acb', '5b9cca4f7763e373', '682e6ff3d6b2b8a3', - '748f82ee5defb2fc', '78a5636f43172f60', '84c87814a1f0ab72', '8cc702081a6439ec', - '90befffa23631e28', 'a4506cebde82bde9', 'bef9a3f7b2c67915', 'c67178f2e372532b', - 'ca273eceea26619c', 'd186b8c721c0c207', 'eada7dd6cde0eb1e', 'f57d4f7fee6ed178', - '06f067aa72176fba', '0a637dc5a2c898a6', '113f9804bef90dae', '1b710b35131c471b', - '28db77f523047d84', '32caab7b40c72493', '3c9ebe0a15c9bebc', '431d67c49c100d4c', - '4cc5d4becb3e42b6', '597f299cfc657e2a', '5fcb6fab3ad6faec', '6c44198c4a475817' - ); - - for ($i = 0; $i < 80; $i++) { - $k[$i] = new BigInteger($k[$i], 16); - } - } - - $hash = $this->l == 48 ? $init384 : $init512; - - // Pre-processing - $length = strlen($m); - // to round to nearest 112 mod 128, we'll add 128 - (length + (128 - 112)) % 128 - $m.= str_repeat(chr(0), 128 - (($length + 16) & 0x7F)); - $m[$length] = chr(0x80); - // we don't support hashing strings 512MB long - $m.= pack('N4', 0, 0, 0, $length << 3); - - // Process the message in successive 1024-bit chunks - $chunks = str_split($m, 128); - foreach ($chunks as $chunk) { - $w = array(); - for ($i = 0; $i < 16; $i++) { - $temp = new BigInteger($this->_string_shift($chunk, 8), 256); - $temp->setPrecision(64); - $w[] = $temp; - } - - // Extend the sixteen 32-bit words into eighty 32-bit words - for ($i = 16; $i < 80; $i++) { - $temp = array( - $w[$i - 15]->bitwise_rightRotate(1), - $w[$i - 15]->bitwise_rightRotate(8), - $w[$i - 15]->bitwise_rightShift(7) - ); - $s0 = $temp[0]->bitwise_xor($temp[1]); - $s0 = $s0->bitwise_xor($temp[2]); - $temp = array( - $w[$i - 2]->bitwise_rightRotate(19), - $w[$i - 2]->bitwise_rightRotate(61), - $w[$i - 2]->bitwise_rightShift(6) - ); - $s1 = $temp[0]->bitwise_xor($temp[1]); - $s1 = $s1->bitwise_xor($temp[2]); - $w[$i] = $w[$i - 16]->copy(); - $w[$i] = $w[$i]->add($s0); - $w[$i] = $w[$i]->add($w[$i - 7]); - $w[$i] = $w[$i]->add($s1); - } - - // Initialize hash value for this chunk - $a = $hash[0]->copy(); - $b = $hash[1]->copy(); - $c = $hash[2]->copy(); - $d = $hash[3]->copy(); - $e = $hash[4]->copy(); - $f = $hash[5]->copy(); - $g = $hash[6]->copy(); - $h = $hash[7]->copy(); - - // Main loop - for ($i = 0; $i < 80; $i++) { - $temp = array( - $a->bitwise_rightRotate(28), - $a->bitwise_rightRotate(34), - $a->bitwise_rightRotate(39) - ); - $s0 = $temp[0]->bitwise_xor($temp[1]); - $s0 = $s0->bitwise_xor($temp[2]); - $temp = array( - $a->bitwise_and($b), - $a->bitwise_and($c), - $b->bitwise_and($c) - ); - $maj = $temp[0]->bitwise_xor($temp[1]); - $maj = $maj->bitwise_xor($temp[2]); - $t2 = $s0->add($maj); - - $temp = array( - $e->bitwise_rightRotate(14), - $e->bitwise_rightRotate(18), - $e->bitwise_rightRotate(41) - ); - $s1 = $temp[0]->bitwise_xor($temp[1]); - $s1 = $s1->bitwise_xor($temp[2]); - $temp = array( - $e->bitwise_and($f), - $g->bitwise_and($e->bitwise_not()) - ); - $ch = $temp[0]->bitwise_xor($temp[1]); - $t1 = $h->add($s1); - $t1 = $t1->add($ch); - $t1 = $t1->add($k[$i]); - $t1 = $t1->add($w[$i]); - - $h = $g->copy(); - $g = $f->copy(); - $f = $e->copy(); - $e = $d->add($t1); - $d = $c->copy(); - $c = $b->copy(); - $b = $a->copy(); - $a = $t1->add($t2); - } - - // Add this chunk's hash to result so far - $hash = array( - $hash[0]->add($a), - $hash[1]->add($b), - $hash[2]->add($c), - $hash[3]->add($d), - $hash[4]->add($e), - $hash[5]->add($f), - $hash[6]->add($g), - $hash[7]->add($h) - ); - } - - // Produce the final hash value (big-endian) - // (\phpseclib\Crypt\Hash::hash() trims the output for hashes but not for HMACs. as such, we trim the output here) - $temp = $hash[0]->toBytes() . $hash[1]->toBytes() . $hash[2]->toBytes() . $hash[3]->toBytes() . - $hash[4]->toBytes() . $hash[5]->toBytes(); - if ($this->l != 48) { - $temp.= $hash[6]->toBytes() . $hash[7]->toBytes(); - } - - return $temp; - } - - /** - * Right Rotate - * - * @access private - * @param int $int - * @param int $amt - * @see _sha256() - * @return int - */ - function _rightRotate($int, $amt) - { - $invamt = 32 - $amt; - $mask = (1 << $invamt) - 1; - return (($int << $invamt) & 0xFFFFFFFF) | (($int >> $amt) & $mask); - } - - /** - * Right Shift - * - * @access private - * @param int $int - * @param int $amt - * @see _sha256() - * @return int - */ - function _rightShift($int, $amt) - { - $mask = (1 << (32 - $amt)) - 1; - return ($int >> $amt) & $mask; - } - - /** - * Not - * - * @access private - * @param int $int - * @see _sha256() - * @return int - */ - function _not($int) - { - return ~$int & 0xFFFFFFFF; - } - - /** - * Add - * - * _sha256() adds multiple unsigned 32-bit integers. Since PHP doesn't support unsigned integers and since the - * possibility of overflow exists, care has to be taken. BigInteger could be used but this should be faster. - * - * @param int $... - * @return int - * @see _sha256() - * @access private - */ - function _add() - { - static $mod; - if (!isset($mod)) { - $mod = pow(2, 32); - } - - $result = 0; - $arguments = func_get_args(); - foreach ($arguments as $argument) { - $result+= $argument < 0 ? ($argument & 0x7FFFFFFF) + 0x80000000 : $argument; - } - - return fmod($result, $mod); - } - - /** - * String Shift - * - * Inspired by array_shift - * - * @param string $string - * @param int $index - * @return string - * @access private - */ - function _string_shift(&$string, $index = 1) - { - $substr = substr($string, 0, $index); - $string = substr($string, $index); - return $substr; + return $this->length; } } diff --git a/phpseclib/Crypt/RSA.php b/phpseclib/Crypt/RSA.php index 254b7351..558cd5fa 100644 --- a/phpseclib/Crypt/RSA.php +++ b/phpseclib/Crypt/RSA.php @@ -29,11 +29,9 @@ * * $plaintext = 'terrafrost'; * - * $rsa->loadKey($privatekey); - * $signature = $rsa->sign($plaintext); + * $signature = $privatekey->sign($plaintext); * - * $rsa->loadKey($publickey); - * echo $rsa->verify($plaintext, $signature) ? 'verified' : 'unverified'; + * echo $publickey->verify($plaintext, $signature) ? 'verified' : 'unverified'; * ?> * * @@ -351,6 +349,20 @@ class RSA } } + /** + * Initialize static variables + * + * @access private + */ + static function _initialize_static_variables() + { + if (!isset(self::$zero)) { + self::$zero= new BigInteger(0); + self::$one = new BigInteger(1); + self::$configFile = __DIR__ . '/../openssl.cnf'; + } + } + /** * The constructor * @@ -664,7 +676,6 @@ class RSA $this->encryptionMode = $key->encryptionMode; $this->signatureMode = $key->signatureMode; $this->password = $key->password; - $this->configFile = $key->configFile; $this->comment = $key->comment; if (is_object($key->hash)) { diff --git a/phpseclib/Crypt/Random.php b/phpseclib/Crypt/Random.php index 0bee3c45..4ce919f7 100644 --- a/phpseclib/Crypt/Random.php +++ b/phpseclib/Crypt/Random.php @@ -49,6 +49,7 @@ class Random * eg. for RSA key generation. * * @param int $length + * @throws \RuntimeException if a symmetric cipher is needed but not loaded * @return string */ static function string($length) @@ -211,8 +212,7 @@ class Random $crypto = new RC4(); break; default: - user_error(__CLASS__ . ' requires at least one symmetric cipher be loaded'); - return false; + throw new \RuntimeException(__CLASS__ . ' requires at least one symmetric cipher be loaded'); } $crypto->setKey($key); diff --git a/phpseclib/Exception/BadConfigurationException.php b/phpseclib/Exception/BadConfigurationException.php new file mode 100644 index 00000000..096148a0 --- /dev/null +++ b/phpseclib/Exception/BadConfigurationException.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; + +/** + * BadConfigurationException + * + * @package BadConfigurationException + * @author Jim Wigginton + */ +class BadConfigurationException extends \RuntimeException +{ +} diff --git a/phpseclib/Exception/FileNotFoundException.php b/phpseclib/Exception/FileNotFoundException.php new file mode 100644 index 00000000..984edfcc --- /dev/null +++ b/phpseclib/Exception/FileNotFoundException.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; + +/** + * FileNotFoundException + * + * @package FileNotFoundException + * @author Jim Wigginton + */ +class FileNotFoundException extends \RuntimeException +{ +} diff --git a/phpseclib/Exception/NoSupportedAlgorithmsException.php b/phpseclib/Exception/NoSupportedAlgorithmsException.php new file mode 100644 index 00000000..bca9a753 --- /dev/null +++ b/phpseclib/Exception/NoSupportedAlgorithmsException.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; + +/** + * NoSupportedAlgorithmsException + * + * @package NoSupportedAlgorithmsException + * @author Jim Wigginton + */ +class NoSupportedAlgorithmsException extends \RuntimeException +{ +} diff --git a/phpseclib/Exception/UnsupportedAlgorithmException.php b/phpseclib/Exception/UnsupportedAlgorithmException.php new file mode 100644 index 00000000..47cc41d4 --- /dev/null +++ b/phpseclib/Exception/UnsupportedAlgorithmException.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; + +/** + * UnsupportedAlgorithmException + * + * @package UnsupportedAlgorithmException + * @author Jim Wigginton + */ +class UnsupportedAlgorithmException extends \RuntimeException +{ +} diff --git a/phpseclib/File/ASN1.php b/phpseclib/File/ASN1.php index b71469b4..cd17174b 100644 --- a/phpseclib/File/ASN1.php +++ b/phpseclib/File/ASN1.php @@ -793,6 +793,7 @@ class ASN1 * @param string $mapping * @param int $idx * @return string + * @throws \RuntimeException if the input has an error in it * @access private */ function _encode_der($source, $mapping, $idx = null, $special = array()) @@ -985,7 +986,7 @@ class ASN1 case self::TYPE_OBJECT_IDENTIFIER: $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids); if ($oid === false) { - user_error('Invalid OID'); + throw new \RuntimeException('Invalid OID'); return false; } $value = ''; @@ -1038,7 +1039,7 @@ class ASN1 $filters = $filters[$part]; } if ($filters === false) { - user_error('No filters defined for ' . implode('/', $loc)); + throw new \RuntimeException('No filters defined for ' . implode('/', $loc)); return false; } return $this->_encode_der($source, $filters + $mapping, null, $special); @@ -1062,7 +1063,7 @@ class ASN1 $value = $source ? "\xFF" : "\x00"; break; default: - user_error('Mapping provides no type definition for ' . implode('/', $this->location)); + throw new \RuntimeException('Mapping provides no type definition for ' . implode('/', $this->location)); return false; } diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index 55d25706..2ee9300d 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -32,6 +32,7 @@ use phpseclib\Crypt\Random; use phpseclib\File\ASN1; use phpseclib\File\ASN1\Element; use phpseclib\Math\BigInteger; +use phpseclib\Exception\UnsupportedAlgorithmException; /** * Pure-PHP X.509 Parser @@ -1641,7 +1642,7 @@ class X509 $map = $this->_getMapping($id); if (is_bool($map)) { if (!$map) { - user_error($id . ' is not a currently supported extension'); + //user_error($id . ' is not a currently supported extension'); unset($extensions[$i]); } } else { @@ -1714,7 +1715,7 @@ class X509 $id = $attributes[$i]['type']; $map = $this->_getMapping($id); if ($map === false) { - user_error($id . ' is not a currently supported attribute', E_USER_NOTICE); + //user_error($id . ' is not a currently supported attribute', E_USER_NOTICE); unset($attributes[$i]); } elseif (is_array($attributes[$i]['value'])) { $values = &$attributes[$i]['value']; @@ -2107,7 +2108,8 @@ class X509 /** * Validates a signature * - * Returns true if the signature is verified, false if it is not correct or null on error + * Returns true if the signature is verified and false if it is not correct. + * If the algorithms are unsupposed an exception is thrown. * * @param string $publicKeyAlgorithm * @param string $publicKey @@ -2115,7 +2117,8 @@ class X509 * @param string $signature * @param string $signatureSubject * @access private - * @return int + * @throws \phpseclib\Exception\UnsupportedAlgorithmException if the algorithm is unsupported + * @return bool */ function _validateSignature($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject) { @@ -2139,11 +2142,11 @@ class X509 } break; default: - return null; + throw new UnsupportedAlgorithmException('Signature algorithm unsupported'); } break; default: - return null; + throw new UnsupportedAlgorithmException('Public key algorithm unsupported'); } return true; @@ -3628,6 +3631,7 @@ class X509 * @param \phpseclib\File\X509 $subject * @param string $signatureAlgorithm * @access public + * @throws \phpseclib\Exception\UnsupportedAlgorithmException if the algorithm is unsupported * @return mixed */ function _sign($key, $signatureAlgorithm) @@ -3646,10 +3650,12 @@ class X509 $this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject)); return $this->currentCert; + default: + throw new UnsupportedAlgorithmException('Signature algorithm unsupported'); } } - return false; + throw new UnsupportedAlgorithmException('Unsupported public key algorithm'); } /** diff --git a/phpseclib/Net/SCP.php b/phpseclib/Net/SCP.php index 4db28857..a2818970 100644 --- a/phpseclib/Net/SCP.php +++ b/phpseclib/Net/SCP.php @@ -34,6 +34,7 @@ namespace phpseclib\Net; use phpseclib\Net\SSH1; use phpseclib\Net\SSH2; +use phpseclib\Exception\FileNotFoundException; /** * Pure-PHP implementations of SCP. @@ -140,6 +141,7 @@ class SCP * @param string $data * @param int $mode * @param callable $callback + * @throws \phpseclib\Exception\FileNotFoundException if you're uploading via a file and the file doesn't exist * @return bool * @access public */ @@ -168,8 +170,7 @@ class SCP $size = strlen($data); } else { if (!is_file($data)) { - user_error("$data is not a valid file", E_USER_NOTICE); - return false; + throw new FileNotFoundException("$data is not a valid file"); } $fp = @fopen($data, 'rb'); @@ -289,6 +290,7 @@ class SCP * Receives a packet from an SSH server * * @return string + * @throws \UnexpectedValueException on receipt of an unexpected packet * @access private */ function _receive() @@ -314,8 +316,7 @@ class SCP $this->ssh->bitmap = 0; return false; default: - user_error('Unknown packet received', E_USER_NOTICE); - return false; + throw new \UnexpectedValueException('Unknown packet received'); } } } diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index e139be53..eba6fc03 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -38,6 +38,7 @@ namespace phpseclib\Net; use phpseclib\Net\SSH2; +use phpseclib\Exception\FileNotFoundException; /** * Pure-PHP implementations of SFTP. @@ -383,6 +384,7 @@ class SFTP extends SSH2 * * @param string $username * @param string $password + * @throws \UnexpectedValueException on receipt of unexpected packets * @return bool * @access public */ @@ -470,8 +472,7 @@ class SFTP extends SSH2 $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_VERSION) { - user_error('Expected SSH_FXP_VERSION'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_VERSION'); } extract(unpack('Nversion', $this->_string_shift($response, 4))); @@ -610,6 +611,7 @@ class SFTP extends SSH2 * * @see \phpseclib\Net\SFTP::chdir() * @param string $path + * @throws \UnexpectedValueException on receipt of unexpected packets * @return mixed * @access private */ @@ -634,8 +636,7 @@ class SFTP extends SSH2 $this->_logError($response); return false; default: - user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_NAME or SSH_FXP_STATUS'); } } @@ -666,6 +667,7 @@ class SFTP extends SSH2 * Changes the current directory * * @param string $dir + * @throws \UnexpectedValueException on receipt of unexpected packets * @return bool * @access public */ @@ -710,8 +712,7 @@ class SFTP extends SSH2 $this->_logError($response); return false; default: - user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); } if (!$this->_close_handle($handle)) { @@ -813,6 +814,7 @@ class SFTP extends SSH2 * @param string $dir * @param bool $raw * @return mixed + * @throws \UnexpectedValueException on receipt of unexpected packets * @access private */ function _list($dir, $raw = true) @@ -844,8 +846,7 @@ class SFTP extends SSH2 $this->_logError($response); return false; default: - user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); } $this->_update_stat_cache($dir, array()); @@ -899,8 +900,7 @@ class SFTP extends SSH2 } break 2; default: - user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_NAME or SSH_FXP_STATUS'); } } @@ -1259,6 +1259,7 @@ class SFTP extends SSH2 * * @param string $filename * @param int $type + * @throws \UnexpectedValueException on receipt of unexpected packets * @return mixed * @access private */ @@ -1279,8 +1280,7 @@ class SFTP extends SSH2 return false; } - user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS'); } /** @@ -1306,6 +1306,7 @@ class SFTP extends SSH2 * @param string $filename * @param int $time * @param int $atime + * @throws \UnexpectedValueException on receipt of unexpected packets * @return bool * @access public */ @@ -1342,8 +1343,7 @@ class SFTP extends SSH2 $this->_logError($response); break; default: - user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); } return $this->_setstat($filename, $attr, false); @@ -1396,6 +1396,7 @@ class SFTP extends SSH2 * @param int $mode * @param string $filename * @param bool $recursive + * @throws \UnexpectedValueException on receipt of unexpected packets * @return mixed * @access public */ @@ -1433,8 +1434,7 @@ class SFTP extends SSH2 return false; } - user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS'); } /** @@ -1443,6 +1443,7 @@ class SFTP extends SSH2 * @param string $filename * @param string $attr * @param bool $recursive + * @throws \UnexpectedValueException on receipt of unexpected packets * @return bool * @access private */ @@ -1481,8 +1482,7 @@ class SFTP extends SSH2 */ $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_STATUS'); } extract(unpack('Nstatus', $this->_string_shift($response, 4))); @@ -1570,6 +1570,7 @@ class SFTP extends SSH2 * Return the target of a symbolic link * * @param string $link + * @throws \UnexpectedValueException on receipt of unexpected packets * @return mixed * @access public */ @@ -1593,8 +1594,7 @@ class SFTP extends SSH2 $this->_logError($response); return false; default: - user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_NAME or SSH_FXP_STATUS'); } extract(unpack('Ncount', $this->_string_shift($response, 4))); @@ -1614,6 +1614,7 @@ class SFTP extends SSH2 * * @param string $target * @param string $link + * @throws \UnexpectedValueException on receipt of unexpected packets * @return bool * @access public */ @@ -1633,8 +1634,7 @@ class SFTP extends SSH2 $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_STATUS'); } extract(unpack('Nstatus', $this->_string_shift($response, 4))); @@ -1686,6 +1686,7 @@ class SFTP extends SSH2 * * @param string $dir * @return bool + * @throws \UnexpectedValueException on receipt of unexpected packets * @access private */ function _mkdir_helper($dir, $attr) @@ -1696,8 +1697,7 @@ class SFTP extends SSH2 $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_STATUS'); } extract(unpack('Nstatus', $this->_string_shift($response, 4))); @@ -1713,6 +1713,7 @@ class SFTP extends SSH2 * Removes a directory. * * @param string $dir + * @throws \UnexpectedValueException on receipt of unexpected packets * @return bool * @access public */ @@ -1733,8 +1734,7 @@ class SFTP extends SSH2 $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_STATUS'); } extract(unpack('Nstatus', $this->_string_shift($response, 4))); @@ -1794,6 +1794,9 @@ class SFTP extends SSH2 * @param int $start * @param int $local_start * @param callable|null $progressCallback + * @throws \UnexpectedValueException on receipt of unexpected packets + * @throws \BadFunctionCallException if you're uploading via a callback and the callback function is invalid + * @throws \phpseclib\Exception\FileNotFoundException if you're uploading via a file and the file doesn't exist * @return bool * @access public * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - \phpseclib\Net\SFTP::setMode(). @@ -1841,8 +1844,7 @@ class SFTP extends SSH2 $this->_logError($response); return false; default: - user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); } // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3 @@ -1850,7 +1852,7 @@ class SFTP extends SSH2 switch (true) { case $mode & self::SOURCE_CALLBACK: if (!is_callable($data)) { - user_error("\$data should be is_callable() if you specify SOURCE_CALLBACK flag"); + throw new \BadFunctionCallException("\$data should be is_callable() if you specify SOURCE_CALLBACK flag"); } $dataCallback = $data; // do nothing @@ -1861,8 +1863,7 @@ class SFTP extends SSH2 break; case $mode & self::SOURCE_LOCAL_FILE: if (!is_file($data)) { - user_error("$data is not a valid file"); - return false; + throw new FileNotFoundException("$data is not a valid file"); } $fp = @fopen($data, 'rb'); if (!$fp) { @@ -1876,10 +1877,7 @@ class SFTP extends SSH2 if ($local_start >= 0) { fseek($fp, $local_start); - } elseif ($mode & self::RESUME_START) { - // do nothing - } else { - fseek($fp, $offset); + $size-= $local_start; } } elseif ($dataCallback) { $size = 0; @@ -1950,6 +1948,7 @@ class SFTP extends SSH2 * * @param int $i * @return bool + * @throws \UnexpectedValueException on receipt of unexpected packets * @access private */ function _read_put_responses($i) @@ -1957,8 +1956,7 @@ class SFTP extends SSH2 while ($i--) { $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_STATUS'); } extract(unpack('Nstatus', $this->_string_shift($response, 4))); @@ -1976,6 +1974,7 @@ class SFTP extends SSH2 * * @param string $handle * @return bool + * @throws \UnexpectedValueException on receipt of unexpected packets * @access private */ function _close_handle($handle) @@ -1988,8 +1987,7 @@ class SFTP extends SSH2 // -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3 $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_STATUS'); } extract(unpack('Nstatus', $this->_string_shift($response, 4))); @@ -2014,6 +2012,7 @@ class SFTP extends SSH2 * @param string $local_file * @param int $offset * @param int $length + * @throws \UnexpectedValueException on receipt of unexpected packets * @return mixed * @access public */ @@ -2042,8 +2041,7 @@ class SFTP extends SSH2 $this->_logError($response); return false; default: - user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS'); } if (is_resource($local_file)) { @@ -2091,11 +2089,10 @@ class SFTP extends SSH2 $this->_logError($response); break 2; default: - user_error('Expected SSH_FXP_DATA or SSH_FXP_STATUS'); if ($fclose_check) { fclose($fp); } - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_DATA or SSH_FXP_STATUS'); } if ($length > 0 && $length <= $offset - $start) { @@ -2129,6 +2126,7 @@ class SFTP extends SSH2 * @param string $path * @param bool $recursive * @return bool + * @throws \UnexpectedValueException on receipt of unexpected packets * @access public */ function delete($path, $recursive = true) @@ -2149,8 +2147,7 @@ class SFTP extends SSH2 $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_STATUS'); } // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED @@ -2482,6 +2479,7 @@ class SFTP extends SSH2 * @param string $oldname * @param string $newname * @return bool + * @throws \UnexpectedValueException on receipt of unexpected packets * @access public */ function rename($oldname, $newname) @@ -2504,8 +2502,7 @@ class SFTP extends SSH2 $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { - user_error('Expected SSH_FXP_STATUS'); - return false; + throw new \UnexpectedValueException('Expected SSH_FXP_STATUS'); } // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED diff --git a/phpseclib/Net/SFTP/Stream.php b/phpseclib/Net/SFTP/Stream.php index a62eb4ab..7af7280a 100644 --- a/phpseclib/Net/SFTP/Stream.php +++ b/phpseclib/Net/SFTP/Stream.php @@ -19,6 +19,7 @@ namespace phpseclib\Net\SFTP; use phpseclib\Crypt\RSA; use phpseclib\Net\SFTP; +use phpseclib\Net\SSH2; /** * SFTP Stream Wrapper @@ -166,13 +167,12 @@ class Stream } } - if ($host[0] == '$') { - $host = substr($host, 1); - global $$host; - if (($$host instanceof SFTP) === false) { + if (preg_match('/^{[a-z0-9]+}$/i', $host)) { + $host = SSH2::getConnectionByResourceId($host); + if ($host === false) { return false; } - $this->sftp = $$host; + $this->sftp = $host; } else { if (isset($this->context)) { $context = stream_context_get_options($this->context); diff --git a/phpseclib/Net/SSH1.php b/phpseclib/Net/SSH1.php index 3ddb47b6..2e0a26d0 100644 --- a/phpseclib/Net/SSH1.php +++ b/phpseclib/Net/SSH1.php @@ -537,14 +537,15 @@ class SSH1 * Connect to an SSHv1 server * * @return bool + * @throws \UnexpectedValueException on receipt of unexpected packets + * @throws \RuntimeException on other errors * @access private */ function _connect() { $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->connectionTimeout); if (!$this->fsock) { - user_error(rtrim("Cannot connect to {$this->host}:{$this->port}. Error $errno. $errstr")); - return false; + throw new \RuntimeException(rtrim("Cannot connect to $host. Error $errno. $errstr")); } $this->server_identification = $init_line = fgets($this->fsock, 255); @@ -555,20 +556,17 @@ class SSH1 } if (!preg_match('#SSH-([0-9\.]+)-(.+)#', $init_line, $parts)) { - user_error('Can only connect to SSH servers'); - return false; + throw new \RuntimeException('Can only connect to SSH servers'); } if ($parts[1][0] != 1) { - user_error("Cannot connect to SSH $parts[1] servers"); - return false; + throw new \RuntimeException("Cannot connect to $parts[1] servers"); } fputs($this->fsock, $this->identifier."\r\n"); $response = $this->_get_binary_packet(); if ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_PUBLIC_KEY) { - user_error('Expected SSH_SMSG_PUBLIC_KEY'); - return false; + throw new \UnexpectedValueException('Expected SSH_SMSG_PUBLIC_KEY'); } $anti_spoofing_cookie = $this->_string_shift($response[self::RESPONSE_DATA], 8); @@ -652,8 +650,7 @@ class SSH1 $data = pack('C2a*na*N', NET_SSH1_CMSG_SESSION_KEY, $cipher, $anti_spoofing_cookie, 8 * strlen($double_encrypted_session_key), $double_encrypted_session_key, 0); if (!$this->_send_binary_packet($data)) { - user_error('Error sending SSH_CMSG_SESSION_KEY'); - return false; + throw new \RuntimeException('Error sending SSH_CMSG_SESSION_KEY'); } switch ($cipher) { @@ -682,8 +679,7 @@ class SSH1 $response = $this->_get_binary_packet(); if ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_SUCCESS) { - user_error('Expected SSH_SMSG_SUCCESS'); - return false; + throw new \UnexpectedValueException('Expected SSH_SMSG_SUCCESS'); } $this->bitmap = self::MASK_CONNECTED; @@ -697,6 +693,8 @@ class SSH1 * @param string $username * @param string $password * @return bool + * @throws \UnexpectedValueException on receipt of unexpected packets + * @throws \RuntimeException on other errors * @access public */ function login($username, $password = '') @@ -715,8 +713,7 @@ class SSH1 $data = pack('CNa*', NET_SSH1_CMSG_USER, strlen($username), $username); if (!$this->_send_binary_packet($data)) { - user_error('Error sending SSH_CMSG_USER'); - return false; + throw new \RuntimeException('Error sending SSH_CMSG_USER'); } $response = $this->_get_binary_packet(); @@ -728,15 +725,13 @@ class SSH1 $this->bitmap |= self::MASK_LOGIN; return true; } elseif ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_FAILURE) { - user_error('Expected SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE'); - return false; + throw new \UnexpectedValueException('Expected SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE'); } $data = pack('CNa*', NET_SSH1_CMSG_AUTH_PASSWORD, strlen($password), $password); if (!$this->_send_binary_packet($data)) { - user_error('Error sending SSH_CMSG_AUTH_PASSWORD'); - return false; + throw new \RuntimeException('Error sending SSH_CMSG_AUTH_PASSWORD'); } // remove the username and password from the last logged packet @@ -756,8 +751,7 @@ class SSH1 } elseif ($response[self::RESPONSE_TYPE] == NET_SSH1_SMSG_FAILURE) { return false; } else { - user_error('Expected SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE'); - return false; + throw new \UnexpectedValueException('Expected SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE'); } } @@ -792,20 +786,19 @@ class SSH1 * @see \phpseclib\Net\SSH1::interactiveWrite() * @param string $cmd * @return mixed + * @throws \RuntimeException on error sending command * @access public */ function exec($cmd, $block = true) { if (!($this->bitmap & self::MASK_LOGIN)) { - user_error('Operation disallowed prior to login()'); - return false; + throw new \RuntimeException('Operation disallowed prior to login()'); } $data = pack('CNa*', NET_SSH1_CMSG_EXEC_CMD, strlen($cmd), $cmd); if (!$this->_send_binary_packet($data)) { - user_error('Error sending SSH_CMSG_EXEC_CMD'); - return false; + throw new \RuntimeException('Error sending SSH_CMSG_EXEC_CMD'); } if (!$block) { @@ -841,6 +834,8 @@ class SSH1 * @see \phpseclib\Net\SSH1::interactiveRead() * @see \phpseclib\Net\SSH1::interactiveWrite() * @return bool + * @throws \UnexpectedValueException on receipt of unexpected packets + * @throws \RuntimeException on other errors * @access private */ function _initShell() @@ -851,8 +846,7 @@ class SSH1 $data = pack('CNa*N4C', NET_SSH1_CMSG_REQUEST_PTY, strlen('vt100'), 'vt100', 24, 80, 0, 0, self::TTY_OP_END); if (!$this->_send_binary_packet($data)) { - user_error('Error sending SSH_CMSG_REQUEST_PTY'); - return false; + throw new \RuntimeException('Error sending SSH_CMSG_REQUEST_PTY'); } $response = $this->_get_binary_packet(); @@ -861,15 +855,13 @@ class SSH1 return false; } if ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_SUCCESS) { - user_error('Expected SSH_SMSG_SUCCESS'); - return false; + throw new \UnexpectedValueException('Expected SSH_SMSG_SUCCESS'); } $data = pack('C', NET_SSH1_CMSG_EXEC_SHELL); if (!$this->_send_binary_packet($data)) { - user_error('Error sending SSH_CMSG_EXEC_SHELL'); - return false; + throw new \RuntimeException('Error sending SSH_CMSG_EXEC_SHELL'); } $this->bitmap |= self::MASK_SHELL; @@ -902,18 +894,17 @@ class SSH1 * @param string $expect * @param int $mode * @return bool + * @throws \RuntimeException on connection error * @access public */ function read($expect, $mode = self::READ__SIMPLE) { if (!($this->bitmap & self::MASK_LOGIN)) { - user_error('Operation disallowed prior to login()'); - return false; + throw new \RuntimeException('Operation disallowed prior to login()'); } if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) { - user_error('Unable to initiate an interactive shell session'); - return false; + throw new \RuntimeException('Unable to initiate an interactive shell session'); } $match = $expect; @@ -941,25 +932,23 @@ class SSH1 * @see \phpseclib\Net\SSH1::interactiveRead() * @param string $cmd * @return bool + * @throws \RuntimeException on connection error * @access public */ function interactiveWrite($cmd) { if (!($this->bitmap & self::MASK_LOGIN)) { - user_error('Operation disallowed prior to login()'); - return false; + throw new \RuntimeException('Operation disallowed prior to login()'); } if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) { - user_error('Unable to initiate an interactive shell session'); - return false; + throw new \RuntimeException('Unable to initiate an interactive shell session'); } $data = pack('CNa*', NET_SSH1_CMSG_STDIN_DATA, strlen($cmd), $cmd); if (!$this->_send_binary_packet($data)) { - user_error('Error sending SSH_CMSG_STDIN'); - return false; + throw new \RuntimeException('Error sending SSH_CMSG_STDIN'); } return true; @@ -976,18 +965,17 @@ class SSH1 * * @see \phpseclib\Net\SSH1::interactiveRead() * @return string + * @throws \RuntimeException on connection error * @access public */ function interactiveRead() { if (!($this->bitmap & self::MASK_LOGIN)) { - user_error('Operation disallowed prior to login()'); - return false; + throw new \RuntimeException('Operation disallowed prior to login()'); } if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) { - user_error('Unable to initiate an interactive shell session'); - return false; + throw new \RuntimeException('Unable to initiate an interactive shell session'); } $read = array($this->fsock); @@ -1313,7 +1301,7 @@ class SSH1 { /* $rsa = new RSA(); - $rsa->loadKey($key, RSA::PUBLIC_FORMAT_RAW); + $rsa->load($key, RSA::PUBLIC_FORMAT_RAW); $rsa->setEncryptionMode(RSA::ENCRYPTION_PKCS1); return $rsa->encrypt($m); */ diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index a779dc7c..c084c8a3 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -26,7 +26,7 @@ * * $key = new \phpseclib\Crypt\RSA(); * //$key->setPassword('whatever'); - * $key->loadKey(file_get_contents('privatekey')); + * $key->load(file_get_contents('privatekey')); * * $ssh = new \phpseclib\Net\SSH2('www.domain.tld'); * if (!$ssh->login('username', $key)) { @@ -60,6 +60,7 @@ use phpseclib\Crypt\TripleDES; use phpseclib\Crypt\Twofish; use phpseclib\Math\BigInteger; // Used to do Diffie-Hellman key exchange and DSA/RSA signature verification. use phpseclib\System\SSH\Agent; +use phpseclib\Exception\NoSupportedAlgorithmsException; /** * Pure-PHP implementation of SSHv2. @@ -866,6 +867,14 @@ class SSH2 */ var $agent; + /** + * Connection storage to replicates ssh2 extension functionality: + * {@link http://php.net/manual/en/wrappers.ssh2.php#refsect1-wrappers.ssh2-examples} + * + * @var SSH2[] + */ + static $connections; + /** * Default Constructor. * @@ -959,6 +968,8 @@ class SSH2 31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY') ); + self::$connections[$this->getResourceId()] = $this; + if (is_resource($host)) { $this->fsock = $host; return; @@ -989,6 +1000,8 @@ class SSH2 * Connect to an SSHv2 server * * @return bool + * @throws \UnexpectedValueException on receipt of unexpected packets + * @throws \RuntimeException on other errors * @access private */ function _connect() @@ -1008,8 +1021,7 @@ class SSH2 $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->curTimeout); if (!$this->fsock) { $host = $this->host . ':' . $this->port; - user_error(rtrim("Cannot connect to $host. Error $errno. $errstr")); - return false; + throw new \RuntimeException(rtrim("Cannot connect to $host. Error $errno. $errstr")); } $elapsed = microtime(true) - $start; @@ -1060,8 +1072,7 @@ class SSH2 } if (feof($this->fsock)) { - user_error('Connection closed by server'); - return false; + throw new \RuntimeException('Connection closed by server'); } $this->identifier = $this->_generate_identifier(); @@ -1077,21 +1088,18 @@ class SSH2 } if ($matches[1] != '1.99' && $matches[1] != '2.0') { - user_error("Cannot connect to SSH $matches[1] servers"); - return false; + throw new \RuntimeException("Cannot connect to SSH $matches[1] servers"); } fputs($this->fsock, $this->identifier . "\r\n"); $response = $this->_get_binary_packet(); if ($response === false) { - user_error('Connection closed by server'); - return false; + throw new \RuntimeException('Connection closed by server'); } if (ord($response[0]) != NET_SSH2_MSG_KEXINIT) { - user_error('Expected SSH_MSG_KEXINIT'); - return false; + throw new \UnexpectedValueException('Expected SSH_MSG_KEXINIT'); } if (!$this->_key_exchange($response)) { @@ -1143,6 +1151,9 @@ class SSH2 * Key Exchange * * @param string $kexinit_payload_server + * @throws \UnexpectedValueException on receipt of unexpected packets + * @throws \RuntimeException on other errors + * @throws \phpseclib\Exception\NoSupportedAlgorithmsException when none of the algorithms phpseclib has loaded are compatible * @access private */ function _key_exchange($kexinit_payload_server) @@ -1354,27 +1365,28 @@ class SSH2 // here ends the second place. // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange + // we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the // diffie-hellman key exchange as fast as possible $decrypt = $this->_array_intersect_first($encryption_algorithms, $this->encryption_algorithms_server_to_client); $decryptKeyLength = $this->_encryption_algorithm_to_key_size($decrypt); if ($decryptKeyLength === null) { - user_error('No compatible server to client encryption algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible server to client encryption algorithms found'); } $encrypt = $this->_array_intersect_first($encryption_algorithms, $this->encryption_algorithms_client_to_server); $encryptKeyLength = $this->_encryption_algorithm_to_key_size($encrypt); if ($encryptKeyLength === null) { - user_error('No compatible client to server encryption algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible client to server encryption algorithms found'); } // through diffie-hellman key exchange a symmetric key is obtained $kex_algorithm = $this->_array_intersect_first($kex_algorithms, $this->kex_algorithms); if ($kex_algorithm === false) { - user_error('No compatible key exchange algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible key exchange algorithms found'); } // Only relevant in diffie-hellman-group-exchange-sha{1,256}, otherwise empty. @@ -1491,20 +1503,17 @@ class SSH2 $data = pack('CNa*', $clientKexInitMessage, strlen($eBytes), $eBytes); if (!$this->_send_binary_packet($data)) { - user_error('Connection closed by server'); - return false; + throw new \RuntimeException('Connection closed by server'); } $response = $this->_get_binary_packet(); if ($response === false) { - user_error('Connection closed by server'); - return false; + throw new \RuntimeException('Connection closed by server'); } extract(unpack('Ctype', $this->_string_shift($response, 1))); if ($type != $serverKexReplyMessage) { - user_error('Expected SSH_MSG_KEXDH_REPLY'); - return false; + throw new \UnexpectedValueException('Expected SSH_MSG_KEXDH_REPLY'); } $temp = unpack('Nlength', $this->_string_shift($response, 4)); @@ -1564,13 +1573,13 @@ class SSH2 $server_host_key_algorithm = $this->_array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms); if ($server_host_key_algorithm === false) { - user_error('No compatible server host key algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible server host key algorithms found'); } if ($public_key_format != $server_host_key_algorithm || $this->signature_format != $server_host_key_algorithm) { - user_error('Server Host Key Algorithm Mismatch'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new \RuntimeException('Server Host Key Algorithm Mismatch'); } $packet = pack( @@ -1585,15 +1594,13 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { - user_error('Connection closed by server'); - return false; + throw new \RuntimeException('Connection closed by server'); } extract(unpack('Ctype', $this->_string_shift($response, 1))); if ($type != NET_SSH2_MSG_NEWKEYS) { - user_error('Expected SSH_MSG_NEWKEYS'); - return false; + throw new \UnexpectedValueException('Expected SSH_MSG_NEWKEYS'); } $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes); @@ -1662,8 +1669,8 @@ class SSH2 $mac_algorithm = $this->_array_intersect_first($mac_algorithms, $this->mac_algorithms_client_to_server); if ($mac_algorithm === false) { - user_error('No compatible client to server message authentication algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible client to server message authentication algorithms found'); } $createKeyLength = 0; // ie. $mac_algorithm == 'none' @@ -1691,8 +1698,8 @@ class SSH2 $mac_algorithm = $this->_array_intersect_first($mac_algorithms, $this->mac_algorithms_server_to_client); if ($mac_algorithm === false) { - user_error('No compatible server to client message authentication algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible server to client message authentication algorithms found'); } $checkKeyLength = 0; @@ -1738,15 +1745,15 @@ class SSH2 $compression_algorithm = $this->_array_intersect_first($compression_algorithms, $this->compression_algorithms_server_to_client); if ($compression_algorithm === false) { - user_error('No compatible server to client compression algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible server to client compression algorithms found'); } $this->decompress = $compression_algorithm == 'zlib'; $compression_algorithm = $this->_array_intersect_first($compression_algorithms, $this->compression_algorithms_client_to_server); if ($compression_algorithm === false) { - user_error('No compatible client to server compression algorithms found'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new NoSupportedAlgorithmsException('No compatible client to server compression algorithms found'); } $this->compress = $compression_algorithm == 'zlib'; @@ -1891,6 +1898,8 @@ class SSH2 * @param string $username * @param string $password * @return bool + * @throws \UnexpectedValueException on receipt of unexpected packets + * @throws \RuntimeException on other errors * @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. @@ -1915,15 +1924,13 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { - user_error('Connection closed by server'); - return false; + throw new \RuntimeException('Connection closed by server'); } extract(unpack('Ctype', $this->_string_shift($response, 1))); if ($type != NET_SSH2_MSG_SERVICE_ACCEPT) { - user_error('Expected SSH_MSG_SERVICE_ACCEPT'); - return false; + throw new \UnexpectedValueException('Expected SSH_MSG_SERVICE_ACCEPT'); } $this->bitmap |= self::MASK_LOGIN_REQ; } @@ -1964,8 +1971,7 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { - user_error('Connection closed by server'); - return false; + throw new \RuntimeException('Connection closed by server'); } extract(unpack('Ctype', $this->_string_shift($response, 1))); @@ -2019,8 +2025,7 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { - user_error('Connection closed by server'); - return false; + throw new \RuntimeException('Connection closed by server'); } extract(unpack('Ctype', $this->_string_shift($response, 1))); @@ -2096,6 +2101,7 @@ class SSH2 * * @param string $responses... * @return bool + * @throws \RuntimeException on connection error * @access private */ function _keyboard_interactive_process() @@ -2107,8 +2113,7 @@ class SSH2 } else { $orig = $response = $this->_get_binary_packet(); if ($response === false) { - user_error('Connection closed by server'); - return false; + throw new \RuntimeException('Connection closed by server'); } } @@ -2232,6 +2237,7 @@ class SSH2 * @param string $username * @param \phpseclib\Crypt\RSA $password * @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. @@ -2277,8 +2283,7 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { - user_error('Connection closed by server'); - return false; + throw new \RuntimeException('Connection closed by server'); } extract(unpack('Ctype', $this->_string_shift($response, 1))); @@ -2312,8 +2317,7 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { - user_error('Connection closed by server'); - return false; + throw new \RuntimeException('Connection closed by server'); } extract(unpack('Ctype', $this->_string_shift($response, 1))); @@ -2363,6 +2367,7 @@ class SSH2 * @param string $command * @param Callback $callback * @return string + * @throws \RuntimeException on connection error * @access public */ function exec($command, $callback = null) @@ -2430,8 +2435,7 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { - user_error('Connection closed by server'); - return false; + throw new \RuntimeException('Connection closed by server'); } list(, $type) = unpack('C', $this->_string_shift($response, 1)); @@ -2441,8 +2445,8 @@ class SSH2 break; case NET_SSH2_MSG_CHANNEL_FAILURE: default: - user_error('Unable to request pseudo-terminal'); - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + throw new \RuntimeException('Unable to request pseudo-terminal'); } $this->in_request_pty_exec = true; } @@ -2510,6 +2514,8 @@ class SSH2 * @see \phpseclib\Net\SSH2::read() * @see \phpseclib\Net\SSH2::write() * @return bool + * @throws \UnexpectedValueException on receipt of unexpected packets + * @throws \RuntimeException on other errors * @access private */ function _initShell() @@ -2566,8 +2572,7 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { - user_error('Connection closed by server'); - return false; + throw new \RuntimeException('Connection closed by server'); } list(, $type) = unpack('C', $this->_string_shift($response, 1)); @@ -2578,8 +2583,8 @@ class SSH2 case NET_SSH2_MSG_CHANNEL_FAILURE: break; default: - user_error('Unable to request pseudo-terminal'); - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + throw new \UnexpectedValueException('Unable to request pseudo-terminal'); } $packet = pack( @@ -2656,6 +2661,7 @@ class SSH2 * @param string $expect * @param int $mode * @return string + * @throws \RuntimeException on connection error * @access public */ function read($expect = '', $mode = self::READ_SIMPLE) @@ -2664,13 +2670,11 @@ class SSH2 $this->is_timeout = false; if (!($this->bitmap & self::MASK_LOGIN)) { - user_error('Operation disallowed prior to login()'); - return false; + throw new \RuntimeException('Operation disallowed prior to login()'); } if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) { - user_error('Unable to initiate an interactive shell session'); - return false; + throw new \RuntimeException('Unable to initiate an interactive shell session'); } $channel = $this->_get_interactive_channel(); @@ -2701,18 +2705,17 @@ class SSH2 * @see \phpseclib\Net\SSH2::read() * @param string $cmd * @return bool + * @throws \RuntimeException on connection error * @access public */ function write($cmd) { if (!($this->bitmap & self::MASK_LOGIN)) { - user_error('Operation disallowed prior to login()'); - return false; + throw new \RuntimeException('Operation disallowed prior to login()'); } if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) { - user_error('Unable to initiate an interactive shell session'); - return false; + throw new \RuntimeException('Unable to initiate an interactive shell session'); } return $this->_send_channel_packet($this->_get_interactive_channel(), $cmd); @@ -2836,6 +2839,7 @@ class SSH2 if (isset($this->realtime_log_file) && is_resource($this->realtime_log_file)) { fclose($this->realtime_log_file); } + unset(self::$connections[$this->getResourceId()]); } /** @@ -2869,14 +2873,14 @@ class SSH2 * * @see \phpseclib\Net\SSH2::_send_binary_packet() * @return string + * @throws \RuntimeException on connection errors * @access private */ function _get_binary_packet() { if (!is_resource($this->fsock) || feof($this->fsock)) { - user_error('Connection closed prematurely'); $this->bitmap = 0; - return false; + throw new \RuntimeException('Connection closed prematurely'); } $start = microtime(true); @@ -2890,8 +2894,7 @@ class SSH2 $raw = $this->decrypt->decrypt($raw); } if ($raw === false) { - user_error('Unable to decrypt content'); - return false; + throw new \RuntimeException('Unable to decrypt content'); } extract(unpack('Npacket_length/Cpadding_length', $this->_string_shift($raw, 5))); @@ -2902,17 +2905,15 @@ class SSH2 // "implementations SHOULD check that the packet length is reasonable" // PuTTY uses 0x9000 as the actual max packet size and so to shall we if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) { - user_error('Invalid size'); - return false; + throw new \RuntimeException('Invalid size'); } $buffer = ''; while ($remaining_length > 0) { $temp = fread($this->fsock, $remaining_length); if ($temp === false || feof($this->fsock)) { - user_error('Error reading from socket'); $this->bitmap = 0; - return false; + throw new \RuntimeException('Error reading from socket'); } $buffer.= $temp; $remaining_length-= strlen($temp); @@ -2928,12 +2929,10 @@ class SSH2 if ($this->hmac_check !== false) { $hmac = fread($this->fsock, $this->hmac_size); if ($hmac === false || strlen($hmac) != $this->hmac_size) { - user_error('Error reading socket'); $this->bitmap = 0; - return false; + throw new \RuntimeException('Error reading socket'); } elseif ($hmac != $this->hmac_check->hash(pack('NNCa*', $this->get_seq_no, $packet_length, $padding_length, $payload . $padding))) { - user_error('Invalid HMAC'); - return false; + throw new \RuntimeException('Invalid HMAC'); } } @@ -3161,6 +3160,7 @@ class SSH2 * * @param $client_channel * @return mixed + * @throws \RuntimeException on connection error * @access private */ function _get_channel_packet($client_channel, $skip_extended = false) @@ -3193,8 +3193,7 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { - user_error('Connection closed by server'); - return false; + throw new \RuntimeException('Connection closed by server'); } if ($client_channel == -1 && $response === true) { return true; @@ -3243,8 +3242,8 @@ class SSH2 return $result; //case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE: default: - user_error('Unable to open channel'); - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + throw new \RuntimeException('Unable to open channel'); } break; case NET_SSH2_MSG_CHANNEL_REQUEST: @@ -3254,8 +3253,8 @@ class SSH2 case NET_SSH2_MSG_CHANNEL_FAILURE: return false; default: - user_error('Unable to fulfill channel request'); - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + throw new \RuntimeException('Unable to fulfill channel request'); } case NET_SSH2_MSG_CHANNEL_CLOSE: return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->_get_channel_packet($client_channel, $skip_extended); @@ -3364,8 +3363,8 @@ class SSH2 case NET_SSH2_MSG_CHANNEL_EOF: break; default: - user_error('Error reading channel data'); - return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + throw new \RuntimeException('Error reading channel data'); } } } @@ -3384,9 +3383,8 @@ class SSH2 function _send_binary_packet($data, $logged = null) { if (!is_resource($this->fsock) || feof($this->fsock)) { - user_error('Connection closed prematurely'); $this->bitmap = 0; - return false; + throw new \RuntimeException('Connection closed prematurely'); } //if ($this->compress) { @@ -3945,6 +3943,8 @@ class SSH2 * is recommended. Returns false if the server signature is not signed correctly with the public host key. * * @return mixed + * @throws \RuntimeException on badly formatted keys + * @throws \phpseclib\Exception\NoSupportedAlgorithmsException when the key isn't in a supported format * @access public */ function getServerPublicHostKey() @@ -3990,8 +3990,8 @@ class SSH2 padding, unsigned, and in network byte order). */ $temp = unpack('Nlength', $this->_string_shift($signature, 4)); if ($temp['length'] != 40) { - user_error('Invalid signature'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new \RuntimeException('Invalid signature'); } $r = new BigInteger($this->_string_shift($signature, 20), 256); @@ -4002,8 +4002,8 @@ class SSH2 case $r->compare($q) >= 0: case $s->equals($zero): case $s->compare($q) >= 0: - user_error('Invalid signature'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new \RuntimeException('Invalid signature'); } $w = $s->modInverse($q); @@ -4022,7 +4022,7 @@ class SSH2 list(, $v) = $v->divide($q); if (!$v->equals($r)) { - user_error('Bad server signature'); + //user_error('Bad server signature'); return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } @@ -4042,9 +4042,9 @@ class SSH2 $rsa = new RSA(); $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1); - $rsa->loadKey(array('e' => $e, 'n' => $n), 'Raw'); + $rsa->load(array('e' => $e, 'n' => $n), 'Raw'); if (!$rsa->verify($this->exchange_hash, $signature)) { - user_error('Bad server signature'); + //user_error('Bad server signature'); return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } */ @@ -4059,8 +4059,8 @@ class SSH2 // also, see SSHRSA.c (rsa2_verifysig) in PuTTy's source. if ($s->compare(new BigInteger()) < 0 || $s->compare($n->subtract(new BigInteger(1))) > 0) { - user_error('Invalid signature'); - return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + throw new \RuntimeException('Invalid signature'); } $s = $s->modPow($e, $n); @@ -4070,13 +4070,13 @@ class SSH2 $h = chr(0x01) . str_repeat(chr(0xFF), $nLength - 2 - strlen($h)) . $h; if ($s != $h) { - user_error('Bad server signature'); + //user_error('Bad server signature'); return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } break; default: - user_error('Unsupported signature format'); - return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); + $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); + throw new NoSupportedAlgorithmsException('Unsupported signature format'); } return $this->signature_format . ' ' . base64_encode($this->server_public_host_key); @@ -4152,4 +4152,47 @@ class SSH2 $this->windowColumns = $columns; $this->windowRows = $rows; } + + /** + * @return string + */ + function __toString() + { + return $this->getResourceId(); + } + + /** + * We use {} because that symbols should not be in URL according to + * {@link http://tools.ietf.org/html/rfc3986#section-2 RFC}. + * It will safe us from any conflicts, because otherwise regexp will + * match all alphanumeric domains. + * + * @return string + */ + function getResourceId() + { + return '{' . spl_object_hash($this) . '}'; + } + + /** + * Return existing connection + * + * @param string $id + * + * @return bool|SSH2 will return false if no such connection + */ + static function getConnectionByResourceId($id) + { + return isset(self::$connections[$id]) ? self::$connections[$id] : false; + } + + /** + * Return all excising connections + * + * @return SSH2[] + */ + static function getConnections() + { + return self::$connections; + } } diff --git a/phpseclib/System/SSH/Agent.php b/phpseclib/System/SSH/Agent.php index af055f52..17b52ad8 100644 --- a/phpseclib/System/SSH/Agent.php +++ b/phpseclib/System/SSH/Agent.php @@ -35,6 +35,7 @@ namespace phpseclib\System\SSH; use phpseclib\Crypt\RSA; use phpseclib\System\SSH\Agent\Identity; +use phpseclib\Exception\BadConfigurationException; /** * Pure-PHP ssh-agent client identity factory @@ -115,6 +116,8 @@ class Agent * Default Constructor * * @return \phpseclib\System\SSH\Agent + * @throws \phpseclib\Exception\BadConfigurationException if SSH_AUTH_SOCK cannot be found + * @throws \RuntimeException on connection errors * @access public */ function __construct() @@ -127,13 +130,12 @@ class Agent $address = $_ENV['SSH_AUTH_SOCK']; break; default: - user_error('SSH_AUTH_SOCK not found'); - return false; + throw new \BadConfigurationException('SSH_AUTH_SOCK not found'); } $this->fsock = fsockopen('unix://' . $address, 0, $errno, $errstr); if (!$this->fsock) { - user_error("Unable to connect to ssh-agent (Error $errno: $errstr)"); + throw new \RuntimeException("Unable to connect to ssh-agent (Error $errno: $errstr)"); } } @@ -144,6 +146,7 @@ class Agent * Returns an array containing zero or more \phpseclib\System\SSH\Agent\Identity objects * * @return array + * @throws \RuntimeException on receipt of unexpected packets * @access public */ function requestIdentities() @@ -154,13 +157,13 @@ class Agent $packet = pack('NC', 1, self::SSH_AGENTC_REQUEST_IDENTITIES); if (strlen($packet) != fputs($this->fsock, $packet)) { - user_error('Connection closed while requesting identities'); + throw new \RuntimeException('Connection closed while requesting identities'); } $length = current(unpack('N', fread($this->fsock, 4))); $type = ord(fread($this->fsock, 1)); if ($type != self::SSH_AGENT_IDENTITIES_ANSWER) { - user_error('Unable to request identities'); + throw new \RuntimeException('Unable to request identities'); } $identities = array(); @@ -271,6 +274,7 @@ class Agent * * @param string $data * @return data from SSH Agent + * @throws \RuntimeException on connection errors * @access private */ function _forward_data($data) @@ -289,7 +293,7 @@ class Agent } if (strlen($this->socket_buffer) != fwrite($this->fsock, $this->socket_buffer)) { - user_error('Connection closed attempting to forward data to SSH agent'); + throw new \RuntimeException('Connection closed attempting to forward data to SSH agent'); } $this->socket_buffer = ''; diff --git a/phpseclib/System/SSH/Agent/Identity.php b/phpseclib/System/SSH/Agent/Identity.php index f52bc31c..42a4dae2 100644 --- a/phpseclib/System/SSH/Agent/Identity.php +++ b/phpseclib/System/SSH/Agent/Identity.php @@ -134,6 +134,7 @@ class Identity * * @param string $message * @return string + * @throws \RuntimeException on connection errors * @access public */ function sign($message) @@ -142,13 +143,13 @@ class Identity $packet = pack('CNa*Na*N', Agent::SSH_AGENTC_SIGN_REQUEST, strlen($this->key_blob), $this->key_blob, strlen($message), $message, 0); $packet = pack('Na*', strlen($packet), $packet); if (strlen($packet) != fputs($this->fsock, $packet)) { - user_error('Connection closed during signing'); + throw new \RuntimeException('Connection closed during signing'); } $length = current(unpack('N', fread($this->fsock, 4))); $type = ord(fread($this->fsock, 1)); if ($type != Agent::SSH_AGENT_SIGN_RESPONSE) { - user_error('Unable to retreive signature'); + throw new \RuntimeException('Unable to retreive signature'); } $signature_blob = fread($this->fsock, $length - 1); diff --git a/tests/Functional/Net/SFTPStreamTest.php b/tests/Functional/Net/SFTPStreamTest.php index b596c4f8..aab16ec9 100644 --- a/tests/Functional/Net/SFTPStreamTest.php +++ b/tests/Functional/Net/SFTPStreamTest.php @@ -27,6 +27,19 @@ class Functional_Net_SFTPStreamTest extends Functional_Net_SFTPTestCase $this->assertSame(0, $this->sftp->size('fooo.txt')); } + /** + * Tests connection reuse functionality same as ssh2 extension: + * {@link http://php.net/manual/en/wrappers.ssh2.php#refsect1-wrappers.ssh2-examples} + */ + public function testConnectionReuse() + { + $originalConnectionsCount = count(\phpseclib\Net\SSH2::getConnections()); + $session = $this->sftp; + $dirs = scandir("sftp://$session/"); + $this->assertCount($originalConnectionsCount, \phpseclib\Net\SSH2::getConnections()); + $this->assertEquals(array('.', '..'), array_slice($dirs, 0, 2)); + } + protected function buildUrl($suffix) { return sprintf( diff --git a/tests/Functional/Net/SFTPTestCase.php b/tests/Functional/Net/SFTPTestCase.php index abd5999d..aec75e71 100644 --- a/tests/Functional/Net/SFTPTestCase.php +++ b/tests/Functional/Net/SFTPTestCase.php @@ -13,6 +13,9 @@ use phpseclib\Net\SFTP; */ abstract class Functional_Net_SFTPTestCase extends PhpseclibFunctionalTestCase { + /** + * @var SFTP + */ protected $sftp; protected $scratchDir; diff --git a/tests/Functional/Net/SFTPUserStoryTest.php b/tests/Functional/Net/SFTPUserStoryTest.php index 6539c675..fcc41eda 100644 --- a/tests/Functional/Net/SFTPUserStoryTest.php +++ b/tests/Functional/Net/SFTPUserStoryTest.php @@ -651,5 +651,28 @@ class Functional_Net_SFTPUserStoryTest extends PhpseclibFunctionalTestCase $this->assertSame($stat['type'], NET_SFTP_TYPE_SYMLINK); $sftp->enableStatCache(); + + return $sftp; + } + + /** + * @depends testStatVsLstat + * @group github830 + */ + public function testUploadOffsets($sftp) + { + $sftp->put('offset.txt', 'res.txt', SFTP::SOURCE_LOCAL_FILE, 0, 10); + $this->assertSame( + substr(self::$exampleData, 10), + $sftp->get('offset.txt'), + 'Failed asserting that portions of a file could be uploaded.' + ); + + $sftp->put('offset.txt', 'res.txt', SFTP::SOURCE_LOCAL_FILE, self::$exampleDataLength - 100); + $this->assertSame( + substr(self::$exampleData, 10, -90) . self::$exampleData, + $sftp->get('offset.txt'), + 'Failed asserting that you could upload into the middle of a file.' + ); } } diff --git a/tests/PhpseclibFunctionalTestCase.php b/tests/PhpseclibFunctionalTestCase.php index e0b3a7cf..ba646edb 100644 --- a/tests/PhpseclibFunctionalTestCase.php +++ b/tests/PhpseclibFunctionalTestCase.php @@ -28,9 +28,7 @@ abstract class PhpseclibFunctionalTestCase extends PhpseclibTestCase 'Should have gmp or bcmath extension for functional test.' ); } - self::ensureConstant('CRYPT_HASH_MODE', Hash::MODE_HASH); self::reRequireFile('Math/BigInteger.php'); - self::reRequireFile('Crypt/Hash.php'); } parent::setUpBeforeClass(); } diff --git a/tests/Unit/Crypt/Hash/MD5Test.php b/tests/Unit/Crypt/Hash/MD5Test.php deleted file mode 100644 index 431184ee..00000000 --- a/tests/Unit/Crypt/Hash/MD5Test.php +++ /dev/null @@ -1,49 +0,0 @@ - - * @copyright 2012 Andreas Fischer - * @license http://www.opensource.org/licenses/mit-license.html MIT License - */ - -use phpseclib\Crypt\Hash; - -class Unit_Crypt_Hash_MD5Test extends Unit_Crypt_Hash_TestCase -{ - public function getInstance() - { - return new Hash('md5'); - } - - /** - * @dataProvider hashData() - */ - public function testHash($message, $result) - { - $this->assertHashesTo($this->getInstance(), $message, $result); - } - - public static function hashData() - { - return array( - array('', 'd41d8cd98f00b204e9800998ecf8427e'), - array('The quick brown fox jumps over the lazy dog', '9e107d9d372bb6826bd81d3542a419d6'), - array('The quick brown fox jumps over the lazy dog.', 'e4d909c290d0fb1ca068ffaddf22cbd0'), - ); - } - - /** - * @dataProvider hmacData() - */ - public function testHMAC($key, $message, $result) - { - $this->assertHMACsTo($this->getInstance(), $key, $message, $result); - } - - public static function hmacData() - { - return array( - array('', '', '74e6f7298a9c2d168935f58c001bad88'), - array('key', 'The quick brown fox jumps over the lazy dog', '80070713463e7749b90c2dc24911e275'), - ); - } -} diff --git a/tests/Unit/Crypt/Hash/SHA256Test.php b/tests/Unit/Crypt/Hash/SHA256Test.php deleted file mode 100644 index bad46b36..00000000 --- a/tests/Unit/Crypt/Hash/SHA256Test.php +++ /dev/null @@ -1,81 +0,0 @@ - - * @copyright 2014 Andreas Fischer - * @license http://www.opensource.org/licenses/mit-license.html MIT License - */ - -use phpseclib\Crypt\Hash; - -class Unit_Crypt_Hash_SHA256Test extends Unit_Crypt_Hash_TestCase -{ - public function getInstance() - { - return new Hash('sha256'); - } - - /** - * @dataProvider hashData() - */ - public function testHash($message, $result) - { - $this->assertHashesTo($this->getInstance(), $message, $result); - } - - public static function hashData() - { - return array( - array( - '', - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' - ), - array( - 'The quick brown fox jumps over the lazy dog', - 'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592', - ), - array( - 'The quick brown fox jumps over the lazy dog.', - 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c', - ), - ); - } - - /** - * @dataProvider hmacData() - */ - public function testHMAC($key, $message, $result) - { - $this->assertHMACsTo($this->getInstance(), $key, $message, $result); - } - - public static function hmacData() - { - return array( - // RFC 4231 - // Test Case 1 - array( - pack('H*', '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'), - pack('H*', '4869205468657265'), - 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7', - ), - // Test Case 2 - array( - pack('H*', '4a656665'), - pack('H*', '7768617420646f2079612077616e7420666f72206e6f7468696e673f'), - '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843', - ), - // Test Case 3 - array( - pack('H*', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'), - pack('H*', 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'), - '773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe', - ), - // Test Case 4 - array( - pack('H*', '0102030405060708090a0b0c0d0e0f10111213141516171819'), - pack('H*', 'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd'), - '82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b', - ), - ); - } -} diff --git a/tests/Unit/Crypt/Hash/SHA256_96Test.php b/tests/Unit/Crypt/Hash/SHA256_96Test.php deleted file mode 100644 index 85aaf295..00000000 --- a/tests/Unit/Crypt/Hash/SHA256_96Test.php +++ /dev/null @@ -1,32 +0,0 @@ - - * @copyright 2014 Andreas Fischer - * @license http://www.opensource.org/licenses/mit-license.html MIT License - */ - -use phpseclib\Crypt\Hash; - -class Unit_Crypt_Hash_SHA256_96Test extends Unit_Crypt_Hash_SHA256Test -{ - public function getInstance() - { - return new Hash('sha256-96'); - } - - /** - * @dataProvider hashData() - */ - public function testHash($message, $longResult) - { - parent::testHash($message, substr($longResult, 0, 24)); - } - - /** - * @dataProvider hmacData() - */ - public function testHMAC($key, $message, $longResult) - { - parent::testHMAC($key, $message, substr($longResult, 0, 24)); - } -} diff --git a/tests/Unit/Crypt/Hash/SHA512Test.php b/tests/Unit/Crypt/Hash/SHA512Test.php deleted file mode 100644 index ad2e63e3..00000000 --- a/tests/Unit/Crypt/Hash/SHA512Test.php +++ /dev/null @@ -1,81 +0,0 @@ - - * @copyright 2014 Andreas Fischer - * @license http://www.opensource.org/licenses/mit-license.html MIT License - */ - -use phpseclib\Crypt\Hash; - -class Unit_Crypt_Hash_SHA512Test extends Unit_Crypt_Hash_TestCase -{ - public function getInstance() - { - return new Hash('sha512'); - } - - /** - * @dataProvider hashData() - */ - public function testHash($message, $result) - { - $this->assertHashesTo($this->getInstance(), $message, $result); - } - - public static function hashData() - { - return array( - array( - '', - 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e' - ), - array( - 'The quick brown fox jumps over the lazy dog', - '07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6', - ), - array( - 'The quick brown fox jumps over the lazy dog.', - '91ea1245f20d46ae9a037a989f54f1f790f0a47607eeb8a14d12890cea77a1bbc6c7ed9cf205e67b7f2b8fd4c7dfd3a7a8617e45f3c463d481c7e586c39ac1ed', - ), - ); - } - - /** - * @dataProvider hmacData() - */ - public function testHMAC($key, $message, $result) - { - $this->assertHMACsTo($this->getInstance(), $key, $message, $result); - } - - public static function hmacData() - { - return array( - // RFC 4231 - // Test Case 1 - array( - pack('H*', '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'), - pack('H*', '4869205468657265'), - '87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854', - ), - // Test Case 2 - array( - pack('H*', '4a656665'), - pack('H*', '7768617420646f2079612077616e7420666f72206e6f7468696e673f'), - '164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737', - ), - // Test Case 3 - array( - pack('H*', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'), - pack('H*', 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'), - 'fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb', - ), - // Test Case 4 - array( - pack('H*', '0102030405060708090a0b0c0d0e0f10111213141516171819'), - pack('H*', 'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd'), - 'b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd', - ), - ); - } -} diff --git a/tests/Unit/Crypt/Hash/SHA512_96Test.php b/tests/Unit/Crypt/Hash/SHA512_96Test.php deleted file mode 100644 index 760fa243..00000000 --- a/tests/Unit/Crypt/Hash/SHA512_96Test.php +++ /dev/null @@ -1,32 +0,0 @@ - - * @copyright 2014 Andreas Fischer - * @license http://www.opensource.org/licenses/mit-license.html MIT License - */ - -use phpseclib\Crypt\Hash; - -class Unit_Crypt_Hash_SHA512_96Test extends Unit_Crypt_Hash_SHA512Test -{ - public function getInstance() - { - return new Hash('sha512-96'); - } - - /** - * @dataProvider hashData() - */ - public function testHash($message, $longResult) - { - parent::testHash($message, substr($longResult, 0, 24)); - } - - /** - * @dataProvider hmacData() - */ - public function testHMAC($key, $message, $longResult) - { - parent::testHMAC($key, $message, substr($longResult, 0, 24)); - } -} diff --git a/tests/Unit/Crypt/Hash/TestCase.php b/tests/Unit/Crypt/Hash/TestCase.php deleted file mode 100644 index 2ef63c6b..00000000 --- a/tests/Unit/Crypt/Hash/TestCase.php +++ /dev/null @@ -1,52 +0,0 @@ - - * @copyright 2012 Andreas Fischer - * @license http://www.opensource.org/licenses/mit-license.html MIT License - */ - -use phpseclib\Crypt\Hash; - -abstract class Unit_Crypt_Hash_TestCase extends PhpseclibTestCase -{ - public static function setUpBeforeClass() - { - if (!defined('CRYPT_HASH_MODE')) { - define('CRYPT_HASH_MODE', Hash::MODE_INTERNAL); - } - } - - public function setUp() - { - if (defined('CRYPT_HASH_MODE') && CRYPT_HASH_MODE !== Hash::MODE_INTERNAL) { - $this->markTestSkipped( - 'Skipping test because CRYPT_HASH_MODE is not defined as \phpseclib\Crypt\Hash::MODE_INTERNAL.' - ); - } - } - - protected function assertHashesTo(Hash $hash, $message, $expected) - { - $this->assertEquals( - strtolower($expected), - bin2hex($hash->hash($message)), - sprintf("Failed asserting that '%s' hashes to '%s'.", $message, $expected) - ); - } - - protected function assertHMACsTo(Hash $hash, $key, $message, $expected) - { - $hash->setKey($key); - - $this->assertEquals( - strtolower($expected), - bin2hex($hash->hash($message)), - sprintf( - "Failed asserting that '%s' HMACs to '%s' with key '%s'.", - $message, - $expected, - $key - ) - ); - } -} diff --git a/tests/Unit/Crypt/HashTest.php b/tests/Unit/Crypt/HashTest.php new file mode 100644 index 00000000..72779ea1 --- /dev/null +++ b/tests/Unit/Crypt/HashTest.php @@ -0,0 +1,265 @@ + + * @copyright 2012 Andreas Fischer + * @license http://www.opensource.org/licenses/mit-license.html MIT License + */ + +use phpseclib\Crypt\Hash; + +class Unit_Crypt_HashTest extends PhpseclibTestCase +{ + protected function assertHashesTo($hash, $message, $expected) + { + $hash = new Hash($hash); + + $this->assertSame( + strtolower($expected), + bin2hex($hash->hash($message)), + sprintf("Failed asserting that '%s' hashes to '%s'.", $message, $expected) + ); + } + + protected function assertHMACsTo($hash, $key, $message, $expected) + { + $hash = new Hash($hash); + $hash->setKey($key); + + $this->assertSame( + strtolower($expected), + bin2hex($hash->hash($message)), + sprintf( + "Failed asserting that '%s' HMACs to '%s' with key '%s'.", + $message, + $expected, + $key + ) + ); + } + + public static function hashData() + { + return array( + array('md5', '', 'd41d8cd98f00b204e9800998ecf8427e'), + array('md5', 'The quick brown fox jumps over the lazy dog', '9e107d9d372bb6826bd81d3542a419d6'), + array('md5', 'The quick brown fox jumps over the lazy dog.', 'e4d909c290d0fb1ca068ffaddf22cbd0'), + array('sha1', 'The quick brown fox jumps over the lazy dog', '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12'), + array('sha1', 'The quick brown fox jumps over the lazy dog.', '408d94384216f890ff7a0c3528e8bed1e0b01621'), + array( + 'sha256', + '', + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + ), + array( + 'sha256', + 'The quick brown fox jumps over the lazy dog', + 'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592', + ), + array( + 'sha256', + 'The quick brown fox jumps over the lazy dog.', + 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c', + ), + array( + 'sha384', + '', + '38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b' + ), + array( + 'sha384', + 'The quick brown fox jumps over the lazy dog', + 'ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1', + ), + array( + 'sha512', + '', + 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e' + ), + array( + 'sha512', + 'The quick brown fox jumps over the lazy dog', + '07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6', + ), + array( + 'sha512', + 'The quick brown fox jumps over the lazy dog.', + '91ea1245f20d46ae9a037a989f54f1f790f0a47607eeb8a14d12890cea77a1bbc6c7ed9cf205e67b7f2b8fd4c7dfd3a7a8617e45f3c463d481c7e586c39ac1ed', + ), + array( + 'whirlpool', + 'The quick brown fox jumps over the lazy dog.', + '87a7ff096082e3ffeb86db10feb91c5af36c2c71bc426fe310ce662e0338223e217def0eab0b02b80eecf875657802bc5965e48f5c0a05467756f0d3f396faba' + ), + array( + 'whirlpool', + 'The quick brown fox jumps over the lazy dog.', + '87a7ff096082e3ffeb86db10feb91c5af36c2c71bc426fe310ce662e0338223e217def0eab0b02b80eecf875657802bc5965e48f5c0a05467756f0d3f396faba' + ), + ); + } + + /** + * @dataProvider hmacData() + */ + public function testHMAC($hash, $key, $message, $result) + { + $this->assertHMACsTo($hash, $key, $message, $result); + } + + /** + * @dataProvider hmacData() + */ + public function testHMAC96($hash, $key, $message, $result) + { + $this->assertHMACsTo($hash . '-96', $key, $message, substr($result, 0, 24)); + } + + public static function hmacData() + { + return array( + array('md5', '', '', '74e6f7298a9c2d168935f58c001bad88'), + array('md5', 'key', 'The quick brown fox jumps over the lazy dog', '80070713463e7749b90c2dc24911e275'), + // RFC 4231 + // Test Case 1 + array( + 'sha256', + pack('H*', '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'), + pack('H*', '4869205468657265'), + 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7', + ), + // Test Case 2 + array( + 'sha256', + pack('H*', '4a656665'), + pack('H*', '7768617420646f2079612077616e7420666f72206e6f7468696e673f'), + '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843', + ), + // Test Case 3 + array( + 'sha256', + pack('H*', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'), + pack('H*', 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'), + '773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe', + ), + // Test Case 4 + array( + 'sha256', + pack('H*', '0102030405060708090a0b0c0d0e0f10111213141516171819'), + pack('H*', 'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd'), + '82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b', + ), + // RFC 4231 + // Test Case 1 + array( + 'sha512', + pack('H*', '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'), + pack('H*', '4869205468657265'), + '87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854', + ), + // Test Case 2 + array( + 'sha512', + pack('H*', '4a656665'), + pack('H*', '7768617420646f2079612077616e7420666f72206e6f7468696e673f'), + '164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737', + ), + // Test Case 3 + array( + 'sha512', + pack('H*', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'), + pack('H*', 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'), + 'fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb', + ), + // Test Case 4 + array( + 'sha512', + pack('H*', '0102030405060708090a0b0c0d0e0f10111213141516171819'), + pack('H*', 'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd'), + 'b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd', + ), + array( + 'whirlpool', + 'abcd', + 'The quick brown fox jumps over the lazy dog', + 'e71aabb2588d789292fa6fef00b35cc269ec3ea912b1c1cd7127daf95f004a5df5392ee563d322bac7e19d9eab161932fe9c257d63e0d09eca0d91ab4010125e', + ), + ); + } + + /** + * @dataProvider hashData() + */ + public function testHash($hash, $message, $result) + { + $this->assertHashesTo($hash, $message, $result); + } + + /** + * @dataProvider hashData() + */ + public function testHash96($hash, $message, $result) + { + $this->assertHashesTo($hash . '-96', $message, substr($result, 0, 24)); + } + + public function testConstructorDefault() + { + $hash = new Hash(); + $this->assertSame($hash->getHash(), 'sha256'); + } + + /** + * @expectedException \phpseclib\Exception\UnsupportedAlgorithmException + */ + public function testConstructorArgumentInvalid() + { + new Hash('abcdefghijklmnopqrst'); + } + + public function testConstructorArgumentValid() + { + $hash = new Hash('whirlpool'); + $this->assertSame($hash->getHash(), 'whirlpool'); + } + + /** + * @expectedException \phpseclib\Exception\UnsupportedAlgorithmException + */ + public function testSetHashInvalid() + { + $hash = new Hash('md5'); + $hash->setHash('abcdefghijklmnopqrst-96'); + } + + public function testSetHashValid() + { + $hash = new Hash('md5'); + $this->assertSame($hash->getHash(), 'md5'); + $hash->setHash('sha1'); + $this->assertSame($hash->getHash(), 'sha1'); + } + + /** + * @dataProvider lengths + */ + public function testGetLengthKnown($algorithm, $length) + { + $hash = new Hash($algorithm); + $this->assertSame($hash->getLength(), $length); + } + + public function lengths() + { + return array( + // known + array('md5-96', 12), + array('md5', 16), + array('sha1', 20), + array('sha256', 32), + array('sha384', 48), + array('sha512', 64), + // unknown + array('whirlpool', 64), + ); + } +} diff --git a/tests/Unit/Net/SSH2Test.php b/tests/Unit/Net/SSH2Test.php index df9651af..1d3c6439 100644 --- a/tests/Unit/Net/SSH2Test.php +++ b/tests/Unit/Net/SSH2Test.php @@ -110,6 +110,18 @@ class Unit_Net_SSH2Test extends PhpseclibTestCase $this->assertFalse($ssh->isQuietModeEnabled()); } + public function testGetConnectionByResourceId() + { + $ssh = new \phpseclib\Net\SSH2('localhost'); + $this->assertSame($ssh, \phpseclib\Net\SSH2::getConnectionByResourceId($ssh->getResourceId())); + } + + public function testGetResourceId() + { + $ssh = new \phpseclib\Net\SSH2('localhost'); + $this->assertSame('{' . spl_object_hash($ssh) . '}', $ssh->getResourceId()); + } + /** * @return \phpseclib\Net\SSH2 */