feat(osint): verify execution output integrity

This commit is contained in:
grayTerminal-sh 2026-07-22 13:02:39 +02:00
parent bb8295af85
commit 9a4ba1de1c
8 changed files with 200 additions and 29 deletions

View file

@ -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)

View file

@ -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 :

View file

@ -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

View file

@ -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 <glib.h>
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

View file

@ -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,

View file

@ -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 <string.h>
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;
}

View file

@ -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);

View file

@ -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 <glib.h>
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();
}