mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-25 02:55:23 +00:00
Merge pull request #1583 from oxen-io/feature/blinded-version
Feature/blinded version
This commit is contained in:
commit
fc4bf6ff27
@ -86,6 +86,7 @@ import org.thoughtcrime.securesms.sskenvironment.ProfileManager;
|
||||
import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager;
|
||||
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
|
||||
import org.thoughtcrime.securesms.util.Broadcaster;
|
||||
import org.thoughtcrime.securesms.util.VersionUtil;
|
||||
import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
|
||||
import org.thoughtcrime.securesms.webrtc.CallMessageProcessor;
|
||||
import org.webrtc.PeerConnectionFactory;
|
||||
@ -142,6 +143,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
||||
private HandlerThread conversationListHandlerThread;
|
||||
private Handler conversationListHandler;
|
||||
private PersistentLogger persistentLogger;
|
||||
private VersionUtil versionUtil;
|
||||
|
||||
@Inject LokiAPIDatabase lokiAPIDatabase;
|
||||
@Inject public Storage storage;
|
||||
@ -248,6 +250,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
||||
resubmitProfilePictureIfNeeded();
|
||||
loadEmojiSearchIndexIfNeeded();
|
||||
EmojiSource.refresh();
|
||||
versionUtil = new VersionUtil(textSecurePreferences);
|
||||
|
||||
NetworkConstraint networkConstraint = new NetworkConstraint.Factory(this).create();
|
||||
HTTP.INSTANCE.setConnectedToNetwork(networkConstraint::isMet);
|
||||
@ -274,6 +277,9 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
||||
|
||||
OpenGroupManager.INSTANCE.startPolling();
|
||||
});
|
||||
|
||||
// fetch last version data
|
||||
versionUtil.startTimedVersionCheck();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -286,12 +292,14 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
||||
poller.stopIfNeeded();
|
||||
}
|
||||
ClosedGroupPollerV2.getShared().stopAll();
|
||||
versionUtil.stopTimedVersionCheck();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
stopKovenant(); // Loki
|
||||
OpenGroupManager.INSTANCE.stopPolling();
|
||||
versionUtil.clear();
|
||||
super.onTerminate();
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ fun showMuteDialog(
|
||||
|
||||
private enum class Option(@StringRes val stringRes: Int, val getTime: () -> Long) {
|
||||
ONE_HOUR(R.string.arrays__mute_for_one_hour, duration = TimeUnit.HOURS.toMillis(1)),
|
||||
TWO_HOURS(R.string.arrays__mute_for_two_hours, duration = TimeUnit.DAYS.toMillis(2)),
|
||||
TWO_HOURS(R.string.arrays__mute_for_two_hours, duration = TimeUnit.HOURS.toMillis(2)),
|
||||
ONE_DAY(R.string.arrays__mute_for_one_day, duration = TimeUnit.DAYS.toMillis(1)),
|
||||
SEVEN_DAYS(R.string.arrays__mute_for_seven_days, duration = TimeUnit.DAYS.toMillis(7)),
|
||||
FOREVER(R.string.arrays__mute_forever, getTime = { Long.MAX_VALUE });
|
||||
|
@ -22,7 +22,7 @@ public class MemoryFileUtil {
|
||||
|
||||
int fd = field.getInt(fileDescriptor);
|
||||
|
||||
return ParcelFileDescriptor.adoptFd(fd);
|
||||
return ParcelFileDescriptor.fromFd(fd);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IOException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
|
@ -0,0 +1,68 @@
|
||||
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 org.session.libsignal.utilities.Log
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class VersionUtil(
|
||||
private val prefs: TextSecurePreferences
|
||||
) {
|
||||
private val TAG: String = VersionUtil::class.java.simpleName
|
||||
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 {
|
||||
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
|
||||
|
||||
job?.cancel()
|
||||
job = scope.launch {
|
||||
try {
|
||||
// perform the version check
|
||||
val clientVersion = FileServerApi.getClientVersion()
|
||||
Log.i(TAG, "Fetched version data: $clientVersion")
|
||||
prefs.setLastVersionCheck()
|
||||
} catch (e: Exception) {
|
||||
// we can silently ignore the error
|
||||
Log.e(TAG, "Error fetching version data: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -30,6 +30,7 @@ set(SOURCES
|
||||
config_base.cpp
|
||||
contacts.cpp
|
||||
conversation.cpp
|
||||
blinded_key.cpp
|
||||
util.cpp)
|
||||
|
||||
add_library( # Sets the name of the library.
|
||||
|
34
libsession-util/src/main/cpp/blinded_key.cpp
Normal file
34
libsession-util/src/main/cpp/blinded_key.cpp
Normal file
@ -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);
|
||||
});
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package network.loki.messenger.libsession_util.util
|
||||
|
||||
object BlindKeyAPI {
|
||||
private val loadLibrary by lazy {
|
||||
System.loadLibrary("session_util")
|
||||
}
|
||||
|
||||
init {
|
||||
// Ensure the library is loaded at initialization
|
||||
loadLibrary
|
||||
}
|
||||
|
||||
external fun blindVersionKeyPair(ed25519SecretKey: ByteArray): KeyPair
|
||||
external fun blindVersionSign(ed25519SecretKey: ByteArray, timestamp: Long): ByteArray
|
||||
}
|
@ -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,53 @@ 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
|
||||
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
|
||||
return JsonUtil.fromJson(result, Map::class.java).let {
|
||||
VersionData(
|
||||
statusCode = it["status_code"] as? Int ?: 0,
|
||||
version = it["result"] as? String ?: "",
|
||||
updated = it["updated"] as? Double ?: 0.0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
||||
)
|
@ -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)
|
||||
fail(cont::resumeWithException)
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import org.session.libsession.R
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.AUTOPLAY_AUDIO_MESSAGES
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_DARK
|
||||
@ -20,6 +21,7 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.FOLLOW_SYSTEM_SETTINGS
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.HIDE_PASSWORD
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.LAST_VACUUM_TIME
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.LAST_VERSION_CHECK
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.LEGACY_PREF_KEY_SELECTED_UI_MODE
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_DARK
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_LIGHT
|
||||
@ -186,6 +188,8 @@ interface TextSecurePreferences {
|
||||
fun clearAll()
|
||||
fun getHidePassword(): Boolean
|
||||
fun setHidePassword(value: Boolean)
|
||||
fun getLastVersionCheck(): Long
|
||||
fun setLastVersionCheck()
|
||||
|
||||
companion object {
|
||||
val TAG = TextSecurePreferences::class.simpleName
|
||||
@ -272,6 +276,7 @@ interface TextSecurePreferences {
|
||||
const val AUTOPLAY_AUDIO_MESSAGES = "pref_autoplay_audio"
|
||||
const val FINGERPRINT_KEY_GENERATED = "fingerprint_key_generated"
|
||||
const val SELECTED_ACCENT_COLOR = "selected_accent_color"
|
||||
const val LAST_VERSION_CHECK = "pref_last_version_check"
|
||||
|
||||
const val HAS_RECEIVED_LEGACY_CONFIG = "has_received_legacy_config"
|
||||
const val HAS_FORCED_NEW_CONFIG = "has_forced_new_config"
|
||||
@ -1541,6 +1546,14 @@ class AppTextSecurePreferences @Inject constructor(
|
||||
setLongPreference(LAST_VACUUM_TIME, System.currentTimeMillis())
|
||||
}
|
||||
|
||||
override fun getLastVersionCheck(): Long {
|
||||
return getLongPreference(LAST_VERSION_CHECK, 0)
|
||||
}
|
||||
|
||||
override fun setLastVersionCheck() {
|
||||
setLongPreference(LAST_VERSION_CHECK, System.currentTimeMillis())
|
||||
}
|
||||
|
||||
override fun setShownCallNotification(): Boolean {
|
||||
val previousValue = getBooleanPreference(SHOWN_CALL_NOTIFICATION, false)
|
||||
if (previousValue) return false
|
||||
|
Loading…
Reference in New Issue
Block a user