分享发现 Web Authentication API 分享

goll · May 30, 2022 · 12 hits

注意: 此项功能仅在安全上下文 (HTTPS) 及支持的浏览器中可用。

Web Authentication API 继承自 Credential Management API,使用公钥密码学赋能强身份验证,不需要手机短信就能实现无密码验证和安全的双因素验证。

Web Authentication 相关概念及用例

Web Authentication API(也称作 WebAuthn)使用非对称(公钥)加密替代密码或短信进行网站注册、身份验证以及双因素验证,其优点包括:

  • 防网络钓鱼:通过创建虚假登录网站的攻击形式将不再可行,因为签名会随着网站 origin(域、协议和端口)的变化而改变。
  • 减少数据泄露的危害:开发人员不需要对公钥进行哈希处理,即使攻击者获取了用于身份验证的公钥,也无法完成验证,因为缺少了私钥。
  • 免受密码攻击:部分用户可能会重复使用密码,攻击者可借此获取其他网站的密码(如通过数据泄漏)。此外,密码相较于数字签名也更容易被暴力破解。

许多网站已经包含了用户注册、登录的页面,而 Web Authentication API 则能够取代这些已有网页,或作为补充。与其他的 Credential Management API 形式相类似,Web Authentication API 的两个基本方法分别对应于注册和登录:

  • navigator.credentials.create() – 当使用 publicKey 选项时,该方法会创建新的凭证,可用于注册新账号,或将新的非对称密钥对凭证与已有账户关联。
  • navigator.credentials.get() – 当使用 publicKey 选项时,该方法会使用现有的凭证集对服务进行身份验证,可以是用户登录或作为双因素身份验证的一种形式。

注意:create() 和 get() 都要求安全上下文(即服务器通过 HTTPS 连接或为本地服务器),而且如果浏览器没有在安全上下文中运作,也无法使用。

在基础实现中,create() 和 get() 会从服务器接收一个大随机数,称为挑战,用私钥签名后将挑战返回服务器。这个过程向服务器证明了用户拥有身份验证所需要的私钥,且没有任何密码在网络上传输。

为了了解 create() 和 get() 方法的实际应用,我们首先需要理解这两个方法处在浏览器之外的以下两个部分之间:

  • 1.服务器 – Web Authentication API 是用来在服务器(也称为服务或依赖方)上注册新的凭证,而后在同一服务器上使用相同凭证来验证用户身份。
  • 2.认证器 – 凭证的创建与存储都在被称为认证器的设备上完成。对于身份验证来说,这是一个新的概念。使用密码进行身份验证时,密码由用户记忆,无需其他设备;而使用 Web Authentication 时,密码被密钥对取代,且存储于认证器中。认证器可内嵌于用户代理商、操作系统(如 Windows Hello),也可以是物理令牌(如 USB 或蓝牙安全密钥)。

注册

注册过程通常包含六个步骤,图 1 所示是对注册过程所需数据的简要概述。创建注册请求所需的字段、可选字段及其意义的完整信息可查看 PublicKeyCredentialCreationOptions 字典。同样,所有的响应字段可以在 PublicKeyCredential 接口中查看(其中 PublicKeyCredential.response 是 AuthenticatorAttestationResponse 接口)。注意,大部分 JavaScript 程序员在创建应用程序时只会关注步骤 1 和 5,也就是调用 create() 函数与返回。但是,步骤 2、3、4 对于了解浏览器和认证器中的处理过程以及返回数据的意义都至关重要。

图 1 – WebAuthn 注册流程及各步骤中的相关重要数据

首先,即图中步骤 0 所示,应用程序发起注册请求,该请求的协议与格式均不在 Web Authentication API 的范围内。

