diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c index 3238bf153..d216dd16e 100644 --- a/src/command/cmd_defs.c +++ b/src/command/cmd_defs.c @@ -2332,13 +2332,18 @@ static const struct cmd_t command_defs[] = { "/omemo clear_device_list", "/omemo qrcode") CMD_DESC( - "OMEMO commands to manage keys, and perform encryption during chat sessions.") + "OMEMO commands to manage keys, and perform encryption during chat sessions.\n" + "The title bar will show the OMEMO session status:\n" + "[OMEMO][trusted] - All active devices for the contact are trusted.\n" + "[OMEMO][untrusted] - One or more active devices for the contact are untrusted.\n") CMD_ARGS( { "gen", "Generate OMEMO cryptographic materials for current account." }, { "start []", "Start an OMEMO session with contact, or current recipient if omitted." }, { "end", "End the current OMEMO session." }, { "log on|off", "Enable or disable plaintext logging of OMEMO encrypted messages." }, { "log redact", "Log OMEMO encrypted messages, but replace the contents with [redacted]." }, + { "trust [] ", "Trust a fingerprint for a contact, or current recipient if omitted. If all active devices are trusted, the title bar will show [trusted]. Otherwise, it will show [untrusted]." }, + { "untrust [] ","Untrust a fingerprint for a contact, or current recipient if omitted." }, { "fingerprint []", "Show contact's fingerprints, or current recipient's if omitted." }, { "char ", "Set the character to be displayed next to OMEMO encrypted messages." }, { "trustmode manual", "Set the global OMEMO trust mode to manual, OMEMO keys has to be trusted manually." }, diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 7f574aff7..326e3e628 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -8479,6 +8479,7 @@ cmd_omemo_start(ProfWin* window, const char* const command, gchar** args) } accounts_add_omemo_state(session_get_account_name(), chatwin->barejid, TRUE); + win_println((ProfWin*)chatwin, THEME_DEFAULT, "!", "Initiating OMEMO session with %s...", chatwin->barejid); omemo_start_session(chatwin->barejid); chatwin->is_omemo = TRUE; } else if (window->type == WIN_MUC) { @@ -8493,6 +8494,7 @@ cmd_omemo_start(ProfWin* window, const char* const command, gchar** args) if (muc_anonymity_type(mucwin->roomjid) == MUC_ANONYMITY_TYPE_NONANONYMOUS && muc_member_type(mucwin->roomjid) == MUC_MEMBER_TYPE_MEMBERS_ONLY) { accounts_add_omemo_state(session_get_account_name(), mucwin->roomjid, TRUE); + win_println((ProfWin*)mucwin, THEME_DEFAULT, "!", "Initiating OMEMO session in %s...", mucwin->roomjid); omemo_start_muc_sessions(mucwin->roomjid); mucwin->is_omemo = TRUE; } else { @@ -8686,8 +8688,9 @@ cmd_omemo_fingerprint(ProfWin* window, const char* const command, gchar** args) for (GList* fingerprint = fingerprints; fingerprint != NULL; fingerprint = fingerprint->next) { auto_char char* formatted_fingerprint = omemo_format_fingerprint(fingerprint->data); gboolean trusted = omemo_is_trusted_identity(jid->barejid, fingerprint->data); + gboolean active = omemo_is_device_active(jid->barejid, fingerprint->data); - win_println(window, THEME_DEFAULT, "-", "%s's OMEMO fingerprint: %s%s", jid->barejid, formatted_fingerprint, trusted ? " (trusted)" : ""); + win_println(window, THEME_DEFAULT, "-", "%s's OMEMO fingerprint: %s%s%s", jid->barejid, formatted_fingerprint, trusted ? " (trusted)" : " (untrusted)", active ? " (active)" : " (inactive)"); } g_list_free(fingerprints); diff --git a/src/omemo/omemo.c b/src/omemo/omemo.c index f78413236..17f6c836f 100644 --- a/src/omemo/omemo.c +++ b/src/omemo/omemo.c @@ -105,6 +105,7 @@ typedef struct omemo_context prof_keyfile_t knowndevices; GHashTable* known_devices; gboolean loaded; + gboolean notifying; } omemo_context; static omemo_context omemo_ctx; @@ -168,6 +169,8 @@ _omemo_finalize_identity_load(ProfAccount* account) omemo_devicelist_subscribe(); + omemo_ctx.notifying = TRUE; + return TRUE; } @@ -254,6 +257,7 @@ omemo_on_connect(ProfAccount* account) signal_protocol_store_context_set_identity_key_store(omemo_ctx.store, &identity_key_store); omemo_ctx.loaded = FALSE; + omemo_ctx.notifying = FALSE; omemo_ctx.device_list = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)g_list_free); omemo_ctx.device_list_handler = g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL); omemo_ctx.known_devices = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)g_hash_table_destroy); @@ -515,6 +519,40 @@ omemo_set_device_list(const char* const from, GList* device_list) log_debug("[OMEMO] Setting device list for %s", STR_MAYBE_NULL(from)); auto_jid Jid* jid = jid_create(from ?: connection_get_fulljid()); + if (omemo_ctx.notifying) { + GList* old_list = g_hash_table_lookup(omemo_ctx.device_list, jid->barejid); + GList* new_iter; + for (new_iter = device_list; new_iter != NULL; new_iter = new_iter->next) { + uint32_t dev_id = GPOINTER_TO_UINT(new_iter->data); + gboolean found = FALSE; + GList* old_iter; + for (old_iter = old_list; old_iter != NULL; old_iter = old_iter->next) { + if (GPOINTER_TO_UINT(old_iter->data) == dev_id) { + found = TRUE; + break; + } + } + + if (!found) { + if (equals_our_barejid(jid->barejid)) { + if (dev_id != omemo_ctx.device_id) { + cons_show("New OMEMO device (ID: %u) added to your account.", dev_id); + } + } else { + ProfChatWin* chatwin = wins_get_chat(jid->barejid); + if (chatwin) { + win_println((ProfWin*)chatwin, THEME_DEFAULT, "!", "New OMEMO device (ID: %u) found for %s.", dev_id, jid->barejid); + } else { + ProfMucWin* mucwin = wins_get_muc(jid->barejid); + if (mucwin) { + win_println((ProfWin*)mucwin, THEME_DEFAULT, "!", "New OMEMO device (ID: %u) found for %s.", dev_id, jid->barejid); + } + } + } + } + } + } + g_hash_table_insert(omemo_ctx.device_list, strdup(jid->barejid), device_list); OmemoDeviceListHandler handler = g_hash_table_lookup(omemo_ctx.device_list_handler, jid->barejid); @@ -691,8 +729,30 @@ omemo_start_device_session(const char* const jid, uint32_t device_id, } log_debug("[OMEMO] create session with %s device %d", jid, device_id); + if (omemo_ctx.notifying) { + ProfChatWin* chatwin = wins_get_chat(jid); + if (chatwin) { + win_println((ProfWin*)chatwin, THEME_DEFAULT, "!", "OMEMO session with %s (device %u) ready.", jid, device_id); + } else { + ProfMucWin* mucwin = wins_get_muc(jid); + if (mucwin) { + win_println((ProfWin*)mucwin, THEME_DEFAULT, "!", "OMEMO session with %s (device %u) ready.", jid, device_id); + } + } + } } else { log_debug("[OMEMO] session with %s device %d exists", jid, device_id); + if (omemo_ctx.notifying) { + ProfChatWin* chatwin = wins_get_chat(jid); + if (chatwin) { + win_println((ProfWin*)chatwin, THEME_DEFAULT, "!", "OMEMO session with %s (device %u) already exists.", jid, device_id); + } else { + ProfMucWin* mucwin = wins_get_muc(jid); + if (mucwin) { + win_println((ProfWin*)mucwin, THEME_DEFAULT, "!", "OMEMO session with %s (device %u) already exists.", jid, device_id); + } + } + } } out: @@ -837,10 +897,42 @@ omemo_on_message_send(ProfWin* win, const char* const message, gboolean request_ // Don't send the message if no key could be encrypted. // (Since none of the recipients would be able to read the message.) if (keys == NULL) { - win_println(win, THEME_ERROR, "!", "This message cannot be decrypted for any recipient.\n" - "You should trust your recipients' device fingerprint(s) using \"/omemo trust FINGERPRINT\".\n" - "It could also be that the key bundle of the recipient(s) have not been received. " - "In this case, you could try \"omemo end\", \"omemo start\", and send the message again."); + win_println(win, THEME_ERROR, "!", "This message cannot be encrypted for any recipient."); + + // Check for untrusted fingerprints for each recipient + GList* recipients = NULL; + if (muc) { + ProfMucWin* mucwin = (ProfMucWin*)win; + GList* members = muc_members(mucwin->roomjid); + GList* iter; + for (iter = members; iter != NULL; iter = iter->next) { + auto_jid Jid* jidp = jid_create(iter->data); + recipients = g_list_append(recipients, strdup(jidp->barejid)); + } + g_list_free(members); + } else { + ProfChatWin* chatwin = (ProfChatWin*)win; + recipients = g_list_append(recipients, strdup(chatwin->barejid)); + } + + GList* rec_iter; + for (rec_iter = recipients; rec_iter != NULL; rec_iter = rec_iter->next) { + GList* untrusted = omemo_get_jid_untrusted_fingerprints(rec_iter->data); + if (untrusted) { + win_println(win, THEME_ERROR, "!", "Untrusted OMEMO fingerprints for %s:", (char*)rec_iter->data); + GList* fp_iter; + for (fp_iter = untrusted; fp_iter != NULL; fp_iter = fp_iter->next) { + char* formatted = omemo_format_fingerprint(fp_iter->data); + win_println(win, THEME_ERROR, "!", " - %s", formatted); + free(formatted); + } + win_println(win, THEME_ERROR, "!", "Use \"/omemo trust %s \" to trust them.", (char*)rec_iter->data); + g_list_free_full(untrusted, free); + } + } + g_list_free_full(recipients, free); + + win_println(win, THEME_ERROR, "!", "It could also be that key bundles have not been received. Try \"/omemo end\", then \"/omemo start\"."); goto out; } @@ -922,13 +1014,17 @@ omemo_on_message_send(ProfWin* win, const char* const message, gboolean request_ char* omemo_on_message_recv(const char* const from_jid, uint32_t sid, const unsigned char* const iv, size_t iv_len, GList* keys, - const unsigned char* const payload, size_t payload_len, gboolean muc, gboolean* trusted) + const unsigned char* const payload, size_t payload_len, gboolean muc, gboolean* trusted, omemo_error_t* error) { unsigned char* plaintext = NULL; auto_jid Jid* sender = NULL; auto_jid Jid* from = jid_create(from_jid); + + *error = OMEMO_ERR_NONE; + if (!from) { log_error("[OMEMO][RECV] Invalid jid %s", from_jid); + *error = OMEMO_ERR_INVALID_JID; return NULL; } @@ -944,6 +1040,7 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid, if (!key) { log_warning("[OMEMO][RECV] received a message with no corresponding key"); + *error = OMEMO_ERR_NO_KEY; return NULL; } @@ -960,6 +1057,7 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid, g_list_free(roster); if (!sender) { log_warning("[OMEMO][RECV] cannot find MUC message sender fulljid"); + *error = OMEMO_ERR_MUC_SENDER_NOT_FOUND; return NULL; } } else { @@ -977,6 +1075,7 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid, res = session_cipher_create(&cipher, omemo_ctx.store, &address, omemo_ctx.signal); if (res != 0) { log_error("[OMEMO][RECV] cannot create session cipher"); + *error = OMEMO_ERR_NO_SESSION; return NULL; } @@ -1035,13 +1134,19 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid, session_cipher_free(cipher); if (res != 0) { - log_error("[OMEMO][RECV] cannot decrypt message key"); + log_error("[OMEMO][RECV] cannot decrypt message key: %d", res); + if (!*trusted) { + *error = OMEMO_ERR_NOT_TRUSTED; + } else { + *error = OMEMO_ERR_DECRYPT_FAILED; + } return NULL; } if (signal_buffer_len(plaintext_key) != AES128_GCM_KEY_LENGTH + AES128_GCM_TAG_LENGTH) { log_error("[OMEMO][RECV] invalid key length"); signal_buffer_free(plaintext_key); + *error = OMEMO_ERR_DECRYPT_FAILED; return NULL; } @@ -1054,6 +1159,7 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid, if (res != 0) { log_error("[OMEMO][RECV] cannot decrypt message: %s", gcry_strerror(res)); free(plaintext); + *error = OMEMO_ERR_DECRYPT_FAILED; return NULL; } @@ -1137,7 +1243,7 @@ omemo_is_trusted_identity(const char* const jid, const char* const fingerprint) signal_protocol_address address = { .name = jid, .name_len = strlen(jid), - .device_id = GPOINTER_TO_INT(device_id), + .device_id = GPOINTER_TO_UINT(device_id), }; size_t fingerprint_len; @@ -1147,7 +1253,7 @@ omemo_is_trusted_identity(const char* const jid, const char* const fingerprint) buffer = signal_buffer_append(buffer, fingerprint_raw, fingerprint_len); gboolean trusted = is_trusted_identity(&address, signal_buffer_data(buffer), signal_buffer_len(buffer), &omemo_ctx.identity_key_store); - log_debug("[OMEMO] Device trusted %s (%d): %d", jid, GPOINTER_TO_INT(device_id), trusted); + log_debug("[OMEMO] Device trusted %s (%u): %d", jid, GPOINTER_TO_UINT(device_id), trusted); free(fingerprint_raw); signal_buffer_free(buffer); @@ -1155,6 +1261,127 @@ omemo_is_trusted_identity(const char* const jid, const char* const fingerprint) return trusted; } +gboolean +omemo_is_jid_trusted(const char* const jid) +{ + if (!omemo_loaded()) { + return FALSE; + } + + GList* device_list = g_hash_table_lookup(omemo_ctx.device_list, jid); + if (!device_list) { + return FALSE; + } + + GList* device_id_iter; + for (device_id_iter = device_list; device_id_iter != NULL; device_id_iter = device_id_iter->next) { + uint32_t device_id = GPOINTER_TO_UINT(device_id_iter->data); + if (device_id == omemo_ctx.device_id && equals_our_barejid(jid)) { + continue; + } + + GHashTable* known_identities = g_hash_table_lookup(omemo_ctx.known_devices, jid); + if (!known_identities) { + return FALSE; + } + + gboolean found = FALSE; + GList* fp_list = g_hash_table_get_keys(known_identities); + GList* fp_iter; + for (fp_iter = fp_list; fp_iter != NULL; fp_iter = fp_iter->next) { + if (device_id == GPOINTER_TO_UINT(g_hash_table_lookup(known_identities, fp_iter->data))) { + if (!omemo_is_trusted_identity(jid, fp_iter->data)) { + g_list_free(fp_list); + return FALSE; + } + found = TRUE; + break; + } + } + g_list_free(fp_list); + + if (!found) { + return FALSE; + } + } + + return TRUE; +} + +GList* +omemo_get_jid_untrusted_fingerprints(const char* const jid) +{ + if (!omemo_loaded()) { + return NULL; + } + + GList* device_list = g_hash_table_lookup(omemo_ctx.device_list, jid); + if (!device_list) { + return NULL; + } + + GList* untrusted_fps = NULL; + GList* device_id_iter; + for (device_id_iter = device_list; device_id_iter != NULL; device_id_iter = device_id_iter->next) { + uint32_t device_id = GPOINTER_TO_UINT(device_id_iter->data); + if (device_id == omemo_ctx.device_id && equals_our_barejid(jid)) { + continue; + } + + GHashTable* known_identities = g_hash_table_lookup(omemo_ctx.known_devices, jid); + if (!known_identities) { + continue; + } + + GList* fp_list = g_hash_table_get_keys(known_identities); + GList* fp_iter; + for (fp_iter = fp_list; fp_iter != NULL; fp_iter = fp_iter->next) { + if (device_id == GPOINTER_TO_UINT(g_hash_table_lookup(known_identities, fp_iter->data))) { + if (!omemo_is_trusted_identity(jid, fp_iter->data)) { + untrusted_fps = g_list_append(untrusted_fps, g_strdup(fp_iter->data)); + } + break; + } + } + g_list_free(fp_list); + } + + return untrusted_fps; +} + +gboolean +omemo_is_device_active(const char* const jid, const char* const fingerprint) +{ + if (!omemo_loaded()) { + return FALSE; + } + + GList* device_list = g_hash_table_lookup(omemo_ctx.device_list, jid); + if (!device_list) { + return FALSE; + } + + GHashTable* known_identities = g_hash_table_lookup(omemo_ctx.known_devices, jid); + if (!known_identities) { + return FALSE; + } + + void* device_id = g_hash_table_lookup(known_identities, fingerprint); + if (!device_id) { + return FALSE; + } + + uint32_t dev_id = GPOINTER_TO_UINT(device_id); + GList* iter; + for (iter = device_list; iter != NULL; iter = iter->next) { + if (GPOINTER_TO_UINT(iter->data) == dev_id) { + return TRUE; + } + } + + return FALSE; +} + static char* _omemo_fingerprint(ec_public_key* identity, gboolean formatted) { @@ -1697,6 +1924,27 @@ _cache_device_identity(const char* const jid, uint32_t device_id, ec_public_key* if (!fingerprint) { return; } + + if (omemo_ctx.notifying) { + if (!g_hash_table_contains(known_identities, fingerprint)) { + char* formatted = omemo_format_fingerprint(fingerprint); + if (equals_our_barejid(jid)) { + cons_show("New OMEMO fingerprint for your account: %s (ID: %u).", formatted, device_id); + } else { + ProfChatWin* chatwin = wins_get_chat(jid); + if (chatwin) { + win_println((ProfWin*)chatwin, THEME_DEFAULT, "!", "New OMEMO fingerprint for %s: %s (ID: %u).", jid, formatted, device_id); + } else { + ProfMucWin* mucwin = wins_get_muc(jid); + if (mucwin) { + win_println((ProfWin*)mucwin, THEME_DEFAULT, "!", "New OMEMO fingerprint for %s: %s (ID: %u).", jid, formatted, device_id); + } + } + } + free(formatted); + } + } + log_debug("[OMEMO] cache identity for %s:%d: %s", jid, device_id, fingerprint); g_hash_table_insert(known_identities, strdup(fingerprint), GINT_TO_POINTER(device_id)); diff --git a/src/omemo/omemo.h b/src/omemo/omemo.h index 39479da5f..bbae28564 100644 --- a/src/omemo/omemo.h +++ b/src/omemo/omemo.h @@ -85,6 +85,9 @@ void omemo_trust(const char* const jid, const char* const fingerprint); void omemo_untrust(const char* const jid, const char* const fingerprint); GList* omemo_known_device_identities(const char* const jid); gboolean omemo_is_trusted_identity(const char* const jid, const char* const fingerprint); +gboolean omemo_is_jid_trusted(const char* const jid); +GList* omemo_get_jid_untrusted_fingerprints(const char* const jid); +gboolean omemo_is_device_active(const char* const jid, const char* const fingerprint); char* omemo_fingerprint_autocomplete(const char* const search_str, gboolean previous, void* context); void omemo_fingerprint_autocomplete_reset(void); gboolean omemo_automatic_start(const char* const recipient); @@ -95,8 +98,9 @@ void omemo_start_muc_sessions(const char* const roomjid); void omemo_start_device_session(const char* const jid, uint32_t device_id, GList* prekeys, uint32_t signed_prekey_id, const unsigned char* const signed_prekey, size_t signed_prekey_len, const unsigned char* const signature, size_t signature_len, const unsigned char* const identity_key, size_t identity_key_len); gboolean omemo_loaded(void); + char* omemo_on_message_send(ProfWin* win, const char* const message, gboolean request_receipt, gboolean muc, const char* const replace_id); -char* omemo_on_message_recv(const char* const from, uint32_t sid, const unsigned char* const iv, size_t iv_len, GList* keys, const unsigned char* const payload, size_t payload_len, gboolean muc, gboolean* trusted); +char* omemo_on_message_recv(const char* const from, uint32_t sid, const unsigned char* const iv, size_t iv_len, GList* keys, const unsigned char* const payload, size_t payload_len, gboolean muc, gboolean* trusted, omemo_error_t* error) __attribute__((nonnull(9, 10))); char* omemo_encrypt_file(FILE* in, FILE* out, off_t file_size, int* gcry_res); gcry_error_t omemo_decrypt_file(FILE* in, FILE* out, off_t file_size, const char* fragment); diff --git a/src/ui/titlebar.c b/src/ui/titlebar.c index 4c812e80d..77a9a11c6 100644 --- a/src/ui/titlebar.c +++ b/src/ui/titlebar.c @@ -49,6 +49,9 @@ #include "ui/window_list.h" #include "ui/window.h" #include "ui/screen.h" +#ifdef HAVE_OMEMO +#include "omemo/omemo.h" +#endif #include "xmpp/roster_list.h" #include "xmpp/chat_session.h" @@ -410,6 +413,10 @@ _show_muc_privacy(ProfMucWin* mucwin) { int bracket_attrs = theme_attrs(THEME_TITLE_BRACKET); int encrypted_attrs = theme_attrs(THEME_TITLE_ENCRYPTED); +#ifdef HAVE_OMEMO + int trusted_attrs = theme_attrs(THEME_TITLE_TRUSTED); + int untrusted_attrs = theme_attrs(THEME_TITLE_UNTRUSTED); +#endif if (mucwin->is_omemo) { wprintw(win, " "); @@ -423,6 +430,32 @@ _show_muc_privacy(ProfMucWin* mucwin) wprintw(win, "]"); wattroff(win, bracket_attrs); +#ifdef HAVE_OMEMO + if (omemo_is_jid_trusted(mucwin->roomjid)) { + wprintw(win, " "); + wattron(win, bracket_attrs); + wprintw(win, "["); + wattroff(win, bracket_attrs); + wattron(win, trusted_attrs); + wprintw(win, "trusted"); + wattroff(win, trusted_attrs); + wattron(win, bracket_attrs); + wprintw(win, "]"); + wattroff(win, bracket_attrs); + } else { + wprintw(win, " "); + wattron(win, bracket_attrs); + wprintw(win, "["); + wattroff(win, bracket_attrs); + wattron(win, untrusted_attrs); + wprintw(win, "untrusted"); + wattroff(win, untrusted_attrs); + wattron(win, bracket_attrs); + wprintw(win, "]"); + wattroff(win, bracket_attrs); + } +#endif + return; } @@ -552,6 +585,32 @@ _show_privacy(ProfChatWin* chatwin) wprintw(win, "]"); wattroff(win, bracket_attrs); +#ifdef HAVE_OMEMO + if (omemo_is_jid_trusted(chatwin->barejid)) { + wprintw(win, " "); + wattron(win, bracket_attrs); + wprintw(win, "["); + wattroff(win, bracket_attrs); + wattron(win, trusted_attrs); + wprintw(win, "trusted"); + wattroff(win, trusted_attrs); + wattron(win, bracket_attrs); + wprintw(win, "]"); + wattroff(win, bracket_attrs); + } else { + wprintw(win, " "); + wattron(win, bracket_attrs); + wprintw(win, "["); + wattroff(win, bracket_attrs); + wattron(win, untrusted_attrs); + wprintw(win, "untrusted"); + wattroff(win, untrusted_attrs); + wattron(win, bracket_attrs); + wprintw(win, "]"); + wattroff(win, bracket_attrs); + } +#endif + return; } diff --git a/src/xmpp/message.c b/src/xmpp/message.c index bc07b0737..4562cc874 100644 --- a/src/xmpp/message.c +++ b/src/xmpp/message.c @@ -356,6 +356,7 @@ message_init(void) ProfMessage* message = g_new0(ProfMessage, 1); message->enc = PROF_MSG_ENC_NONE; + message->omemo_err = OMEMO_ERR_NONE; message->trusted = true; message->type = PROF_MSG_TYPE_UNINITIALIZED; @@ -1110,8 +1111,29 @@ _handle_groupchat(xmpp_stanza_t* const stanza) // check omemo encryption #ifdef HAVE_OMEMO - message->plain = omemo_receive_message(stanza, &message->trusted); - if (message->plain != NULL) { + message->plain = omemo_receive_message(stanza, &message->trusted, &message->omemo_err); + if (message->omemo_err != OMEMO_ERR_NONE) { + message->enc = PROF_MSG_ENC_OMEMO; + if (message->plain == NULL) { + switch (message->omemo_err) { + case OMEMO_ERR_NO_KEY: + message->plain = g_strdup("OMEMO message received but no key for this device found."); + break; + case OMEMO_ERR_NOT_TRUSTED: + message->plain = g_strdup("OMEMO message received but sender identity is untrusted."); + break; + case OMEMO_ERR_NO_SESSION: + message->plain = g_strdup("OMEMO message received but no session found. Try '/omemo start'."); + break; + case OMEMO_ERR_DECRYPT_FAILED: + message->plain = g_strdup("OMEMO message received but decryption failed."); + break; + default: + message->plain = g_strdup("OMEMO message received but could not be decrypted."); + break; + } + } + } else if (message->plain != NULL) { message->enc = PROF_MSG_ENC_OMEMO; } #endif @@ -1270,8 +1292,29 @@ _handle_muc_private_message(xmpp_stanza_t* const stanza) // check omemo encryption #ifdef HAVE_OMEMO - message->plain = omemo_receive_message(stanza, &message->trusted); - if (message->plain != NULL) { + message->plain = omemo_receive_message(stanza, &message->trusted, &message->omemo_err); + if (message->omemo_err != OMEMO_ERR_NONE) { + message->enc = PROF_MSG_ENC_OMEMO; + if (message->plain == NULL) { + switch (message->omemo_err) { + case OMEMO_ERR_NO_KEY: + message->plain = g_strdup("OMEMO message received but no key for this device found."); + break; + case OMEMO_ERR_NOT_TRUSTED: + message->plain = g_strdup("OMEMO message received but sender identity is untrusted."); + break; + case OMEMO_ERR_NO_SESSION: + message->plain = g_strdup("OMEMO message received but no session found. Try '/omemo start'."); + break; + case OMEMO_ERR_DECRYPT_FAILED: + message->plain = g_strdup("OMEMO message received but decryption failed."); + break; + default: + message->plain = g_strdup("OMEMO message received but could not be decrypted."); + break; + } + } + } else if (message->plain != NULL) { message->enc = PROF_MSG_ENC_OMEMO; } #endif @@ -1437,8 +1480,29 @@ _handle_chat(xmpp_stanza_t* const stanza, gboolean is_mam, gboolean is_carbon, c #ifdef HAVE_OMEMO // check omemo encryption - message->plain = omemo_receive_message(stanza, &message->trusted); - if (message->plain != NULL) { + message->plain = omemo_receive_message(stanza, &message->trusted, &message->omemo_err); + if (message->omemo_err != OMEMO_ERR_NONE) { + message->enc = PROF_MSG_ENC_OMEMO; + if (message->plain == NULL) { + switch (message->omemo_err) { + case OMEMO_ERR_NO_KEY: + message->plain = g_strdup("OMEMO message received but no key for this device found."); + break; + case OMEMO_ERR_NOT_TRUSTED: + message->plain = g_strdup("OMEMO message received but sender identity is untrusted."); + break; + case OMEMO_ERR_NO_SESSION: + message->plain = g_strdup("OMEMO message received but no session found. Try '/omemo start'."); + break; + case OMEMO_ERR_DECRYPT_FAILED: + message->plain = g_strdup("OMEMO message received but decryption failed."); + break; + default: + message->plain = g_strdup("OMEMO message received but could not be decrypted."); + break; + } + } + } else if (message->plain != NULL) { message->enc = PROF_MSG_ENC_OMEMO; } #endif diff --git a/src/xmpp/omemo.c b/src/xmpp/omemo.c index d41588a6a..4e3b52b5c 100644 --- a/src/xmpp/omemo.c +++ b/src/xmpp/omemo.c @@ -350,7 +350,7 @@ omemo_start_device_session_handle_bundle(xmpp_stanza_t* const stanza, void* cons } char* -omemo_receive_message(xmpp_stanza_t* const stanza, gboolean* trusted) +omemo_receive_message(xmpp_stanza_t* const stanza, gboolean* trusted, omemo_error_t* error) { char* plaintext = NULL; const char* type = xmpp_stanza_get_type(stanza); @@ -360,6 +360,8 @@ omemo_receive_message(xmpp_stanza_t* const stanza, gboolean* trusted) char* iv_text = NULL; char* payload_text = NULL; + *error = OMEMO_ERR_NONE; + xmpp_stanza_t* encrypted = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_OMEMO); if (!encrypted) { return NULL; @@ -367,40 +369,48 @@ omemo_receive_message(xmpp_stanza_t* const stanza, gboolean* trusted) xmpp_stanza_t* header = xmpp_stanza_get_child_by_name(encrypted, "header"); if (!header) { + *error = OMEMO_ERR_OTHER; return NULL; } const char* sid_text = xmpp_stanza_get_attribute(header, "sid"); if (!sid_text) { + *error = OMEMO_ERR_OTHER; return NULL; } uint32_t sid = strtoul(sid_text, NULL, 10); xmpp_stanza_t* iv = xmpp_stanza_get_child_by_name(header, "iv"); if (!iv) { + *error = OMEMO_ERR_OTHER; return NULL; } iv_text = xmpp_stanza_get_text(iv); if (!iv_text) { + *error = OMEMO_ERR_OTHER; return NULL; } size_t iv_len; iv_raw = g_base64_decode(iv_text, &iv_len); if (!iv_raw) { + *error = OMEMO_ERR_OTHER; return NULL; } xmpp_stanza_t* payload = xmpp_stanza_get_child_by_name(encrypted, "payload"); if (!payload) { + *error = OMEMO_ERR_OTHER; goto quit; } payload_text = xmpp_stanza_get_text(payload); if (!payload_text) { + *error = OMEMO_ERR_OTHER; goto quit; } size_t payload_len; payload_raw = g_base64_decode(payload_text, &payload_len); if (!payload_raw) { + *error = OMEMO_ERR_OTHER; goto quit; } @@ -439,7 +449,7 @@ omemo_receive_message(xmpp_stanza_t* const stanza, gboolean* trusted) plaintext = omemo_on_message_recv(from, sid, iv_raw, iv_len, keys, payload_raw, payload_len, - g_strcmp0(type, STANZA_TYPE_GROUPCHAT) == 0, trusted); + g_strcmp0(type, STANZA_TYPE_GROUPCHAT) == 0, trusted, error); if (keys) { g_list_free_full(keys, (GDestroyNotify)omemo_key_free); diff --git a/src/xmpp/omemo.h b/src/xmpp/omemo.h index 9f29cb96b..622bae49b 100644 --- a/src/xmpp/omemo.h +++ b/src/xmpp/omemo.h @@ -44,4 +44,5 @@ void omemo_devicelist_request(const char* const jid); void omemo_bundle_publish(gboolean first); void omemo_bundle_request(const char* const jid, uint32_t device_id, ProfIqCallback func, ProfIqFreeCallback free_func, void* userdata); int omemo_start_device_session_handle_bundle(xmpp_stanza_t* const stanza, void* const userdata); -char* omemo_receive_message(xmpp_stanza_t* const stanza, gboolean* trusted); + +char* omemo_receive_message(xmpp_stanza_t* const stanza, gboolean* trusted, omemo_error_t* error) __attribute__((nonnull(2, 3))); diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h index 5fd214c9a..620fb66e0 100644 --- a/src/xmpp/xmpp.h +++ b/src/xmpp/xmpp.h @@ -150,6 +150,17 @@ typedef enum { PROF_MSG_TYPE_MUCPM } prof_msg_type_t; +typedef enum { + OMEMO_ERR_NONE = 0, + OMEMO_ERR_NO_KEY, + OMEMO_ERR_NOT_TRUSTED, + OMEMO_ERR_NO_SESSION, + OMEMO_ERR_DECRYPT_FAILED, + OMEMO_ERR_INVALID_JID, + OMEMO_ERR_MUC_SENDER_NOT_FOUND, + OMEMO_ERR_OTHER +} omemo_error_t; + typedef struct prof_message_t { Jid* from_jid; @@ -172,6 +183,7 @@ typedef struct prof_message_t char* plain; GDateTime* timestamp; prof_enc_t enc; + omemo_error_t omemo_err; gboolean trusted; gboolean is_mam; prof_msg_type_t type; diff --git a/tests/unittests/omemo/stub_omemo.c b/tests/unittests/omemo/stub_omemo.c index a19c6b711..375257bce 100644 --- a/tests/unittests/omemo/stub_omemo.c +++ b/tests/unittests/omemo/stub_omemo.c @@ -42,12 +42,30 @@ omemo_is_trusted_identity(const char* const jid, const char* const fingerprint) return TRUE; } +gboolean +omemo_is_jid_trusted(const char* const jid) +{ + return TRUE; +} + +GList* +omemo_get_jid_untrusted_fingerprints(const char* const jid) +{ + return NULL; +} + GList* omemo_known_device_identities(const char* const jid) { return NULL; } +gboolean +omemo_is_device_active(const char* const jid, const char* const fingerprint) +{ + return TRUE; +} + gboolean omemo_loaded(void) { @@ -111,6 +129,23 @@ omemo_encrypt_file(FILE* in, FILE* out, off_t file_size, int* gcry_res) }; void omemo_free(void* a) {}; +char* +omemo_on_message_recv(const char* const from, uint32_t sid, + const unsigned char* const iv, size_t iv_len, + GList* keys, const unsigned char* const payload, + size_t payload_len, gboolean muc, gboolean* trusted, omemo_error_t* error) +{ + *error = OMEMO_ERR_NONE; + return NULL; +} + +char* +omemo_receive_message(xmpp_stanza_t* const stanza, gboolean* trusted, omemo_error_t* error) +{ + *error = OMEMO_ERR_NONE; + return NULL; +} + uint32_t omemo_device_id() {