46fd698ce73c

fix: Restore legacy Chrome passkey assertions (v5.2.4)

time
2026-09-06 19:32 UTC · 1 month ago
machine
koh2
parent
0a1c5debb187
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/passkey-chrome-ceremony.test.ts+21 −0src/background/passkey.ts+7 −1
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "5.2.3",4+  "version": "5.2.4",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

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.2.3",5+  "version": "5.2.4",66   "description": "Zero-knowledge password manager",77   "action": {88     "default_popup": "src/popup/index.html",

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "5.2.3",3+  "version": "5.2.4",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/background/passkey-chrome-ceremony.test.ts+21 −0
@@ -579,6 +579,27 @@579579     expect(after!.passkey!.lastUsedAt).toBeDefined();580580   });581581 582+  it("get preserves the BE flag used to register legacy Chrome passkeys", async () => {583+    const vault = await import("./vault");584+    await vault.createVault("correct-pass-12345");585+    const { entry } = await seedPasskey("github.com");586+587+    expect(entry.passkey!.browser).toBe("chrome");588+    expect(entry.passkey!.backupEligible).toBe(false);589+590+    autoApprove();591+    fireGet({ rpId: "github.com", challenge: "Y2g", allowCredentials: [] }, 206);592+    const done = await waitForGetComplete(206);593+    const response = JSON.parse(done.responseJson!);594+    const authenticatorData = Uint8Array.from(595+      atob(response.response.authenticatorData.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(response.response.authenticatorData.length / 4) * 4, "=")),596+      (char) => char.charCodeAt(0),597+    );598+599+    expect(authenticatorData[32]! & 0x08).toBe(0x08);600+    expect(vault.getEntries().find((candidate) => candidate.id === entry.id)!.passkey!.backupEligible).toBe(true);601+  });602+582603   it("get passes through to platform when the stored passkey has no private key (Firefox-created)", async () => {583604     const vault = await import("./vault");584605     await vault.createVault("correct-pass-12345");

view file

src/background/passkey.ts+7 −1
@@ -730,7 +730,12 @@730730   }731731 732732   const nextSignCount = credential.signCount + 1;733-  const flags = 0x05 | (credential.backupEligible ? 0x08 : 0) | (credential.backupState ? 0x10 : 0);733+  // Chrome passkeys have always been registered by Peach with BE=1 (0x4d),734+  // but versions before v5.0.87 incorrectly persisted backupEligible=false.735+  // WebAuthn requires BE to match registration, so repair that legacy metadata736+  // while constructing the assertion GitHub and other strict RPs verify.737+  const backupEligible = credential.browser === "chrome" ? true : credential.backupEligible;738+  const flags = 0x05 | (backupEligible ? 0x08 : 0) | (credential.backupState ? 0x10 : 0);734739   const authData = await buildAuthenticatorData({735740     rpId: credential.rpId,736741     flags,@@ -754,6 +759,7 @@754759   await vault.updateEntry(match.id, {755760     passkey: {756761       ...credential,762+      backupEligible,757763       signCount: nextSignCount,758764       lastUsedAt: Date.now(),759765     },

view file