V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
pierreoui12
V2EX  ›  程序员

为什么 RSA 证书在新版 OpenSSH 上认证失败? russh 库 0.59 协议编码的两层坑

  •  
  •   pierreoui12 · 8 小时 43 分钟前 · 168 次点击

    最近在做一个基于 Tauri + Rust 的 SSH 终端客户端(OxideTerm),底层 SSH 协议用的 russh 。这周修了一个比较有意思的兼容性 bug ,涉及 RSA SHA-2 认证在严格 OpenSSH 配置下的三条路径,记录一下排查过程。


    问题

    有用户反馈 RSA 私钥在某些新部署的服务端上登不上。让 AI 排查后发现不是单点故障,而是 RSA SHA-2 相关的三条认证路径都有问题:

    • 普通私钥认证
    • SSH Agent 认证
    • OpenSSH Certificate 认证

    根源是同一件事:OpenSSH 8.2+ 默认禁用 ssh-rsa ( SHA-1 ),只允许 rsa-sha2-256 / rsa-sha2-512。客户端如果还在用旧的算法名或者协商逻辑有漏洞,严格服务端直接拒。


    私钥和 Agent:相对直白

    私钥路径:没有根据 server-sig-algs 做正确的 RSA 算法协商。修复后按 sha2-512 → sha2-256 → ssh-rsa 的顺序尝试,服务端不让继续就停。

    Agent 路径:调 russh 的认证 API 时 hash_alg 传了 None ,等于放弃了 SHA-2 协商。修复策略一样——RSA key 走 SHA-2 顺序尝试,区分 transport error 和 sign error ,前者立即返回,后者换算法重试。


    Certificate:这才是有意思的部分

    russh 0.59 暴露了 authenticate_certificate_with(cert, hash_alg, signer) 这个 API 。第一反应是把 hash_alg 传进去就行了?

    不行。

    hash_alg 确实能控制 signer 用哪个 hash 算法签名,但 russh 在把证书认证请求编码发给服务端的时候,外层的算法名仍然写死了 [email protected]

    真实 OpenSSH 的行为是:先看外层算法名,不对直接拒,根本不看你签名用了什么 hash

    客户端: "我要用 [email protected] 认证"
    服务端: "这个算法我不认,拒绝。"
    客户端: "但是我里面用的是 SHA-256 啊!"
    服务端: "我不管里面是什么,外面名字不对就不行。"
    

    正确的算法名应该是 [email protected][email protected]


    修了一层,又来一层

    确认外层算法名是问题后,vendor 了一份 russh 0.59 做最小补丁——只改证书发包路径的算法名编码。

    改完跑真实 OpenSSH 测试,probe 阶段通过了。

    但最终签名请求又挂了,OpenSSH 日志:

    parse signature packet: unexpected bytes remain after decoding
    

    不是算法名的问题了,是签名包格式有问题。

    russh 的自定义 signer 协议要求返回值不是「裸签名」,而是「原始 to_sign buffer + 追加一个 length-prefixed signature blob 」。本地证书 signer 只返回了编码后的签名本体,少了前面的原始数据,OpenSSH 直接按格式错误处理。

    对照 russh agent signer 的实现确认了这个协议约定后,修正了返回格式。


    验证

    我没有让 AI 只写 mock test 就收工,因为我怕还是有问题,于是让 GPT 写了一组真实本地 OpenSSH 集成测试

    • 本地启动真实 sshd + ssh-agent
    • ssh-keygen 生成真实 RSA key 和 user certificate
    • 分别对服务端施加 rsa-sha2-256-onlyrsa-sha2-512-only 限制
    路径 rsa-sha2-256-only rsa-sha2-512-only
    Agent
    Certificate

    为什么坚持用真实 sshd ?因为这次的两层 bug——算法名编码和签名包格式——在 mock 环境下根本不会触发,只有在真实二进制流交换时才会暴露。


    叠甲:为什么 vendor 而不是提 PR

    1. russh 的维护节奏和项目不完全同步,等上游合并发版时间不可控
    2. 补丁只改了一个函数的算法名编码,diff 不到 30 行
    3. 问题只有在严格限制 PubkeyAcceptedAlgorithms 的真实 OpenSSH 上才能复现
    4. [patch.crates-io] 引用本地 vendor ,上游修了随时切回

    这个 bug 影响谁

    如果你的服务端是这几年新部署的,或者手动加了:

    PubkeyAcceptedAlgorithms rsa-sha2-256,rsa-sha2-512
    

    那么任何基于 russh 0.59 做 SSH Certificate 认证的项目理论上都会踩到。不只是我们的问题,russh 本身在证书路径上的协议编码就有缺口

    对了,这是我们的项目,有兴趣的话也可以看看

    项目地址: https://github.com/AnalyseDeCircuit/oxideterm

    官网: https://oxideterm.app


    后续想法:打算把技术周记做成系列,手头积攒了不少坑。大家有兴趣的话我再发点?

    目前尚无回复
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   Solana   ·   972 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 22:23 · PVG 06:23 · LAX 15:23 · JFK 18:23
    ♥ Do have faith in what you're doing.