Prepare attachments with an independent keypair and both party wraps
This commit is contained in:
1 parent
08f9efb02b
commit
de94b5bbaf
2 files changed
+56
-11
No files matched your search
+50
-11
@@ -45,6 +45,7 @@ struct dm_file_job {
|
||||
enum dm_file_work work;
|
||||
struct file_transfer_op* transfer;
|
||||
uint8_t id[16], key[32], hash[32];
|
||||
uint8_t wrapped[DM_FILE_KEYS_SIZE]; /* Подписывается с metadata после подготовки. */
|
||||
uint64_t author, size;
|
||||
char conv[64];
|
||||
struct attachment_info info;
|
||||
@@ -88,6 +89,46 @@ const uint8_t* dm_message_media(const uint8_t* msg, size_t len) {
|
||||
return !dm_message_parse(msg, len, &view) && view.size == len ? view.media : NULL;
|
||||
}
|
||||
|
||||
int dm_media_metadata_decode(const uint8_t* data, size_t size, struct attachment_info* info, const uint8_t** keys) {
|
||||
if (!data || !info || !keys) { DEBUG_ERROR(DEBUG_CATEGORY_DM, "DM metadata: invalid arguments"); return -1; }
|
||||
const uint8_t* wrapped = NULL;
|
||||
if (size >= 8 && !memcmp(data, "DMKEY001", 8)) {
|
||||
if (size <= DM_MEDIA_KEYS_HEADER_SIZE) { DEBUG_WARN(DEBUG_CATEGORY_DM, "DM metadata: truncated file keys"); return -1; }
|
||||
wrapped = data + 8; data += DM_MEDIA_KEYS_HEADER_SIZE; size -= DM_MEDIA_KEYS_HEADER_SIZE;
|
||||
}
|
||||
if (attachment_decode(data, size, info)) return -1;
|
||||
*keys = wrapped; return 0;
|
||||
}
|
||||
|
||||
/* Получение ключа разрешено только действующему участнику закреплённой беседы. */
|
||||
static int dm_file_key(struct UTUN_INSTANCE* inst, const uint8_t* body, size_t len, uint8_t out[32]) {
|
||||
struct dm_message_view view;
|
||||
if (dm_message_parse(body, len, &view) || !view.media || view.size != len) return -1;
|
||||
char conv[64]; snprintf(conv, sizeof(conv), "%llu", (unsigned long long)view.conv);
|
||||
struct dm_party local[2], parties[2]; uint8_t key[32], secret[32], plain[DM_DATA_MAX], nonce[DM_NONCE_SIZE];
|
||||
if (dm_conversation_access(inst, conv, local, key)) return -1;
|
||||
size_t size = 0; const uint8_t* wrapped; struct attachment_info info; int result = -1;
|
||||
dm_build_nonce(view.conv, view.author, view.seq, nonce);
|
||||
if (media_ccm_decrypt(key, nonce, view.ciphertext, view.ciphertext_size, plain, &size) ||
|
||||
dm_media_metadata_decode(plain, size, &info, &wrapped)) goto done;
|
||||
if (!wrapped) {
|
||||
if (view.mode != DM_MODE_DEVICE) { DEBUG_WARN(DEBUG_CATEGORY_DM, "DM file: GROUPS requires independent file keys"); goto done; }
|
||||
memcpy(out, key, 32); result = 0; goto done;
|
||||
}
|
||||
unsigned side = view.author == inst->node_id ? 0 : 1;
|
||||
if (view.mode == DM_MODE_GROUPS) {
|
||||
int member = dm_message_target(&view, inst->node_id, NULL, NULL);
|
||||
if (member < 0) goto done;
|
||||
side = (unsigned)member; memcpy(parties, view.parties, sizeof(parties));
|
||||
} else { parties[side] = local[0]; parties[1 - side] = local[1]; }
|
||||
if (!dm_party_private(inst, parties + side, secret))
|
||||
result = dm_file_keys_open(view.conv, view.author, view.media, parties, side, secret, wrapped, out);
|
||||
done:
|
||||
OPENSSL_cleanse(key, sizeof(key)); OPENSSL_cleanse(secret, sizeof(secret)); OPENSSL_cleanse(plain, sizeof(plain));
|
||||
if (result) DEBUG_WARN(DEBUG_CATEGORY_DM, "DM file: key unavailable conv=%s author=%llu", conv, (unsigned long long)view.author);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t dm_media_cipher_size(uint64_t size) {
|
||||
return size + (size ? (size + DM_MEDIA_PLAIN_SIZE - 1) / DM_MEDIA_PLAIN_SIZE : 1) * DM_TAG_SIZE;
|
||||
}
|
||||
@@ -350,6 +391,7 @@ int dm_media_track(struct UTUN_INSTANCE* inst, int dir, const uint8_t* msg, size
|
||||
sqlite3_bind_text(st, 1, id, -1, SQLITE_STATIC);
|
||||
int rc = sqlite3_step(st);
|
||||
if (rc != SQLITE_ROW) { DEBUG_ERROR(DEBUG_CATEGORY_DM, "dm_media: conversation absent conv=%s rc=%d", id, rc); sqlite3_finalize(st); return -1; }
|
||||
dir = author == inst->node_id; /* Здесь направление означает владение оригиналом, а не отображение своей реплики. */
|
||||
recipient = dir ? (uint64_t)sqlite3_column_int64(st, 0) : inst->node_id;
|
||||
group = (uint64_t)sqlite3_column_int64(st, 1);
|
||||
sqlite3_finalize(st);
|
||||
@@ -581,11 +623,13 @@ static void dm_file_done(void* arg, int error) {
|
||||
(unsigned long long)job->generation, job->work); goto fail;
|
||||
}
|
||||
if (job->work == DM_FILE_PREPARE) {
|
||||
uint8_t data[DM_MEDIA_DESC_SIZE + ATTACHMENT_WIRE_MAX];
|
||||
uint8_t data[DM_MEDIA_DESC_SIZE + DM_MEDIA_KEYS_HEADER_SIZE + ATTACHMENT_WIRE_MAX];
|
||||
memcpy(data, job->id, 16); memcpy(data + 16, &job->size, 8); memcpy(data + 24, job->hash, 32);
|
||||
int n = attachment_encode(&job->info, data + DM_MEDIA_DESC_SIZE, ATTACHMENT_WIRE_MAX);
|
||||
memcpy(data + DM_MEDIA_DESC_SIZE, "DMKEY001", 8);
|
||||
memcpy(data + DM_MEDIA_DESC_SIZE + 8, job->wrapped, sizeof(job->wrapped));
|
||||
int n = attachment_encode(&job->info, data + DM_MEDIA_DESC_SIZE + DM_MEDIA_KEYS_HEADER_SIZE, ATTACHMENT_WIRE_MAX);
|
||||
if (n < 0) goto fail;
|
||||
if (dm_send(inst, job->conv, "file", data, (uint32_t)(DM_MEDIA_DESC_SIZE + n))) goto fail;
|
||||
if (dm_send(inst, job->conv, "file", data, (uint32_t)(DM_MEDIA_DESC_SIZE + DM_MEDIA_KEYS_HEADER_SIZE + n))) goto fail;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DM, "dm_media: prepared conv=%s bytes=%llu name=%s", job->conv,
|
||||
(unsigned long long)job->size, job->info.name);
|
||||
chat_event_attachment(inst, 1, job->conv, job->id, 1);
|
||||
@@ -761,14 +805,9 @@ int dm_send_file(struct UTUN_INSTANCE* inst, const char* conv, const char* path,
|
||||
}
|
||||
snprintf(job->conv, sizeof(job->conv), "%s", conv); job->info = *info; job->remove_source = remove_source;
|
||||
snprintf(job->input, sizeof(job->input), "%s", path);
|
||||
sqlite3_stmt* st = dm_media_prepare(inst, "SELECT peer_x25519 FROM dm_conversations WHERE conv_id=?");
|
||||
if (!st) { u_free(job); return -1; }
|
||||
sqlite3_bind_text(st, 1, conv, -1, SQLITE_STATIC);
|
||||
uint8_t pub[32];
|
||||
int rc = sqlite3_step(st), found = rc == SQLITE_ROW && sqlite3_column_bytes(st, 0) == sizeof(pub);
|
||||
if (found) memcpy(pub, sqlite3_column_blob(st, 0), sizeof(pub));
|
||||
sqlite3_finalize(st);
|
||||
if (!found || dm_derive_content_key(inst->my_keys.private_key, pub, job->key) || RAND_bytes(job->id, sizeof(job->id)) != 1 ||
|
||||
struct dm_party parties[2];
|
||||
if (dm_conversation_access(inst, conv, parties, NULL) || RAND_bytes(job->id, sizeof(job->id)) != 1 ||
|
||||
dm_file_keys_create(strtoull(conv, NULL, 10), inst->node_id, job->id, parties, job->wrapped, job->key) ||
|
||||
dm_file_path(media, 0, job->id, ".file", job->plain, sizeof(job->plain)) ||
|
||||
dm_file_path(media, 0, job->id, ".cipher", job->cipher, sizeof(job->cipher)) ||
|
||||
dm_file_path(media, 0, job->id, ".encrypt", job->temp, sizeof(job->temp))) {
|
||||
|
||||
@@ -21,6 +21,12 @@ struct UTUN_INSTANCE;
|
||||
#define DM_MEDIA_DESC_SIZE 56 /* UUID:16, plaintext_size:8 native-endian, SHA256(ciphertext):32. */
|
||||
#define DM_MEDIA_RECEIPT_SIZE 152
|
||||
#define DM_MEDIA_MAX_SIZE (1024ULL * 1024 * 1024)
|
||||
#define DM_MEDIA_KEYS_HEADER_SIZE 232 /* "DMKEY001" и DM_FILE_KEYS_SIZE байт. */
|
||||
|
||||
/* Расшифрованная metadata: magic + две упаковки ключа + attachment_info.
|
||||
* keys получает заимствованный блок ключей либо NULL для сохранённого DEVICE-файла
|
||||
* прежнего формата. 0/-1; info/keys обязательны, живут вместе с data. */
|
||||
int dm_media_metadata_decode(const uint8_t* data, size_t size, struct attachment_info* info, const uint8_t** keys);
|
||||
|
||||
/* ETCP_RT_ID_DM_MEDIA=0x38: [cmd:1][body], router-подпись обязательна; числа native-endian.
|
||||
* OFFER: [recipient:8][holder:8][expires:8][каноническое file-сообщение]. A предлагает себя с expires=0.
|
||||
|
||||
Reference in new issue
Block a user