后面的注册步骤如下:

  1. 服务器发送挑战、用户信息及依赖方信息 – 服务器将挑战、用户信息和依赖方信息发送到 JavaScript 程序。这里并没有指定与服务器通信的协议,且不在 Web Authentication API 的范围内。通常情况下,服务器通信协议是 REST over HTTPS(可能会使用 XMLHttpRequest 或 Fetch),但也可以用 SOAP、RFC 2549 或几乎任何安全的协议。从服务器接收到的参数将传递给 create(),基本上不会作任何变动,即使有变动也很少。create() 会返回一个 Promise,最终解析为包含 AuthenticatorAttestationResponse 的 PublicKeyCredential。需要注意的是,挑战须为随机数缓存(至少 16 字节),且必须在服务器上生成以确保注册过程的安全性。
  2. 浏览器向认证器调用 authenticatorMakeCredential() – 在浏览器内部,浏览器会验证参数并用默认值补全缺少的参数,然后这些参数会变为 AuthenticatorResponse.clientDataJSON。其中最重要的参数之一是 origin,该参数是 clientData 的一部分,随后服务器会加以验证。调用 create() 的参数与 clientDataJSON 的 SHA-256 哈希一起传递到认证器(这个过程只有哈希被传输,因为认证器可能是通过低带宽的 NFC 或蓝牙连接,认证器只需要对哈希进行签名以确保未被篡改)。
  3. 认证器创建新密钥对与证明 – 在进行下一步之前,认证器通常会以某种形式要求用户确认,如输入 PIN、指纹、虹膜扫描等等,以证明用户在场且允许注册。完成用户确认之后,认证器会生成新的非对称密钥对,并将私钥安全存储以供将来验证使用。公钥则将作为证明的一部分,然后使用创建过程中就已刻录于认证器内的私钥进行签名。私钥具有一个证书链,可验证信任根。
  4. 认证器将数据返回浏览器 – 新的公钥、全局唯一的凭证 id 及其他证明数据返回到浏览器,成为 attestationObject。
  5. 浏览器生成最终数据,应用程序将响应发送到服务器 – create() Promise 解析为 PublicKeyCredential,其中包含全局唯一的凭证 id PublicKeyCredential.rawId 以及包含 AuthenticatorResponse.clientDataJSON 和 AuthenticatorAttestationResponse.attestationObject 的响应 AuthenticatorAttestationResponse。PublicKeyCredential 发送回服务器,可使用任何想要的格式和协议(注意,ArrayBuffer 类型的属性需要使用 base64 或类似编码方式进行编码)。
  6. 服务器验证并完成注册 – 最后,服务器需要执行一系列检查以确保注册完成且未被篡改。其中包括:
    i. 验证接收到的挑战与发送的挑战相同
    ii.确保 origin 与预期一致
    iii.使用对应认证器型号的证书链验证 clientDataHash 的签名和证明

完整的验证步骤可查看 Web Authentication API 规范。检查完成后,服务器会将与用户账户相关联的新公钥进行存储,以供将来用户希望使用公钥进行身份验证时使用。

验证

用户在 WebAuthn 注册完成之后就可以进行身份验证(或者说登录)。验证的流程与注册流程相似,图 2 所示也与图 1 相类似。但是,二者的主要区别在于:1)验证不需要用户或依赖方信息;2)验证使用之前生成的密钥对来创建断言,而不是用认证器生产时就刻录进去的密钥对来创建证明。和上文一样,以下是对验证流程的简要概述,并不会详细描述 WebAuthn API 的每个环节或细节。验证所需的数据可查看 PublicKeyCredentialRequestOptions 字典,返回的数据可查看 PublicKeyCredential 接口(其中 PublicKeyCredential.response 是 AuthenticatorAssertionResponse 接口)。

图 2 –WebAuthn 验证流程及各步骤中的相关重要数据

后面的验证步骤如下:

  1. 服务器发送挑战 – 服务器发送挑战到 JavaScript 程序。这里并没有指定与服务器通信的协议,且不在 Web Authentication API 的范围内。通常情况下,服务器通信协议是 REST over HTTPS(可能会使用 XMLHttpReques 或 Fetch),但也可以用 SOAP、RFC 2549 或几乎任何安全的协议。从服务器接收到的参数将传递给 get(),基本上不会作任何变动,即使有变动也很少。需要注意的是,挑战须为随机数缓存(至少 16 字节),且必须在服务器上生成以确保验证过程的安全性。
  2. 浏览器向认证器调用 authenticatorGetCredential() - 在浏览器内部,浏览器会验证参数并用默认值补全缺少的参数,然后这些参数会变为 AuthenticatorResponse.clientDataJSON。其中最重要的参数之一是 origin,该参数是 clientData 的一部分,随后服务器会加以验证。调用 get() 的参数与 clientDataJSON 的 SHA-256 哈希一起传递到认证器(这个过程只有哈希被传输,因为认证器可能是通过低带宽的 NFC 或蓝牙连接,认证器只需要对哈希进行签名以确保未被篡改)。
  3. 认证器创建断言 – 认证器找到匹配依赖方 ID 的凭证,并提示用户同意验证。上述两步成功后,认证器会使用注册调用时为该账户生成的私钥给 clientDataHash 和 authenticatorData 进行签名,从而创建新的断言。
  4. 认证器将数据返回浏览器 – 认证器将 authenticatorData 和断言签名返回到浏览器。
  5. 浏览器生成最终数据,应用程序将响应发送到服务器 – 浏览器将 Promise 解析为 PublicKeyCredential,其中含有包含 AuthenticatorAssertionResponse 的 PublicKeyCredential.response。这些数据具体使用何种协议和格式来传输返回至服务器,主要取决于 JavaScript 应用程序。
  6. 服务器验证并完成身份验证 – 服务器接收到验证请求的结果后,会对响应进行验证,包括:
    i.使用注册请求中存储的公钥验证认证器的签名。
    ii.确保认证器签名的挑战与服务器生成的挑战一致。
    iii.检查依赖方 ID 与预期一致

