diff --git a/Makefile.am b/Makefile.am index c3e3503f..48b2e57e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -236,7 +236,10 @@ omemo_sources = \ src/tools/aesgcm_download.h src/tools/aesgcm_download.c omemo_unittest_sources = \ - tests/unittests/omemo/stub_omemo.c + tests/unittests/omemo/stub_omemo.c \ + tests/unittests/omemo/test_omemo_crypto.c \ + tests/unittests/omemo/test_omemo_crypto.h \ + src/omemo/crypto.c if BUILD_PYTHON_API core_sources += $(python_sources) diff --git a/src/common.c b/src/common.c index 009c6f41..feb70439 100644 --- a/src/common.c +++ b/src/common.c @@ -455,6 +455,32 @@ str_xml_sanitize(const char* const str) return g_string_free(sanitized, FALSE); } +gchar* +redact_secrets(const char* const str) +{ + if (str == NULL) { + return NULL; + } + + // SASL exchanges and elements carry credentials — strip their content before logging + static gsize init = 0; + static GRegex* secret_regex = NULL; + if (g_once_init_enter(&init)) { + secret_regex = g_regex_new( + "(<(?:auth|response|challenge|success|password|digest)\\b[^>]*>)[^<]+()", + 0, 0, NULL); + g_once_init_leave(&init, 1); + } + + if (secret_regex == NULL) { + return g_strdup(str); + } + + auto_gchar gchar* valid = g_utf8_make_valid(str, -1); // invalid UTF-8 would make the regex fail open + gchar* redacted = g_regex_replace(secret_regex, valid, -1, 0, "\\1[REDACTED]\\2", 0, NULL); + return redacted ? redacted : g_steal_pointer(&valid); +} + char* release_get_latest(void) { diff --git a/src/common.h b/src/common.h index 7abcb9fd..4c47eca8 100644 --- a/src/common.h +++ b/src/common.h @@ -161,6 +161,7 @@ gboolean strtoi_range(const char* str, int* saveptr, int min, int max, char** er gsize g_diff_to_gsize(const void* end, const void* start); int utf8_display_len(const char* const str); gchar* str_xml_sanitize(const char* const str); +gchar* redact_secrets(const char* const str); gboolean string_matches_one_of(const char* what, const char* is, gboolean is_can_be_null, const char* first, ...) __attribute__((sentinel)); gboolean valid_tls_policy_option(const char* is); diff --git a/src/database_sqlite.c b/src/database_sqlite.c index 9af82bc4..6f0bfab6 100644 --- a/src/database_sqlite.c +++ b/src/database_sqlite.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -124,6 +125,15 @@ _get_db_filename(ProfAccount* account) return files_file_in_account_data_path(DIR_DATABASE, account->jid, "chatlog.db"); } +static int +_quick_check_cb(void* intact, int argc, char** argv, char** column_names) +{ + if (argc > 0 && argv[0] && strcmp(argv[0], "ok") == 0) { + *(gboolean*)intact = TRUE; + } + return 0; +} + static gboolean _sqlite_init(ProfAccount* account) { @@ -149,6 +159,20 @@ _sqlite_init(ProfAccount* account) return FALSE; } + g_chmod(filename, S_IRUSR | S_IWUSR); // history holds plaintext; journal/WAL files inherit these perms + + // catch corruption before running queries or migrations against the file + char* check_err = NULL; + gboolean intact = FALSE; + ret = sqlite3_exec(g_chatlog_database, "PRAGMA quick_check(1);", _quick_check_cb, &intact, &check_err); + if (ret != SQLITE_OK || !intact) { + log_error("Chat history database failed integrity check (%s): %s", filename, + check_err ? check_err : "quick_check did not return 'ok'"); + sqlite3_free(check_err); + _db_teardown("_sqlite_init(quick_check)"); + return FALSE; + } + char* err_msg = NULL; int db_version = _get_db_version(); @@ -691,7 +715,7 @@ _add_to_db(ProfMessage* message, const char* type, const Jid* const from_jid, co original_message_id = tmp ? tmp : original_message_id; if (g_strcmp0(from_jid_orig, from_jid->barejid) != 0) { - log_error("Mismatch in sender JIDs when trying to do LMC. Corrected message sender: %s. Original message sender: %s. Replace-ID: %s. Message: %s", from_jid->barejid, from_jid_orig, message->replace_id, message->plain); + log_error("Mismatch in sender JIDs when trying to do LMC. Corrected message sender: %s. Original message sender: %s. Replace-ID: %s.", from_jid->barejid, from_jid_orig, message->replace_id); cons_show_error("%s sent a message correction with mismatched sender. See log for details.", from_jid->barejid); sqlite3_finalize(lmc_stmt); return; @@ -714,7 +738,7 @@ _add_to_db(ProfMessage* message, const char* type, const Jid* const from_jid, co sqlite3_stmt* stmt; if (_db_prepare_ctx(duplicate_check_query, &stmt, "_add_to_db(duplicate_check)")) { if (sqlite3_step(stmt) == SQLITE_ROW) { - log_error("Duplicate stanza-id found for the message. stanza_id: %s; archive_id: %s; sender: %s; content: %s", message->id, message->stanzaid, from_jid->barejid, message->plain); + log_error("Duplicate stanza-id found for the message. stanza_id: %s; archive_id: %s; sender: %s", message->id, message->stanzaid, from_jid->barejid); cons_show_error("Got a message with duplicate (server-generated) stanza-id from %s.", from_jid->fulljid); } sqlite3_finalize(stmt); @@ -745,7 +769,7 @@ _add_to_db(ProfMessage* message, const char* type, const Jid* const from_jid, co return; } - log_debug("Writing to DB. Query: %s", query); + log_debug("Writing message to DB (id: %s, stanza_id: %s, type: %s)", message->id, message->stanzaid, type); // no query text: it embeds the plaintext body if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) { if (err_msg) { @@ -757,7 +781,7 @@ _add_to_db(ProfMessage* message, const char* type, const Jid* const from_jid, co } else { int inserted_rows_count = sqlite3_changes(g_chatlog_database); if (inserted_rows_count < 1) { - log_error("SQLite did not insert message (rows: %d, id: %s, content: %s)", inserted_rows_count, message->id, message->plain); + log_error("SQLite did not insert message (rows: %d, id: %s)", inserted_rows_count, message->id); } } } diff --git a/src/event/server_events.c b/src/event/server_events.c index 71d7af35..952b5c61 100644 --- a/src/event/server_events.c +++ b/src/event/server_events.c @@ -83,6 +83,7 @@ sv_ev_login_account_success(char* account_name, gboolean secured) if (!log_database_init(account)) { log_error("Failed to initialize database for account: %s", account->jid); + cons_show_error("Chat history storage is unavailable for this session, messages will not be saved. See the log for details."); } vcard_user_refresh(); avatar_pep_subscribe(); diff --git a/src/log.c b/src/log.c index ea526926..b1adf937 100644 --- a/src/log.c +++ b/src/log.c @@ -286,7 +286,8 @@ log_stderr_handler(void) for (int i = 0; i < size; ++i) { if (buf[i] == '\n') { - log_msg(stderr_level, "stderr", s->str); + auto_gchar gchar* redacted = redact_secrets(s->str); // third-party libs may echo credentials + log_msg(stderr_level, "stderr", redacted); g_string_assign(s, ""); } else g_string_append_c(s, buf[i]); @@ -294,7 +295,8 @@ log_stderr_handler(void) } while (1); if (s->len > 0 && s->str[0] != '\0') { - log_msg(stderr_level, "stderr", s->str); + auto_gchar gchar* redacted = redact_secrets(s->str); + log_msg(stderr_level, "stderr", redacted); g_string_assign(s, ""); } } diff --git a/src/otr/otr.c b/src/otr/otr.c index b5c336a9..e2d1c52e 100644 --- a/src/otr/otr.c +++ b/src/otr/otr.c @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include "log.h" #include "chatlog.h" @@ -114,6 +116,8 @@ cb_write_fingerprints(void* opdata) if (err != GPG_ERR_NO_ERROR) { log_error("Failed to write fingerprints file"); cons_show_error("Failed to write fingerprints file"); + } else { + g_chmod(fpsfilename, S_IRUSR | S_IWUSR); } } @@ -376,6 +380,7 @@ otr_keygen(ProfAccount* account) cons_show_error("Failed to generate private key"); return; } + g_chmod(keysfilename->str, S_IRUSR | S_IWUSR); log_info("Private key generated"); cons_show(""); cons_show("Private key generation complete."); @@ -390,6 +395,7 @@ otr_keygen(ProfAccount* account) cons_show_error("Failed to create fingerprints file"); return; } + g_chmod(fpsfilename->str, S_IRUSR | S_IWUSR); log_info("Fingerprints file created"); err = otrl_privkey_read(user_state, keysfilename->str); diff --git a/src/tools/aesgcm_download.c b/src/tools/aesgcm_download.c index 47729b5e..b029afb9 100644 --- a/src/tools/aesgcm_download.c +++ b/src/tools/aesgcm_download.c @@ -13,10 +13,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -62,14 +64,28 @@ aesgcm_file_get(void* userdata) return NULL; } - // Open the target file for storing the cleartext. - auto_FILE FILE* outfh = fopen(aesgcm_dl->filename, "wb"); + // Decrypt into a temporary file next to the target and rename it into + // place only after the GCM tag verified, so tampered or truncated + // content never appears at the destination path. + auto_gchar gchar* partname = g_strdup_printf("%s.part.XXXXXX", aesgcm_dl->filename); + gint outfd = g_mkstemp(partname); + if (outfd == -1) { + http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id, THEME_ERROR, ENTRY_ERROR, + "Downloading '%s' failed: Unable to open " + "output file at '%s' for writing (%s).", + https_url, aesgcm_dl->filename, + g_strerror(errno)); + return NULL; + } + FILE* outfh = fdopen(outfd, "wb"); if (outfh == NULL) { http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id, THEME_ERROR, ENTRY_ERROR, "Downloading '%s' failed: Unable to open " "output file at '%s' for writing (%s).", https_url, aesgcm_dl->filename, g_strerror(errno)); + close(outfd); + remove(partname); return NULL; } @@ -90,6 +106,8 @@ aesgcm_file_get(void* userdata) ssize_t* p_bytes_received = http_file_get(http_dl); if (!p_bytes_received) { + fclose(outfh); + remove(partname); return NULL; } ssize_t bytes_received = *p_bytes_received; @@ -102,6 +120,8 @@ aesgcm_file_get(void* userdata) "temporary file at '%s' for reading (%s).", aesgcm_dl->url, tmpname, g_strerror(errno)); + fclose(outfh); + remove(partname); return NULL; } @@ -110,20 +130,32 @@ aesgcm_file_get(void* userdata) bytes_received, fragment); fclose(tmpfh); remove(tmpname); + fclose(outfh); + gboolean saved = FALSE; if (crypt_res != GPG_ERR_NO_ERROR) { + remove(partname); http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id, THEME_ERROR, ENTRY_ERROR, "Downloading '%s' failed: Failed to decrypt " "file (%s).", https_url, gcry_strerror(crypt_res)); + } else if (g_rename(partname, aesgcm_dl->filename) != 0) { + remove(partname); + http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id, THEME_ERROR, ENTRY_ERROR, + "Downloading '%s' failed: Unable to move " + "decrypted file to '%s' (%s).", + https_url, aesgcm_dl->filename, + g_strerror(errno)); } else { + saved = TRUE; http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id, THEME_ONLINE, ENTRY_COMPLETED, "Downloading '%s': done\nSaved to '%s'", aesgcm_dl->url, aesgcm_dl->filename); win_mark_received(aesgcm_dl->window, aesgcm_dl->id); } - if (aesgcm_dl->cmd_template != NULL) { + // never hand an unverified file to the external command + if (saved && aesgcm_dl->cmd_template != NULL) { gchar** argv = format_call_external_argv(aesgcm_dl->cmd_template, aesgcm_dl->filename, aesgcm_dl->filename); @@ -140,8 +172,8 @@ aesgcm_file_get(void* userdata) } g_strfreev(argv); - free(aesgcm_dl->cmd_template); } + free(aesgcm_dl->cmd_template); free(aesgcm_dl->id); free(aesgcm_dl->filename); diff --git a/src/xmpp/connection.c b/src/xmpp/connection.c index b0a8c7f4..6ca8fd77 100644 --- a/src/xmpp/connection.c +++ b/src/xmpp/connection.c @@ -1104,7 +1104,8 @@ _xmpp_file_logger(void* const userdata, const xmpp_log_level_t xmpp_level, const break; } - log_msg(prof_level, area, msg); + auto_gchar gchar* redacted = redact_secrets(msg); // raw traffic contains SASL/register credentials + log_msg(prof_level, area, redacted); if ((g_strcmp0(area, "xmpp") == 0) || (g_strcmp0(area, "conn")) == 0) { sv_ev_xmpp_stanza(msg); diff --git a/tests/functionaltests/functionaltests.c b/tests/functionaltests/functionaltests.c index 394b31d9..73b99262 100644 --- a/tests/functionaltests/functionaltests.c +++ b/tests/functionaltests/functionaltests.c @@ -255,6 +255,9 @@ main(int argc, char* argv[]) PROF_FUNC_TEST(message_db_history_verify), PROF_FUNC_TEST(message_db_history_lmc), PROF_FUNC_TEST(message_db_history_multi_resource), +#ifdef HAVE_SQLITE + PROF_FUNC_TEST(message_db_corrupt_database_degrades_gracefully), +#endif /* Basic message send/receive */ PROF_FUNC_TEST(message_send), diff --git a/tests/functionaltests/test_history.c b/tests/functionaltests/test_history.c index 72553e14..c4752c20 100644 --- a/tests/functionaltests/test_history.c +++ b/tests/functionaltests/test_history.c @@ -536,3 +536,41 @@ message_db_history_multi_resource(void** state) assert_true(prof_output_regex("Buddy1/laptop")); assert_true(prof_output_regex("Buddy1/tablet")); } + +/* + * Test: corrupt chatlog.db degrades gracefully (issue #146, REQ-RES-02). + * + * A chatlog.db with a valid SQLite magic but garbage content is planted + * before connecting. Database init must fail cleanly: the user gets a + * console warning, the session stays up, and the client stays responsive. + */ +void +message_db_corrupt_database_degrades_gracefully(void** state) +{ + const char* xdg_data = getenv("XDG_DATA_HOME"); + assert_non_null(xdg_data); + + GString* db_file = g_string_new(xdg_data); + g_string_append(db_file, "/profanity/database/stabber_at_localhost"); + assert_int_equal(0, g_mkdir_with_parents(db_file->str, 0700)); + g_string_append(db_file, "/chatlog.db"); + + /* valid 16-byte SQLite header magic followed by garbage: sqlite3_open + * succeeds (lazy open), the integrity gate must catch it */ + FILE* db = fopen(db_file->str, "wb"); + assert_non_null(db); + assert_int_equal(16, fwrite("SQLite format 3", 1, 16, db)); + for (int i = 0; i < 4096; i++) { + fputc(0xA5, db); + } + fclose(db); + g_string_free(db_file, TRUE); + + prof_connect(); + + assert_true(prof_output_exact("Chat history storage is unavailable for this session")); + + /* client is still alive and responsive after the failed DB init */ + prof_input("/autoping set 60"); + assert_true(prof_output_exact("Autoping interval set to 60 seconds.")); +} diff --git a/tests/functionaltests/test_history.h b/tests/functionaltests/test_history.h index 3745f98a..942036a1 100644 --- a/tests/functionaltests/test_history.h +++ b/tests/functionaltests/test_history.h @@ -11,3 +11,4 @@ void message_db_history_service_chars(void** state); void message_db_history_verify(void** state); void message_db_history_lmc(void** state); void message_db_history_multi_resource(void** state); +void message_db_corrupt_database_degrades_gracefully(void** state); diff --git a/tests/unittests/omemo/test_omemo_crypto.c b/tests/unittests/omemo/test_omemo_crypto.c new file mode 100644 index 00000000..86bfceaf --- /dev/null +++ b/tests/unittests/omemo/test_omemo_crypto.c @@ -0,0 +1,152 @@ +/* + * test_omemo_crypto.c + * + * Unit tests for the OMEMO AES-256-GCM file crypto (src/omemo/crypto.c). + * The decrypt direction streams plaintext before the tag is checked, so + * callers rely on the returned error code to discard unverified output — + * these tests pin that contract (issue #146, REQ-CRY-06). + */ + +#include "config.h" + +#include +#include +#include +#include "prof_cmocka.h" + +#ifdef HAVE_OMEMO + +#include "omemo/omemo.h" +#include "omemo/crypto.h" + +#define TAG_LENGTH 16 + +static const unsigned char PLAINTEXT[] = "at-rest integrity check payload: 0123456789abcdef"; + +// gcrypt secure memory must be set up exactly once per process +static int +_crypto_init_once(void) +{ + static gboolean done = FALSE; + static int rc = 0; + if (!done) { + rc = omemo_crypto_init(); + done = TRUE; + } + return rc; +} + +static off_t +_file_size(FILE* fh) +{ + fseeko(fh, 0, SEEK_END); + off_t size = ftello(fh); + rewind(fh); + return size; +} + +// encrypt PLAINTEXT with a fixed key/nonce into a fresh tmpfile +static FILE* +_encrypted_tmpfile(unsigned char* key, unsigned char* nonce) +{ + memset(key, 0x42, OMEMO_AESGCM_KEY_LENGTH); + memset(nonce, 0x24, OMEMO_AESGCM_NONCE_LENGTH); + + FILE* plain = tmpfile(); + FILE* cipher = tmpfile(); + assert_non_null(plain); + assert_non_null(cipher); + + assert_int_equal(sizeof(PLAINTEXT), fwrite(PLAINTEXT, 1, sizeof(PLAINTEXT), plain)); + rewind(plain); + + assert_int_equal(GPG_ERR_NO_ERROR, + aes256gcm_crypt_file(plain, cipher, (off_t)sizeof(PLAINTEXT), key, nonce, TRUE)); + fclose(plain); + rewind(cipher); + return cipher; +} + +// corrupt one byte at offset (negative counts from the end), return reopened stream +static FILE* +_flip_byte(FILE* cipher, long offset) +{ + off_t size = _file_size(cipher); + unsigned char* buf = g_malloc(size); + assert_int_equal(size, fread(buf, 1, size, cipher)); + fclose(cipher); + + long pos = offset >= 0 ? offset : (long)size + offset; + buf[pos] ^= 0xFF; + + FILE* tampered = tmpfile(); + assert_non_null(tampered); + assert_int_equal(size, fwrite(buf, 1, size, tampered)); + rewind(tampered); + g_free(buf); + return tampered; +} + +void +aes256gcm_crypt_file__roundtrip_succeeds(void** state) +{ + assert_int_equal(0, _crypto_init_once()); + + unsigned char key[OMEMO_AESGCM_KEY_LENGTH]; + unsigned char nonce[OMEMO_AESGCM_NONCE_LENGTH]; + FILE* cipher = _encrypted_tmpfile(key, nonce); + + off_t cipher_size = _file_size(cipher); + assert_int_equal((off_t)sizeof(PLAINTEXT) + TAG_LENGTH, cipher_size); + + FILE* decrypted = tmpfile(); + assert_non_null(decrypted); + assert_int_equal(GPG_ERR_NO_ERROR, + aes256gcm_crypt_file(cipher, decrypted, cipher_size, key, nonce, FALSE)); + + unsigned char readback[sizeof(PLAINTEXT)]; + rewind(decrypted); + assert_int_equal(sizeof(PLAINTEXT), fread(readback, 1, sizeof(readback), decrypted)); + assert_memory_equal(PLAINTEXT, readback, sizeof(PLAINTEXT)); + + fclose(cipher); + fclose(decrypted); +} + +void +aes256gcm_crypt_file__rejects_tampered_tag(void** state) +{ + assert_int_equal(0, _crypto_init_once()); + + unsigned char key[OMEMO_AESGCM_KEY_LENGTH]; + unsigned char nonce[OMEMO_AESGCM_NONCE_LENGTH]; + FILE* cipher = _flip_byte(_encrypted_tmpfile(key, nonce), -1); // last tag byte + + FILE* decrypted = tmpfile(); + assert_non_null(decrypted); + gcry_error_t res = aes256gcm_crypt_file(cipher, decrypted, _file_size(cipher), key, nonce, FALSE); + assert_int_not_equal(GPG_ERR_NO_ERROR, res); + + fclose(cipher); + fclose(decrypted); +} + +void +aes256gcm_crypt_file__rejects_tampered_ciphertext(void** state) +{ + assert_int_equal(0, _crypto_init_once()); + + unsigned char key[OMEMO_AESGCM_KEY_LENGTH]; + unsigned char nonce[OMEMO_AESGCM_NONCE_LENGTH]; + FILE* cipher = _flip_byte(_encrypted_tmpfile(key, nonce), 0); // first payload byte + + FILE* decrypted = tmpfile(); + assert_non_null(decrypted); + gcry_error_t res = aes256gcm_crypt_file(cipher, decrypted, _file_size(cipher), key, nonce, FALSE); + assert_int_not_equal(GPG_ERR_NO_ERROR, res); + + fclose(cipher); + fclose(decrypted); +} + +#endif diff --git a/tests/unittests/omemo/test_omemo_crypto.h b/tests/unittests/omemo/test_omemo_crypto.h new file mode 100644 index 00000000..b4c24a90 --- /dev/null +++ b/tests/unittests/omemo/test_omemo_crypto.h @@ -0,0 +1,8 @@ +/* test_omemo_crypto.h + * + * Unit tests for OMEMO AES-256-GCM file crypto (issue #146, REQ-CRY-06) + */ + +void aes256gcm_crypt_file__roundtrip_succeeds(void** state); +void aes256gcm_crypt_file__rejects_tampered_tag(void** state); +void aes256gcm_crypt_file__rejects_tampered_ciphertext(void** state); diff --git a/tests/unittests/test_common.c b/tests/unittests/test_common.c index 7e876ecf..64538898 100644 --- a/tests/unittests/test_common.c +++ b/tests/unittests/test_common.c @@ -1384,3 +1384,55 @@ str_xml_sanitize__strips_illegal_characters(void** state) assert_string_equal("UTF-8: üñîçøðé and more", res5); g_free(res5); } + +void +redact_secrets__masks_credentials(void** state) +{ + // NULL input + assert_null(redact_secrets(NULL)); + + // Plain text and non-secret XML pass through unchanged + gchar* res1 = redact_secrets("hello world"); + assert_string_equal("hello world", res1); + g_free(res1); + + gchar* res2 = redact_secrets("secret-looking text"); + assert_string_equal("secret-looking text", res2); + g_free(res2); + + // SASL auth payload is redacted, envelope kept + gchar* res3 = redact_secrets("SENT: AGFsaWNlAHBhc3N3b3Jk"); + assert_string_equal("SENT: [REDACTED]", res3); + g_free(res3); + + // SASL challenge/response round-trip + gchar* res4 = redact_secrets("cj1abc"); + assert_string_equal("[REDACTED]", res4); + g_free(res4); + + gchar* res5 = redact_secrets("Yz1iaXdz"); + assert_string_equal("[REDACTED]", res5); + g_free(res5); + + // Empty SASL response element has no content to redact + gchar* res6 = redact_secrets(""); + assert_string_equal("", res6); + g_free(res6); + + // XEP-0077 registration: password redacted, username kept + gchar* res7 = redact_secrets("alicehunter2"); + assert_string_equal("alice[REDACTED]", res7); + g_free(res7); + + // XEP-0078 legacy auth: password-derived digest redacted + gchar* res8 = redact_secrets("alice48fc78be9ec8f86d8ce1c39ebd7a5b4c9d0e2f13tui"); + assert_string_equal("alice[REDACTED]tui", res8); + g_free(res8); + + // invalid UTF-8 must not make redaction fail open + gchar* res9 = redact_secrets("\xFF garbage hunter2"); + assert_non_null(res9); + assert_null(strstr(res9, "hunter2")); + assert_non_null(strstr(res9, "[REDACTED]")); + g_free(res9); +} diff --git a/tests/unittests/test_common.h b/tests/unittests/test_common.h index 277a7cdb..a243a45e 100644 --- a/tests/unittests/test_common.h +++ b/tests/unittests/test_common.h @@ -64,5 +64,6 @@ void valid_tls_policy_option__is__correct_for_various_inputs(void** state); void get_mentions__tests__various(void** state); void release_is_new__tests__various(void** state); void str_xml_sanitize__strips_illegal_characters(void** state); +void redact_secrets__masks_credentials(void** state); #endif diff --git a/tests/unittests/unittests.c b/tests/unittests/unittests.c index cf01788e..4c196ced 100644 --- a/tests/unittests/unittests.c +++ b/tests/unittests/unittests.c @@ -48,6 +48,7 @@ #include "test_ai_client.h" #include "test_database_export.h" #include "test_database_stress.h" +#include "omemo/test_omemo_crypto.h" #define muc_unit_test(f) cmocka_unit_test_setup_teardown(f, muc_before_test, muc_after_test) @@ -687,6 +688,12 @@ main(int argc, char* argv[]) cmocka_unit_test(get_mentions__tests__various), cmocka_unit_test(release_is_new__tests__various), cmocka_unit_test(str_xml_sanitize__strips_illegal_characters), + cmocka_unit_test(redact_secrets__masks_credentials), +#ifdef HAVE_OMEMO + cmocka_unit_test(aes256gcm_crypt_file__roundtrip_succeeds), + cmocka_unit_test(aes256gcm_crypt_file__rejects_tampered_tag), + cmocka_unit_test(aes256gcm_crypt_file__rejects_tampered_ciphertext), +#endif cmocka_unit_test_setup_teardown(plugins_get_command_names__returns__no_commands, load_preferences,