diff --git a/tests/Makefile.am b/tests/Makefile.am index 262d3c17..970ea7ee 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -316,7 +316,9 @@ test_chat_channel_SOURCES = test_chat_channel.c test_chat_channel_LDADD = $(LIBUTUN) $(COMMON_LIBS) test_chat_channel_LDFLAGS = -Wl,--wrap=EVP_DigestSign -Wl,--wrap=sqlite3_exec -Wl,--wrap=sqlite3_prepare_v2 \ -Wl,--wrap=sqlite3_bind_blob -Wl,--wrap=sqlite3_bind_text -Wl,--wrap=sqlite3_bind_int64 -Wl,--wrap=sqlite3_bind_null \ - -Wl,--wrap=sqlite3_step -Wl,--wrap=sqlite3_changes -Wl,--wrap=sqlite3_finalize -Wl,--wrap=u_malloc_impl + -Wl,--wrap=sqlite3_step -Wl,--wrap=sqlite3_changes -Wl,--wrap=sqlite3_finalize -Wl,--wrap=u_malloc_impl \ + -Wl,--wrap=EVP_PKEY_CTX_new_id -Wl,--wrap=EVP_PKEY_keygen_init -Wl,--wrap=EVP_PKEY_keygen \ + -Wl,--wrap=EVP_PKEY_get_raw_public_key -Wl,--wrap=EVP_PKEY_get_raw_private_key -Wl,--wrap=RAND_bytes else test_socket_monitor_LDFLAGS += -Wl,--wrap=read endif diff --git a/tests/test_chat_channel.c b/tests/test_chat_channel.c index 3e42ce20..c68fc4b8 100644 --- a/tests/test_chat_channel.c +++ b/tests/test_chat_channel.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "../src/utun_instance.h" #include "../src/chat/chat_core.h" #include "../src/chat/chat_core_priv.h" @@ -22,6 +23,55 @@ enum sql_fault { Q_NONE, Q_PREPARE, Q_BIND, Q_STEP, Q_CHANGES, Q_FINALIZE, Q_EXE static enum sql_fault sql_fault; static const char *sql_prefix, *sql_suffix; static int sql_bind, allocations_until_failure, failed_rollback, last_target; +enum crypto_fault { K_NONE, K_CONTEXT, K_INITIALIZE, K_GENERATE, K_PUBLIC, K_PUBLIC_SIZE, K_PRIVATE, K_PRIVATE_SIZE, + K_RANDOM, K_DESCRIPTION, K_INVITATION, K_PROFILE }; +static enum crypto_fault crypto_fault; +static int crypto_type; +static EVP_PKEY_CTX* crypto_context; +static EVP_PKEY* crypto_key; + +static int take_crypto(enum crypto_fault point) { + if (crypto_fault != point) return 0; + crypto_fault = K_NONE; DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "channel test: crypto fault type=%d stage=%d", crypto_type, point); return 1; +} +extern EVP_PKEY_CTX* __real_EVP_PKEY_CTX_new_id(int, ENGINE*); +EVP_PKEY_CTX* __wrap_EVP_PKEY_CTX_new_id(int type, ENGINE* engine) { + if (type == crypto_type && take_crypto(K_CONTEXT)) return NULL; + EVP_PKEY_CTX* ctx = __real_EVP_PKEY_CTX_new_id(type, engine); + if (type == crypto_type && crypto_fault != K_NONE) crypto_context = ctx; + return ctx; +} +extern int __real_EVP_PKEY_keygen_init(EVP_PKEY_CTX*); +int __wrap_EVP_PKEY_keygen_init(EVP_PKEY_CTX* ctx) { + if (ctx == crypto_context && take_crypto(K_INITIALIZE)) return 0; + return __real_EVP_PKEY_keygen_init(ctx); +} +extern int __real_EVP_PKEY_keygen(EVP_PKEY_CTX*, EVP_PKEY**); +int __wrap_EVP_PKEY_keygen(EVP_PKEY_CTX* ctx, EVP_PKEY** key) { + if (ctx == crypto_context && take_crypto(K_GENERATE)) return 0; + int rc = __real_EVP_PKEY_keygen(ctx, key); + if (ctx == crypto_context && rc == 1 && crypto_fault != K_NONE) crypto_key = *key; + return rc; +} +extern int __real_EVP_PKEY_get_raw_public_key(const EVP_PKEY*, unsigned char*, size_t*); +int __wrap_EVP_PKEY_get_raw_public_key(const EVP_PKEY* key, unsigned char* data, size_t* length) { + if (key == crypto_key && take_crypto(K_PUBLIC)) return 0; + int rc = __real_EVP_PKEY_get_raw_public_key(key, data, length); + if (key == crypto_key && take_crypto(K_PUBLIC_SIZE)) *length = 31; + return rc; +} +extern int __real_EVP_PKEY_get_raw_private_key(const EVP_PKEY*, unsigned char*, size_t*); +int __wrap_EVP_PKEY_get_raw_private_key(const EVP_PKEY* key, unsigned char* data, size_t* length) { + if (key == crypto_key && take_crypto(K_PRIVATE)) return 0; + int rc = __real_EVP_PKEY_get_raw_private_key(key, data, length); + if (key == crypto_key && take_crypto(K_PRIVATE_SIZE)) *length = 31; + return rc; +} +extern int __real_RAND_bytes(unsigned char*, int); +int __wrap_RAND_bytes(unsigned char* data, int length) { + if (length == 8 && take_crypto(K_RANDOM)) return 0; + return __real_RAND_bytes(data, length); +} static int sql_target(const char* sql) { return sql_prefix && !strncmp(sql, sql_prefix, strlen(sql_prefix)) && (!sql_suffix || strstr(sql, sql_suffix)); @@ -125,6 +175,12 @@ int __wrap_EVP_DigestSign(EVP_MD_CTX* ctx, unsigned char* signature, size_t* len if (fail_sign_number && active && size == 112 && !memcmp(data + 72, active->my_keys.public_key, 32) && !--fail_sign_number) { DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "channel test: injected signature failure bytes=%zu", size); return 0; } + if (active && size == MEMBER_SIGN_MSG_SIZE && !memcmp(data, active->my_keys.public_key, 32) && + !memcmp(data + 32, MEMBER_SIGN_SUFFIX, MEMBER_SIGN_SUFFIX_LEN) && take_crypto(K_INVITATION)) return 0; + if (size >= 80 && !memcmp(data + 72, "{\"name\":", 8) && take_crypto(K_PROFILE)) return 0; + const uint8_t* zero = memchr(data, 0, size); + if (zero && zero - data < 20 && (size_t)(zero - data + 1) + sizeof("crypto refusal") <= size && + !memcmp(zero + 1, "crypto refusal", sizeof("crypto refusal")) && take_crypto(K_DESCRIPTION)) return 0; return __real_EVP_DigestSign(ctx, signature, length, data, size); } @@ -168,7 +224,7 @@ static void refused(struct UTUN_INSTANCE* inst, const char* name) { uint64_t created = chat_core_create_channel_auto(inst, name); if (created || sql_fault != Q_NONE) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "channel test: expected refusal name=%s ID=%llu pending_fault=%d", name, (unsigned long long)created, sql_fault); - assert(!created && sql_fault == Q_NONE && sqlite3_get_autocommit(inst->topo_sqlite_db)); + assert(!created && sql_fault == Q_NONE && crypto_fault == K_NONE && !fail_sign_number && sqlite3_get_autocommit(inst->topo_sqlite_db)); snapshot(reader, after); assert(!memcmp(before, after, 32) && channels() == count && channel_events == events); assert(queue_entry_count(inst->topo_groups->group_list) == groups); } @@ -180,6 +236,22 @@ static void retry(struct UTUN_INSTANCE* inst) { static int veto(void* arg) { (*(unsigned*)arg)++; return 1; } +static void crypto_regressions(struct UTUN_INSTANCE* inst) { + const int types[] = {EVP_PKEY_X25519, EVP_PKEY_ED25519}; unsigned cases = 0; + for (unsigned type = 0; type < 2; type++) { + for (enum crypto_fault point = K_CONTEXT; point <= K_PRIVATE_SIZE; point++) { + crypto_type = types[type]; crypto_context = NULL; crypto_key = NULL; crypto_fault = point; + refused(inst, "crypto refusal"); retry(inst); cases++; + } + } + crypto_type = 0; crypto_context = NULL; crypto_key = NULL; + for (enum crypto_fault point = K_RANDOM; point <= K_PROFILE; point++) { + crypto_fault = point; refused(inst, "crypto refusal"); retry(inst); cases++; + } + fail_sign_number = 1; refused(inst, "crypto refusal"); retry(inst); cases++; + DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "PASS channel crypto:%u faults/retries; key generation/extraction sizes,RNG,description,JOIN,invitation,profile", cases); +} + static void sql_regressions(struct UTUN_INSTANCE* inst) { const char* queries[] = {"SELECT 1 FROM channels WHERE channel_id=?", "INSERT INTO channels(channel_id, name, owner_node_id,", "INSERT INTO \"peers_"}; @@ -248,8 +320,14 @@ int main(void) { DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "channel test: profile bind failure ID=%llu channels=%d events=%u", (unsigned long long)created, channels(), channel_events); assert(!fail_profile_binding && !created && channels() == 1 && channel_events == 1); + crypto_regressions(inst); sql_regressions(inst); int committed = channels(); + uint8_t before[32], after[32]; snapshot(reader, before); unsigned events = channel_events; + sql_prefix = "COMMIT /* channel creation */"; sql_suffix = NULL; sql_fault = Q_EXEC; failed_rollback = 1; inst->running = 1; + assert(!chat_core_create_channel_auto(inst, "failed rollback") && sql_fault == Q_NONE && !failed_rollback); + assert(!inst->running && !sqlite3_get_autocommit(inst->topo_sqlite_db) && channel_events == events); + snapshot(reader, after); assert(!memcmp(before, after, 32)); utun_instance_destroy(inst); active = NULL; assert(channels() == committed && sqlite3_close(reader) == SQLITE_OK); reader = NULL; uasync_poll(ua, 0); uasync_destroy(ua, 0); assert(u_get_allocated_count() == baseline); DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "PASS channel creation: failed root signature leaves no channel/event; retry publishes valid membership after commit");