2

我正在尝试实现 webauthn,但无法让签名验证正常工作。根据https://w3c.github.io/webauthn/#verifying-assertion我必须基本上验证以下数据的签名:

身份验证数据 || sha256(clientDataJSON)

authData 和 sha256 哈希应该是“二进制连接的”。我不知道它们到底是什么意思,但我认为它们只是意味着将字节彼此相邻,但不知道究竟是什么“二进制”。

因此,给定一个名为 attestation 的 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 验证它。这个签名的计算只是为了显示我在做什么——我当然不会相信服务器端的那个值。

4

2 回答 2

0

签名格式authenticatorData || clientDataJSONHash仅用于断言(认证)签名,并navigator.credentials.create()返回一个证明(注册)签名。您的代码应该用于验证navigator.credentials.get()响应,但不能用于验证响应.create()

通常,证明签名不是用新创建的凭证私钥签名的,而是用认证者的证明私钥签名的。此外,签名数据的格式和验证程序由证明声明格式决定。这样做的目的是现有的硬件可能只支持以特定方式生成签名,因此这些不同的证明格式允许这些硬件实现与 WebAuthn 一起工作,而无需硬件或固件升级。

验证证明签名始终需要解析证明声明验证者数据中的 CBOR 。这是因为签名是在 CBOR 编码的证明对象中传递的,而凭证公钥是在验证者数据中传递的。尽管credential.response.getPublicKey()也返回相同的凭证公钥,但它的这种表示是未签名的。验证者数据被签名所覆盖,因此必须从验证者数据中解析出凭证公钥,才能使签名有意义。

如果您的应用程序不关心证明,您可以简单地不验证证明签名,而只验证未来的断言签名。您仍然可以存储证明对象,以防您改变主意并希望将来关心证明。

于 2021-05-12T14:17:03.167 回答
0

假设从您发布的链接中,您正在尝试创建断言签名。我在文件中找到了这个——

令签名为连接authenticatorData的断言签名|| 使用 selectedCredential 的 privateKey 进行散列,如下面的图 4 所示。在这里使用简单的、无定界的连接是安全的,因为身份验证数据描述了它自己的长度。序列化客户端数据的哈希(可能具有可变长度)始终是最后一个元素。

看下图第 11 点

我认为正确的方法是使用 AuthData 的 JSON 序列化字符串并将其附加到客户端数据哈希并使用私钥对其进行签名。在服务器端,您可以尝试重新创建相同的结构并使用公钥对其进行验证。

我不了解 C++,但在 python 中,您可以使用一个名为 cryptography 的包并像这样验证签名 -

>>> public_key.verify(
...     signature,
...     message,
...     padding.PSS(
...         mgf=padding.MGF1(hashes.SHA256()),
...         salt_length=padding.PSS.MAX_LENGTH
...     ),
...     hashes.SHA256()
... )
于 2019-07-12T03:30:05.510 回答