摘要

  • TLS 或 SASL 协商成功后,XMPP 会替换当前 XML 流:不发送通常的 </stream> 结束标签,也不关闭底层 TCP,而是在同一连接上重新交换流头。
  • 新流获得新的流 ID 和符合当前阶段的能力列表;TCP 存活、加密完成、对端身份验证、SASL 成功、资源绑定与 stanza 交付始终是不同层次的证据。

一条没有断开的连接,容纳了三个不同的现在

客户端先向服务器打开 XML 流,服务器在能力列表中提出 STARTTLS。双方完成 TLS 握手以后,最省事的做法似乎是让原流继续,只把后面的字节纳入加密。XMPP 没有这样做。

发起方要在已经加密的 TCP 连接上重新发送初始流头,接收方再以新的流头与新的流 ID 回应。双方不会先用 </stream> 正常关闭旧流,因为 TLS 成功本身已经让旧流被替换。

完成 SASL 时,这个动作再发生一次。服务器发出 <success/>,发起方随即在原 TCP 上开一个新流;接收方产生另一个流 ID,并只在此时公布认证后可以继续协商的事项。

于是,物理上连续的 socket 里先后存在三个应用层语境:TLS 之前、TLS 之后而 SASL 之前、SASL 之后。把它们都叫作“一次连接”没有错,却不足以说明哪一项事实是在什么条件下形成的。

流不是 socket 的文学别名

RFC 6120 对层次的区分很严格。TCP 是双向传输;严格说,XMPP 的 XML 流是单向的,两个对等方通常各维持一个发送方向。应用层流有自己的头、编号、能力与关闭语义,寿命不必和 TCP 重合。

当某项协商要求 restart,旧流被视为“已替换”,而不是经历通常的 XML 收尾。TCP 则继续存在,并可能已经因为 TLS 或 SASL 安全层而处在新状态。发起方开新流,接收方必须生成新编号,不能复用旧 ID。

因此,“重新开流”不能翻成“重新连接”。TCP 重连会重新创建传输关联,面对新的路径、端口状态与拥塞过程。这里的目的恰恰是避免那项成本,只清除不再适用的上层上下文。

它也不是消息重放或流恢复。restart 规则没有说明旧 stanza 是否抵达、是否要补发,更没有证明远端应用完成了动作。它只说明:今后的 XML 要在新一代协商语境中解释。

能力列表是一张阶段性通行证

服务器发送的 stream features 不是软件产品说明书。它描述的是眼下这个对等方仍须或可以完成哪些协商。只要列表中还有强制项,协商就没有结束,普通 stanza 不能被当作已经获准。空列表,或只剩自愿项目的列表,才表示可以进入正常交换。

顺序决定内容。RFC 6120 把层次写成 TCP、TLS、SASL、XMPP。服务器愿意提供哪些 SASL 机制,可能取决于 TLS 是否已经建立。客户端的资源绑定则只会在 SASL 认证成功后出现。

每次 restart 之后,接收方必须重新发送能力列表。这不是重复劳动,而是把权限声明绑定到当前状态。TLS 前看到的项目不自动成为 TLS 后的承诺;未认证时不存在的 binding 也不会提前泄漏成可执行动作。

缺席同样不能无限外推。一项能力可能尚未到公布阶段,也可能已经完成而不再出现。RFC 7590 还指出,攻击者可以剥除 STARTTLS 或其中的 required。一次抓包能证明路径上传来了什么,不能单凭“没看见”就证明服务器永远不支持什么。

TLS 保护未来,也要求重新审理过去

TLS 成功后,RFC 6120 要求双方丢弃在 TLS 生效前、通过 TCP 以上层次不安全取得的信息。规范举出的例子包括对端的 from 地址、旧流 ID 与旧能力列表。

这一条解释了 restart 的核心。若攻击者在未加密阶段改写地址或能力,而客户端在加密后继续沿用,安全通道就会替一项从未受保护的陈述背书。重新交换流头,迫使双方在新的保护条件下重新提出和接收有关事实。

不过,加密成功仍不等于身份问题全部解决。TLS 参数、证书或其他验证凭据、目标名称、验证结果与本地政策必须分别记录。RFC 7590 加强了 XMPP 的 TLS 要求:客户端要认证服务器,服务器要认证客户端,服务器之间也强烈建议进行身份验证;它同时把“已加密但未认证”列为较弱且独立的情形。

所以,一个 post-TLS 新流证明协议走过了状态边界,却不自动证明对端是谁。那项结论仍要由验证证据支撑。

SASL 解决凭据问题,却没有一次性授予行动权

RFC 4422 把 SASL 定义为可替换认证机制的框架。它把认证身份、授权身份、交换结果,以及某些机制可能建立的数据安全层分开。使用 SASL 这个名字,并不保证所有机制都提供同样的完整性或机密性。

XMPP 用 XML 承载 SASL 交换,并在交换后强制 restart。<success/> 表明所选机制的认证过程成功;它没有说明客户端已经完成全部 XMPP 前置条件,也没有说明它可向任意对象发送内容。

对客户端而言,后面还有资源绑定。服务器只会在 post-SASL 流中公布 <bind/>。绑定把一个 resourcepart 与账户及当前流联系起来,使同一账户的多个连接资源可以区分。

绑定完成以前,客户端若向服务器或自己账户之外的实体发送 stanza,服务器不得处理,并须以 not-authorized 流错误结束该流。TCP 此时可以是通的,TLS 可以是好的,SASL 也可以已经成功;应用层依然没有准备完毕。

这正是分层状态的价值。凭据通过是一项事实,地址成形是另一项事实,stanza 获准又是下一项事实。把它们压成“登录成功”,会让故障排查与责任归属都失去边界。

资源绑定之后反而不得再次开流

RFC 6120 明文规定,资源绑定之后,双方不得 restart。这个否定命令说明协议并非把“成功”机械地等同于“开新流”。

TLS 与 SASL 会改变旧信息赖以成立的安全或身份背景,所以要清空并重建。资源绑定发生在已经认证的新流内部,它完成地址与接入阶段,却不会使同一阶段的流头和能力失去意义。

实现者因此不能只写一条“任何协商成功就 restart”的规则。TLS 成功不重启是错,SASL 成功不重启也是错;binding 成功还重启,同样可能是错。协议状态机必须知道每个转折为何存在。

记录系统也应保持这种克制。新流 ID 是一代流的关联值,不是用户身份。resourcepart 区分在线资源,不是永久授权令牌。服务器收下一条 stanza,不是远端收件或业务生效的回执。

2004 年已有动作,2011 年写清了模型

2004 年 10 月发布的 RFC 3920 已经要求 TLS 成功后开新流,也要求收到 SASL <success/> 后开新流,并写明 TCP、TLS、SASL、XMPP 的层次顺序。旧流在成功点被视为结束,无须先发送关闭标签。

2011 年的 RFC 6120 取代它,把这些动作整理成更明确的通用模型:旧流被替换,现有 TCP 被复用,新流必须有新 ID,能力也要按新状态重发。历史变化不是从“不重启”变成“重启”,而是把不同章节里的动作提炼成一致的状态语义。

2015 年的 RFC 7590 又针对变化后的威胁环境加强 TLS 实践。它提醒运营者:流程走对不代表密码套件、证书验证或降级防护就一定足够。协议编排与安全质量要分别审计。

XMPP 留下的设计教训,不是所有协议都应频繁重开,而是“连续”必须带层次。传输连接可以为了效率延续;一旦上层事实的信任条件改变,旧上下文就不该搭便车进入新阶段。

资料来源