From 9a4ba1de1cb635632c7e184d22aa6f36b8566335 Mon Sep 17 00:00:00 2001 From: grayTerminal-sh Date: Wed, 22 Jul 2026 13:02:39 +0200 Subject: [PATCH] feat(osint): verify execution output integrity --- Makefile | 13 +++++- README.md | 4 +- docs/database/DATABASE_ARCHITECTURE.md | 6 +++ include/core/osint_execution_integrity.h | 41 ++++++++++++++++++ src/core/application.c | 26 +----------- src/core/osint_execution_integrity.c | 44 +++++++++++++++++++ src/views/osint_execution_history_dialog.c | 49 +++++++++++++++++++++- tests/test_osint_execution_integrity.c | 46 ++++++++++++++++++++ 8 files changed, 200 insertions(+), 29 deletions(-) create mode 100644 include/core/osint_execution_integrity.h create mode 100644 src/core/osint_execution_integrity.c create mode 100644 tests/test_osint_execution_integrity.c diff --git a/Makefile b/Makefile index 68f7f6f..4f8b125 100644 --- a/Makefile +++ b/Makefile @@ -119,6 +119,7 @@ TEST_OSINT_DNS_QUERY := tests/test_osint_dns_query TEST_OSINT_DNS_PROPOSAL := tests/test_osint_dns_proposal TEST_OSINT_DNS_INTEGRATION := tests/test_osint_dns_integration TEST_OSINT_EXECUTION_DAO := tests/test_osint_execution_dao +TEST_OSINT_EXECUTION_INTEGRITY := tests/test_osint_execution_integrity all: $(TARGET) @@ -589,6 +590,11 @@ $(TEST_OSINT_EXECUTION_DAO): \ src/database/error.c $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3 +$(TEST_OSINT_EXECUTION_INTEGRITY): \ + tests/test_osint_execution_integrity.c \ + src/core/osint_execution_integrity.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) + $(TEST_INVESTIGATION_GRAPH_LOAD_TASK): \ tests/test_investigation_graph_load_task.c \ src/core/investigation_graph_load_task.c \ @@ -662,7 +668,8 @@ test: \ $(TEST_OSINT_DNS_QUERY) \ $(TEST_OSINT_DNS_PROPOSAL) \ $(TEST_OSINT_DNS_INTEGRATION) \ - $(TEST_OSINT_EXECUTION_DAO) + $(TEST_OSINT_EXECUTION_DAO) \ + $(TEST_OSINT_EXECUTION_INTEGRITY) @echo "Exécution des tests..." @./$(TEST_NODE) @./$(TEST_TREE_MODEL) @@ -716,6 +723,7 @@ test: \ @$(TEST_OSINT_DNS_PROPOSAL) @$(TEST_OSINT_DNS_INTEGRATION) @$(TEST_OSINT_EXECUTION_DAO) + @$(TEST_OSINT_EXECUTION_INTEGRITY) @echo "Tous les tests sont valides." %.o: %.c @@ -775,7 +783,8 @@ clean: $(TEST_OSINT_DNS_QUERY) \ $(TEST_OSINT_DNS_PROPOSAL) \ $(TEST_OSINT_DNS_INTEGRATION) \ - $(TEST_OSINT_EXECUTION_DAO) + $(TEST_OSINT_EXECUTION_DAO) \ + $(TEST_OSINT_EXECUTION_INTEGRITY) -include $(DEP) diff --git a/README.md b/README.md index e15ba8d..4d398b4 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,9 @@ Le socle actuel comprend notamment : - provenance OSINT SQLite V3 conservant les arguments, sorties brutes, empreinte SHA-256 et liaisons vers les entités et relations intégrées ; - historique OSINT contextuel en lecture seule avec détail des exécutions, - sorties standard et d'erreur, et objets créés ou réutilisés. + sorties standard et d'erreur, et objets créés ou réutilisés ; +- vérification manuelle de l'intégrité des sorties OSINT enregistrées, sans + réécriture de l'empreinte ou des données contrôlées. Les outils actuellement présents dans le catalogue initial sont : diff --git a/docs/database/DATABASE_ARCHITECTURE.md b/docs/database/DATABASE_ARCHITECTURE.md index 85e7d3c..40cd707 100644 --- a/docs/database/DATABASE_ARCHITECTURE.md +++ b/docs/database/DATABASE_ARCHITECTURE.md @@ -1130,6 +1130,12 @@ l'entité ou la relation sélectionnée. La vue est strictement en lecture seule et expose les métadonnées, les sorties brutes rendues en UTF-8, l'empreinte et les objets liés avec leur disposition `created` ou `reused`. +Depuis ce détail, une vérification explicite recalcule l'empreinte SHA-256 à +partir des BLOB `stdout_raw` et `stderr_raw`, séparés par l'octet nul défini +lors de l'enregistrement. Le résultat indique si les sorties sont intactes, +altérées ou impossibles à vérifier. Ce contrôle est strictement en lecture +seule : aucune sortie ni empreinte persistée n'est corrigée automatiquement. + --- # 6. Tables de liaison diff --git a/include/core/osint_execution_integrity.h b/include/core/osint_execution_integrity.h new file mode 100644 index 0000000..9b7c5a3 --- /dev/null +++ b/include/core/osint_execution_integrity.h @@ -0,0 +1,41 @@ +/****************************************************************************** + * @file osint_execution_integrity.h + * @brief Calcul et vérification de l'intégrité des sorties OSINT. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_OSINT_EXECUTION_INTEGRITY_H +#define LABFY_INVESTIGATION_OSINT_EXECUTION_INTEGRITY_H + +#include + +G_BEGIN_DECLS + +/** @brief Résultat d'une vérification d'intégrité OSINT. */ +typedef enum +{ + OSINT_EXECUTION_INTEGRITY_UNAVAILABLE = 0, + OSINT_EXECUTION_INTEGRITY_INTACT, + OSINT_EXECUTION_INTEGRITY_ALTERED +} OsintExecutionIntegrityStatus; + +/** + * @brief Calcule le SHA-256 de stdout, d'un séparateur nul et de stderr. + * @param stdout_raw Sortie standard brute. + * @param stderr_raw Sortie d'erreur brute. + * @return Empreinte hexadécimale possédée, ou NULL en cas d'échec. + */ +char *osint_execution_integrity_calculate(GBytes *stdout_raw, GBytes *stderr_raw); + +/** + * @brief Compare les sorties brutes à une empreinte SHA-256 enregistrée. + * @param stdout_raw Sortie standard brute. + * @param stderr_raw Sortie d'erreur brute. + * @param expected_sha256 Empreinte attendue. + * @return État d'intégrité sans modifier les données fournies. + */ +OsintExecutionIntegrityStatus osint_execution_integrity_verify( + GBytes *stdout_raw, GBytes *stderr_raw, const char *expected_sha256 +); + +G_END_DECLS +#endif diff --git a/src/core/application.c b/src/core/application.c index e9e7f37..0a559d7 100644 --- a/src/core/application.c +++ b/src/core/application.c @@ -26,6 +26,7 @@ #include "core/tool_initializer.h" #include "core/tool_task.h" #include "core/osint_dns_integration.h" +#include "core/osint_execution_integrity.h" #include "views/file_dialog.h" #include "core/evidence_import_task.h" #include "models/evidence_record.h" @@ -1103,29 +1104,6 @@ static char *application_osint_create_timestamp(void) return timestamp; } -/** @brief Calcule l'empreinte déterministe de stdout et stderr bruts. */ -static char *application_osint_hash_outputs(GBytes *stdout_raw, GBytes *stderr_raw) -{ - GChecksum *checksum = g_checksum_new(G_CHECKSUM_SHA256); - gconstpointer data = NULL; - gsize data_size = 0U; - const guint8 separator = 0U; - char *digest = NULL; - if (checksum == NULL || stdout_raw == NULL || stderr_raw == NULL) - { - g_clear_pointer(&checksum, g_checksum_free); - return NULL; - } - data = g_bytes_get_data(stdout_raw, &data_size); - if (data_size > 0U) g_checksum_update(checksum, data, data_size); - g_checksum_update(checksum, &separator, 1U); - data = g_bytes_get_data(stderr_raw, &data_size); - if (data_size > 0U) g_checksum_update(checksum, data, data_size); - digest = g_strdup(g_checksum_get_string(checksum)); - g_checksum_free(checksum); - return digest; -} - /** @brief Persiste une exécution DNS terminée et retourne son UUID. */ static char *application_persist_dns_execution( ApplicationOsintActionContext *context, @@ -1157,7 +1135,7 @@ static char *application_persist_dns_execution( arguments = g_strdup_printf( "[\"+noall\",\"+answer\",\"%s\"]", context->target_value ); - sha256 = application_osint_hash_outputs(stdout_raw, stderr_raw); + sha256 = osint_execution_integrity_calculate(stdout_raw, stderr_raw); record = osint_execution_record_new( identifier, "dns.dig", tool_info != NULL ? tool_info_get_detected_version(tool_info) : NULL, diff --git a/src/core/osint_execution_integrity.c b/src/core/osint_execution_integrity.c new file mode 100644 index 0000000..d19e2fb --- /dev/null +++ b/src/core/osint_execution_integrity.c @@ -0,0 +1,44 @@ +/****************************************************************************** + * @file osint_execution_integrity.c + * @brief Calcul et vérification de l'intégrité des sorties OSINT. + ******************************************************************************/ + +#include "core/osint_execution_integrity.h" + +#include + +char *osint_execution_integrity_calculate(GBytes *stdout_raw, GBytes *stderr_raw) +{ + GChecksum *checksum = NULL; + gconstpointer data = NULL; + gsize data_size = 0U; + const guint8 separator = 0U; + char *digest = NULL; + if (stdout_raw == NULL || stderr_raw == NULL) return NULL; + checksum = g_checksum_new(G_CHECKSUM_SHA256); + if (checksum == NULL) return NULL; + data = g_bytes_get_data(stdout_raw, &data_size); + if (data_size > 0U) g_checksum_update(checksum, data, data_size); + g_checksum_update(checksum, &separator, 1U); + data = g_bytes_get_data(stderr_raw, &data_size); + if (data_size > 0U) g_checksum_update(checksum, data, data_size); + digest = g_strdup(g_checksum_get_string(checksum)); + g_checksum_free(checksum); + return digest; +} + +OsintExecutionIntegrityStatus osint_execution_integrity_verify( + GBytes *stdout_raw, GBytes *stderr_raw, const char *expected_sha256 +) +{ + char *calculated_sha256 = NULL; + OsintExecutionIntegrityStatus status = OSINT_EXECUTION_INTEGRITY_UNAVAILABLE; + if (expected_sha256 == NULL || strlen(expected_sha256) != 64U) return status; + calculated_sha256 = osint_execution_integrity_calculate(stdout_raw, stderr_raw); + if (calculated_sha256 != NULL) + status = g_ascii_strcasecmp(calculated_sha256, expected_sha256) == 0 + ? OSINT_EXECUTION_INTEGRITY_INTACT + : OSINT_EXECUTION_INTEGRITY_ALTERED; + g_free(calculated_sha256); + return status; +} diff --git a/src/views/osint_execution_history_dialog.c b/src/views/osint_execution_history_dialog.c index 472ee23..4496754 100644 --- a/src/views/osint_execution_history_dialog.c +++ b/src/views/osint_execution_history_dialog.c @@ -5,12 +5,15 @@ #include "views/osint_execution_history_dialog.h" +#include "core/osint_execution_integrity.h" #include "models/osint_execution_record.h" typedef struct { GtkWindow *window; GtkTextBuffer *details_buffer; + GtkWidget *integrity_label; + const OsintExecutionRecord *current_record; GPtrArray *records; GHashTable *linked_objects; } OsintExecutionHistoryDialogContext; @@ -96,6 +99,10 @@ static void osint_execution_history_dialog_on_record_clicked( if (context == NULL || encoded_index == 0U || encoded_index > context->records->len) return; record = g_ptr_array_index(context->records, encoded_index - 1U); + context->current_record = record; + gtk_label_set_text(GTK_LABEL(context->integrity_label), + "Intégrité : non vérifiée"); + gtk_widget_remove_css_class(context->integrity_label, "error"); linked_objects = g_hash_table_lookup(context->linked_objects, osint_execution_record_get_identifier(record)); details = osint_execution_history_dialog_build_details(record, linked_objects); @@ -103,6 +110,33 @@ static void osint_execution_history_dialog_on_record_clicked( g_free(details); } +/** @brief Recalcule et affiche l'intégrité de l'exécution sélectionnée. */ +static void osint_execution_history_dialog_on_verify_clicked( + GtkButton *button, gpointer user_data +) +{ + OsintExecutionHistoryDialogContext *context = user_data; + GBytes *stdout_raw = NULL; GBytes *stderr_raw = NULL; + OsintExecutionIntegrityStatus status; + const char *status_text = "Intégrité : vérification impossible"; + (void) button; + if (context == NULL || context->current_record == NULL) return; + stdout_raw = osint_execution_record_ref_stdout(context->current_record); + stderr_raw = osint_execution_record_ref_stderr(context->current_record); + status = osint_execution_integrity_verify( + stdout_raw, stderr_raw, + osint_execution_record_get_output_sha256(context->current_record)); + if (status == OSINT_EXECUTION_INTEGRITY_INTACT) + status_text = "Intégrité : intacte"; + else if (status == OSINT_EXECUTION_INTEGRITY_ALTERED) + status_text = "Intégrité : altérée"; + gtk_label_set_text(GTK_LABEL(context->integrity_label), status_text); + if (status == OSINT_EXECUTION_INTEGRITY_INTACT) + gtk_widget_remove_css_class(context->integrity_label, "error"); + else gtk_widget_add_css_class(context->integrity_label, "error"); + g_bytes_unref(stdout_raw); g_bytes_unref(stderr_raw); +} + void osint_execution_history_dialog_present( GtkWindow *parent_window, GPtrArray *records, GHashTable *linked_objects ) @@ -111,6 +145,7 @@ void osint_execution_history_dialog_present( GtkWidget *main_box = NULL; GtkWidget *content_box = NULL; GtkWidget *history_scroll = NULL; GtkWidget *history_box = NULL; GtkWidget *details_scroll = NULL; GtkWidget *details_view = NULL; + GtkWidget *button_box = NULL; GtkWidget *verify_button = NULL; GtkWidget *close_button = NULL; if (records == NULL || records->len == 0U || linked_objects == NULL) return; context = g_new0(OsintExecutionHistoryDialogContext, 1); @@ -160,12 +195,22 @@ void osint_execution_history_dialog_present( gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(details_scroll), details_view); gtk_box_append(GTK_BOX(content_box), history_scroll); gtk_box_append(GTK_BOX(content_box), details_scroll); + button_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8); + gtk_widget_set_halign(button_box, GTK_ALIGN_END); + context->integrity_label = gtk_label_new("Intégrité : non vérifiée"); + gtk_widget_set_hexpand(context->integrity_label, TRUE); + gtk_widget_set_halign(context->integrity_label, GTK_ALIGN_START); + verify_button = gtk_button_new_with_label("Vérifier l'intégrité"); + g_signal_connect(verify_button, "clicked", + G_CALLBACK(osint_execution_history_dialog_on_verify_clicked), context); close_button = gtk_button_new_with_label("Fermer"); - gtk_widget_set_halign(close_button, GTK_ALIGN_END); g_signal_connect_swapped(close_button, "clicked", G_CALLBACK(gtk_window_destroy), context->window); + gtk_box_append(GTK_BOX(button_box), verify_button); + gtk_box_append(GTK_BOX(button_box), close_button); gtk_box_append(GTK_BOX(main_box), content_box); - gtk_box_append(GTK_BOX(main_box), close_button); + gtk_box_append(GTK_BOX(main_box), context->integrity_label); + gtk_box_append(GTK_BOX(main_box), button_box); gtk_window_set_child(context->window, main_box); osint_execution_history_dialog_on_record_clicked( GTK_BUTTON(gtk_widget_get_first_child(history_box)), context); diff --git a/tests/test_osint_execution_integrity.c b/tests/test_osint_execution_integrity.c new file mode 100644 index 0000000..e3a51f4 --- /dev/null +++ b/tests/test_osint_execution_integrity.c @@ -0,0 +1,46 @@ +/****************************************************************************** + * @file test_osint_execution_integrity.c + * @brief Tests du contrôle d'intégrité des sorties OSINT. + ******************************************************************************/ + +#include "core/osint_execution_integrity.h" + +#include + +static void test_empty_outputs_are_intact(void) +{ + GBytes *empty = g_bytes_new_static("", 0U); + const char *expected = + "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"; + g_assert_cmpint(osint_execution_integrity_verify(empty, empty, expected), ==, + OSINT_EXECUTION_INTEGRITY_INTACT); + g_bytes_unref(empty); +} + +static void test_binary_outputs_and_alteration(void) +{ + const guint8 stdout_data[] = {0x41U, 0x00U, 0xFFU}; + const guint8 stderr_data[] = {0x80U, 0x42U}; + GBytes *stdout_raw = g_bytes_new_static(stdout_data, sizeof(stdout_data)); + GBytes *stderr_raw = g_bytes_new_static(stderr_data, sizeof(stderr_data)); + char *sha256 = osint_execution_integrity_calculate(stdout_raw, stderr_raw); + g_assert_nonnull(sha256); + g_assert_cmpint(osint_execution_integrity_verify( + stdout_raw, stderr_raw, sha256), ==, OSINT_EXECUTION_INTEGRITY_INTACT); + sha256[0] = sha256[0] == '0' ? '1' : '0'; + g_assert_cmpint(osint_execution_integrity_verify( + stdout_raw, stderr_raw, sha256), ==, OSINT_EXECUTION_INTEGRITY_ALTERED); + g_assert_cmpint(osint_execution_integrity_verify( + NULL, stderr_raw, sha256), ==, OSINT_EXECUTION_INTEGRITY_UNAVAILABLE); + g_free(sha256); g_bytes_unref(stdout_raw); g_bytes_unref(stderr_raw); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + g_test_add_func("/osint-execution-integrity/empty", + test_empty_outputs_are_intact); + g_test_add_func("/osint-execution-integrity/binary-altered", + test_binary_outputs_and_alteration); + return g_test_run(); +}