$address, 'username' => $username, 'password' => $password, 'privateKey' => $privateKey, 'port' => $port, 'timeout' => $timeout, 'hostFingerprint' => $pinnedHostKey !== null ? self::fingerprint($pinnedHostKey) : null, // Ask for the type of the saved key, else a server with several keys can present another one. 'preferredAlgorithms' => $pinnedHostKey !== null ? ['hostkey' => self::hostKeyAlgorithms($pinnedHostKey)] : [], ]); return new self($provider, $host, $pinnedHostKey); } public function provideConnection(): SFTP { try { $connection = $this->provider->provideConnection(); } catch (UnableToEstablishAuthenticityOfHost $exception) { throw $this->pinnedHostKey !== null ? SftpHostKeyChanged::atHost($this->host, $exception) : $exception; } if ($this->presentedHostKey === null) { $serverKey = $connection->getServerPublicHostKey(); $this->presentedHostKey = is_string($serverKey) ? self::normalize($serverKey) : null; } return $connection; } public function disconnect(): void { $this->provider->disconnect(); } /** * The host key that the server presented on the last connection, as " ". */ public function presentedHostKey(): ?string { return $this->presentedHostKey; } /** * The OpenSSH SHA256 fingerprint of a host key, as `ssh-keygen -l` prints it. */ public static function fingerprint(string $hostKey): string { $blob = base64_decode(explode(' ', $hostKey)[1] ?? '', true); return 'SHA256:'.rtrim(base64_encode(hash('sha256', (string) $blob, true)), '='); } /** * @return array */ private static function hostKeyAlgorithms(string $hostKey): array { $type = explode(' ', $hostKey)[0]; return $type === 'ssh-rsa' ? ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-rsa'] : [$type]; } /** * phpseclib returns the signature algorithm with the key (for example "rsa-sha2-512" for an RSA key). rclone and * known_hosts need the key type, which is the first field of the key blob. */ private static function normalize(string $serverKey): ?string { $base64 = explode(' ', $serverKey)[1] ?? ''; $blob = base64_decode($base64, true); if ($blob === false || strlen($blob) < 4) { return null; } $type = substr($blob, 4, unpack('N', $blob)[1]); return preg_match('/^[a-z0-9@.-]+$/i', $type) === 1 ? $type.' '.$base64 : null; } }