Skip to content

task(db): Add passkeyWraps table - #20958

Open
nshirley wants to merge 1 commit into
mainfrom
FXA-13138
Open

task(db): Add passkeyWraps table#20958
nshirley wants to merge 1 commit into
mainfrom
FXA-13138

Conversation

@nshirley

@nshirley nshirley commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Because

  • Passkey-based sync sign-in needs somewhere to persist the PRF-wrapped recovery
    keypair and the HPKE-sealed kB, one envelope per passkey. This is the storage
    model from the technical spec §2.2.5.
  • The rest of the P2 key-wrapping work (data model, endpoints, HPKE utilities)
    builds on this table, so the schema needs to land first.

This pull request

  • Adds packages/db-migrations/databases/fxa/patches/patch-194-195.sql, creating
    passkeyWraps keyed on (uid, credentialId). Every payload column is
    fixed-length and sized from the v1 ciphersuite
    (DHKEM(P-521, HKDF-SHA512) / HKDF-SHA512 / AES-256-GCM).
  • Uses a composite ON DELETE CASCADE foreign key to passkeys(uid, credentialId),
    so deleting a passkey drops its envelope. Account deletion needs no procedure
    change: deleteAccount_24 already deletes from passkeys before accounts, and
    wraps cascade with that delete.
  • Adds the patch-195-194.sql rollback and bumps target-patch.json to 195.

Issue that this pull request solves

Closes: FXA-13138

Checklist

Put an x in the boxes that apply

  • My commit is GPG signed.
  • If applicable, I have modified or added tests which pass locally.
  • I have added necessary documentation (if appropriate).
  • I have verified that my changes render correctly in RTL (if appropriate).
  • I have manually reviewed all AI generated code.

How to review (Optional)

  • Key files/areas to focus on: patch-194-195.sql — specifically that uid,
    credentialId, and the timestamp types match passkeys exactly, since the
    composite FK cannot be created otherwise.
  • Suggested review order: forward patch, then the rollback, then the target-patch bump.
  • Risky or complex parts: the cascade behaviour is the load-bearing part. It is what
    lets this land without touching deleteAccount_24.

Screenshots (Optional)

Please attach the screenshots of the changes made in case of change in user interface.

Other information (Optional)

Verified against a local DB. packages/db-migrations has no automated schema
assertions, so these checks are manual:

  • Forward migration applies at level 194 and lands the schema above; reverse drops the
    table and returns the level to 194; forward then re-applies cleanly.
  • Deleting one of two passkeys removed exactly that passkey's wrap, leaving the sibling
    wrap intact.
  • CALL deleteAccount_24(uid, reason) succeeded with a wrap present — account,
    passkeys, and wraps all removed, deletionReason recorded — with no change to the
    procedure.

Purely additive, and no application code reads the table yet, so this must deploy ahead
of the data model that consumes it.

@nshirley
nshirley marked this pull request as ready for review August 3, 2026 17:36
@nshirley
nshirley requested a review from a team as a code owner August 3, 2026 17:36
Copilot AI review requested due to automatic review settings August 3, 2026 17:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new FxA DB migration level (195) introducing a passkeys_wraps table intended to store per-passkey key-wrapping “envelopes” (PRF-wrapped recovery keypair material and HPKE-sealed kB) and wiring it to passkeys via a composite ON DELETE CASCADE FK so passkey deletion also deletes its wrap.

Changes:

  • Add forward migration patch-194-195.sql to create passkeys_wraps with (uid, credentialId) primary key and a composite cascading FK to passkeys(uid, credentialId).
  • Add rollback migration patch-195-194.sql (commented-out rollback steps, consistent with existing rollback patch style).
  • Bump DB schema target level from 194 → 195.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
packages/db-migrations/databases/fxa/patches/patch-194-195.sql Creates the new wraps table and advances schema patch level to 195.
packages/db-migrations/databases/fxa/patches/patch-195-194.sql Provides the rollback patch steps (commented), returning schema patch level to 194.
packages/db-migrations/databases/fxa/target-patch.json Updates the target schema patch level to 195.

