我正在尝试实现Webauthn,但是在使签名验证正常工作方面遇到了麻烦。根据{{3}},我必须对以下数据进行基本验证:
authData || sha256(clientDataJSON)
authData和sha256哈希应为“二进制级联”。我不知道它们的确切含义,但是我认为它们只是意味着将字节紧挨着,不知道到底是什么“二进制”。
因此,给定名为证明的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进行验证。这种签名的计算只是为了显示我在做什么-我当然不会相信服务器端的价值。
答案 0 :(得分:0)
假设您从发布的链接中尝试创建断言签名。我在文档中找到了-
让签名为串联authenticatorData ||的断言签名。使用selectedCredential的privateKey进行散列,如下图4所示。在这里可以安全地使用简单的,无限制的串联,因为身份验证器数据描述了它自己的长度。序列化客户端数据(可能具有可变长度)的哈希始终是最后一个元素。
查看下面的图片Point 11
我认为正确的方法是使用AuthData的JSON序列化字符串并将其附加到Client Data Hash并使用私钥对其进行签名。在服务器端,您可以尝试重新创建相同的结构,并使用公共密钥对其进行验证。
我不了解C ++,但是在python中,您可以使用一个名为cryptography的软件包并像这样验证签名-
>>> public_key.verify(
... signature,
... message,
... padding.PSS(
... mgf=padding.MGF1(hashes.SHA256()),
... salt_length=padding.PSS.MAX_LENGTH
... ),
... hashes.SHA256()
... )
答案 1 :(得分:0)
签名格式 authenticatorData || clientDataJSONHash
仅用于 assertion(身份验证)签名,而 navigator.credentials.create()
返回一个 attestation(注册)签名。您的代码应该用于验证 navigator.credentials.get()
响应,但不能用于验证 .create()
响应。
一般来说,证明签名不是用新创建的 credential private key 签名的,而是用验证者的 attestation private key 签名的。此外,签名数据的格式以及验证它的程序由 attestation statement format 决定。这样做的目的是现有硬件可能仅支持以特定方式生成签名,因此这些不同的证明格式允许这些硬件实现无需硬件或固件升级即可与 WebAuthn 配合使用。
验证证明签名始终需要解析 attestation statement 和 authenticator data 中的 CBOR。这是因为签名是在 CBOR 编码的证明对象中传递的,而凭证公钥是传递 in the authenticator data。尽管 credential.response.getPublicKey()
也返回相同的凭证公钥,但它的这种表示没有签名。身份验证器数据由签名覆盖,因此凭证公钥必须是 parsed from the authenticator data 才能使签名有意义。
如果您的应用程序不关心证明,您可以简单地不验证证明签名,而只验证未来的断言签名。您仍然可以存储 attestation object,以防您改变主意并希望将来关注认证。