我正在尝试实现webauthn,但在使签名验证工作时遇到了问题。根据https://w3c.github.io/webauthn/#verifying-断言我必须基本上验证以下数据的签名:
authData||sha256(客户端数据JSON)
authData和sha256散列应该是"二进制连接"的。我不知道他们到底是什么意思,但我认为他们只是想把字节挨着放,不知道这到底是什么"二进制"。
因此,给定一个名为certificate的PublicKeyCredential,我可以生成生成签名的数据,如下所示:
var auth_data = new Uint8Array(attestation.response.authenticatorData);
var data_hash = sha256(new Uint8Array(attestation.response.clientDataJSON));
var signed = new Uint8Array(auth_data.length + data_hash.length);
signed.set(auth_data);
signed.set(data_hash, auth_data.length);
当然,我尝试过直接验证这个"签名"值,也尝试过对其进行哈希处理。两者都没有验证。我在计算签名的数据时做错了什么?
我在服务器端(用C++)有等效的代码,在那里我构建了相同的值,然后用OpenSSL进行验证。这个签名计算只是为了显示我在做什么——当然,我不会相信服务器端的值。
签名格式authenticatorData || clientDataJSONHash
仅用于断言(身份验证)签名,navigator.credentials.create()
返回证明的(注册)签名。您的代码应该能够验证navigator.credentials.get()
响应,但不能验证.create()
响应。
通常,证明签名不是用新创建的凭证私钥签名的,而是用验证器的证明私钥签名的。此外,签名数据的格式和验证过程由证明语句格式决定。这样做的目的是,现有的硬件可能只支持以特定的方式生成签名,因此这些不同的证明格式允许这些硬件实现与WebAuthn一起工作,而无需硬件或固件升级。
验证证明签名总是需要解析证明语句和验证器数据中的CBOR。这是因为签名是在CBOR编码的证明对象中传递的,凭证公钥是在验证器数据中传递的。尽管credential.response.getPublicKey()
也返回相同的凭证公钥,但它的这种表示形式没有签名。验证器数据由签名覆盖,因此必须从验证器数据中解析凭据公钥,签名才有意义。
如果您的应用程序不关心证明,您可以简单地不验证证明签名,而只验证未来的断言签名。您仍然可以存储证明对象,以防将来改变主意并希望关心证明。
假设从您发布的链接中,您正在尝试创建断言签名。我在文件中找到了这个-
让signature是级联authenticatorData||散列的断言签名,使用selectedCredential的privateKey,如下图4所示。在这里使用简单的、不受限制的串联是安全的,因为验证器数据描述了它自己的长度。序列化的客户端数据的哈希(可能具有可变长度)始终是最后一个元素
查看点11 下方的图像
我认为正确的方法是使用AuthData的JSON串行字符串,并将其附加到客户端数据哈希中,然后使用私钥对其进行签名。在服务器端,您可以尝试重新创建相同的结构,并使用公钥进行验证。
我不知道C++,但在python中,你可以使用一个名为密码学的包来验证签名,比如
>>> public_key.verify(
... signature,
... message,
... padding.PSS(
... mgf=padding.MGF1(hashes.SHA256()),
... salt_length=padding.PSS.MAX_LENGTH
... ),
... hashes.SHA256()
... )