diff --git a/docker-compose.yml b/docker-compose.yml index 44742374b..7e3efd07f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,16 @@ services: - ./test_files/sftp/id_rsa.pub:/home/bar/.ssh/keys/id_rsa.pub ports: - "2222:22" + sftp_eddsa_only: + container_name: sftp_eddsa_only + restart: always + image: atmoz/sftp + volumes: + - ./test_files/sftp/users.conf:/etc/sftp/users.conf + - ./test_files/sftp/sshd_custom_configs.sh:/etc/sftp.d/sshd_custom_configs.sh + - ./test_files/sftp/ssh_host_ed25519_key:/etc/ssh/ssh_host_ed25519_key + ports: + - "2223:22" ftp: container_name: ftp restart: always diff --git a/src/PhpseclibV3/SftpConnectionProvider.php b/src/PhpseclibV3/SftpConnectionProvider.php index 4dda92093..095085d21 100644 --- a/src/PhpseclibV3/SftpConnectionProvider.php +++ b/src/PhpseclibV3/SftpConnectionProvider.php @@ -78,6 +78,11 @@ class SftpConnectionProvider implements ConnectionProvider */ private $maxTries; + /** + * @var array + */ + private $preferredAlgorithms; + public function __construct( string $host, string $username, @@ -89,7 +94,8 @@ public function __construct( int $timeout = 10, int $maxTries = 4, string $hostFingerprint = null, - ConnectivityChecker $connectivityChecker = null + ConnectivityChecker $connectivityChecker = null, + array $preferredAlgorithms = [] ) { $this->host = $host; $this->username = $username; @@ -102,6 +108,7 @@ public function __construct( $this->hostFingerprint = $hostFingerprint; $this->connectivityChecker = $connectivityChecker ?: new SimpleConnectivityChecker(); $this->maxTries = $maxTries; + $this->preferredAlgorithms = $preferredAlgorithms; } public function provideConnection(): SFTP @@ -131,6 +138,7 @@ public function provideConnection(): SFTP private function setupConnection(): SFTP { $connection = new SFTP($this->host, $this->port, $this->timeout); + $connection->setPreferredAlgorithms($this->preferredAlgorithms); $connection->disableStatCache(); try { @@ -190,7 +198,8 @@ public static function fromArray(array $options): SftpConnectionProvider $options['timeout'] ?? 10, $options['maxTries'] ?? 4, $options['hostFingerprint'] ?? null, - $options['connectivityChecker'] ?? null + $options['connectivityChecker'] ?? null, + $options['preferredAlgorithms'] ?? [], ); } diff --git a/src/PhpseclibV3/SftpConnectionProviderTest.php b/src/PhpseclibV3/SftpConnectionProviderTest.php index 31764c0e5..97b81f64c 100644 --- a/src/PhpseclibV3/SftpConnectionProviderTest.php +++ b/src/PhpseclibV3/SftpConnectionProviderTest.php @@ -4,6 +4,7 @@ namespace League\Flysystem\PhpseclibV3; +use phpseclib3\Exception\NoSupportedAlgorithmsException; use phpseclib3\Net\SFTP; use PHPUnit\Framework\TestCase; @@ -21,6 +22,8 @@ */ class SftpConnectionProviderTest extends TestCase { + const KEX_ACCEPTED_BY_DEFAULT_OPENSSH_BUT_DISABLED_IN_EDDSA_ONLY = 'diffie-hellman-group14-sha256'; + public static function setUpBeforeClass(): void { if ( ! class_exists(SFTP::class)) { @@ -241,6 +244,60 @@ public function providing_an_invalid_password(): void $provider->provideConnection(); } + /** + * @test + */ + public function authenticate_with_supported_preferred_kex_algorithm_succedes(): void + { + $provider = SftpConnectionProvider::fromArray( + [ + 'host' => 'localhost', + 'username' => 'foo', + 'password' => 'pass', + 'port' => 2222, + 'preferredAlgorithms' => [ + 'kex' => [self::KEX_ACCEPTED_BY_DEFAULT_OPENSSH_BUT_DISABLED_IN_EDDSA_ONLY], + ], + ] + ); + $this->assertInstanceOf(SFTP::class, $provider->provideConnection()); + + $provider = SftpConnectionProvider::fromArray( + [ + 'host' => 'localhost', + 'username' => 'foo', + 'password' => 'pass', + 'port' => 2223, + 'preferredAlgorithms' => [ + 'kex' => ['curve25519-sha256'], + ], + ] + ); + $provider->provideConnection(); + $this->assertInstanceOf(SFTP::class, $provider->provideConnection()); + } + + /** + * @test + */ + public function authenticate_with_unsupported_preferred_kex_algorithm_failes(): void + { + $provider = SftpConnectionProvider::fromArray( + [ + 'host' => 'localhost', + 'username' => 'foo', + 'password' => 'pass', + 'port' => 2223, + 'preferredAlgorithms' => [ + 'kex' => [self::KEX_ACCEPTED_BY_DEFAULT_OPENSSH_BUT_DISABLED_IN_EDDSA_ONLY], + ], + ] + ); + + $this->expectException(NoSupportedAlgorithmsException::class); + $provider->provideConnection(); + } + private function computeFingerPrint(string $publicKey): string { $content = explode(' ', $publicKey, 3); diff --git a/test_files/sftp/sshd_custom_configs.sh b/test_files/sftp/sshd_custom_configs.sh new file mode 100755 index 000000000..b173f3caa --- /dev/null +++ b/test_files/sftp/sshd_custom_configs.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +cat <<'EOF' >> /etc/ssh/sshd_config + +KexAlgorithms curve25519-sha256 +Ciphers aes256-gcm@openssh.com +MACs hmac-sha2-256-etm@openssh.com +HostKeyAlgorithms ssh-ed25519 + +EOF