为什么 RSA 证书在新版 OpenSSH 上认证失败? russh 库 0.59 协议编码的两层坑 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
pierreoui12
V2EX    程序员

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

  •  
  •   pierreoui12 8 小时 5 分钟前 157 次点击

    最近在做一个基于 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     892 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 17ms UTC 21:45 PVG 05:45 LAX 14:45 JFK 17:45
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86