1. 引言
本节不是规范性的。
请注意,这只是用于撰写协作的非常早期草案
Web 建立在无状态协议之上。为了为某些 功能维护状态,Web 应用会在用户设备上本地存储数据。这种 存储可以持续很长时间,并用于安全敏感数据,例如已登录用户会话的凭据。
一般而言,在常见操作系统上,用户代理没有办法以一种能抵御与 用户代理本身拥有同等权限的软件的方式存储这类数据。同时, 由这些数据认证的操作可能会产生严重后果,例如从银行账户转账。此模型面临的一种 常见且可扩展的威胁是恶意软件外泄此类 凭据并在别处实施滥用,从而规避检测。
本文档定义了一种新的 API,Device Bound Sessions Credentials(DBSC),它 使服务器能够验证会话凭据并未从 设备导出。这些凭据是私钥,因此用户代理可以使用 TPM 或类似 API 等设施,来保护它们即使面对同等权限的恶意软件也不会被外泄。
目标是在提供用户已经习惯的 用例的同时,为用户提供安全且受保护的体验。同时,我们希望确保 尊重用户隐私,本协议不会泄漏新的隐私标识符。该 API 特别注意与现有 服务器端认证栈轻松集成,为此类保护提供渐进式路径,而 不需要重写 Web 软件栈的大部分内容。
2. 安全考量
DBSC 的目标是通过提供一种替代 长期 cookie 持有者令牌的方案来减少会话窃取,并允许会话认证 基于可更好防止外泄的私钥。 这会使互联网对用户更安全,因为用户身份被滥用的可能性更低, 恶意软件被迫在本地行动,因此更容易被检测和缓解。用户代理实现负责选择保护 此类私钥免于外泄的最佳方式,并考虑不同平台上存在的恶意软件威胁类型 以及现有保护机制。这 包括但不限于 TPM 或安全元件等安全硬件、 操作系统提供的安全密钥生成和管理 API,或能针对本地运行的恶意软件 提供合理保护的进程隔离机制。
只要会话有效并注册到未受攻陷的设备, 主机就可以凭借密码学确定性知道会话密钥受到保护, 不会被恶意外泄到其他设备。
2.1. 非目标
当攻击者驻留在用户设备上时,DBSC 不会阻止其临时访问 浏览器会话。私钥应尽现代操作系统所允许的方式安全存储, 防止会话私钥被外泄, 但签名能力很可能仍可供以该用户身份在该用户设备上运行的任何程序使用。如果攻击者在会话注册时替换或注入 用户代理,DBSC 也不会阻止攻击,因为攻击者可以将 会话绑定到未受 TPM 绑定的密钥,或绑定到攻击者 永久控制的 TPM。
DBSC 并非设计用于向主机提供任何关于 会话注册到的具体设备或该设备状态的保证。
3. 隐私考量
DBSC 协议的隐私目标是不引入额外的用户 跟踪表面:实现此 API(对浏览器而言)或启用它(对网站而言) 不应带来任何重大的用户隐私权衡。为确保这一点而采取的一些考量:
-
会话/密钥材料的生命周期:这不应提供额外的客户端 数据存储(即伪 cookie)。因此,我们要求浏览器在清除其他站点数据(如 cookie)时, MUST 清除会话和密钥。
-
实现此 API 不应有意义地增加 启发式设备指纹信号的熵。特别是,DBSC 不应泄漏任何稳定的设备标识符。
-
由于此 API MAY 允许用于性能目的的后台 "pings",这不得使用户在离开已连接 站点后仍被长期跟踪。
-
每个会话都会创建一个单独的新密钥,并且不应能够 检测出不同会话来自同一设备。
3.1. Cookie 考量
站点应不可能使用此 API 绕过同源 策略、第三方 cookie 策略等。由于当前和不断变化的 cookie 行为具有复杂性,且 DBSC 与 cookie 紧密集成,当前 方案是每个用户代理都应对 DBSC 使用与其对 cookie 使用的相同策略。如果基于用户设置、已应用策略或用户代理实现细节, DBSC cookie 凭据不会适用于某个网络请求, 那么任何额外的 DBSC 行为也同样不会适用。这确保实现 DBSC 不会带来新的隐私 行为。3.2. 计时侧信道泄漏
如果第三方 cookie 已启用,攻击者可以通过测量请求耗时来判断 用户是否已认证。 这是因为刷新可能相当慢,尤其是当它依赖于用于密钥保护的缓慢底层设施 (例如 TPM)时。这通过会话配置中的 allowed_refresh_initiators 键来缓解,
该键可用于严格限制哪些站点能够触发
DBSC 刷新。它不能被现有方案
(例如 X-Frame-Options)取代,因为现有方案只在
请求完成后才适用,而 DBSC 必须在
请求开始前选择是否刷新。
3.3. 联合会话
许多站点使用没有浏览器参与的联合登录机制, 经常依赖链接装饰(例如 OIDC)。为了实现 DBSC 易于采用的目标,我们希望这些站点能够保护自己 免受 cookie 窃取,而无需完全重写其认证流程。理想情况下, Relying Parties(RP)可以简单地独立于 Identity Provider(IdP)建立 DBSC 会话。不幸的是,如果 用户已经登录,大多数 IdP 不要求密码。如果不需要用户交互,恶意软件就可以 利用其对用户机器的临时访问来模拟登录流程,并 使用恶意软件创建且可外泄的新私钥建立 DBSC 会话。 这违反了 DBSC 的安全目标。
由于 DBSC 并不内在绑定到登录或身份,Identity Provider 不是最准确的术语。因此我们使用术语 Session Provider(SP),尽管 在目标用例中,我们预期 SP 和 IdP 是同一方。
为了保护 RP 的会话,我们需要以某种方式链接 RP 和 SP 会话。
最简单的方法是让 SP 和 RP 使用相同的
会话密钥对。由于我们假设 SP 的会话是在
恶意软件攻陷设备之前建立的,因此我们信任私钥会被
安全存储。但跨站点共享密钥具有复杂的隐私属性。为了
缓解共享高熵标识符的隐私风险,我们要求
RP 已经知道 SP 会话的公钥和会话标识符。RP 将在
`Secure-Session-Registration`
标头中包含 SP URL、会话标识符以及 base64 编码的
JWK thumbprint(参见 [RFC4648] 和 [RFC7638])。如果密钥正确,用户代理
将在 RP 上创建一个与 SP 上会话使用相同密钥的新会话。
恶意 RP 和 SP 还存在潜在风险,它们可能协作尝试在 RP 和 SP 本不应已经共享信息的情况下识别用户 (例如存在针对指纹识别或链接装饰的保护时)。 RP 可以简单地尝试猜测许多公钥,直到找到匹配项, 从而获得用户的唯一标识符。这会允许协作的 RP 和 SP 在站点之间共享用户身份,超出预期的跨站点身份链接机制。 为防止这一点,用户代理应对注册尝试加入 显著的退避或配额(这也建议用于避免对 TPM 造成拒绝服务)。 请注意,DBSC 的安全属性依赖于一个密码学假设: 很难让两个用户拥有相同的密钥对,因此查询用户在 SP 上是否拥有特定 DBSC 公钥所提供的信息远小于一比特熵。
为了进一步限制成功揭示用户身份的价值,我们还要求 SP
通过 .well-known 进行选择加入。浏览器应限制该列表中的
RP 来源数量。我们还需要防止 RP 使用
多个 SP 并以这种方式聚合身份。为此,我们要求 RP
也在 .well-known 中声明其 SP。二者结合将确保
大型站点群无法在高价值用户浏览 Web 时协作揭示其身份。
example.com 的所有者也运营 example.co.uk。登录始终
发生在 example.com,并通过链接
装饰传播到 example.co.uk。为了用 DBSC 会话保护这两个站点,
example.com 应继续使用其现有的
`Secure-Session-Registration`
标头:
Secure-Session-Registration: (ES256);path="/register";challenge="challenge"
在将 DBSC 会话扩展到 example.com.uk 时,该站点应向
`Secure-Session-Registration`
标头追加新的参数:
Secure-Session-Registration: (ES256);path="/register";challenge="challenge";provider_key="abc";provider_id="example.com id";provider_url="https://example.com"
假设 example.com 和 example.co.uk 具有适当的 .well-known
条目,这将导致 example.co.uk 使用
与 example.com 上会话相同的密钥注册新的 DBSC 会话。这允许 DBSC 保护
example.co.uk,而无需用户在登录时重新向 example.com 认证。
4. 已考虑的替代方案
4.1. WebAuthn 和静默中介
WebAuthn 为站点提供某些密钥管理能力,因此它可能可以 扩展以满足与 DBSC 相同的目标。我们不采用此 方法,因为 WebAuthn 对交互式用户登录的关注,使其 API 具有若干与 DBSC 这类功能目标相反的属性。例如, WebAuthn 凭据并不绑定到特定会话。它们 被设计为长期存在,并且不会随站点数据一同清除。这与会话 密钥所需的隐私属性并不匹配。类似地,WebAuthn 凭据 设计用于明确用户意图。扩展 WebAuthn 以涵盖 DBSC 提供的功能,将要求 WebAuthn 建立一种可静默使用的 不同类型密钥。这会为 WebAuthn 和 DBSC 都引入额外复杂性。因此, 我们的立场是 WebAuthn 旨在提供安全登录,而 DBSC 是 互补的,在登录后保护用户。
5. 服务器考量
为了使用 DBSC,站点所有者需要建立两个新端点: 注册端点和刷新端点。
浏览器在收到
`Secure-Session-Registration`
标头后,会异步联系注册端点。此端点应:
-
提供会话配置,包括新的会话标识符。
-
持久保存请求的公钥,并将其与会话标识符关联。
刷新端点敏感得多。每当请求携带过期的绑定 cookie 时, 都会联系此端点,并且其响应会阻塞 原始请求。未能响应或恢复绑定 cookie 可能 导致浏览器代理启动拒绝服务防护机制,甚至 终止会话。二者都可能导致后续请求没有绑定 cookie。此端点的预期行为是:
-
按标识符查找该会话的公钥和近期 challenge。
-
验证 `
Secure-Session-Response` 标头已使用 正确密钥对近期 challenge 签名。请注意,由于网络延迟和竞态条件, 有可能在发出新的 challenge 后收到旧 challenge 的签名。 -
签发新的绑定 cookie。
-
可选地更新当前会话配置。
浏览器可以选择主动刷新会话,以尝试最小化站点采用 DBSC 带来的延迟成本。站点不应假设刷新只会在 绑定 cookie 过期时发生。
如果允许跨站点 fetch,刷新端点很可能会通过计时侧
信道直接泄漏登录状态。服务器可以检查有效的
`Sec-Secure-Session-Id`
标头,以确保传入请求由用户代理发起,
而不是跨站点请求。还建议在此端点上设置
狭窄的 CORS 策略,并且不要允许跨站点来源携带凭据发起
请求。DBSC 的 CORS 集成已设计为通过在延迟
请求携带凭据时隐式包含凭据来实现这一点。出于类似原因,
还建议刷新端点通过 X-Frame-Options 或
Cross-Origin-Resource-Policy 标头拒绝被嵌入。
example.com 有两个端点:
-
/authenticated将对任何 请求来源返回Access-Control-Allow-Credentials。 -
/refresh是 DBSC 刷新端点。
该站点希望 DBSC 保护对 /authenticated 的跨站点请求,并且
认为此单一端点上的计时侧信道风险
很小。如果我们没有隐式允许由用户代理触发的刷新请求携带
凭据,那么 /refresh 将需要为任何请求来源返回
Access-Control-Allow-Credentials。这样攻击者
就可以直接 fetch /refresh 来泄漏登录状态。
由于 DBSC 对 /refresh 的调用会隐式允许凭据,/refresh
端点可以拒绝永远返回 Access-Control-Allow-Credentials。它仍会
在 DBSC 刷新的上下文中收到带凭据的请求。其他
站点将无法直接携带凭据 fetch 该端点,
从而防止登录状态发生简单的跨站点泄漏。
使用 DBSC 进行联合登录的站点(见 § 3.3 联合会话)应 确保只接受具有联合密钥的会话。联合 会话的安全性依赖于机器在 Session Provider(SP)登录时未被攻陷,因为恶意软件可以利用对机器的临时访问 在 Relying Party(RP)上用新密钥注册会话。因此 RP 应 只接受使用从 SP 收到的适当公钥注册的会话。
6. 用户代理考量
DBSC 为浏览器调度 cookie 刷新提供了很大的灵活性。这可以缓解 DBSC 的延迟并降低 TPM 和服务器负载。我们建议用户代理按以下方式调度 cookie 刷新:
-
如果会话已有一个待处理的 DBSC 刷新,不要启动另一个 刷新。这会减少 TPM 负载,并允许更简单的服务器 实现
-
如果会话凭据很快将过期,并且范围内文档处于 活跃状态,用户代理可以主动刷新,以消除 即将到来的请求上的延迟。
7. 框架
本文档使用 ABNF 语法指定句法,如 [RFC5234] 中定义,并由 [RFC7405] 更新,同时使用 第 7 节 中定义的#rule 扩展,该节来自
[RFC9112],以及同一文档
第 3.2.6 节
中定义的 quoted-string 规则。
本文档依赖 Infra Standard 中用于其算法和正文的若干基础 概念 [INFRA]。
7.1. 会话存储
用户代理维护一个会话 存储。它是一个从有序映射 到 按 id 的会话映射 的映射,键为可注册域。会话应在 用户代理重启后继续持久存在。7.2. 按 id 的会话
按 id 的会话映射是一个有序 映射,对于给定的可注册域,它从 会话标识符映射到设备绑定会话。7.3. 设备绑定会话
设备绑定 会话是一个结构体,具有以下 项:- 会话标识符
- 刷新 URL
- 已缓存 challenge
-
一个字符串或 null,将用作此 会话的下一个 challenge
- 会话范围
- 会话凭据
- 过期时间戳
-
一个时刻,此会话应在该时刻被移除
- 会话密钥对
-
会话使用的密钥对。私钥 应以安全方式存储(见 § 2 安全 考量)。
- 允许的刷新发起者
-
一个列表,包含 描述哪些主机允许发起 DBSC 刷新的字符串。详见 § 8.3 识别请求是否允许 刷新。
7.4. 会话范围
会话范围是一个结构体,具有以下 项:7.5. 范围规范
范围规范 是一个结构体,具有 以下 项:- type
-
一个字符串,为 "include" 或 "exclude",定义此结构体中 定义的项应添加到范围还是从范围中移除
- host
-
一个字符串,定义此 范围规范适用时必须匹配的域名或域名模式
- path
-
一个字符串,定义此范围规范的路径部分
7.6. 会话凭据
会话凭据是 一个结构体,具有 以下 项:7.7. 可注册来源标签
域的可注册来源 标签是该域的可注册域的第一个 域 标签,或者如果可注册域为 null,则为 null。例如,如果co.uk 和
de 都是公共后缀,那么
example.co.uk 和 www.example.de 的可注册来源标签都是 example。
8. 算法
8.1. 识别会话
给定一个 URL(url)和会话标识符 (session identifier),此算法返回一个设备绑定会话;如果不存在此类会话,则返回 null。
-
令 domain sessions 为用户代理的会话存储[domain],作为一个 按 id 的 会话映射。
-
返回 domain sessions[session identifier] 并以 null 作为默认值。
8.2. 识别 URL 是否在会话范围内
-
令 scope 为 session 的会话范围。
-
如果 scope 的 include site 为 true,且 URL 的 origin 与 scope 的 origin 不是同站点, 则返回 "exclude"。
-
如果 scope 的 include site 为 false,且 URL 的 origin 与 scope 的 origin 不是同源, 则返回 "exclude"。
-
如果 URL 匹配 session 的刷新 URL,返回 "exclude"。
-
返回 "include"。
8.3. 识别请求是否允许刷新
-
如果 session 的会话范围的 include site 为 true,且 request 的origin 与 session 的会话范围 的 origin 同站点,返回 "allowed"。
-
如果 session 的会话范围的 include site 为 false,且 request 的origin 与 session 的会话范围 的 origin 同源,返回 "allowed"。
-
对每个 initiator pattern,该项在 session 的 允许的刷新发起者中:
-
如果对 request 的 origin 的host和 initiator pattern 运行 § 8.4 识别主机是否匹配 模式返回 true,则返回 "allowed"。
-
-
返回 "disallowed"。
8.4. 识别主机是否匹配模式
-
如果 pattern 等于 '*',返回 true。
-
令 host string 为序列化后的 host。
-
如果 pattern 以 '*' 开头,且 host 是一个域:
-
如果 pattern 不以 '*.' 开头,返回 false。
-
如果 host string 以 pattern 去掉第一个字符后的全部内容结尾, 返回 true。
-
-
如果 host string 等于 pattern,返回 true。
8.5. 识别需要刷新的会话
-
令 domain sessions 为用户代理的会话存储[domain],作为一个 按 id 的 会话映射。
-
对每个 session,该项在 domain sessions 中:
-
令 id 为 session 的会话标识符。
-
如果元组(domain, id)在 request 的 延迟的设备绑定会话 ids中,继续。
-
对 request 的 URL 和 session 运行 § 8.2 识别 URL 是否在 会话范围内 中的步骤。如果结果不是 "include",继续。
-
对 request 和 session 运行 § 8.3 识别请求是否 允许刷新 中的步骤。如果结果不是 "allowed",继续。
-
对 request 和 session 的会话凭据运行 § 8.6 识别是否 缺少会话凭据。 如果结果为 false,继续。
-
将(domain, id)添加到 request 的延迟的设备绑定 会话 ids。
-
将 session 的过期时间戳设置 为未来的时间戳。过期时间戳的 具体选择由用户代理决定。建议 与最大 cookie 生命周期对齐。
-
返回 session。
-
返回 null。
8.6. 识别是否缺少会话凭据
-
对每个 credential,该项在 credentials 中:
-
如果具有 credential 的attributes 的 cookie 不会附加到 request(见 第 5.4 节, 来自 [COOKIES]),继续。
-
如果 request 的header list 包含一个 cookie,且 满足以下所有条件,继续:
-
cookie 的名称匹配 credential 的name。
-
cookie 的以下所有属性都匹配 credential 的attributes中的属性:Domain、Path、 Secure、HttpOnly、SameSite。
-
-
返回 true。
-
-
返回 false。
8.7. 缓存 challenge
给定一个响应(response),此算法更新某个设备绑定 会话的已缓存 challenge。
-
令 header name 为 "
Secure-Session-Challenge"。 -
令 challenge list 为在 response 的 header list上,给定 header name 和 "list" 执行获取结构化 字段值所得的结果。
-
如果 challenge list 为 null,返回。
-
对每个(challenge, params),该项在 challenge list 中:
-
令 session id 为 null。
-
如果 params["id"] 存在且是 sf-string,将 session id 设为 params["id"]。
-
如果 session id 为 null,继续。
-
令 session 为给定 response 的 URL 和 session id 运行 § 8.1 识别会话 所得的结果。
-
如果 session 为 null,继续。
-
对每个 credential,该项在 session 的会话凭据中:
-
如果具有 credential 的attributes 的 cookie 不能被 response 设置(见 第 5.3 节, 来自 [COOKIES]),继续。
-
将 challenge 存储为 session 的已缓存 challenge,以便下次 从此设备绑定 会话发送DBSC 证明时使用。
-
中断。
-
8.8. 发送请求
每当绑定凭据缺失时,用户代理会执行这些步骤, 但 MAY 在任何时候执行。主动在绑定 凭据过期之前发送请求可以最小化 DBSC 的延迟成本。
-
用户代理 MAY 跳过此请求,以防止对用户或站点造成拒绝服务。 例如,当此会话正在请求过多 TPM 操作(损害用户)或刷新 端点最近不可达(对站点存在拒绝服务风险)时,可能会发生这种情况。 如果用户代理选择这样做,它应使用 originating request、适当的 token(见 § 9.5 `Secure-Session-Skipped` HTTP 标头 字段中的选项)和 session id 执行 § 8.12 添加 debug 标头 的步骤, 以向站点指示这一点。
-
令 terminate the session 为具有以下步骤的算法:
-
如果 originating request 的URL的origin与 destination 的origin不是同站点,返回。
-
令 signed challenge 为 null。如果 challenge 或 authorization 非 null,创建一个DBSC 证明,使用 key pair 对其签名,并将 结果存储到 signed challenge 中。
-
创建一个用于 HTTP fetch 的 request。
-
将 request 的method 设为 "POST"。
-
将 request 的URL 设为 destination。
-
将 request 的redirect mode 设为 "follow"。
-
如果 signed challenge 非 null,将标头 ("Secure-Session-Response", signed challenge) 追加到 request 的 header list。
-
如果 session id 非 null,将标头 ("Sec-Secure-Session-Id", session id) 追加到 request 的 header list。
-
如果 authorization 非 null,将标头 ("Authorization", authorization) 追加到 request 的header list。
-
令 response 为对 request 运行 HTTP fetch 的结果。
-
如果 response 的status 至少为 300 且小于 400,则 返回。
-
如果 response 的status 为 403:
-
如果 session id 为 null,返回。
-
令 session 为对 destination 和 session id 运行 § 8.1 识别会话 中步骤的结果。
-
如果 session 为 null,返回。
-
否则,用原始输入重新开始此算法,但将 challenge 替换为 session 的已缓存 challenge。
-
-
如果 response 的status 至少为 400 且小于 500, 则执行 terminate the session 并返回。
-
如果 response 的status 至少为 500,则 返回。用户代理可以选择在此会话后续刷新请求上触发退避机制, 以限制刷新端点临时故障造成的损害。
-
如果 session id 非 null,且 response 的body 为空,返回。
-
以 response、 destination、 session id 和 key pair 调用 § 8.9 创建会话。
8.9. 创建会话
-
令 terminate the session 为具有以下步骤的算法:
-
令 JSON session 为将 response 的body 解析为JSON 会话指令的结果。如果解析失败, 执行 terminate the session 并返回。这包括验证所有必需键均已 存在。
-
如果 JSON session 的 continue 为 false, 执行 terminate the session 并返回。
-
令 session identifier 为 JSON session 的session_identifier。
-
令 JSON scope 为 JSON session 的scope。
-
令 origin 为从 JSON scope 的origin(如果存在)构造的来源;如果不存在,则为 destination 的来源。
-
如果 JSON session 的refresh_url没有 值,令 refresh URL 为 destination。
-
否则,令 refresh URL 为用 JSON session 的refresh_url 的值相对于 destination 解析所得的结果。
-
执行以下验证。如果任一失败, 执行 terminate the session 并返回。
-
如果 JSON scope 的 include_site 为 true,执行 以下验证。如果任一失败,执行 terminate the session 并返回。
-
origin 的host必须等于 destination domain。
-
如果 destination domain 等于 destination 的host,跳过此步骤中所有剩余验证。
-
如果 session id 非 null,跳过此 步骤中所有剩余验证。
-
否则,令 well known URL 为 destination 的副本,将 host 替换为 destination domain, 并将path 替换为 "/.well-known/device-bound-sessions"。
-
令 well known response 为 fetch well known URL 的结果。
-
well known response 的status 必须为 200。
-
well known response 的body 必须是 JSON 编码的 字典。
-
well known response 的body 必须包含键 "registering_origins"。
-
键 "registering_origins" 的值必须包含 destination 的来源。
-
-
令 session 为一个新会话。
-
将 session 的会话标识符设为 session identifier。
-
将 session 的刷新 URL设为 refresh URL。
-
将 session 的会话范围设为一个新范围,其 origin 为 origin,且 include site 为 JSON scope 的 include_site 的值。
-
对每个 JSON scope rule,该项在 JSON scope 的scope_specification中:
-
对每个 JSON session credential,该项在 JSON session 的credentials中:
-
令 session credential 为一个新的会话凭据。
-
如果 JSON session credential 的 type 不是 "cookie",执行 terminate the session 并返回。
-
将 session credential 的name设 为 JSON session credential 的name。
-
将 session credential 的attributes设为 JSON session credential 的attributes。
-
将 session credential 追加到 session 的会话凭据中。
-
-
将 session 的会话密钥对设为 key pair。
-
将 session 的允许的刷新发起者 设为 JSON session 的allowed_refresh_initiators。
-
将 session 的过期时间戳设为未来 时间戳。过期时间戳的 具体选择由用户代理决定。建议 与最大 cookie 生命周期对齐。
8.10. 处理会话注册
-
令 header name 为 "
Secure-Session-Registration"。 -
令 registration list 为在 response 的 header list中给定 header name 和 "list" 执行获取结构化 字段值所得的结果。
-
如果 registration list 为 null,返回。
-
对每个(registration entry, params) ,该项在 registration list 中:
-
如果 registration entry 不是 sf-inner-list, 继续。
-
令 path 为 params["path"]。
-
令 challenge 为 null,并令 authorization 为 null。
-
如果 params["challenge"] 或 params["authorization"] 中任一个存在 但 类型不是 sf-string,继续。
-
如果 params["challenge"] 存在,将 challenge 设为 params["challenge"]。
-
如果 params["authorization"] 存在,将 authorization 设为 params["authorization"]。
-
令 endpoint 为相对于 response 的 URL 解析 path 所得的结果。
-
令 key pair 为对 registration entry、 params 和 endpoint 运行 § 8.11 创建会话密钥对所得的结果。
-
如果 key pair 为 null,返回。
-
使用 request、 key pair、endpoint、 作为 null 的 session identifier、challenge 和 authorization 调用 § 8.8 发送请求。
-
8.11. 创建会话密钥对
Secure-Session-Registration`
标头的 registration entry 和 params,以及注册端点的URL(registration
URL)作为输入。
它返回用于该会话的密钥对;如果没有可用密钥,则返回 null。
-
令 algorithm list 为空列表。
-
对每个 algorithm,该项在 registration entry 中:
-
如果 algorithm 表示一个在 `
Secure-Session-Registration` 中受支持的加密算法,并且此客户端支持它, 将 algorithm 添加到 algorithm list。
-
如果 algorithm list 为空,返回 null。
-
如果 params["provider_key"]、params["provider_id"] 或 params["provider_url"] 中任一个存在:
-
如果三个键中有任意一个缺失,返回 null。
-
令 permission 为对 "device-bound-session-key-sharing" 执行请求使用权限的结果。
-
如果 permission 是
"denied", 返回 null。 -
令 provider URL 为从 params["provider_url"] 构造的URL。
-
令 provider origin 为 provider URL 的origin。
-
如果 provider origin 为不透明,返回 null。
-
令 provider identifier 为 params["provider_id"]。
-
令 provider session 为用户代理的 会话 存储[provider domain][provider identifier] 中的会话。
-
如果 provider session 为 null,返回 null。
-
如果 provider session 的会话范围 的 origin 与 provider origin 不是同源,返回 null。
-
如果 provider session 的会话密钥对的 JWK thumbprint(参见 [RFC7638])在用 base64 编码时(参见 [RFC4648])不匹配 params["provider_key"], 返回 null。
-
令 provider well known URL 为 provider URL 的副本,将 path 替换为 "/.well-known/device-bound-sessions"。
-
令 provider well known response 为 fetch provider well known URL 的结果。如果满足以下任一条件,返回 null:
-
用户代理 SHOULD 对 "relying_origins" 中可注册来源标签的数量设置上限,以防止 滥用。
-
令 relying well known URL 为 registration URL 的副本,将 path 替换为 "/.well-known/device-bound-sessions"。
-
令 relying well known response 为 fetch relying well known URL 的结果。如果满足以下任一条件,返回 null:
-
返回 provider session 的会话密钥对。
-
-
返回为 algorithm list 创建的新密钥对。
8.12. 添加 debug 标头
-
令 value 为一个sf-token, 其值为 token。
-
将 value 上的sf-parameter "session_identifier" 设为 session id。
-
令 skipped header value 为在 request 的 header list上,使用header name "Secure-Session-Skipped"、类型 "list" 运行获取结构化字段值 的步骤所得的结果。
-
如果 skipped header value 为 null,将其设为空sf-list。
-
将 value 追加到 skipped header value。
-
在 request 的 header list上,给定 ("Secure-Session-Skipped", skipped header value),运行设置结构化字段值 的步骤。
9. DBSC 格式
9.1.
`Secure-Session-Registration`
HTTP 标头字段
Secure-Session-Registration 标头字段可由服务器在
响应中使用,以在客户端上启动一个新的设备绑定会话。
`Secure-Session-Registration`
是一个 List Structured Header [RFC9651]。其 ABNF
为:
SecureSessionRegistration = sf-list
列表中的每一项都必须是一个 inner list,且 inner list 中的每一项 MUST 是表示受支持算法(ES256、RS256)的sf-token。 当前仅支持这两个值。
定义了以下 sf-parameter:
-
键为 "path"、值为 sf-string 的sf-parameter,传达注册端点的路径。它可以 相对于当前 URL, 也可以是完整的 URL。没有 此参数的条目将在 § 8.10 处理 会话注册中被忽略。
-
键为 "challenge"、值为 sf-string 的sf-parameter,传达会话 注册中要使用的 challenge。
-
键为 "authorization"、值为 sf-string 的sf-parameter。此 sf-parameter 将被复制到 注册 JWT 中。
-
键为 "provider_key"、值为 sf-string 的sf-parameter,传达此会话应与具有给定公钥的会话 共享密钥。
-
键为 "provider_id"、值为 sf-string 的sf-parameter,传达此会话应与具有给定会话标识符的会话 共享密钥。
-
键为 "provider_url"、值 为sf-string 的sf-parameter,传达此会话应与相应站点上的会话 共享密钥。
Secure-Session-Registration`
的一些示例:
HTTP/1.1 200 OK Secure-Session-Registration: (ES256);path="reg";challenge="cv";authorization="ac"
HTTP/1.1 200 OK Secure-Session-Registration: (ES256 RS256);path="reg";challenge="cv"
HTTP/1.1 200 OK Secure-Session-Registration: (ES256);path="reg1";challenge="cv1";authorization="a" Secure-Session-Registration: (RS256);path="reg2";challenge="cv2";authorization="b"
HTTP/1.1 200 OK Secure-Session-Registration: (ES256);path="reg1";challenge="cv1";authorization="a", (RS256);path="reg2";challenge="cv2";authorization="b"
HTTP/1.1 200 OK Secure-Session-Registration: (ES256);path="reg1";challenge="cv1";provider_key="abc";provider_id="idp_id";provider_url="https://id_origin.example.com"
9.2.
`Secure-Session-Challenge`
HTTP 标头字段
Secure-Session-Challenge 标头字段可由服务器在
响应中使用,以向客户端发送 challenge,服务器期望该 challenge
在未来
`Secure-Session-Response`
标头内的DBSC 证明中使用,
或者在 cookie 刷新期间
如果status 为 403,则立即请求新签名的DBSC 证明。
`Secure-Session-Challenge`
是一个 structured header。其值必须是字符串。
其 ABNF 为:
SecureSessionChallenge = sf-string该项的语义定义在 § 9.2.1 `Secure-Session-Challenge` 结构化标头 序列化中。
处理步骤定义在 § 8.7 缓存 challenge 中。
9.2.1. `Secure-Session-Challenge`
结构化标头序列化
`Secure-Session-Challenge`
表示为 Structured Field。[RFC9651]
在此表示中,challenge 表示为字符串。
Challenge MUST 具有一个名为 "id" 的sf-parameter,其值 MUST 是一个表示会话标识符的字符串。任何其他
sf-parameter SHOULD 被忽略。
Secure-Session-Challenge`
的一些示例:
HTTP/1.1 403 Forbidden Secure-Session-Challenge: "new challenge";id="my session"
HTTP/1.1 200 OK Secure-Session-Challenge: "new challenge";id="my session"
HTTP/1.1 200 OK Secure-Session-Challenge: "new challenge";id="my session 1" Secure-Session-Challenge: "another challenge";id="my session 2"
HTTP/1.1 200 OK Secure-Session-Challenge: "c1";id="session 1", "c2";id="session 2"
9.3.
`Secure-Session-Response`
HTTP 标头字段
Secure-Session-Response 标头字段可由用户代理在
请求中使用,以向服务器发送DBSC 证明,证明
客户端仍持有会话密钥对的私钥。
Secure-Session-Response 是一个 structured
header。其值必须是字符串。其 ABNF 为:
SecureSessionResponse = sf-string
此字符串 MUST 只包含DBSC 证明 JWT。任何sf-parameter SHOULD 被 忽略。
POST example.com/refresh Secure-Session-Response: "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRic2Mrand0In0.eyJhdWQiOiJodHRwczovL2V4YW1wbGUuY29tL3JlZyIsImp0aSI6ImN2IiwiaWF0IjoiMTcyNTU3OTA1NSIsImp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IjZfR0Iydm9RMHFyb01oNk9sREZDRlNfU0pyaVFpMVBUdnZCT2hHWjNiSEkiLCJ5IjoiSWVnT0pVTHlFN1N4SF9DZDFLQ0VSN2xXQnZHRkhRLWgweHlqelVqRUlXRSJ9LCJhdXRob3JpemF0aW9uIjoiYWMifQ.6Fb_vVBDmfNghQiBmIGe8o7tBfYPbPCywhQruP0vIhxgmcJmuNTaMHeVn_M8ZnOm1_bzIitbZqCWEn-1Qzmtyw"
9.4.
`Sec-Secure-Session-Id`
HTTP 标头字段
Sec-Secure-Session-Id 标头字段可由用户代理在
请求中使用,以请求刷新当前会话,
并将当前会话标识符作为字符串参数。
`Sec-Secure-Session-Id`
是一个 structured header。
其值必须是字符串。其 ABNF 为:
SecSecureSessionId = sf-string
此字符串 MUST 只包含会话标识符。任何参数 SHOULD 被 忽略。
9.5.
`Secure-Session-Skipped`
HTTP 标头字段
Secure-Session-Skipped 标头字段可在
请求中使用,以指示该请求因用户代理策略而有意缺少绑定
凭据。
`Secure-Session-Skipped`
是一个 List Structured Header [RFC9651]。其 ABNF
为:
SecureSessionSkipped = sf-list
列表中的每一项 MUST 是一个表示 跳过 cookie 刷新的粗粒度原因的sf-token。 唯一受支持的值是:"unreachable"、"server_error" 和 "quota_exceeded"。这些 token 应在以下情况下使用:
-
"server_error" 表示会话刷新因来自服务器的 响应(例如 500 状态码)而失败。
-
"unreachable" 表示会话刷新因 无法到达服务器而失败。
-
"quota_exceeded" 覆盖用户代理选择不 刷新的所有情况(例如近期 TPM 操作过多)。
定义了一个 sf-parameter:
-
键为 "session_identifier"、值为 sf-string 的sf-parameter,传达被跳过会话的标识符。
GET example.com/requires_bound_cookie Secure-Session-Skipped: unreachable;session_identifier=123, quota_exceeded;session_identifier=456
9.6. JSON 会话指令格式
服务器在会话注册期间发送JSON 会话指令,并可选地在会话刷新期间发送。如果响应 包含会话指令,它 MUST 为 JSON 格式。在 JSON 对象根部,可以存在以下键:
- session_identifier
-
一个字符串,表示会话标识符。 注册期间,这是新创建 会话的标识符。此键 MUST 存在,除非 continue 键的值为 false。刷新期间,这 MUST 是当前 会话的标识符。见 § 8.9 创建会话。
- refresh_url
-
一个字符串,表示用于未来刷新请求的 URL。 它可以是完整的 URL,也可以相对于提供这些指令的 请求。 此键是 OPTIONAL;如果不存在,将使用该请求的 url 作为未来刷新请求。
- continue
-
一个布尔值,指示会话是否应继续适用。 注册和刷新端点可以将此设为 false 以终止会话。 此键是 OPTIONAL;如果不存在,默认值将为 true。
- scope
-
一个JSON 会话 范围,描述该 会话覆盖的资源。此键 MUST 存在,除非 continue 键的值为 false。
- credentials
-
一个列表,包含 JSON 会话凭据,描述此会话保护的 cookie。此键 MUST 存在,除非 continue 键的值为 false。
- allowed_refresh_initiators
-
一个列表,包含 描述哪些主机允许发起 DBSC 刷新的字符串。详见 § 8.3 识别请求是否允许 刷新。此键 是 OPTIONAL;如果不存在,默认值将为空列表。
{ "session_identifier" : "session_id" , "refresh_url" : "/RefreshEndpoint" , "continue" : false , "scope" : { // 默认按来源定范围(即 https://example.com) "origin" : "https://example.com" , // 指定包含 https://*.example.com,但排除的子域除外。 // 只有当来源的主机是根 eTLD+1 时,这才能为 true。 "include_site" : true , "scope_specification" : [ { "type" : "include" , "domain" : "trusted.example.com" , "path" : "/only_trusted_path" }, { "type" : "exclude" , "domain" : "untrusted.example.com" , "path" : "/" }, { "type" : "exclude" , "domain" : "*.example.com" , "path" : "/static" } ] }, "credentials" : [{ "type" : "cookie" , // 这指定此配置适用的确切 cookie。属性 // 匹配 RFC 6265bis 中的 cookie 属性,并以类似于 // 普通 Set-Cookie 行的方式解析,使用相同默认值。 // 这些 SHOULD 等同于伴随此 // 响应的 Set-Cookie 行。 "name" : "auth_cookie" , "attributes" : "Domain=example.com; Path=/; Secure; HttpOnly; SameSite=None" // 属性 Max-Age 和 Expires 会被忽略 }], "allowed_refresh_initiators" : [ "example.com" , "*.example.com" , "site-embedding-example.com" ] }
9.7. JSON 会话范围指令格式
服务器在注册期间的JSON 会话指令中发送一个JSON 会话范围,并可选地在 会话刷新期间发送。在 JSON 对象根部,可以存在以下键:
- origin
-
一个字符串,指示会话适用的来源或站点。 此键是 OPTIONAL;如果不存在,将使用提供 指令的 URL 的来源。注册期间这是注册 URL, 刷新期间这是刷新 URL。
- include_site
-
一个布尔值,指示会话是按来源定范围(false)还是 按站点定范围(true)。此键 MUST 存在。请注意,这优先于 "scope_specification" 中的任何JSON 会话范围规则 (见 § 8.2 识别 URL 是否在会话范围内)。
- scope_specification
-
一个列表,包含 JSON 会话范围规则,描述对 默认范围(整个来源或站点)的修改。此键是 OPTIONAL;如果 不存在,将使用空列表。
9.8. JSON 会话范围规则格式
服务器在注册期间的 JSON 会话范围 中发送一个JSON 会话范围规则对象列表,并可选地在会话 刷新期间发送。在每个JSON 会话范围规则的根部,可以存在以下键:
- type
-
一个字符串,指示该规则包含还是排除目标。 此键 MUST 存在,且值 MUST 是 "include" 或 "exclude"。
- domain
-
一个字符串,指示应与规则匹配的域名。这可以 包含通配符(见 § 8.2 识别 URL 是否在 会话范围内)。此键是 OPTIONAL;如果 不存在,它将为 '*',匹配所有域名。
- path
-
一个字符串,指示应与规则匹配的路径前缀。此 键是 OPTIONAL;如果不存在,它将为 '/',匹配所有路径。详细 语义见 § 8.2 识别 URL 是否在会话范围内。
9.9. JSON 会话凭据格式
服务器在注册期间的 JSON 会话 指令中发送一个JSON 会话凭据对象列表,并可选地在会话 刷新期间发送。在 JSON 对象根部,可以存在以下键:
- type
-
一个字符串,指示此会话保护的凭据种类。 此键 MUST 存在,且值 MUST 是 "cookie"。
- name
-
一个字符串,指示绑定 cookie 的名称。
- attributes
-
一个字符串,包含受保护 cookie 的预期属性。 详见 § 8.6 识别是否缺少会话 凭据 中关于此内容如何 使用的说明。
9.10. DBSC 证明 JWT 语法
DBSC 证明是一个 JWT proof,它由客户端选择的私钥签名(使用 JSON Web Signature (JWS))。DBSC 证明的标头 MUST 至少包含以下sf-parameter:
- typ
-
一个字符串。这 MUST 是 "dbsc+jwt"。
- alg
-
一个字符串,定义用于签署此 JWT 的算法。它 MUST 是 [IANA.JOSE.ALGS] 中的 "RS256" 或 "ES256"。
DBSC 证明的载荷 MUST 至少包含以下声明:
- aud
-
一个字符串。这 MUST 是此 JWT 最初发送到的 URL。 示例:"https://example.com/refresh.html"。
- jti
-
一个字符串。这是注册 标头中发送的 challenge 值的副本。
- iat
-
一个 double,用于标识 JWT 签发的时间。它可用于 确定 JWT 的年龄。其值 MUST 是一个包含 NumericDate 值的数字, 如 [RFC7519] 所述。
- key
-
一个字典,定义 [RFC7517] 中指定的 JWK。
此外,如果以下声明出现在
`Secure-Session-Registration`
标头字段中,则 MUST 存在:
- authorization
-
一个字符串。这是直接复制自 `
Secure-Session-Registration` 标头字段(如果在那里设置)的字符串。请注意,此 字符串在标头中是 OPTIONAL,但如果它存在, 客户端 MUST 将其添加到DBSC 证明中的声明。
如果 DBSC 证明用于刷新请求,则以下声明 MUST 存在:
// 标头 { "alg" : "ES256" , "typ" : "dbsc+jwt" } // 载荷 { "aud" : "https://example.com/reg" , "jti" : "cv" , "iat" : 1725579055.0 , "key" : { "kty" : "EC" , "crv" : "P-256" , "x" : "6_GB2voQ0qroMh6OlDFCFS_SJriQi1PTvvBOhGZ3bHI" , "y" : "IegOJULyE7SxH_Cd1KCER7lWBvGFHQ-h0xyjzUjEIWE" }, "authorization" : "ac" }
基于对 http://example.com/page.html 的响应,该响应具有来自服务器的此
响应标头:
HTTP/1.1 200 OK Secure-Session-Registration: (ES256);path="reg";challenge="cv";authorization="ac"
10. 对其他规范的更改
10.1. 对 Fetch 规范的更改
本规范要求更新 HTTP-network-or-cache fetch 算法。请求具有延迟的设备 绑定会话 ids,这是一个列表,包含由以下内容组成的元组:
此列表初始为空。在步骤 8.21 中计算 cookie 后,运行 § 8.5 识别需要刷新的会话。如果得到的 session 非 null:
-
使用 httpRequest、返回的 session 的会话密钥对、刷新 URL、会话标识符、已缓存 challenge,以及空 authorization 运行 § 8.8 发送请求。
-
使用原始输入重新启动 HTTP-network-or-cache fetch。
本规范还要求进行两次新的调用以处理新的 标头。在当前 HTTP-network fetch 的步骤 14 之后,执行以下步骤:
10.2. 对 Clear Site Data 规范的更改
本规范要求 Clear Site Data 规范第 4.2.5 节 Clear DOM-accessible storage for origin 清除其范围匹配 origin 的所有设备绑定会话。它还要求更新第 4.2.4 节,以 清除与注册域匹配的站点的设备绑定会话。
11. IANA 考量
永久消息标头字段注册表应更新为包含以下 注册项:[RFC3864]
11.1. Secure-Session-Challenge
- 标头字段名称
- Secure-Session-Challenge
- 适用协议
- http
- 状态
- draft
- 作者/变更控制者
- W3C
- 规范文档
- 本规范(见 § 9.2 `Secure-Session-Challenge` HTTP 标头字段)
11.2. Sec-Secure-Session-Id
- 标头字段名称
- Sec-Secure-Session-Id
- 适用协议
- http
- 状态
- draft
- 作者/变更控制者
- W3C
- 规范文档
- 本规范(见 § 9.4 `Sec-Secure-Session-Id` HTTP 标头字段)
11.3. Secure-Session-Registration
- 标头字段名称
- Secure-Session-Registration
- 适用协议
- http
- 状态
- draft
- 作者/变更控制者
- W3C
- 规范文档
- 本规范(见 § 9.1 `Secure-Session-Registration` HTTP 标头字段)
11.4. Secure-Session-Response
- 标头字段名称
- Secure-Session-Response
- 适用协议
- http
- 状态
- draft
- 作者/变更控制者
- W3C
- 规范文档
- 本规范(见 § 9.3 `Secure-Session-Response` HTTP 标头字段)
11.5. Secure-Session-Skipped
- 标头字段名称
- Secure-Session-Skipped
- 适用协议
- http
- 状态
- draft
- 作者/变更控制者
- W3C
- 规范文档
- 本规范(见 § 9.5 `Secure-Session-Skipped` HTTP 标头字段)
11.6. device-bound-sessions Well Known
Well-Known URI 注册表应更新为包含 /.well-known/device-bound-sessions。
此端点必须提供一个 JSON 编码的字典。定义了三个键:
-
"registering_origins" 是字符串列表。它包含 被允许为整个站点注册会话的来源。
-
"relying_origins" 是字符串列表。它包含 被允许在与此 站点的会话共享密钥时作为 Relying Party(RP)的来源。
-
"provider_origin" 是一个可选字符串。它包含当此来源用作 Relying Party 时的 Session Provider 来源。
https://example.com/.well-known/device-bound-sessions 提供
{ "registering_origins" : [ "https://subdomain.example.com" , "https://subdomain.example.com:8000" , ], "relying_origins" : [ "https://example.co.uk" , "https://example-partner.com" , ], }
则只有在以下任一条件为 true 时,注册请求才能定义 站点范围的会话:
-
注册端点的主机为
example.com -
注册端点的来源为
https://subdomain.example.com -
注册端点的来源为
https://subdomain.example.com:8000
此外,https://example.co.uk 和 https://example-partner.com
被允许与 https://example.com 上的会话共享密钥,
从而使这些站点能够同时实现联合登录和 DBSC
保护。为此,这两个站点都必须在其
.well-known 文件中提供以下条目:
{ "provider_origin" : "https://example.com/" }