-
Notifications
You must be signed in to change notification settings - Fork 171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/blinded version #1583
Feature/blinded version #1583
Changes from 5 commits
dba0ca9
d23d8f3
35a9f9f
d399057
42733c9
4b87e92
83ea71d
7a8e130
a594952
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package org.thoughtcrime.securesms.util | ||
|
||
import android.os.Handler | ||
import android.os.Looper | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.Job | ||
import kotlinx.coroutines.launch | ||
import org.session.libsession.messaging.file_server.FileServerApi | ||
import org.session.libsession.utilities.TextSecurePreferences | ||
import java.util.concurrent.TimeUnit | ||
|
||
class VersionUtil( | ||
private val prefs: TextSecurePreferences | ||
) { | ||
private val FOUR_HOURS: Long = TimeUnit.HOURS.toMillis(4) | ||
|
||
private val handler = Handler(Looper.getMainLooper()) | ||
private val runnable: Runnable | ||
|
||
private val scope = CoroutineScope(Dispatchers.Default) | ||
private var job: Job? = null | ||
|
||
init { | ||
runnable = Runnable { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. inline |
||
fetchAndScheduleNextVersionCheck() | ||
} | ||
} | ||
|
||
fun startTimedVersionCheck() { | ||
handler.post(runnable) | ||
} | ||
|
||
fun stopTimedVersionCheck() { | ||
handler.removeCallbacks(runnable) | ||
} | ||
|
||
fun clear() { | ||
job?.cancel() | ||
stopTimedVersionCheck() | ||
} | ||
|
||
private fun fetchAndScheduleNextVersionCheck() { | ||
fetchVersionData() | ||
handler.postDelayed(runnable, FOUR_HOURS) | ||
} | ||
|
||
private fun fetchVersionData() { | ||
// only perform this if at least 4h has elapsed since th last successful check | ||
val lastCheck = System.currentTimeMillis() - prefs.getLastVersionCheck() | ||
if(lastCheck < FOUR_HOURS) return | ||
ThomasSession marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
job?.cancel() | ||
job = scope.launch { | ||
try { | ||
// perform the version check | ||
val clientVersion = FileServerApi.getClientVersion() | ||
prefs.setLastVersionCheck() | ||
} catch (e: Exception) { | ||
// we can silently ignore the error | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
#include <jni.h> | ||
#include <session/blinding.hpp> | ||
|
||
#include "util.h" | ||
#include "jni_utils.h" | ||
|
||
// | ||
// Created by Thomas Ruffie on 29/7/2024. | ||
// | ||
|
||
extern "C" | ||
JNIEXPORT jobject JNICALL | ||
Java_network_loki_messenger_libsession_1util_util_BlindKeyAPI_blindVersionKeyPair(JNIEnv *env, | ||
jobject thiz, | ||
jbyteArray ed25519_secret_key) { | ||
return jni_utils::run_catching_cxx_exception_or_throws<jobject>(env, [=] { | ||
const auto [pk, sk] = session::blind_version_key_pair(util::ustring_from_bytes(env, ed25519_secret_key)); | ||
|
||
jclass kp_class = env->FindClass("network/loki/messenger/libsession_util/util/KeyPair"); | ||
jmethodID kp_constructor = env->GetMethodID(kp_class, "<init>", "([B[B)V"); | ||
return env->NewObject(kp_class, kp_constructor, util::bytes_from_ustring(env, {pk.data(), pk.size()}), util::bytes_from_ustring(env, {sk.data(), sk.size()})); | ||
}); | ||
} | ||
extern "C" | ||
JNIEXPORT jbyteArray JNICALL | ||
Java_network_loki_messenger_libsession_1util_util_BlindKeyAPI_blindVersionSign(JNIEnv *env, | ||
jobject thiz, | ||
jbyteArray ed25519_secret_key, | ||
jlong timestamp) { | ||
return jni_utils::run_catching_cxx_exception_or_throws<jbyteArray>(env, [=] { | ||
auto bytes = session::blind_version_sign(util::ustring_from_bytes(env, ed25519_secret_key), session::Platform::android, timestamp); | ||
return util::bytes_from_ustring(env, bytes); | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package network.loki.messenger.libsession_util.util | ||
|
||
object BlindKeyAPI { | ||
init { | ||
System.loadLibrary("session_util") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would perhaps be ideal to just have one IDK if it's optimised away sometimes/always/never, but at least when we loaded a lib way to many times, it crashed on some devices. |
||
} | ||
|
||
external fun blindVersionKeyPair(ed25519SecretKey: ByteArray): KeyPair | ||
external fun blindVersionSign(ed25519SecretKey: ByteArray, timestamp: Long): ByteArray | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,18 +1,22 @@ | ||||||||||||||||||||||||||||
package org.session.libsession.messaging.file_server | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
import android.util.Base64 | ||||||||||||||||||||||||||||
import network.loki.messenger.libsession_util.util.BlindKeyAPI | ||||||||||||||||||||||||||||
import nl.komponents.kovenant.Promise | ||||||||||||||||||||||||||||
import nl.komponents.kovenant.functional.map | ||||||||||||||||||||||||||||
import okhttp3.Headers | ||||||||||||||||||||||||||||
import okhttp3.Headers.Companion.toHeaders | ||||||||||||||||||||||||||||
import okhttp3.HttpUrl | ||||||||||||||||||||||||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull | ||||||||||||||||||||||||||||
import okhttp3.MediaType | ||||||||||||||||||||||||||||
import okhttp3.MediaType.Companion.toMediaType | ||||||||||||||||||||||||||||
import okhttp3.RequestBody | ||||||||||||||||||||||||||||
import org.session.libsession.messaging.MessagingModuleConfiguration | ||||||||||||||||||||||||||||
import org.session.libsession.snode.OnionRequestAPI | ||||||||||||||||||||||||||||
import org.session.libsession.snode.utilities.await | ||||||||||||||||||||||||||||
import org.session.libsignal.utilities.HTTP | ||||||||||||||||||||||||||||
import org.session.libsignal.utilities.JsonUtil | ||||||||||||||||||||||||||||
import org.session.libsignal.utilities.Log | ||||||||||||||||||||||||||||
import org.session.libsignal.utilities.toHexString | ||||||||||||||||||||||||||||
import java.util.concurrent.TimeUnit | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
object FileServerApi { | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
@@ -23,6 +27,7 @@ object FileServerApi { | |||||||||||||||||||||||||||
sealed class Error(message: String) : Exception(message) { | ||||||||||||||||||||||||||||
object ParsingFailed : Error("Invalid response.") | ||||||||||||||||||||||||||||
object InvalidURL : Error("Invalid URL.") | ||||||||||||||||||||||||||||
object NoEd25519KeyPair : Error("Couldn't find ed25519 key pair.") | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
data class Request( | ||||||||||||||||||||||||||||
|
@@ -105,4 +110,52 @@ object FileServerApi { | |||||||||||||||||||||||||||
val request = Request(verb = HTTP.Verb.GET, endpoint = "file/$file") | ||||||||||||||||||||||||||||
return send(request) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||
* Returns the current version of session | ||||||||||||||||||||||||||||
* This is effectively proxying (and caching) the response from the github release | ||||||||||||||||||||||||||||
* page. | ||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||
* Note that the value is cached and can be up to 30 minutes out of date normally, and up to 24 | ||||||||||||||||||||||||||||
* hours out of date if we cannot reach the Github API for some reason. | ||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||
* https://github.com/oxen-io/session-file-server/blob/dev/doc/api.yaml#L119 | ||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||
suspend fun getClientVersion(): VersionData { | ||||||||||||||||||||||||||||
// Generate the auth signature | ||||||||||||||||||||||||||||
val secretKey = MessagingModuleConfiguration.shared.getUserED25519KeyPair()?.secretKey?.asBytes | ||||||||||||||||||||||||||||
?: throw (Error.NoEd25519KeyPair) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
val blindedKeys = BlindKeyAPI.blindVersionKeyPair(secretKey) | ||||||||||||||||||||||||||||
val timestamp = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) // The current timestamp in seconds | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so... you could perhaps more clearly:
as it's useful to put units in the variable name if it is not contained in the type. alternatively
|
||||||||||||||||||||||||||||
val signature = BlindKeyAPI.blindVersionSign(secretKey, timestamp) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
// The hex encoded version-blinded public key with a 07 prefix | ||||||||||||||||||||||||||||
val blindedPkHex = buildString { | ||||||||||||||||||||||||||||
append("07") | ||||||||||||||||||||||||||||
append(blindedKeys.pubKey.toHexString()) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
val request = Request( | ||||||||||||||||||||||||||||
verb = HTTP.Verb.GET, | ||||||||||||||||||||||||||||
endpoint = "session_version", | ||||||||||||||||||||||||||||
queryParameters = mapOf("platform" to "android"), | ||||||||||||||||||||||||||||
headers = mapOf( | ||||||||||||||||||||||||||||
"X-FS-Pubkey" to blindedPkHex, | ||||||||||||||||||||||||||||
"X-FS-Timestamp" to timestamp.toString(), | ||||||||||||||||||||||||||||
"X-FS-Signature" to Base64.encodeToString(signature, Base64.NO_WRAP) | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
// transform the promise into a coroutine | ||||||||||||||||||||||||||||
val result = send(request).await() | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
// map out the result | ||||||||||||||||||||||||||||
val json = JsonUtil.fromJson(result, Map::class.java) | ||||||||||||||||||||||||||||
val statusCode = json.getOrDefault("status_code", 0) as Int | ||||||||||||||||||||||||||||
val version = json.getOrDefault("result", "") as String | ||||||||||||||||||||||||||||
val updated = json.getOrDefault("updated", 0.0) as Double | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
return VersionData(statusCode, version, updated) | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seeing as there's all of these named vals that are simple enough to inline, they may as well be named params, and you get a little bit more safety that they're in the right order etc.
Suggested change
|
||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package org.session.libsession.messaging.file_server | ||
|
||
data class VersionData( | ||
val statusCode: Int, // The value 200. Included for backwards compatibility, and may be removed someday. | ||
val version: String, // The Session version. | ||
val updated: Double // The unix timestamp when this version was retrieved from Github; this can be up to 24 hours ago in case of consistent fetch errors, though normally will be within the last 30 minutes. | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package org.session.libsession.snode.utilities | ||
|
||
import nl.komponents.kovenant.Promise | ||
import kotlin.coroutines.resume | ||
import kotlin.coroutines.resumeWithException | ||
import kotlin.coroutines.suspendCoroutine | ||
|
||
suspend fun <T, E: Throwable> Promise<T, E>.await(): T { | ||
return suspendCoroutine { cont -> | ||
success { cont.resume(it) } | ||
fail { cont.resumeWithException(it) } | ||
ThomasSession marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could probably inject without much trouble