Skip to content

fix(passkey-crypto): use base64url everywhere for the PRF salt#8715

Open
derranW26 wants to merge 2 commits intomasterfrom
WCN-410/passkey-crypto-base64url-salt
Open

fix(passkey-crypto): use base64url everywhere for the PRF salt#8715
derranW26 wants to merge 2 commits intomasterfrom
WCN-410/passkey-crypto-base64url-salt

Conversation

@derranW26
Copy link
Copy Markdown
Contributor

The attach and derive flows were feeding different bytes to the WebAuthn PRF extension for the same passkey, so the password derived at attach time could not decrypt the keychain blob written with that same password (ccm tag mismatch on every transaction approval).

Three encoding inconsistencies caused this:

  • deriveEnterpriseSalt returned hex while the server stores base64url and the WebAuthn extension expects raw bytes — every consumer had to re-encode, and one of them did it wrong.
  • attachPasskeyToWallet ran the hex output back through a hex-to-base64url conversion before handing it to provider.get, so the browser PRF saw the hex characters interpreted as base64 garbage.
  • prfHelpers.buildEvalByCredential tried to convert the stored base64url salt to hex via Buffer.from(...).toString('hex'), which is a no-op under the browser Buffer polyfill and a real conversion in Node — same code, different bytes.

Standardise on base64url end-to-end: deriveEnterpriseSalt emits base64url, attachPasskeyToWallet passes that string straight through to the PRF eval, and prfHelpers reads device.prfSalt unchanged. The WebAuthn provider layer is the single point that decodes base64url to bytes for navigator.credentials.get.

Refs: WCN-410

TICKET: WCN-410

The attach and derive flows were feeding different bytes to the WebAuthn
PRF extension for the same passkey, so the password derived at attach
time could not decrypt the keychain blob written with that same password
(ccm tag mismatch on every transaction approval).

Three encoding inconsistencies caused this:

- deriveEnterpriseSalt returned hex while the server stores base64url
  and the WebAuthn extension expects raw bytes — every consumer had to
  re-encode, and one of them did it wrong.
- attachPasskeyToWallet ran the hex output back through a
  hex-to-base64url conversion before handing it to provider.get, so
  the browser PRF saw the hex characters interpreted as base64 garbage.
- prfHelpers.buildEvalByCredential tried to convert the stored
  base64url salt to hex via Buffer.from(...).toString('hex'), which is
  a no-op under the browser Buffer polyfill and a real conversion in
  Node — same code, different bytes.

Standardise on base64url end-to-end: deriveEnterpriseSalt emits
base64url, attachPasskeyToWallet passes that string straight through
to the PRF eval, and prfHelpers reads device.prfSalt unchanged. The
WebAuthn provider layer is the single point that decodes base64url to
bytes for navigator.credentials.get.

Refs: WCN-410

TICKET: WCN-410
@derranW26 derranW26 requested review from a team as code owners May 7, 2026 23:51
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 7, 2026

WCN-410

Comment thread modules/passkey-crypto/src/deriveEnterpriseSalt.ts Outdated
Replace the duplicated `.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')`
pattern (and its inverse) with a small `base64url` utility module shared by
deriveEnterpriseSalt, prfHelpers, registerPasskey, and attachPasskeyToWallet.

Addresses PR review feedback to deduplicate the encoding logic.

TICKET: WCN-410
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants