PairedDeviceRevocationTest.kt

6.4 KB · raw

1package com.peachpasswords.sync23import org.junit.Assert.assertEquals4import org.junit.Assert.assertNull5import org.junit.Test67class PairedDeviceRevocationTest {8    private val browser = PairedDevice(9        deviceId = "browser-1",10        deviceName = "Chrome",11        deviceType = "extension",12        longTermPublicKeyHex = "ab".repeat(32),13        pairedAt = 1L,14    )1516    @Test17    fun `new revocation records the key and removes the paired row`() {18        val mutation = planDeviceRevocation(19            devices = listOf(browser),20            revocations = emptyList(),21            deviceId = browser.deviceId,22            revokedByDeviceId = "phone-1",23            nowMs = 42L,24        )2526        assertEquals(emptyList<PairedDevice>(), mutation.devices)27        assertEquals(1, mutation.revocations.size)28        assertEquals(browser.longTermPublicKeyHex, mutation.added?.longTermPublicKeyHex)29        assertEquals(42L, mutation.added?.revokedAt)30    }3132    @Test33    fun `full ledger admits another issuer by evicting the largest issuer oldest record`() {34        val existing = List(500) { index ->35            DeviceRevocation("terminal-$index", index.toString(16).padStart(64, '0'), index.toLong(), "issuer-a")36        }37        val merged = mergeRevocationRecords(existing, listOf(38            DeviceRevocation("peer-extra", "ff".repeat(32), 501L, "issuer-b"),39        ))40        assertEquals(500, merged.records.size)41        assertEquals(listOf("peer-extra"), merged.admitted.map { it.deviceId })42        assertEquals(false, merged.records.any { it.deviceId == "terminal-0" })43        assertEquals(true, merged.records.any { it.deviceId == "peer-extra" })44    }4546    @Test47    fun `same issuer local flood uses bounded capacity`() {48        var records = emptyList<DeviceRevocation>()49        repeat(501) { index ->50            records = if (records.size < 500) records + DeviceRevocation("local-$index", index.toString(16).padStart(64, '0'), index.toLong(), "issuer-a")51            else records.drop(1) + DeviceRevocation("local-$index", index.toString(16).padStart(64, '0'), index.toLong(), "issuer-a")52        }53        assertEquals(500, records.size)54        assertEquals("local-1", records.first().deviceId)55        assertEquals("local-500", records.last().deviceId)56    }5758    @Test59    fun `different issuer local capacity preserves genuine issuer record`() {60        val existing = List(500) { index ->61            DeviceRevocation("terminal-$index", index.toString(16).padStart(64, '0'), index.toLong(), if (index == 0) "issuer-a" else "issuer-b")62        }63        val removable = existing.indexOfFirst { it.revokedByDeviceId != "issuer-a" }64        val records = existing.toMutableList().also { it.removeAt(removable); it += DeviceRevocation("local-new", "ff".repeat(32), 501L, "issuer-a") }65        assertEquals(500, records.size)66        assertEquals("terminal-0", records.first().deviceId)67        assertEquals("local-new", records.last().deviceId)68    }6970    @Test71    fun `peer flood evicts only its own oldest records once it is largest issuer`() {72        val existing = listOf(73            DeviceRevocation("genuine", "aa".repeat(32), 1L, "honest-issuer"),74        )75        val flood = List(600) { index ->76            DeviceRevocation(77                "junk-$index",78                index.toString(16).padStart(64, '0'),79                index.toLong() + 2,80                "flooding-issuer",81            )82        }8384        val merged = mergeRevocationRecords(existing, flood, setOf("honest-issuer"))8586        assertEquals(500, merged.records.size)87        assertEquals(true, merged.records.any { it.deviceId == "genuine" })88        assertEquals(false, merged.records.any { it.deviceId == "junk-0" })89        assertEquals(true, merged.records.any { it.deviceId == "junk-599" })90        // Admitted is exactly what survived the cap: a dropped record must not91        // be reported as applied (PB-26-130).92        assertEquals(93            merged.records.count { it.deviceId.startsWith("junk-") },94            merged.admitted.size,95        )96    }9798    @Test99    fun `an all-protected ledger refuses the overflow instead of dropping trust`() {100        val existing = List(500) { index ->101            DeviceRevocation("terminal-$index", index.toString(16).padStart(64, '0'), index.toLong(), "honest-issuer")102        }103        val incoming = listOf(DeviceRevocation("incoming", "ff".repeat(32), 999L, "honest-issuer"))104105        val merged = mergeRevocationRecords(existing, incoming, setOf("honest-issuer"))106107        assertEquals(RevocationMergeOutcome.OVERFLOW_PROTECTED_LEDGER, merged.outcome)108        assertEquals("refusing to evict trusted state keeps the ledger as it was", 500, merged.records.size)109        assertEquals(true, merged.records.any { it.deviceId == "terminal-0" })110        assertEquals(false, merged.records.any { it.deviceId == "incoming" })111        assertEquals(0, merged.admitted.size)112    }113114    @Test115    fun `existing revocation still removes a stale overlapping paired row`() {116        val existing = DeviceRevocation(117            deviceId = browser.deviceId,118            longTermPublicKeyHex = browser.longTermPublicKeyHex!!,119            revokedAt = 1L,120            revokedByDeviceId = "phone-1",121        )122123        val mutation = planDeviceRevocation(124            devices = listOf(browser),125            revocations = listOf(existing),126            deviceId = browser.deviceId,127            revokedByDeviceId = "phone-1",128            nowMs = 99L,129        )130131        assertEquals(emptyList<PairedDevice>(), mutation.devices)132        assertEquals(listOf(existing), mutation.revocations)133        assertNull(mutation.added)134    }135136    @Test137    fun `fresh re-pair rotates a stable profile id already in the revocation ledger`() {138        val revoked = DeviceRevocation(139            deviceId = browser.deviceId,140            longTermPublicKeyHex = browser.longTermPublicKeyHex!!,141            revokedAt = 1L,142            revokedByDeviceId = "chrome-1",143        )144145        assertEquals(true, hasAdoptionIdentityConflict(browser, emptyList(), listOf(revoked)))146    }147148    @Test149    fun `fresh re-pair rotates a stable profile key revoked under an older id`() {150        val revoked = DeviceRevocation(151            deviceId = "old-firefox-id",152            longTermPublicKeyHex = browser.longTermPublicKeyHex!!,153            revokedAt = 1L,154            revokedByDeviceId = "chrome-1",155        )156157        assertEquals(true, hasAdoptionIdentityConflict(browser, emptyList(), listOf(revoked)))158    }159}