Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -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" }
Expand Down
2 changes: 1 addition & 1 deletion library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ android {
compileSdk = 35

defaultConfig {
minSdk = 24
minSdk = 26

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

Expand Down
1 change: 1 addition & 0 deletions library/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ set(SOURCES
ed25519.cpp
curve25519.cpp
hash.cpp
protocol.cpp
)

add_library( # Sets the name of the library.
Expand Down
7 changes: 7 additions & 0 deletions library/src/main/cpp/group_keys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
38 changes: 37 additions & 1 deletion library/src/main/cpp/jni_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,18 @@ 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_;
}
};


/**
* Create a Java List from an iterator.
*
Expand Down Expand Up @@ -162,6 +168,8 @@ namespace jni_utils {
data = std::span<char>(const_cast<char *>(c_str), env->GetStringUTFLength(s));
}

JavaStringRef(const JavaStringRef &) = delete;

~JavaStringRef() {
env->ReleaseStringUTFChars(s, data.data());
}
Expand Down Expand Up @@ -195,8 +203,18 @@ namespace jni_utils {
data = std::span<unsigned char>(reinterpret_cast<unsigned char *>(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<jbyte *>(data.data()), 0);
if (byte_array) {
env->ReleaseByteArrayElements(byte_array,
reinterpret_cast<jbyte *>(data.data()), 0);
}
}

// Get the data as a span. Only valid during the lifetime of this object.
Expand All @@ -208,6 +226,24 @@ namespace jni_utils {
return std::vector(data.begin(), data.end());
}
};

template <size_t N>
static std::optional<std::array<unsigned char, N>> 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<unsigned char, N> out;
std::copy(span.begin(), span.end(), out.begin());
return out;
}

}

#endif //SESSION_ANDROID_JNI_UTILS_H
189 changes: 189 additions & 0 deletions library/src/main/cpp/protocol.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
#include <jni.h>
#include <session/session_protocol.hpp>
#include <session/sodium_array.hpp>

#include "jni_utils.h"

using namespace jni_utils;



static JavaLocalRef<jobject> 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 {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(), "<init>", "(JJ)V");
return {env, env->NewObject(validClass.get(), init,
static_cast<jlong>(envelope.pro->proof.expiry_unix_ts.time_since_epoch().count()),
static_cast<jlong>(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 {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<std::span<const unsigned char>> privateKeysStorage;

struct RegularStorage {
JavaLocalRef<jbyteArray> ed25519PrivKeyRef;
JavaByteArrayRef ed25519PrivKeyBytesRef;
};

struct GroupData {
JavaLocalRef<jbyteArray> groupPubKeyRef;
JavaByteArrayRef groupPubKeyBytesRef;

std::vector<std::pair<JavaLocalRef<jbyteArray>, JavaByteArrayRef>> groupKeysRef;
};

std::optional<RegularStorage> regularStorage;
std::optional<GroupData> 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<jbyteArray>(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<jbyteArray>(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<jobjectArray>(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<jbyteArray>(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<jobject>(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(),
"<init>",
"(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<jlong>(envelop.envelope.timestamp.count()));
});
}


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<jbyteArray>(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<jbyteArray>(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<jbyteArray>(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)
));
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ sealed class ConfigSig(pointer: Long) : Config(pointer)

interface ReadableGroupKeysConfig {
fun groupKeys(): List<ByteArray>
fun groupEncKey(): ByteArray
fun needsDump(): Boolean
fun dump(): ByteArray
fun needsRekey(): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class GroupKeysConfig private constructor(
override fun namespace() = Namespace.GROUP_KEYS()

external override fun groupKeys(): List<ByteArray>
external override fun groupEncKey(): ByteArray
external override fun needsDump(): Boolean
external override fun dump(): ByteArray
external fun loadKey(message: ByteArray,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package network.loki.messenger.libsession_util.protocol


sealed interface DecryptEnvelopeKey {

class Group(
val groupEd25519PubKey: ByteArray,
val groupKeys: Array<ByteArray>
) : DecryptEnvelopeKey {
constructor(
groupEd25519PubKey: ByteArray,
groupKeys: Collection<ByteArray>
) : this(
groupEd25519PubKey,
groupKeys.toTypedArray()
)
}

class Regular(
val ed25519PrivKey: ByteArray
) : DecryptEnvelopeKey
}
Original file line number Diff line number Diff line change
@@ -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)
)
}
Original file line number Diff line number Diff line change
@@ -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<ProFeature> {
return buildSet(ProFeature.entries.size) {
for (entry in ProFeature.entries) {
if (this@toFeatures and (1L shl entry.bitIndex) != 0L) {
add(entry)
}
}
}
}

internal fun Collection<ProFeature>.toLong(): Long {
return fold(0L) { acc, entry ->
acc or (1L shl entry.bitIndex)
}
}
Loading