9346444bdaff

fix: small-order DH guard compares the leading 32 bytes of snow's 56-byte MAXDHLEN buffer — the strict try_from never fired on real handshakes (found by the Android parity test); regression test now drives all nine blocklist entries through the KK handshake plus a padded-buffer case, WASM rebuilt; bump 5.0.16→5.0.17

time
2026-08-30 02:32 UTC · 1 month ago
machine
koh2
agent
glm-5.3-flash
session
small-order dh rejection
parent
282a941beb44
6 files crypto-core/src/lib.rs+33 −11manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/shared/noise-core-wasm/peach_noise_core_bg.wasmbinarysrc/shared/noise-core-wasm/peach_noise_core_bytes.ts+2 −2
crypto-core/src/lib.rs+33 −11
@@ -426,9 +426,16 @@426426 ];427427 428428 fn is_small_order_x25519_pubkey(public_key: &[u8]) -> bool {429-    let Ok(key) = <[u8; 32]>::try_from(public_key) else {429+    // snow 0.10 passes the remote static inside its 56-byte MAXDHLEN buffer430+    // (32 key bytes + 24 trailing zeros), so a strict 32-byte try_from always431+    // fails and the guard would never fire on a real handshake. Compare the432+    // leading 32 bytes whenever a full key is present.433+    let Some(key) = public_key.get(..32) else {430434         return false;431435     };436+    let Ok(key) = <[u8; 32]>::try_from(key) else {437+        return false;438+    };432439     X25519_SMALL_ORDER_PUBKEYS433440         .iter()434441         .any(|low| low.ct_eq(&key).unwrap_u8() == 1)@@ -802,24 +809,39 @@802809 803810     // PB-26-014: contributory behaviour — a small-order remote static must804811     // fail the handshake at the DH step instead of contributing a constant805-    // low-entropy secret to the keying material.812+    // low-entropy secret to the keying material. Drive ALL nine entries:813+    // seven have an all-zero DH output (the pre-existing check catches them)814+    // but two produce non-zero output and only this guard rejects them.806815     #[test]807816     fn small_order_remote_static_fails_handshake() {808817         let (local_private, local_public) = keypair(11);809-        let mut initiator = CoreNoiseSession::kk_initiator(810-            &local_private,811-            &local_public,812-            &X25519_SMALL_ORDER_PUBKEYS[3],813-        )814-        .unwrap();815-        let result = initiator.write_handshake(&[]);816-        assert!(result.is_err());817-        // Every small-order key is recognized by the guard; real keys never are.818818         for low in X25519_SMALL_ORDER_PUBKEYS.iter() {819+            let mut initiator =820+                CoreNoiseSession::kk_initiator(&local_private, &local_public, low).unwrap();821+            assert!(822+                initiator.write_handshake(&[]).is_err(),823+                "handshake against small-order static must fail"824+            );819825             assert!(is_small_order_x25519_pubkey(low));820826         }821827         let (_, real_public) = keypair(9);822828         assert!(!is_small_order_x25519_pubkey(&real_public));829+    }830+831+    // snow hands the guard the remote static inside its 56-byte MAXDHLEN832+    // buffer (32 key bytes + 24 trailing zeros) — the guard must compare the833+    // leading 32 bytes, not require exactly 32 (PB-26-014 regression).834+    #[test]835+    fn small_order_guard_handles_snow_padded_buffers() {836+        for low in X25519_SMALL_ORDER_PUBKEYS.iter() {837+            let mut padded = [0u8; 56];838+            padded[..32].copy_from_slice(low);839+            assert!(is_small_order_x25519_pubkey(&padded));840+        }841+        let (_, real_public) = keypair(13);842+        let mut padded = [0u8; 56];843+        padded[..32].copy_from_slice(&real_public);844+        assert!(!is_small_order_x25519_pubkey(&padded));823845     }824846 825847     #[test]

view file

manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "5.0.16",4+  "version": "5.0.17",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.0.16",5+  "version": "5.0.17",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.0.16",3+  "version": "5.0.17",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/shared/noise-core-wasm/peach_noise_core_bg.wasmbinary

binary file, not shown

view file

src/shared/noise-core-wasm/peach_noise_core_bytes.ts+2 −2
@@ -866,7 +866,7 @@866866   "WyAGIAQgBSACEFsgA0EsaiADQbACakHQAPwKAAAgA0GwAmogA0H8AGogA0HgAWogAhBbIAYgBSAHIAIQWyADQfwAaiADQbACakHQAPwKAAAgA0EsaiADQfwA",867867   "aiADQQRqECcgAyABOgDPASABIQgMAAsLIAgQ5AIhAiADQeABaiADQSxqQdAA/AoAACADQbACaiADQSxqIANB/ABqIAIQWyADQbACakEoaiIBIAQgBSACEFsg",868868   "A0EsaiADQbACakHQAPwKAAAgA0GwAmogA0H8AGogA0HgAWogAhBbIAEgBSADQeABakEoaiACEFsgA0EAOgDPASADIANBzwFqNgKwAiADQbACaiECIANBzwFq",869-  "EKcDIAAgA0EsahCqAiADQYADaiQADwsgCUEgQbDUwAAQ+wEAC9IEAQh/IwBBwABrIgYkAAJAAkACQAJAAkAgA0EgRw0AIAYgAikAGDcDICAGIAIpABA3Axgg",869+  "EKcDIAAgA0EsahCqAiADQYADaiQADwsgCUEgQbDUwAAQ+wEAC9IEAQh/IwBBwABrIgYkAAJAAkACQAJAAkAgA0EgSQ0AIAYgAikAGDcDICAGIAIpABA3Axgg",870870   "BiACKQAINwMQIAYgAikAADcDCCAGQQhqQSBqIQdBACEIA0AgCEGgAkYNASAGQShqIAhB7aXAAGogCEGNpsAAaiAGQQhqIAcQmgJBASEJAkAgBigCPCIKIAYo",871871   "AjgiC00NACAGKAIoIAtqIQwgBigCMCALaiENIAogC2shC0EBIQkDQCANLQAAIAwtAABzIgpBACAKa3LAQX9KEOQCIAlxIQkgDEEBaiEMIA1BAWohDSALQX9q",872872   "IgsNAAsLIAhBIGohCCAJEOQCQf8BcUEBRw0ADAILCyAGIAEoAgAiDSACIAMgBCAFIAEoAgQiCSgCKBELAAJAIAYtAAAiDEEIRg0AIAYtAAEhDQwDCyANIAko",@@ -1583,7 +1583,7 @@15831583   "RFzEWByOhtgiTt3QnxFX7P///////////////////////////////////////3/t////////////////////////////////////////f+7/////////////",15841584   "//////////////////////////9/zet6fDtBuK4WVuP68Z/EatoJjeucMrH9hmIFFl9JuIBMnJW8o1CMJLHQsVWcg+9bBERcxFgcjobYIk7d0J8R1wAAAAcA",15851585   "AAAMAAAABAAAAAgAAAAJAAAACgAAAAAAAAAAAAAAAQAAAAsAAABhIERpc3BsYXkgaW1wbGVtZW50YXRpb24gcmV0dXJuZWQgYW4gZXJyb3IgdW5leHBlY3Rl",1586-  "ZGx5AHwIEABLAAAAZAsAAA4AAAAAAAAAAAAAAAEAAAAMAAAAug0QAAoAAADYAQAAEgAAAA0AAAAIAAAABAAAAA4AAAAPAAAAEAAAABEAAAASAAAAEwAAABQA",1586+  "ZGx5AHwIEABLAAAAZAsAAA4AAAAAAAAAAAAAAAEAAAAMAAAAug0QAAoAAADfAQAAEgAAAA0AAAAIAAAABAAAAA4AAAAPAAAAEAAAABEAAAASAAAAEwAAABQA",15871587   "AAAVAAAAFgAAAAAAAQAAAAAABAAAAAQAAAACAAAAcwcQAE8AAADNAQAANwAAAFRyeUZyb21TbGljZUVycm9yAAAA+wUQAE4AAAANCAAAEQAAAKwKEABqAAAA",15881588   "fAAAACQAAABODBAAXwAAAKQAAAAgAAAATgwQAF8AAACuAAAAGgAAAE4MEABfAAAAnQAAACUAAABODBAAXwAAADgBAAAeAAAATgwQAF8AAAA1AQAAGAAAAAAA",15891589   "AgEAAAIBAQACAwAAAgECAwAAAgAAANAUEAABAAAAihUQAAIAAAB4FRAAAgAAAIoVEAACAAAAAAACAAEAAgHQFBAAAQAAALAVEAAEAAAAAQACAtAUEAABAAAA",

view file