独立式 Web 应用

Web 是一种真正独特的应用平台。基于该框架构建的应用可在任何操作系统上立即访问,无需进行任何代码更改或编译。无论用户何时访问您的应用,他们始终都能获得最新版本。 它们可安装,可以离线使用,功能强大,只需一个链接即可轻松分享。构建一个 Web 应用,让它在任何地方都能正常运行。

由于 Web 的目标是默认情况下安全可靠,因此其安全模型需要非常保守。添加的任何新功能都必须安全可靠,以免普通用户通过网址意外发现。我们将这种安全模型称为“drive-by web”。虽然这种方式非常适合许多应用,并且可以通过使用内容安全政策跨源隔离来提高安全性,但它并不适用于所有使用情形。许多非常重要且功能强大的 API(例如 Direct SocketsControlled Frame)是开发者需要的,但无法通过网页版 Google 云端硬盘实现足够的安全性。

对于这些应用,目前没有在 Web 上构建的选项。对于另一些人来说,Web 的安全模型可能不够保守;他们可能不认同服务器值得信赖这一假设,而是更喜欢离散版本化和签名的独立应用。需要一种新的高信任度安全模型。独立式 Web 应用 (IWA) 提供了一种基于现有 Web 平台构建的隔离式、捆绑式、版本化、签名式且可信的应用模型,以满足这些开发者的需求。

网络上的信任谱系

您可以将网络上的安全性和功能视为一个谱。

一幅插图,展示了网络上的信任谱。左侧是代表浏览式网络攻击的地球。中间是渐进式 Web 应用。右侧是一个鱼缸,里面有一条金鱼,代表独立式 Web 应用。一条黑色实线水平连接所有三个图标,一条红色虚线将渐进式 Web 应用与独立式 Web 应用分开

左侧的浏览时下载具有最低的信任安全模型,因为它需要最容易访问,因此对用户系统的访问权限最少。浏览器安装的 Web 应用的信任度略高,可以更深入地集成到用户系统中。用户通常可以在应用的免安装 Web 版和浏览器安装版之间自由切换,而不会遇到问题。

然后是高信任度的独立式 Web 应用。

它们在运行方式和使用体验上更像原生应用,并且可以访问深层系统集成和强大的功能。用户无法在它们与 drive-by-web 之间跳转。如果您需要此安全级别或这些功能,则无法回退。

在尝试确定您应采用哪种安全模型时,请尽可能选择信任度最低的安全模型,例如渐进式网页应用。这样一来,您的应用将覆盖最广泛的用户群,您需要自行管理的安全问题最少,并且对开发者和用户而言也最灵活。

从设计上保证安全

独立式 Web 应用为 Web 应用提供高度可信的安全模型。不过,为了实现这一点,我们需要重新考虑“通过网络驱动”对信任所做的一些假设。服务器和 DNS 等核心 Web 构建块不再能被明确信任。对于原生应用来说可能更相关的攻击媒介突然变得重要起来。因此,为了获得 IWA 提供的新高信任安全模型,Web 应用需要进行打包、隔离和锁定。

已打包

独立式 Web 应用的网页和资源无法像普通 Web 应用那样从实时服务器提供或通过网络提取。不过,如需访问新的高信任度安全模型,Web 应用需要将运行所需的所有资源打包到已签名的 WebBundle 中。已签名的 Web 软件包会获取运行网站所需的所有资源,并将它们打包到一个 .swbn 文件中,并使用完整性块将它们串联起来。这样一来,便可安全地完整下载 Web 应用,甚至在离线时共享或安装该应用。

不过,这会给验证网站代码的真实性带来问题:TLS 密钥需要连接到互联网才能正常运行。IWA 不使用 TLS 密钥进行签名,而是使用可安全离线保存的密钥进行签名。好消息是,如果您能将所有制作文件收集到一个文件夹中,则无需进行太多修改即可将其转换为 IWA。

生成签名密钥

签名密钥是 Ed25519 或 ECDSA P-256 密钥对,其中私钥用于对软件包进行签名,公钥用于验证软件包。您可以使用 OpenSSL 生成并加密 Ed25519 或 ECDSA P-256 密钥:

# Generate an unencrypted Ed25519 key
openssl genpkey -algorithm Ed25519 -out private_key.pem

# or generate an unencrypted ECDSA P-256 key
openssl ecparam -name prime256v1 -genkey -noout -out private_key.pem

# Encrypt the generated key. This will ask for a passphrase, make sure to use a strong one
openssl pkcs8 -in private_key.pem -topk8 -out encrypted_key.pem

# Delete the unencrypted key
rm private_key.pem

