提升 Web 端 JavaScript 的可信度:WAICT 体系详解

提升 Web 端 JavaScript 的可信度:WAICT 体系详解

在当前互联网时代,网页是最强大的应用平台。只要在浏览器中拥有合适的 API,你理论上可以安全运行任何你想运行的东西。不过------除了"加密学"这块。事实上,自 2011 年以来,"网页中的 JavaScript 加密"一说就被认为是"不靠谱"的。 其核心问题在于:代码的分发。如果我们在客户端浏览器中生成密钥,从而让用户能够发送/接收端对端加密消息,那么如果应用被篡改,恶意者究竟有什么阻止他们修改 JavaScript 代码并将消息外泄呢?

相比之下,智能手机应用商店在这方面做得比较完善:它们为应用生态提供了完整性保障(确保所交付的应用未被篡改)、一致性保障(确保所有用户获取的是同一个版本)以及透明性保障(可见的版本记录)。

如果我们能让网页也具备类似属性------也就是无需集中式应用商店,也能为网页应用提供"完整性"、"一致性"、"透明性"保障,那么对于网页中运行的加密、钱包、投票系统、机密 LLM 等都会大有裨益。

本文将介绍一个名为 Web Application Integrity, Consistency, and Transparency(WAICT)的方案(Cloudflare 参与了其起草),这是一个由浏览器厂商、云服务商、加密通信/应用开发者联合推动、在 W3C 拥有支持背景的项目。我们将先从问题定义谈起,再逐步构建解决方案。

一、定义"网页应用"

在谈安全保障之前,首先必须明确"网页应用(web application)"是什么。智能手机上的应用可以看作一个压缩包(zip);网页则由相互关联的资源组成------HTML、JavaScript、WASM、CSS 等,这些资源既可能来自本域,也可能来自外域;而任一资源变化,都可能大幅改变应用行为。

因此,一个连贯的"应用"定义就要求:应用必须对其所加载的资源做出承诺(commit)。也就是说,需要有机制让浏览器知道"这是这个应用应该加载的资源集合"。下面我们先从"完整性(Integrity)"谈起。

二、完整性:从 SRI 到 "完整性清单"

2.1 子资源完整性(Subresource Integrity, SRI)

网页的一个重要机制是 SRI:浏览器允许页面在 <script><link> 等标签中指定外部资源的哈希。示例如下:

HTML 复制代码
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.13.7/underscore-min.js"
        integrity="sha512-dvWGkLATSdw5qWb2qozZBRKJ80Omy2YN/aF3wTUVC5+D1eqbA+TjWpPpoj8vorK5xGLMa2ZqIeWCpDZP/+pQGQ==">
</script>

浏览器会下载 underscore.js,然后校验其 SHA-512 哈希是否与 integrity 属性中的值匹配;匹配则加载,否则抛错、不执行。

如果页面中所有外部脚本、样式等资源都带有 SRI 属性,那么整个页面就可被其 HTML 定义。这距离我们想要的状态已经很接近了。但网页应用通常包含多个页面,并且页面之间还可能相互链接。换句话说:页面无法强制其链接的"下一个页面"的哈希

2.2 完整性清单(Integrity Manifest)

为了让整个站点(域)下的每个资源都受到完整性保障,WAICT 提出了"完整性清单"的机制:站点向客户端提供一个 JSON 配置文件(manifest),其内容包括:

  • 一个"哈希字典(hashes)",将资源的哈希值映射到其路径。路径为空字符串表示"任意路径下可能的资源"(例如错误页)。示例如下:
JSON 复制代码
"hashes": {
  "81db308d0df59b74d4a9bd25c546f25ec0fdb15a8d6d530c07a89344ae8eeb02": "/assets/js/main.js",
  "fbd1d07879e672fd4557a2fa1bb2e435d88eac072f8903020a18672d5eddfb7c": "/index.html",
  "5e737a67c38189a01f73040b06b4a0393b7ea71c86cf73744914bbb0cf0062eb": "/vendored/main.css",
  "684ad58287ff2d085927cb1544c7d685ace897b6b25d33e46d2ec46a355b1f0e": "",
  "f802517f1b2406e308599ca6f4c02d2ae28bb53ff2a5dbcddb538391cb6ad56a": ""
}
  • 一个"完整性策略(integrity-policy)",指定哪些类型的数据被强制检查、如何检查。例如:
JSON 复制代码
"integrity-policy": "blocked-destinations=(script), checked-destinations=(wasm)"

将二者合起来后,完整的 manifest 结构类似:

JSON 复制代码
"manifest": {
  "version": 1,
  "integrity-policy": ...,
  "hashes": ...
}

有了 SRI + 完整性清单,那么整个站点及其浏览器端所加载的资源集合就由这个清单的哈希唯一决定。也就是说,整个网站的状态可被一个哈希值所承诺。

三、一致性与透明性:公开、可监控的日志机制

3.1 透明性的意义

"透明性(Transparency)"指的是:应用的代码/资源被记录在一个公开可访问、只增不删的日志中。这样做有两方面好处:

  1. 如果用户被服务到恶意的代码,且其察觉到了,他们可以向外部证明自己运行的是什么。
  2. 即便用户没有察觉,外部审计者也可能在历史日志中发现恶意代码。

注意:透明性不能防止恶意代码的分发,但至少使其可审计。现在,由于我们已把整个站点状态浓缩为一个哈希,我们就可以让这个哈希进入一个公开日志。以下是我们设计时应满足的重要要求:

  • 不破坏已有站点 --- 应可选择性启用,不影响现有网站功能。
  • 不增加额外往返(round-trip)网络请求。
  • 尊重用户隐私:不得要求用户向新第三方识别/认证。
  • 用户无需保存站点特定数据(无状态客户端)。
  • 无中心化:不能有单点失败、单点信任。
  • 启用门槛低:站点运营方可以轻松加入日志。
  • 停用也容易:站点可以退出日志机制。
  • 停用透明化也要可被察觉:攻击者不能悄悄退出机制。
  • 监控功能:站点运营方应能监控其透明化状态。

3.2 哈希链(Hash Chain)

日志通常实现为追加(append-only)结构,支持"包含证明(inclusion proof)"与"一致性证明(consistency proof)"。最简单的追加结构即哈希链:每个新元素的哈希被串联进链中,最终链哈希代表整条链。

通过哈希链即可构建包含证明和一致性证明。

3.3 为网站构建透明机制

每站日志(Per-Site Log)

首先,为每个参与透明化的站点单独建立一个日志(哈希链)。该日志中的条目即该站点在某时刻的完整性清单(manifest)哈希。

但仅有日志还不够,因为日志运营方若为恶意方,仍可随意"新增/删除"条目并重新计算哈希链。为防止这种情况,我们引入"见证者(witness)"角色:见证者验证日志一致性证明,并对新的链哈希进行签名。

客户端(浏览器)在用户访问站点时,会收到:

  1. 该站点当前的 manifest ;
  2. 该 manifest 在站点日志中的包含证明;
  3. 见证者对日志链哈希的签名。 浏览器验证签名、验证包含证明、再执行完整性检查。此时用户可较为确知:该 manifest 已被记录在日志中,且日志没有被篡改/删除。
透明服务(Transparency Service)

为了维护所有参与透明化的站点记录,我们使用一个前缀树(trie)结构,将「域名 → 站点日志链哈希 + 链大小 + 资源托管地址」做映射。

站点加入/更新/退出透明体系时,都会在该前缀树中更新其条目。见证者需验证该前缀树更新证明,并对根哈希进行签名。

当用户访问站点时,浏览器除了验证站点日志、还要验证:站点日志是否在前缀树中包含、前缀树根签名是否可信。

此外,为满足"无额外往返请求"这一要求,浏览器可预装一个「透明预加载列表(transparency preload list)」,其中列出已参与透明体系的站点域名。若站点出现在此列表中,则必须提供包含证明或非包含证明(证明其已退出)。

监控、可退出、无单点
  • 监控:前缀树叶节点新增了 "created" 时间戳;站点运营方仅需监控"创建时间"和"日志条目数"即可判断是否被篡改。
  • 退出透明体系:站点提出退出时,叶节点不是直接删除,而是置为"墓碑(tombstone)"形式,保留创建时间。
  • 无单点:体系设计支持多个透明服务/见证者,"非中心化"以减少信任或失败依赖。

四、一致性挑战:树不一致与时间不一致

4.1 树不一致(Tree Inconsistency)

如果多个透明服务的前缀树对某个站点记录不一致(即链哈希不同),就构成"树不一致"。一种极端解决办法是让客户端要求多个服务的包含证明,但这样增大负担。

方案是限制透明服务的数量(类似于 Google Chrome 中采用的证书透明度日志数量约为8条)。

4.2 时间不一致(Temporal Inconsistency)