验证断言的完整步骤可查看 Web Authentication API 规范。验证完成后,服务器会注意到用户身份已被验证。此步骤超出了 Web Authentication API 规范的范围,但有一个选项是给用户会话删除新 cookie。

接口

Credential 提供关于实体的信息,作为信任决策的先决条件。

CredentialsContainer 公开请求凭证的方法,并在成功登录或退出时通知用户代理。该接口可通过 Navigator.credentials 访问。Web Authentication 规范在 create() 和 get() 方法中添加了 publicKey 成员,分别是为了创建新的公钥对与获取密钥对验证。

PublicKeyCredential 该接口提供关于公钥/私钥对的信息,是一个使用防网络钓鱼与数据泄露的非对称密钥对进行服务登录的凭证,能够有效取代密码。

AuthenticatorResponse AuthenticatorAttestationResponse 和 AuthenticatorAssertionResponse 的基接口,二者为密钥对提供加密信任根。二者分别由 CredentialsContainer.create() 和 CredentialsContainer.get() 返回,这些子接口包含来自浏览器的信息,如挑战、origin。二者都可以通过 PublicKeyCredential.response 返回。

AuthenticatorAttestationResponse PublicKeyCredential 传递时,由 CredentialsContainer.create() 返回,该接口还能为已生成的新密钥对提供加密信任根。

AuthenticatorAssertionResponse PublicKeyCredential 传递时,由 CredentialsContainer.get() 返回,该接口还能向服务证明拥有密钥对且验证请求有效、已批准同意。

选项

PublicKeyCredentialCreationOptions 传递到 CredentialsContainer.create() 的选项。

PublicKeyCredentialRequestOptions 传递到 CredentialsContainer.get() 的选项。

举例

Demo 网站

用例

注意,出于安全考虑,如果浏览器窗口在调用过程中失去焦点,web authentication 调用 create() 和 get() 会被取消。

// sample arguments for registration
var createCredentialDefaultArgs = {
publicKey: {
// Relying Party (a.k.a. - Service):
rp: {
name: "Acme"
},

        // User:
        user: {
            id: new Uint8Array(16),
            name: "john.p.smith@example.com",
            displayName: "John P. Smith"
        },

        pubKeyCredParams: [{
            type: "public-key",
            alg: -7
        }],

        attestation: "direct",

        timeout: 60000,

        challenge: new Uint8Array([ // must be a cryptographically random number sent from a server
            0x8C, 0x0A, 0x26, 0xFF, 0x22, 0x91, 0xC1, 0xE9, 0xB9, 0x4E, 0x2E, 0x17, 0x1A, 0x98, 0x6A, 0x73,
            0x71, 0x9D, 0x43, 0x48, 0xD5, 0xA7, 0x6A, 0x15, 0x7E, 0x38, 0x94, 0x52, 0x77, 0x97, 0x0F, 0xEF
        ]).buffer
    }
};

// sample arguments for login
var getCredentialDefaultArgs = {
publicKey: {
timeout: 60000,
// allowCredentials: [newCredential] // see below
challenge: new Uint8Array([ // must be a cryptographically random number sent from a server
0x79, 0x50, 0x68, 0x71, 0xDA, 0xEE, 0xEE, 0xB9, 0x94, 0xC3, 0xC2, 0x15, 0x67, 0x65, 0x26, 0x22,
0xE3, 0xF3, 0xAB, 0x3B, 0x78, 0x2E, 0xD5, 0x6F, 0x81, 0x26, 0xE2, 0xA6, 0x01, 0x7D, 0x74, 0x50
]).buffer
},
};

// register / create a new credential
navigator.credentials.create(createCredentialDefaultArgs)
.then((cred) => {
console.log("NEW CREDENTIAL", cred);

        // normally the credential IDs available for an account would come from a server
        // but we can just copy them from above...
        var idList = [{
            id: cred.rawId,
            transports: ["usb", "nfc", "ble"],
            type: "public-key"
        }];
        getCredentialDefaultArgs.publicKey.allowCredentials = idList;
        return navigator.credentials.get(getCredentialDefaultArgs);
    })
    .then((assertion) => {
        console.log("ASSERTION", assertion);
    })
    .catch((err) => {
        console.log("ERROR", err);
    });

规范

Web Authentication: An API for accessing Public Key Credentials

浏览器兼容性

Credential

在 Github 上报告该兼容性数据的相关问题

CredentialsContainer

在 Github 上报告该兼容性数据的相关问题

PublicKeyCredential

在 Github 上报告该兼容性数据的相关问题

AuthenticatorResponse

在 Github 上报告该兼容性数据的相关问题

AuthenticatorAttestationResponse

在 Github 上报告该兼容性数据的相关问题

AuthenticatorAssertionResponse

在 Github 上报告该兼容性数据的相关问题

文章来源:https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API

No Reply at the moment.
You need to Sign in before reply, if you don't have an account, please Sign up first.