feat(webauthn): passkeys in the vault — dcrypt as a software authenticator - #26
Merged
Conversation
…cator A passkey is a P-256 keypair, not a stored password: @decryption/webauthn mints one, keeps the private half as a concealed vault field, and signs the challenge a site issues over authenticatorData || sha256(clientDataJSON). `dcrypt passkey register|list|assert|forget` drives it from the CLI, printing the JSON a WebAuthn relying party expects, so it can be piped into one. Attestation is fmt "none" with an all-zero AAGUID — this is software and says so; a relying party demanding hardware attestation should reject it.
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A passkey is not a stored password — it is a P-256 keypair, and holding keys in an encrypted store is precisely what a platform authenticator is. So dcrypt becomes one:
@decryption/webauthnmints the key, keeps the private half as a concealed vault field, and signs the challenge a site issues.The signed bytes are the whole point:
The origin and the RP id hash are inside what is signed, so a site cannot obtain a signature naming a different origin — that is the unphishable property, and it is why nothing here can be replayed elsewhere.
verifyAssertionchecks the signature the way a relying party would, so the round trip is actually proved rather than asserted.Attestation is
fmt: "none"with an all-zero AAGUID. This is software; claiming hardware attestation is the exact lie attestation exists to catch, and a relying party demanding it should reject us.PasskeyStoreputs the key in the vault as a newpasskeyitem kind, concealing only the private key — a site name and a sign count are not secrets, and leaving them readable is what lets the list render without decrypting anything:rp_id,credential_id,user_handle,user_name,sign_countprivate_keyassert()persists the advanced sign count before returning, because a site that later sees the count fail to advance is entitled to conclude the key was cloned — losing that write is worse than losing the assertion.From the CLI:
The challenge is required rather than generated — signing a challenge we invented ourselves proves nothing. It is allowed in argv, unlike a password: it is a public nonce.
Not in this PR: the relying-party half against constructive-db's
auth:passkeyprocedures (webauthn_begin_registration/finish_registration/begin_sign_in/finish_sign_in), and the desktop UI. Those procedures are deliberately crypto-free and are currently tested with opaque-but-plausible bytes, so this is the piece that will let them be exercised with real signatures for the first time.One infrastructure note: adding a workspace package forces pnpm to re-resolve, which trips
minimumReleaseAgeon a transitivews@8.21.3that the lockfile already pins (published 41h ago, gate is 48h). Rather than weaken the policy, the new importers were added topnpm-lock.yamldirectly — the resolution step is then skipped and the install is a no-op, as onmain.Link to Devin session: https://app.devin.ai/sessions/04636534e07048089ffb6b78142e12cd
Requested by: @pyramation