[WebAuthn] (1)概论及用户注册流程
Web authentication API, aka WebAuthn, 是Web平台用于提供基于公钥密码学的无密码鉴权或安全的双(多)因子认证的API。WebAuthn并使用浏览器的凭据管理API存储密钥对*。
在公钥密码学中,一组对应的公钥和私钥称为一个密钥对。公钥通常可以较轻松得从私钥计算得出,而私钥(应当)无法在有意义的时间*内从公钥得出。用私钥编码(通常称为签名)的信息可以用公钥解码(通常称为验证),用公钥编码(通常称为加密)的信息可以(只能)用私钥解码(通常称为解密)。
现代密码学的一个常识是没有任何密码系统是绝对安全的,这一方面是在说漏洞永远可能存在,另一方面是关于时间,因为通过穷举计算进行的破解总是可行的。衡量一个密码系统强度的关键属性是时间:即“平均来说”使用该密码系统进行的一次通信在多长时间之内是安全的,以及该密码系统本身在多长时间之内是安全的。在决定使用何种密码系统和密钥长度时,时间是需要考虑的关键因素之一。但是即便如此,攻击者依然可能在显著更短的时间内破译通信,虽然这种可能性能够被显著降低。
这也意味着整个现代密码学几乎完全建立在P不等于NP的假设之上:因为知道密钥时的解密应当可以快速完成,这就将一个密码系统所依赖的困难问题限制在了NP问题的范围之内。如果真相是P=NP,那么目前所有的密码系统都会是脆弱的,并且将来也几乎不可能再有新的强度足够高的密码系统。
另外,还有一点需要注意的是,随着算力的不断提高和破译技术的改进,一个在目前看来能够提供数十年安全时间的密码系统,可能在更近的未来被更快攻破,攻击者可以记录现在(和过去)的密文以供未来破译,因此衡量安全性不仅应当考虑当下的水平,还需要估计未来的情况,然而对未来的估计永远是错误的,所以,

然后是免责声明:现代密码学的另一个常识是不要创建你自己的密码系统。一个密码系统的安全性不仅需要数学证明,更需要事实证明。一个密码系统需要经受住严格挑战才有足够的证据信服其安全,而一个新系统,即使它的作者是全世界最专业的密码学家,也缺乏这种事实证明。因此,不要创建你自己的密码系统,不要将未经验证的密码系统用于演示以外的用途,这包括在下文中演示的技术。
这也意味着,新的技术意味着新的风险和不确定性,以及更少的现实验证。当然新技术可能提供更好的结果,但新技术在应用之前首选需要得到充分验证。
另外,让一个密码系统得到充分验证的最好方式之一是公开其实现,这种“透明式安全”的设计理念也是柯克霍夫原则的体现,但这并不意味着只要是公开实现的系统就一定能得到充分的公开验证。当然,我不会批评“不使用‘透明式安全’设计的系统本质上不可靠”的观点,但是基于现实考虑,并不是说不采用“透明式安全”就是不对的,或者只要采用了“透明式安全”的系统就一定是安全的。对于后者我们已经见过足够多的反例了。毕竟,“没有系统本质上是可靠的”。
------吟唱完毕------
虽然据caniuse和mdn显示,目前所有的主流浏览器的正式版本都已对webauthn提供了支持,但是,WebAuthn目前依然应当被认为是尚不完善,试验性,未经充分验证的。实际上,它的用户界面也还没有那么完整,距离大规模商用可能还需要一段时间。
不过,无密码身份认证(实际上更准确的说法是无口令身份认证)一直有着不小的热度,而随着各平台上安全芯片的普及,无密码身份认证的实施正在变得越来越简单,作为Web端无密码认证基础的WebAuthn在将来会成为其中不可或缺的一环。虽然对于开发者来说未来会有完善的认证库供使用,但是了解其中的流程和原理依然是必要的。
另一方面,即使对于普通用户来说,了解正在快速普及的无密码认证背后的机制和理念,对用户个人的信息安全同样是必要的。
注册流程
作为一款基于公钥密码学的API,WebAuthn的注册过程即创建及认证密钥对的过程,在这个过程中我们将使用`navigator.credentials`作为密钥对的凭据存储。在大多数浏览器上,这项功能只允许在安全环境中使用,不过,虽然具体细节有所不同,file://和localhost通常会归为安全环境以方便开发者(不然你就需要在本地部署自签名的https服务器来测试仅安全环境可用的功能了)。下文中我们将在本地启动一个web服务器用于测试。为了方便大家测试,这里不会使用任何前端框架。因为B站专栏对嵌入代码不太友好,下文中的代码将主要以截图展示,最终的示例代码会在整个系列完成后一并给出。
虽然在实际应用中不多见,但WebAuthn的全部流程实际上都可以在浏览器中完成,因此除非特殊需要,本文的示例将尽量保持在浏览器中。注意,这是因为本文完全出于演示目的,除非你明确知道这样做的理由,对公钥的认证通常应当由“依赖方”,一般来说,也就是在服务器中进行。