Comment thread packages/db-migrations/databases/fxa/patches/patch-194-195.sql Outdated
Comment thread packages/db-migrations/databases/fxa/patches/patch-195-194.sql Outdated
Comment on lines +12 to +16
pkR VARBINARY(512) NOT NULL, -- v1: 133B, uncompressed P-521 point
prfWrappedSkR VARBINARY(512) NOT NULL, -- v1: 257B, AES-256-GCM(pkcs8 skR) + tag
keyWrapIv BINARY(12) NOT NULL, -- AES-GCM nonce
hpkeEncapsulatedSecret VARBINARY(512) NOT NULL, -- v1: 133B, RFC 9180 Nenc for DHKEM(P-521)
hpkeSealedKb VARBINARY(512) NOT NULL, -- v1: 48B, 32B kB + tag

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking was that we give a little bit of wiggle room so we're less likely to hit issues where the write is rejected because the size changes, but I'm unsure if that's even possible for the size to change. If it's not, or it shouldn't change, than I can make it more strict

@dschom dschom Aug 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IUC, those sizes are fixed and based on the crypto libraries. If this is infact true, and we potentially have a bunch of records here, we should make them the exact size. Also use binary instead of varbinary. This is consistent with how columns that hold wrapped kbs work.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure obviously, but thanks for confirming! I'll get that updated

@nshirley
nshirley force-pushed the FXA-13138 branch 2 times, most recently from c0f8db3 to bb72fb9 Compare August 3, 2026 19:01
@nshirley nshirley changed the title task(db): Add passkeys_wraps table task(db): Add passkeyWraps table Aug 3, 2026
@nshirley
nshirley force-pushed the FXA-13138 branch 2 times, most recently from 804e7a8 to 3e293be Compare August 5, 2026 22:34
-- opaque. Matches the passkeys table.
credentialId VARBINARY(1023) NOT NULL,
pkR BINARY(133) NOT NULL, -- uncompressed P-521 point: 0x04 || x(66) || y(66)
prfWrappedSkR BINARY(257) NOT NULL, -- AES-256-GCM(pkcs8 skR, 241B) + 16B tag

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an alternative to consider here - The panva hpke library we're considering has a DeserializePrivateKey helper we may want to use, and it would make prfWrappedSkR 82 bytes instead of 257 (raw 66-byte scalar). Two things favour it for a format we can't change later: 66 is a spec constant while 241 rests on an optional DER field, and it's the only option with a post-quantum path. It also derives the public point itself, so the pkR slicing risk doesn't apply.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion - I did a bit of digging and would agree it makes sense; dropping the extras out of the PKCS container saves space and enforces a stronger contract between front and back end. I've made the adjustment

Because:

* Passkey-based sync sign-in needs somewhere to store the PRF-wrapped
  recovery keypair and the HPKE-sealed kB, one envelope per passkey.

This commit:

* Adds patch-194-195.sql creating passkeyWraps, keyed on
  (uid, credentialId) with an ON DELETE CASCADE FK to passkeys so wraps
  are removed with their passkey. deleteAccount_24 needs no change.
* Sizes each crypto column as fixed-width BINARY holding exactly what the
  v1 ciphersuite emits. skR is stored as the raw 66-byte scalar (Nsk)
  rather than a pkcs8 export, so every length is a ciphersuite constant:
  pkcs8's 241 bytes rest on Web Crypto emitting an OPTIONAL public-key
  field, and a conforming implementation that omitted it would be
  zero-padded into the fixed column and never open again.
* Keeps credentialId as VARBINARY(1023), matching passkeys: WebAuthn
  credential IDs are authenticator-chosen and genuinely variable.
* Adds the patch-195-194.sql rollback and bumps target-patch to 195.
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.

4 participants