about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/event/server_events.c2
-rw-r--r--src/event/server_events.h2
-rw-r--r--src/omemo/omemo.c103
-rw-r--r--src/omemo/store.c44
-rw-r--r--src/omemo/store.h198
-rw-r--r--src/xmpp/message.c18
-rw-r--r--src/xmpp/stanza.c2
7 files changed, 326 insertions, 43 deletions
diff --git a/src/event/server_events.c b/src/event/server_events.c
index b8ee36cf..33712328 100644
--- a/src/event/server_events.c
+++ b/src/event/server_events.c
@@ -510,7 +510,7 @@ sv_ev_incoming_message(char *barejid, char *resource, char *message, char *pgp_m
 }
 
 void
-sv_ev_incoming_carbon(char *barejid, char *resource, char *message, char *pgp_message)
+sv_ev_incoming_carbon(char *barejid, char *resource, char *message, char *pgp_message, gboolean omemo)
 {
     gboolean new_win = FALSE;
     ProfChatWin *chatwin = wins_get_chat(barejid);
diff --git a/src/event/server_events.h b/src/event/server_events.h
index b12ac94c..96fdb58c 100644
--- a/src/event/server_events.h
+++ b/src/event/server_events.h
@@ -74,7 +74,7 @@ void sv_ev_room_banned(const char *const room, const char *const actor, const ch
 void sv_ev_room_occupent_banned(const char *const room, const char *const nick, const char *const actor,
     const char *const reason);
 void sv_ev_outgoing_carbon(char *barejid, char *message, char *pgp_message);
-void sv_ev_incoming_carbon(char *barejid, char *resource, char *message, char *pgp_message);
+void sv_ev_incoming_carbon(char *barejid, char *resource, char *message, char *pgp_message, gboolean omemo);
 void sv_ev_xmpp_stanza(const char *const msg);
 void sv_ev_muc_self_online(const char *const room, const char *const nick, gboolean config_required,
     const char *const role, const char *const affiliation, const char *const actor, const char *const reason,
diff --git a/src/omemo/omemo.c b/src/omemo/omemo.c
index 4ca8bd47..6e964e81 100644
--- a/src/omemo/omemo.c
+++ b/src/omemo/omemo.c
@@ -42,8 +42,7 @@ struct omemo_context_t {
     GHashTable *device_list_handler;
     ratchet_identity_key_pair *identity_key_pair;
     uint32_t registration_id;
-    signal_protocol_key_helper_pre_key_list_node *pre_keys_head;
-    session_signed_pre_key *signed_pre_key;
+    uint32_t signed_pre_key_id;
     signal_protocol_store_context *store;
     GHashTable *session_store;
     GHashTable *pre_key_store;
@@ -132,7 +131,7 @@ omemo_init(void)
         .contains_signed_pre_key = contains_signed_pre_key,
         .remove_signed_pre_key = remove_signed_pre_key,
         .destroy_func = NULL,
-        .user_data = omemo_ctx.pre_key_store
+        .user_data = omemo_ctx.signed_pre_key_store
     };
     signal_protocol_store_context_set_signed_pre_key_store(omemo_ctx.store, &signed_pre_key_store);
 
@@ -246,12 +245,23 @@ omemo_generate_crypto_materials(ProfAccount *account)
 static void
 omemo_generate_short_term_crypto_materials(ProfAccount *account)
 {
-    signal_protocol_key_helper_generate_pre_keys(&omemo_ctx.pre_keys_head, randombytes_random(), 100, omemo_ctx.signal);
+    signal_protocol_key_helper_pre_key_list_node *pre_keys_head;
+    signal_protocol_key_helper_generate_pre_keys(&pre_keys_head, randombytes_random(), 100, omemo_ctx.signal);
 
+    session_signed_pre_key *signed_pre_key;
     struct timeval tv;
     gettimeofday(&tv, NULL);
     unsigned long long timestamp = (unsigned long long)(tv.tv_sec) * 1000 + (unsigned long long)(tv.tv_usec) / 1000;
-    signal_protocol_key_helper_generate_signed_pre_key(&omemo_ctx.signed_pre_key, omemo_ctx.identity_key_pair, 5, timestamp, omemo_ctx.signal);
+
+    omemo_ctx.signed_pre_key_id = 1;
+    signal_protocol_key_helper_generate_signed_pre_key(&signed_pre_key, omemo_ctx.identity_key_pair, omemo_ctx.signed_pre_key_id, timestamp, omemo_ctx.signal);
+
+    signal_protocol_key_helper_pre_key_list_node *p;
+    for (p = pre_keys_head; p != NULL; p = signal_protocol_key_helper_key_list_next(p)) {
+        session_pre_key *prekey = signal_protocol_key_helper_key_list_element(p);
+        signal_protocol_pre_key_store_key(omemo_ctx.store, prekey);
+    }
+    signal_protocol_signed_pre_key_store_key(omemo_ctx.store, signed_pre_key);
 
     loaded = TRUE;
 
@@ -308,8 +318,11 @@ omemo_identity_key(unsigned char **output, size_t *length)
 void
 omemo_signed_prekey(unsigned char **output, size_t *length)
 {
+    session_signed_pre_key *signed_pre_key;
     signal_buffer *buffer = NULL;
-    ec_public_key_serialize(&buffer, ec_key_pair_get_public(session_signed_pre_key_get_key_pair(omemo_ctx.signed_pre_key)));
+
+    signal_protocol_signed_pre_key_load_key(omemo_ctx.store, &signed_pre_key, omemo_ctx.signed_pre_key_id);
+    ec_public_key_serialize(&buffer, ec_key_pair_get_public(session_signed_pre_key_get_key_pair(signed_pre_key)));
     *length = signal_buffer_len(buffer);
     *output = malloc(*length);
     memcpy(*output, signal_buffer_data(buffer), *length);
@@ -319,25 +332,36 @@ omemo_signed_prekey(unsigned char **output, size_t *length)
 void
 omemo_signed_prekey_signature(unsigned char **output, size_t *length)
 {
-    *length = session_signed_pre_key_get_signature_len(omemo_ctx.signed_pre_key);
+    session_signed_pre_key *signed_pre_key;
+
+    signal_protocol_signed_pre_key_load_key(omemo_ctx.store, &signed_pre_key, omemo_ctx.signed_pre_key_id);
+    *length = session_signed_pre_key_get_signature_len(signed_pre_key);
     *output = malloc(*length);
-    memcpy(*output, session_signed_pre_key_get_signature(omemo_ctx.signed_pre_key), *length);
+    memcpy(*output, session_signed_pre_key_get_signature(signed_pre_key), *length);
 }
 
 void
 omemo_prekeys(GList **prekeys, GList **ids, GList **lengths)
 {
-    signal_protocol_key_helper_pre_key_list_node *p;
-    for (p = omemo_ctx.pre_keys_head; p != NULL; p = signal_protocol_key_helper_key_list_next(p)) {
-        session_pre_key *prekey = signal_protocol_key_helper_key_list_element(p);
-        signal_buffer *buffer = NULL;
-        ec_public_key_serialize(&buffer, ec_key_pair_get_public(session_pre_key_get_key_pair(prekey)));
-        size_t length = signal_buffer_len(buffer);
+    GHashTableIter iter;
+    gpointer id;
+
+    g_hash_table_iter_init(&iter, omemo_ctx.pre_key_store);
+    while (g_hash_table_iter_next(&iter, &id, NULL)) {
+        session_pre_key *pre_key;
+        int ret;
+        ret = signal_protocol_pre_key_load_key(omemo_ctx.store, &pre_key, GPOINTER_TO_INT(id));
+        if (ret != SG_SUCCESS) {
+            continue;
+        }
+
+        signal_buffer *public_key;
+        ec_public_key_serialize(&public_key, ec_key_pair_get_public(session_pre_key_get_key_pair(pre_key)));
+        size_t length = signal_buffer_len(public_key);
         unsigned char *prekey_value = malloc(length);
-        memcpy(prekey_value, signal_buffer_data(buffer), length);
-        signal_buffer_free(buffer);
+        memcpy(prekey_value, signal_buffer_data(public_key), length);
         *prekeys = g_list_append(*prekeys, prekey_value);
-        *ids = g_list_append(*ids, GINT_TO_POINTER(session_pre_key_get_id(prekey)));
+        *ids = g_list_append(*ids, GINT_TO_POINTER(id));
         *lengths = g_list_append(*lengths, GINT_TO_POINTER(length));
     }
 }
@@ -349,6 +373,10 @@ omemo_set_device_list(const char *const jid, GList * device_list)
     char *barejid = xmpp_jid_bare(ctx, jid);
 
     g_hash_table_insert(omemo_ctx.device_list, strdup(barejid), device_list);
+    if (strchr(barejid, '@') == NULL) {
+        // barejid is server so we should handle it as our own device list
+        g_hash_table_insert(omemo_ctx.device_list_handler, strdup(barejid), handle_own_device_list);
+    }
 
     OmemoDeviceListHandler handler = g_hash_table_lookup(omemo_ctx.device_list_handler, barejid);
     if (handler) {
@@ -397,16 +425,13 @@ omemo_on_message_send(ProfChatWin *chatwin, const char *const message, gboolean
     int res;
     xmpp_ctx_t * const ctx = connection_get_ctx();
     char *barejid = xmpp_jid_bare(ctx, session_get_account_name());
-    GList *device_ids = NULL;
 
     GList *recipient_device_id = g_hash_table_lookup(omemo_ctx.device_list, chatwin->barejid);
     if (!recipient_device_id) {
         return FALSE;
     }
-    device_ids = g_list_copy(recipient_device_id);
 
     GList *sender_device_id = g_hash_table_lookup(omemo_ctx.device_list, barejid);
-    device_ids = g_list_concat(device_ids, g_list_copy(sender_device_id));
 
     /* TODO generate fresh AES-GCM materials */
     /* TODO encrypt message */
@@ -430,7 +455,7 @@ omemo_on_message_send(ProfChatWin *chatwin, const char *const message, gboolean
 
     GList *keys = NULL;
     GList *device_ids_iter;
-    for (device_ids_iter = device_ids; device_ids_iter != NULL; device_ids_iter = device_ids_iter->next) {
+    for (device_ids_iter = recipient_device_id; device_ids_iter != NULL; device_ids_iter = device_ids_iter->next) {
         int res;
         ciphertext_message *ciphertext;
         session_cipher *cipher;
@@ -452,7 +477,33 @@ omemo_on_message_send(ProfChatWin *chatwin, const char *const message, gboolean
         key->data = signal_buffer_data(buffer);
         key->length = signal_buffer_len(buffer);
         key->device_id = GPOINTER_TO_INT(device_ids_iter->data);
-        key->prekey = TRUE;
+        key->prekey = ciphertext_message_get_type(ciphertext) == CIPHERTEXT_PREKEY_TYPE;
+        keys = g_list_append(keys, key);
+    }
+
+    for (device_ids_iter = sender_device_id; device_ids_iter != NULL; device_ids_iter = device_ids_iter->next) {
+        int res;
+        ciphertext_message *ciphertext;
+        session_cipher *cipher;
+        signal_protocol_address address = {
+            barejid, strlen(barejid), GPOINTER_TO_INT(device_ids_iter->data)
+        };
+
+        res = session_cipher_create(&cipher, omemo_ctx.store, &address, omemo_ctx.signal);
+        if (res != 0) {
+            continue;
+        }
+
+        res = session_cipher_encrypt(cipher, key, AES128_GCM_KEY_LENGTH, &ciphertext);
+        if (res != 0) {
+            continue;
+        }
+        signal_buffer *buffer = ciphertext_message_get_serialized(ciphertext);
+        omemo_key_t *key = malloc(sizeof(omemo_key_t));
+        key->data = signal_buffer_data(buffer);
+        key->length = signal_buffer_len(buffer);
+        key->device_id = GPOINTER_TO_INT(device_ids_iter->data);
+        key->prekey = ciphertext_message_get_type(ciphertext) == CIPHERTEXT_PREKEY_TYPE;
         keys = g_list_append(keys, key);
     }
 
@@ -465,7 +516,6 @@ omemo_on_message_send(ProfChatWin *chatwin, const char *const message, gboolean
     free(ciphertext);
     sodium_free(key);
     sodium_free(iv);
-    g_list_free(device_ids);
 
     return TRUE;
 }
@@ -544,7 +594,7 @@ unlock(void *user_data)
 static void
 omemo_log(int level, const char *message, size_t len, void *user_data)
 {
-        cons_show(message);
+        cons_show("OMEMO: %s", message);
 }
 
 static gboolean
@@ -559,6 +609,11 @@ handle_own_device_list(const char *const jid, GList *device_list)
         omemo_devicelist_publish(device_list);
     }
 
+    GList *device_id;
+    for (device_id = device_list; device_id != NULL; device_id = device_id->next) {
+        omemo_bundle_request(jid, GPOINTER_TO_INT(device_id->data), omemo_start_device_session_handle_bundle, free, strdup(jid));
+    }
+
     return TRUE;
 }
 
diff --git a/src/omemo/store.c b/src/omemo/store.c
index ab8cd81b..9fec33e5 100644
--- a/src/omemo/store.c
+++ b/src/omemo/store.c
@@ -96,9 +96,19 @@ store_session(const signal_protocol_address *address, uint8_t *record,
 int
 contains_session(const signal_protocol_address *address, void *user_data)
 {
-    signal_buffer *record;
-    load_session(&record, address, user_data);
-    return record != NULL;
+    GHashTable *session_store = (GHashTable *)user_data;
+    GHashTable *device_store = NULL;
+
+    device_store = g_hash_table_lookup(session_store, address->name);
+    if (!device_store) {
+        return 0;
+    }
+
+    if (!g_hash_table_lookup(device_store, GINT_TO_POINTER(address->device_id))) {
+        return 0;
+    }
+
+    return 1;
 }
 
 int
@@ -134,9 +144,15 @@ delete_all_sessions(const char *name, size_t name_len, void *user_data)
 int
 load_pre_key(signal_buffer **record, uint32_t pre_key_id, void *user_data)
 {
+    signal_buffer *original;
     GHashTable *pre_key_store = (GHashTable *)user_data;
 
-    *record = g_hash_table_lookup(pre_key_store, GINT_TO_POINTER(pre_key_id));
+    original = g_hash_table_lookup(pre_key_store, GINT_TO_POINTER(pre_key_id));
+    if (original == NULL) {
+        return SG_ERR_INVALID_KEY_ID;
+    }
+
+    *record = signal_buffer_copy(original);
     return SG_SUCCESS;
 }
 
@@ -154,10 +170,9 @@ store_pre_key(uint32_t pre_key_id, uint8_t *record, size_t record_len,
 int
 contains_pre_key(uint32_t pre_key_id, void *user_data)
 {
-    signal_buffer *record;
-    load_pre_key(&record, pre_key_id, user_data);
+    GHashTable *pre_key_store = (GHashTable *)user_data;
 
-    return record != NULL;
+    return g_hash_table_lookup(pre_key_store, GINT_TO_POINTER(pre_key_id)) != NULL;
 }
 
 int
@@ -172,9 +187,15 @@ int
 load_signed_pre_key(signal_buffer **record, uint32_t signed_pre_key_id,
     void *user_data)
 {
+    signal_buffer *original;
     GHashTable *signed_pre_key_store = (GHashTable *)user_data;
 
-    *record = g_hash_table_lookup(signed_pre_key_store, GINT_TO_POINTER(signed_pre_key_id));
+    original = g_hash_table_lookup(signed_pre_key_store, GINT_TO_POINTER(signed_pre_key_id));
+    if (!original) {
+        return SG_ERR_INVALID_KEY_ID;
+    }
+
+    *record = signal_buffer_copy(original);
     return SG_SUCCESS;
 }
 
@@ -192,10 +213,9 @@ store_signed_pre_key(uint32_t signed_pre_key_id, uint8_t *record,
 int
 contains_signed_pre_key(uint32_t signed_pre_key_id, void *user_data)
 {
-    signal_buffer *record;
-    load_signed_pre_key(&record, signed_pre_key_id, user_data);
+    GHashTable *signed_pre_key_store = (GHashTable *)user_data;
 
-    return record != NULL;
+    return g_hash_table_lookup(signed_pre_key_store, GINT_TO_POINTER(signed_pre_key_id)) != NULL;
 }
 
 int
@@ -251,7 +271,7 @@ is_trusted_identity(const signal_protocol_address *address, uint8_t *key_data,
     signal_buffer *buffer = signal_buffer_create(key_data, key_len);
     signal_buffer *original = g_hash_table_lookup(identity_key_store->identity_key_store, node);
 
-    return original == NULL || signal_buffer_compare(buffer, original);
+    return original == NULL || signal_buffer_compare(buffer, original) == 0;
 }
 
 int
diff --git a/src/omemo/store.h b/src/omemo/store.h
index 491e1495..ad33d5a5 100644
--- a/src/omemo/store.h
+++ b/src/omemo/store.h
@@ -18,23 +18,221 @@ GHashTable * pre_key_store_new(void);
 GHashTable * signed_pre_key_store_new(void);
 void identity_key_store_new(identity_key_store_t *identity_key_store);
 
+/**
+ * Returns a copy of the serialized session record corresponding to the
+ * provided recipient ID + device ID tuple.
+ *
+ * @param record pointer to a freshly allocated buffer containing the
+ *     serialized session record. Unset if no record was found.
+ *     The Signal Protocol library is responsible for freeing this buffer.
+ * @param address the address of the remote client
+ * @return 1 if the session was loaded, 0 if the session was not found, negative on failure
+ */
 int load_session(signal_buffer **record, const signal_protocol_address *address, void *user_data);
+
+/**
+ * Returns all known devices with active sessions for a recipient
+ *
+ * @param pointer to an array that will be allocated and populated with the result
+ * @param name the name of the remote client
+ * @param name_len the length of the name
+ * @return size of the sessions array, or negative on failure
+ */
 int get_sub_device_sessions(signal_int_list **sessions, const char *name, size_t name_len, void *user_data);
+
+/**
+ * Commit to storage the session record for a given
+ * recipient ID + device ID tuple.
+ *
+ * @param address the address of the remote client
+ * @param record pointer to a buffer containing the serialized session
+ *     record for the remote client
+ * @param record_len length of the serialized session record
+ * @return 0 on success, negative on failure
+ */
 int store_session(const signal_protocol_address *address, uint8_t *record, size_t record_len, void *user_data);
+
+/**
+ * Determine whether there is a committed session record for a
+ * recipient ID + device ID tuple.
+ *
+ * @param address the address of the remote client
+ * @return 1 if a session record exists, 0 otherwise.
+ */
 int contains_session(const signal_protocol_address *address, void *user_data);
+
+/**
+ * Remove a session record for a recipient ID + device ID tuple.
+ *
+ * @param address the address of the remote client
+ * @return 1 if a session was deleted, 0 if a session was not deleted, negative on error
+ */
 int delete_session(const signal_protocol_address *address, void *user_data);
+
+/**
+ * Remove the session records corresponding to all devices of a recipient ID.
+ *
+ * @param name the name of the remote client
+ * @param name_len the length of the name
+ * @return the number of deleted sessions on success, negative on failure
+ */
 int delete_all_sessions(const char *name, size_t name_len, void *user_data);
+
+/**
+ * Load a local serialized PreKey record.
+ *
+ * @param record pointer to a newly allocated buffer containing the record,
+ *     if found. Unset if no record was found.
+ *     The Signal Protocol library is responsible for freeing this buffer.
+ * @param pre_key_id the ID of the local serialized PreKey record
+ * @retval SG_SUCCESS if the key was found
+ * @retval SG_ERR_INVALID_KEY_ID if the key could not be found
+ */
 int load_pre_key(signal_buffer **record, uint32_t pre_key_id, void *user_data);
+
+/**
+ * Store a local serialized PreKey record.
+ *
+ * @param pre_key_id the ID of the PreKey record to store.
+ * @param record pointer to a buffer containing the serialized record
+ * @param record_len length of the serialized record
+ * @return 0 on success, negative on failure
+ */
 int store_pre_key(uint32_t pre_key_id, uint8_t *record, size_t record_len, void *user_data);
+
+/**
+ * Determine whether there is a committed PreKey record matching the
+ * provided ID.
+ *
+ * @param pre_key_id A PreKey record ID.
+ * @return 1 if the store has a record for the PreKey ID, 0 otherwise
+ */
 int contains_pre_key(uint32_t pre_key_id, void *user_data);
+
+/**
+ * Delete a PreKey record from local storage.
+ *
+ * @param pre_key_id The ID of the PreKey record to remove.
+ * @return 0 on success, negative on failure
+ */
 int remove_pre_key(uint32_t pre_key_id, void *user_data);
+
+/**
+ * Load a local serialized signed PreKey record.
+ *
+ * @param record pointer to a newly allocated buffer containing the record,
+ *     if found. Unset if no record was found.
+ *     The Signal Protocol library is responsible for freeing this buffer.
+ * @param signed_pre_key_id the ID of the local signed PreKey record
+ * @retval SG_SUCCESS if the key was found
+ * @retval SG_ERR_INVALID_KEY_ID if the key could not be found
+ */
 int load_signed_pre_key(signal_buffer **record, uint32_t signed_pre_key_id, void *user_data);
+
+/**
+ * Store a local serialized signed PreKey record.
+ *
+ * @param signed_pre_key_id the ID of the signed PreKey record to store
+ * @param record pointer to a buffer containing the serialized record
+ * @param record_len length of the serialized record
+ * @return 0 on success, negative on failure
+ */
 int store_signed_pre_key(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *user_data);
+
+/**
+ * Determine whether there is a committed signed PreKey record matching
+ * the provided ID.
+ *
+ * @param signed_pre_key_id A signed PreKey record ID.
+ * @return 1 if the store has a record for the signed PreKey ID, 0 otherwise
+ */
 int contains_signed_pre_key(uint32_t signed_pre_key_id, void *user_data);
+
+/**
+ * Delete a SignedPreKeyRecord from local storage.
+ *
+ * @param signed_pre_key_id The ID of the signed PreKey record to remove.
+ * @return 0 on success, negative on failure
+ */
 int remove_signed_pre_key(uint32_t signed_pre_key_id, void *user_data);
+
+/**
+ * Get the local client's identity key pair.
+ *
+ * @param public_data pointer to a newly allocated buffer containing the
+ *     public key, if found. Unset if no record was found.
+ *     The Signal Protocol library is responsible for freeing this buffer.
+ * @param private_data pointer to a newly allocated buffer containing the
+ *     private key, if found. Unset if no record was found.
+ *     The Signal Protocol library is responsible for freeing this buffer.
+ * @return 0 on success, negative on failure
+ */
 int get_identity_key_pair(signal_buffer **public_data, signal_buffer **private_data, void *user_data);
+
+/**
+ * Return the local client's registration ID.
+ *
+ * Clients should maintain a registration ID, a random number
+ * between 1 and 16380 that's generated once at install time.
+ *
+ * @param registration_id pointer to be set to the local client's
+ *     registration ID, if it was successfully retrieved.
+ * @return 0 on success, negative on failure
+ */
 int get_local_registration_id(void *user_data, uint32_t *registration_id);
+
+/**
+ * Save a remote client's identity key
+ * <p>
+ * Store a remote client's identity key as trusted.
+ * The value of key_data may be null. In this case remove the key data
+ * from the identity store, but retain any metadata that may be kept
+ * alongside it.
+ *
+ * @param address the address of the remote client
+ * @param key_data Pointer to the remote client's identity key, may be null
+ * @param key_len Length of the remote client's identity key
+ * @return 0 on success, negative on failure
+ */
 int save_identity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *user_data);
+
+/**
+ * Verify a remote client's identity key.
+ *
+ * Determine whether a remote client's identity is trusted.  Convention is
+ * that the TextSecure protocol is 'trust on first use.'  This means that
+ * an identity key is considered 'trusted' if there is no entry for the recipient
+ * in the local store, or if it matches the saved key for a recipient in the local
+ * store.  Only if it mismatches an entry in the local store is it considered
+ * 'untrusted.'
+ *
+ * @param address the address of the remote client
+ * @param identityKey The identity key to verify.
+ * @param key_data Pointer to the identity key to verify
+ * @param key_len Length of the identity key to verify
+ * @return 1 if trusted, 0 if untrusted, negative on failure
+ */
 int is_trusted_identity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *user_data);
+
+/**
+ * Store a serialized sender key record for a given
+ * (groupId + senderId + deviceId) tuple.
+ *
+ * @param sender_key_name the (groupId + senderId + deviceId) tuple
+ * @param record pointer to a buffer containing the serialized record
+ * @param record_len length of the serialized record
+ * @return 0 on success, negative on failure
+ */
 int store_sender_key(const signal_protocol_sender_key_name *sender_key_name, uint8_t *record, size_t record_len, uint8_t *user_record, size_t user_record_len, void *user_data);
+
+/**
+ * Returns a copy of the sender key record corresponding to the
+ * (groupId + senderId + deviceId) tuple.
+ *
+ * @param record pointer to a newly allocated buffer containing the record,
+ *     if found. Unset if no record was found.
+ *     The Signal Protocol library is responsible for freeing this buffer.
+ * @param sender_key_name the (groupId + senderId + deviceId) tuple
+ * @return 1 if the record was loaded, 0 if the record was not found, negative on failure
+ */
 int load_sender_key(signal_buffer **record, signal_buffer **user_record, const signal_protocol_sender_key_name *sender_key_name, void *user_data);
diff --git a/src/xmpp/message.c b/src/xmpp/message.c
index 08ff4530..28fe96d8 100644
--- a/src/xmpp/message.c
+++ b/src/xmpp/message.c
@@ -840,6 +840,7 @@ _private_chat_handler(xmpp_stanza_t *const stanza, const char *const fulljid)
 static gboolean
 _handle_carbons(xmpp_stanza_t *const stanza)
 {
+    char *message_txt;
     xmpp_stanza_t *carbons = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_CARBONS);
     if (!carbons) {
         return FALSE;
@@ -873,10 +874,19 @@ _handle_carbons(xmpp_stanza_t *const stanza)
         return TRUE;
     }
 
-    char *message_txt = xmpp_message_get_body(message);
+    // check omemo encryption
+    gboolean omemo = FALSE;
+#ifdef HAVE_OMEMO
+    message_txt = omemo_receive_message(message);
+    omemo = message_txt != NULL;
+#endif
+
     if (!message_txt) {
-        log_warning("Carbon received with no message.");
-        return TRUE;
+        message_txt = xmpp_message_get_body(message);
+        if (!message_txt) {
+            log_warning("Carbon received with no message.");
+            return TRUE;
+        }
     }
 
     Jid *my_jid = jid_create(connection_get_fulljid());
@@ -904,7 +914,7 @@ _handle_carbons(xmpp_stanza_t *const stanza)
 
     // if we are the recipient, treat as standard incoming message
     if (g_strcmp0(my_jid->barejid, jid_to->barejid) == 0) {
-        sv_ev_incoming_carbon(jid_from->barejid, jid_from->resourcepart, message_txt, enc_message);
+        sv_ev_incoming_carbon(jid_from->barejid, jid_from->resourcepart, message_txt, enc_message, omemo);
 
     // else treat as a sent message
     } else {
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c
index 302258ec..61086f77 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -2251,7 +2251,7 @@ stanza_create_omemo_bundle_publish(xmpp_ctx_t *ctx, uint32_t device_id,
         xmpp_stanza_t *prekey = xmpp_stanza_new(ctx);
         xmpp_stanza_set_name(prekey, "preKeyPublic");
         char *id = g_strdup_printf("%d", GPOINTER_TO_INT(i->data));
-        xmpp_stanza_set_attribute(prekey, "id", id);
+        xmpp_stanza_set_attribute(prekey, "preKeyId", id);
         g_free(id);
 
         xmpp_stanza_t *prekey_text = xmpp_stanza_new(ctx);