安全浏览 Oblivious HTTP Gateway API
注意:本文档目前仍处于开发阶段。预计在不久的将来会有所改进。
Safe Browsing Oblivious HTTP Gateway API 是一种隐私保护 API,基于名为 Oblivious HTTP、RFC 9458 的 IETF RFC 协议而构建。
概览
Safe Browsing Oblivious HTTP Gateway API 是 Google 的一项服务,可让客户端应用根据 Google 不断更新的不安全网络资源列表来检查网址,并实施额外的隐私保护措施。
这是通过名为 Oblivious HTTP(简称 OHTTP)的轻量级协议实现的。安全浏览客户端可以使用这种无状态协议来访问 Google Safe Browsing V5 API,从而在不影响用户保护隐私。
注意:无法通过此服务访问 Google Safe Browsing V4 API。
安全浏览 Oblivious HTTP 协议
RFC 协议
Oblivious HTTP 是 RFC 9458 中定义的轻量级协议,用于加密从客户端到目标服务器的 HTTP 消息并将其发送。此服务使用可信中继服务,减少目标服务器使用元数据(例如 IP 地址和连接信息)来识别客户端的元数据,从而基于普通 HTTP/S 协议提供隐私和安全保护。该协议使用 RFC 9292 中定义的二进制 HTTP 对 HTTP 请求/响应进行编码/解码。
概括来讲,Relay 位于客户端和网关资源之间,负责通过移除所有客户端标识符(包括 IP 地址等隐私敏感属性)来代理客户端流量,从而有效地对发送到网关服务的传入 HTTP 请求进行匿名化处理。OHTTP 的另一个优势是所有请求都经过端到端加密安全浏览查询(即网址表达式的截断哈希)对 Relay 不可见。如需查看 Chrome 中的实现示例,请参阅blogpost。
客户端可以选择任何中继提供商(例如,Fastly),以便与该服务集成。Relay 必须使用具有以下授权范围的 Oauth 2.0 身份验证才能访问服务。
// OAuth Authorization scope:
https://www.googleapis.com/auth/3p-relay-safe-browsing
API 端点
OHTTP 公钥
此端点将提供 RFC 9458 中指定的 OHTTP 公钥配置,客户端将使用该配置来加密 OHTTP 请求。
GET https://safebrowsingohttpgateway.googleapis.com/v1/ohttp/hpkekeyconfig?key=<API key>
上述 API 密钥并非绝对必要;服务器不会根据提供的 API 密钥更改 OHTTP 公钥。客户端可以通过以下方式探测这一事实:使用不同的有效 API 密钥访问此端点,或完全不使用 API 密钥,然后检查响应是否确实包含相同的 OHTTP 公钥。不过,为了方便调试,建议您使用 API 密钥;这样一来,客户端便可以在 Google Cloud 控制台上查看请求数量等统计信息。如果客户端打算提供 API 密钥,请参阅此文档,了解如何设置 API 密钥。
如隐私保护建议部分所述,为了实现密钥一致性目标,建议客户端供应商设置一个集中式密钥分发基础架构,以便从此端点获取密钥,然后将其分发到其客户端应用。
根据密钥管理指南,服务器上的密钥会定期轮替。客户端应不时刷新密钥,即提取和更新密钥的本地副本,以避免解密失败。
客户端应每天刷新(提取和更新)一次公钥。如果使用的是集中式分发机制,该机制应确保每天提取和分发一次密钥。
OHTTP 封装请求
此端点将通过执行请求解密来提供 POST 请求的 HTTP 正文中包含的 OHTTP 请求,并随后对 OHTTP 响应进行加密,以便在 HTTP 响应中转发回 Relay。客户端必须将 Content-Type 请求标头作为 message/ohttp-req 添加到 HTTP POST 请求中。
POST https://safebrowsingohttpgateway.googleapis.com/v1/ohttp:handleOhttpEncapsulatedRequest?key=<API key>
注意:根据 RFC 指南,使用 Binary HTTP 协议 RFC 9292 对内部请求进行编码(请参阅 V5 文档,了解如何构建安全浏览请求)。
客户端库
Google Quiche 提供适用于 OHTTP 和 BHTTP 协议的客户端实现。建议客户端使用这些库。请参阅以下伪代码,了解如何构建 OHTTP 请求以访问 API。
客户端实现示例
客户端从 public key 端点中提取 Oblivious HTTP 公钥。随后,像这样初始化 quiche OHTTP 键配置,并初始化 quiche OHTTP 客户端。
auto ohttp_key_cfgs = quiche::ObliviousHttpKeyConfigs::ParseConcatenatedKeys(std::string public_key);
auto key_config = ohttp_key_cfgs->PreferredConfig();
auto public_key = ohttp_key_cfgs->GetPublicKeyForId(key_config.GetKeyId())
auto ohttp_client = quiche::ObliviousHttpClient::Create(public_key, key_config);
客户端将使用 HTTP 二进制编码来创建 BHTTP 请求,这是加密前的第一步。
quiche::BinaryHttpRequest::ControlData bhttp_ctrl_data{
.method = "POST",
.scheme = "https",
.authority = "safebrowsing.googleapis.com",
.path = "/v5/hashes:search?key=<API key>&hashPrefixes=<HASH prefix 1>&hashPrefixes=<HASH prefix 2>",
};
quiche::BinaryHttpRequest bhttp_request(bhttp_ctrl_data);
客户端随后会对上述步骤中创建的二进制 HTTP 请求进行加密。
auto bhttp_serialized = bhttp_request.Serialize();
auto ohttp_request = ohttp_client.CreateObliviousHttpRequest(*bhttp_serialized);
// Client must include this in POST body, and add `Content-Type` header as "message/ohttp-req".
auto payload_include_in_post_body = ohttp_request.EncapsulateAndSerialize();
收到来自 Relay 的响应后,客户端将对响应进行解密。该响应将包含 ohttp-res 形式的 Content-Type 响应标头。
auto ctx = std::move(ohttp_request).ReleaseContext();
auto ohttp_response = ohttp_client.DecryptObliviousHttpResponse("data included in body of http_response", ctx);
成功解密 OHTTP 响应后,请按如下所示使用二进制 HTTP 解码输出。
auto bhttp_response = BinaryHttpResponse::Create(ohttp_response.GetPlaintextData());
if (bhttp_response.status_code() == 200) {
auto http_response = bhttp_response.body();
auto response_headers = bhttp_response.GetHeaderFields();
}