签名密钥还有其他用途。由于网域可能像服务器一样容易受到失控的威胁,因此无法用于标识已安装的 IWA。相反,IWA 由软件包的公钥(属于其签名的一部分并与私钥相关联)来标识。这会显著改变驱动式网络的工作方式,因此 IWA 不再使用 HTTPS,而是使用新的方案isolated-app://

捆绑应用

有了签名密钥后,就可以开始打包 Web 应用了。为此,您可以使用官方 NodeJS 软件包来打包并签署 IWA(Go 命令行工具也可供使用)。首先,使用 wbn 软件包,指向包含所有 IWA 生产文件的文件夹(此处为 dist),将它们打包成未签名的 bundle:

npx wbn --dir dist

这会将相应目录的未签名 Web 软件包生成到 out.wbn.。生成后,使用您之前创建的加密 Ed25519 或 ECDSA P-256 密钥,通过 wbn-sign 对其进行签名:

npx wbn-sign -i out.wbn -k encrypted_key.pem -o signed.swbn

这将从名为 signed.swbn 的未签名 Web 软件包生成一个已签名的 Web 软件包。签名后,该工具还会输出 Web 软件包 ID 及其独立式 Web 应用来源。独立式 Web 应用来源是指浏览器中识别 IWA 的方式。

Web Bundle ID: ggx2sheak3vpmm7vmjqnjwuzx3xwot3vdayrlgnvbkq2mp5lg4daaaic
Isolated Web App Origin: isolated-app://ggx2sheak3vpmm7vmjqnjwuzx3xwot3vdayrlgnvbkq2mp5lg4daaaic/

如果您使用的是 WebpackRollup 或支持其插件的工具(例如 Vite),则可以使用封装这些软件包的捆绑器插件(WebpackRollup),而不是直接调用它们。这样做会生成一个已签名的软件包作为 build 的输出。

测试应用

您可以通过以下两种方式之一测试 IWA:通过 Chrome 的内置 IWA 开发者代理运行开发服务器,或安装捆绑的 IWA。为此,您需要使用 Chrome 或 ChromeOS 120 或更高版本,启用 IWA 标志,并通过 Chrome 的 Web 应用内部组件安装应用:

  1. 启用 chrome://flags/#enable-isolated-web-app-dev-mode 标志
  2. 前往 Chrome 的 Web 应用内部页面 chrome://web-app-internals,测试您的 IWA

进入“Web 应用内部”页面后,您有两种选择:Install IWA with Dev Mode ProxyInstall IWA from Signed Web Bundle

如果您通过开发者模式代理进行安装,则可以将任何网址(包括从本地开发服务器运行的网站)作为 IWA 进行安装,而无需将其捆绑在一起,前提是这些网址满足其他 IWA 安装要求。安装完成后,系统会为相应网址添加 IWA,并设置正确的安全政策和限制,以及对仅限 IWA 的 API 的访问权限。系统会为其分配一个随机标识符。在此模式下,您还可以使用 Chrome 开发者工具来调试应用。如果您从已签名的 Web Bundle 进行安装,则需要上传已签名的捆绑式 IWA,并且该 IWA 将像最终用户下载的那样进行安装。

在“Web 应用内部”页面上,您还可以强制检查通过开发者模式代理或从签名 Web 软件包安装的任何应用的更新,以测试更新流程。

隔离

信任是独立式 Web 应用的关键。首先,我们来了解一下它们是如何运行的。用户对应用能做什么和应该能做什么有不同的心理模型,具体取决于应用是在浏览器中运行还是在独立窗口中运行,他们通常认为独立应用具有更多访问权限,功能也更强大。由于 IWA 可以访问高信任度 API,因此需要它们在独立窗口中运行,以符合此心理模型。这样可以直观地将它们与浏览器分开。但它不仅限于视觉分离。

独立式 Web 应用 (IWA) 采用的协议与浏览器内网站不同(isolated-apphttphttps)。这意味着,即使 IWA 和浏览器内网站由同一公司构建,由于同源政策,每个 IWA 也完全与浏览器内网站分离。 IWA 存储空间也是相互隔离的。这样一来,便可确保跨源内容不会在不同的 IWA 之间或在 IWA 与用户的正常浏览上下文之间泄露。

但如果 IWA 在安装后可以下载并运行任意代码,那么隔离和捆绑并签署网站的代码都无法建立信任。为了确保这一点,同时仍允许 IWA 连接到其他网站以获取内容,IWA 会强制执行一套严格的内容安全政策

Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval';
  connect-src 'self' https: wss: blob: data:;
  require-trusted-types-for 'script';
  frame-src 'self' https: blob: data:;
  img-src 'self' https: blob: data:;
  media-src 'self' https: blob: data:;
  font-src 'self' blob: data:;
  style-src 'self' 'unsafe-inline';
  object-src 'none';
  base-uri 'none';
  default-src 'self';

