10 个不稳定版本 (3 个破坏性更新)

0.4.2 2024 年 5 月 28 日
0.4.1 2024 年 5 月 19 日
0.3.2 2024 年 5 月 5 日
0.3.1 2024 年 4 月 30 日
0.1.1 2024 年 2 月 29 日

#140 in 嵌入式开发

Download history 252/week @ 2024-04-26 321/week @ 2024-05-03 17/week @ 2024-05-10 121/week @ 2024-05-17 137/week @ 2024-05-24 18/week @ 2024-05-31 4/week @ 2024-06-07

每月下载量 759

MIT 许可证

120KB
2.5K SLoC

zssh

Documentation Crates.io

这个软件包包含一个针对嵌入式设备编写的最小 SSH 服务器 实现,使用安全的 Rust 语言。这个库的特点是同时是 no_stdno_allocasync,并实现了 SSH 规范的有用子集,具有非常低的内存使用量。截至目前,我还没有发现其他具有这些特征的公开 Rust 实现。

  • #![no_std]
  • #![禁止(不安全代码)]
  • 零堆内存分配
  • 使用异步 I/O
  • stable 上编译

这个库仍在早期开发阶段。可能添加新功能,API 也可能会以破坏性的方式更改!欢迎提交拉取请求和问题。

协议功能

功能 状态 详细信息
密钥交换 ✔️ curve25519-sha256
主机密钥 ✔️ ssh-ed25519
加密 ✔️ chacha20-poly1305@openssh.com
完整性 ✔️ (隐式)
压缩
身份验证 ✔️ none, publickey
公钥身份验证 ✔️ ssh-ed25519
客户端环境变量 目前被忽略。
执行请求 ✔️ 您必须解析命令。
shell 请求 ✔️ 您必须实现 shell。
PTY、X11、TCP 不会实现。
多路复用 ✔️ 打开的通道是序列化的。
重新密钥协商 ✔️ 由客户端发起。

兼容性

该库的目标是在尽可能低内存和代码占用的情况下实现 SSH 连接,因此它实现了一个小巧而现代的加密算法套件,这些算法将被大多数来自过去十年左右的客户端理解。

使用方法

examples/demo.rs 中的示例将设置一个在本机上运行的本地 SSH 服务器,端口为 2222。您可以使用 zssh 用户名(使用存储在 examples/zssh.priv 的私钥作为 SSH 身份)或 guest 用户名(无需认证)来连接到它。该示例支持三个命令,展示了 API 的不同方面

  1. sha256sum 计算stdin的SHA-256摘要并返回到stdout;
  2. echo 将stdin发送回stdout而不做任何更改;
  3. sum 打印出命令行上传递的所有数字的总和;

该示例仅使用 Tokio 在任何操作系统上运行。

设计注意事项

API 的设计受到某些设计约束的指导,其中一些在本节中进行了描述。

阅读

由于无分配的要求,通道在没有迫使您从通道读取的情况下不能有非零的接收窗口。这是因为接收数据消息会阻止在处理该消息或其有效载荷丢失之前取得任何进一步的进展。

这个约束意味着除非您愿意在写入后不再从通道读取,否则您必须为所有通道读取指定确切的读取长度。在 API 中,这表现为 Channel::reader 接受一个可选的大小,具有以下语义

  • 如果提供了大小,则读取器对象 必须 读取到那个大小,然后可以读取或写入通道;
  • 否则,它将读取到EOF,但一旦您删除了读取器,就不能再次在通道上调用 Channel::reader

此外,请注意由于如何向客户端通告接收窗口而可能产生的性能影响。如果向读取器提供了大小,我们将该大小通告为我们的接收窗口,因此客户端一次可能不会提交超过那么多字节。如果没有向读取器提供大小,我们将维护一个 2^32 - 1 字节的接收窗口,这是协议允许的最大值,允许客户端发送无限量的数据。

写入

由于无分配的要求,传输内部无法缓冲写入,因为它没有处理与通道无关的消息(如客户端的周期性密钥重置请求)的方法,而不会破坏数据包缓冲区。因此,在 API 中,对 Channel::write_all_stdoutChannel::write_all_stderr 的任何单个调用都至少对应于一个 SSH 协议级别的消息。

API 以 Channel::writer 的形式提供零拷贝功能,允许调用者直接将数据写入数据包缓冲区以发送。这既避免了拷贝,又允许调用者尽可能多地缓冲数据,以适应所需的单个 SSH 协议级别消息。

但是,请注意

  • Writer::write_all 方法消耗了写入器,这是设计使然,因为一旦数据准备好发送,它就会被传输原地加密;这是为了避免 API 脚本错误,并且没有性能影响,因为 Channel::writer 不做任何工作;
  • 根据前一点,调用者必须假设Writer::buffer包含垃圾数据,它不会被crate清零!

性能

该库依赖于embedded-io-async中的异步I/O特性,并以零拷贝的方式编写。预计主要性能瓶颈将是传输层加密的开销。在内存使用方面,传输模块借用一个外部提供的字节切片,将其用作数据包缓冲区,而其内部状态机大约需要1-2KB的内存(主要包含密钥材料和加密状态),栈使用量低。

虽然SSH规范要求最小数据包缓冲区大小为32768字节,但在实际操作中,大多数客户端在4KB大小的缓冲区中即可正常工作,甚至更小的数据包缓冲区也可以通过适当配置的客户端来实现;一般来说,限制因素是客户端初始KEXINIT消息的大小。

这使得zssh可以在大多数适合启动SSH服务器的嵌入式目标上使用。

多路复用

该库支持在同一个连接上处理多个并发通道。它通过一次只处理一个通道并延迟响应进一步的通道打开请求来实现这一点。这样,即使服务器只支持单个TCP连接,客户端也可以使用多路复用以提高用户体验。目前,传输模块被硬编码为最多可以挂起四个通道,同时一个通道处于活动状态。超出此限制的附加通道打开请求将被传输模块拒绝。

依赖项

~2.4–3.5MB
~64K SLoC