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
}
}
|