这些 CSP 不足以全面防范潜在的恶意第三方代码。IWA 也是跨源隔离的,通过设置标头来降低第三方资源影响它们的能力:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-origin
Content-Security-Policy: frame-ancestors 'self'

即使有这些限制,IWA 仍需防范另一种潜在攻击:序列中断攻击。当恶意第三方内容尝试以意外方式(例如直接前往内部设置页面)导航到某个页面,从而创建令人困惑且可能被利用的用户体验时,就会发生序列中断攻击。IWA 通过禁止从外部网站进行任意深层链接来防止这种情况,仅允许通过导航到明确定义的入口点(例如 start_url协议处理程序共享目标或通过启动处理程序)来打开应用。

锁定

封装和隔离可提供一组有关允许运行的内容及其来源的保证,但 Web 上权限的动态特性意味着,仅凭这些保证无法确保 Web 应用仅使用其所需的功能。由于不同的功能具有不同的安全性考虑因素,因此用户或管理员在安装或更新应用之前,会希望审核 IWA 可能使用的权限,就像他们对其他原生应用(如 Android 和 iOS)所做的那样。

为实现此目的,独立式 Web 应用 (IWA) 默认会屏蔽所有权限请求。 然后,开发者可以通过向 Web 应用清单添加 permissions_policy 字段来选择加入所需的权限。此字段包含 IWA 或任何子框架(例如受控框架或 iframe)可能请求的每项权限的权限政策指令权限政策许可名单的键/值对。在此处添加权限并不会自动授予该权限,而是使该权限在请求相应功能时可供请求。

假设您要构建一个车队跟踪 IWA。您可能需要让 IWA 能够请求用户的位置信息,并且让嵌入式地图也能请求位置信息。您可能还希望任何嵌入式网站都能进入全屏模式,以便为用户提供沉浸式视图。为此,您需要在 Web 应用清单中设置以下权限政策:

"permissions_policy": {
   "geolocation": [ "self", "https://map.example.com" ],
   "fullscreen": [ "*" ]
}

由于 WebBundle 也可以指定权限政策标头,因此只有在两者中都声明的权限才会被允许,并且只有在两个许可名单中的来源才会被允许。

已命名且已版本化

普通 Web 应用依靠其域名向用户标识自己,并且可以通过更改在该网域中提供的代码进行更新。但由于隔离 Web 应用存在安全限制,因此需要以不同的方式处理身份和更新。与渐进式 Web 应用类似,独立式 Web 应用也需要 Web 应用清单文件来向用户标识它们。

Web 应用清单

独立式 Web 应用 (IWA) 与 PWA 共享相同的 Web 应用清单关键清单属性,但略有不同。例如,display 的工作方式略有不同;browserminimal-ui 都会强制进入 minimal-ui 显示,而 fullscreenstandalone 都会强制进入 standalone 显示(其他 display_override 选项可正常工作)。 此外,还应包含另外两个字段:versionupdate_manifest_url

  • version:对于独立式 Web 应用,此属性为必需属性。一个由一个或多个整数组成的字符串,整数之间用英文句点 (.) 分隔。您的版本可以是简单的 123 等,也可以是复杂的 SemVer (1.2.3)。版本号应与正则表达式 ^(\d+.?)*\d$ 匹配。
  • update_manifest_url可选字段,但建议使用,指向可从中检索 Web 应用更新清单的 HTTPS 网址(或用于测试的 localhost)。

独立式 Web 应用的最小 Web 应用清单可能如下所示:

{
  "name": "IWA Kitchen Sink",
  "version": "0.1.0",
  "update_manifest_url": "https://example.com/updates.json",
  "start_url": "/",
  "icons": [
    {
      "src": "/images/icon.png",
      "type": "image/png",
      "sizes": "512x512",
      "purpose": "any"
    },
    {
      "src": "/images/icon-mask.png",
      "type": "image/png",
      "sizes": "512x512",
      "purpose": "maskable"
    }
  ]
}

Web 应用更新清单

