Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Sokolovskyy Roman 2017-09-11 11:11:41 +02:00
commit 137b5dae42
8 changed files with 224 additions and 74 deletions

View File

@ -14,7 +14,7 @@ env:
before_install: true
install:
- wget http://ftp.gnu.org/gnu/parallel/parallel-20120522.tar.bz2
- wget http://ftp.gnu.org/gnu/parallel/parallel-20170822.tar.bz2
- tar -xvjf parallel*
- cd parallel*
- ./configure

View File

@ -8,6 +8,7 @@ AES, Blowfish, Twofish, SSH-1, SSH-2, SFTP, and X.509
* [Browse Git](https://github.com/phpseclib/phpseclib)
* [Code Coverage Report](https://coverage.phpseclib.org/master/latest/)
* Support phpseclib development by [![Becoming a patron](https://img.shields.io/badge/become-patron-brightgreen.svg)](https://www.patreon.com/phpseclib)
## Documentation

View File

@ -51,9 +51,9 @@
}
],
"require": {
"paragonie/constant_time_encoding": "^1|^2",
"paragonie/constant_time_encoding": "^1",
"paragonie/random_compat": "^1.4|^2.0",
"php": ">=7.0"
"php": ">=5.6"
},
"require-dev": {
"phing/phing": "~2.7",

View File

@ -27,6 +27,8 @@ use ParagonIE\ConstantTime\Base64;
use phpseclib\File\ASN1\Element;
use phpseclib\Math\BigInteger;
use phpseclib\Common\Functions\Strings;
use DateTime;
use DateTimeZone;
/**
* Pure-PHP ASN.1 Parser
@ -739,7 +741,7 @@ abstract class ASN1
if (isset($mapping['implicit'])) {
$decoded['content'] = self::decodeTime($decoded['content'], $decoded['type']);
}
return @date(self::$format, $decoded['content']);
return $decoded['content'] ? $decoded['content']->format(self::$format) : false;
case self::TYPE_BIT_STRING:
if (isset($mapping['mapping'])) {
$offset = ord($decoded['content'][0]);
@ -989,7 +991,8 @@ abstract 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'])) {
@ -1151,33 +1154,32 @@ abstract 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);
}
/**

View File

@ -35,7 +35,8 @@ use phpseclib\Exception\UnsupportedAlgorithmException;
use phpseclib\File\ASN1\Element;
use phpseclib\Math\BigInteger;
use phpseclib\File\ASN1\Maps;
use DateTime;
use DateTimeZone;
/**
* Pure-PHP X.509 Parser
@ -1061,7 +1062,7 @@ class X509
}
if (!isset($date)) {
$date = time();
$date = new DateTime($date, new DateTimeZone(@date_default_timezone_get()));
}
$notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore'];
@ -1071,8 +1072,8 @@ class X509
$notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime'];
switch (true) {
case $date < @strtotime($notBefore):
case $date > @strtotime($notAfter):
case $date < new DateTime($notBefore, new DateTimeZone(@date_default_timezone_get())):
case $date > new DateTime($notAfter, new DateTimeZone(@date_default_timezone_get())):
return false;
}
@ -2290,7 +2291,11 @@ class X509
*/
private 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 ['utcTime' => $date];
} else {
@ -2355,8 +2360,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
@ -2624,7 +2633,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;
@ -2777,7 +2788,11 @@ class X509
*/
public 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);
}
$this->startDate = $date->format('D, d M Y H:i:s O', new DateTimeZone(@date_default_timezone_get()));
}
/**
@ -2800,7 +2815,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);
}
$this->endDate = $date->format('D, d M Y H:i:s O', new DateTimeZone(@date_default_timezone_get()));
}
}
@ -3530,8 +3549,9 @@ class X509
}
$i = count($rclist);
$revocationDate = new DateTime('now', new DateTimeZone(@date_default_timezone_get()));
$rclist[] = ['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;
}

View File

@ -1969,7 +1969,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);

View File

@ -103,10 +103,10 @@ 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;
/**#@-*/
/**#@+
@ -897,6 +897,38 @@ class SSH2
*/
private $send_kex_first = true;
/**
* Some versions of OpenSSH incorrectly calculate the key size
*
* @var bool
* @access private
*/
private $bad_key_size_fix = false;
/**
* The selected decryption algorithm
*
* @var string
* @access private
*/
private $decrypt_algorithm = '';
/**
* Should we try to re-connect to re-establish keys?
*
* @var bool
* @access private
*/
private $retry_connect = false;
/**
* Binary Packet Buffer
*
* @var string|false
* @access private
*/
private $binary_packet_buffer = false;
/**
* Default Constructor.
*
@ -1027,7 +1059,7 @@ class SSH2
*
* @access public
*/
function sendIdentificationStringFirst()
public function sendIdentificationStringFirst()
{
$this->send_id_string_first = true;
}
@ -1041,7 +1073,7 @@ class SSH2
*
* @access public
*/
function sendIdentificationStringLast()
public function sendIdentificationStringLast()
{
$this->send_id_string_first = false;
}
@ -1055,7 +1087,7 @@ class SSH2
*
* @access public
*/
function sendKEXINITFirst()
public function sendKEXINITFirst()
{
$this->send_kex_first = true;
}
@ -1069,7 +1101,7 @@ class SSH2
*
* @access public
*/
function sendKEXINITLast()
public function sendKEXINITLast()
{
$this->send_kex_first = false;
}
@ -1190,8 +1222,8 @@ class SSH2
$this->errors[] = utf8_decode($data);
}
if ($matches[3] != '1.99' && $matches[3] != '2.0') {
throw new \RuntimeException("Cannot connect to SSH $matches[1] servers");
if (version_compare($matches[3], '1.99', '<')) {
throw new \RuntimeException("Cannot connect to SSH $matches[3] servers");
}
if (!$this->send_id_string_first) {
@ -1789,6 +1821,8 @@ class SSH2
throw new \UnexpectedValueException('Expected SSH_MSG_NEWKEYS');
}
$this->decrypt_algorithm = $decrypt;
$keyBytes = pack('Na*', strlen($keyBytes), $keyBytes);
$this->encrypt = $this->encryption_algorithm_to_crypt_instance($encrypt);
@ -1959,6 +1993,10 @@ class SSH2
*/
private 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;
@ -2033,6 +2071,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
*/
private function bad_algorithm_candidate($algorithm)
{
switch ($algorithm) {
case 'arcfour256':
case 'aes192-ctr':
case 'aes256-ctr':
return true;
}
return false;
}
/**
* Login
*
@ -2114,6 +2173,13 @@ 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);
}
throw new \RuntimeException('Connection closed by server');
}
@ -2676,7 +2742,7 @@ class SSH2
return false;
}
$response = $this->get_binary_packet();
$response = $this->get_binary_packet(true);
if ($response === false) {
throw new \RuntimeException('Connection closed by server');
}
@ -3130,6 +3196,24 @@ class SSH2
return (bool) ($this->bitmap & self::MASK_LOGIN);
}
/**
* Resets a connection for re-use
*
* @param int $reason
* @access private
*/
private function reset_connection($reason)
{
$this->disconnect_helper($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
*
@ -3140,7 +3224,7 @@ class SSH2
* @throws \RuntimeException on connection errors
* @access private
*/
private function get_binary_packet()
private function get_binary_packet($filter_channel_packets = false)
{
if (!is_resource($this->fsock) || feof($this->fsock)) {
$this->bitmap = 0;
@ -3169,6 +3253,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;
}
throw new \RuntimeException('Invalid size');
}
@ -3182,6 +3271,7 @@ class SSH2
$buffer.= $temp;
$remaining_length-= strlen($temp);
}
$stop = microtime(true);
if (strlen($buffer)) {
$raw.= $this->decrypt !== false ? $this->decrypt->decrypt($buffer) : $buffer;
@ -3215,7 +3305,7 @@ class SSH2
$this->last_packet = $current;
}
return $this->filter($payload);
return $this->filter($payload, $filter_channel_packets);
}
/**
@ -3227,7 +3317,7 @@ class SSH2
* @return string
* @access private
*/
private function filter($payload)
private function filter($payload, $filter_channel_packets)
{
switch (ord($payload[0])) {
case NET_SSH2_MSG_DISCONNECT:
@ -3277,6 +3367,17 @@ class SSH2
// 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 ($filter_channel_packets) {
$this->binary_packet_buffer = $payload;
$this->get_channel_packet(true);
$payload = $this->get_binary_packet(true);
}
break;
case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4
if (strlen($payload) < 4) {
return false;
@ -3461,31 +3562,37 @@ 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 {
if ($this->curTimeout) {
if ($this->curTimeout < 0) {
$this->is_timeout = true;
return true;
}
$read = [$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;
}
$elapsed = microtime(true) - $start;
$this->curTimeout-= $elapsed;
}
$read = [$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;
$response = $this->get_binary_packet();
if ($response === false) {
throw new \RuntimeException('Connection closed by server');
}
$elapsed = microtime(true) - $start;
$this->curTimeout-= $elapsed;
}
$response = $this->get_binary_packet();
if ($response === false) {
throw new \RuntimeException('Connection closed by server');
}
if ($client_channel == -1 && $response === true) {
return true;
}
@ -3805,7 +3912,7 @@ class SSH2
@flush();
@ob_flush();
break;
// basically the same thing as self::LOG_REALTIME with the caveat that NET_SFTP_LOG_REALTIME_FILENAME
// basically the same thing as self::LOG_REALTIME with the caveat that NET_SSH2_LOG_REALTIME_FILENAME
// needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE.
// the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily
// at the beginning of the file

View File

@ -148,5 +148,25 @@ class Functional_Net_SSH2Test extends PhpseclibFunctionalTestCase
$ssh->exec('ls -latr');
$ssh->disablePTY();
$ssh->exec('pwd');
return $ssh;
}
/**
* @depends testDisablePTY
* @group github1167
*/
public function testChannelDataAfterOpen($ssh)
{
$ssh->write("ping 127.0.0.1\n");
$ssh->enablePTY();
$ssh->exec('bash');
$ssh->write("ls -latr\n");
$ssh->setTimeout(1);
$ssh->read();
}
}