降级租约 (Downgrade Leases)
背景
对于团队,我们通常面临在两个不同的签名链中对两个事件进行排序的问题, 以确保,例如,一个密钥在被撤销之前(而不是之后)被用于签署团队更新, 或者一个团队成员在他被降级之前(而不是之后)行使了他的管理员特权。 我们最终有了一个简单且通用的解决方案,但有一个重要的边缘情况需要考虑。 本文档详细介绍了 (1) 简单且通用的解决方案;(2) 令人烦恼的边缘情况; 以及 (3) 修复这个(不幸的)边缘情况的机制。开始吧!
在团队中建立可证明的“发生于...之前”关系
我们在两种情况下需要证明上述的“发生于...之前”关系。 首先,当团队成员使用设备密钥更改团队时,他必须在密钥配置之后、密钥撤销之前进行。 同样,当团队成员作为团队管理员行事时,他必须在他被指定为管理员之后、被移除管理员身份之前进行。 这些关系在线性化的签名链中很简单,但当需要在跨链证明“发生于...之前”时就变得复杂了, 就像刚才列举的两个例子中发生的那样。
一个通用且简单的解决方案
一般问题是建立 a < b < c 的关系,其中 a 和 c 在一条签名链上, 而 b 在另一条上。例如,a 是密钥被配置的时间,b 是它被使用的时间, c 是它被撤销的时间(对于未撤销的密钥,c = ∞)。在这两种情况下,Keybase 客户端执行以下算法:
- 首先建立 a < b:
- 查看 b 中的签名以确定在生成签名 b 时最后看到的 Merkle 根哈希。这被捕获在签名的
body.merkle_root.hash_meta字段中。 - 向 Keybase 服务器请求从步骤 1.1 中的 Merkle 根到 a 所在签名链尾部的 Merkle 路径。
- 沿着 prev 指针从 a 的尾部回溯到 a。
- 查看 b 中的签名以确定在生成签名 b 时最后看到的 Merkle 根哈希。这被捕获在签名的
- 接下来建立 b < c
- 查看 c 中的签名以获取
body.merkle_root.hash_meta - 向 Keybase 服务器请求从步骤 2.1 中的 Merkle 根到 b 所在签名链尾部的 Merkle 路径
- 沿着 prev 指针从 b 的尾部回溯到 b
- 查看 c 中的签名以获取
步骤 (1) 和 (2) 中使用的技术基本相同,但有一个重要的区别。
让我们先看步骤 (1),建立 a < b。为了让 b 的签名者使用在 a 中配置的密钥,他必须已经消费了
Keybase Merkle 树到 a 的配置之时或之后的一个点,因此,
作为 body.merkle_root.hash_meta 嵌入的 Merkle 根必须包含一个带有 a 的配置的签名链。
我们当然应该在服务器上强制执行这个不变量,以防止有缺陷的客户端意外包含旧的 Merkle 根。
但如果客户端工作正常,它们真的不需要改变。
一个令人烦恼的边缘情况
当涉及到保证 b < c 时,我们就不那么幸运了。可能存在竞争,并且服务器可能接受这种交错:
- 设备 B 下载最新的 Merkle 根 t1 并签署 b
- 设备 C 在时间 t2 生成声明 c 撤销设备 B
- 设备 B 在时间 t3 提交其更新 b,带有时间 t1 的
body.merkle_root.hash_meta - 设备 C 在时间 t4 提交其更新 c,带有时间 t2 的
body.merkle_root.hash_meta
服务器将允许这一系列事件发生,因为设备 B 在时间 t3 是存活的,
就在它于时间 t4 被撤销之前。问题在于
我们不能使用上面的技术让客户端证明 b < c,
因为 hash_meta 指针交叉了!换句话说,如果客户端试图证明 b < c,它将跟随 hash_meta 指针 t2,
但不可能找到从 t2 到 b 的签名链的 merkle_path,
因为 b 发生在 t2 之后。我们卡住了!
这里的关键概念差异在于 a 导致了 b,因此 a 必须 在 b 之前足够早发生,以便 b 的签名者观察到 a。但是 b 并没有导致 c,因为撤销设备可能在任何时候发生。 所以我们没有得到良好的排序保证。
解决方案
这是称为“降级租约”的解决方案。有两类重要的降级:(1) 当用户撤销设备时;(2) 当用户从群组中被移除或从管理员降级为非管理员时。在这一两种情况下,我们必须检查 b < c,但容易受到刚才提到的降级竞争的影响。这是一个解决方案:
- 设备 C 向服务器请求涵盖某些降级活动的“租约”,例如用户 u 用设备 C 取消配置设备 B。
- 服务器回复一个在 Merkle 根时间 t1 的租约。
- 如果存在针对设备 B 撤销的未完成租约,所有使用设备 B 的操作都无效。所以我们要更改所有签名处理程序,不仅要检查 B 是否仍然活跃,还要检查 B 是否没有被安排即将撤销。
- 当设备 C 上传 B 的撤销时,服务器检查撤销是否被正确租赁,并且签名中的
body.merkle_root.hash_meta发生在租约中指定的 t1 或之后。如果是,则撤销成功。 - 客户端在持有租约时可能会死亡,因此这些租约在大约一分钟后过期。每当有人从团队中失去管理员权限时,也会采用相同的解决方案,类比完全成立。