Web 应用更新清单是一个 JSON 文件,用于描述给定 Web 应用的每个版本。JSON 对象包含一个必需字段 version,该字段是一个对象列表,其中包含 versionsrcchannels

  • version - 应用的版本号,与 Web 应用清单的 version 字段相同
  • src - 指向相应版本(即 .swbn 文件)的托管软件包的 HTTPS 网址(或用于测试的 localhost)。相对网址相对于 Web 应用更新清单文件。
  • channels - 一个字符串列表,用于标识相应版本所属的更新渠道。特殊的 default 渠道用于描述在未选择其他渠道时将使用的主要渠道。

您还可以添加 channels 字段,即包含频道 ID 的对象,其中每个 ID 都有一个可选的 name 属性,用于提供人类可读的名称(包括 default 频道)。不包含 name 属性或未包含在 channels 对象中的渠道会使用其 ID 作为名称。

一个最小的更新清单可能如下所示:

{
  "versions": [
    {
      "version": "5.2.17",
      "src": "https://cdn.example.com/app-package-5.2.17.swbn",
      "channels": ["next", "5-lts", "default"]
    },
    {
      "version": "5.3.0",
      "src": "v5.3.0/package.swbn",
      "channels": ["next", "default"]
    },
    {
      "version": "5.3.1",
      "src": "v5.3.1/package.swbn",
      "channels": ["next"]
    },
  ],
  "channels": {
    "default": {
      "name": "Stable"
    },
    "5-lts": {
      "name": "5.x Long-term Stable"
    }
  }
}

在此示例中,有三个渠道:将标记为 Stabledefault、将标记为 5.x Long-term Stable5-lts,以及将标记为 nextnext。如果用户使用的是渠道 5-lts,则会获得版本 5.2.17;如果用户使用的是渠道 default,则会获得版本 5.3.0;如果用户使用的是渠道 next,则会获得版本 5.3.1

Web 应用更新清单可以托管在任何服务器上。系统每 4-6 小时检查一次更新。

由管理员管理

在首次发布时,独立式 Web 应用只能由管理员通过管理面板安装在 Chrome 企业版管理的 Chromebook 上。

首先,在管理面板中,依次点击设备 > Chrome > 应用和扩展程序 > 用户和浏览器。您可以在此标签页中为组织中的用户添加来自 Chrome 应用商店、Google Play 和网络的应用和扩展程序。如需添加商品,请打开屏幕右下角的黄色浮动“添加”按钮 (+),然后选择要添加的商品类型。

打开后,您会看到一个方框内嵌方框的图标,标签为添加独立式 Web 应用。点击该图标会打开一个用于向组织部门添加 IWA 的模态框。为此,您需要两项信息:IWA 的 Web 软件包 ID(根据应用的公钥生成,在应用打包和签名后显示)以及 IWA 的 Web 应用更新清单的网址。安装完成后,您将拥有管理该应用的标准管理员面板选项:

  • 安装政策:您希望如何安装 IWA,是强制安装、强制安装并固定到 ChromeOS 搁架,还是阻止安装。
  • 登录时启动:您希望如何启动 IWA,是允许用户手动启动,还是强制在用户登录时启动 IWA 但允许用户关闭,或者强制在用户登录时启动并阻止用户关闭。

保存后,系统会在下次将政策更新应用到相应组织部门中的 Chromebook 时安装该应用。安装后,用户的设备会每 4-6 小时检查一次 Web 应用更新清单是否有更新。

除了强制安装 IWA 之外,您还可以像为其他 Web 应用那样,自动授予 IWA 某些权限。为此,请依次前往设备 > Chrome > Web 功能,然后点击添加来源按钮。在 Origin / site pattern field 中,粘贴 IWA 的 Web 软件包 ID(isolated-app:// 将自动作为其协议添加到其中)。在该页面中,您可以为不同的 API 设置访问权限级别(允许/禁止/未设置),包括窗口管理、本地字体管理和屏幕监控 API。对于可能需要管理员额外选择启用才能使用的 API(例如强制性屏幕监控 API),系统会弹出额外的对话框来确认您的选择。完成后,保存更改,您的用户即可开始使用 IWA!

使用扩展程序

虽然独立式 Web 应用无法直接使用扩展程序,但您可以将自己拥有的扩展程序连接到它们。为此,您需要修改扩展程序的清单文件。清单的 externally_connectable 部分用于定义您的扩展程序可以与哪些外部网页或其他 Chrome 扩展程序互动。在 externally_connectable 中的 matches 字段下添加 IWA 的来源(请务必添加 isolated-app:// 方案):

{
  "externally_connectable": {
    "matches": ["isolated-app://79990854-bc9f-4319-a6f3-47686e54ed29/*"]
  }
}

虽然这会允许您的扩展程序在隔离的 Web 应用中运行,但不会允许它将内容注入到其中;您只能在扩展程序和 IWA 之间传递消息