3

I'm trying to implement webauthn, but am having trouble getting the signature verification to work. According to https://w3c.github.io/webauthn/#verifying-assertion I have to basically verify the signature over the following data:

authData || sha256(clientDataJSON)

The authData and the sha256 hash should be "binary concatenated". I have no idea what they mean with that, exactly, but I assume they just mean to stick the bytes next to each other, no idea what exactly would be "binary" about that though.

So, given a PublicKeyCredential named attestation, I can generate the data over which the signature is generated as follows:

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);

I have of course tried validating this 'signed' value directly, and I have tried hashing it as well. Neither of them validate. What am I doing wrong in calculating the data which is signed?

I have the equivalent code on the server side (in C++) where I build the same value and then verify it with OpenSSL. This signed calculation is only to show what I'm doing - I won't trust that value server-side of course.

Martijn Otto
  • 878
  • 4
  • 21
  • Having the same problem here is a Gist that shows my attempt to do the same as above. Paste the code in the console to run: https://gist.github.com/philholden/50120652bfe0498958fd5926694ba354 – Phil Holden May 12 '21 at 10:00

2 Answers2

1

The signature format authenticatorData || clientDataJSONHash is only for assertion (authentication) signatures, and navigator.credentials.create() returns an attestation (registration) signature. Your code should work for verifying navigator.credentials.get() responses, but not .create() ones.

Attestation signatures are, in general, not signed with the newly created credential private key, but with the authenticator's attestation private key. In addition, the format of the signed data, and the procedure for verifying it, is determined by the attestation statement format. The purpose of this is that already existing hardware might only support generating signatures in a particular way, so these different attestation formats allow those hardware implementations to work with WebAuthn without a hardware or firmware upgrade.

Verifying an attestation signature always requires parsing the CBOR in both the attestation statement and the authenticator data. This is because the signature is delivered in the CBOR-encoded attestation object, and the credential public key is delivered in the authenticator data. Although credential.response.getPublicKey() also returns the same credential public key, this representation of it is not signed. The authenticator data is covered by the signature, so the credential public key must be parsed from the authenticator data for the signature to be meaningful.

If your application does not care about attestation, you can simply not verify the attestation signature and instead only verify future assertion signatures. You can still store the attestation object in case you change your mind and want to care about attestation in the future.

Emil Lundberg
  • 7,268
  • 6
  • 37
  • 53
0

Assuming, from the link that you posted, that you are trying to create assertion signature. I found this in the documents -

Let signature be the assertion signature of the concatenation authenticatorData || hash using the privateKey of selectedCredential as shown in Figure 4, below. A simple, undelimited concatenation is safe to use here because the authenticator data describes its own length. The hash of the serialized client data (which potentially has a variable length) is always the last element.

look at the image below Point 11

I think the correct way would be to use JSON serialised string of AuthData and append it to Client Data Hash and sign it using the private key. On the server side you can try to recreate the same structure and verify it using the public key.

I don't know about C++ but in python you can use a package called cryptography and verify the signature like this-

>>> public_key.verify(
...     signature,
...     message,
...     padding.PSS(
...         mgf=padding.MGF1(hashes.SHA256()),
...         salt_length=padding.PSS.MAX_LENGTH
...     ),
...     hashes.SHA256()
... )
Laksh Matai
  • 176
  • 1
  • 5