From d73ffee957fcb96cc957ac7bb672a595cd7c4da3 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 29 Aug 2019 20:23:11 +0200 Subject: [PATCH] phpceclib 2.0.21 --- lam-packaging/debian/copyright | 2 +- lam/copyright | 2 +- lam/lib/3rdParty/phpseclib/Crypt/Base.php | 177 ++++- lam/lib/3rdParty/phpseclib/Crypt/Blowfish.php | 44 +- lam/lib/3rdParty/phpseclib/Crypt/DES.php | 4 +- lam/lib/3rdParty/phpseclib/Crypt/Hash.php | 128 ++- lam/lib/3rdParty/phpseclib/Crypt/RC2.php | 2 +- lam/lib/3rdParty/phpseclib/Crypt/RC4.php | 2 +- lam/lib/3rdParty/phpseclib/Crypt/RSA.php | 206 ++++- lam/lib/3rdParty/phpseclib/Crypt/Random.php | 4 + lam/lib/3rdParty/phpseclib/Crypt/Twofish.php | 68 +- lam/lib/3rdParty/phpseclib/File/ANSI.php | 5 +- lam/lib/3rdParty/phpseclib/File/ASN1.php | 214 +++-- lam/lib/3rdParty/phpseclib/File/X509.php | 315 +++++++- .../3rdParty/phpseclib/Math/BigInteger.php | 80 +- lam/lib/3rdParty/phpseclib/Net/SCP.php | 5 + lam/lib/3rdParty/phpseclib/Net/SFTP.php | 147 +++- .../3rdParty/phpseclib/Net/SFTP/Stream.php | 2 +- lam/lib/3rdParty/phpseclib/Net/SSH1.php | 6 +- lam/lib/3rdParty/phpseclib/Net/SSH2.php | 752 +++++++++++++----- .../3rdParty/phpseclib/System/SSH/Agent.php | 28 +- .../phpseclib/System/SSH/Agent/Identity.php | 80 +- lam/lib/modules/imapAccess.inc | 15 +- 23 files changed, 1787 insertions(+), 501 deletions(-) diff --git a/lam-packaging/debian/copyright b/lam-packaging/debian/copyright index 586f9bb4..ca07053c 100644 --- a/lam-packaging/debian/copyright +++ b/lam-packaging/debian/copyright @@ -422,7 +422,7 @@ main license and authors: lib/3rdParty/tcpdf D 2018 Nicola Asuni - Tecnick.com LTD lib/3rdParty/tcpdf/fonts/DejaVu*.ttf A Public Domain, Bitstream, Inc., Tavmjong Bah lib/3rdParty/tcpdf/fonts/DejaVu*.z A Public Domain, Bitstream, Inc., Tavmjong Bah -lib/3rdParty/phpseclib B Jim Wigginton +lib/3rdParty/phpseclib B 2019 TerraFrost and other contributors lib/3rdParty/Monolog B 2011 Jordi Boggiano lib/3rdParty/Psr B 2012 PHP Framework Interoperability Group lib/3rdParty/yubico/Yubico.php C 2015 Yubico AB diff --git a/lam/copyright b/lam/copyright index 878733ed..0640e706 100644 --- a/lam/copyright +++ b/lam/copyright @@ -421,7 +421,7 @@ main license and authors: lib/3rdParty/tcpdf D 2018 Nicola Asuni - Tecnick.com LTD lib/3rdParty/tcpdf/fonts/DejaVu*.ttf A Public Domain, Bitstream, Inc., Tavmjong Bah lib/3rdParty/tcpdf/fonts/DejaVu*.z A Public Domain, Bitstream, Inc., Tavmjong Bah -lib/3rdParty/phpseclib B Jim Wigginton +lib/3rdParty/phpseclib B 2019 TerraFrost and other contributors lib/3rdParty/Monolog B 2011 Jordi Boggiano lib/3rdParty/Psr B 2012 PHP Framework Interoperability Group lib/3rdParty/yubico/Yubico.php C 2015 Yubico AB diff --git a/lam/lib/3rdParty/phpseclib/Crypt/Base.php b/lam/lib/3rdParty/phpseclib/Crypt/Base.php index fd99c44f..03b176e7 100644 --- a/lam/lib/3rdParty/phpseclib/Crypt/Base.php +++ b/lam/lib/3rdParty/phpseclib/Crypt/Base.php @@ -76,6 +76,10 @@ abstract class Base * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29 */ const MODE_CFB = 3; + /** + * Encrypt / decrypt using the Cipher Feedback mode (8bit) + */ + const MODE_CFB8 = 38; /** * Encrypt / decrypt using the Output Feedback mode. * @@ -479,6 +483,7 @@ abstract class Base break; case self::MODE_CTR: case self::MODE_CFB: + case self::MODE_CFB8: case self::MODE_OFB: case self::MODE_STREAM: $this->mode = $mode; @@ -492,8 +497,8 @@ abstract class Base $this->_setEngine(); // Determining whether inline crypting can be used by the cipher - if ($this->use_inline_crypt !== false && function_exists('create_function')) { - $this->use_inline_crypt = true; + if ($this->use_inline_crypt !== false) { + $this->use_inline_crypt = version_compare(PHP_VERSION, '5.3.0') >= 0 || function_exists('create_function'); } } @@ -644,10 +649,10 @@ abstract class Base case !function_exists('hash_algos'): case !in_array($hash, hash_algos()): $i = 1; + $hmac = new Hash(); + $hmac->setHash($hash); + $hmac->setKey($password); while (strlen($key) < $dkLen) { - $hmac = new Hash(); - $hmac->setHash($hash); - $hmac->setKey($password); $f = $u = $hmac->hash($salt . pack('N', $i++)); for ($j = 2; $j <= $count; ++$j) { $u = $hmac->hash($u); @@ -702,7 +707,7 @@ abstract class Base case self::MODE_STREAM: return openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options); case self::MODE_ECB: - $result = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options); + $result = @openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options); return !defined('OPENSSL_RAW_DATA') ? substr($result, 0, -$this->block_size) : $result; case self::MODE_CBC: $result = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->encryptIV); @@ -762,6 +767,16 @@ abstract class Base $iv = substr($ciphertext, -$this->block_size); } + return $ciphertext; + case self::MODE_CFB8: + $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->encryptIV); + if ($this->continuousBuffer) { + if (($len = strlen($ciphertext)) >= $this->block_size) { + $this->encryptIV = substr($ciphertext, -$this->block_size); + } else { + $this->encryptIV = substr($this->encryptIV, $len - $this->block_size) . substr($ciphertext, -$len); + } + } return $ciphertext; case self::MODE_OFB: return $this->_openssl_ofb_process($plaintext, $this->encryptIV, $this->enbuffer); @@ -942,6 +957,24 @@ abstract class Base $pos = $len; } break; + case self::MODE_CFB8: + $ciphertext = ''; + $len = strlen($plaintext); + $iv = $this->encryptIV; + + for ($i = 0; $i < $len; ++$i) { + $ciphertext .= ($c = $plaintext[$i] ^ $this->_encryptBlock($iv)); + $iv = substr($iv, 1) . $c; + } + + if ($this->continuousBuffer) { + if ($len >= $block_size) { + $this->encryptIV = substr($ciphertext, -$block_size); + } else { + $this->encryptIV = substr($this->encryptIV, $len - $block_size) . substr($ciphertext, -$len); + } + } + break; case self::MODE_OFB: $xor = $this->encryptIV; if (strlen($buffer['xor'])) { @@ -1007,14 +1040,14 @@ abstract class Base break; case self::MODE_ECB: if (!defined('OPENSSL_RAW_DATA')) { - $ciphertext.= openssl_encrypt('', $this->cipher_name_openssl_ecb, $this->key, true); + $ciphertext.= @openssl_encrypt('', $this->cipher_name_openssl_ecb, $this->key, true); } $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options); break; case self::MODE_CBC: if (!defined('OPENSSL_RAW_DATA')) { $padding = str_repeat(chr($this->block_size), $this->block_size) ^ substr($ciphertext, -$this->block_size); - $ciphertext.= substr(openssl_encrypt($padding, $this->cipher_name_openssl_ecb, $this->key, true), 0, $this->block_size); + $ciphertext.= substr(@openssl_encrypt($padding, $this->cipher_name_openssl_ecb, $this->key, true), 0, $this->block_size); $offset = 2 * $this->block_size; } else { $offset = $this->block_size; @@ -1072,6 +1105,16 @@ abstract class Base $iv = substr($ciphertext, -$this->block_size); } break; + case self::MODE_CFB8: + $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->decryptIV); + if ($this->continuousBuffer) { + if (($len = strlen($ciphertext)) >= $this->block_size) { + $this->decryptIV = substr($ciphertext, -$this->block_size); + } else { + $this->decryptIV = substr($this->decryptIV, $len - $this->block_size) . substr($ciphertext, -$len); + } + } + break; case self::MODE_OFB: $plaintext = $this->_openssl_ofb_process($ciphertext, $this->decryptIV, $this->debuffer); } @@ -1235,6 +1278,24 @@ abstract class Base $pos = $len; } break; + case self::MODE_CFB8: + $plaintext = ''; + $len = strlen($ciphertext); + $iv = $this->decryptIV; + + for ($i = 0; $i < $len; ++$i) { + $plaintext .= $ciphertext[$i] ^ $this->_encryptBlock($iv); + $iv = substr($iv, 1) . $ciphertext[$i]; + } + + if ($this->continuousBuffer) { + if ($len >= $block_size) { + $this->decryptIV = substr($ciphertext, -$block_size); + } else { + $this->decryptIV = substr($this->decryptIV, $len - $block_size) . substr($ciphertext, -$len); + } + } + break; case self::MODE_OFB: $xor = $this->decryptIV; if (strlen($buffer['xor'])) { @@ -1297,7 +1358,7 @@ abstract class Base for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { $block = substr($plaintext, $i, $block_size); if (strlen($block) > strlen($buffer['ciphertext'])) { - $result = openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, $this->openssl_options); + $result = @openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, $this->openssl_options); $result = !defined('OPENSSL_RAW_DATA') ? substr($result, 0, -$this->block_size) : $result; $buffer['ciphertext'].= $result; } @@ -1308,7 +1369,7 @@ abstract class Base } else { for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { $block = substr($plaintext, $i, $block_size); - $otp = openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, $this->openssl_options); + $otp = @openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, $this->openssl_options); $otp = !defined('OPENSSL_RAW_DATA') ? substr($otp, 0, -$this->block_size) : $otp; $this->_increment_str($xor); $ciphertext.= $block ^ $otp; @@ -1352,7 +1413,7 @@ abstract class Base } if ($this->continuousBuffer) { if (!defined('OPENSSL_RAW_DATA')) { - $encryptIV.= openssl_encrypt('', $this->cipher_name_openssl_ecb, $key, $this->openssl_options); + $encryptIV.= @openssl_encrypt('', $this->cipher_name_openssl_ecb, $key, $this->openssl_options); } $encryptIV = openssl_decrypt($encryptIV, $this->cipher_name_openssl_ecb, $key, $this->openssl_options); if ($overflow) { @@ -1435,6 +1496,8 @@ abstract class Base return 'ctr'; case self::MODE_CFB: return 'cfb'; + case self::MODE_CFB8: + return 'cfb8'; case self::MODE_OFB: return 'ofb'; } @@ -1788,6 +1851,7 @@ abstract class Base self::MODE_ECB => MCRYPT_MODE_ECB, self::MODE_CBC => MCRYPT_MODE_CBC, self::MODE_CFB => 'ncfb', + self::MODE_CFB8 => MCRYPT_MODE_CFB, self::MODE_OFB => MCRYPT_MODE_NOFB, self::MODE_STREAM => MCRYPT_MODE_STREAM, ); @@ -2359,6 +2423,52 @@ abstract class Base $_pos = $_len; } + return $_plaintext; + '; + break; + case self::MODE_CFB8: + $encrypt = $init_encrypt . ' + $_ciphertext = ""; + $_len = strlen($_text); + $_iv = $self->encryptIV; + + for ($_i = 0; $_i < $_len; ++$_i) { + $in = $_iv; + '.$encrypt_block.' + $_ciphertext .= ($_c = $_text[$_i] ^ $in); + $_iv = substr($_iv, 1) . $_c; + } + + if ($self->continuousBuffer) { + if ($_len >= '.$block_size.') { + $self->encryptIV = substr($_ciphertext, -'.$block_size.'); + } else { + $self->encryptIV = substr($self->encryptIV, $_len - '.$block_size.') . substr($_ciphertext, -$_len); + } + } + + return $_ciphertext; + '; + $decrypt = $init_encrypt . ' + $_plaintext = ""; + $_len = strlen($_text); + $_iv = $self->decryptIV; + + for ($_i = 0; $_i < $_len; ++$_i) { + $in = $_iv; + '.$encrypt_block.' + $_plaintext .= $_text[$_i] ^ $in; + $_iv = substr($_iv, 1) . $_text[$_i]; + } + + if ($self->continuousBuffer) { + if ($_len >= '.$block_size.') { + $self->decryptIV = substr($_text, -'.$block_size.'); + } else { + $self->decryptIV = substr($self->decryptIV, $_len - '.$block_size.') . substr($_text, -$_len); + } + } + return $_plaintext; '; break; @@ -2492,6 +2602,11 @@ abstract class Base } // Create the $inline function and return its name as string. Ready to run! + if (version_compare(PHP_VERSION, '5.3.0') >= 0) { + eval('$func = function ($_action, &$self, $_text) { ' . $init_crypt . 'if ($_action == "encrypt") { ' . $encrypt . ' } else { ' . $decrypt . ' } };'); + return $func; + } + return create_function('$_action, &$self, $_text', $init_crypt . 'if ($_action == "encrypt") { ' . $encrypt . ' } else { ' . $decrypt . ' }'); } @@ -2550,4 +2665,44 @@ abstract class Base return $result . pack('H*', sha1($hash)); } } + + /** + * Convert float to int + * + * On ARM CPUs converting floats to ints doesn't always work + * + * @access private + * @param string $x + * @return int + */ + function safe_intval($x) + { + switch (true) { + case is_int($x): + // PHP 5.3, per http://php.net/releases/5_3_0.php, introduced "more consistent float rounding" + case (php_uname('m') & "\xDF\xDF\xDF") != 'ARM': + return $x; + } + return (fmod($x, 0x80000000) & 0x7FFFFFFF) | + ((fmod(floor($x / 0x80000000), 2) & 1) << 31); + } + + /** + * eval()'able string for in-line float to int + * + * @access private + * @return string + */ + function safe_intval_inline() + { + switch (true) { + case defined('PHP_INT_SIZE') && PHP_INT_SIZE == 8: + case (php_uname('m') & "\xDF\xDF\xDF") != 'ARM': + return '%s'; + break; + default: + $safeint = '(is_int($temp = %s) ? $temp : (fmod($temp, 0x80000000) & 0x7FFFFFFF) | '; + return $safeint . '((fmod(floor($temp / 0x80000000), 2) & 1) << 31))'; + } + } } diff --git a/lam/lib/3rdParty/phpseclib/Crypt/Blowfish.php b/lam/lib/3rdParty/phpseclib/Crypt/Blowfish.php index 19d0a020..74cc49de 100644 --- a/lam/lib/3rdParty/phpseclib/Crypt/Blowfish.php +++ b/lam/lib/3rdParty/phpseclib/Crypt/Blowfish.php @@ -294,7 +294,7 @@ class Blowfish extends Base function setKeyLength($length) { if ($length < 32) { - $this->key_length = 7; + $this->key_length = 4; } elseif ($length > 448) { $this->key_length = 56; } else { @@ -408,16 +408,14 @@ class Blowfish extends Base for ($i = 0; $i < 16; $i+= 2) { $l^= $p[$i]; - $r^= ($sb_0[$l >> 24 & 0xff] + - $sb_1[$l >> 16 & 0xff] ^ + $r^= $this->safe_intval(($this->safe_intval($sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]) ^ $sb_2[$l >> 8 & 0xff]) + - $sb_3[$l & 0xff]; + $sb_3[$l & 0xff]); $r^= $p[$i + 1]; - $l^= ($sb_0[$r >> 24 & 0xff] + - $sb_1[$r >> 16 & 0xff] ^ + $l^= $this->safe_intval(($this->safe_intval($sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]) ^ $sb_2[$r >> 8 & 0xff]) + - $sb_3[$r & 0xff]; + $sb_3[$r & 0xff]); } return pack("N*", $r ^ $p[17], $l ^ $p[16]); } @@ -443,16 +441,14 @@ class Blowfish extends Base for ($i = 17; $i > 2; $i-= 2) { $l^= $p[$i]; - $r^= ($sb_0[$l >> 24 & 0xff] + - $sb_1[$l >> 16 & 0xff] ^ + $r^= $this->safe_intval(($this->safe_intval($sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]) ^ $sb_2[$l >> 8 & 0xff]) + - $sb_3[$l & 0xff]; + $sb_3[$l & 0xff]); $r^= $p[$i - 1]; - $l^= ($sb_0[$r >> 24 & 0xff] + - $sb_1[$r >> 16 & 0xff] ^ + $l^= $this->safe_intval(($this->safe_intval($sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]) ^ $sb_2[$r >> 8 & 0xff]) + - $sb_3[$r & 0xff]; + $sb_3[$r & 0xff]); } return pack("N*", $r ^ $p[0], $l ^ $p[1]); } @@ -478,6 +474,8 @@ class Blowfish extends Base $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key); } + $safeint = $this->safe_intval_inline(); + if (!isset($lambda_functions[$code_hash])) { switch (true) { case $gen_hi_opt_code: @@ -513,16 +511,14 @@ class Blowfish extends Base for ($i = 0; $i < 16; $i+= 2) { $encrypt_block.= ' $l^= ' . $p[$i] . '; - $r^= ($sb_0[$l >> 24 & 0xff] + - $sb_1[$l >> 16 & 0xff] ^ + $r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]') . ' ^ $sb_2[$l >> 8 & 0xff]) + - $sb_3[$l & 0xff]; + $sb_3[$l & 0xff]') . '; $r^= ' . $p[$i + 1] . '; - $l^= ($sb_0[$r >> 24 & 0xff] + - $sb_1[$r >> 16 & 0xff] ^ + $l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]') . ' ^ $sb_2[$r >> 8 & 0xff]) + - $sb_3[$r & 0xff]; + $sb_3[$r & 0xff]') . '; '; } $encrypt_block.= ' @@ -542,16 +538,14 @@ class Blowfish extends Base for ($i = 17; $i > 2; $i-= 2) { $decrypt_block.= ' $l^= ' . $p[$i] . '; - $r^= ($sb_0[$l >> 24 & 0xff] + - $sb_1[$l >> 16 & 0xff] ^ + $r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]') . ' ^ $sb_2[$l >> 8 & 0xff]) + - $sb_3[$l & 0xff]; + $sb_3[$l & 0xff]') . '; $r^= ' . $p[$i - 1] . '; - $l^= ($sb_0[$r >> 24 & 0xff] + - $sb_1[$r >> 16 & 0xff] ^ + $l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]') . ' ^ $sb_2[$r >> 8 & 0xff]) + - $sb_3[$r & 0xff]; + $sb_3[$r & 0xff]') . '; '; } diff --git a/lam/lib/3rdParty/phpseclib/Crypt/DES.php b/lam/lib/3rdParty/phpseclib/Crypt/DES.php index 51220469..9a8225fb 100644 --- a/lam/lib/3rdParty/phpseclib/Crypt/DES.php +++ b/lam/lib/3rdParty/phpseclib/Crypt/DES.php @@ -1357,8 +1357,8 @@ class DES extends Base $k[self::ENCRYPT][$i] = '$ke[' . $i . ']'; $k[self::DECRYPT][$i] = '$kd[' . $i . ']'; } - $init_encrypt = '$ke = $self->keys[self::ENCRYPT];'; - $init_decrypt = '$kd = $self->keys[self::DECRYPT];'; + $init_encrypt = '$ke = $self->keys[$self::ENCRYPT];'; + $init_decrypt = '$kd = $self->keys[$self::DECRYPT];'; break; } diff --git a/lam/lib/3rdParty/phpseclib/Crypt/Hash.php b/lam/lib/3rdParty/phpseclib/Crypt/Hash.php index 07665a16..a6166820 100644 --- a/lam/lib/3rdParty/phpseclib/Crypt/Hash.php +++ b/lam/lib/3rdParty/phpseclib/Crypt/Hash.php @@ -112,6 +112,15 @@ class Hash */ var $key = false; + /** + * Computed Key + * + * @see self::_computeKey() + * @var string + * @access private + */ + var $computedKey = false; + /** * Outer XOR (Internal HMAC) * @@ -130,6 +139,15 @@ class Hash */ var $ipad; + /** + * Engine + * + * @see self::setHash() + * @var string + * @access private + */ + var $engine; + /** * Default Constructor. * @@ -166,6 +184,43 @@ class Hash function setKey($key = false) { $this->key = $key; + $this->_computeKey(); + } + + /** + * Pre-compute the key used by the HMAC + * + * Quoting http://tools.ietf.org/html/rfc2104#section-2, "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." + * + * As documented in https://www.reddit.com/r/PHP/comments/9nct2l/symfonypolyfill_hash_pbkdf2_correct_fix_for/ + * when doing an HMAC multiple times it's faster to compute the hash once instead of computing it during + * every call + * + * @access private + */ + function _computeKey() + { + if ($this->key === false) { + $this->computedKey = false; + return; + } + + if (strlen($this->key) <= $this->b) { + $this->computedKey = $this->key; + return; + } + + switch ($this->engine) { + case self::MODE_MHASH: + $this->computedKey = mhash($this->hash, $this->key); + break; + case self::MODE_HASH: + $this->computedKey = hash($this->hash, $this->key, true); + break; + case self::MODE_INTERNAL: + $this->computedKey = call_user_func($this->hash, $this->key); + } } /** @@ -216,19 +271,38 @@ class Hash } switch ($hash) { + case 'md2-96': case 'md2': - $mode = CRYPT_HASH_MODE == self::MODE_HASH && in_array('md2', hash_algos()) ? + $this->b = 16; + case 'md5-96': + case 'sha1-96': + case 'sha224-96': + case 'sha256-96': + case 'md2': + case 'md5': + case 'sha1': + case 'sha224': + case 'sha256': + $this->b = 64; + break; + default: + $this->b = 128; + } + + switch ($hash) { + case 'md2': + $this->engine = 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->engine = CRYPT_HASH_MODE == self::MODE_MHASH ? self::MODE_INTERNAL : CRYPT_HASH_MODE; break; default: - $mode = CRYPT_HASH_MODE; + $this->engine = CRYPT_HASH_MODE; } - switch ($mode) { + switch ($this->engine) { case self::MODE_MHASH: switch ($hash) { case 'md5': @@ -241,6 +315,7 @@ class Hash default: $this->hash = MHASH_SHA1; } + $this->_computeKey(self::MODE_MHASH); return; case self::MODE_HASH: switch ($hash) { @@ -257,35 +332,33 @@ class Hash default: $this->hash = 'sha1'; } + $this->_computeKey(self::MODE_HASH); return; } 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->_computeKey(self::MODE_INTERNAL); } /** @@ -297,33 +370,25 @@ class Hash */ function hash($text) { - $mode = is_array($this->hash) ? self::MODE_INTERNAL : CRYPT_HASH_MODE; - if (!empty($this->key) || is_string($this->key)) { - switch ($mode) { + switch ($this->engine) { case self::MODE_MHASH: - $output = mhash($this->hash, $text, $this->key); + $output = mhash($this->hash, $text, $this->computedKey); break; case self::MODE_HASH: - $output = hash_hmac($this->hash, $text, $this->key, true); + $output = hash_hmac($this->hash, $text, $this->computedKey, 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 + $key = str_pad($this->computedKey, $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) { + switch ($this->engine) { case self::MODE_MHASH: $output = mhash($this->hash, $text); break; @@ -802,7 +867,12 @@ class Hash $result+= $argument < 0 ? ($argument & 0x7FFFFFFF) + 0x80000000 : $argument; } - return fmod($result, $mod); + if ((php_uname('m') & "\xDF\xDF\xDF") != 'ARM') { + return fmod($result, $mod); + } + + return (fmod($result, 0x80000000) & 0x7FFFFFFF) | + ((fmod(floor($result / 0x80000000), 2) & 1) << 31); } /** diff --git a/lam/lib/3rdParty/phpseclib/Crypt/RC2.php b/lam/lib/3rdParty/phpseclib/Crypt/RC2.php index e9cfa3f8..b2b9d48e 100644 --- a/lam/lib/3rdParty/phpseclib/Crypt/RC2.php +++ b/lam/lib/3rdParty/phpseclib/Crypt/RC2.php @@ -296,7 +296,7 @@ class RC2 extends Base function setKeyLength($length) { if ($length < 8) { - $this->default_key_length = 8; + $this->default_key_length = 1; } elseif ($length > 1024) { $this->default_key_length = 128; } else { diff --git a/lam/lib/3rdParty/phpseclib/Crypt/RC4.php b/lam/lib/3rdParty/phpseclib/Crypt/RC4.php index 4812010c..25e4ff85 100644 --- a/lam/lib/3rdParty/phpseclib/Crypt/RC4.php +++ b/lam/lib/3rdParty/phpseclib/Crypt/RC4.php @@ -107,7 +107,7 @@ class RC4 extends Base * @var string * @access private */ - var $key = "\0"; + var $key; /** * The Key Stream for decryption and encryption diff --git a/lam/lib/3rdParty/phpseclib/Crypt/RSA.php b/lam/lib/3rdParty/phpseclib/Crypt/RSA.php index 9670c01a..d2c6c7cb 100644 --- a/lam/lib/3rdParty/phpseclib/Crypt/RSA.php +++ b/lam/lib/3rdParty/phpseclib/Crypt/RSA.php @@ -182,6 +182,10 @@ class RSA * PKCS#8 formatted private key */ const PRIVATE_FORMAT_PKCS8 = 8; + /** + * OpenSSH formatted private key + */ + const PRIVATE_FORMAT_OPENSSH = 9; /**#@-*/ /**#@+ @@ -468,23 +472,27 @@ class RSA break; case extension_loaded('openssl') && file_exists($this->configFile): // some versions of XAMPP have mismatched versions of OpenSSL which causes it not to work - ob_start(); - @phpinfo(); - $content = ob_get_contents(); - ob_end_clean(); - - preg_match_all('#OpenSSL (Header|Library) Version(.*)#im', $content, $matches); - $versions = array(); - if (!empty($matches[1])) { - for ($i = 0; $i < count($matches[1]); $i++) { - $fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i]))); - // Remove letter part in OpenSSL version - if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) { - $versions[$matches[1][$i]] = $fullVersion; - } else { - $versions[$matches[1][$i]] = $m[0]; + // avoid generating errors (even with suppression) when phpinfo() is disabled (common in production systems) + if (strpos(ini_get('disable_functions'), 'phpinfo') === false) { + ob_start(); + @phpinfo(); + $content = ob_get_contents(); + ob_end_clean(); + + preg_match_all('#OpenSSL (Header|Library) Version(.*)#im', $content, $matches); + + if (!empty($matches[1])) { + for ($i = 0; $i < count($matches[1]); $i++) { + $fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i]))); + + // Remove letter part in OpenSSL version + if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) { + $versions[$matches[1][$i]] = $fullVersion; + } else { + $versions[$matches[1][$i]] = $m[0]; + } } } } @@ -816,6 +824,58 @@ class RSA $key.= 'Private-MAC: ' . bin2hex($hash->hash($source)) . "\r\n"; return $key; + case self::PRIVATE_FORMAT_OPENSSH: + if ($num_primes != 2) { + return false; + } + $publicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($raw['publicExponent']), $raw['publicExponent'], strlen($raw['modulus']), $raw['modulus']); + $privateKey = pack( + 'Na*Na*Na*Na*Na*Na*Na*', + strlen('ssh-rsa'), + 'ssh-rsa', + strlen($raw['modulus']), + $raw['modulus'], + strlen($raw['publicExponent']), + $raw['publicExponent'], + strlen($raw['privateExponent']), + $raw['privateExponent'], + strlen($raw['coefficient']), + $raw['coefficient'], + strlen($raw['prime1']), + $raw['prime1'], + strlen($raw['prime2']), + $raw['prime2'] + ); + $checkint = Random::string(4); + $paddedKey = pack( + 'a*Na*', + $checkint . $checkint . $privateKey, + strlen($this->comment), + $this->comment + ); + $paddingLength = (7 * strlen($paddedKey)) % 8; + for ($i = 1; $i <= $paddingLength; $i++) { + $paddedKey.= chr($i); + } + $key = pack( + 'Na*Na*Na*NNa*Na*', + strlen('none'), + 'none', + strlen('none'), + 'none', + 0, + '', + 1, + strlen($publicKey), + $publicKey, + strlen($paddedKey), + $paddedKey + ); + $key = "openssh-key-v1\0$key"; + + return "-----BEGIN OPENSSH PRIVATE KEY-----\r\n" . + chunk_split(base64_encode($key), 70) . + "-----END OPENSSH PRIVATE KEY-----"; default: // eg. self::PRIVATE_FORMAT_PKCS1 $components = array(); foreach ($raw as $name => $value) { @@ -1016,9 +1076,9 @@ class RSA * @access private * @see self::_convertPublicKey() * @see self::_convertPrivateKey() - * @param string $key + * @param string|array $key * @param int $type - * @return array + * @return array|bool */ function _parseKey($key, $type) { @@ -1329,9 +1389,14 @@ class RSA xml_set_character_data_handler($xml, '_data_handler'); // add to account for "dangling" tags like ... that are sometimes added if (!xml_parse($xml, '' . $key . '')) { + xml_parser_free($xml); + unset($xml); return false; } + xml_parser_free($xml); + unset($xml); + return isset($this->components['modulus']) && isset($this->components['publicExponent']) ? $this->components : false; // from PuTTY's SSHPUBK.C case self::PRIVATE_FORMAT_PUTTY: @@ -1403,6 +1468,75 @@ class RSA } $components['coefficients'] = array(2 => new BigInteger($this->_string_shift($private, $length), -256)); + return $components; + case self::PRIVATE_FORMAT_OPENSSH: + $components = array(); + $decoded = $this->_extractBER($key); + $magic = $this->_string_shift($decoded, 15); + if ($magic !== "openssh-key-v1\0") { + return false; + } + $options = $this->_string_shift($decoded, 24); + // \0\0\0\4none = ciphername + // \0\0\0\4none = kdfname + // \0\0\0\0 = kdfoptions + // \0\0\0\1 = numkeys + if ($options != "\0\0\0\4none\0\0\0\4none\0\0\0\0\0\0\0\1") { + return false; + } + extract(unpack('Nlength', $this->_string_shift($decoded, 4))); + if (strlen($decoded) < $length) { + return false; + } + $publicKey = $this->_string_shift($decoded, $length); + extract(unpack('Nlength', $this->_string_shift($decoded, 4))); + if (strlen($decoded) < $length) { + return false; + } + $paddedKey = $this->_string_shift($decoded, $length); + + if ($this->_string_shift($publicKey, 11) !== "\0\0\0\7ssh-rsa") { + return false; + } + + $checkint1 = $this->_string_shift($paddedKey, 4); + $checkint2 = $this->_string_shift($paddedKey, 4); + if (strlen($checkint1) != 4 || $checkint1 !== $checkint2) { + return false; + } + + if ($this->_string_shift($paddedKey, 11) !== "\0\0\0\7ssh-rsa") { + return false; + } + + $values = array( + &$components['modulus'], + &$components['publicExponent'], + &$components['privateExponent'], + &$components['coefficients'][2], + &$components['primes'][1], + &$components['primes'][2] + ); + + foreach ($values as &$value) { + extract(unpack('Nlength', $this->_string_shift($paddedKey, 4))); + if (strlen($paddedKey) < $length) { + return false; + } + $value = new BigInteger($this->_string_shift($paddedKey, $length), -256); + } + + extract(unpack('Nlength', $this->_string_shift($paddedKey, 4))); + if (strlen($paddedKey) < $length) { + return false; + } + $components['comment'] = $this->_string_shift($decoded, $length); + + $temp = $components['primes'][1]->subtract($this->one); + $components['exponents'] = array(1 => $components['publicExponent']->modInverse($temp)); + $temp = $components['primes'][2]->subtract($this->one); + $components['exponents'][] = $components['publicExponent']->modInverse($temp); + return $components; } } @@ -1501,8 +1635,9 @@ class RSA * Returns true on success and false on failure (ie. an incorrect password was provided or the key was malformed) * * @access public - * @param string $key - * @param int $type optional + * @param string|RSA|array $key + * @param bool|int $type optional + * @return bool */ function loadKey($key, $type = false) { @@ -1559,7 +1694,8 @@ class RSA self::PRIVATE_FORMAT_PKCS1, self::PRIVATE_FORMAT_XML, self::PRIVATE_FORMAT_PUTTY, - self::PUBLIC_FORMAT_OPENSSH + self::PUBLIC_FORMAT_OPENSSH, + self::PRIVATE_FORMAT_OPENSSH ); foreach ($types as $type) { $components = $this->_parseKey($key, $type); @@ -2207,16 +2343,21 @@ class RSA */ function _equals($x, $y) { + if (function_exists('hash_equals')) { + return hash_equals($x, $y); + } + if (strlen($x) != strlen($y)) { return false; } - $result = 0; + $result = "\0"; + $x^= $y; for ($i = 0; $i < strlen($x); $i++) { - $result |= ord($x[$i]) ^ ord($y[$i]); + $result|= $x[$i]; } - return $result == 0; + return $result === "\0"; } /** @@ -2423,19 +2564,26 @@ class RSA $db = $maskedDB ^ $dbMask; $lHash2 = substr($db, 0, $this->hLen); $m = substr($db, $this->hLen); - if ($lHash != $lHash2) { - user_error('Decryption error'); - return false; + $hashesMatch = $this->_equals($lHash, $lHash2); + $leadingZeros = 1; + $patternMatch = 0; + $offset = 0; + for ($i = 0; $i < strlen($m); $i++) { + $patternMatch|= $leadingZeros & ($m[$i] === "\1"); + $leadingZeros&= $m[$i] === "\0"; + $offset+= $patternMatch ? 0 : 1; } - $m = ltrim($m, chr(0)); - if (ord($m[0]) != 1) { + + // we do & instead of && to avoid https://en.wikipedia.org/wiki/Short-circuit_evaluation + // to protect against timing attacks + if (!$hashesMatch & !$patternMatch) { user_error('Decryption error'); return false; } // Output the message M - return substr($m, 1); + return substr($m, $offset + 1); } /** diff --git a/lam/lib/3rdParty/phpseclib/Crypt/Random.php b/lam/lib/3rdParty/phpseclib/Crypt/Random.php index 6b29e751..01e34cc3 100644 --- a/lam/lib/3rdParty/phpseclib/Crypt/Random.php +++ b/lam/lib/3rdParty/phpseclib/Crypt/Random.php @@ -45,6 +45,10 @@ class Random */ static function string($length) { + if (!$length) { + return ''; + } + if (version_compare(PHP_VERSION, '7.0.0', '>=')) { try { return \random_bytes($length); diff --git a/lam/lib/3rdParty/phpseclib/Crypt/Twofish.php b/lam/lib/3rdParty/phpseclib/Crypt/Twofish.php index 3dd4ea1d..70980a2f 100644 --- a/lam/lib/3rdParty/phpseclib/Crypt/Twofish.php +++ b/lam/lib/3rdParty/phpseclib/Crypt/Twofish.php @@ -432,8 +432,10 @@ class Twofish extends Base $m2[$q1[$q0[$j] ^ $key[15]] ^ $key[7]] ^ $m3[$q1[$q1[$j] ^ $key[16]] ^ $key[8]]; $B = ($B << 8) | ($B >> 24 & 0xff); - $K[] = $A+= $B; - $K[] = (($A+= $B) << 9 | $A >> 23 & 0x1ff); + $A = $this->safe_intval($A + $B); + $K[] = $A; + $A = $this->safe_intval($A + $B); + $K[] = ($A << 9 | $A >> 23 & 0x1ff); } for ($i = 0; $i < 256; ++$i) { $S0[$i] = $m0[$q0[$q0[$i] ^ $s4] ^ $s0]; @@ -456,8 +458,10 @@ class Twofish extends Base $m2[$q1[$q0[$q0[$j] ^ $key[23]] ^ $key[15]] ^ $key[7]] ^ $m3[$q1[$q1[$q0[$j] ^ $key[24]] ^ $key[16]] ^ $key[8]]; $B = ($B << 8) | ($B >> 24 & 0xff); - $K[] = $A+= $B; - $K[] = (($A+= $B) << 9 | $A >> 23 & 0x1ff); + $A = $this->safe_intval($A + $B); + $K[] = $A; + $A = $this->safe_intval($A + $B); + $K[] = ($A << 9 | $A >> 23 & 0x1ff); } for ($i = 0; $i < 256; ++$i) { $S0[$i] = $m0[$q0[$q0[$q1[$i] ^ $s8] ^ $s4] ^ $s0]; @@ -481,8 +485,10 @@ class Twofish extends Base $m2[$q1[$q0[$q0[$q0[$j] ^ $key[31]] ^ $key[23]] ^ $key[15]] ^ $key[7]] ^ $m3[$q1[$q1[$q0[$q1[$j] ^ $key[32]] ^ $key[24]] ^ $key[16]] ^ $key[8]]; $B = ($B << 8) | ($B >> 24 & 0xff); - $K[] = $A+= $B; - $K[] = (($A+= $B) << 9 | $A >> 23 & 0x1ff); + $A = $this->safe_intval($A + $B); + $K[] = $A; + $A = $this->safe_intval($A + $B); + $K[] = ($A << 9 | $A >> 23 & 0x1ff); } for ($i = 0; $i < 256; ++$i) { $S0[$i] = $m0[$q0[$q0[$q1[$q1[$i] ^ $sc] ^ $s8] ^ $s4] ^ $s0]; @@ -578,9 +584,9 @@ class Twofish extends Base $S1[ $R1 & 0xff] ^ $S2[($R1 >> 8) & 0xff] ^ $S3[($R1 >> 16) & 0xff]; - $R2^= $t0 + $t1 + $K[++$ki]; + $R2^= $this->safe_intval($t0 + $t1 + $K[++$ki]); $R2 = ($R2 >> 1 & 0x7fffffff) | ($R2 << 31); - $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ ($t0 + ($t1 << 1) + $K[++$ki]); + $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ $this->safe_intval($t0 + ($t1 << 1) + $K[++$ki]); $t0 = $S0[ $R2 & 0xff] ^ $S1[($R2 >> 8) & 0xff] ^ @@ -590,9 +596,9 @@ class Twofish extends Base $S1[ $R3 & 0xff] ^ $S2[($R3 >> 8) & 0xff] ^ $S3[($R3 >> 16) & 0xff]; - $R0^= ($t0 + $t1 + $K[++$ki]); + $R0^= $this->safe_intval($t0 + $t1 + $K[++$ki]); $R0 = ($R0 >> 1 & 0x7fffffff) | ($R0 << 31); - $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ ($t0 + ($t1 << 1) + $K[++$ki]); + $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ $this->safe_intval($t0 + ($t1 << 1) + $K[++$ki]); } // @codingStandardsIgnoreStart @@ -634,9 +640,9 @@ class Twofish extends Base $S1[$R1 & 0xff] ^ $S2[$R1 >> 8 & 0xff] ^ $S3[$R1 >> 16 & 0xff]; - $R3^= $t0 + ($t1 << 1) + $K[--$ki]; + $R3^= $this->safe_intval($t0 + ($t1 << 1) + $K[--$ki]); $R3 = $R3 >> 1 & 0x7fffffff | $R3 << 31; - $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ ($t0 + $t1 + $K[--$ki]); + $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ $this->safe_intval($t0 + $t1 + $K[--$ki]); $t0 = $S0[$R2 & 0xff] ^ $S1[$R2 >> 8 & 0xff] ^ @@ -646,9 +652,9 @@ class Twofish extends Base $S1[$R3 & 0xff] ^ $S2[$R3 >> 8 & 0xff] ^ $S3[$R3 >> 16 & 0xff]; - $R1^= $t0 + ($t1 << 1) + $K[--$ki]; + $R1^= $this->safe_intval($t0 + ($t1 << 1) + $K[--$ki]); $R1 = $R1 >> 1 & 0x7fffffff | $R1 << 31; - $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ ($t0 + $t1 + $K[--$ki]); + $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ $this->safe_intval($t0 + $t1 + $K[--$ki]); } // @codingStandardsIgnoreStart @@ -679,6 +685,8 @@ class Twofish extends Base $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key); } + $safeint = $this->safe_intval_inline(); + if (!isset($lambda_functions[$code_hash])) { switch (true) { case $gen_hi_opt_code: @@ -727,9 +735,9 @@ class Twofish extends Base $S1[ $R1 & 0xff] ^ $S2[($R1 >> 8) & 0xff] ^ $S3[($R1 >> 16) & 0xff]; - $R2^= ($t0 + $t1 + '.$K[++$ki].'); + $R2^= ' . sprintf($safeint, '$t0 + $t1 + ' . $K[++$ki]) . '; $R2 = ($R2 >> 1 & 0x7fffffff) | ($R2 << 31); - $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ ($t0 + ($t1 << 1) + '.$K[++$ki].'); + $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ ' . sprintf($safeint, '($t0 + ($t1 << 1) + ' . $K[++$ki] . ')') . '; $t0 = $S0[ $R2 & 0xff] ^ $S1[($R2 >> 8) & 0xff] ^ @@ -739,16 +747,16 @@ class Twofish extends Base $S1[ $R3 & 0xff] ^ $S2[($R3 >> 8) & 0xff] ^ $S3[($R3 >> 16) & 0xff]; - $R0^= ($t0 + $t1 + '.$K[++$ki].'); + $R0^= ' . sprintf($safeint, '($t0 + $t1 + ' . $K[++$ki] . ')') . '; $R0 = ($R0 >> 1 & 0x7fffffff) | ($R0 << 31); - $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ ($t0 + ($t1 << 1) + '.$K[++$ki].'); + $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ ' . sprintf($safeint, '($t0 + ($t1 << 1) + ' . $K[++$ki] . ')') . '; '; } $encrypt_block.= ' - $in = pack("V4", '.$K[4].' ^ $R2, - '.$K[5].' ^ $R3, - '.$K[6].' ^ $R0, - '.$K[7].' ^ $R1); + $in = pack("V4", ' . $K[4] . ' ^ $R2, + ' . $K[5] . ' ^ $R3, + ' . $K[6] . ' ^ $R0, + ' . $K[7] . ' ^ $R1); '; // Generating decrypt code: @@ -769,9 +777,9 @@ class Twofish extends Base $S1[$R1 & 0xff] ^ $S2[$R1 >> 8 & 0xff] ^ $S3[$R1 >> 16 & 0xff]; - $R3^= $t0 + ($t1 << 1) + '.$K[--$ki].'; + $R3^= ' . sprintf($safeint, '$t0 + ($t1 << 1) + ' . $K[--$ki]) . '; $R3 = $R3 >> 1 & 0x7fffffff | $R3 << 31; - $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ ($t0 + $t1 + '.$K[--$ki].'); + $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ ' . sprintf($safeint, '($t0 + $t1 + '.$K[--$ki] . ')') . '; $t0 = $S0[$R2 & 0xff] ^ $S1[$R2 >> 8 & 0xff] ^ @@ -781,16 +789,16 @@ class Twofish extends Base $S1[$R3 & 0xff] ^ $S2[$R3 >> 8 & 0xff] ^ $S3[$R3 >> 16 & 0xff]; - $R1^= $t0 + ($t1 << 1) + '.$K[--$ki].'; + $R1^= ' . sprintf($safeint, '$t0 + ($t1 << 1) + ' . $K[--$ki]) . '; $R1 = $R1 >> 1 & 0x7fffffff | $R1 << 31; - $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ ($t0 + $t1 + '.$K[--$ki].'); + $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ ' . sprintf($safeint, '($t0 + $t1 + '.$K[--$ki] . ')') . '; '; } $decrypt_block.= ' - $in = pack("V4", '.$K[0].' ^ $R2, - '.$K[1].' ^ $R3, - '.$K[2].' ^ $R0, - '.$K[3].' ^ $R1); + $in = pack("V4", ' . $K[0] . ' ^ $R2, + ' . $K[1] . ' ^ $R3, + ' . $K[2] . ' ^ $R0, + ' . $K[3] . ' ^ $R1); '; $lambda_functions[$code_hash] = $this->_createInlineCryptFunction( diff --git a/lam/lib/3rdParty/phpseclib/File/ANSI.php b/lam/lib/3rdParty/phpseclib/File/ANSI.php index 1f3eecb3..5ff1f2ea 100644 --- a/lam/lib/3rdParty/phpseclib/File/ANSI.php +++ b/lam/lib/3rdParty/phpseclib/File/ANSI.php @@ -305,6 +305,9 @@ class ANSI case preg_match('#\x1B\[(\d+)D#', $this->ansi, $match): // Move cursor left n lines $this->old_x = $this->x; $this->x-= $match[1]; + if ($this->x < 0) { + $this->x = 0; + } break; case preg_match('#\x1B\[(\d+);(\d+)r#', $this->ansi, $match): // Set top and bottom lines of a window break; @@ -416,7 +419,7 @@ class ANSI if ($this->x > $this->max_x) { $this->x = 0; - $this->y++; + $this->_newLine(); } else { $this->x++; } diff --git a/lam/lib/3rdParty/phpseclib/File/ASN1.php b/lam/lib/3rdParty/phpseclib/File/ASN1.php index 504dbe8b..3aaa3090 100644 --- a/lam/lib/3rdParty/phpseclib/File/ASN1.php +++ b/lam/lib/3rdParty/phpseclib/File/ASN1.php @@ -25,6 +25,8 @@ namespace phpseclib\File; use phpseclib\File\ASN1\Element; use phpseclib\Math\BigInteger; +use DateTime; +use DateTimeZone; /** * Pure-PHP ASN.1 Parser @@ -242,9 +244,10 @@ class ASN1 $tag = 0; // process septets (since the eighth bit is ignored, it's not an octet) do { - $loop = ord($encoded[0]) >> 7; + $temp = ord($encoded[$encoded_pos++]); + $loop = $temp >> 7; $tag <<= 7; - $tag |= ord($encoded[$encoded_pos++]) & 0x7F; + $tag |= $temp & 0x7F; $start++; } while ($loop); } @@ -306,6 +309,9 @@ class ASN1 $remainingLength = $length; while ($remainingLength > 0) { $temp = $this->_decode_ber($content, $start, $content_pos); + if ($temp === false) { + break; + } $length = $temp['length']; // end-of-content octets - see paragraph 8.1.5 if (substr($content, $content_pos + $length, 2) == "\0\0") { @@ -357,6 +363,9 @@ class ASN1 $current['content'] = substr($content, $content_pos); } else { $temp = $this->_decode_ber($content, $start, $content_pos); + if ($temp === false) { + return false; + } $length-= (strlen($content) - $content_pos); $last = count($temp) - 1; for ($i = 0; $i < $last; $i++) { @@ -381,6 +390,9 @@ class ASN1 $length = 0; while (substr($content, $content_pos, 2) != "\0\0") { $temp = $this->_decode_ber($content, $length + $start, $content_pos); + if ($temp === false) { + return false; + } $content_pos += $temp['length']; // all subtags should be octet strings //if ($temp['type'] != self::TYPE_OCTET_STRING) { @@ -413,30 +425,16 @@ class ASN1 break 2; } $temp = $this->_decode_ber($content, $start + $offset, $content_pos); + if ($temp === false) { + return false; + } $content_pos += $temp['length']; $current['content'][] = $temp; $offset+= $temp['length']; } break; case self::TYPE_OBJECT_IDENTIFIER: - $temp = ord($content[$content_pos++]); - $current['content'] = sprintf('%d.%d', floor($temp / 40), $temp % 40); - $valuen = 0; - // process septets - $content_len = strlen($content); - while ($content_pos < $content_len) { - $temp = ord($content[$content_pos++]); - $valuen <<= 7; - $valuen |= $temp & 0x7F; - if (~$temp & 0x80) { - $current['content'].= ".$valuen"; - $valuen = 0; - } - } - // the eighth bit of the last byte should not be 1 - //if ($temp >> 7) { - // return false; - //} + $current['content'] = $this->_decodeOID(substr($content, $content_pos)); break; /* Each character string type shall be encoded as if it had been declared: [UNIVERSAL x] IMPLICIT OCTET STRING @@ -580,7 +578,7 @@ class ASN1 $childClass = $tempClass = self::CLASS_UNIVERSAL; $constant = null; if (isset($temp['constant'])) { - $tempClass = isset($temp['class']) ? $temp['class'] : self::CLASS_CONTEXT_SPECIFIC; + $tempClass = $temp['type']; } if (isset($child['class'])) { $childClass = $child['class']; @@ -643,7 +641,7 @@ class ASN1 $temp = $decoded['content'][$i]; $tempClass = self::CLASS_UNIVERSAL; if (isset($temp['constant'])) { - $tempClass = isset($temp['class']) ? $temp['class'] : self::CLASS_CONTEXT_SPECIFIC; + $tempClass = $temp['type']; } foreach ($mapping['children'] as $key => $child) { @@ -707,7 +705,7 @@ class ASN1 if (isset($mapping['implicit'])) { $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']); } - return @date($this->format, $decoded['content']); + return $decoded['content'] ? $decoded['content']->format($this->format) : false; case self::TYPE_BIT_STRING: if (isset($mapping['mapping'])) { $offset = ord($decoded['content'][0]); @@ -956,7 +954,8 @@ class ASN1 case self::TYPE_GENERALIZED_TIME: $format = $mapping['type'] == self::TYPE_UTC_TIME ? 'y' : 'Y'; $format.= 'mdHis'; - $value = @gmdate($format, strtotime($source)) . 'Z'; + $date = new DateTime($source, new DateTimeZone('GMT')); + $value = $date->format($format) . 'Z'; break; case self::TYPE_BIT_STRING: if (isset($mapping['mapping'])) { @@ -998,27 +997,7 @@ class ASN1 $value = base64_decode($source); break; case self::TYPE_OBJECT_IDENTIFIER: - $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids); - if ($oid === false) { - user_error('Invalid OID'); - return false; - } - $value = ''; - $parts = explode('.', $oid); - $value = chr(40 * $parts[0] + $parts[1]); - for ($i = 2; $i < count($parts); $i++) { - $temp = ''; - if (!$parts[$i]) { - $temp = "\0"; - } else { - while ($parts[$i]) { - $temp = chr(0x80 | ($parts[$i] & 0x7F)) . $temp; - $parts[$i] >>= 7; - } - $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F); - } - $value.= $temp; - } + $value = $this->_encodeOID($source); break; case self::TYPE_ANY: $loc = $this->location; @@ -1117,6 +1096,108 @@ class ASN1 return pack('Ca*', 0x80 | strlen($temp), $temp); } + /** + * BER-decode the OID + * + * Called by _decode_ber() + * + * @access private + * @param string $content + * @return string + */ + function _decodeOID($content) + { + static $eighty; + if (!$eighty) { + $eighty = new BigInteger(80); + } + + $oid = array(); + $pos = 0; + $len = strlen($content); + $n = new BigInteger(); + while ($pos < $len) { + $temp = ord($content[$pos++]); + $n = $n->bitwise_leftShift(7); + $n = $n->bitwise_or(new BigInteger($temp & 0x7F)); + if (~$temp & 0x80) { + $oid[] = $n; + $n = new BigInteger(); + } + } + $part1 = array_shift($oid); + $first = floor(ord($content[0]) / 40); + /* + "This packing of the first two object identifier components recognizes that only three values are allocated from the root + node, and at most 39 subsequent values from nodes reached by X = 0 and X = 1." + + -- https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=22 + */ + if ($first <= 2) { // ie. 0 <= ord($content[0]) < 120 (0x78) + array_unshift($oid, ord($content[0]) % 40); + array_unshift($oid, $first); + } else { + array_unshift($oid, $part1->subtract($eighty)); + array_unshift($oid, 2); + } + + return implode('.', $oid); + } + + /** + * DER-encode the OID + * + * Called by _encode_der() + * + * @access private + * @param string $content + * @return string + */ + function _encodeOID($source) + { + static $mask, $zero, $forty; + if (!$mask) { + $mask = new BigInteger(0x7F); + $zero = new BigInteger(); + $forty = new BigInteger(40); + } + + $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids); + if ($oid === false) { + user_error('Invalid OID'); + return false; + } + $parts = explode('.', $oid); + $part1 = array_shift($parts); + $part2 = array_shift($parts); + + $first = new BigInteger($part1); + $first = $first->multiply($forty); + $first = $first->add(new BigInteger($part2)); + + array_unshift($parts, $first->toString()); + + $value = ''; + foreach ($parts as $part) { + if (!$part) { + $temp = "\0"; + } else { + $temp = ''; + $part = new BigInteger($part); + while (!$part->equals($zero)) { + $submask = $part->bitwise_and($mask); + $submask->setPrecision(8); + $temp = (chr(0x80) | $submask->toBytes()) . $temp; + $part = $part->bitwise_rightShift(7); + } + $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F); + } + $value.= $temp; + } + + return $value; + } + /** * BER-decode the time * @@ -1137,33 +1218,32 @@ class ASN1 http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2 http://www.obj-sys.com/asn1tutorial/node14.html */ - $pattern = $tag == self::TYPE_UTC_TIME ? - '#^(..)(..)(..)(..)(..)(..)?(.*)$#' : - '#(....)(..)(..)(..)(..)(..).*([Z+-].*)$#'; - - preg_match($pattern, $content, $matches); - - list(, $year, $month, $day, $hour, $minute, $second, $timezone) = $matches; + $format = 'YmdHis'; if ($tag == self::TYPE_UTC_TIME) { - $year = $year >= 50 ? "19$year" : "20$year"; - } - - if ($timezone == 'Z') { - $mktime = 'gmmktime'; - $timezone = 0; - } elseif (preg_match('#([+-])(\d\d)(\d\d)#', $timezone, $matches)) { - $mktime = 'gmmktime'; - $timezone = 60 * $matches[3] + 3600 * $matches[2]; - if ($matches[1] == '-') { - $timezone = -$timezone; + // https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=28 says "the seconds + // element shall always be present" but none-the-less I've seen X509 certs where it isn't and if the + // browsers parse it phpseclib ought to too + if (preg_match('#^(\d{10})(Z|[+-]\d{4})$#', $content, $matches)) { + $content = $matches[1] . '00' . $matches[2]; } - } else { - $mktime = 'mktime'; - $timezone = 0; + $prefix = substr($content, 0, 2) >= 50 ? '19' : '20'; + $content = $prefix . $content; + } elseif (strpos($content, '.') !== false) { + $format.= '.u'; } - return @$mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year) + $timezone; + if ($content[strlen($content) - 1] == 'Z') { + $content = substr($content, 0, -1) . '+0000'; + } + + if (strpos($content, '-') !== false || strpos($content, '+') !== false) { + $format.= 'O'; + } + + // error supression isn't necessary as of PHP 7.0: + // http://php.net/manual/en/migration70.other-changes.php + return @DateTime::createFromFormat($format, $content); } /** diff --git a/lam/lib/3rdParty/phpseclib/File/X509.php b/lam/lib/3rdParty/phpseclib/File/X509.php index 9a70457b..4ebafa17 100644 --- a/lam/lib/3rdParty/phpseclib/File/X509.php +++ b/lam/lib/3rdParty/phpseclib/File/X509.php @@ -31,6 +31,8 @@ use phpseclib\Crypt\Random; use phpseclib\Crypt\RSA; use phpseclib\File\ASN1\Element; use phpseclib\Math\BigInteger; +use DateTime; +use DateTimeZone; /** * Pure-PHP X.509 Parser @@ -303,6 +305,22 @@ class X509 */ var $challenge; + /** + * Recursion Limit + * + * @var int + * @access private + */ + static $recur_limit = 5; + + /** + * URL fetch flag + * + * @var bool + * @access private + */ + static $disable_url_fetch = false; + /** * Default Constructor. * @@ -1910,6 +1928,9 @@ class X509 // "Certificate Transparency" // https://tools.ietf.org/html/rfc6962 case '1.3.6.1.4.1.11129.2.4.2': + // "Qualified Certificate statements" + // https://tools.ietf.org/html/rfc3739#section-3.2.6 + case '1.3.6.1.5.5.7.1.3': return true; // CSR attributes @@ -2030,30 +2051,32 @@ class X509 } if ($names = $this->getExtension('id-ce-subjectAltName')) { - foreach ($names as $key => $value) { - $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value); - switch ($key) { - case 'dNSName': - /* From RFC2818 "HTTP over TLS": + foreach ($names as $name) { + foreach ($name as $key => $value) { + $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value); + switch ($key) { + case 'dNSName': + /* From RFC2818 "HTTP over TLS": - If a subjectAltName extension of type dNSName is present, that MUST - be used as the identity. Otherwise, the (most specific) Common Name - field in the Subject field of the certificate MUST be used. Although - the use of the Common Name is existing practice, it is deprecated and - Certification Authorities are encouraged to use the dNSName instead. */ - if (preg_match('#^' . $value . '$#', $components['host'])) { - return true; - } - break; - case 'iPAddress': - /* From RFC2818 "HTTP over TLS": + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. */ + if (preg_match('#^' . $value . '$#', $components['host'])) { + return true; + } + break; + case 'iPAddress': + /* From RFC2818 "HTTP over TLS": - In some cases, the URI is specified as an IP address rather than a - hostname. In this case, the iPAddress subjectAltName must be present - in the certificate and must exactly match the IP in the URI. */ - if (preg_match('#(?:\d{1-3}\.){4}#', $components['host'] . '.') && preg_match('#^' . $value . '$#', $components['host'])) { - return true; - } + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. */ + if (preg_match('#(?:\d{1-3}\.){4}#', $components['host'] . '.') && preg_match('#^' . $value . '$#', $components['host'])) { + return true; + } + } } } return false; @@ -2072,7 +2095,7 @@ class X509 * * If $date isn't defined it is assumed to be the current date. * - * @param int $date optional + * @param \DateTime|string $date optional * @access public */ function validateDate($date = null) @@ -2082,7 +2105,7 @@ class X509 } if (!isset($date)) { - $date = time(); + $date = new DateTime(null, new DateTimeZone(@date_default_timezone_get())); } $notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore']; @@ -2091,15 +2114,133 @@ class X509 $notAfter = $this->currentCert['tbsCertificate']['validity']['notAfter']; $notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime']; + if (is_string($date)) { + $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get())); + } + + $notBefore = new DateTime($notBefore, new DateTimeZone(@date_default_timezone_get())); + $notAfter = new DateTime($notAfter, new DateTimeZone(@date_default_timezone_get())); + switch (true) { - case $date < @strtotime($notBefore): - case $date > @strtotime($notAfter): + case $date < $notBefore: + case $date > $notAfter: return false; } return true; } + /** + * Fetches a URL + * + * @param string $url + * @access private + * @return bool|string + */ + static function _fetchURL($url) + { + if (self::$disable_url_fetch) { + return false; + } + + $parts = parse_url($url); + $data = ''; + switch ($parts['scheme']) { + case 'http': + $fsock = @fsockopen($parts['host'], isset($parts['port']) ? $parts['port'] : 80); + if (!$fsock) { + return false; + } + fputs($fsock, "GET $parts[path] HTTP/1.0\r\n"); + fputs($fsock, "Host: $parts[host]\r\n\r\n"); + $line = fgets($fsock, 1024); + if (strlen($line) < 3) { + return false; + } + preg_match('#HTTP/1.\d (\d{3})#', $line, $temp); + if ($temp[1] != '200') { + return false; + } + + // skip the rest of the headers in the http response + while (!feof($fsock) && fgets($fsock, 1024) != "\r\n") { + } + + while (!feof($fsock)) { + $data.= fread($fsock, 1024); + } + + break; + //case 'ftp': + //case 'ldap': + //default: + } + + return $data; + } + + /** + * Validates an intermediate cert as identified via authority info access extension + * + * See https://tools.ietf.org/html/rfc4325 for more info + * + * @param bool $caonly + * @param int $count + * @access private + * @return bool + */ + function _testForIntermediate($caonly, $count) + { + $opts = $this->getExtension('id-pe-authorityInfoAccess'); + if (!is_array($opts)) { + return false; + } + foreach ($opts as $opt) { + if ($opt['accessMethod'] == 'id-ad-caIssuers') { + // accessLocation is a GeneralName. GeneralName fields support stuff like email addresses, IP addresses, LDAP, + // etc, but we're only supporting URI's. URI's and LDAP are the only thing https://tools.ietf.org/html/rfc4325 + // discusses + if (isset($opt['accessLocation']['uniformResourceIdentifier'])) { + $url = $opt['accessLocation']['uniformResourceIdentifier']; + break; + } + } + } + + if (!isset($url)) { + return false; + } + + $cert = static::_fetchURL($url); + if (!is_string($cert)) { + return false; + } + + $parent = new static(); + $parent->CAs = $this->CAs; + /* + "Conforming applications that support HTTP or FTP for accessing + certificates MUST be able to accept .cer files and SHOULD be able + to accept .p7c files." -- https://tools.ietf.org/html/rfc4325 + + A .p7c file is 'a "certs-only" CMS message as specified in RFC 2797" + + These are currently unsupported + */ + if (!is_array($parent->loadX509($cert))) { + return false; + } + + if (!$parent->_validateSignatureCountable($caonly, ++$count)) { + return false; + } + + $this->CAs[] = $parent->currentCert; + //$this->loadCA($cert); + + return true; + } + /** * Validate a signature * @@ -2116,11 +2257,30 @@ class X509 * @return mixed */ function validateSignature($caonly = true) + { + return $this->_validateSignatureCountable($caonly, 0); + } + + /** + * Validate a signature + * + * Performs said validation whilst keeping track of how many times validation method is called + * + * @param bool $caonly + * @param int $count + * @access private + * @return mixed + */ + function _validateSignatureCountable($caonly, $count) { if (!is_array($this->currentCert) || !isset($this->signatureSubject)) { return null; } + if ($count == self::$recur_limit) { + return false; + } + /* TODO: "emailAddress attribute values are not case-sensitive (e.g., "subscriber@example.com" is the same as "SUBSCRIBER@EXAMPLE.COM")." -- http://tools.ietf.org/html/rfc5280#section-4.1.2.6 @@ -2137,7 +2297,8 @@ class X509 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier'); switch (true) { case !is_array($authorityKey): - case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: + case !$subjectKeyID: + case isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: $signingCert = $this->currentCert; // working cert } } @@ -2154,17 +2315,21 @@ class X509 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca); switch (true) { case !is_array($authorityKey): - case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: + case !$subjectKeyID: + case isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: + if (is_array($authorityKey) && isset($authorityKey['authorityCertSerialNumber']) && !$authorityKey['authorityCertSerialNumber']->equals($ca['tbsCertificate']['serialNumber'])) { + break 2; // serial mismatch - check other ca + } $signingCert = $ca; // working cert break 3; } } } if (count($this->CAs) == $i && $caonly) { - return false; + return $this->_testForIntermediate($caonly, $count) && $this->validateSignature($caonly); } } elseif (!isset($signingCert) || $caonly) { - return false; + return $this->_testForIntermediate($caonly, $count) && $this->validateSignature($caonly); } return $this->_validateSignature( $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], @@ -2200,7 +2365,11 @@ class X509 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca); switch (true) { case !is_array($authorityKey): - case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: + case !$subjectKeyID: + case isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: + if (is_array($authorityKey) && isset($authorityKey['authorityCertSerialNumber']) && !$authorityKey['authorityCertSerialNumber']->equals($ca['tbsCertificate']['serialNumber'])) { + break 2; // serial mismatch - check other ca + } $signingCert = $ca; // working cert break 3; } @@ -2267,6 +2436,41 @@ class X509 return true; } + /** + * Sets the recursion limit + * + * When validating a signature it may be necessary to download intermediate certs from URI's. + * An intermediate cert that linked to itself would result in an infinite loop so to prevent + * that we set a recursion limit. A negative number means that there is no recursion limit. + * + * @param int $count + * @access public + */ + static function setRecurLimit($count) + { + self::$recur_limit = $count; + } + + /** + * Prevents URIs from being automatically retrieved + * + * @access public + */ + static function disableURLFetch() + { + self::$disable_url_fetch = true; + } + + /** + * Allows URIs to be automatically retrieved + * + * @access public + */ + static function enableURLFetch() + { + self::$disable_url_fetch = false; + } + /** * Reformat public keys * @@ -2472,6 +2676,10 @@ class X509 } $dn = array_values($dn); + // fix for https://bugs.php.net/75433 affecting PHP 7.2 + if (!isset($dn[0])) { + $dn = array_splice($dn, 0, 0); + } } /** @@ -2715,12 +2923,14 @@ class X509 $value = array_pop($value); // Always strip data type. } } elseif (is_object($value) && $value instanceof Element) { - $callback = create_function('$x', 'return "\x" . bin2hex($x[0]);'); + $callback = function ($x) { + return "\x" . bin2hex($x[0]); + }; $value = strtoupper(preg_replace_callback('#[^\x20-\x7E]#', $callback, $value->element)); } $output.= $desc . '=' . $value; $result[$desc] = isset($result[$desc]) ? - array_merge((array) $dn[$prop], array($value)) : + array_merge((array) $result[$desc], array($value)) : $value; $start = false; } @@ -3338,7 +3548,11 @@ class X509 */ function _timeField($date) { - $year = @gmdate("Y", @strtotime($date)); // the same way ASN1.php parses this + if ($date instanceof Element) { + return $date; + } + $dateObj = new DateTime($date, new DateTimeZone('GMT')); + $year = $dateObj->format('Y'); // the same way ASN1.php parses this if ($year < 2050) { return array('utcTime' => $date); } else { @@ -3403,8 +3617,12 @@ class X509 return false; } - $startDate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O'); - $endDate = !empty($this->endDate) ? $this->endDate : @date('D, d M Y H:i:s O', strtotime('+1 year')); + $startDate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); + $startDate = !empty($this->startDate) ? $this->startDate : $startDate->format('D, d M Y H:i:s O'); + + $endDate = new DateTime('+1 year', new DateTimeZone(@date_default_timezone_get())); + $endDate = !empty($this->endDate) ? $this->endDate : $endDate->format('D, d M Y H:i:s O'); + /* "The serial number MUST be a positive integer" "Conforming CAs MUST NOT use serialNumber values longer than 20 octets." -- https://tools.ietf.org/html/rfc5280#section-4.1.2.2 @@ -3420,7 +3638,7 @@ class X509 'tbsCertificate' => array( 'version' => 'v3', - 'serialNumber' => $serialNumber, // $this->setserialNumber() + 'serialNumber' => $serialNumber, // $this->setSerialNumber() 'signature' => array('algorithm' => $signatureAlgorithm), 'issuer' => false, // this is going to be overwritten later 'validity' => array( @@ -3672,7 +3890,9 @@ class X509 $currentCert = isset($this->currentCert) ? $this->currentCert : null; $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null; - $thisUpdate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O'); + + $thisUpdate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); + $thisUpdate = !empty($this->startDate) ? $this->startDate : $thisUpdate->format('D, d M Y H:i:s O'); if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) { $this->currentCert = $crl->currentCert; @@ -3823,7 +4043,11 @@ class X509 */ function setStartDate($date) { - $this->startDate = @date('D, d M Y H:i:s O', @strtotime($date)); + if (!is_object($date) || !is_a($date, 'DateTime')) { + $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get())); + } + + $this->startDate = $date->format('D, d M Y H:i:s O'); } /** @@ -3847,7 +4071,11 @@ class X509 $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp; $this->endDate = new Element($temp); } else { - $this->endDate = @date('D, d M Y H:i:s O', @strtotime($date)); + if (!is_object($date) || !is_a($date, 'DateTime')) { + $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get())); + } + + $this->endDate = $date->format('D, d M Y H:i:s O'); } } @@ -4057,6 +4285,10 @@ class X509 } $extensions = array_values($extensions); + // fix for https://bugs.php.net/75433 affecting PHP 7.2 + if (!isset($extensions[0])) { + $extensions = array_splice($extensions, 0, 0); + } return $result; } @@ -4577,8 +4809,9 @@ class X509 } $i = count($rclist); + $revocationDate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); $rclist[] = array('userCertificate' => $serial, - 'revocationDate' => $this->_timeField(@date('D, d M Y H:i:s O'))); + 'revocationDate' => $this->_timeField($revocationDate->format('D, d M Y H:i:s O'))); return $i; } diff --git a/lam/lib/3rdParty/phpseclib/Math/BigInteger.php b/lam/lib/3rdParty/phpseclib/Math/BigInteger.php index 4b13d7c6..fe1f43c5 100644 --- a/lam/lib/3rdParty/phpseclib/Math/BigInteger.php +++ b/lam/lib/3rdParty/phpseclib/Math/BigInteger.php @@ -45,7 +45,6 @@ * @author Jim Wigginton * @copyright 2006 Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License - * @link http://pear.php.net/package/Math_BigInteger */ namespace phpseclib\Math; @@ -266,23 +265,27 @@ class BigInteger if (extension_loaded('openssl') && !defined('MATH_BIGINTEGER_OPENSSL_DISABLE') && !defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) { // some versions of XAMPP have mismatched versions of OpenSSL which causes it not to work - ob_start(); - @phpinfo(); - $content = ob_get_contents(); - ob_end_clean(); - - preg_match_all('#OpenSSL (Header|Library) Version(.*)#im', $content, $matches); - $versions = array(); - if (!empty($matches[1])) { - for ($i = 0; $i < count($matches[1]); $i++) { - $fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i]))); - // Remove letter part in OpenSSL version - if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) { - $versions[$matches[1][$i]] = $fullVersion; - } else { - $versions[$matches[1][$i]] = $m[0]; + // avoid generating errors (even with suppression) when phpinfo() is disabled (common in production systems) + if (strpos(ini_get('disable_functions'), 'phpinfo') === false) { + ob_start(); + @phpinfo(); + $content = ob_get_contents(); + ob_end_clean(); + + preg_match_all('#OpenSSL (Header|Library) Version(.*)#im', $content, $matches); + + if (!empty($matches[1])) { + for ($i = 0; $i < count($matches[1]); $i++) { + $fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i]))); + + // Remove letter part in OpenSSL version + if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) { + $versions[$matches[1][$i]] = $fullVersion; + } else { + $versions[$matches[1][$i]] = $m[0]; + } } } } @@ -360,8 +363,12 @@ class BigInteger case 256: switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: - $sign = $this->is_negative ? '-' : ''; - $this->value = gmp_init($sign . '0x' . bin2hex($x)); + $this->value = function_exists('gmp_import') ? + gmp_import($x) : + gmp_init('0x' . bin2hex($x)); + if ($this->is_negative) { + $this->value = gmp_neg($this->value); + } break; case self::MODE_BCMATH: // round $len to the nearest 4 (thanks, DavidMJ!) @@ -438,6 +445,9 @@ class BigInteger // (?<=^|-)0*: find any 0's that are preceded by the start of the string or by a - (ie. octals) // [^-0-9].*: find any non-numeric characters and then any characters that follow that $x = preg_replace('#(?add(new static(1)) : $this->copy(); $bytes = $temp->toBytes(); - if (empty($bytes)) { // eg. if the number we're trying to convert is -1 + if (!strlen($bytes)) { // eg. if the number we're trying to convert is -1 $bytes = chr(0); } @@ -548,9 +558,13 @@ class BigInteger return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; } - $temp = gmp_strval(gmp_abs($this->value), 16); - $temp = (strlen($temp) & 1) ? '0' . $temp : $temp; - $temp = pack('H*', $temp); + if (function_exists('gmp_export')) { + $temp = gmp_export($this->value); + } else { + $temp = gmp_strval(gmp_abs($this->value), 16); + $temp = (strlen($temp) & 1) ? '0' . $temp : $temp; + $temp = pack('H*', $temp); + } return $this->precision > 0 ? substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : @@ -1547,7 +1561,9 @@ class BigInteger $temp_value = array($quotient_value[$q_index]); $temp = $temp->multiply($y); $temp_value = &$temp->value; - $temp_value = array_merge($adjust, $temp_value); + if (count($temp_value)) { + $temp_value = array_merge($adjust, $temp_value); + } $x = $x->subtract($temp); @@ -2680,7 +2696,14 @@ class BigInteger { switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: - return gmp_cmp($this->value, $y->value); + $r = gmp_cmp($this->value, $y->value); + if ($r < -1) { + $r = -1; + } + if ($r > 1) { + $r = 1; + } + return $r; case self::MODE_BCMATH: return bccomp($this->value, $y->value, 0); } @@ -2860,8 +2883,7 @@ class BigInteger switch (MATH_BIGINTEGER_MODE) { case self::MODE_GMP: $temp = new static(); - $temp->value = gmp_xor($this->value, $x->value); - + $temp->value = gmp_xor(gmp_abs($this->value), gmp_abs($x->value)); return $this->_normalize($temp); case self::MODE_BCMATH: $left = $this->toBytes(); @@ -2877,6 +2899,7 @@ class BigInteger $length = max(count($this->value), count($x->value)); $result = $this->copy(); + $result->is_negative = false; $result->value = array_pad($result->value, $length, 0); $x->value = array_pad($x->value, $length, 0); @@ -2900,7 +2923,7 @@ class BigInteger // (will always result in a smaller number. ie. ~1 isn't 1111 1110 - it's 0) $temp = $this->toBytes(); if ($temp == '') { - return ''; + return $this->_normalize(new static()); } $pre_msb = decbin(ord($temp[0])); $temp = ~$temp; @@ -3435,7 +3458,7 @@ class BigInteger break; } } - $s = 26 * $i + $j - 1; + $s = 26 * $i + $j; $r->_rshift($s); } @@ -3561,6 +3584,7 @@ class BigInteger $value = &$result->value; if (!count($value)) { + $result->is_negative = false; return $result; } diff --git a/lam/lib/3rdParty/phpseclib/Net/SCP.php b/lam/lib/3rdParty/phpseclib/Net/SCP.php index f95bce6d..cf13496c 100644 --- a/lam/lib/3rdParty/phpseclib/Net/SCP.php +++ b/lam/lib/3rdParty/phpseclib/Net/SCP.php @@ -144,6 +144,11 @@ class SCP return false; } + if (empty($remote_file)) { + user_error('remote_file cannot be blank', E_USER_NOTICE); + return false; + } + if (!$this->ssh->exec('scp -t ' . escapeshellarg($remote_file), false)) { // -t = to return false; } diff --git a/lam/lib/3rdParty/phpseclib/Net/SFTP.php b/lam/lib/3rdParty/phpseclib/Net/SFTP.php index 1421ecb4..a248c258 100644 --- a/lam/lib/3rdParty/phpseclib/Net/SFTP.php +++ b/lam/lib/3rdParty/phpseclib/Net/SFTP.php @@ -109,11 +109,11 @@ class SFTP extends SSH2 * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support * concurrent actions, so it's somewhat academic, here. * - * @var int + * @var boolean * @see self::_send_sftp_packet() * @access private */ - var $request_id = false; + var $use_request_id = false; /** * The Packet Type @@ -158,7 +158,7 @@ class SFTP extends SSH2 * Current working directory * * @var string - * @see self::_realpath() + * @see self::realpath() * @see self::chdir() * @access private */ @@ -187,7 +187,7 @@ class SFTP extends SSH2 * * @see self::getSFTPErrors() * @see self::getLastSFTPError() - * @var string + * @var array * @access private */ var $sftp_errors = array(); @@ -236,6 +236,29 @@ class SFTP extends SSH2 */ var $sortOptions = array(); + /** + * Canonicalization Flag + * + * Determines whether or not paths should be canonicalized before being + * passed on to the remote server. + * + * @see self::enablePathCanonicalization() + * @see self::disablePathCanonicalization() + * @see self::realpath() + * @var bool + * @access private + */ + var $canonicalize_paths = true; + + /** + * Request Buffers + * + * @see self::_get_sftp_packet() + * @var array + * @access private + */ + var $requestBuffer = array(); + /** * Default Constructor. * @@ -335,7 +358,7 @@ class SFTP extends SSH2 // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in // two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000. // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored. - -1 << 31 => 'NET_SFTP_ATTR_EXTENDED' + (-1 << 31) & 0xFFFFFFFF => 'NET_SFTP_ATTR_EXTENDED' ); // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 // the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name @@ -409,7 +432,7 @@ class SFTP extends SSH2 $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN; - $response = $this->_get_channel_packet(self::CHANNEL); + $response = $this->_get_channel_packet(self::CHANNEL, true); if ($response === false) { return false; } @@ -430,7 +453,7 @@ class SFTP extends SSH2 $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST; - $response = $this->_get_channel_packet(self::CHANNEL); + $response = $this->_get_channel_packet(self::CHANNEL, true); if ($response === false) { // from PuTTY's psftp.exe $command = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" . @@ -454,7 +477,7 @@ class SFTP extends SSH2 $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST; - $response = $this->_get_channel_packet(self::CHANNEL); + $response = $this->_get_channel_packet(self::CHANNEL, true); if ($response === false) { return false; } @@ -505,7 +528,7 @@ class SFTP extends SSH2 } */ - $this->request_id = 1; + $this->use_request_id = true; /* A Note on SFTPv4/5/6 support: @@ -575,6 +598,26 @@ class SFTP extends SSH2 $this->stat_cache = array(); } + /** + * Enable path canonicalization + * + * @access public + */ + function enablePathCanonicalization() + { + $this->canonicalize_paths = true; + } + + /** + * Enable path canonicalization + * + * @access public + */ + function disablePathCanonicalization() + { + $this->canonicalize_paths = false; + } + /** * Returns the current directory name * @@ -633,13 +676,20 @@ class SFTP extends SSH2 * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it. Returns * the absolute (canonicalized) path. * + * If canonicalize_paths has been disabled using disablePathCanonicalization(), $path is returned as-is. + * * @see self::chdir() + * @see self::disablePathCanonicalization() * @param string $path * @return mixed * @access private */ function _realpath($path) { + if (!$this->canonicalize_paths) { + return $path; + } + if ($this->pwd === false) { // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9 if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($path), $path))) { @@ -792,6 +842,7 @@ class SFTP extends SSH2 } if (is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $value)))) { $temp = $this->_nlist_helper($dir . '/' . $value, true, $relativeDir . $value . '/'); + $temp = is_array($temp) ? $temp : array(); $result = array_merge($result, $temp); } else { $result[] = $relativeDir . $value; @@ -823,7 +874,17 @@ class SFTP extends SSH2 unset($files[$key]); continue; } - if ($key != '.' && $key != '..' && is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $key)))) { + $is_directory = false; + if ($key != '.' && $key != '..') { + if ($this->use_stat_cache) { + $is_directory = is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $key))); + } else { + $stat = $this->lstat($dir . '/' . $key); + $is_directory = $stat && $stat['type'] === NET_SFTP_TYPE_DIRECTORY; + } + } + + if ($is_directory) { $depth++; $files[$key] = $this->rawlist($dir . '/' . $key, true); $depth--; @@ -1106,7 +1167,7 @@ class SFTP extends SSH2 $temp[$dir] = array(); } if ($i === $max) { - if (is_object($temp[$dir])) { + if (is_object($temp[$dir]) && is_object($value)) { if (!isset($value->stat) && isset($temp[$dir]->stat)) { $value->stat = $temp[$dir]->stat; } @@ -1294,7 +1355,7 @@ class SFTP extends SSH2 /** * Returns general information about a file or symbolic link * - * Determines information without calling \phpseclib\Net\SFTP::_realpath(). + * Determines information without calling \phpseclib\Net\SFTP::realpath(). * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT. * * @param string $filename @@ -1455,7 +1516,7 @@ class SFTP extends SSH2 return true; } - $filename = $this->_realPath($filename); + $filename = $this->realpath($filename); // rather than return what the permissions *should* be, we'll return what they actually are. this will also // tell us if the file actually exists. // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following: @@ -1938,7 +1999,7 @@ class SFTP extends SSH2 if (isset($fp)) { $stat = fstat($fp); - $size = $stat['size']; + $size = !empty($stat) ? $stat['size'] : 0; if ($local_start >= 0) { fseek($fp, $local_start); @@ -2087,10 +2148,11 @@ class SFTP extends SSH2 * @param string $local_file * @param int $offset * @param int $length + * @param callable|null $progressCallback * @return mixed * @access public */ - function get($remote_file, $local_file = false, $offset = 0, $length = -1) + function get($remote_file, $local_file = false, $offset = 0, $length = -1, $progressCallback = null) { if (!($this->bitmap & SSH2::MASK_LOGIN)) { return false; @@ -2148,7 +2210,7 @@ class SFTP extends SSH2 $packet_size = $length > 0 ? min($this->max_sftp_packet, $length - $read) : $this->max_sftp_packet; $packet = pack('Na*N3', strlen($handle), $handle, $tempoffset / 4294967296, $tempoffset, $packet_size); - if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet)) { + if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet, $i)) { if ($fclose_check) { fclose($fp); } @@ -2156,6 +2218,9 @@ class SFTP extends SSH2 } $packet = null; $read+= $packet_size; + if (is_callable($progressCallback)) { + call_user_func($progressCallback, $read); + } $i++; } @@ -2163,15 +2228,17 @@ class SFTP extends SSH2 break; } + $packets_sent = $i - 1; + $clear_responses = false; while ($i > 0) { $i--; if ($clear_responses) { - $this->_get_sftp_packet(); + $this->_get_sftp_packet($packets_sent - $i); continue; } else { - $response = $this->_get_sftp_packet(); + $response = $this->_get_sftp_packet($packets_sent - $i); } switch ($this->packet_type) { @@ -2880,10 +2947,10 @@ class SFTP extends SSH2 * @return bool * @access private */ - function _send_sftp_packet($type, $data) + function _send_sftp_packet($type, $data, $request_id = 1) { - $packet = $this->request_id !== false ? - pack('NCNa*', strlen($data) + 5, $type, $this->request_id, $data) : + $packet = $this->use_request_id ? + pack('NCNa*', strlen($data) + 5, $type, $request_id, $data) : pack('NCa*', strlen($data) + 1, $type, $data); $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 @@ -2921,15 +2988,24 @@ class SFTP extends SSH2 * @return string * @access private */ - function _get_sftp_packet() + function _get_sftp_packet($request_id = null) { - $this->curTimeout = false; + if (isset($request_id) && isset($this->requestBuffer[$request_id])) { + $this->packet_type = $this->requestBuffer[$request_id]['packet_type']; + $temp = $this->requestBuffer[$request_id]['packet']; + unset($this->requestBuffer[$request_id]); + return $temp; + } + + // in SSH2.php the timeout is cumulative per function call. eg. exec() will + // timeout after 10s. but for SFTP.php it's cumulative per packet + $this->curTimeout = $this->timeout; $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 // SFTP packet length while (strlen($this->packet_buffer) < 4) { - $temp = $this->_get_channel_packet(self::CHANNEL); + $temp = $this->_get_channel_packet(self::CHANNEL, true); if (is_bool($temp)) { $this->packet_type = false; $this->packet_buffer = ''; @@ -2944,9 +3020,16 @@ class SFTP extends SSH2 $tempLength = $length; $tempLength-= strlen($this->packet_buffer); + + // 256 * 1024 is what SFTP_MAX_MSG_LENGTH is set to in OpenSSH's sftp-common.h + if ($tempLength > 256 * 1024) { + user_error('Invalid SFTP packet size'); + return false; + } + // SFTP packet type and data payload while ($tempLength > 0) { - $temp = $this->_get_channel_packet(self::CHANNEL); + $temp = $this->_get_channel_packet(self::CHANNEL, true); if (is_bool($temp)) { $this->packet_type = false; $this->packet_buffer = ''; @@ -2960,8 +3043,8 @@ class SFTP extends SSH2 $this->packet_type = ord($this->_string_shift($this->packet_buffer)); - if ($this->request_id !== false) { - $this->_string_shift($this->packet_buffer, 4); // remove the request id + if ($this->use_request_id) { + extract(unpack('Npacket_id', $this->_string_shift($this->packet_buffer, 4))); // remove the request id $length-= 5; // account for the request id and the packet type } else { $length-= 1; // account for the packet type @@ -2984,6 +3067,14 @@ class SFTP extends SSH2 } } + if (isset($request_id) && $this->use_request_id && $packet_id != $request_id) { + $this->requestBuffer[$packet_id] = array( + 'packet_type' => $this->packet_type, + 'packet' => $packet + ); + return $this->_get_sftp_packet($request_id); + } + return $packet; } @@ -3014,7 +3105,7 @@ class SFTP extends SSH2 /** * Returns all errors * - * @return string + * @return array * @access public */ function getSFTPErrors() diff --git a/lam/lib/3rdParty/phpseclib/Net/SFTP/Stream.php b/lam/lib/3rdParty/phpseclib/Net/SFTP/Stream.php index 08d726ca..d2c4425d 100644 --- a/lam/lib/3rdParty/phpseclib/Net/SFTP/Stream.php +++ b/lam/lib/3rdParty/phpseclib/Net/SFTP/Stream.php @@ -179,7 +179,7 @@ class Stream if ($host[0] == '$') { $host = substr($host, 1); - global $$host; + global ${$host}; if (($$host instanceof SFTP) === false) { return false; } diff --git a/lam/lib/3rdParty/phpseclib/Net/SSH1.php b/lam/lib/3rdParty/phpseclib/Net/SSH1.php index 9e608f4b..514b20a2 100644 --- a/lam/lib/3rdParty/phpseclib/Net/SSH1.php +++ b/lam/lib/3rdParty/phpseclib/Net/SSH1.php @@ -916,7 +916,7 @@ class SSH1 /** * Returns the output of an interactive shell when there's a match for $expect * - * $expect can take the form of a string literal or, if $mode == self::READ__REGEX, + * $expect can take the form of a string literal or, if $mode == self::READ_REGEX, * a regular expression. * * @see self::write() @@ -925,7 +925,7 @@ class SSH1 * @return bool * @access public */ - function read($expect, $mode = self::READ__SIMPLE) + function read($expect, $mode = self::READ_SIMPLE) { if (!($this->bitmap & self::MASK_LOGIN)) { user_error('Operation disallowed prior to login()'); @@ -939,7 +939,7 @@ class SSH1 $match = $expect; while (true) { - if ($mode == self::READ__REGEX) { + if ($mode == self::READ_REGEX) { preg_match($expect, $this->interactiveBuffer, $matches); $match = isset($matches[0]) ? $matches[0] : ''; } diff --git a/lam/lib/3rdParty/phpseclib/Net/SSH2.php b/lam/lib/3rdParty/phpseclib/Net/SSH2.php index 937c38cc..817fa165 100644 --- a/lam/lib/3rdParty/phpseclib/Net/SSH2.php +++ b/lam/lib/3rdParty/phpseclib/Net/SSH2.php @@ -100,10 +100,11 @@ class SSH2 * @see \phpseclib\Net\SSH2::_get_channel_packet() * @access private */ - const CHANNEL_EXEC = 0; // PuTTy uses 0x100 - const CHANNEL_SHELL = 1; - const CHANNEL_SUBSYSTEM = 2; - const CHANNEL_AGENT_FORWARD = 3; + const CHANNEL_EXEC = 1; // PuTTy uses 0x100 + const CHANNEL_SHELL = 2; + const CHANNEL_SUBSYSTEM = 3; + const CHANNEL_AGENT_FORWARD = 4; + const CHANNEL_KEEP_ALIVE = 5; /**#@-*/ /**#@+ @@ -126,6 +127,10 @@ class SSH2 * Dumps the content real-time to a file */ const LOG_REALTIME_FILE = 4; + /** + * Make sure that the log never gets larger than this + */ + const LOG_MAX_SIZE = 1048576; // 1024 * 1024 /**#@-*/ /**#@+ @@ -141,9 +146,12 @@ class SSH2 */ const READ_REGEX = 2; /** - * Make sure that the log never gets larger than this + * Returns whenever a data packet is received. + * + * Some data packets may only contain a single character so it may be necessary + * to call read() multiple times when using this option */ - const LOG_MAX_SIZE = 1048576; // 1024 * 1024 + const READ_NEXT = 3; /**#@-*/ /** @@ -866,6 +874,70 @@ class SSH2 */ var $agent; + /** + * Send the identification string first? + * + * @var bool + * @access private + */ + var $send_id_string_first = true; + + /** + * Send the key exchange initiation packet first? + * + * @var bool + * @access private + */ + var $send_kex_first = true; + + /** + * Some versions of OpenSSH incorrectly calculate the key size + * + * @var bool + * @access private + */ + var $bad_key_size_fix = false; + + /** + * The selected decryption algorithm + * + * @var string + * @access private + */ + var $decrypt_algorithm = ''; + + /** + * Should we try to re-connect to re-establish keys? + * + * @var bool + * @access private + */ + var $retry_connect = false; + + /** + * Binary Packet Buffer + * + * @var string|false + * @access private + */ + var $binary_packet_buffer = false; + + /** + * Preferred Signature Format + * + * @var string|false + * @access private + */ + var $preferred_signature_format = false; + + /** + * Authentication Credentials + * + * @var array + * @access private + */ + var $auth = array(); + /** * Default Constructor. * @@ -978,13 +1050,69 @@ class SSH2 * CRYPT_MODE_INTERNAL, CRYPT_MODE_MCRYPT * * @param int $engine - * @access private + * @access public */ function setCryptoEngine($engine) { $this->crypto_engine = $engine; } + /** + * Send Identification String First + * + * https://tools.ietf.org/html/rfc4253#section-4.2 says "when the connection has been established, + * both sides MUST send an identification string". It does not say which side sends it first. In + * theory it shouldn't matter but it is a fact of life that some SSH servers are simply buggy + * + * @access public + */ + function sendIdentificationStringFirst() + { + $this->send_id_string_first = true; + } + + /** + * Send Identification String Last + * + * https://tools.ietf.org/html/rfc4253#section-4.2 says "when the connection has been established, + * both sides MUST send an identification string". It does not say which side sends it first. In + * theory it shouldn't matter but it is a fact of life that some SSH servers are simply buggy + * + * @access public + */ + function sendIdentificationStringLast() + { + $this->send_id_string_first = false; + } + + /** + * Send SSH_MSG_KEXINIT First + * + * https://tools.ietf.org/html/rfc4253#section-7.1 says "key exchange begins by each sending + * sending the [SSH_MSG_KEXINIT] packet". It does not say which side sends it first. In theory + * it shouldn't matter but it is a fact of life that some SSH servers are simply buggy + * + * @access public + */ + function sendKEXINITFirst() + { + $this->send_kex_first = true; + } + + /** + * Send SSH_MSG_KEXINIT Last + * + * https://tools.ietf.org/html/rfc4253#section-7.1 says "key exchange begins by each sending + * sending the [SSH_MSG_KEXINIT] packet". It does not say which side sends it first. In theory + * it shouldn't matter but it is a fact of life that some SSH servers are simply buggy + * + * @access public + */ + function sendKEXINITLast() + { + $this->send_kex_first = false; + } + /** * Connect to an SSHv2 server * @@ -1016,17 +1144,20 @@ class SSH2 } $elapsed = microtime(true) - $start; - $this->curTimeout-= $elapsed; - - if ($this->curTimeout <= 0) { - $this->is_timeout = true; - return false; + if ($this->curTimeout) { + $this->curTimeout-= $elapsed; + if ($this->curTimeout < 0) { + $this->is_timeout = true; + return false; + } } } $this->identifier = $this->_generate_identifier(); - fputs($this->fsock, $this->identifier . "\r\n"); + if ($this->send_id_string_first) { + fputs($this->fsock, $this->identifier . "\r\n"); + } /* According to the SSH2 specs, @@ -1083,6 +1214,7 @@ class SSH2 } if (feof($this->fsock)) { + $this->bitmap = 0; user_error('Connection closed by server'); return false; } @@ -1096,26 +1228,37 @@ class SSH2 $this->server_identifier = trim($temp, "\r\n"); if (strlen($extra)) { - $this->errors[] = utf8_decode($data); + $this->errors[] = $data; } - if ($matches[3] != '1.99' && $matches[3] != '2.0') { + if (version_compare($matches[3], '1.99', '<')) { user_error("Cannot connect to SSH $matches[3] servers"); return false; } - $response = $this->_get_binary_packet(); - if ($response === false) { - user_error('Connection closed by server'); - return false; + if (!$this->send_id_string_first) { + fputs($this->fsock, $this->identifier . "\r\n"); } - if (!strlen($response) || ord($response[0]) != NET_SSH2_MSG_KEXINIT) { - user_error('Expected SSH_MSG_KEXINIT'); - return false; + if (!$this->send_kex_first) { + $response = $this->_get_binary_packet(); + if ($response === false) { + $this->bitmap = 0; + user_error('Connection closed by server'); + return false; + } + + if (!strlen($response) || ord($response[0]) != NET_SSH2_MSG_KEXINIT) { + user_error('Expected SSH_MSG_KEXINIT'); + return false; + } + + if (!$this->_key_exchange($response)) { + return false; + } } - if (!$this->_key_exchange($response)) { + if ($this->send_kex_first && !$this->_key_exchange()) { return false; } @@ -1137,7 +1280,7 @@ class SSH2 $identifier = 'SSH-2.0-phpseclib_2.0'; $ext = array(); - if (extension_loaded('libsodium')) { + if (function_exists('\\Sodium\\library_version_major')) { $ext[] = 'libsodium'; } @@ -1163,10 +1306,10 @@ class SSH2 /** * Key Exchange * - * @param string $kexinit_payload_server + * @param string $kexinit_payload_server optional * @access private */ - function _key_exchange($kexinit_payload_server) + function _key_exchange($kexinit_payload_server = false) { $kex_algorithms = array( // Elliptic Curve Diffie-Hellman Key Agreement (ECDH) using @@ -1189,6 +1332,8 @@ class SSH2 } $server_host_key_algorithms = array( + 'rsa-sha2-256', // RFC 8332 + 'rsa-sha2-512', // RFC 8332 'ssh-rsa', // RECOMMENDED sign Raw RSA Key 'ssh-dss' // REQUIRED sign Raw DSS Key ); @@ -1287,8 +1432,9 @@ class SSH2 ); // some SSH servers have buggy implementations of some of the above algorithms - switch ($this->server_identifier) { - case 'SSH-2.0-SSHD': + switch (true) { + case $this->server_identifier == 'SSH-2.0-SSHD': + case substr($this->server_identifier, 0, 13) == 'SSH-2.0-DLINK': $mac_algorithms = array_values(array_diff( $mac_algorithms, array('hmac-sha1-96', 'hmac-md5-96') @@ -1303,6 +1449,52 @@ class SSH2 $client_cookie = Random::string(16); + $kexinit_payload_client = pack( + 'Ca*Na*Na*Na*Na*Na*Na*Na*Na*Na*Na*CN', + NET_SSH2_MSG_KEXINIT, + $client_cookie, + strlen($str_kex_algorithms), + $str_kex_algorithms, + strlen($str_server_host_key_algorithms), + $str_server_host_key_algorithms, + strlen($encryption_algorithms_client_to_server), + $encryption_algorithms_client_to_server, + strlen($encryption_algorithms_server_to_client), + $encryption_algorithms_server_to_client, + strlen($mac_algorithms_client_to_server), + $mac_algorithms_client_to_server, + strlen($mac_algorithms_server_to_client), + $mac_algorithms_server_to_client, + strlen($compression_algorithms_client_to_server), + $compression_algorithms_client_to_server, + strlen($compression_algorithms_server_to_client), + $compression_algorithms_server_to_client, + 0, + '', + 0, + '', + 0, + 0 + ); + + if ($this->send_kex_first) { + if (!$this->_send_binary_packet($kexinit_payload_client)) { + return false; + } + + $kexinit_payload_server = $this->_get_binary_packet(); + if ($kexinit_payload_server === false) { + $this->bitmap = 0; + user_error('Connection closed by server'); + return false; + } + + if (!strlen($kexinit_payload_server) || ord($kexinit_payload_server[0]) != NET_SSH2_MSG_KEXINIT) { + user_error('Expected SSH_MSG_KEXINIT'); + return false; + } + } + $response = $kexinit_payload_server; $this->_string_shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT) $server_cookie = $this->_string_shift($response, 16); @@ -1373,39 +1565,9 @@ class SSH2 extract(unpack('Cfirst_kex_packet_follows', $this->_string_shift($response, 1))); $first_kex_packet_follows = $first_kex_packet_follows != 0; - // the sending of SSH2_MSG_KEXINIT could go in one of two places. this is the second place. - $kexinit_payload_client = pack( - 'Ca*Na*Na*Na*Na*Na*Na*Na*Na*Na*Na*CN', - NET_SSH2_MSG_KEXINIT, - $client_cookie, - strlen($str_kex_algorithms), - $str_kex_algorithms, - strlen($str_server_host_key_algorithms), - $str_server_host_key_algorithms, - strlen($encryption_algorithms_client_to_server), - $encryption_algorithms_client_to_server, - strlen($encryption_algorithms_server_to_client), - $encryption_algorithms_server_to_client, - strlen($mac_algorithms_client_to_server), - $mac_algorithms_client_to_server, - strlen($mac_algorithms_server_to_client), - $mac_algorithms_server_to_client, - strlen($compression_algorithms_client_to_server), - $compression_algorithms_client_to_server, - strlen($compression_algorithms_server_to_client), - $compression_algorithms_server_to_client, - 0, - '', - 0, - '', - 0, - 0 - ); - - if (!$this->_send_binary_packet($kexinit_payload_client)) { + if (!$this->send_kex_first && !$this->_send_binary_packet($kexinit_payload_client)) { return false; } - // 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 @@ -1459,6 +1621,7 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { + $this->bitmap = 0; user_error('Connection closed by server'); return false; } @@ -1551,12 +1714,14 @@ class SSH2 $data = pack('CNa*', $clientKexInitMessage, strlen($eBytes), $eBytes); if (!$this->_send_binary_packet($data)) { + $this->bitmap = 0; user_error('Connection closed by server'); return false; } $response = $this->_get_binary_packet(); if ($response === false) { + $this->bitmap = 0; user_error('Connection closed by server'); return false; } @@ -1646,9 +1811,25 @@ class SSH2 return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } - 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); + switch ($server_host_key_algorithm) { + case 'ssh-dss': + $expected_key_format = 'ssh-dss'; + break; + //case 'rsa-sha2-256': + //case 'rsa-sha2-512': + //case 'ssh-rsa': + default: + $expected_key_format = 'ssh-rsa'; + } + + if ($public_key_format != $expected_key_format || $this->signature_format != $server_host_key_algorithm) { + switch (true) { + case $this->signature_format == $server_host_key_algorithm: + case $server_host_key_algorithm != 'rsa-sha2-256' && $server_host_key_algorithm != 'rsa-sha2-512': + case $this->signature_format != 'ssh-rsa': + user_error('Server Host Key Algorithm Mismatch'); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } } $packet = pack( @@ -1663,6 +1844,7 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { + $this->bitmap = 0; user_error('Connection closed by server'); return false; } @@ -1677,12 +1859,14 @@ class SSH2 return false; } + $this->decrypt_algorithm = $decrypt; + $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes); $this->encrypt = $this->_encryption_algorithm_to_crypt_instance($encrypt); if ($this->encrypt) { if ($this->crypto_engine) { - $this->encrypt->setEngine($this->crypto_engine); + $this->encrypt->setPreferredEngine($this->crypto_engine); } if ($this->encrypt->block_size) { $this->encrypt_block_size = $this->encrypt->block_size; @@ -1706,7 +1890,7 @@ class SSH2 $this->decrypt = $this->_encryption_algorithm_to_crypt_instance($decrypt); if ($this->decrypt) { if ($this->crypto_engine) { - $this->decrypt->setEngine($this->crypto_engine); + $this->decrypt->setPreferredEngine($this->crypto_engine); } if ($this->decrypt->block_size) { $this->decrypt_block_size = $this->decrypt->block_size; @@ -1843,6 +2027,10 @@ class SSH2 */ function _encryption_algorithm_to_key_size($algorithm) { + if ($this->bad_key_size_fix && $this->_bad_algorithm_candidate($algorithm)) { + return 16; + } + switch ($algorithm) { case 'none': return 0; @@ -1917,6 +2105,27 @@ class SSH2 return null; } + /* + * Tests whether or not proposed algorithm has a potential for issues + * + * @link https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/ssh2-aesctr-openssh.html + * @link https://bugzilla.mindrot.org/show_bug.cgi?id=1291 + * @param string $algorithm Name of the encryption algorithm + * @return bool + * @access private + */ + function _bad_algorithm_candidate($algorithm) + { + switch ($algorithm) { + case 'arcfour256': + case 'aes192-ctr': + case 'aes256-ctr': + return true; + } + + return false; + } + /** * Login * @@ -1932,6 +2141,7 @@ class SSH2 function login($username) { $args = func_get_args(); + $this->auth[] = $args; return call_user_func_array(array(&$this, '_login'), $args); } @@ -1996,6 +2206,14 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { + if ($this->retry_connect) { + $this->retry_connect = false; + if (!$this->_connect()) { + return false; + } + return $this->_login_helper($username, $password); + } + $this->bitmap = 0; user_error('Connection closed by server'); return false; } @@ -2048,6 +2266,7 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { + $this->bitmap = 0; user_error('Connection closed by server'); return false; } @@ -2106,6 +2325,7 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { + $this->bitmap = 0; user_error('Connection closed by server'); return false; } @@ -2124,7 +2344,7 @@ class SSH2 return false; } extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . utf8_decode($this->_string_shift($response, $length)); + $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . $this->_string_shift($response, $length); return $this->_disconnect(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); case NET_SSH2_MSG_USERAUTH_FAILURE: // can we use keyboard-interactive authentication? if not then either the login is bad or the server employees @@ -2206,6 +2426,7 @@ class SSH2 } else { $orig = $response = $this->_get_binary_packet(); if ($response === false) { + $this->bitmap = 0; user_error('Connection closed by server'); return false; } @@ -2375,6 +2596,21 @@ class SSH2 $publickey['n'] ); + switch ($this->signature_format) { + case 'rsa-sha2-512': + $hash = 'sha512'; + $signatureType = 'rsa-sha2-512'; + break; + case 'rsa-sha2-256': + $hash = 'sha256'; + $signatureType = 'rsa-sha2-256'; + break; + //case 'ssh-rsa': + default: + $hash = 'sha1'; + $signatureType = 'ssh-rsa'; + } + $part1 = pack( 'CNa*Na*Na*', NET_SSH2_MSG_USERAUTH_REQUEST, @@ -2385,7 +2621,7 @@ class SSH2 strlen('publickey'), 'publickey' ); - $part2 = pack('Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publickey), $publickey); + $part2 = pack('Na*Na*', strlen($signatureType), $signatureType, strlen($publickey), $publickey); $packet = $part1 . chr(0) . $part2; if (!$this->_send_binary_packet($packet)) { @@ -2394,6 +2630,7 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { + $this->bitmap = 0; user_error('Connection closed by server'); return false; } @@ -2425,8 +2662,9 @@ class SSH2 $packet = $part1 . chr(1) . $part2; $privatekey->setSignatureMode(RSA::SIGNATURE_PKCS1); + $privatekey->setHash($hash); $signature = $privatekey->sign(pack('Na*a*', strlen($this->session_id), $this->session_id, $packet)); - $signature = pack('Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($signature), $signature); + $signature = pack('Na*Na*', strlen($signatureType), $signatureType, strlen($signature), $signature); $packet.= pack('Na*', strlen($signature), $signature); if (!$this->_send_binary_packet($packet)) { @@ -2435,6 +2673,7 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { + $this->bitmap = 0; user_error('Connection closed by server'); return false; } @@ -2561,6 +2800,7 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { + $this->bitmap = 0; user_error('Connection closed by server'); return false; } @@ -2700,6 +2940,7 @@ class SSH2 $response = $this->_get_binary_packet(); if ($response === false) { + $this->bitmap = 0; user_error('Connection closed by server'); return false; } @@ -2812,6 +3053,10 @@ class SSH2 $channel = $this->_get_interactive_channel(); + if ($mode == self::READ_NEXT) { + return $this->_get_channel_packet($channel); + } + $match = $expect; while (true) { if ($mode == self::READ_REGEX) { @@ -3010,6 +3255,84 @@ class SSH2 return (bool) ($this->bitmap & self::MASK_LOGIN); } + /** + * Pings a server connection, or tries to reconnect if the connection has gone down + * + * Inspired by http://php.net/manual/en/mysqli.ping.php + * + * @return bool + * @access public + */ + function ping() + { + if (!$this->isAuthenticated()) { + return false; + } + + $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE] = $this->window_size; + $packet_size = 0x4000; + $packet = pack( + 'CNa*N3', + NET_SSH2_MSG_CHANNEL_OPEN, + strlen('session'), + 'session', + self::CHANNEL_KEEP_ALIVE, + $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE], + $packet_size + ); + + if (!@$this->_send_binary_packet($packet)) { + return $this->_reconnect(); + } + + $this->channel_status[self::CHANNEL_KEEP_ALIVE] = NET_SSH2_MSG_CHANNEL_OPEN; + + $response = @$this->_get_channel_packet(self::CHANNEL_KEEP_ALIVE); + if ($response !== false) { + $this->_close_channel(self::CHANNEL_KEEP_ALIVE); + return true; + } + + return $this->_reconnect(); + } + + /** + * In situ reconnect method + * + * @return boolean + * @access private + */ + function _reconnect() + { + $this->_reset_connection(NET_SSH2_DISCONNECT_CONNECTION_LOST); + $this->retry_connect = true; + if (!$this->_connect()) { + return false; + } + foreach ($this->auth as $auth) { + $result = call_user_func_array(array(&$this, 'login'), $auth); + } + return $result; + } + + /** + * Resets a connection for re-use + * + * @param int $reason + * @access private + */ + function _reset_connection($reason) + { + $this->_disconnect($reason); + $this->decrypt = $this->encrypt = false; + $this->decrypt_block_size = $this->encrypt_block_size = 8; + $this->hmac_check = $this->hmac_create = false; + $this->hmac_size = false; + $this->session_id = false; + $this->retry_connect = true; + $this->get_seq_no = $this->send_seq_no = 0; + } + /** * Gets Binary Packets * @@ -3019,11 +3342,11 @@ class SSH2 * @return string * @access private */ - function _get_binary_packet() + function _get_binary_packet($skip_channel_filter = false) { if (!is_resource($this->fsock) || feof($this->fsock)) { - user_error('Connection closed prematurely'); $this->bitmap = 0; + user_error('Connection closed prematurely'); return false; } @@ -3053,6 +3376,11 @@ 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) { + if (!$this->bad_key_size_fix && $this->_bad_algorithm_candidate($this->decrypt_algorithm) && !($this->bitmap & SSH2::MASK_LOGIN)) { + $this->bad_key_size_fix = true; + $this->_reset_connection(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + return false; + } user_error('Invalid size'); return false; } @@ -3061,13 +3389,14 @@ class SSH2 while ($remaining_length > 0) { $temp = stream_get_contents($this->fsock, $remaining_length); if ($temp === false || feof($this->fsock)) { - user_error('Error reading from socket'); $this->bitmap = 0; + user_error('Error reading from socket'); return false; } $buffer.= $temp; $remaining_length-= strlen($temp); } + $stop = microtime(true); if (strlen($buffer)) { $raw.= $this->decrypt !== false ? $this->decrypt->decrypt($buffer) : $buffer; @@ -3079,8 +3408,8 @@ class SSH2 if ($this->hmac_check !== false) { $hmac = stream_get_contents($this->fsock, $this->hmac_size); if ($hmac === false || strlen($hmac) != $this->hmac_size) { - user_error('Error reading socket'); $this->bitmap = 0; + user_error('Error reading socket'); return false; } elseif ($hmac != $this->hmac_check->hash(pack('NNCa*', $this->get_seq_no, $packet_length, $padding_length, $payload . $padding))) { user_error('Invalid HMAC'); @@ -3103,7 +3432,7 @@ class SSH2 $this->last_packet = $current; } - return $this->_filter($payload); + return $this->_filter($payload, $skip_channel_filter); } /** @@ -3115,7 +3444,7 @@ class SSH2 * @return string * @access private */ - function _filter($payload) + function _filter($payload, $skip_channel_filter) { switch (ord($payload[0])) { case NET_SSH2_MSG_DISCONNECT: @@ -3124,11 +3453,11 @@ class SSH2 return false; } extract(unpack('Nreason_code/Nlength', $this->_string_shift($payload, 8))); - $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . utf8_decode($this->_string_shift($payload, $length)); + $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . $this->_string_shift($payload, $length); $this->bitmap = 0; return false; case NET_SSH2_MSG_IGNORE: - $payload = $this->_get_binary_packet(); + $payload = $this->_get_binary_packet($skip_channel_filter); break; case NET_SSH2_MSG_DEBUG: $this->_string_shift($payload, 2); @@ -3136,18 +3465,19 @@ class SSH2 return false; } extract(unpack('Nlength', $this->_string_shift($payload, 4))); - $this->errors[] = 'SSH_MSG_DEBUG: ' . utf8_decode($this->_string_shift($payload, $length)); - $payload = $this->_get_binary_packet(); + $this->errors[] = 'SSH_MSG_DEBUG: ' . $this->_string_shift($payload, $length); + $payload = $this->_get_binary_packet($skip_channel_filter); break; case NET_SSH2_MSG_UNIMPLEMENTED: return false; case NET_SSH2_MSG_KEXINIT: if ($this->session_id !== false) { + $this->send_kex_first = false; if (!$this->_key_exchange($payload)) { $this->bitmap = 0; return false; } - $payload = $this->_get_binary_packet(); + $payload = $this->_get_binary_packet($skip_channel_filter); } } @@ -3158,13 +3488,24 @@ class SSH2 return false; } extract(unpack('Nlength', $this->_string_shift($payload, 4))); - $this->banner_message = utf8_decode($this->_string_shift($payload, $length)); + $this->banner_message = $this->_string_shift($payload, $length); $payload = $this->_get_binary_packet(); } // only called when we've already logged in if (($this->bitmap & self::MASK_CONNECTED) && $this->isAuthenticated()) { switch (ord($payload[0])) { + case NET_SSH2_MSG_CHANNEL_DATA: + case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA: + case NET_SSH2_MSG_CHANNEL_REQUEST: + case NET_SSH2_MSG_CHANNEL_CLOSE: + case NET_SSH2_MSG_CHANNEL_EOF: + if (!$skip_channel_filter && !empty($this->server_channels)) { + $this->binary_packet_buffer = $payload; + $this->_get_channel_packet(true); + $payload = $this->_get_binary_packet(); + } + break; case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4 if (strlen($payload) < 4) { return false; @@ -3176,7 +3517,7 @@ class SSH2 return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); } - $payload = $this->_get_binary_packet(); + $payload = $this->_get_binary_packet($skip_channel_filter); break; case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1 $this->_string_shift($payload, 1); @@ -3239,7 +3580,7 @@ class SSH2 return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); } } - $payload = $this->_get_binary_packet(); + $payload = $this->_get_binary_packet($skip_channel_filter); break; case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST: $this->_string_shift($payload, 1); @@ -3250,7 +3591,7 @@ class SSH2 extract(unpack('Nwindow_size', $this->_string_shift($payload, 4))); $this->window_size_client_to_server[$channel]+= $window_size; - $payload = ($this->bitmap & self::MASK_WINDOW_ADJUST) ? true : $this->_get_binary_packet(); + $payload = ($this->bitmap & self::MASK_WINDOW_ADJUST) ? true : $this->_get_binary_packet($skip_channel_filter); } } @@ -3347,39 +3688,50 @@ class SSH2 } while (true) { - if ($this->curTimeout) { - if ($this->curTimeout < 0) { - $this->is_timeout = true; - return true; - } - + if ($this->binary_packet_buffer !== false) { + $response = $this->binary_packet_buffer; + $this->binary_packet_buffer = false; + } else { $read = array($this->fsock); $write = $except = null; - $start = microtime(true); - $sec = floor($this->curTimeout); - $usec = 1000000 * ($this->curTimeout - $sec); - // on windows this returns a "Warning: Invalid CRT parameters detected" error - if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) { - $this->is_timeout = true; - return true; + if (!$this->curTimeout) { + @stream_select($read, $write, $except, null); + } else { + if ($this->curTimeout < 0) { + $this->is_timeout = true; + return true; + } + + $read = array($this->fsock); + $write = $except = null; + + $start = microtime(true); + $sec = floor($this->curTimeout); + $usec = 1000000 * ($this->curTimeout - $sec); + // on windows this returns a "Warning: Invalid CRT parameters detected" error + if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) { + $this->is_timeout = true; + if ($client_channel == self::CHANNEL_EXEC && !$this->request_pty) { + $this->_close_channel($client_channel); + } + return true; + } + $elapsed = microtime(true) - $start; + $this->curTimeout-= $elapsed; + } + + $response = $this->_get_binary_packet(true); + if ($response === false) { + $this->bitmap = 0; + user_error('Connection closed by server'); + return false; } - $elapsed = microtime(true) - $start; - $this->curTimeout-= $elapsed; } - $response = $this->_get_binary_packet(); - if ($response === false) { - user_error('Connection closed by server'); - return false; - } if ($client_channel == -1 && $response === true) { return true; } - if (!strlen($response)) { - return ''; - } - if (!strlen($response)) { return false; } @@ -3407,6 +3759,82 @@ class SSH2 $this->window_size_server_to_client[$channel]+= $this->window_size; } + switch ($type) { + case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA: + /* + if ($client_channel == self::CHANNEL_EXEC) { + $this->_send_channel_packet($client_channel, chr(0)); + } + */ + // currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR + if (strlen($response) < 8) { + return false; + } + extract(unpack('Ndata_type_code/Nlength', $this->_string_shift($response, 8))); + $data = $this->_string_shift($response, $length); + $this->stdErrorLog.= $data; + if ($skip_extended || $this->quiet_mode) { + continue 2; + } + if ($client_channel == $channel && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA) { + return $data; + } + if (!isset($this->channel_buffers[$channel])) { + $this->channel_buffers[$channel] = array(); + } + $this->channel_buffers[$channel][] = $data; + + continue 2; + case NET_SSH2_MSG_CHANNEL_REQUEST: + if ($this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_CLOSE) { + continue 2; + } + if (strlen($response) < 4) { + return false; + } + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $value = $this->_string_shift($response, $length); + switch ($value) { + case 'exit-signal': + $this->_string_shift($response, 1); + if (strlen($response) < 4) { + return false; + } + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->errors[] = 'SSH_MSG_CHANNEL_REQUEST (exit-signal): ' . $this->_string_shift($response, $length); + $this->_string_shift($response, 1); + if (strlen($response) < 4) { + return false; + } + extract(unpack('Nlength', $this->_string_shift($response, 4))); + if ($length) { + $this->errors[count($this->errors)].= "\r\n" . $this->_string_shift($response, $length); + } + + $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel])); + $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel])); + + $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_EOF; + + continue 3; + case 'exit-status': + if (strlen($response) < 5) { + return false; + } + extract(unpack('Cfalse/Nexit_status', $this->_string_shift($response, 5))); + $this->exit_status = $exit_status; + + // "The client MAY ignore these messages." + // -- http://tools.ietf.org/html/rfc4254#section-6.10 + + continue 3; + default: + // "Some systems may not implement signals, in which case they SHOULD ignore this message." + // -- http://tools.ietf.org/html/rfc4254#section-6.9 + continue 3; + } + } + switch ($this->channel_status[$channel]) { case NET_SSH2_MSG_CHANNEL_OPEN: switch ($type) { @@ -3489,76 +3917,6 @@ class SSH2 } $this->channel_buffers[$channel][] = $data; break; - case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA: - /* - if ($client_channel == self::CHANNEL_EXEC) { - $this->_send_channel_packet($client_channel, chr(0)); - } - */ - // currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR - if (strlen($response) < 8) { - return false; - } - extract(unpack('Ndata_type_code/Nlength', $this->_string_shift($response, 8))); - $data = $this->_string_shift($response, $length); - $this->stdErrorLog.= $data; - if ($skip_extended || $this->quiet_mode) { - break; - } - if ($client_channel == $channel) { - return $data; - } - if (!isset($this->channel_buffers[$channel])) { - $this->channel_buffers[$channel] = array(); - } - $this->channel_buffers[$channel][] = $data; - break; - case NET_SSH2_MSG_CHANNEL_REQUEST: - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $value = $this->_string_shift($response, $length); - switch ($value) { - case 'exit-signal': - $this->_string_shift($response, 1); - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->errors[] = 'SSH_MSG_CHANNEL_REQUEST (exit-signal): ' . $this->_string_shift($response, $length); - $this->_string_shift($response, 1); - if (strlen($response) < 4) { - return false; - } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - if ($length) { - $this->errors[count($this->errors)].= "\r\n" . $this->_string_shift($response, $length); - } - - $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel])); - $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel])); - - $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_EOF; - - break; - case 'exit-status': - if (strlen($response) < 5) { - return false; - } - extract(unpack('Cfalse/Nexit_status', $this->_string_shift($response, 5))); - $this->exit_status = $exit_status; - - // "The client MAY ignore these messages." - // -- http://tools.ietf.org/html/rfc4254#section-6.10 - - break; - default: - // "Some systems may not implement signals, in which case they SHOULD ignore this message." - // -- http://tools.ietf.org/html/rfc4254#section-6.9 - break; - } - break; case NET_SSH2_MSG_CHANNEL_CLOSE: $this->curTimeout = 0; @@ -3596,8 +3954,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; + user_error('Connection closed prematurely'); return false; } @@ -4258,6 +4616,8 @@ class SSH2 break; case 'ssh-rsa': + case 'rsa-sha2-256': + case 'rsa-sha2-512': if (strlen($server_public_host_key) < 4) { return false; } @@ -4280,8 +4640,21 @@ class SSH2 $signature = $this->_string_shift($signature, $temp['length']); $rsa = new RSA(); + switch ($this->signature_format) { + case 'rsa-sha2-512': + $hash = 'sha512'; + break; + case 'rsa-sha2-256': + $hash = 'sha256'; + break; + //case 'ssh-rsa': + default: + $hash = 'sha1'; + } + $rsa->setHash($hash); $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1); $rsa->loadKey(array('e' => $e, 'n' => $n), RSA::PUBLIC_FORMAT_RAW); + if (!$rsa->verify($this->exchange_hash, $signature)) { user_error('Bad server signature'); return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); @@ -4308,7 +4681,30 @@ class SSH2 $s = $s->modPow($e, $n); $s = $s->toBytes(); - $h = pack('N4H*', 0x00302130, 0x0906052B, 0x0E03021A, 0x05000414, sha1($this->exchange_hash)); + switch ($this->signature_format) { + case 'rsa-sha2-512': + $hash = 'sha512'; + break; + case 'rsa-sha2-256': + $hash = 'sha256'; + break; + //case 'ssh-rsa': + default: + $hash = 'sha1'; + } + $hashObj = new Hash($hash); + switch ($this->signature_format) { + case 'rsa-sha2-512': + $h = pack('N5a*', 0x00305130, 0x0D060960, 0x86480165, 0x03040203, 0x05000440, $hashObj->hash($this->exchange_hash)); + break; + case 'rsa-sha2-256': + $h = pack('N5a*', 0x00303130, 0x0D060960, 0x86480165, 0x03040201, 0x05000420, $hashObj->hash($this->exchange_hash)); + break; + //case 'ssh-rsa': + default: + $hash = 'sha1'; + $h = pack('N4a*', 0x00302130, 0x0906052B, 0x0E03021A, 0x05000414, $hashObj->hash($this->exchange_hash)); + } $h = chr(0x01) . str_repeat(chr(0xFF), $nLength - 2 - strlen($h)) . $h; if ($s != $h) { diff --git a/lam/lib/3rdParty/phpseclib/System/SSH/Agent.php b/lam/lib/3rdParty/phpseclib/System/SSH/Agent.php index a4ff0549..99dcecfe 100644 --- a/lam/lib/3rdParty/phpseclib/System/SSH/Agent.php +++ b/lam/lib/3rdParty/phpseclib/System/SSH/Agent.php @@ -43,7 +43,7 @@ use phpseclib\System\SSH\Agent\Identity; * * @package SSH\Agent * @author Jim Wigginton - * @access internal + * @access public */ class Agent { @@ -117,18 +117,20 @@ class Agent * @return \phpseclib\System\SSH\Agent * @access public */ - function __construct() + function __construct($address = null) { - switch (true) { - case isset($_SERVER['SSH_AUTH_SOCK']): - $address = $_SERVER['SSH_AUTH_SOCK']; - break; - case isset($_ENV['SSH_AUTH_SOCK']): - $address = $_ENV['SSH_AUTH_SOCK']; - break; - default: - user_error('SSH_AUTH_SOCK not found'); - return false; + if (!$address) { + switch (true) { + case isset($_SERVER['SSH_AUTH_SOCK']): + $address = $_SERVER['SSH_AUTH_SOCK']; + break; + case isset($_ENV['SSH_AUTH_SOCK']): + $address = $_ENV['SSH_AUTH_SOCK']; + break; + default: + user_error('SSH_AUTH_SOCK not found'); + return false; + } } $this->fsock = fsockopen('unix://' . $address, 0, $errno, $errstr); @@ -155,12 +157,14 @@ 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'); + return array(); } $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'); + return array(); } $identities = array(); diff --git a/lam/lib/3rdParty/phpseclib/System/SSH/Agent/Identity.php b/lam/lib/3rdParty/phpseclib/System/SSH/Agent/Identity.php index b8cc6cde..b4649046 100644 --- a/lam/lib/3rdParty/phpseclib/System/SSH/Agent/Identity.php +++ b/lam/lib/3rdParty/phpseclib/System/SSH/Agent/Identity.php @@ -32,6 +32,17 @@ use phpseclib\System\SSH\Agent; */ class Identity { + /**@+ + * Signature Flags + * + * See https://tools.ietf.org/html/draft-miller-ssh-agent-00#section-5.3 + * + * @access private + */ + const SSH_AGENT_RSA2_256 = 2; + const SSH_AGENT_RSA2_512 = 4; + /**#@-*/ + /** * Key Object * @@ -59,6 +70,16 @@ class Identity */ var $fsock; + /** + * Signature flags + * + * @var int + * @access private + * @see self::sign() + * @see self::setHash() + */ + var $flags = 0; + /** * Default Constructor. * @@ -126,6 +147,31 @@ class Identity { } + /** + * Set Hash + * + * ssh-agent doesn't support using hashes for RSA other than SHA1 + * + * @param string $hash + * @access public + */ + function setHash($hash) + { + $this->flags = 0; + switch ($hash) { + case 'sha1': + break; + case 'sha256': + $this->flags = self::SSH_AGENT_RSA2_256; + break; + case 'sha512': + $this->flags = self::SSH_AGENT_RSA2_512; + break; + default: + user_error('The only supported hashes for RSA are sha1, sha256 and sha512'); + } + } + /** * Create a signature * @@ -138,7 +184,7 @@ class Identity function sign($message) { // the last parameter (currently 0) is for flags and ssh-agent only defines one flag (for ssh-dss): SSH_AGENT_OLD_SIGNATURE - $packet = pack('CNa*Na*N', Agent::SSH_AGENTC_SIGN_REQUEST, strlen($this->key_blob), $this->key_blob, strlen($message), $message, 0); + $packet = pack('CNa*Na*N', Agent::SSH_AGENTC_SIGN_REQUEST, strlen($this->key_blob), $this->key_blob, strlen($message), $message, $this->flags); $packet = pack('Na*', strlen($packet), $packet); if (strlen($packet) != fputs($this->fsock, $packet)) { user_error('Connection closed during signing'); @@ -151,8 +197,34 @@ class Identity } $signature_blob = fread($this->fsock, $length - 1); - // the only other signature format defined - ssh-dss - is the same length as ssh-rsa - // the + 12 is for the other various SSH added length fields - return substr($signature_blob, strlen('ssh-rsa') + 12); + $length = current(unpack('N', $this->_string_shift($signature_blob, 4))); + if ($length != strlen($signature_blob)) { + user_error('Malformed signature blob'); + } + $length = current(unpack('N', $this->_string_shift($signature_blob, 4))); + if ($length > strlen($signature_blob) + 4) { + user_error('Malformed signature blob'); + } + $type = $this->_string_shift($signature_blob, $length); + $this->_string_shift($signature_blob, 4); + + return $signature_blob; + } + + /** + * 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; } } diff --git a/lam/lib/modules/imapAccess.inc b/lam/lib/modules/imapAccess.inc index d08c4d11..df4ca939 100644 --- a/lam/lib/modules/imapAccess.inc +++ b/lam/lib/modules/imapAccess.inc @@ -281,21 +281,20 @@ class imapAccess extends baseModule { * @return array HTML meta data */ function display_html_password() { - $return = new htmlTable(); + $return = new htmlResponsiveRow(); if($this->moduleSettings['ImapAccess_ImapAdminPasswordSelect'][0] == "lam_user_pass"){ $message = $this->messages['managemailbox'][6]; $messageElement = new htmlStatusMessage($message[0], $message[1]); - $messageElement->colspan = 3; - $return->addElement($messageElement); - $return->addElement(new htmlSpacer(null, '10px'), true); + $return->add($messageElement, 12); + $return->addVerticalSpacer('1rem'); } - $passwordInput = new htmlTableExtendedInputField(_("Password of IMAP admin user"), 'ImapAdminPassword', '', 'ImapAdminPassword_Sess'); + $passwordInput = new htmlResponsiveInputField(_("Password of IMAP admin user"), 'ImapAdminPassword', '', 'ImapAdminPassword_Sess'); $passwordInput->setIsPassword(true); $passwordInput->setRequired(true); $passwordInput->setOnKeyPress('SubmitForm(\'enterPasswordButton\', event);'); - $return->addElement($passwordInput, true); - $return->addElement(new htmlSpacer(null, '10px'), true); - $return->addElement(new htmlButton('enterPasswordButton', _('Ok'))); + $return->add($passwordInput, 12); + $return->addVerticalSpacer('2rem'); + $return->add(new htmlButton('enterPasswordButton', _('Ok')), 12, 12, 12, 'text-center'); return $return; }