diff --git a/CHANGELOG.md b/CHANGELOG.md index 77a42d4..4c8d1ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## [4.1.0] - Unreleased +## [4.1.0] - 2026-09-04 - Added `SSHClient.pipelineChannelRequests`, off by default, which sends all of a session's channel requests before reading any reply instead of waiting for each one in turn. `execute` and `shell` send `env`, agent forwarding, `pty-req` and `x11-req` ahead of `exec` or `shell`, and each of them cost a round trip: against a server 40 ms away, `execute` with a pty measured 246 ms before and 206 ms after. RFC 4254 §5.4 permits sending further messages without waiting and §4 requires the peer to answer a channel's requests in the order it received them, which is what `ssh(1)` relies on when it does the same thing. Setting it also adopts OpenSSH's reporting, because it has to: the command is on the wire before a refusal can come back, so a refused `pty-req`, `env`, agent forwarding or `x11-req` is reported through `printDebug` and the command runs, the way `ssh(1)` prints `PTY allocation request failed on channel 0` and carries on, rather than throwing an error that means the command has already run. Only a refused `exec` or `shell` still throws `SSHChannelRequestError`, because nothing has run when that one fails. Leaving it unset changes nothing, down to the order the requests go out and the message of every error [#243]. - Added a `dartssh2-nopty` account to the interop server, which `PermitTTY no` applies to, so both sides of a refused `pty-req` are exercised against a real OpenSSH: by default the command does not run, and with pipelining it does [#243]. - Fixed the package reading as web-incompatible on pub.dev. `SftpFile.downloadToRandomAccess` takes a `dart:io` `RandomAccessFile`, and naming that type from the SFTP library was enough for pub.dev to drop `platform:web` from the whole package, which also keeps it out of any search filtered to web. Everything else already compiled and ran there, and 4.0.0 made the ciphers and SFTP work. The method moves to an `SftpFileDownload` extension in its own library, exported conditionally the way `SSHSocket` and dynamic forwarding already are, so nothing changes for a caller on the VM: same import, same call. On the web the extension is simply absent, as is the `RandomAccessFile` it would need. Confirmed with pana, the tool pub.dev scores with: `platform:web` is present after and absent before [#248]. diff --git a/README.md b/README.md index 33c7cce..c95a0f5 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ SSH and SFTP client written in pure Dart, aiming to be feature-rich as well as e ## ✨ Features -- **Pure Dart**: Working with both Dart VM and Flutter. +- **Pure Dart**: Runs on the Dart VM, Flutter and the web. Browser support is checked in CI against a real OpenSSH server, see [Web support](#web-support). - **SSH Session**: Executing commands, spawning shells, setting environment variables, pseudo terminals, etc. - **Authentication**: Supports password, in-memory private keys (`SSHKeyPair`), external asynchronous identities (`SSHIdentity` for Secure Enclave, YubiKey/FIDO2, smart cards, OS agents), RFC 4252 §7.8 public-key probing, RFC 4252 §9 hostbased authentication, and keyboard-interactive authentication. - **Forwarding**: Supports local forwarding, remote forwarding, and dynamic forwarding (SOCKS5 CONNECT). @@ -167,30 +167,34 @@ already trust end to end. ### Web support -Direct native TCP sockets are not available in browsers, so this will fail on -Flutter Web / Dart Web: +dartssh2 runs on Flutter Web and Dart Web. The protocol, authentication, +sessions, channel forwarding and SFTP all work compiled to JavaScript, and CI +checks it rather than assuming it: on every pull request a browser completes a +handshake, runs a command, holds an `aes256-gcm` session and round-trips a file +over SFTP against a real OpenSSH server. + +**You have to supply the transport.** Browsers cannot open TCP sockets, so this +fails: ```dart await SSHSocket.connect('host', 22); ``` -For web apps, use a custom `SSHSocket` transport over a browser-supported -channel (for example, a WebSocket tunnel/proxy to your SSH endpoint). - -Before 4.0.0 that was all it said, and it was not enough: every AEAD cipher and -the whole of SFTP went through `ByteData.getUint64`/`setUint64`, which throw -under dart2js, so a browser connection died at the first encrypted packet even -with a correct transport. That is fixed, and a `dart test -p chrome` job now -guards it. +`SSHSocket` is an interface, so give it something a browser does have, normally +a WebSocket to a proxy that bridges to your SSH endpoint. For a minimal working +pair of both ends, see [`tool/ws_bridge.dart`](tool/ws_bridge.dart) and the +socket in +[`test/src/integration/web_interop_test.dart`](test/src/integration/web_interop_test.dart). -Two caveats remain. `chacha20-poly1305@openssh.com` cannot be used on the web, -because PointyCastle's Poly1305 requires full-width 64-bit integers. It sits -third in the default cipher list, so AES-GCM or AES-CTR is normally negotiated -and nothing needs doing. Only pinning ChaCha20-Poly1305 explicitly will fail. +Two things are not available in a browser: -And `SftpFile.downloadToRandomAccess` is not available, since it takes a -`dart:io` `RandomAccessFile` and there is no local file to hand it. Use -`SftpFile.downloadTo`, which takes a sink, or `SftpFile.read`. +- `chacha20-poly1305@openssh.com`, because PointyCastle's Poly1305 needs + full-width 64-bit integers. It sits third in the default cipher list, so + AES-GCM or AES-CTR is negotiated instead and nothing needs doing. Only + pinning it explicitly will fail. +- `SftpFile.downloadToRandomAccess`, which takes a `dart:io` + `RandomAccessFile`, and there is no local file to hand it. Use + `SftpFile.downloadTo`, which takes a sink, or `SftpFile.read`. ### Customize client SSH identification diff --git a/pubspec.yaml b/pubspec.yaml index 36da4bb..3791839 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: dartssh2 -version: 4.0.1 +version: 4.1.0 description: SSH and SFTP client written in pure Dart, aiming to be feature-rich as well as easy to use. homepage: https://github.com/vicajilau/dartssh2 repository: https://github.com/vicajilau/dartssh2