DRM 能力检查:密钥系统、稳健性、输出
面向 Widevine 与 EME 的 DRM 能力概览:按顺序排列的四项检查点、每项应记录的值,以及浏览器证据在哪里结束。
BotBrowser Team
加密媒体会因为四种不同的原因失败,而只问一句 DRM 能不能用的团队无法把它们区分开。本文按顺序排列 DRM 能力评审的四项检查点,并把每一项指向负责详解它的集群文章:模块协商、协商后的稳健性、受保护输出,以及许可决定。
当构建版本与授权支持时,BotBrowser 能够按声明的配置文件在已授权上下文中提供一致的按上下文媒体策略,并让页面看到的密钥系统与稳健性取值跟随该配置文件。它不能凭空生成内容解密模块,不能推翻提供方对账号的决定,也不保证某部具体作品在任何主机上都能播放。
摘要
- 每个检查点只问一个问题。在访问步骤被拒绝与在许可步骤被拒绝是两种不同的失败,责任方也不同;把其中任何一种报告成“DRM 不能用”,都会把工单送给错误的团队。
- 记录被接受的稳健性字符串(
SW_SECURE_DECODE、HW_SECURE_ALL),而不是把它转换成是或否的答案。 - 把 headless 与 headed 当作呈现模式。虚拟显示器不是更低的安全等级,物理显示器也不是硬件安全路径的证明。
- 把提供方的状态码放在单独的字段里。它是关于账号与作品的策略决定,而不是浏览器能力。
目录
一个问题背后的四项检查点
四项检查点是循序渐进的,每一项回答的都是同一个会话的不同问题。把它们混在一起,是 DRM 排查最常见的停滞原因,因为查找原因从错误的层开始,永远到不了真正失败的那一层。
第一项检查点是浏览器究竟会不会协商密钥系统。第二项是它接受哪个稳健性等级。第三项是输出路径是否被允许承载受保护的帧。第四项是许可服务器是否会为这个账号、地区和作品发放密钥。
顺序对诊断很重要,而不只是为了整齐。第一项检查点被拒绝就会中断整条链路,因此下游的项目从未被测试。第一项通过而第四项被拒绝,则证明浏览器一侧协商成功,决定来自提供方。这两个结果指向不同的责任方,而只报告“DRM 不能用”的支持工单无法把它们区分开。
| 检查点 | 浏览器调用或事件 | 需要记录的值 |
|---|---|---|
| 1 访问 | requestMediaKeySystemAccess() | supported 或 NotSupportedError |
| 2 稳健性 | 请求之后的 getConfiguration() | 被接受的稳健性字符串 |
| 3 输出 | 会话上的 keystatuseschange | usable、output-restricted、expired |
| 4 许可 | 许可消息的 HTTP 响应 | 状态码与提供方的错误类别 |
每一行都是一个独立的实验,有自己的失败类别。把各行分开记录的评审者可以指出第一条出问题的行,并交给负责它的团队,而不必重新打开整套配置。
每一行也有一个值得写在取值旁边的限度。检查点一通过,证明浏览器会协商密钥系统;它并不证明密钥可以被解密,也不证明作品能够播放。检查点二通过,证明模块接受了一个等级;它并不证明显示路径能够承载受保护的帧。检查点三的 usable 状态证明密钥已发放给会话;它并不证明提供方授权了该账号。检查点四的 HTTP 200 证明一个许可响应已经到达;它并不证明下一个请求、另一部作品或另一个地区会成功。把这些限度写进记录,才不会让连续四项通过被读成播放保证。
先说明模块边界
模块是其他一切所依赖的输入,而在自动化启动中它经常缺失,且没有任何错误提到它。没有模块的浏览器会拒绝密钥系统的所有配置,于是探测报告一个看起来像配置问题的能力失败,实际上是打包问题。
关于模块的两个事实值得在评审记录中明确写出。第一,二进制比参数更早起作用:开源 Chromium 构建通常不附带 Widevine 模块,而 Google Chrome 构建会附带。启动内置 Chromium 构建的测试套件很可能报告不支持,无论它传入什么参数,修复方法是启动一个至少能承载模块的构建。
第二,自动化框架自身的默认值可能禁用让模块可用的组件。Playwright 的默认启动包含 --disable-component-update,对于不希望有后台网络流量的运行器来说这是合理选择,但它也意味着这种环境中新建的配置文件在本集群发表的一次测量中报告不支持 Widevine。在同一台机器上移除这一个参数后,同样的探测在 headless 与 headed 模式下都报告支持 SW_SECURE_CRYPTO 与 SW_SECURE_DECODE。该测量运行的是已安装的 Google Chrome 构建,而不是内置 Chromium,因此它对完全不附带模块的构建不说明任何问题。
这次测量应当作为方法来读,而不是作为普遍规则。它只隔离一个参数,其他都不改变,并比较前后同一个探测。Chrome 的组件系统属于 Chrome 自己,因此不同的版本或操作系统可能表现不同,可复现的部分是这次比较,而不是那个具体结果。
对本次评审来说,影响是一条放置规则,而不是关于 Chrome 的事实:缺失的模块永远到不了检查点一,因此必须记录在运行头的构建字段里,而不是作为一条失败的行。一张在内置 Chromium 构建上把第一行标成红色 NotSupportedError 的表,描述的是运行头,而从未看过运行头的读者会把它读成模块或提供方的问题。
const base = { executablePath: '/opt/google/chrome/chrome', headless: true };
await chromium.launch(base); // 框架默认值,包含 --disable-component-update
await chromium.launch({ ...base, ignoreDefaultArgs: ['--disable-component-update'] }); // 唯一的变化
在结果旁边记录配置文件是新建的还是持久的。临时配置文件每次启动都会重复首次运行的注册,而长期使用的桌面配置文件的结果,不能作为对每个任务都新建目录的 CI 容器的证据。同类与同类比较。
探测中页面内的那一半足够短,可以放在记录旁边。在每次上面的启动之后通过 page.evaluate 运行它;它只请求一个配置,打印哪些密钥系统被解析,从不尝试播放:
const config = [
{
initDataTypes: ['cenc'],
videoCapabilities: [{ contentType: 'video/mp4; codecs="avc1.42E01E"', robustness: 'SW_SECURE_DECODE' }],
},
];
for (const keySystem of ['com.widevine.alpha', 'org.w3.clearkey']) {
try {
const access = await navigator.requestMediaKeySystemAccess(keySystem, config);
const c = access.getConfiguration();
console.log(keySystem, 'supported', c.videoCapabilities[0].robustness, c.persistentState);
} catch (error) {
console.log(keySystem, error.name);
}
}
循环里的 Clear Key 是对照,而不是平台标记。规范定义了它,因此每个符合规范的浏览器都会对它作答,它在这里的作用是把页面或网络问题与 DRM 问题区分开。如果 Clear Key 正常解析,而平台密钥系统拒绝所有配置,那么要查的是 CDM 层,而不是页面。
把稳健性读作区间,而不是结论
一旦密钥系统协商成功,浏览器与模块就会商定一个稳健性等级。这些字符串是提供方用来决定提供什么的词汇:SW_SECURE_CRYPTO 与 SW_SECURE_DECODE 把密码学运算或解码留在软件中,而 HW_SECURE_ALL 要求可信的硬件路径。Widevine 把纯软件的情形称为等级 3,更高的等级依赖主机可能并不具备的硬件。
被拒绝的硬件等级不是配置的缺陷。许多桌面与虚拟环境没有硬件安全路径,而按软件等级提供普通内容的提供方在那里可以工作。因此有用的记录包含实际被接受的字符串,以及当时请求的配置,因为正是这一对信息让后来的评审者看出答案发生了变化。
两个习惯能让这一行可信。每次运行都重复完全相同的配置,这样输出中的差异是真实的差异,而不是另一个问题。并且要读取返回的配置对象,而不是假定请求按原样获批:DRM 指纹 列出了该对象携带的字段以及每个字段揭示了平台的什么,所以本文不再重复。
一个成功的 H.264 配置是矩阵的一个单元格,而不是整个矩阵,而 DRM 指纹 讲解了如何读取这个矩阵。这里要紧的更窄:记录你请求的是哪个单元格、回答的是哪个单元格,因为在不同等级上提供更低分辨率的提供方正在做出一项策略决定,它在这里只表现为一个被接受的等级。
把输出保护与显示模式分开
播放可能已经开始,随后在显示路径不满足受保护输出要求时停止。会话通过 keystatuseschange 报告这一状态,通常是 output-restricted,应用应当显示回退内容,而不是一个不透明的失败。这个检查点关乎显示路径,与密钥系统是否协商成功无关。
常见的错误是把这一行并入 headless 与 headed 的对比。headless 是呈现模式,不是安全等级。虚拟显示器可能出于与模式切换无关的原因满足或不满足输出要求,而在物理显示器上的 headed 运行并不能证明虚拟显示器会失败。用同一个配置文件、同一个编解码器和同样的请求稳健性来做比较,让显示模式成为唯一的自变量。
本文为第三行的变化补充阅读顺序:先看运行头里的显示模式字段,再看编解码器字段,最后看请求的稳健性。与显示模式变化同时发生的第三行变动不需要进一步排查;显示模式相同却发生的变动则指向输出夹具。
保持诚实的清晰做法是记录输出状态与应用呈现的回退内容,而不记录任何关于显示器型号或 GPU 身份的信息。浏览器可以告诉你发生了输出限制。它无法告诉你有人正看着某块特定屏幕,这种推断会把能力记录变成测量并不支持的断言。
值得记录的状态名来自媒体密钥状态映射,而不是播放器自己的标签。会话在密钥可用时报告 usable,在显示路径阻断受保护帧时报告 output-restricted,在许可窗口关闭时报告 expired,在清理之后报告 released。把应用面向用户的提示映射回记录中的这些名字之一,这样后来的评审者比较的是状态,而不是翻译四种语言的界面文案。
负向路径属于测试夹具。Widevine headless 边界 列出了夹具应当刻意检验的负向用例,并解释了为什么经过测试的回退内容比写在文档里的更有价值。在这个检查点上,要补充的具体用例是输出限制,因为它是唯一一个在协商成功之后才出现的失败。
确定谁拥有许可答案
最后一个检查点是唯一一个浏览器在转述消息、而不是在做决定的检查点。页面把不透明的许可请求转发给提供方,提供方作答,会话要么变得可用,要么不可用。这一阶段的 HTTP 错误可能意味着账号、地区、并发上限、已过期的授权或服务中断,而这些都不是浏览器兼容性的事实。
把浏览器证据与提供方证据分开,交接才能成立,而本文的贡献在于责任划分而不是字段机制:第一行属于二进制与启动的负责人,第四行属于提供方负责人。Widevine headless 边界 说明了哪个字段承载哪项结果;这份评审新增的内容是,只写着“DRM 不能用”的工单让接收团队无法区分这两位负责人,因此这一行必须随工单一起传递。
保留策略沿用同样的划分。保留构建标识、测试日期、配置文件标签、请求的配置、结果名称、状态和时间戳。移除 cookie、账号标识、许可消息、访问令牌和原始密钥材料。只保留类别的记录仍然可以比较版本,这正是保留它的理由。
仅凭 HTTP 状态往往太粗,无法转派工单。403 可能是授权已失效、地区不匹配、同一账号已达到并发上限,或签名 URL 过期,而提供方通常会在旁边返回自己的错误码或消息。把这个错误码作为类别记录,把完整消息留在共享产物之外,因为许可消息可能带有属于账号而非版本的标识。同一提供方的 429 与 503 需要相同的处理方式,却属于不同的责任方:前者说明账号触达了上限,后者说明服务不可用,而两者都不是靠更改配置文件能解决的。
当结果发生变化时,每次运行只改变一个声明的输入,其余保持固定。Widevine headless 边界 已经列出了它针对四行推荐的比较顺序;本文只补充一条规则:两次运行之间运行头必须保持相同,这样发生变化的行才能被读成发生变化的输入。
每个版本记录一张表
能跨过发布周期的产物,是一张每个检查点一行的表,放在运行头旁边,而运行头的形状在版本之间从不改变。运行头必须固定一次并复用,因为形状漂移的运行头会让两张表无法比较。八个字段覆盖会改变能力结果的情形:浏览器产品与完整构建字符串、自动化库与版本、操作系统与版本、显示模式、被测的编解码器字符串、配置文件是否为新建、密钥系统名称,以及请求的稳健性。Widevine headless 边界 为其矩阵列出了前六个;后两个是能力评审所增加的。
在浏览器或自动化升级后、配置文件架构变更后、运行器或显示器变更后重复这张表,并把上一张表放在新表旁边。第一条发生变化的行就指出要排查的层,所以运行头必须保持固定:因为运行头改变而移动的行,对浏览器什么也说明不了。
一个具体例子让比较变得明确。假设版本 A 在检查点一记录 supported,在二记录 SW_SECURE_DECODE,在三记录 usable,在四用测试资源记录 HTTP 200。版本 B 记录 supported、SW_SECURE_DECODE,然后三记录 output-restricted,而许可这一行没有执行,因为会话不可用。只有一行发生变化,而且变化在显示路径上,所以下一步是比较运行器的显示配置,而不是重新打开模块问题或联系提供方。
反向情形同样清楚。如果检查点一从 supported 变为 NotSupportedError,而运行头显示相同的构建和相同的配置文件生命周期,那么差异来自某个启动参数,或来自未执行的组件更新。把参数列表与普通启动比较,一步就能回答。如果运行头显示的是另一个构建,那么参数列表就不是首先要检查的东西。
集群为每个检查点提供详细说明。安装模块并把它与配置文件匹配的配置约定在 Widevine DRM 配置。从不播放任何内容就能读取 EME 表面哪部分的指纹视角在 DRM 指纹。用每行一个夹具区分四道边界的矩阵在 Widevine headless 边界。隔离出导致模块缺失的启动参数的测量在 自动化浏览器中的 Widevine CDM 可用性。
当评审从零开始时按这个顺序阅读:配置定义输入,指纹解释哪些内容可观察,边界矩阵把观察变成行,可用性测量解释最常见的零结果。当评审已经在进行中时,只读与失败检查点匹配的那一篇,因为其他篇会回答没人问过的问题。
实践结论
DRM 能力评审是四个按顺序提出的问题,每个都有自己的值和责任方。把协商结果、被接受的稳健性字符串、输出状态和提供方状态记录为独立字段,然后通过查找第一条变化的行来比较版本。通过的一行证明某一份约定成立;它并不证明某部作品能播放,说明这一限制本身就是结果的一部分。
公开来源
延伸阅读
先从 Widevine DRM 配置 了解输入,然后跟随与失败检查点匹配的集群链接。