时间不一致指:用户可能因为地域、cookie 等因素,访问到较新的或较旧版本的站点。理论上,如果签名有效期过长(如十年),站点可能一直提供非常旧的版本而用户不察。

虽然最强一致性(所有用户同时看到完全相同版本)难以实现,但我们可以降低版本分叉的规模。比如令见证者签名根哈希的有效期较短(例如一周),以限制可服务的版本数量。缺点是:站点即便未更新,也需周期性向透明服务请求新的签名。

五、超越「完整性/一致性/透明性」:其他增强特性

5.1 代码签名(Code Signing)

WAICT 本身并不解决"代码来自何处"的问题(即来源可追溯性)。例如,Alice 自己托管一个开源软件版本,Bob 如何确定其与官方仓库一致?

为此,与 WEBCAT(由 Freedom of the Press Foundation)协议整合:允许站点在 manifest 中加入扩展字段 dev-ids,列出已签名站点资产的开发者身份(例如通过 Sigstore)。浏览器插件可读取该字段,从而建立信任。

5.2 冷却期(Cooldown)机制

攻击者若想悄然退出透明体系或更换签名开发者身份,可借助短期停顿。在预加载列表中注册的站点,客户端可要求:"若站点出现该名单中,则必须为透明启用状态,或其退出状态须已达冷却期(如 24 小时)之后才接受"。这样攻击者若突然切换状态,将被检测。

六、部署考量

各角色的信任与资源需求如下:

  • 透明服务(Transparency Service):存储所有透明化站点的元数据。若有 1 亿域名、每条256 B数据,则单棵前缀树约 26 GB(不含中间哈希)。运营方需具备高可用性且多个服务应无关联宕机。
  • 见证者(Witness):验证前缀树更新、签名根哈希。存储需求类似,需要高可用性并长期保管签名私钥。
  • 资源托管方(Asset Host):存储实际代码/资源。信任要求低,因为浏览器已通过哈希校验。但托管方不能篡改内容,仅可能拒绝服务。
  • 客户端(Client,即浏览器):执行所有检查(包含证明、签名、完整性等),是最需要信任的部分。

Cloudflare 表示愿意在该生态中提供透明服务及见证者角色,但会避免"自我见证"以防利益冲突。

支持替代生态系统

对于如 Tor Browser 这样的匿名环境,可能不能信任现有透明服务。WAICT 支持将前缀树托管到区块链上,以满足无中心化、无需传统域名验证的环境需求。

七、下一步与总结

目前 WAICT 仍处于标准化的早期阶段。下一步重点包括:

  • 扩展 SRI 支持更多数据类型(例如 WASM、图片)
  • 标准化完整性清单格式
  • 标准化附加特性(如代码签名、冷却机制)

我们鼓励开发者关注透明规范草案、参与讨论、提交 PR 或 Issue(规范开源于 GitHub)

八、小结:为什么这对你我很重要?

作为前端/全栈/安全工程师,我们往往假设"在浏览器中运行的 JavaScript 就是安全的"。但现实中很多安全问题源于 代码分发的不确定性:代码可能被替换、被篡改、版本可能混乱。WAICT 所提供的机制------完整性清单 + 透明日志 + 前缀树索引 +签名机制------力图为网页应用构建一个类似于「应用商店签名校验」的信任层。

如果你在开发例如网页钱包、端对端加密应用、投票系统、在浏览器运行的 LLM 等敏感应用,理解并尽早采用这些机制,将对你提升安全性、合规性、用户信任度都有重大意义。

相关推荐
等风起8813 小时前
Element Plus实现TreeSelect树形选择在不同父节点下子节点有相同id的双向绑定联动
前端·javascript
摸着石头过河的石头3 小时前
跨域资源共享(CORS)完全指南:从基础概念到实际应用
前端·javascript
2301_801252223 小时前
Vue中的指令
前端·javascript·vue.js
心.c4 小时前
深拷贝浅拷贝
开发语言·前端·javascript·ecmascript
咖啡の猫5 小时前
Vue全局事件总线
前端·javascript·vue.js
T___T5 小时前
JavaScript 变量声明详解:var、let、const 的核心差异
javascript·面试
豆苗学前端5 小时前
10分钟带你入门websocket,并实现一个在线多人聊天室
前端·javascript·后端
luckyPian6 小时前
ES6+新特性:ES7(二)
开发语言·javascript·ecmascript
边洛洛6 小时前
解决[PM2][ERROR] Script not found: D:\projects\xxx\start
前端·javascript