且听疯吟 如此生活三十年
XMPP 安全相关

最近又从头看 XMPP 的 RFC1,有时候也考虑如果是自己来设计,会如何做。
之前的印象是 XMPP 的繁琐和低效,现在看来,作为通用的协议,XMPP 确实有做得不错的地方,从头看下来还是有不少收获的。

现在项目告一段落,回头整理下 XMPP 服务端安全方面的一些简单想法,大概想到哪写到哪吧,安全方面懂得不多,欢迎指正。
考虑到安全,我们的初始目标大概包括这些:

  • 与服务端通信安全(不被窃听/篡改)
  • 鉴别用户身份
  • 保护用户数据

TLS

TLS2 用于在两个通信应用程序之间提供保密性和数据完整性,是必须开启的。

SASL

SASL3 是一种验证用户身份的框架。XMPP 本身没有办法分辨用户身份,必须借助于 SASL 协议。
SASL 协议确定了客户端和服务端沟通的应答机制及传输的编码方法,剩下的就需要自己实现了。
要识别用户身份,你需要在 SASL 的框架下定义和服务端交换的具体身份信息(比如用户名、密码),以及实现身份信息的存储和验证方式,而不需要考虑其他细节。
具体到 XMPP 下 SASL 的验证流程(如果建立了 TLS 连接,此时是在 TLS 连接上的):
一般不必支持所有的 SASL mechanisms ,选择安全性更可靠的,比如 SCRAM-SHA-1 (尽量不要使用 plain):

详细登录流程可以参考 ejabberd: Login

SCRAM-SHA-1

SCRAM4(Salted Challenge Response Authentication Mechanism) 是近年才开始使用的更安全的一种加密验证机制,可以很好的在 Server 和 Client 之间做双向的验证,已经有很多的服务开始使用这种方式验证了,比如 MongoDB。XMPP 也在协议中提供了这种方式的说明。
不讨论详细的加密算法细节,客户端验证登录时,大概流程如下:

  1. client 发送想要登录的 username 到 server (即 auth)
  2. server 为该 username 生成/查找出 salt(s),和 iteration count(i)、server nonce (r) 一并发回给 client (challenge,base64 编码)
  3. client 使用给定的 salt 和 iteration count 加密持有的 password,发回给 server (如果服务端对该 client 使用的 salt 和 iteration count 是固定的话,可以存储下生成的 client key,从而避免在 client 明文存储密码,会更安全)
  4. server 验证结果,如果成功则返回 success,并附上计算值
  5. client 校验 success 中返回的值,通过则证明 server 拥有 client 的验证

end_to_end

如果有非常严格的安全需求,可以考虑 OpenPGP ,XMPP 协议也提供了有限的支持
不过我个人觉得这种方法也不是那么完美:

  • 需要额外的交换 key 的渠道,而这些渠道不一定可信
  • 杜绝不了伪造身份,你没法确定 id 对应的绑定的就一定是 key 的拥有者
  • 信息冗长,加密的信息可能字节数会扩大到 10 倍甚至以上

文件服务

文件传输基本是现在 IM 客户端的基础功能了。
一般我们不会选择通过文本方式直接在消息中发送文件/图片,而是选择先上传到 HTTP 文件服务器,然后发送链接的形式。但是 HTTP 是无状态的,我们也不能每次在用户对资源操作的时候要求用户输入用户名密码。

首先考虑上传:

  • 上传通道泄露可能会带来滥用
  • AWS 可以使用 key 和 secret 校验上传请求
  • 将 secret key 存放在客户端可能被逆向
  • XMPP 服务端可以看作可信的已鉴权服务

所以可以考虑这样操作:

  • 已鉴权的客户端从 XMPP 服务端申请上传
  • 文件服务器生成一个上传用的 token,生成方式可能是
    token = hash(user + id + nonce + expire_stamp + secret)
  • XMPP 服务端从文件服务器获取生成的 token 并返回给客户端(假设 XMPP 服务端与文件服务器之间的通信是可信的,比如处于同一个内网。当然也可以 XMPP 服务端使用同样的 secret 和算法来计算 token 而不请求文件服务器)
  • 客户端向文件服务器提交上传请求,并带上 token, user, id, expire_stamp, nonce
  • 文件服务器校验是否过期以及
    hash(user + id + nonce + expire_stamp + secret) == token
  • 校验通过,上传文件到 AWS S3 或其他存储服务,返回文件 id 给客户端

然后是下载:
出于安全考虑,我们通常不会允许固定的 url 访问,而是通过增加验证和过期的方式来做一些限制。
一方面防止文件抓取,同时降低私密文件意外公开的风险,甚至可以通过更换 secret key,让公开的链接失效,强制客户端重新向服务端请求鉴权。
和上传文件类似:

  • 客户端提交请求的文件 id 给 XMPP 服务端-
  • XMPP 服务端用类似的方式生成一个 token 返回给客户端
  • 客户端使用链接 + token 的形式访问文件资源
  • 文件服务器校验 token, 以及是否过期等等,通过则返回资源

当然现实情况可能用不到复杂的机制,也可以根据情况适当放松要求。
btw, 至少我抓包验证不止一个重量级的 App 使用的就是类似于 http://xxxxx/uuid 的永久文件地址= =。

业务逻辑

即使在完善的安全机制下,也不要完全信任用户的输入
不像网页上按按 F12 就可以拿到很多信息,可能移动设备给人一种难以 hack 的错觉,但是决定有没有人来搞的是值不值得,还有运气 :)