From e4bfda2f8a957f17c959269fa0beb606c7594da5 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Sun, 22 Mar 2026 17:14:24 +0100 Subject: [PATCH 1/2] fix: Incorrect omemo decryption error for Key Transport Messages Correctly handle OMEMO stanzas that contain a
but no . These "Key Transport Messages" (heartbeats) are used by some clients for session maintenance and establishing trust. Ref: a2726b6a7 made the element mandatory in the parser. Ref: 4d49c2b74 the bug became user-visible. Fixes: https://github.com/profanity-im/profanity/issues/2129 Signed-off-by: Michael Vetter --- src/omemo/omemo.c | 5 +++++ src/xmpp/message.c | 6 ++++++ src/xmpp/omemo.c | 28 ++++++++++++++-------------- src/xmpp/xmpp.h | 1 + 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/omemo/omemo.c b/src/omemo/omemo.c index 728fe5c1..9dac4d76 100644 --- a/src/omemo/omemo.c +++ b/src/omemo/omemo.c @@ -1145,6 +1145,11 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid, return NULL; } + if (payload == NULL) { + signal_buffer_free(plaintext_key); + return NULL; + } + size_t plaintext_len = payload_len; plaintext = malloc(plaintext_len + 1); res = aes128gcm_decrypt(plaintext, &plaintext_len, payload, payload_len, iv, diff --git a/src/xmpp/message.c b/src/xmpp/message.c index a154f7d3..09f90ba9 100644 --- a/src/xmpp/message.c +++ b/src/xmpp/message.c @@ -1102,6 +1102,8 @@ _handle_groupchat(xmpp_stanza_t* const stanza) case OMEMO_ERR_DECRYPT_FAILED: message->plain = g_strdup("OMEMO message received but decryption failed."); break; + case OMEMO_ERR_KEY_TRANSPORT: + break; default: message->plain = g_strdup("OMEMO message received but could not be decrypted."); break; @@ -1283,6 +1285,8 @@ _handle_muc_private_message(xmpp_stanza_t* const stanza) case OMEMO_ERR_DECRYPT_FAILED: message->plain = g_strdup("OMEMO message received but decryption failed."); break; + case OMEMO_ERR_KEY_TRANSPORT: + break; default: message->plain = g_strdup("OMEMO message received but could not be decrypted."); break; @@ -1471,6 +1475,8 @@ _handle_chat(xmpp_stanza_t* const stanza, gboolean is_mam, gboolean is_carbon, c case OMEMO_ERR_DECRYPT_FAILED: message->plain = g_strdup("OMEMO message received but decryption failed."); break; + case OMEMO_ERR_KEY_TRANSPORT: + break; default: message->plain = g_strdup("OMEMO message received but could not be decrypted."); break; diff --git a/src/xmpp/omemo.c b/src/xmpp/omemo.c index ca55d5f4..9238f493 100644 --- a/src/xmpp/omemo.c +++ b/src/xmpp/omemo.c @@ -333,6 +333,7 @@ omemo_receive_message(xmpp_stanza_t* const stanza, gboolean* trusted, omemo_erro unsigned char* payload_raw = NULL; char* iv_text = NULL; char* payload_text = NULL; + size_t payload_len = 0; *error = OMEMO_ERR_NONE; @@ -372,20 +373,15 @@ omemo_receive_message(xmpp_stanza_t* const stanza, gboolean* trusted, omemo_erro } 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; + if (payload) { + payload_text = xmpp_stanza_get_text(payload); + if (payload_text) { + payload_raw = g_base64_decode(payload_text, &payload_len); + if (!payload_raw) { + *error = OMEMO_ERR_OTHER; + goto quit; + } + } } xmpp_stanza_t* key_stanza; @@ -425,6 +421,10 @@ omemo_receive_message(xmpp_stanza_t* const stanza, gboolean* trusted, omemo_erro keys, payload_raw, payload_len, g_strcmp0(type, STANZA_TYPE_GROUPCHAT) == 0, trusted, error); + if (plaintext == NULL && *error == OMEMO_ERR_NONE && payload == NULL) { + *error = OMEMO_ERR_KEY_TRANSPORT; + } + if (keys) { g_list_free_full(keys, (GDestroyNotify)omemo_key_free); } diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h index c329d221..83d87fb6 100644 --- a/src/xmpp/xmpp.h +++ b/src/xmpp/xmpp.h @@ -132,6 +132,7 @@ typedef enum { OMEMO_ERR_DECRYPT_FAILED, OMEMO_ERR_INVALID_JID, OMEMO_ERR_MUC_SENDER_NOT_FOUND, + OMEMO_ERR_KEY_TRANSPORT, OMEMO_ERR_OTHER } omemo_error_t; From a824b26008a6bbdb4f974e03e87161702abb9f05 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Sun, 22 Mar 2026 17:36:41 +0100 Subject: [PATCH 2/2] refactor: improve key transport message handling and deduplicate errors Centralize heartbeat detection and have a function for error to string mapping. So we don't need the same code in multiple handlers. Signed-off-by: Michael Vetter --- src/omemo/omemo.c | 1 + src/xmpp/message.c | 60 +++------------------------------------------- src/xmpp/omemo.c | 25 +++++++++++++++---- src/xmpp/omemo.h | 1 + 4 files changed, 26 insertions(+), 61 deletions(-) diff --git a/src/omemo/omemo.c b/src/omemo/omemo.c index 9dac4d76..e3a652b1 100644 --- a/src/omemo/omemo.c +++ b/src/omemo/omemo.c @@ -1147,6 +1147,7 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid, if (payload == NULL) { signal_buffer_free(plaintext_key); + *error = OMEMO_ERR_KEY_TRANSPORT; return NULL; } diff --git a/src/xmpp/message.c b/src/xmpp/message.c index 09f90ba9..76179efc 100644 --- a/src/xmpp/message.c +++ b/src/xmpp/message.c @@ -1089,25 +1089,7 @@ _handle_groupchat(xmpp_stanza_t* const stanza) 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; - case OMEMO_ERR_KEY_TRANSPORT: - break; - default: - message->plain = g_strdup("OMEMO message received but could not be decrypted."); - break; - } + message->plain = omemo_error_to_string(message->omemo_err); } } else if (message->plain != NULL) { message->enc = PROF_MSG_ENC_OMEMO; @@ -1272,25 +1254,7 @@ _handle_muc_private_message(xmpp_stanza_t* const stanza) 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; - case OMEMO_ERR_KEY_TRANSPORT: - break; - default: - message->plain = g_strdup("OMEMO message received but could not be decrypted."); - break; - } + message->plain = omemo_error_to_string(message->omemo_err); } } else if (message->plain != NULL) { message->enc = PROF_MSG_ENC_OMEMO; @@ -1462,25 +1426,7 @@ _handle_chat(xmpp_stanza_t* const stanza, gboolean is_mam, gboolean is_carbon, c 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; - case OMEMO_ERR_KEY_TRANSPORT: - break; - default: - message->plain = g_strdup("OMEMO message received but could not be decrypted."); - break; - } + message->plain = omemo_error_to_string(message->omemo_err); } } else if (message->plain != NULL) { message->enc = PROF_MSG_ENC_OMEMO; diff --git a/src/xmpp/omemo.c b/src/xmpp/omemo.c index 9238f493..cef09dde 100644 --- a/src/xmpp/omemo.c +++ b/src/xmpp/omemo.c @@ -421,10 +421,6 @@ omemo_receive_message(xmpp_stanza_t* const stanza, gboolean* trusted, omemo_erro keys, payload_raw, payload_len, g_strcmp0(type, STANZA_TYPE_GROUPCHAT) == 0, trusted, error); - if (plaintext == NULL && *error == OMEMO_ERR_NONE && payload == NULL) { - *error = OMEMO_ERR_KEY_TRANSPORT; - } - if (keys) { g_list_free_full(keys, (GDestroyNotify)omemo_key_free); } @@ -707,3 +703,24 @@ _omemo_bundle_publish_configure_result(xmpp_stanza_t* const stanza, void* const return 0; } + +char* +omemo_error_to_string(omemo_error_t error) +{ + switch (error) { + case OMEMO_ERR_NO_KEY: + return g_strdup("OMEMO message received but no key for this device found."); + case OMEMO_ERR_NOT_TRUSTED: + return g_strdup("OMEMO message received but sender identity is untrusted."); + case OMEMO_ERR_NO_SESSION: + return g_strdup("OMEMO message received but no session found. Try '/omemo start'."); + case OMEMO_ERR_DECRYPT_FAILED: + return g_strdup("OMEMO message received but decryption failed."); + case OMEMO_ERR_KEY_TRANSPORT: + return NULL; + case OMEMO_ERR_NONE: + return NULL; + default: + return g_strdup("OMEMO message received but could not be decrypted."); + } +} diff --git a/src/xmpp/omemo.h b/src/xmpp/omemo.h index ff6d648b..772bd186 100644 --- a/src/xmpp/omemo.h +++ b/src/xmpp/omemo.h @@ -20,3 +20,4 @@ void omemo_bundle_request(const char* const jid, uint32_t device_id, ProfIqCallb 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, omemo_error_t* error) __attribute__((nonnull(2, 3))); +char* omemo_error_to_string(omemo_error_t error);