From: terrafrost <terrafrost@php.net>
Date: Wed, 22 Nov 2023 04:44:23 -0600
Subject: SSH2: add support for RFC8308

Origin: backport, https://github.com/phpseclib/phpseclib/commit/7cc1814f9d6ee57f6d830f44fe252fdf6617bf6c
---
 phpseclib/Net/SSH2.php | 33 +++++++++++++++++++++++++++++++--
 1 file changed, 31 insertions(+), 2 deletions(-)

diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php
index 7722d97..9a98f8c 100644
--- a/phpseclib/Net/SSH2.php
+++ b/phpseclib/Net/SSH2.php
@@ -343,6 +343,14 @@ class SSH2
      */
     private $languages_client_to_server = false;
 
+    /**
+     * Server Signature Algorithms
+     *
+     * @link https://www.rfc-editor.org/rfc/rfc8308.html#section-3.1
+     * @var array|false
+     */
+    private $server_sig_algs = false;
+
     /**
      * Preferred Algorithms
      *
@@ -1112,6 +1120,7 @@ class SSH2
             4 => 'NET_SSH2_MSG_DEBUG',
             5 => 'NET_SSH2_MSG_SERVICE_REQUEST',
             6 => 'NET_SSH2_MSG_SERVICE_ACCEPT',
+            7 => 'NET_SSH2_MSG_EXT_INFO', // RFC 8308
             20 => 'NET_SSH2_MSG_KEXINIT',
             21 => 'NET_SSH2_MSG_NEWKEYS',
             30 => 'NET_SSH2_MSG_KEXDH_INIT',
@@ -1486,6 +1495,8 @@ class SSH2
             $preferred['client_to_server']['comp'] :
             SSH2::getSupportedCompressionAlgorithms();
 
+        $kex_algorithms = array_merge($kex_algorithms, array('ext-info-c'));
+
         // some SSH servers have buggy implementations of some of the above algorithms
         switch (true) {
             case $this->server_identifier == 'SSH-2.0-SSHD':
@@ -2269,7 +2280,23 @@ class SSH2
                 throw new ConnectionClosedException('Connection closed by server');
             }
 
-            list($type, $service) = Strings::unpackSSH2('Cs', $response);
+            list($type) = Strings::unpackSSH2('C', $response);
+
+            if ($type == NET_SSH2_MSG_EXT_INFO) {
+                list($nr_extensions) = Strings::unpackSSH2('N', $response);
+                for ($i = 0; $i < $nr_extensions; $i++) {
+                    list($extension_name, $extension_value) = Strings::unpackSSH2('ss', $response);
+                    if ($extension_name == 'server-sig-algs') {
+                        $this->server_sig_algs = explode(',', $extension_value);
+                    }
+                }
+
+                $response = $this->get_binary_packet();
+                list($type) = Strings::unpackSSH2('C', $response);
+            }
+
+            list($service) = Strings::unpackSSH2('s', $response);
+
             if ($type != NET_SSH2_MSG_SERVICE_ACCEPT || $service != 'ssh-userauth') {
                 $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
                 throw new \UnexpectedValueException('Expected SSH_MSG_SERVICE_ACCEPT');
@@ -2544,7 +2571,9 @@ class SSH2
         if ($publickey instanceof RSA) {
             $privatekey = $privatekey->withPadding(RSA::SIGNATURE_PKCS1);
             $algos = ['rsa-sha2-256', 'rsa-sha2-512', 'ssh-rsa'];
-            if (isset($this->preferred['hostkey'])) {
+            if ($this->server_sig_algs) {
+                $algos = array_intersect($this->server_sig_algs, $algos);
+            } elseif (isset($this->preferred['hostkey'])) {
                 $algos = array_intersect($this->preferred['hostkey'], $algos);
             }
             $algo = self::array_intersect_first($algos, $this->supported_private_key_algorithms);
