From 1819ad285af6c75adcb4832d497abbd8aa5a0797 Mon Sep 17 00:00:00 2001 From: SessionHero01 Date: Thu, 28 Aug 2025 15:05:01 +1000 Subject: [PATCH 1/2] Add session protocol binding --- gradle/libs.versions.toml | 4 +- library/build.gradle.kts | 2 +- library/src/main/cpp/CMakeLists.txt | 1 + library/src/main/cpp/group_keys.cpp | 7 + library/src/main/cpp/jni_utils.h | 19 +- library/src/main/cpp/protocol.cpp | 325 ++++++++++++++++++ .../loki/messenger/libsession_util/Config.kt | 1 + .../libsession_util/GroupKeysConfig.kt | 1 + .../protocol/DecryptEnvelopeKey.kt | 22 ++ .../protocol/DecryptedEnvelope.kt | 26 ++ .../libsession_util/protocol/Destination.kt | 106 ++++++ .../libsession_util/protocol/ProFeatures.kt | 24 ++ .../libsession_util/protocol/ProStatus.kt | 22 ++ .../protocol/SessionProtocol.kt | 19 + libsession-util | 2 +- 15 files changed, 576 insertions(+), 5 deletions(-) create mode 100644 library/src/main/cpp/protocol.cpp create mode 100644 library/src/main/java/network/loki/messenger/libsession_util/protocol/DecryptEnvelopeKey.kt create mode 100644 library/src/main/java/network/loki/messenger/libsession_util/protocol/DecryptedEnvelope.kt create mode 100644 library/src/main/java/network/loki/messenger/libsession_util/protocol/Destination.kt create mode 100644 library/src/main/java/network/loki/messenger/libsession_util/protocol/ProFeatures.kt create mode 100644 library/src/main/java/network/loki/messenger/libsession_util/protocol/ProStatus.kt create mode 100644 library/src/main/java/network/loki/messenger/libsession_util/protocol/SessionProtocol.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 241dc78..e3f6a18 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] -agp = "8.12.0" -kotlin = "2.2.0" +agp = "8.12.1" +kotlin = "2.2.10" [libraries] junit = { module = "junit:junit", version = "4.13.2" } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 23ed558..1597fa6 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -12,7 +12,7 @@ android { compileSdk = 35 defaultConfig { - minSdk = 24 + minSdk = 26 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/library/src/main/cpp/CMakeLists.txt b/library/src/main/cpp/CMakeLists.txt index 8256a10..21863b0 100644 --- a/library/src/main/cpp/CMakeLists.txt +++ b/library/src/main/cpp/CMakeLists.txt @@ -44,6 +44,7 @@ set(SOURCES ed25519.cpp curve25519.cpp hash.cpp + protocol.cpp ) add_library( # Sets the name of the library. diff --git a/library/src/main/cpp/group_keys.cpp b/library/src/main/cpp/group_keys.cpp index 45f87f7..55f43bc 100644 --- a/library/src/main/cpp/group_keys.cpp +++ b/library/src/main/cpp/group_keys.cpp @@ -187,6 +187,13 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_keys(JNIEnv *env, j return jni_utils::jlist_from_collection(env, ptr->group_keys(), util::bytes_from_span); } +extern "C" +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_GroupKeysConfig_groupEncKey(JNIEnv *env, jobject thiz) { + auto ptr = ptrToKeys(env, thiz); + return util::bytes_from_span(env, ptr->group_enc_key()); +} + extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_GroupKeysConfig_activeHashes(JNIEnv *env, diff --git a/library/src/main/cpp/jni_utils.h b/library/src/main/cpp/jni_utils.h index 5aca809..45b0552 100644 --- a/library/src/main/cpp/jni_utils.h +++ b/library/src/main/cpp/jni_utils.h @@ -69,6 +69,11 @@ namespace jni_utils { env_->DeleteLocalRef(ref_); } } + JavaLocalRef(JavaLocalRef&& other) : env_(other.env_), ref_(other.ref_) { + other.ref_ = nullptr; + } + + JavaLocalRef(const JavaLocalRef&) = delete; inline JNIType get() const { return ref_; @@ -162,6 +167,8 @@ namespace jni_utils { data = std::span(const_cast(c_str), env->GetStringUTFLength(s)); } + JavaStringRef(const JavaStringRef &) = delete; + ~JavaStringRef() { env->ReleaseStringUTFChars(s, data.data()); } @@ -195,8 +202,18 @@ namespace jni_utils { data = std::span(reinterpret_cast(env->GetByteArrayElements(byte_array, nullptr)), length); } + JavaByteArrayRef(const JavaByteArrayRef &) = delete; + + JavaByteArrayRef(JavaByteArrayRef&& other) : env(other.env), byte_array(other.byte_array), data(other.data) { + other.byte_array = nullptr; + other.data = {}; + } + ~JavaByteArrayRef() { - env->ReleaseByteArrayElements(byte_array, reinterpret_cast(data.data()), 0); + if (byte_array) { + env->ReleaseByteArrayElements(byte_array, + reinterpret_cast(data.data()), 0); + } } // Get the data as a span. Only valid during the lifetime of this object. diff --git a/library/src/main/cpp/protocol.cpp b/library/src/main/cpp/protocol.cpp new file mode 100644 index 0000000..b9aaf85 --- /dev/null +++ b/library/src/main/cpp/protocol.cpp @@ -0,0 +1,325 @@ +#include +#include + +#include "jni_utils.h" + +using namespace jni_utils; + +template +static std::optional> java_to_cpp_array(JNIEnv *env, jbyteArray array) { + if (!array) { + return std::nullopt; + } + + JavaByteArrayRef bytes(env, array); + auto span = bytes.get(); + if (span.size() != N) { + throw std::runtime_error("Invalid byte array length from java, expecting " + std::to_string(N) + " got " + std::to_string(span.size())); + } + + std::array out; + std::copy(span.begin(), span.end(), out.begin()); + return out; +} + +extern "C" +JNIEXPORT void JNICALL +Java_network_loki_messenger_libsession_1util_protocol_Destination_00024Contact_toNativeDestination( + JNIEnv *env, jobject thiz, jlong native_ptr) { + auto &dest = *reinterpret_cast(native_ptr); + + JavaLocalRef clazz(env, env->GetObjectClass(thiz)); + + JavaLocalRef pub_key( + env, + reinterpret_cast(env->CallObjectMethod( + thiz, + env->GetMethodID(clazz.get(), "getRecipientPubKey", "()[B")))); + + JavaLocalRef sig( + env, + reinterpret_cast(env->CallObjectMethod( + thiz, + env->GetMethodID(clazz.get(), "getProSignature", "()[B")))); + jlong timestamp = env->CallLongMethod(thiz, env->GetMethodID(clazz.get(), "getSentTimestampMs", + "()J")); + + run_catching_cxx_exception_or_throws(env, [&] { + dest.type = session::DestinationType::Contact; + dest.pro_sig = java_to_cpp_array<64>(env, sig.get()); + dest.recipient_pubkey = java_to_cpp_array<33>(env, pub_key.get()).value(); + dest.sent_timestamp_ms = std::chrono::milliseconds{timestamp}; + }); +} + +extern "C" +JNIEXPORT void JNICALL +Java_network_loki_messenger_libsession_1util_protocol_Destination_00024Sync_toNativeDestination( + JNIEnv *env, jobject thiz, jlong native_ptr) { + auto &dest = *reinterpret_cast(native_ptr); + + JavaLocalRef clazz(env, env->GetObjectClass(thiz)); + + JavaLocalRef pub_key( + env, + reinterpret_cast(env->CallObjectMethod( + thiz, + env->GetMethodID(clazz.get(), "getMyPubKey", "()[B")))); + + JavaLocalRef sig( + env, + reinterpret_cast(env->CallObjectMethod( + thiz, + env->GetMethodID(clazz.get(), "getProSignature", "()[B")))); + jlong timestamp = env->CallLongMethod(thiz, env->GetMethodID(clazz.get(), "getSentTimestampMs", + "()J")); + + run_catching_cxx_exception_or_throws(env, [&] { + dest.type = session::DestinationType::SyncMessage; + dest.pro_sig = java_to_cpp_array<64>(env, sig.get()); + dest.recipient_pubkey = java_to_cpp_array<33>(env, pub_key.get()).value(); + dest.sent_timestamp_ms = std::chrono::milliseconds{timestamp}; + }); +} + +extern "C" +JNIEXPORT void JNICALL +Java_network_loki_messenger_libsession_1util_protocol_Destination_00024Group_toNativeDestination( + JNIEnv *env, jobject thiz, jlong native_ptr) { + auto &dest = *reinterpret_cast(native_ptr); + + JavaLocalRef clazz(env, env->GetObjectClass(thiz)); + + JavaLocalRef pub_key( + env, + reinterpret_cast(env->CallObjectMethod( + thiz, + env->GetMethodID(clazz.get(), "getEd25519PubKey", "()[B")))); + + JavaLocalRef priv_key( + env, + reinterpret_cast(env->CallObjectMethod( + thiz, + env->GetMethodID(clazz.get(), "getEd25519PrivKey", "()[B")))); + + JavaLocalRef sig( + env, + reinterpret_cast(env->CallObjectMethod( + thiz, + env->GetMethodID(clazz.get(), "getProSignature", "()[B")))); + + jlong timestamp = env->CallLongMethod(thiz, env->GetMethodID(clazz.get(), "getSentTimestampMs", + "()J")); + + run_catching_cxx_exception_or_throws(env, [&] { + dest.type = session::DestinationType::Group; + dest.pro_sig = java_to_cpp_array<64>(env, sig.get()); + dest.group_ed25519_privkey = java_to_cpp_array<32>(env, priv_key.get()).value(); + dest.group_ed25519_pubkey = java_to_cpp_array<33>(env, pub_key.get()).value(); + dest.sent_timestamp_ms = std::chrono::milliseconds{timestamp}; + }); +} + +extern "C" +JNIEXPORT void JNICALL +Java_network_loki_messenger_libsession_1util_protocol_Destination_00024Community_toNativeDestination( + JNIEnv *env, jobject thiz, jlong native_ptr) { + auto &dest = *reinterpret_cast(native_ptr); + + JavaLocalRef clazz(env, env->GetObjectClass(thiz)); + JavaLocalRef sig( + env, + reinterpret_cast(env->CallObjectMethod( + thiz, + env->GetMethodID(clazz.get(), "getProSignature", "()[B")))); + + jlong timestamp = env->CallLongMethod(thiz, env->GetMethodID(clazz.get(), "getSentTimestampMs", + "()J")); + + run_catching_cxx_exception_or_throws(env, [&] { + dest.type = session::DestinationType::Community; + dest.pro_sig = java_to_cpp_array<64>(env, sig.get()); + dest.sent_timestamp_ms = std::chrono::milliseconds{timestamp}; + }); +} + +extern "C" +JNIEXPORT void JNICALL +Java_network_loki_messenger_libsession_1util_protocol_Destination_00024CommunityInbox_toNativeDestination( + JNIEnv *env, jobject thiz, jlong native_ptr) { + auto &dest = *reinterpret_cast(native_ptr); + + JavaLocalRef clazz(env, env->GetObjectClass(thiz)); + + JavaLocalRef recipient_pub_key( + env, + reinterpret_cast(env->CallObjectMethod( + thiz, + env->GetMethodID(clazz.get(), "getRecipientPubKey", "()[B")))); + + + JavaLocalRef pub_key( + env, + reinterpret_cast(env->CallObjectMethod( + thiz, + env->GetMethodID(clazz.get(), "getCommunityPubKey", "()[B")))); + + JavaLocalRef sig( + env, + reinterpret_cast(env->CallObjectMethod( + thiz, + env->GetMethodID(clazz.get(), "getProSignature", "()[B")))); + + jlong timestamp = env->CallLongMethod(thiz, env->GetMethodID(clazz.get(), "getSentTimestampMs", + "()J")); + + run_catching_cxx_exception_or_throws(env, [&] { + dest.type = session::DestinationType::CommunityInbox; + dest.pro_sig = java_to_cpp_array<64>(env, sig.get()); + dest.community_inbox_server_pubkey = java_to_cpp_array<32>(env, pub_key.get()).value(); + dest.recipient_pubkey = java_to_cpp_array<33>(env, recipient_pub_key.get()).value(); + dest.sent_timestamp_ms = std::chrono::milliseconds{timestamp}; + }); +} + + +extern "C" +JNIEXPORT jbyteArray JNICALL +Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_encryptForDestination( + JNIEnv *env, + jobject thiz, + jbyteArray java_message, + jbyteArray java_my_ed25519_privkey, + jobject java_destination, + jint java_namespace) { + session::Destination dest; + auto to_native_method = env->GetMethodID(env->GetObjectClass(java_destination), "toNativeDestination", "(J)V"); + env->CallVoidMethod(java_destination, to_native_method, reinterpret_cast(&dest)); + + // Make sure nothing went wrong in toNativeDestination + if (env->ExceptionCheck()) { + return nullptr; + } + + return run_catching_cxx_exception_or_throws(env, [&] { + auto result = session::encrypt_for_destination( + JavaByteArrayRef(env, java_message).get(), + JavaByteArrayRef(env, java_my_ed25519_privkey).get(), + dest, + static_cast(java_namespace)); + if (result.encrypted) { + return util::bytes_from_vector(env, result.ciphertext); + } else { + return (jbyteArray) nullptr; + } + }); +} + +static JavaLocalRef serializeProStatus(JNIEnv *env, const session::DecryptedEnvelope & envelope) { + if (!envelope.pro.has_value()) { + JavaLocalRef noneClass(env, env->FindClass("network/loki/messenger/libsession_util/protocol/ProStatus$None")); + auto fieldId = env->GetStaticFieldID( + noneClass.get(), + "INSTANCE", "Lnetwork/loki/messenger/libsession_util/protocol/ProStatus$None;"); + return JavaLocalRef(env, env->GetStaticObjectField(noneClass.get(), fieldId)); + } + + if (envelope.pro->status == session::config::ProStatus::Valid) { + JavaLocalRef validClass(env, env->FindClass("network/loki/messenger/libsession_util/protocol/ProStatus$Valid")); + auto init = env->GetMethodID(validClass.get(), "", "(JJ)V"); + return JavaLocalRef(env, env->NewObject(validClass.get(), init, + static_cast(envelope.pro->proof.expiry_unix_ts.time_since_epoch().count()), + static_cast(envelope.pro->features))); + } + + JavaLocalRef invalidClass(env, env->FindClass("network/loki/messenger/libsession_util/protocol/ProStatus$Invalid")); + auto fieldId = env->GetStaticFieldID( + invalidClass.get(), + "INSTANCE", "Lnetwork/loki/messenger/libsession_util/protocol/ProStatus$Invalid;"); + return JavaLocalRef(env, env->GetStaticObjectField(invalidClass.get(), fieldId)); +} + +extern "C" +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_decryptEnvelope(JNIEnv *env, + jobject thiz, + jobject java_key, + jbyteArray java_payload, + jlong now_epoch_seconds, + jbyteArray java_pro_backend_pub_key) { + + session::DecryptEnvelopeKey key; + + std::vector> privateKeysStorage; + + struct RegularStorage { + JavaLocalRef ed25519PrivKeyRef; + JavaByteArrayRef ed25519PrivKeyBytesRef; + }; + + struct GroupData { + JavaLocalRef groupPubKeyRef; + JavaByteArrayRef groupPubKeyBytesRef; + + std::vector, JavaByteArrayRef>> groupKeysRef; + }; + + std::optional regularStorage; + std::optional groupStorage; + + JavaLocalRef regularClazz(env, env->FindClass("network/loki/messenger/libsession_util/protocol/DecryptEnvelopeKey$Regular")); + if (env->IsInstanceOf(java_key, regularClazz.get())) { + auto bytes = reinterpret_cast(env->CallObjectMethod(java_key, env->GetMethodID(regularClazz.get(), "getEd25519PrivKey", "()[B"))); + regularStorage.emplace(RegularStorage { + .ed25519PrivKeyRef = JavaLocalRef(env, bytes), + .ed25519PrivKeyBytesRef = JavaByteArrayRef(env, bytes) + }); + + privateKeysStorage.push_back(regularStorage->ed25519PrivKeyBytesRef.get()); + } + + JavaLocalRef groupClazz(env, env->FindClass("network/loki/messenger/libsession_util/protocol/DecryptEnvelopeKey$Group")); + if (env->IsInstanceOf(java_key, groupClazz.get())) { + auto pubKeyBytes = reinterpret_cast(env->CallObjectMethod(java_key, env->GetMethodID(groupClazz.get(), "getGroupEd25519PubKey", "()[B"))); + groupStorage.emplace(GroupData { + .groupPubKeyRef = JavaLocalRef(env, pubKeyBytes), + .groupPubKeyBytesRef = JavaByteArrayRef(env, pubKeyBytes) + }); + + key.group_ed25519_pubkey.emplace(groupStorage->groupPubKeyBytesRef.get()); + + JavaLocalRef privKeyArrays(env, reinterpret_cast(env->CallObjectMethod(java_key, env->GetMethodID(groupClazz.get(), "getGroupKeys", "()[[B")))); + for (int i = 0, size = env->GetArrayLength(privKeyArrays.get()); i < size; i++) { + auto bytes = reinterpret_cast(env->GetObjectArrayElement(privKeyArrays.get(), i)); + const auto &last = groupStorage->groupKeysRef.emplace_back(JavaLocalRef(env, bytes), JavaByteArrayRef(env, bytes)); + privateKeysStorage.emplace_back(last.second.get()); + } + } + + key.ed25519_privkeys = { privateKeysStorage.data(), privateKeysStorage.size() }; + + return run_catching_cxx_exception_or_throws(env, [&] { + auto envelop = session::decrypt_envelope(key, JavaByteArrayRef(env, java_payload).get(), + std::chrono::sys_seconds { std::chrono::seconds { now_epoch_seconds } }, + *java_to_cpp_array<32>(env, java_pro_backend_pub_key)); + + JavaLocalRef sender_ed25519(env, util::bytes_from_span(env, envelop.sender_ed25519_pubkey)); + JavaLocalRef sender_x25519(env, util::bytes_from_span(env, envelop.sender_x25519_pubkey)); + JavaLocalRef content(env, util::bytes_from_vector(env, envelop.content_plaintext)); + + JavaLocalRef envelopClass(env, env->FindClass("network/loki/messenger/libsession_util/protocol/DecryptedEnvelope")); + jmethodID init = env->GetMethodID( + envelopClass.get(), + "", + "(Lnetwork/loki/messenger/libsession_util/protocol/ProStatus;[B[B[BJ)V" + ); + + return env->NewObject(envelopClass.get(), init, + serializeProStatus(env, envelop).get(), + content.get(), + sender_ed25519.get(), + sender_x25519.get(), + static_cast(envelop.envelope.timestamp.count())); + }); +} + diff --git a/library/src/main/java/network/loki/messenger/libsession_util/Config.kt b/library/src/main/java/network/loki/messenger/libsession_util/Config.kt index d177bf5..053ae71 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/Config.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/Config.kt @@ -221,6 +221,7 @@ sealed class ConfigSig(pointer: Long) : Config(pointer) interface ReadableGroupKeysConfig { fun groupKeys(): List + fun groupEncKey(): ByteArray fun needsDump(): Boolean fun dump(): ByteArray fun needsRekey(): Boolean diff --git a/library/src/main/java/network/loki/messenger/libsession_util/GroupKeysConfig.kt b/library/src/main/java/network/loki/messenger/libsession_util/GroupKeysConfig.kt index 4f7fff9..2970257 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/GroupKeysConfig.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/GroupKeysConfig.kt @@ -39,6 +39,7 @@ class GroupKeysConfig private constructor( override fun namespace() = Namespace.GROUP_KEYS() external override fun groupKeys(): List + external override fun groupEncKey(): ByteArray external override fun needsDump(): Boolean external override fun dump(): ByteArray external fun loadKey(message: ByteArray, diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/DecryptEnvelopeKey.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/DecryptEnvelopeKey.kt new file mode 100644 index 0000000..58de8fd --- /dev/null +++ b/library/src/main/java/network/loki/messenger/libsession_util/protocol/DecryptEnvelopeKey.kt @@ -0,0 +1,22 @@ +package network.loki.messenger.libsession_util.protocol + + +sealed interface DecryptEnvelopeKey { + + class Group( + val groupEd25519PubKey: ByteArray, + val groupKeys: Array + ) : DecryptEnvelopeKey { + constructor( + groupEd25519PubKey: ByteArray, + groupKeys: Collection + ) : this( + groupEd25519PubKey, + groupKeys.toTypedArray() + ) + } + + class Regular( + val ed25519PrivKey: ByteArray + ) : DecryptEnvelopeKey +} \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/DecryptedEnvelope.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/DecryptedEnvelope.kt new file mode 100644 index 0000000..637af9a --- /dev/null +++ b/library/src/main/java/network/loki/messenger/libsession_util/protocol/DecryptedEnvelope.kt @@ -0,0 +1,26 @@ +package network.loki.messenger.libsession_util.protocol + +import network.loki.messenger.libsession_util.util.Bytes +import java.time.Instant + +data class DecryptedEnvelope( + val proStatus: ProStatus, + val contentPlainText: Bytes, + val senderEd25519PubKey: Bytes, + val senderX25519PubKey: Bytes, + val timestamp: Instant +) { + constructor( + proStatus: ProStatus, + contentPlainText: ByteArray, + senderEd25519PubKey: ByteArray, + senderX25519PubKey: ByteArray, + timestampEpochMills: Long + ): this( + proStatus = proStatus, + contentPlainText = Bytes(contentPlainText), + senderEd25519PubKey = Bytes(senderEd25519PubKey), + senderX25519PubKey = Bytes(senderX25519PubKey), + timestamp = Instant.ofEpochMilli(timestampEpochMills) + ) +} diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/Destination.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/Destination.kt new file mode 100644 index 0000000..fde0122 --- /dev/null +++ b/library/src/main/java/network/loki/messenger/libsession_util/protocol/Destination.kt @@ -0,0 +1,106 @@ +package network.loki.messenger.libsession_util.protocol + + +sealed interface Destination { + val proSignature: ByteArray? + val sentTimestampMs: Long + + fun toNativeDestination(nativePtr: Long) + + class Contact( + // 33 ByteArray prefixed key + val recipientPubKey: ByteArray, + override val proSignature: ByteArray?, + override val sentTimestampMs: Long + ) : Destination { + init { + check(recipientPubKey.size == 33) { + "recipientPubKey must be 33 ByteArray prefixed key" + } + + check(proSignature == null || proSignature.size == 64) { + "proSignature must be null or 64 ByteArray" + } + } + + external override fun toNativeDestination(nativePtr: Long) + } + + class Sync( + // 33 ByteArray prefixed key + val myPubKey: ByteArray, + override val proSignature: ByteArray?, + override val sentTimestampMs: Long + ) : Destination { + init { + check(myPubKey.size == 33) { + "myPubKey must be 33 ByteArray prefixed key" + } + + check(proSignature == null || proSignature.size == 64) { + "proSignature must be null or 64 ByteArray" + } + } + + external override fun toNativeDestination(nativePtr: Long) + } + + class Group( + val ed25519PubKey: ByteArray, + val ed25519PrivKey: ByteArray, + override val proSignature: ByteArray?, + override val sentTimestampMs: Long + ) : Destination { + init { + check(ed25519PubKey.size == 33) { + "groupPubKey must be 33 ByteArray unprefixed key" + } + + check(ed25519PrivKey.size == 32) { + "groupPrivKey must be 32 ByteArray unprefixed key" + } + + check(proSignature == null || proSignature.size == 64) { + "proSignature must be null or 64 ByteArray" + } + } + + external override fun toNativeDestination(nativePtr: Long) + } + + class Community( + override val proSignature: ByteArray?, + override val sentTimestampMs: Long + ) : Destination { + init { + check(proSignature == null || proSignature.size == 64) { + "proSignature must be null or 64 ByteArray" + } + } + + external override fun toNativeDestination(nativePtr: Long) + } + + class CommunityInbox( + val communityPubKey: ByteArray, + val recipientPubKey: ByteArray, + override val proSignature: ByteArray?, + override val sentTimestampMs: Long + ) : Destination { + init { + check(communityPubKey.size == 33) { + "communityInboxPubKey must be 33 ByteArray unprefixed key" + } + + check(recipientPubKey.size == 33) { + "recipientPubKey must be 33 ByteArray prefixed key" + } + + check(proSignature == null || proSignature.size == 64) { + "proSignature must be null or 64 ByteArray" + } + } + + external override fun toNativeDestination(nativePtr: Long) + } +} \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProFeatures.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProFeatures.kt new file mode 100644 index 0000000..843be42 --- /dev/null +++ b/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProFeatures.kt @@ -0,0 +1,24 @@ +package network.loki.messenger.libsession_util.protocol + + +enum class ProFeature(internal val bitIndex: Int) { + HIGHER_CHARACTER_LIMIT(0), + PRO_BADGE(1), + ANIMATED_AVATAR(2), +} + +internal fun Long.toFeatures(): Set { + return buildSet(ProFeature.entries.size) { + for (entry in ProFeature.entries) { + if (this@toFeatures and (1L shl entry.bitIndex) != 0L) { + add(entry) + } + } + } +} + +internal fun Collection.toLong(): Long { + return fold(0L) { acc, entry -> + acc or (1L shl entry.bitIndex) + } +} \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProStatus.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProStatus.kt new file mode 100644 index 0000000..bf53bb8 --- /dev/null +++ b/library/src/main/java/network/loki/messenger/libsession_util/protocol/ProStatus.kt @@ -0,0 +1,22 @@ +package network.loki.messenger.libsession_util.protocol + +import java.time.Instant + + +sealed interface ProStatus { + data object None : ProStatus + data object Invalid : ProStatus + + data class Valid( + val expiresAt: Instant, + val proFeatures: Set + ) { + constructor( + expiresAtEpochSeconds: Long, + proFeatures: Long + ): this( + expiresAt = Instant.ofEpochSecond(expiresAtEpochSeconds), + proFeatures = proFeatures.toFeatures() + ) + } +} \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/SessionProtocol.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/SessionProtocol.kt new file mode 100644 index 0000000..09befae --- /dev/null +++ b/library/src/main/java/network/loki/messenger/libsession_util/protocol/SessionProtocol.kt @@ -0,0 +1,19 @@ +package network.loki.messenger.libsession_util.protocol + +import network.loki.messenger.libsession_util.LibSessionUtilCApi + +object SessionProtocol : LibSessionUtilCApi() { + external fun encryptForDestination( + message: ByteArray, + myEd25519PrivKey: ByteArray, + destination: Destination, + namespace: Int + ): ByteArray? + + external fun decryptEnvelope( + key: DecryptEnvelopeKey, + payload: ByteArray, + nowEpochSeconds: Long, + proBackendPubKey: ByteArray, // 32 bytes backend key + ): DecryptedEnvelope +} \ No newline at end of file diff --git a/libsession-util b/libsession-util index 3ee705f..c310e57 160000 --- a/libsession-util +++ b/libsession-util @@ -1 +1 @@ -Subproject commit 3ee705f3248337c71c159a88f6baafe85c02f1c3 +Subproject commit c310e576965311e84e32dd37f0908275110019ca From d4898ab95e4644a20bf1991e20a184bef389aa41 Mon Sep 17 00:00:00 2001 From: SessionHero01 Date: Fri, 29 Aug 2025 16:09:13 +1000 Subject: [PATCH 2/2] Update libsession's protocol encryption API --- library/src/main/cpp/jni_utils.h | 19 ++ library/src/main/cpp/protocol.cpp | 288 +++++------------- .../libsession_util/protocol/Destination.kt | 106 ------- .../protocol/SessionProtocol.kt | 29 +- libsession-util | 2 +- 5 files changed, 120 insertions(+), 324 deletions(-) delete mode 100644 library/src/main/java/network/loki/messenger/libsession_util/protocol/Destination.kt diff --git a/library/src/main/cpp/jni_utils.h b/library/src/main/cpp/jni_utils.h index 45b0552..a9cc6f5 100644 --- a/library/src/main/cpp/jni_utils.h +++ b/library/src/main/cpp/jni_utils.h @@ -80,6 +80,7 @@ namespace jni_utils { } }; + /** * Create a Java List from an iterator. * @@ -225,6 +226,24 @@ namespace jni_utils { return std::vector(data.begin(), data.end()); } }; + + template + static std::optional> java_to_cpp_array(JNIEnv *env, jbyteArray array) { + if (!array) { + return std::nullopt; + } + + JavaByteArrayRef bytes(env, array); + auto span = bytes.get(); + if (span.size() != N) { + throw std::runtime_error("Invalid byte array length from java, expecting " + std::to_string(N) + " got " + std::to_string(span.size())); + } + + std::array out; + std::copy(span.begin(), span.end(), out.begin()); + return out; + } + } #endif //SESSION_ANDROID_JNI_UTILS_H diff --git a/library/src/main/cpp/protocol.cpp b/library/src/main/cpp/protocol.cpp index b9aaf85..68fb27c 100644 --- a/library/src/main/cpp/protocol.cpp +++ b/library/src/main/cpp/protocol.cpp @@ -1,219 +1,12 @@ #include #include +#include #include "jni_utils.h" using namespace jni_utils; -template -static std::optional> java_to_cpp_array(JNIEnv *env, jbyteArray array) { - if (!array) { - return std::nullopt; - } - - JavaByteArrayRef bytes(env, array); - auto span = bytes.get(); - if (span.size() != N) { - throw std::runtime_error("Invalid byte array length from java, expecting " + std::to_string(N) + " got " + std::to_string(span.size())); - } - - std::array out; - std::copy(span.begin(), span.end(), out.begin()); - return out; -} - -extern "C" -JNIEXPORT void JNICALL -Java_network_loki_messenger_libsession_1util_protocol_Destination_00024Contact_toNativeDestination( - JNIEnv *env, jobject thiz, jlong native_ptr) { - auto &dest = *reinterpret_cast(native_ptr); - - JavaLocalRef clazz(env, env->GetObjectClass(thiz)); - - JavaLocalRef pub_key( - env, - reinterpret_cast(env->CallObjectMethod( - thiz, - env->GetMethodID(clazz.get(), "getRecipientPubKey", "()[B")))); - - JavaLocalRef sig( - env, - reinterpret_cast(env->CallObjectMethod( - thiz, - env->GetMethodID(clazz.get(), "getProSignature", "()[B")))); - jlong timestamp = env->CallLongMethod(thiz, env->GetMethodID(clazz.get(), "getSentTimestampMs", - "()J")); - run_catching_cxx_exception_or_throws(env, [&] { - dest.type = session::DestinationType::Contact; - dest.pro_sig = java_to_cpp_array<64>(env, sig.get()); - dest.recipient_pubkey = java_to_cpp_array<33>(env, pub_key.get()).value(); - dest.sent_timestamp_ms = std::chrono::milliseconds{timestamp}; - }); -} - -extern "C" -JNIEXPORT void JNICALL -Java_network_loki_messenger_libsession_1util_protocol_Destination_00024Sync_toNativeDestination( - JNIEnv *env, jobject thiz, jlong native_ptr) { - auto &dest = *reinterpret_cast(native_ptr); - - JavaLocalRef clazz(env, env->GetObjectClass(thiz)); - - JavaLocalRef pub_key( - env, - reinterpret_cast(env->CallObjectMethod( - thiz, - env->GetMethodID(clazz.get(), "getMyPubKey", "()[B")))); - - JavaLocalRef sig( - env, - reinterpret_cast(env->CallObjectMethod( - thiz, - env->GetMethodID(clazz.get(), "getProSignature", "()[B")))); - jlong timestamp = env->CallLongMethod(thiz, env->GetMethodID(clazz.get(), "getSentTimestampMs", - "()J")); - - run_catching_cxx_exception_or_throws(env, [&] { - dest.type = session::DestinationType::SyncMessage; - dest.pro_sig = java_to_cpp_array<64>(env, sig.get()); - dest.recipient_pubkey = java_to_cpp_array<33>(env, pub_key.get()).value(); - dest.sent_timestamp_ms = std::chrono::milliseconds{timestamp}; - }); -} - -extern "C" -JNIEXPORT void JNICALL -Java_network_loki_messenger_libsession_1util_protocol_Destination_00024Group_toNativeDestination( - JNIEnv *env, jobject thiz, jlong native_ptr) { - auto &dest = *reinterpret_cast(native_ptr); - - JavaLocalRef clazz(env, env->GetObjectClass(thiz)); - - JavaLocalRef pub_key( - env, - reinterpret_cast(env->CallObjectMethod( - thiz, - env->GetMethodID(clazz.get(), "getEd25519PubKey", "()[B")))); - - JavaLocalRef priv_key( - env, - reinterpret_cast(env->CallObjectMethod( - thiz, - env->GetMethodID(clazz.get(), "getEd25519PrivKey", "()[B")))); - - JavaLocalRef sig( - env, - reinterpret_cast(env->CallObjectMethod( - thiz, - env->GetMethodID(clazz.get(), "getProSignature", "()[B")))); - - jlong timestamp = env->CallLongMethod(thiz, env->GetMethodID(clazz.get(), "getSentTimestampMs", - "()J")); - - run_catching_cxx_exception_or_throws(env, [&] { - dest.type = session::DestinationType::Group; - dest.pro_sig = java_to_cpp_array<64>(env, sig.get()); - dest.group_ed25519_privkey = java_to_cpp_array<32>(env, priv_key.get()).value(); - dest.group_ed25519_pubkey = java_to_cpp_array<33>(env, pub_key.get()).value(); - dest.sent_timestamp_ms = std::chrono::milliseconds{timestamp}; - }); -} - -extern "C" -JNIEXPORT void JNICALL -Java_network_loki_messenger_libsession_1util_protocol_Destination_00024Community_toNativeDestination( - JNIEnv *env, jobject thiz, jlong native_ptr) { - auto &dest = *reinterpret_cast(native_ptr); - - JavaLocalRef clazz(env, env->GetObjectClass(thiz)); - JavaLocalRef sig( - env, - reinterpret_cast(env->CallObjectMethod( - thiz, - env->GetMethodID(clazz.get(), "getProSignature", "()[B")))); - - jlong timestamp = env->CallLongMethod(thiz, env->GetMethodID(clazz.get(), "getSentTimestampMs", - "()J")); - - run_catching_cxx_exception_or_throws(env, [&] { - dest.type = session::DestinationType::Community; - dest.pro_sig = java_to_cpp_array<64>(env, sig.get()); - dest.sent_timestamp_ms = std::chrono::milliseconds{timestamp}; - }); -} - -extern "C" -JNIEXPORT void JNICALL -Java_network_loki_messenger_libsession_1util_protocol_Destination_00024CommunityInbox_toNativeDestination( - JNIEnv *env, jobject thiz, jlong native_ptr) { - auto &dest = *reinterpret_cast(native_ptr); - - JavaLocalRef clazz(env, env->GetObjectClass(thiz)); - - JavaLocalRef recipient_pub_key( - env, - reinterpret_cast(env->CallObjectMethod( - thiz, - env->GetMethodID(clazz.get(), "getRecipientPubKey", "()[B")))); - - - JavaLocalRef pub_key( - env, - reinterpret_cast(env->CallObjectMethod( - thiz, - env->GetMethodID(clazz.get(), "getCommunityPubKey", "()[B")))); - - JavaLocalRef sig( - env, - reinterpret_cast(env->CallObjectMethod( - thiz, - env->GetMethodID(clazz.get(), "getProSignature", "()[B")))); - - jlong timestamp = env->CallLongMethod(thiz, env->GetMethodID(clazz.get(), "getSentTimestampMs", - "()J")); - - run_catching_cxx_exception_or_throws(env, [&] { - dest.type = session::DestinationType::CommunityInbox; - dest.pro_sig = java_to_cpp_array<64>(env, sig.get()); - dest.community_inbox_server_pubkey = java_to_cpp_array<32>(env, pub_key.get()).value(); - dest.recipient_pubkey = java_to_cpp_array<33>(env, recipient_pub_key.get()).value(); - dest.sent_timestamp_ms = std::chrono::milliseconds{timestamp}; - }); -} - - -extern "C" -JNIEXPORT jbyteArray JNICALL -Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_encryptForDestination( - JNIEnv *env, - jobject thiz, - jbyteArray java_message, - jbyteArray java_my_ed25519_privkey, - jobject java_destination, - jint java_namespace) { - session::Destination dest; - auto to_native_method = env->GetMethodID(env->GetObjectClass(java_destination), "toNativeDestination", "(J)V"); - env->CallVoidMethod(java_destination, to_native_method, reinterpret_cast(&dest)); - - // Make sure nothing went wrong in toNativeDestination - if (env->ExceptionCheck()) { - return nullptr; - } - - return run_catching_cxx_exception_or_throws(env, [&] { - auto result = session::encrypt_for_destination( - JavaByteArrayRef(env, java_message).get(), - JavaByteArrayRef(env, java_my_ed25519_privkey).get(), - dest, - static_cast(java_namespace)); - if (result.encrypted) { - return util::bytes_from_vector(env, result.ciphertext); - } else { - return (jbyteArray) nullptr; - } - }); -} static JavaLocalRef serializeProStatus(JNIEnv *env, const session::DecryptedEnvelope & envelope) { if (!envelope.pro.has_value()) { @@ -221,22 +14,22 @@ static JavaLocalRef serializeProStatus(JNIEnv *env, const session::Decr auto fieldId = env->GetStaticFieldID( noneClass.get(), "INSTANCE", "Lnetwork/loki/messenger/libsession_util/protocol/ProStatus$None;"); - return JavaLocalRef(env, env->GetStaticObjectField(noneClass.get(), fieldId)); + return {env, env->GetStaticObjectField(noneClass.get(), fieldId)}; } if (envelope.pro->status == session::config::ProStatus::Valid) { JavaLocalRef validClass(env, env->FindClass("network/loki/messenger/libsession_util/protocol/ProStatus$Valid")); auto init = env->GetMethodID(validClass.get(), "", "(JJ)V"); - return JavaLocalRef(env, env->NewObject(validClass.get(), init, + return {env, env->NewObject(validClass.get(), init, static_cast(envelope.pro->proof.expiry_unix_ts.time_since_epoch().count()), - static_cast(envelope.pro->features))); + static_cast(envelope.pro->features))}; } JavaLocalRef invalidClass(env, env->FindClass("network/loki/messenger/libsession_util/protocol/ProStatus$Invalid")); auto fieldId = env->GetStaticFieldID( invalidClass.get(), "INSTANCE", "Lnetwork/loki/messenger/libsession_util/protocol/ProStatus$Invalid;"); - return JavaLocalRef(env, env->GetStaticObjectField(invalidClass.get(), fieldId)); + return {env, env->GetStaticObjectField(invalidClass.get(), fieldId)}; } extern "C" @@ -323,3 +116,74 @@ Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_decryptEnv }); } + +extern "C" +JNIEXPORT jbyteArray JNICALL +Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_encryptFor1o1(JNIEnv *env, + jobject thiz, + jbyteArray plaintext, + jbyteArray my_ed25519_priv_key, + jlong timestamp_ms, + jbyteArray recipient_pub_key, + jbyteArray pro_signature) { + return run_catching_cxx_exception_or_throws(env, [=] { + return util::bytes_from_vector( + env, + session::encrypt_for_1o1( + JavaByteArrayRef(env, plaintext).get(), + JavaByteArrayRef(env, my_ed25519_priv_key).get(), + std::chrono::milliseconds { timestamp_ms }, + *java_to_cpp_array<33>(env, recipient_pub_key), + java_to_cpp_array<64>(env, pro_signature) + )); + }); +} + +extern "C" +JNIEXPORT jbyteArray JNICALL +Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_encryptForCommunityInbox( + JNIEnv *env, jobject thiz, jbyteArray plaintext, jbyteArray my_ed25519_priv_key, + jlong timestamp_ms, jbyteArray recipient_pub_key, jbyteArray community_server_pub_key, + jbyteArray pro_signature) { + return run_catching_cxx_exception_or_throws(env, [=] { + return util::bytes_from_vector( + env, + session::encrypt_for_community_inbox( + JavaByteArrayRef(env, plaintext).get(), + JavaByteArrayRef(env, my_ed25519_priv_key).get(), + std::chrono::milliseconds { timestamp_ms }, + *java_to_cpp_array<33>(env, recipient_pub_key), + *java_to_cpp_array<32>(env, community_server_pub_key), + java_to_cpp_array<64>(env, pro_signature) + )); + }); +} + +extern "C" +JNIEXPORT jbyteArray JNICALL +Java_network_loki_messenger_libsession_1util_protocol_SessionProtocol_encryptForGroup(JNIEnv *env, + jobject thiz, + jbyteArray plaintext, + jbyteArray my_ed25519_priv_key, + jlong timestamp_ms, + jbyteArray group_ed25519_public_key, + jbyteArray group_ed25519_private_key, + jbyteArray pro_signature) { + return run_catching_cxx_exception_or_throws(env, [=] { + session::cleared_uc32 group_private_key; + + auto array = *java_to_cpp_array<32>(env, group_ed25519_private_key); + std::copy(array.begin(), array.end(), group_private_key.begin()); + + return util::bytes_from_vector( + env, + session::encrypt_for_group( + JavaByteArrayRef(env, plaintext).get(), + JavaByteArrayRef(env, my_ed25519_priv_key).get(), + std::chrono::milliseconds { timestamp_ms }, + *java_to_cpp_array<33>(env, group_ed25519_public_key), + group_private_key, + java_to_cpp_array<64>(env, pro_signature) + )); + }); +} \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/Destination.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/Destination.kt deleted file mode 100644 index fde0122..0000000 --- a/library/src/main/java/network/loki/messenger/libsession_util/protocol/Destination.kt +++ /dev/null @@ -1,106 +0,0 @@ -package network.loki.messenger.libsession_util.protocol - - -sealed interface Destination { - val proSignature: ByteArray? - val sentTimestampMs: Long - - fun toNativeDestination(nativePtr: Long) - - class Contact( - // 33 ByteArray prefixed key - val recipientPubKey: ByteArray, - override val proSignature: ByteArray?, - override val sentTimestampMs: Long - ) : Destination { - init { - check(recipientPubKey.size == 33) { - "recipientPubKey must be 33 ByteArray prefixed key" - } - - check(proSignature == null || proSignature.size == 64) { - "proSignature must be null or 64 ByteArray" - } - } - - external override fun toNativeDestination(nativePtr: Long) - } - - class Sync( - // 33 ByteArray prefixed key - val myPubKey: ByteArray, - override val proSignature: ByteArray?, - override val sentTimestampMs: Long - ) : Destination { - init { - check(myPubKey.size == 33) { - "myPubKey must be 33 ByteArray prefixed key" - } - - check(proSignature == null || proSignature.size == 64) { - "proSignature must be null or 64 ByteArray" - } - } - - external override fun toNativeDestination(nativePtr: Long) - } - - class Group( - val ed25519PubKey: ByteArray, - val ed25519PrivKey: ByteArray, - override val proSignature: ByteArray?, - override val sentTimestampMs: Long - ) : Destination { - init { - check(ed25519PubKey.size == 33) { - "groupPubKey must be 33 ByteArray unprefixed key" - } - - check(ed25519PrivKey.size == 32) { - "groupPrivKey must be 32 ByteArray unprefixed key" - } - - check(proSignature == null || proSignature.size == 64) { - "proSignature must be null or 64 ByteArray" - } - } - - external override fun toNativeDestination(nativePtr: Long) - } - - class Community( - override val proSignature: ByteArray?, - override val sentTimestampMs: Long - ) : Destination { - init { - check(proSignature == null || proSignature.size == 64) { - "proSignature must be null or 64 ByteArray" - } - } - - external override fun toNativeDestination(nativePtr: Long) - } - - class CommunityInbox( - val communityPubKey: ByteArray, - val recipientPubKey: ByteArray, - override val proSignature: ByteArray?, - override val sentTimestampMs: Long - ) : Destination { - init { - check(communityPubKey.size == 33) { - "communityInboxPubKey must be 33 ByteArray unprefixed key" - } - - check(recipientPubKey.size == 33) { - "recipientPubKey must be 33 ByteArray prefixed key" - } - - check(proSignature == null || proSignature.size == 64) { - "proSignature must be null or 64 ByteArray" - } - } - - external override fun toNativeDestination(nativePtr: Long) - } -} \ No newline at end of file diff --git a/library/src/main/java/network/loki/messenger/libsession_util/protocol/SessionProtocol.kt b/library/src/main/java/network/loki/messenger/libsession_util/protocol/SessionProtocol.kt index 09befae..8de9a48 100644 --- a/library/src/main/java/network/loki/messenger/libsession_util/protocol/SessionProtocol.kt +++ b/library/src/main/java/network/loki/messenger/libsession_util/protocol/SessionProtocol.kt @@ -3,12 +3,31 @@ package network.loki.messenger.libsession_util.protocol import network.loki.messenger.libsession_util.LibSessionUtilCApi object SessionProtocol : LibSessionUtilCApi() { - external fun encryptForDestination( - message: ByteArray, + external fun encryptFor1o1( + plaintext: ByteArray, myEd25519PrivKey: ByteArray, - destination: Destination, - namespace: Int - ): ByteArray? + timestampMs: Long, + recipientPubKey: ByteArray, // 33 bytes prefixed key + proSignature: ByteArray?, // 64 bytes + ): ByteArray + + external fun encryptForCommunityInbox( + plaintext: ByteArray, + myEd25519PrivKey: ByteArray, + timestampMs: Long, + recipientPubKey: ByteArray, // 33 bytes prefixed key + communityServerPubKey: ByteArray, // 32 bytes key + proSignature: ByteArray?, // 64 bytes + ): ByteArray + + external fun encryptForGroup( + plaintext: ByteArray, + myEd25519PrivKey: ByteArray, + timestampMs: Long, + groupEd25519PublicKey: ByteArray, // 33 bytes 03 prefixed key + groupEd25519PrivateKey: ByteArray, // 32 bytes group "encryption" key + proSignature: ByteArray?, // 64 bytes + ): ByteArray external fun decryptEnvelope( key: DecryptEnvelopeKey, diff --git a/libsession-util b/libsession-util index c310e57..e0ec4c1 160000 --- a/libsession-util +++ b/libsession-util @@ -1 +1 @@ -Subproject commit c310e576965311e84e32dd37f0908275110019ca +Subproject commit e0ec4c198ba003acf95f96e29e2e855b8ae3fad5