aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/keys/DeviceKey.kt
blob: 4cd840c61f7f87233df6e19d0f255163364536c2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package org.meshbay.client.keys

import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters
import org.bouncycastle.crypto.signers.Ed25519Signer
import org.json.JSONObject
import java.security.SecureRandom

/**
 * The device's key for signing in to the hub (E3): generated, held and used
 * here, never handed to the page, which asks for a signature over
 * `meshbay:user_auth:<username>:<ts>` — the bytes `POST /v1/users/auth`
 * verifies. Not a per-node identity: nothing here correlates a person across
 * operators (main.js `device:*`).
 */
class DeviceKey(private val store: Secrets, private val now: () -> Long = { System.currentTimeMillis() / 1000 }) {

    private fun current(): Ed25519PrivateKeyParameters? {
        val stored = store.read().optString(SecretStore.DEVICE_KEY, "")
        return if (stored.isEmpty()) null else Kdf.edFromPkcs8(Kdf.unb64(stored))
    }

    private fun publicOf(k: Ed25519PrivateKeyParameters) = Kdf.b64(k.generatePublicKey().encoded)

    fun ensure(): String {
        current()?.let { return publicOf(it) }
        val k = Ed25519PrivateKeyParameters(SecureRandom())
        store.update { it.put(SecretStore.DEVICE_KEY, Kdf.b64(Kdf.edToPkcs8(k))) }
        return publicOf(k)
    }

    fun publicKey(): String? = current()?.let { publicOf(it) }

    fun sign(username: String): JSONObject? {
        val k = current() ?: return null
        val ts = now()
        // The username is inside the signature, so one collected for another
        // account is not usable.
        val message = "meshbay:user_auth:$username:$ts".toByteArray(Charsets.UTF_8)
        val s = Ed25519Signer().apply { init(true, k); update(message, 0, message.size) }
        return JSONObject().put("timestamp", ts).put("signature", Kdf.b64(s.generateSignature()))
    }

    fun forget(): Boolean {
        store.update { it.remove(SecretStore.DEVICE_KEY) }
        return true
    }
}