diff options
author | Dr. David Alan Gilbert <dave@treblig.org> | 2017-04-10 13:55:03 +0100 |
---|---|---|
committer | Dr. David Alan Gilbert <dave@treblig.org> | 2018-02-25 02:08:49 +0000 |
commit | ea32cc082ce321e6951268f4f9548323700a833a (patch) | |
tree | af6b4ec7c058d4eeb569a596921e71d7609adb1f /matrix-e2e.c | |
parent | 437f18c038134fc2f8541e2d55728b72d2392103 (diff) | |
download | purple-matrix-ea32cc082ce321e6951268f4f9548323700a833a.tar.gz |
e2e: Look for olm sessions in our db
Keep a hash/list of olm sessions in memory and load them from the db
if we find we haven't got one.
Signed-off-by: Dr. David Alan Gilbert <dave@treblig.org>
Diffstat (limited to 'matrix-e2e.c')
-rw-r--r-- | matrix-e2e.c | 286 |
1 files changed, 265 insertions, 21 deletions
diff --git a/matrix-e2e.c b/matrix-e2e.c index c43a360..2c59888 100644 --- a/matrix-e2e.c +++ b/matrix-e2e.c @@ -17,6 +17,8 @@ */ #include <stdio.h> +#include <stdint.h> +#include <inttypes.h> #include <string.h> #include <sqlite3.h> #include "libmatrix.h" @@ -32,12 +34,16 @@ #ifndef MATRIX_NO_E2E #include "olm/olm.h" +struct _MatrixOlmSession; + struct _MatrixE2EData { OlmAccount *oa; gchar *device_id; gchar *curve25519_pubkey; gchar *ed25519_pubkey; sqlite3 *db; + /* Mapping from MatrixHashKeyOlm to MatrixOlmSession */ + GHashTable *olm_session_hash; }; #define PURPLE_CONV_E2E_STATE "e2e" @@ -48,6 +54,19 @@ typedef struct _MatrixE2ERoomData { GHashTable *megolm_sessions_inbound; } MatrixE2ERoomData; +typedef struct _MatrixHashKeyOlm { + gchar *sender_key; + gchar *sender_id; +} MatrixHashKeyOlm; + +typedef struct _MatrixOlmSession { + gchar *sender_key; + gchar *sender_id; + OlmSession *session; + sqlite3_int64 unique; + struct _MatrixOlmSession *next; +} MatrixOlmSession; + typedef struct _MatrixHashKeyInBoundMegOlm { gchar *sender_key; gchar *sender_id; @@ -94,6 +113,53 @@ static void *get_random(size_t n) fclose(urandom); return buffer; } + +/* GHashFunc for two MatrixHasKeyOlm */ +static gboolean olm_inbound_equality(gconstpointer a, gconstpointer b) +{ + const MatrixHashKeyOlm *hk_a; + const MatrixHashKeyOlm *hk_b; + hk_a = (const MatrixHashKeyOlm *)a; + hk_b = (const MatrixHashKeyOlm *)b; + + return !strcmp(hk_a->sender_key, hk_b->sender_key) && + !strcmp(hk_a->sender_id, hk_b->sender_id); +} + +/* GHashFunc for a _MatrixHashKeyOlm */ +static guint olm_inbound_hash(gconstpointer a) +{ + const MatrixHashKeyOlm *hk; + hk = (const MatrixHashKeyOlm *)a; + return g_str_hash(hk->sender_key) + + g_str_hash(hk->sender_id); +} + +static void olm_hash_key_destroy(gpointer k) +{ + MatrixHashKeyOlm *ok = k; + + clear_mem(ok->sender_key, strlen(ok->sender_key)); + g_free(ok->sender_key); + g_free(ok->sender_id); + g_free(ok); +} + +static void olm_hash_value_destroy(gpointer v) +{ + MatrixOlmSession *os = v; + + while (os) { + MatrixOlmSession *next = os->next; + olm_clear_session(os->session); + g_free(os->session); + g_free(os->sender_key); + g_free(os->sender_id); + g_free(os); + os = next; + } +} + /* GEqualFunc for two MatrixHashKeyInBoundMegOlm */ static gboolean megolm_inbound_equality(gconstpointer a, gconstpointer b) { @@ -180,6 +246,162 @@ static void store_inbound_megolm_session(PurpleConversation *conv, g_hash_table_insert(get_e2e_inbound_megolm_hash(conv), key, igs); } +/* Find if we already have an OlmSession for this sender/sender_key somewhere + * that this body matches. + */ +static MatrixOlmSession *find_olm_session(MatrixConnectionData *conn, + const char *sender_id, const char *sender_key, + const char *body) +{ + gboolean have_sender = FALSE; + MatrixOlmSession *cur_entry, *list_head, *hash_result; + MatrixOlmSession *result = NULL; + MatrixHashKeyOlm match; + + purple_debug_info("matrixprpl", "find_olm_session for %s/%s\n", + sender_id, sender_key); + match.sender_key = (gchar *)sender_key; + match.sender_id = (gchar *)sender_id; + hash_result = (MatrixOlmSession *)g_hash_table_lookup( + conn->e2e->olm_session_hash, &match); + cur_entry = hash_result; + list_head = hash_result; + + while (cur_entry) { + if (!strcmp(sender_id, cur_entry->sender_id) && + !strcmp(sender_key, cur_entry->sender_key)) { + size_t ret; + char *body_double = g_strdup(body); + have_sender = TRUE; + ret = olm_matches_inbound_session(cur_entry->session, body_double, + strlen(body)); + g_free(body_double); + if (ret == 1) { + purple_debug_info("matrixprpl", + "%s: Found matching session for %s/%s\n", + __func__, sender_id, sender_key); + return cur_entry; + } + if (ret == olm_error()) { + purple_debug_warning("matrixprpl", + "%s: Error while checking session %p for " + "match with %s/%s: %s\n", __func__, cur_entry->session, + sender_id, sender_key, + olm_session_last_error(cur_entry->session)); + } + } + cur_entry = cur_entry->next; + } + + if (!have_sender) { + MatrixOlmSession **chain = &list_head; + int ret; + /* We have no entries at all for the sender+key, lets load all the + * data from the db + */ + const char *query = "SELECT session_pickle, rowid " + "FROM olmsessions " + "WHERE sender_name = ? AND " + "sender_key = ?"; + + sqlite3_stmt *dbstmt = NULL; + ret = sqlite3_prepare_v2(conn->e2e->db, query, -1, &dbstmt, NULL); + if (ret != SQLITE_OK || !dbstmt) { + purple_debug_warning("matrixprpl", + "%s: Failed to prep select %d '%s'\n", + __func__, ret, query); + goto bad_sql; + } + ret = sqlite3_bind_text(dbstmt, 1, sender_id, -1, NULL); + if (ret == SQLITE_OK) { + ret = sqlite3_bind_text(dbstmt, 2, sender_key, -1, NULL); + } + if (ret != SQLITE_OK) { + purple_debug_warning("matrixprpl", "%s: Failed to bind %d\n", + __func__, ret); + goto bad_sql; + } + + /* Find the end of the chain to get the pointer to update */ + for (cur_entry = list_head; cur_entry; cur_entry=cur_entry->next) { + chain = &(cur_entry->next); + } + + while (ret = sqlite3_step(dbstmt), ret == SQLITE_ROW) { + const gchar *pickle = (gchar *)sqlite3_column_text(dbstmt, 0); + gchar *dupe_pickle; + if (!pickle) { + purple_debug_warning("matrixprpl", + "%s: Empty pickle for %s/%s\n", __func__, + sender_id, sender_key); + continue; + }; + dupe_pickle = g_strdup(pickle); + OlmSession *session = olm_session(g_malloc0(olm_session_size())); + if (olm_unpickle_session(session, "!", 1, dupe_pickle, + strlen(dupe_pickle)) == olm_error()) { + purple_debug_warning("matrixprpl", + "%s: Failed to unpickle %s for %s/%s\n", + __func__, pickle, sender_id, sender_key); + g_free(dupe_pickle); + g_free(session); + continue; + } + g_free(dupe_pickle); + cur_entry = g_new0(MatrixOlmSession, 1); + cur_entry->sender_id = g_strdup(sender_id); + cur_entry->sender_key = g_strdup(sender_key); + cur_entry->session = session; + cur_entry->unique = sqlite3_column_int64(dbstmt, 1); + *chain = cur_entry; + + if (!result) { + char *body_double = g_strdup(body); + /* But is this the session we're after ? */ + ret = olm_matches_inbound_session(session, + body_double, strlen(body)); + g_free(body_double); + if (ret == 1) { + purple_debug_info("matrixprpl", + "%s: Found (loaded) session for %s/%s\n", + __func__, sender_id, sender_key); + result = cur_entry; + /* Carry on loading any other sessions */ + } + if (ret == olm_error()) { + purple_debug_warning("matrixprpl", + "%s: Error while checking loaded session %p for " + "match with %s/%s: %s\n", __func__, session, + sender_id, sender_key, + olm_session_last_error(session)); + } + if (!ret) { + purple_debug_warning("matrixprpl", + "%s: Loaded session (%" PRIx64 + ") is not a match for %s/%s\n", + __func__, (int64_t)cur_entry->unique, sender_id, + sender_key); + } + } + } + if (ret != SQLITE_DONE) { + purple_debug_warning("matrixprpl", "%s: db step failed %d\n", + __func__, ret); + goto bad_sql; + } +bad_sql: + sqlite3_finalize(dbstmt); + } + if (list_head && !hash_result) { + MatrixHashKeyOlm *key = g_new0(MatrixHashKeyOlm, 1); + key->sender_key = g_strdup(sender_key); + key->sender_id = g_strdup(sender_id); + /* We loaded entries where there were none before, set the hash */ + g_hash_table_insert(conn->e2e->olm_session_hash, key, list_head); + } + return result; +} + /* Sign the JsonObject with olm_account_sign and add it to the object * as a 'signatures' member of the top level object. * 0 on success @@ -654,7 +876,11 @@ static int open_e2e_db(MatrixConnectionData *conn) return ret; } - /* TODO: Add calls to ensure_table here */ + ret = ensure_table(conn, + "SELECT name FROM sqlite_master WHERE type='table' AND name='olmsessions'", + "CREATE TABLE olmsessions (sender_name text, sender_key text," + " session_pickle text," + " PRIMARY KEY (sender_name, sender_key))"); if (ret) { close_e2e_db(conn); @@ -682,6 +908,10 @@ int matrix_e2e_get_device_keys(MatrixConnectionData *conn, const gchar *device_i if (!conn->e2e) { conn->e2e = g_new0(MatrixE2EData,1); conn->e2e->device_id = g_strdup(device_id); + conn->e2e->olm_session_hash = g_hash_table_new_full(olm_inbound_hash, + olm_inbound_equality, + olm_hash_key_destroy, + olm_hash_value_destroy); } conn->e2e->oa = account; @@ -802,6 +1032,7 @@ void matrix_e2e_cleanup_connection(MatrixConnectionData *conn) { if (conn->e2e) { close_e2e_db(conn); + g_hash_table_destroy(conn->e2e->olm_session_hash); g_free(conn->e2e->curve25519_pubkey); g_free(conn->e2e->oa); g_free(conn->e2e->device_id); @@ -984,7 +1215,6 @@ static void decrypt_olm(PurpleConnection *pc, MatrixConnectionData *conn, JsonOb gchar *cevent_body_copy = NULL; gchar *plaintext = NULL; size_t max_plaintext_len = 0; - OlmSession *session = NULL; cevent_sender = matrix_json_object_get_string_member(cevent, "sender"); sender_key = matrix_json_object_get_string_member(cevent_content, "sender_key"); @@ -992,6 +1222,8 @@ static void decrypt_olm(PurpleConnection *pc, MatrixConnectionData *conn, JsonOb "ciphertext"); /* TODO: Look up sender_key - I think we need to check this against device * list from user? */ + MatrixOlmSession *mos = NULL; + OlmSession *session = NULL; if (!cevent_ciphertext || !sender_key) { purple_debug_info("matrixprpl", @@ -1019,29 +1251,42 @@ static void decrypt_olm(PurpleConnection *pc, MatrixConnectionData *conn, JsonOb "%s: Type %zd olm encrypted message from %s\n", __func__, (size_t)type, cevent_sender); if (!type) { - /* A 'prekey' message to establish an Olm session - * TODO!!!!: Try existing sessions and check with - * matches_inbound_session */ - session = olm_session(g_malloc0(olm_session_size())); + /* A 'prekey' message to establish an Olm session */ const gchar *cevent_body; cevent_body = matrix_json_object_get_string_member(our_ciphertext, - "body"); - gchar *cevent_body_copy = g_strdup(cevent_body); - if (olm_create_inbound_session_from(session, conn->e2e->oa, sender_key, - strlen(sender_key), - cevent_body_copy, strlen(cevent_body)) - == olm_error()) { - purple_debug_info("matrixprpl", - "%s: prekey inbound_session_from failed : %s\n", + "body"); + mos = find_olm_session(conn, cevent_sender, sender_key, cevent_body); + if (!mos) { + cevent_body_copy = g_strdup(cevent_body); + /* OK, no existing session, lets create one */ + session = olm_session(g_malloc0(olm_session_size())); + + if (olm_create_inbound_session_from(session, conn->e2e->oa, + sender_key, strlen(sender_key), + cevent_body_copy, + strlen(cevent_body)) == + olm_error()) { + purple_debug_info("matrixprpl", + "%s: olm prekey inbound_session_from failed with %s\n", __func__, olm_session_last_error(session)); - goto err; - } - if (olm_remove_one_time_keys(conn->e2e->oa, session) == olm_error()) { - purple_debug_info("matrixprpl", - "%s: Failed to remove 1tk, inbound session: %s\n", + g_free(session); + goto err; + } + if (olm_remove_one_time_keys(conn->e2e->oa, session) == + olm_error()) { + purple_debug_info("matrixprpl", + "%s: Failed to remove 1tk from inbound session " + " creation: %s\n", __func__, olm_account_last_error(conn->e2e->oa)); - goto err; + g_free(session); + goto err; + } + if (matrix_store_e2e_account(conn)) { + g_free(session); + goto err; + } } + session = mos->session; cevent_body_copy = g_strdup(cevent_body); max_plaintext_len = olm_decrypt_max_plaintext_length(session, 0 /* Prekey */, @@ -1088,7 +1333,6 @@ err: } g_free(plaintext); g_free(cevent_body_copy); - g_free(session); } /* |