在chrome中你会看到如下提示:

在Chrome中,用户可以换用实体安全密钥(智能卡)或登录了同一账号的带有安全芯片的Android设备,也可以选择将密钥存储在当前设备中。用户可以在密码管理器->管理通行密钥中看到所有此设备上的密钥。
在Chrome Android中,本机存储的密钥在密码管理器中与密码一同列出(不包括其他设备使用此设备作为安全密钥的情况)。
Android设备可以在Chrome Android的“隐私设置和安全性->将手机用作安全密钥”界面重置用于其他设备的安全密钥。

这是在MacOS Safari中弹出的提示

目前为止Safari(或者说MacOS和iOS)尚未提供可用的密钥管理界面。
注册信息与密钥的认证
我们需要验证返回的key并获取公钥,与验证和公钥相关信息在`key.response`中,其中`key.response.clientDataJSON`是一些json格式的信息,然后以utf-8编码保存在一个ArrayBuffer中,其中包含明文的challenge和一些信息,而`key.response.attestationObject`是CBOR编码的验证信息,此处我们引入一个CBOR编解码库用于编解码,这个编解码库托管于github:paroga/cbor-js。

我们首先需要验证此处的challenge是否与传入的challenge相同,如果不同,那么证明注册流程出现了问题,应当终止这次注册。
然后,我们需要从attestation中获取信息。attestation的信息由两部分组成,一部分是存储于attestationObject.authData的authenticator data,这里主要是公钥,另一部分是attestationObject.attStmt的attestation statement,也就是用于验证的信息,对于本文和多数情况来说其中有用的信息是challenge的签名。

AuthData具体格式在https://w3c.github.io/webauthn/#authenticator-data,公钥在attestedCredentialData,它开始于authData的第37字节,以16字节(即第37至第52字节)的AAGUID开头,紧接着是2字节(第53,54字节)的凭据id长度,然后是相当于此长度L(第55至第55+L-1字节)的凭据id,然后是可变长度的COSE_KEY格式的公钥(开始于第55+L字节),所以接下来我们要取出这个公钥。

对于ecdsa算法,cose编码的公钥会类似下图所示,其中`3:-7`指示公钥所使用的算法为ES256,而-1,-2,-3分别指示公钥使用的曲线和坐标:

接下来我们使用这个公钥来验证attStmt中的签名。attStmt的具体格式被attestationObject.fmt规定。受限于可用库和篇幅限制,接下来的代码将仅限于ES256算法签名的packed验证格式。下文将使用Crypto.subtle验证签名,该API同时也提供了其他的算法组合。一般来说,ES256会使用COSE格式中编号为1的p-256曲线。在packed格式中,签名算法由attStmt.alg标示,签名存储在attStmt.sig,attStmt.x5c指示关于验证器的证书链,或者说它通常用来指示实体安全密钥或安全芯片来自可被验证的厂商。如果验证器是“自认证”的,那么这个参数将不会提供。鉴于浏览器内建的密钥存储功能通常是“自认证”的,下文将仅限于验证自认证签名。
Crypto.subtle使用的ES256证书的原始二进制格式为<0x04 x y>,这里同样也可以转换为jwk或其他格式导入,但是raw格式导入比较方便。
另外,WebAuthn中的ECDSA签名使用ASN.1格式,它的具体二进制格式为<0x30 b1 0x02 b2 r 0x02 b3 s>,而Web Crypto API使用的签名格式为P1363,二进制格式为r|s,在验证签名时需要转换。

这样我们就得到了证书和p1363格式的签名,但是还需要另一个关键参数,就是签名的“原文”。https://w3c.github.io/webauthn/#sctn-packed-attestation约定了packed attestation生成签名的程序。需要注意如果attestation format不是packed,此处的程序会有不同。
我们看到最终被签名的原文包含了challenge,依赖方,用户以及密钥本身相关的信息,因此这个签名可以证明此次注册请求,请求双方以及请求生成的密钥间的对应关系。

至此注册流程完毕。用户已经将密钥对存储于凭据存储中,而依赖方得到了可以用于验证用户身份的公钥。接下来,我们将演示用户与依赖方之间如何利用密钥对确认用户身份。
有余力的读者可以自行查阅其他认证格式和认证方式的资料,并尝试使用安全芯片或实体安全密钥代替浏览器凭据存储完成注册流程。

