070c26097d42
preserve Chrome passkey registration flags in Firefox assertions (v5.3.3)
- time
- 2026-10-08 15:38 UTC · 2 weeks ago
- machine
- Junes-MacBook-Pro.local
- agent
- gpt-6
- session
- repair firefox passkey sign-in
- parent
- a1cd10fe1734
↓ peach-browser-070c2609.tar.gzatlas at this save
files changed5 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/passkey-firefox-proxy.test.ts+41 −0src/background/passkey.ts+5 −6manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "5.3.2",4+ "version": "5.3.3",55 "description": "Zero-knowledge password manager",66 "browser_specific_settings": {77 "gecko": {manifest.json+1 −1
@@ -2,7 +2,7 @@22 "manifest_version": 3,33 "minimum_chrome_version": "116",44 "name": "Peach Password Manager",5- "version": "5.3.2",5+ "version": "5.3.3",66 "description": "Zero-knowledge password manager",77 "action": {88 "default_popup": "src/popup/index.html",package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "5.3.2",3+ "version": "5.3.3",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/background/passkey-firefox-proxy.test.ts+41 −0
@@ -434,6 +434,47 @@434434 ).resolves.toMatchObject({ ok: false, error: "no_credentials" });435435 });436436 437+ it.each([["chrome", true], ["firefox", false]] as const)("PASSKEY_GET preserves registration backup eligibility for a legacy %s passkey", async (browser, expectedBackupEligible) => {438+ const vault = await import("./vault");439+ await vault.createVault("correct-pass-12345");440+ const generated = await generatePasskeyKeypair(-7);441+ const credentialId = base64urlEncode(crypto.getRandomValues(new Uint8Array(32)));442+ const entry = await vault.addEntry({443+ type: "login",444+ title: "GitHub",445+ passkey: {446+ rpId: "github.com", rpName: "GitHub", credentialId,447+ publicKeyCose: base64urlEncode(generated.publicKeyCose),448+ privateKeyJwk: generated.privateKeyJwk,449+ userHandle: base64urlEncode(crypto.getRandomValues(new Uint8Array(16))),450+ userName: "june", userDisplayName: "June", algorithm: -7, signCount: 0, aaguid: "",451+ backupEligible: false, backupState: false, createdAt: 1, browser,452+ },453+ });454+ const challenge = "challenge-base64url";455+ const origin = "https://github.com";456+ const clientDataJSON = Array.from(new TextEncoder().encode(457+ JSON.stringify({ type: "webauthn.get", challenge, origin, crossOrigin: false })458+ ));459+460+ const resultPromise = dispatch({461+ type: "PASSKEY_GET",462+ payload: { rpId: "github.com", challenge, origin, clientDataJSON, allowCredentials: [{ id: credentialId, type: "public-key" }] },463+ });464+ const passkey = await import("./passkey");465+ await vi.waitFor(async () => {466+ expect(await passkey.getPendingPasskeyRequest()).not.toBeNull();467+ });468+ const pending = await passkey.getPendingPasskeyRequest();469+ await passkey.resolvePendingPasskeyRequest(pending!.requestId, true);470+ const result = await resultPromise;471+ expect(result.ok).toBe(true);472+ const assertion = (result as unknown as { credential: { authenticatorData: string } }).credential;473+ const authenticatorData = Uint8Array.from(atob(assertion.authenticatorData.replace(/-/g, "+").replace(/_/g, "/")), (char) => char.charCodeAt(0));474+ expect(Boolean(authenticatorData[32]! & 0x08)).toBe(expectedBackupEligible);475+ expect(vault.getEntries().find((candidate) => candidate.id === entry.id)!.passkey!.backupEligible).toBe(expectedBackupEligible);476+ });477+437478 it("PASSKEY_CREATE registers a passkey and increments signCount on PASSKEY_GET", async () => {438479 const vault = await import("./vault");439480 await vault.createVault("correct-pass-12345");src/background/passkey.ts+5 −6
@@ -1308,12 +1308,10 @@13081308 if (!approved) return { ok: false, error: "user_cancelled" };13091309 13101310 const nextSignCount = credential.signCount + 1;1311- // 0x05 = UP|UV. backup bits track whatever was set at registration time1312- // (consistent with the Chrome path).1313- const flags =1314- 0x05 |1315- (credential.backupEligible ? 0x08 : 0) |1316- (credential.backupState ? 0x10 : 0);1311+ // Legacy Chrome registrations used BE=1 even when the stored flag was false.1312+ // Legacy Firefox registrations used BE=0, so retain their recorded value.1313+ const backupEligible = credential.browser === "chrome" ? true : credential.backupEligible;1314+ const flags = 0x05 | (backupEligible ? 0x08 : 0) | (credential.backupState ? 0x10 : 0);13171315 const authenticatorData = await buildAuthenticatorData({13181316 rpId: credential.rpId,13191317 flags,@@ -1337,6 +1335,7 @@13371335 await vault.updateEntry(match.id, {13381336 passkey: {13391337 ...credential,1338+ backupEligible,13401339 signCount: nextSignCount,13411340 lastUsedAt: Date.now(),13421341 },