package org.meshbay.client import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters import org.bouncycastle.crypto.signers.Ed25519Signer import org.json.JSONArray import org.json.JSONObject import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNull import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue import org.junit.Test import org.meshbay.client.bridge.KeyChannels import org.meshbay.client.bridge.Refused import org.meshbay.client.keys.Kdf class KeyChannelsTest { private val user = "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0" private val node = Kdf.b64(ByteArray(32) { 0x11 }) private var asked = 0 private var answer = false private val secrets = FakeSecrets() private val keys = KeyChannels(secrets, confirm = { asked++; answer }, declined = { "Cancelled" }) private fun call(ch: String, vararg args: Any?) = keys.call(ch, JSONArray(args.toList())) @Test fun `ids and node keys are checked before anything is done`() { for (bad in listOf("", "../x", "not-an-id", "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0x", null)) { assertThrows("$bad", Refused::class.java) { call("keys:fingerprint", bad) } } for (bad in listOf("", "a/b c", "x".repeat(101), null)) { assertThrows("$bad", Refused::class.java) { call("keys:identity", user, bad) } } } @Test fun `the device key signs the bytes the hub verifies and never leaves`() { val pub = call("device:ensure") as String assertEquals(pub, call("device:ensure")) // once, then the same key val s = call("device:sign", "alice") as JSONObject val msg = "meshbay:user_auth:alice:${s.getLong("timestamp")}".toByteArray() val v = Ed25519Signer().apply { init(false, Ed25519PublicKeyParameters(Kdf.unb64(pub), 0)); update(msg, 0, msg.size) } assertTrue(v.verifySignature(Kdf.unb64(s.getString("signature")))) call("device:forget") assertNull(call("device:public")) } @Test fun `browser access is widened only by the person, natively`() { call("keys:created-here", user) assertEquals(false, call("keys:browser-access", user)) answer = false val e = assertThrows(Refused::class.java) { call("keys:set-browser-access", user, true) } assertEquals("Cancelled", e.message) assertEquals(1, asked) assertEquals(false, call("keys:browser-access", user)) answer = true assertEquals(true, call("keys:set-browser-access", user, true)) // Narrowing asks nobody. assertEquals(false, call("keys:set-browser-access", user, false)) assertEquals(2, asked) } @Test fun `without OS key storage nothing is minted or derived`() { val none = KeyChannels(FakeSecrets("unavailable"), confirm = { true }, declined = { "" }) assertEquals(false, none.call("keys:available", JSONArray())) assertThrows(Refused::class.java) { none.call("keys:mint", JSONArray(listOf(user, node))) } assertEquals(false, none.call("keys:has-session", JSONArray(listOf(user)))) } @Test fun `a minted identity answers with public keys only`() { val r = call("keys:mint", user, node) as JSONObject assertEquals(setOf("pkEdB64", "pkXB64"), r.keys().asSequence().toSet()) val id = call("keys:identity", user, node) as JSONObject assertEquals(r.getString("pkEdB64"), id.getString("pkEdB64")) assertEquals(JSONObject.NULL, id.get("sealedWith")) } @Test fun `channels outside the list are refused`() { assertThrows(Refused::class.java) { call("keys:export") } assertFalse(keys.handles("hub:fetch")) } }