You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
you must retrieve the PublicKey by Credential ID or Challenge on Authentication (3)
(3) a.k.a. attestation, webauthn.get
the WebAuthn (COSE) signature is in ASN.1 format, but WebCrypto requires it as p1363
(i.e. if you use WebCrypto in Node or Bun or Deno)
attToBeSigned is the concatenation of authData and the sha256 hash of clientData
challenge
you must generate the challenge server side
otherwise an attacker with device access could generate many signed challenges and use them in the future
DO NOT verify the signature alone, you MUST VERIFY the challenge
otherwise an attacker who gains access to the device can possibly create lots and lots of authentications and use them on future dates.
if signCount > 0 then you should track the sign count and make sure that each new sign count is ALWAYS greater than the previous one (savedCount = newCount; db.save();
Only save the new signCountAFTER successful signature verification - otherwise an attacker can present a very high signCount, have it saved, and then lock the user out of their account.
userHandle
Don't send userHandle to the server. It's a misnomer. It's actually arbitrary user data. It can be used to store a local encryption key, for example.
Keep it secret. Keep it safe. Keep it client-side.
Note: in at least one place it's referred to as "id". This is incorrect. The "id" is the "credential id" and the remote user id is "user.name".
bottom line: rate limit the retrieval of credential ids by ip address or other means so that an attacker can't just scan email addresses, and don't include any metadata such as created_at or user agent, etc with the credential ids you return
These are more used to identify the Authenticator than the specific Credential.
If you fail to provide these on new Passkey registration, you could easily permanently delete and replace an existing Passkey (i.e. iCloud only allows one Passkey per domain and will delete rather than add).
Since they are required before the user is authenticated in order to authenticate the user, they are essentially public values.
However, they also leak a small amount of statistical information about the user - the format of the ID may fingerprint which device or service is being used (by length, character distribution or prefix or suffix). Also, they show how many authenticators a user has.
So there's a bit of a catch 22:
asking the user to try to authenticate and then register on failure is cumbersome and annoying
if the user has cleared out their browser application cache or is on a different computer, you don't know if they have an authenticator (i.e. "silent" authentication doesn't actually work in practice)
if you request the credential ids by email address, there's a small amount of info that an attacker can farm by looking up these ids in your system by email or whatever - and you obviously can't mask them otherwise you can't use them
Relying Party
In theory you should check that the domain or domain hash matches (rp.id / rpid / relying party / rpidhash) matches, but in practice, this doesn't actually matter because only a malicious device or browser plugin would be sharing your private key between accounts internally, at which point you've got other problems.
Authenticator devices or services or plugins that you can get from legitimate vendors (Apple, Yubico, Microsoft, Android, Lastpass, etc), all create pairwise Credential IDs - meaning that the ID has a unique constraint across domain and user id user accounts - so you're not going to get an "attestation" that's signed with the wrong domain.
The theoretical vulnerability requires that the device or service that uses a single Public Key and a single Credential ID to sign attestations for many domains, and that the user has given such as signed attestation to a malicious domain which then relays to your domain.
Also, the cross-origin spec, as of today (Oct 2024), does not exist. It's mentioned that there may be one in the future, and there's a cross-origin flag reserved for that use, but it's not spec'd or implemented.
etc
WebAuthn is chuck full of YAGNI.
There are so many different representations of similar data, so many things that will obviously never actually get implemented, and actual implementations lacking many things that we wish they would have (i.e. seamless background login, JSON).
Don't dig too deep. As Douglas Crockford warns: almost anything can be useful - just look at Rube Goldberg machines - but that doesn't mean it's a good idea.
Stick to the core things you need. Ignore the rest until the use case arises.
You could lose your life trying to build a framework that finds utility for every possible nook and cranny of the spec.
The text was updated successfully, but these errors were encountered:
The major security considerations
Signature Verification
You must verify signatures. You cannot rely on the challenge alone.
.id
,.rawId
,response.authenticator.attestedCredential.credentialId
webauthn.create
webauthn.get
(i.e. if you use WebCrypto in Node or Bun or Deno)
attToBeSigned
is the concatenation ofauthData
and the sha256 hash ofclientData
challenge
otherwise an attacker with device access could generate many signed challenges and use them in the future
otherwise an attacker who gains access to the device can possibly create lots and lots of authentications and use them on future dates.
Sign Count
Sign count mitigates hardware cloning (e.g. YubiKey) and out-of-sync / offline / system-time-faked / cloned cloud authenticators (e.g. Apple iCloud Keychain).
if signCount > 0
then you should track the sign count and make sure that each new sign count is ALWAYS greater than the previous one (savedCount = newCount; db.save();
Only save the new
signCount
AFTER successful signature verification - otherwise an attacker can present a very high signCount, have it saved, and then lock the user out of their account.userHandle
Don't send
userHandle
to the server. It's a misnomer. It's actually arbitrary user data. It can be used to store a local encryption key, for example.Keep it secret. Keep it safe. Keep it client-side.
Note: in at least one place it's referred to as "id". This is incorrect. The "id" is the "credential id" and the remote user id is "user.name".
See also: mylofi/local-data-lock#7
Credential IDs
bottom line: rate limit the retrieval of credential ids by ip address or other means so that an attacker can't just scan email addresses, and don't include any metadata such as created_at or user agent, etc with the credential ids you return
These are more used to identify the Authenticator than the specific Credential.
If you fail to provide these on new Passkey registration, you could easily permanently delete and replace an existing Passkey (i.e. iCloud only allows one Passkey per domain and will delete rather than add).
Since they are required before the user is authenticated in order to authenticate the user, they are essentially public values.
However, they also leak a small amount of statistical information about the user - the format of the ID may fingerprint which device or service is being used (by length, character distribution or prefix or suffix). Also, they show how many authenticators a user has.
So there's a bit of a catch 22:
Relying Party
In theory you should check that the domain or domain hash matches (rp.id / rpid / relying party / rpidhash) matches, but in practice, this doesn't actually matter because only a malicious device or browser plugin would be sharing your private key between accounts internally, at which point you've got other problems.
Authenticator devices or services or plugins that you can get from legitimate vendors (Apple, Yubico, Microsoft, Android, Lastpass, etc), all create pairwise Credential IDs - meaning that the ID has a unique constraint across domain and user id user accounts - so you're not going to get an "attestation" that's signed with the wrong domain.
The theoretical vulnerability requires that the device or service that uses a single Public Key and a single Credential ID to sign attestations for many domains, and that the user has given such as signed attestation to a malicious domain which then relays to your domain.
Also, the cross-origin spec, as of today (Oct 2024), does not exist. It's mentioned that there may be one in the future, and there's a cross-origin flag reserved for that use, but it's not spec'd or implemented.
etc
WebAuthn is chuck full of YAGNI.
There are so many different representations of similar data, so many things that will obviously never actually get implemented, and actual implementations lacking many things that we wish they would have (i.e. seamless background login, JSON).
Don't dig too deep. As Douglas Crockford warns: almost anything can be useful - just look at Rube Goldberg machines - but that doesn't mean it's a good idea.
Stick to the core things you need. Ignore the rest until the use case arises.
You could lose your life trying to build a framework that finds utility for every possible nook and cranny of the spec.
The text was updated successfully, but these errors were encountered: