755aa7c71150

add JNI all-nine small-order blocklist regression to NoiseKKTest - KK initiator must fail writeHandshake for every blocklist entry incl. the two non-zero-output ones (cdeb..., 4c9c...), plus an honest-remote-static control; green against the rebuilt fixed core with zero production Kotlin changes

time
2026-08-30 02:36 UTC · 2 months ago
machine
koh2
agent
glm-5.3-flash
session
small-order dh rejection
parent
ead822036918
1 file app/src/test/java/com/peachpasswords/sync/NoiseKKTest.kt+64 −0
app/src/test/java/com/peachpasswords/sync/NoiseKKTest.kt+64 −0
@@ -126,4 +126,68 @@126126         assertTrue(failure?.message?.contains("decrypt", ignoreCase = true) == true)127127         initiatorHandshake.cancel()128128     }129+130+    // PB-26-014 Android parity: the nine-entry small-order X25519 blocklist131+    // lives in the shared Rust core, so this test only proves the JNI driver132+    // surfaces its rejection. The last two entries produce non-zero DH output,133+    // so only the core's small-order guard (not the all-zero-output check)134+    // rejects them.135+    private val smallOrderRemoteStatics = listOf(136+        "0000000000000000000000000000000000000000000000000000000000000000",137+        "0100000000000000000000000000000000000000000000000000000000000000",138+        "e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b880",139+        "5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157",140+        "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",141+        "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",142+        "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",143+        "cdeb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b880",144+        "4c9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f11d7",145+    )146+147+    @Test148+    fun `KK initiator rejects every small-order remote static at the DH`() {149+        val local = keys(0x0b)150+        for (entry in smallOrderRemoteStatics) {151+            val session = RustNoiseSession(152+                RustNoiseNative.nativeCreateKkInitiator(153+                    local.secretKey,154+                    local.publicKey,155+                    entry.hexToBytes(),156+                ),157+            )158+            try {159+                val failure = runCatching { session.writeHandshake() }.exceptionOrNull()160+                assertTrue(161+                    "small-order remote static $entry must fail the handshake at the DH",162+                    failure != null,163+                )164+            } finally {165+                session.close()166+            }167+        }168+    }169+170+    @Test171+    fun `KK accepts an honest remote static`() {172+        val local = keys(0x0b)173+        val responder = keys(0x0c)174+        val session = RustNoiseSession(175+            RustNoiseNative.nativeCreateKkInitiator(176+                local.secretKey,177+                local.publicKey,178+                responder.publicKey,179+            ),180+        )181+        try {182+            session.writeHandshake()183+        } finally {184+            session.close()185+        }186+    }129187 }188+189+private fun String.hexToBytes(): ByteArray =190+    ByteArray(length / 2) { index ->191+        ((Character.digit(this[index * 2], 16) shl 4) or192+            Character.digit(this[index * 2 + 1], 16)).toByte()193+    }

view file