c67f9103ca9a

fix signed revocation identifier bounds and release Browser 5.2.55

time
2026-09-12 12:17 UTC · 1 month ago
machine
koh2
agent
camel-stream/auto
session
close revocation bounds
parent
53d1a99ff53f
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/shared/device-manifest.test.ts+44 −1src/shared/device-manifest.ts+12 −7
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "5.2.54",4+  "version": "5.2.55",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.54",5+  "version": "5.2.55",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.54",3+  "version": "5.2.55",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/shared/device-manifest.test.ts+44 −1
@@ -131,6 +131,49 @@131131     } as DeviceManifestError);132132   });133133 134+  it("rejects oversized revocation identifiers", async () => {135+    const m = await buildSignedDeviceManifest(136+      ISSUER_DEVICE_ID,137+      [entry()],138+      signingPrivateKey,139+      {140+        revoked: [141+          {142+            deviceId: "x".repeat(257),143+            longTermPublicKeyHex: "c".repeat(64),144+            revokedAt: Date.now(),145+            revokedByDeviceId: ISSUER_DEVICE_ID,146+            recordVersion: DEVICE_REVOCATION_RECORD_VERSION,147+          },148+        ],149+      },150+    );151+    await expect(verifyDeviceManifest(m, signingPubkey)).rejects.toMatchObject({152+      kind: "malformed",153+    } as DeviceManifestError);154+  });155+156+  it("rejects oversized revocation issuer identifiers", async () => {157+    const m = await buildSignedDeviceManifest(158+      ISSUER_DEVICE_ID,159+      [entry()],160+      signingPrivateKey,161+      {162+        revoked: [163+          {164+            deviceId: PEER_DEVICE_ID,165+            longTermPublicKeyHex: "c".repeat(64),166+            revokedAt: Date.now(),167+            revokedByDeviceId: "x".repeat(257),168+            recordVersion: DEVICE_REVOCATION_RECORD_VERSION,169+          },170+        ],171+      },172+    );173+    await expect(verifyDeviceManifest(m, signingPubkey)).rejects.toMatchObject({174+      kind: "malformed",175+    } as DeviceManifestError);176+  });134177   it("derives the same keypair across calls (determinism)", async () => {135178     const kp2 = await deriveSyncSigningKeypair(MASTER_KEY, ISSUER_DEVICE_ID);136179     expect(Array.from(kp2.publicKeyRaw)).toEqual(Array.from(signingPubkey));@@ -185,8 +228,8 @@185228       }),186229     ).toBe(CANONICAL);187230   });188-});189231 232+});190233 describe("canonicalise — null fields are omitted (cross-platform parity)", () => {191234   // Android's manifestEntryMap only inserts wsPort / relayUrl when they are192235   // non-null. Match that on the browser side so the manifest signed by

view file

src/shared/device-manifest.ts+12 −7
@@ -251,8 +251,12 @@251251     throw new DeviceManifestError("malformed", "revoked is not an array");252252   }253253   for (const r of manifest.body.revoked) {254-    if (typeof r.deviceId !== "string" || !r.deviceId) {255-      throw new DeviceManifestError("malformed", "revocation.deviceId missing");254+    if (255+      typeof r.deviceId !== "string" ||256+      !r.deviceId ||257+      r.deviceId.length > MAX_MANIFEST_STRING258+    ) {259+      throw new DeviceManifestError("malformed", "revocation.deviceId invalid");256260     }257261     if (258262       typeof r.longTermPublicKeyHex !== "string" ||@@ -266,11 +270,12 @@266270     if (typeof r.revokedAt !== "number" || !Number.isFinite(r.revokedAt)) {267271       throw new DeviceManifestError("malformed", "revocation.revokedAt invalid");268272     }269-    if (typeof r.revokedByDeviceId !== "string" || !r.revokedByDeviceId) {270-      throw new DeviceManifestError(271-        "malformed",272-        "revocation.revokedByDeviceId missing",273-      );273+    if (274+      typeof r.revokedByDeviceId !== "string" ||275+      !r.revokedByDeviceId ||276+      r.revokedByDeviceId.length > MAX_MANIFEST_STRING277+    ) {278+      throw new DeviceManifestError("malformed", "revocation.revokedByDeviceId invalid");274279     }275280     if (276281       r.recordVersion != null &&

view file