Попытка обновления шифрования звонков и работа над UI

This commit is contained in:
2026-03-25 01:47:12 +05:00
parent 419101a4a9
commit 530047c5d0
14 changed files with 1326 additions and 99 deletions

View File

@@ -0,0 +1,390 @@
/**
* JNI bridge for Rosetta E2EE.
*
* Provides:
* 1. HSalsa20 — for nacl.box.before() compatible key derivation
* 2. XChaCha20 FrameEncryptor / FrameDecryptor — inherits DIRECTLY from
* webrtc::FrameEncryptorInterface / FrameDecryptorInterface so the
* vtable is generated by the compiler, not guessed by us.
*/
#include <jni.h>
#include <cstdint>
#include <cstring>
#include <atomic>
#include <vector>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <android/log.h>
/* WebRTC M125 interface stubs (exact copies of the real headers) */
#include "webrtc/api/crypto/frame_encryptor_interface.h"
#include "webrtc/api/crypto/frame_decryptor_interface.h"
#include "crypto.h"
#define TAG "RosettaE2EE"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
/* ── Diagnostics file — written from native, visible in crash reports ── */
static char g_diag_path[512] = {0};
static int g_diag_fd = -1;
static void diag_write(const char *fmt, ...) {
if (g_diag_fd < 0) return;
char buf[512];
va_list ap;
va_start(ap, fmt);
int n = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if (n > 0) write(g_diag_fd, buf, n);
}
/* ── Native crash handler — writes to file before dying ──────── */
static char g_crash_path[512] = {0};
static struct sigaction g_old_sigsegv = {};
static struct sigaction g_old_sigabrt = {};
static void native_crash_handler(int sig, siginfo_t *info, void *ctx) {
if (g_crash_path[0] != 0) {
int fd = open(g_crash_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd >= 0) {
const char *msg;
if (sig == SIGSEGV)
msg = "NATIVE CRASH: SIGSEGV (segmentation fault) in rosetta_e2ee\n"
"Fault address: see logcat for details.\n";
else if (sig == SIGABRT)
msg = "NATIVE CRASH: SIGABRT (abort) in rosetta_e2ee\n";
else
msg = "NATIVE CRASH: unknown signal in rosetta_e2ee\n";
write(fd, msg, strlen(msg));
close(fd);
}
}
LOGE("NATIVE CRASH sig=%d addr=%p", sig, info ? info->si_addr : nullptr);
struct sigaction *old = (sig == SIGSEGV) ? &g_old_sigsegv : &g_old_sigabrt;
sigaction(sig, old, nullptr);
raise(sig);
}
/* ════════════════════════════════════════════════════════════════
* XChaCha20 Encryptor — inherits from the REAL interface
* ════════════════════════════════════════════════════════════════ */
class XChaCha20Encryptor final : public webrtc::FrameEncryptorInterface {
public:
explicit XChaCha20Encryptor(const uint8_t key[32]) {
memcpy(key_, key, 32);
}
/* ── RefCountInterface ─────────────────────────────────────── */
void AddRef() const override {
ref_.fetch_add(1, std::memory_order_relaxed);
}
rtc::RefCountReleaseStatus Release() const override {
if (ref_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
delete this;
return rtc::RefCountReleaseStatus::kDroppedLastRef;
}
return rtc::RefCountReleaseStatus::kOtherRefsRemained;
}
/**
* Frame format: [4-byte counter BE] + [xchacha20_xor(frame)]
*
* Nonce (24 bytes): [0,0,0,0, counter_BE_4bytes, 0,...,0]
* This matches Desktop's layout where nonce[4..7] = timestamp.
* The counter is embedded so the receiver can reconstruct the nonce
* even if frames are dropped/reordered.
*/
int Encrypt(cricket::MediaType /*media_type*/,
uint32_t /*ssrc*/,
rtc::ArrayView<const uint8_t> /*additional_data*/,
rtc::ArrayView<const uint8_t> frame,
rtc::ArrayView<uint8_t> encrypted_frame,
size_t* bytes_written) override {
const size_t HEADER = 4; // counter prefix
if (frame.size() == 0 || encrypted_frame.size() < frame.size() + HEADER) {
*bytes_written = 0;
return -1;
}
uint32_t ctr = counter_.fetch_add(1, std::memory_order_relaxed);
// Write 4-byte counter as big-endian prefix
encrypted_frame.data()[0] = (uint8_t)(ctr >> 24);
encrypted_frame.data()[1] = (uint8_t)(ctr >> 16);
encrypted_frame.data()[2] = (uint8_t)(ctr >> 8);
encrypted_frame.data()[3] = (uint8_t)(ctr);
// Build nonce from counter (same positions as Desktop's timestamp)
uint8_t nonce[24] = {0};
nonce[4] = encrypted_frame.data()[0];
nonce[5] = encrypted_frame.data()[1];
nonce[6] = encrypted_frame.data()[2];
nonce[7] = encrypted_frame.data()[3];
rosetta_xchacha20_xor(encrypted_frame.data() + HEADER,
frame.data(), frame.size(), nonce, key_);
*bytes_written = frame.size() + HEADER;
// Diag: log first 3 frames
int n = diag_count_.fetch_add(1, std::memory_order_relaxed);
if (n < 3) {
LOGI("ENC frame#%d: sz=%zu ctr=%u out=%zu",
n, frame.size(), ctr, frame.size() + HEADER);
diag_write("ENC frame#%d: sz=%zu ctr=%u nonce[4..7]=%02x%02x%02x%02x\n",
n, frame.size(), ctr, nonce[4], nonce[5], nonce[6], nonce[7]);
}
return 0;
}
size_t GetMaxCiphertextByteSize(cricket::MediaType, size_t frame_size) override {
return frame_size + 4; // +4 for counter prefix
}
protected:
~XChaCha20Encryptor() override { memset(key_, 0, 32); }
private:
mutable std::atomic<int> ref_{0};
mutable std::atomic<uint32_t> counter_{0};
mutable std::atomic<int> diag_count_{0};
uint8_t key_[32];
};
/* ════════════════════════════════════════════════════════════════
* XChaCha20 Decryptor — inherits from the REAL interface
* ════════════════════════════════════════════════════════════════ */
class XChaCha20Decryptor final : public webrtc::FrameDecryptorInterface {
public:
explicit XChaCha20Decryptor(const uint8_t key[32]) {
memcpy(key_, key, 32);
}
/* ── RefCountInterface ─────────────────────────────────────── */
void AddRef() const override {
ref_.fetch_add(1, std::memory_order_relaxed);
}
rtc::RefCountReleaseStatus Release() const override {
if (ref_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
delete this;
return rtc::RefCountReleaseStatus::kDroppedLastRef;
}
return rtc::RefCountReleaseStatus::kOtherRefsRemained;
}
/**
* Decrypt frame: read 4-byte counter prefix → derive nonce → decrypt.
* If frame has no prefix (< 5 bytes or from Desktop), fallback to
* nonce derived from additional_data (RTP header) or zeros.
*/
Result Decrypt(cricket::MediaType /*media_type*/,
const std::vector<uint32_t>& /*csrcs*/,
rtc::ArrayView<const uint8_t> additional_data,
rtc::ArrayView<const uint8_t> encrypted_frame,
rtc::ArrayView<uint8_t> frame) override {
const size_t HEADER = 4;
uint8_t nonce[24] = {0};
const uint8_t *payload;
size_t payload_sz;
if (encrypted_frame.size() > HEADER) {
// Android format: [4-byte counter] + [encrypted data]
nonce[4] = encrypted_frame.data()[0];
nonce[5] = encrypted_frame.data()[1];
nonce[6] = encrypted_frame.data()[2];
nonce[7] = encrypted_frame.data()[3];
payload = encrypted_frame.data() + HEADER;
payload_sz = encrypted_frame.size() - HEADER;
} else {
// Fallback: no counter prefix
payload = encrypted_frame.data();
payload_sz = encrypted_frame.size();
}
if (payload_sz == 0 || frame.size() < payload_sz) {
return {Result::Status::kFailedToDecrypt, 0};
}
rosetta_xchacha20_xor(frame.data(), payload, payload_sz, nonce, key_);
// Diag: log first 3 frames
int n = diag_count_.fetch_add(1, std::memory_order_relaxed);
if (n < 3) {
LOGI("DEC frame#%d: enc_sz=%zu payload=%zu nonce=%02x%02x%02x%02x",
n, encrypted_frame.size(), payload_sz,
nonce[4], nonce[5], nonce[6], nonce[7]);
diag_write("DEC frame#%d: enc_sz=%zu payload=%zu nonce[4..7]=%02x%02x%02x%02x\n",
n, encrypted_frame.size(), payload_sz,
nonce[4], nonce[5], nonce[6], nonce[7]);
}
return {Result::Status::kOk, payload_sz};
}
size_t GetMaxPlaintextByteSize(cricket::MediaType, size_t encrypted_frame_size) override {
return encrypted_frame_size; // >= actual (payload = enc - 4)
}
protected:
~XChaCha20Decryptor() override { memset(key_, 0, 32); }
private:
mutable std::atomic<int> ref_{0};
mutable std::atomic<int> diag_count_{0};
uint8_t key_[32];
};
/* ════════════════════════════════════════════════════════════════
* JNI exports
* ════════════════════════════════════════════════════════════════ */
extern "C" {
/* ── HSalsa20 for nacl.box.before() ──────────────────────────── */
JNIEXPORT jbyteArray JNICALL
Java_com_rosetta_messenger_network_XChaCha20E2EE_nativeHSalsa20(
JNIEnv *env, jclass, jbyteArray jRawDh)
{
jsize len = env->GetArrayLength(jRawDh);
if (len < 32) return nullptr;
auto *raw = (uint8_t *)env->GetByteArrayElements(jRawDh, nullptr);
uint8_t out[32];
uint8_t zeros[16] = {0};
rosetta_hsalsa20(out, zeros, raw);
env->ReleaseByteArrayElements(jRawDh, (jbyte *)raw, JNI_ABORT);
jbyteArray result = env->NewByteArray(32);
env->SetByteArrayRegion(result, 0, 32, (jbyte *)out);
memset(out, 0, 32);
return result;
}
/* ── Create / destroy encryptor ──────────────────────────────── */
JNIEXPORT jlong JNICALL
Java_com_rosetta_messenger_network_XChaCha20E2EE_nativeCreateEncryptor(
JNIEnv *env, jclass, jbyteArray jKey)
{
jsize len = env->GetArrayLength(jKey);
if (len < 32) return 0;
auto *key = (uint8_t *)env->GetByteArrayElements(jKey, nullptr);
auto *enc = new XChaCha20Encryptor(key);
env->ReleaseByteArrayElements(jKey, (jbyte *)key, JNI_ABORT);
// AddRef so the pointer we hand out has ref=1.
// WebRTC's scoped_refptr will AddRef again when it takes ownership.
enc->AddRef();
LOGI("Created XChaCha20 encryptor %p", enc);
return reinterpret_cast<jlong>(enc);
}
JNIEXPORT void JNICALL
Java_com_rosetta_messenger_network_XChaCha20E2EE_nativeReleaseEncryptor(
JNIEnv *, jclass, jlong ptr)
{
if (ptr == 0) return;
auto *enc = reinterpret_cast<XChaCha20Encryptor *>(ptr);
enc->Release();
}
/* ── Create / destroy decryptor ──────────────────────────────── */
JNIEXPORT jlong JNICALL
Java_com_rosetta_messenger_network_XChaCha20E2EE_nativeCreateDecryptor(
JNIEnv *env, jclass, jbyteArray jKey)
{
jsize len = env->GetArrayLength(jKey);
if (len < 32) return 0;
auto *key = (uint8_t *)env->GetByteArrayElements(jKey, nullptr);
auto *dec = new XChaCha20Decryptor(key);
env->ReleaseByteArrayElements(jKey, (jbyte *)key, JNI_ABORT);
dec->AddRef();
LOGI("Created XChaCha20 decryptor %p", dec);
return reinterpret_cast<jlong>(dec);
}
JNIEXPORT void JNICALL
Java_com_rosetta_messenger_network_XChaCha20E2EE_nativeReleaseDecryptor(
JNIEnv *, jclass, jlong ptr)
{
if (ptr == 0) return;
auto *dec = reinterpret_cast<XChaCha20Decryptor *>(ptr);
dec->Release();
}
/* ── Install native crash handler ─────────────────────────────── */
JNIEXPORT void JNICALL
Java_com_rosetta_messenger_network_XChaCha20E2EE_nativeInstallCrashHandler(
JNIEnv *env, jclass, jstring jPath)
{
const char *path = env->GetStringUTFChars(jPath, nullptr);
strncpy(g_crash_path, path, sizeof(g_crash_path) - 1);
env->ReleaseStringUTFChars(jPath, path);
struct sigaction sa = {};
sa.sa_sigaction = native_crash_handler;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, &g_old_sigsegv);
sigaction(SIGABRT, &sa, &g_old_sigabrt);
LOGI("Native crash handler installed, path=%s", g_crash_path);
}
/* ── Open diagnostics file for E2EE frame logging ────────────── */
JNIEXPORT void JNICALL
Java_com_rosetta_messenger_network_XChaCha20E2EE_nativeOpenDiagFile(
JNIEnv *env, jclass, jstring jPath)
{
if (g_diag_fd >= 0) { close(g_diag_fd); g_diag_fd = -1; }
const char *path = env->GetStringUTFChars(jPath, nullptr);
strncpy(g_diag_path, path, sizeof(g_diag_path) - 1);
g_diag_fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
env->ReleaseStringUTFChars(jPath, path);
if (g_diag_fd >= 0) {
diag_write("=== E2EE DIAGNOSTICS ===\n");
LOGI("Diag file opened: %s", g_diag_path);
} else {
LOGE("Failed to open diag file: %s", g_diag_path);
}
}
JNIEXPORT void JNICALL
Java_com_rosetta_messenger_network_XChaCha20E2EE_nativeCloseDiagFile(
JNIEnv *, jclass)
{
if (g_diag_fd >= 0) {
diag_write("=== END ===\n");
close(g_diag_fd);
g_diag_fd = -1;
}
}
} /* extern "C" */