diff --git a/phpseclib/Crypt/Blowfish.php b/phpseclib/Crypt/Blowfish.php index 9c91a0e1..5cf8b7a2 100644 --- a/phpseclib/Crypt/Blowfish.php +++ b/phpseclib/Crypt/Blowfish.php @@ -327,10 +327,10 @@ class Blowfish extends BlockCipher * * @see \phpseclib\Crypt\Common\SymmetricKey::isValidEngine() * @param int $engine - * @access public + * @access protected * @return bool */ - public function isValidEngine($engine) + protected function isValidEngineHelper($engine) { if ($engine == self::ENGINE_OPENSSL) { if (version_compare(PHP_VERSION, '5.3.7') < 0 && $this->key_length != 16) { @@ -343,7 +343,7 @@ class Blowfish extends BlockCipher $this->cipher_name_openssl = 'bf-' . $this->openssl_translate_mode(); } - return parent::isValidEngine($engine); + return parent::isValidEngineHelper($engine); } /** diff --git a/phpseclib/Crypt/Common/SymmetricKey.php b/phpseclib/Crypt/Common/SymmetricKey.php index 14f73fc9..6f198cd3 100644 --- a/phpseclib/Crypt/Common/SymmetricKey.php +++ b/phpseclib/Crypt/Common/SymmetricKey.php @@ -1712,10 +1712,10 @@ abstract class SymmetricKey * * @see self::__construct() * @param int $engine - * @access public + * @access private * @return bool */ - public function isValidEngine($engine) + protected function isValidEngineHelper($engine) { switch ($engine) { case self::ENGINE_OPENSSL: @@ -1756,6 +1756,29 @@ abstract class SymmetricKey return false; } + /** + * Test for engine validity + * + * @see self::__construct() + * @param string $engine + * @access public + * @return bool + */ + public function isValidEngine($engine) + { + static $reverseMap; + if (!isset($reverseMap)) { + $reverseMap = array_map('strtolower', self::ENGINE_MAP); + $reverseMap = array_flip($reverseMap); + } + $engine = strtolower($engine); + if (!isset($reverseMap[$engine])) { + return false; + } + + return $this->isValidEngineHelper($reverseMap[$engine]); + } + /** * Sets the preferred crypt engine * @@ -1816,7 +1839,7 @@ abstract class SymmetricKey self::ENGINE_EVAL ]; foreach ($candidateEngines as $engine) { - if ($this->isValidEngine($engine)) { + if ($this->isValidEngineHelper($engine)) { $this->engine = $engine; break; } diff --git a/phpseclib/Crypt/DES.php b/phpseclib/Crypt/DES.php index 6981cd96..b7c45ee4 100644 --- a/phpseclib/Crypt/DES.php +++ b/phpseclib/Crypt/DES.php @@ -603,10 +603,10 @@ class DES extends BlockCipher * * @see \phpseclib\Crypt\Common\SymmetricKey::isValidEngine() * @param int $engine - * @access public + * @access protected * @return bool */ - public function isValidEngine($engine) + protected function isValidEngineHelper($engine) { if ($this->key_length_max == 8) { if ($engine == self::ENGINE_OPENSSL) { @@ -615,7 +615,7 @@ class DES extends BlockCipher } } - return parent::isValidEngine($engine); + return parent::isValidEngineHelper($engine); } /** diff --git a/phpseclib/Crypt/RC2.php b/phpseclib/Crypt/RC2.php index 8854be51..013b32ec 100644 --- a/phpseclib/Crypt/RC2.php +++ b/phpseclib/Crypt/RC2.php @@ -284,10 +284,10 @@ class RC2 extends BlockCipher * * @see \phpseclib\Crypt\Common\SymmetricKey::__construct() * @param int $engine - * @access public + * @access protected * @return bool */ - public function isValidEngine($engine) + protected function isValidEngineHelper($engine) { switch ($engine) { case self::ENGINE_OPENSSL: @@ -298,7 +298,7 @@ class RC2 extends BlockCipher $this->cipher_name_openssl = 'rc2-' . $this->openssl_translate_mode(); } - return parent::isValidEngine($engine); + return parent::isValidEngineHelper($engine); } /** diff --git a/phpseclib/Crypt/RC4.php b/phpseclib/Crypt/RC4.php index 865776ff..e2d8b24e 100644 --- a/phpseclib/Crypt/RC4.php +++ b/phpseclib/Crypt/RC4.php @@ -139,10 +139,10 @@ class RC4 extends StreamCipher * * @see \phpseclib\Crypt\Common\SymmetricKey::__construct() * @param int $engine - * @access public + * @access protected * @return bool */ - public function isValidEngine($engine) + protected function isValidEngineHelper($engine) { if ($engine == self::ENGINE_OPENSSL) { if (version_compare(PHP_VERSION, '5.3.7') >= 0) { @@ -164,7 +164,7 @@ class RC4 extends StreamCipher } } - return parent::isValidEngine($engine); + return parent::isValidEngineHelper($engine); } /** diff --git a/phpseclib/Crypt/Rijndael.php b/phpseclib/Crypt/Rijndael.php index e78d578c..1553bb09 100644 --- a/phpseclib/Crypt/Rijndael.php +++ b/phpseclib/Crypt/Rijndael.php @@ -273,10 +273,10 @@ class Rijndael extends BlockCipher * * @see \phpseclib\Crypt\Common\SymmetricKey::__construct() * @param int $engine - * @access public + * @access protected * @return bool */ - public function isValidEngine($engine) + protected function isValidEngineHelper($engine) { switch ($engine) { case self::ENGINE_OPENSSL: @@ -294,7 +294,7 @@ class Rijndael extends BlockCipher } } - return parent::isValidEngine($engine); + return parent::isValidEngineHelper($engine); } /** diff --git a/phpseclib/Crypt/TripleDES.php b/phpseclib/Crypt/TripleDES.php index 9dede481..3c665f29 100644 --- a/phpseclib/Crypt/TripleDES.php +++ b/phpseclib/Crypt/TripleDES.php @@ -178,10 +178,10 @@ class TripleDES extends DES * * @see \phpseclib\Crypt\Common\SymmetricKey::__construct() * @param int $engine - * @access public + * @access protected * @return bool */ - public function isValidEngine($engine) + protected function isValidEngineHelper($engine) { if ($engine == self::ENGINE_OPENSSL) { $this->cipher_name_openssl_ecb = 'des-ede3'; @@ -189,7 +189,7 @@ class TripleDES extends DES $this->cipher_name_openssl = $mode == 'ecb' ? 'des-ede3' : 'des-ede3-' . $mode; } - return parent::isValidEngine($engine); + return parent::isValidEngineHelper($engine); } /** diff --git a/phpseclib/File/ANSI.php b/phpseclib/File/ANSI.php index 07624d60..6c42ef1f 100644 --- a/phpseclib/File/ANSI.php +++ b/phpseclib/File/ANSI.php @@ -419,7 +419,7 @@ class ANSI if ($this->x > $this->max_x) { $this->x = 0; - $this->y++; + $this->newLine(); } else { $this->x++; } diff --git a/phpseclib/File/ASN1.php b/phpseclib/File/ASN1.php index 57cd79cd..53ac6ff2 100644 --- a/phpseclib/File/ASN1.php +++ b/phpseclib/File/ASN1.php @@ -1349,27 +1349,6 @@ abstract class ASN1 return $temp != false ? $temp : $str; } - /** - * DER-decode the length - * - * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See - * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information. - * - * @access public - * @param string $string - * @return int - */ - public static function decodeLength(&$string) - { - $length = ord(Strings::shift($string)); - if ($length & 0x80) { // definite length, long form - $length&= 0x7F; - $temp = Strings::shift($string, $length); - list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)); - } - return $length; - } - /** * DER-encode the length * diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index d63d71fb..fe19b9cf 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -3132,12 +3132,13 @@ class X509 * Returns a list of all extensions in use in certificate, CSR or CRL * * @param array $cert optional + * @param string $path optional * @access public * @return array */ - public function getExtensions($cert = null) + public function getExtensions($cert = null, $path = null) { - return $this->getExtensionsHelper($cert); + return $this->getExtensionsHelper($cert, $path); } /** @@ -3689,7 +3690,7 @@ class X509 if (is_array($rclist = $this->subArray($crl, 'tbsCertList/revokedCertificates'))) { if (($i = $this->revokedCertificate($rclist, $serial)) !== false) { - return $this->getExtensionsHelper($crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); + return $this->getExtensions($crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); } } diff --git a/phpseclib/Math/BigInteger/Engines/BCMath/Base.php b/phpseclib/Math/BigInteger/Engines/BCMath/Base.php index 91efeb00..026cd86f 100644 --- a/phpseclib/Math/BigInteger/Engines/BCMath/Base.php +++ b/phpseclib/Math/BigInteger/Engines/BCMath/Base.php @@ -60,7 +60,7 @@ abstract class Base extends BCMath * @param string $class * @return \phpseclib\Math\BigInteger\Engines\BCMath */ - public static function powModHelper(BCMath $x, BCMath $e, BCMath $n, $class) + protected static function powModHelper(BCMath $x, BCMath $e, BCMath $n, $class) { if (empty($e->value)) { $temp = new $class(); diff --git a/phpseclib/Math/BigInteger/Engines/PHP/Base.php b/phpseclib/Math/BigInteger/Engines/PHP/Base.php index 3529906e..640d17c6 100644 --- a/phpseclib/Math/BigInteger/Engines/PHP/Base.php +++ b/phpseclib/Math/BigInteger/Engines/PHP/Base.php @@ -80,7 +80,7 @@ abstract class Base extends PHP * @param string $class * @return \phpseclib\Math\BigInteger\Engines\PHP */ - public static function powModHelper(PHP $x, PHP $e, PHP $n, $class) + protected static function powModHelper(PHP $x, PHP $e, PHP $n, $class) { if (empty($e->value)) { $temp = new $class(); diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index e1e2f48d..4deaed70 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -881,6 +881,22 @@ class SSH2 */ private static $connections; + /** + * Send the identification string first? + * + * @var bool + * @access private + */ + private $send_id_string_first = true; + + /** + * Send the key exchange initiation packet first? + * + * @var bool + * @access private + */ + private $send_kex_first = true; + /** * Default Constructor. * @@ -995,13 +1011,69 @@ class SSH2 * OpenSSL, mcrypt, Eval, PHP * * @param int $engine - * @access private + * @access public */ 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 * @@ -1044,7 +1116,9 @@ class SSH2 $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, @@ -1120,16 +1194,26 @@ class SSH2 throw new \RuntimeException("Cannot connect to SSH $matches[1] servers"); } - $response = $this->get_binary_packet(); - if ($response === false) { - throw new \RuntimeException('Connection closed by server'); + if (!$this->send_id_string_first) { + fputs($this->fsock, $this->identifier . "\r\n"); } - if (!strlen($response) || ord($response[0]) != NET_SSH2_MSG_KEXINIT) { - throw new \UnexpectedValueException('Expected SSH_MSG_KEXINIT'); + if (!$this->send_kex_first) { + $response = $this->get_binary_packet(); + if ($response === false) { + throw new \RuntimeException('Connection closed by server'); + } + + if (!strlen($response) || ord($response[0]) != NET_SSH2_MSG_KEXINIT) { + throw new \UnexpectedValueException('Expected SSH_MSG_KEXINIT'); + } + + if (!$this->key_exchange($response)) { + return false; + } } - if (!$this->key_exchange($response)) { + if ($this->send_kex_first && !$this->key_exchange()) { return false; } @@ -1177,13 +1261,13 @@ class SSH2 /** * Key Exchange * - * @param string $kexinit_payload_server + * @param string $kexinit_payload_server optional * @throws \UnexpectedValueException on receipt of unexpected packets * @throws \RuntimeException on other errors * @throws \phpseclib\Exception\NoSupportedAlgorithmsException when none of the algorithms phpseclib has loaded are compatible * @access private */ - private function key_exchange($kexinit_payload_server) + private function key_exchange($kexinit_payload_server = false) { $kex_algorithms = [ // Elliptic Curve Diffie-Hellman Key Agreement (ECDH) using @@ -1321,6 +1405,49 @@ 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) { + throw new \RuntimeException('Connection closed by server'); + } + + if (!strlen($kexinit_payload_server) || ord($kexinit_payload_server[0]) != NET_SSH2_MSG_KEXINIT) { + throw new \UnexpectedValueException('Expected SSH_MSG_KEXINIT'); + } + } + $response = $kexinit_payload_server; Strings::shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT) $server_cookie = Strings::shift($response, 16); @@ -1392,39 +1519,9 @@ class SSH2 $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 @@ -4236,7 +4333,7 @@ class SSH2 case $r->compare($q) >= 0: case $s->equals($zero): case $s->compare($q) >= 0: - $this->disconnectHepler(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); throw new \RuntimeException('Invalid signature'); } diff --git a/tests/Unit/Crypt/RC4Test.php b/tests/Unit/Crypt/RC4Test.php index e7aa6e3b..872b6a48 100644 --- a/tests/Unit/Crypt/RC4Test.php +++ b/tests/Unit/Crypt/RC4Test.php @@ -215,20 +215,20 @@ class Unit_Crypt_RC4Test extends PhpseclibTestCase { $objects = $engines = array(); $temp = new RC4(RC4::MODE_CTR); - $temp->setPreferredEngine(RC4::ENGINE_INTERNAL); + $temp->setPreferredEngine('internal'); $objects[] = $temp; $engines[] = 'internal'; - if ($temp->isValidEngine(RC4::ENGINE_MCRYPT)) { + if ($temp->isValidEngine('mcrypt')) { $temp = new RC4(RC4::MODE_CTR); - $temp->setPreferredEngine(RC4::ENGINE_MCRYPT); + $temp->setPreferredEngine('mcrypt'); $objects[] = $temp; $engines[] = 'mcrypt'; } - if ($temp->isValidEngine(RC4::ENGINE_OPENSSL)) { + if ($temp->isValidEngine('openssl')) { $temp = new RC4(RC4::MODE_CTR); - $temp->setPreferredEngine(RC4::ENGINE_OPENSSL); + $temp->setPreferredEngine('openssl'); $objects[] = $temp; $engines[] = 'OpenSSL'; } diff --git a/tests/Unit/Crypt/TripleDESTest.php b/tests/Unit/Crypt/TripleDESTest.php index cd8a7ccf..64350663 100644 --- a/tests/Unit/Crypt/TripleDESTest.php +++ b/tests/Unit/Crypt/TripleDESTest.php @@ -167,7 +167,7 @@ class Unit_Crypt_TripleDESTest extends PhpseclibTestCase $des->disablePadding(); $result = $des->encrypt($plaintext); $plaintext = bin2hex($plaintext); - $this->assertEquals($result, $expected, "Failed asserting that $plaintext yielded expected output in $engin engine"); + $this->assertEquals($result, $expected, "Failed asserting that $plaintext yielded expected output in $engine engine"); } public function testInnerChaining() diff --git a/tests/Unit/File/ANSITest.php b/tests/Unit/File/ANSITest.php index 3f6e607e..5f356e11 100644 --- a/tests/Unit/File/ANSITest.php +++ b/tests/Unit/File/ANSITest.php @@ -44,4 +44,22 @@ class Unit_File_ANSITest extends PhpseclibTestCase $this->assertSame($ansi->getScreen(), $expected); } -} + + public function testLineOverflow() + { + $str = ''; + foreach (range('a', 'y') as $char) { + $str.= "$char\r\n"; + } + $str.= str_repeat('z', 100); + + $ansi = new ANSI(); + $ansi->appendString($str); + + $screen = $ansi->getScreen(); + + $lines = explode("\r\n", $screen); + $this->assertSame(24, count($lines)); + $this->assertSame(str_repeat('z', 80), $lines[22]); + } +} \ No newline at end of file diff --git a/tests/Unit/File/X509/CRLTest.php b/tests/Unit/File/X509/CRLTest.php new file mode 100644 index 00000000..78ad5d7d --- /dev/null +++ b/tests/Unit/File/X509/CRLTest.php @@ -0,0 +1,24 @@ + + * @copyright 2017 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + */ + +use phpseclib\File\X509; + +class Unit_File_X509_CRLTest extends PhpseclibTestCase +{ + public function testLoadCRL() + { + $test = file_get_contents(__DIR__ . '/crl.bin'); + + $x509 = new X509(); + + $x509->loadCRL($test); + + $reason = $x509->getRevokedCertificateExtension('9048354325167497831898969642461237543', 'id-ce-cRLReasons'); + + $this->assertSame('unspecified', $reason); + } +} diff --git a/tests/Unit/File/X509/X509Test.php b/tests/Unit/File/X509/X509Test.php index 41258b21..773e2c41 100644 --- a/tests/Unit/File/X509/X509Test.php +++ b/tests/Unit/File/X509/X509Test.php @@ -181,6 +181,7 @@ aBtsWpliLSex/HHhtRW9AkBGcq67zKmEpJ9kXcYLEjJii3flFS+Ct/rNm+Hhm1l7 $issuer->setDN($subject->getDN()); $x509 = new X509(); + $x509->setEndDate('lifetime'); $result = $x509->sign($issuer, $subject); $cert = $x509->saveX509($result); diff --git a/tests/Unit/File/X509/crl.bin b/tests/Unit/File/X509/crl.bin new file mode 100644 index 00000000..ef6d3ee1 Binary files /dev/null and b/tests/Unit/File/X509/crl.bin differ