Compare commits

...

2 commits

Author SHA1 Message Date
grayTerminal-sh
ce2a95b4b0 feat: verify evidence integrity from interface 2026-07-20 14:26:10 +02:00
grayTerminal-sh
4de91a7ad0 feat: add evidence integrity verification action 2026-07-20 14:01:53 +02:00
5 changed files with 1176 additions and 0 deletions

View file

@ -53,6 +53,34 @@ typedef void (*MainWindowEvidenceSelectionCallback)(
gpointer user_data
);
/**
* @brief Callback appelé lorsque l'utilisateur demande une vérification.
*
* L'identifiant est emprunté et uniquement valide pendant l'appel.
*
* @param evidence_identifier UUID de la preuve.
* @param user_data Données privées du callback.
*/
typedef void (*MainWindowVerifyEvidenceCallback)(
const char *evidence_identifier,
gpointer user_data
);
/**
* @brief Définit le callback de vérification d'une preuve.
*
* MainWindow relaie la demande provenant du Workspace.
*
* @param main_window Fenêtre principale.
* @param callback Callback facultatif.
* @param user_data Données privées transmises au callback.
*/
void main_window_set_verify_evidence_callback(
MainWindow *main_window,
MainWindowVerifyEvidenceCallback callback,
gpointer user_data
);
/**
* @brief Définit le callback du bouton « Nouvelle enquête ».
*

View file

@ -27,6 +27,20 @@ typedef struct Workspace Workspace;
*/
Workspace *workspace_new(void);
/**
* @brief Callback appelé lorsque l'utilisateur demande la vérification
* de la preuve affichée.
*
* L'identifiant est emprunté et uniquement valide pendant l'appel.
*
* @param evidence_identifier UUID de la preuve.
* @param user_data Données privées du callback.
*/
typedef void (*WorkspaceVerifyEvidenceCallback)(
const char *evidence_identifier,
gpointer user_data
);
/**
* @brief Retourne le widget GTK racine de la zone de travail.
*
@ -73,6 +87,21 @@ void workspace_set_selected_evidence(
const EvidenceRecord *evidence_record
);
/**
* @brief Définit le callback de vérification d'une preuve.
*
* Le Workspace transmet uniquement l'identifiant de la preuve affichée.
*
* @param workspace Zone de travail.
* @param callback Callback facultatif.
* @param user_data Données privées transmises au callback.
*/
void workspace_set_verify_evidence_callback(
Workspace *workspace,
WorkspaceVerifyEvidenceCallback callback,
gpointer user_data
);
/**
* @brief Libère la structure d'encapsulation de la zone de travail.
*

View file

@ -27,6 +27,9 @@
#include "models/evidence_type.h"
#include "widgets/evidence_list_model.h"
#include "widgets/evidence_category_model.h"
#include "core/evidence_integrity_task.h"
#include "core/evidence_integrity_verifier.h"
#include "database/database.h"
#include <gtk/gtk.h>
#include <errno.h>
@ -88,6 +91,7 @@ struct Application
ToolInitializer *tool_initializer;
EvidenceListModel *evidence_list_model;
EvidenceCategoryModel *evidence_category_model;
char *selected_evidence_identifier;
};
/**
@ -111,6 +115,19 @@ typedef struct
char *file_path;
} ApplicationEvidenceDialogContext;
/**
* @brief Contexte conservé jusqu'à la fin d'une vérification.
*/
typedef struct
{
Application *application;
char *investigation_root_path;
char *database_path;
char *evidence_identifier;
char *original_name;
} ApplicationEvidenceIntegrityContext;
/**
* @brief Libère le contexte du dialogue de métadonnées.
@ -160,6 +177,42 @@ static void application_evidence_import_context_free(
);
}
/**
* @brief Libère le contexte d'une vérification d'intégrité.
*/
static void application_evidence_integrity_context_free(
gpointer user_data
)
{
ApplicationEvidenceIntegrityContext *context =
user_data;
if (context == NULL)
{
return;
}
g_free(
context->original_name
);
g_free(
context->evidence_identifier
);
g_free(
context->database_path
);
g_free(
context->investigation_root_path
);
g_free(
context
);
}
/**
* @brief Vérifie que lenquête active correspond à un chemin racine.
*/
@ -199,6 +252,41 @@ static gboolean application_session_matches_root(
) == 0;
}
/**
* @brief Mémorise l'identifiant de la preuve actuellement affichée.
*/
static void application_set_selected_evidence_identifier(
Application *application,
const char *evidence_identifier
)
{
char *identifier_copy =
NULL;
if (application == NULL)
{
return;
}
if (evidence_identifier != NULL &&
g_uuid_string_is_valid(
evidence_identifier
))
{
identifier_copy =
g_strdup(
evidence_identifier
);
}
g_free(
application->selected_evidence_identifier
);
application->selected_evidence_identifier =
identifier_copy;
}
static void application_present_error(
Application *application,
const char *title,
@ -926,6 +1014,16 @@ static gboolean application_install_session(
return FALSE;
}
application_set_selected_evidence_identifier(
application,
NULL
);
main_window_set_selected_evidence(
application->main_window,
NULL
);
/*
* Les nouveaux objets sont entièrement valides.
* Les anciens peuvent maintenant être libérés.
@ -2595,11 +2693,21 @@ static void application_on_evidence_selected(
if (evidence_identifier == NULL ||
evidence_identifier[0] == '\0')
{
application_set_selected_evidence_identifier(
application,
NULL
);
main_window_set_selected_evidence(
application->main_window,
NULL
);
application_set_selected_evidence_identifier(
application,
NULL
);
return;
}
@ -2729,6 +2837,11 @@ static void application_on_evidence_selected(
return;
}
application_set_selected_evidence_identifier(
application,
evidence_identifier
);
/*
* Workspace copie immédiatement les valeurs dans ses widgets GTK.
*/
@ -2765,6 +2878,779 @@ static void application_on_evidence_selected(
);
}
/**
* @brief Enregistre le statut d'intégrité dans l'enquête d'origine.
*
* Une connexion indépendante est utilisée afin que la bonne base soit
* mise à jour même si l'utilisateur a changé d'enquête entre-temps.
*/
static gboolean application_persist_evidence_integrity_status(
const ApplicationEvidenceIntegrityContext *context,
EvidenceIntegrityStatus integrity_status,
GError **error
)
{
Database *database =
NULL;
EvidenceDao *evidence_dao =
NULL;
gboolean updated =
FALSE;
g_return_val_if_fail(
error == NULL || *error == NULL,
FALSE
);
if (context == NULL ||
context->database_path == NULL ||
context->database_path[0] == '\0' ||
context->evidence_identifier == NULL ||
!g_uuid_string_is_valid(
context->evidence_identifier
))
{
g_set_error_literal(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_EVIDENCE,
"Le contexte de mise à jour de l'intégrité est invalide."
);
return FALSE;
}
database =
database_open(
context->database_path
);
if (database == NULL)
{
g_set_error(
error,
APPLICATION_OPEN_ERROR,
APPLICATION_OPEN_ERROR_EVIDENCE,
"Impossible d'ouvrir la base SQLite : %s",
context->database_path
);
return FALSE;
}
evidence_dao =
evidence_dao_new(
database,
error
);
if (evidence_dao == NULL)
{
database_close(
database
);
return FALSE;
}
updated =
evidence_dao_update_integrity_status(
evidence_dao,
context->evidence_identifier,
integrity_status,
error
);
evidence_dao_free(
evidence_dao
);
database_close(
database
);
return updated;
}
/**
* @brief Traite la fin d'une vérification d'intégrité.
*/
static void application_on_evidence_integrity_completed(
BackgroundTask *task,
gpointer user_data
)
{
ApplicationEvidenceIntegrityContext *context =
user_data;
Application *application =
NULL;
const EvidenceIntegrityVerificationResult *result =
NULL;
EvidenceIntegrityStatus integrity_status =
EVIDENCE_INTEGRITY_STATUS_ERROR;
BackgroundTaskState task_state;
const char *diagnostic =
NULL;
const char *display_name =
NULL;
char *status_message =
NULL;
GError *error =
NULL;
GError *refresh_error =
NULL;
gboolean session_matches =
FALSE;
gboolean evidence_was_selected =
FALSE;
if (task == NULL ||
context == NULL)
{
return;
}
application =
context->application;
if (application == NULL)
{
return;
}
task_state =
background_task_get_state(
task
);
session_matches =
application_session_matches_root(
application,
context->investigation_root_path
);
display_name =
context->original_name != NULL &&
context->original_name[0] != '\0'
? context->original_name
: context->evidence_identifier;
if (task_state ==
BACKGROUND_TASK_STATE_COMPLETED)
{
result =
background_task_get_result(
task
);
if (result == NULL)
{
if (session_matches)
{
application_present_error(
application,
"Vérification incomplète",
"La tâche s'est terminée sans fournir de résultat."
);
}
return;
}
integrity_status =
evidence_integrity_verification_result_get_status(
result
);
/*
* UNKNOWN après une vérification complète serait incohérent.
*/
if (integrity_status ==
EVIDENCE_INTEGRITY_STATUS_UNKNOWN)
{
integrity_status =
EVIDENCE_INTEGRITY_STATUS_ERROR;
}
diagnostic =
evidence_integrity_verification_result_get_diagnostic(
result
);
if (!application_persist_evidence_integrity_status(
context,
integrity_status,
&error
))
{
g_warning(
"Impossible d'enregistrer le statut de la preuve '%s' : %s",
context->evidence_identifier,
error != NULL
? error->message
: "erreur inconnue"
);
if (session_matches)
{
application_present_error(
application,
"Statut non enregistré",
error != NULL
? error->message
: "Le résultat n'a pas pu être enregistré."
);
}
g_clear_error(
&error
);
return;
}
if (session_matches)
{
/*
* La reconstruction du modèle peut provoquer temporairement
* une sélection vide. On mémorise donc l'état avant celle-ci.
*/
evidence_was_selected =
g_strcmp0(
application->selected_evidence_identifier,
context->evidence_identifier
) == 0;
if (!application_refresh_evidence_models(
application,
&refresh_error
))
{
g_warning(
"Le statut a été enregistré, mais la liste "
"des preuves n'a pas été actualisée : %s",
refresh_error != NULL
? refresh_error->message
: "erreur inconnue"
);
}
g_clear_error(
&refresh_error
);
/*
* On ne force pas l'affichage d'une ancienne preuve lorsque
* l'utilisateur en a sélectionné une autre entre-temps.
*/
if (evidence_was_selected)
{
application_on_evidence_selected(
context->evidence_identifier,
application
);
}
switch (integrity_status)
{
case EVIDENCE_INTEGRITY_STATUS_VALID:
status_message =
g_strdup_printf(
"Intégrité valide : %s",
display_name
);
break;
case EVIDENCE_INTEGRITY_STATUS_MODIFIED:
status_message =
g_strdup_printf(
"Alerte d'intégrité — fichier modifié : %s",
display_name
);
break;
case EVIDENCE_INTEGRITY_STATUS_MISSING:
status_message =
g_strdup_printf(
"Preuve absente du disque : %s",
display_name
);
break;
case EVIDENCE_INTEGRITY_STATUS_ERROR:
case EVIDENCE_INTEGRITY_STATUS_UNKNOWN:
default:
status_message =
g_strdup_printf(
"Erreur de vérification : %s",
display_name
);
break;
}
main_window_set_status(
application->main_window,
status_message
);
if (integrity_status ==
EVIDENCE_INTEGRITY_STATUS_ERROR &&
diagnostic != NULL &&
diagnostic[0] != '\0')
{
application_present_error(
application,
"Vérification impossible",
diagnostic
);
}
}
g_message(
"Vérification de '%s' terminée avec le statut %d.",
context->evidence_identifier,
(int) integrity_status
);
g_free(
status_message
);
return;
}
if (task_state ==
BACKGROUND_TASK_STATE_CANCELLED)
{
if (session_matches)
{
main_window_set_status(
application->main_window,
"Vérification d'intégrité annulée."
);
}
return;
}
error =
background_task_dup_error(
task
);
g_warning(
"La vérification de la preuve '%s' a échoué : %s",
context->evidence_identifier,
error != NULL
? error->message
: "erreur inconnue"
);
if (session_matches)
{
application_present_error(
application,
"Vérification impossible",
error != NULL
? error->message
: "La vérification n'a pas pu être exécutée."
);
}
g_clear_error(
&error
);
}
/**
* @brief Charge la preuve et démarre sa vérification asynchrone.
*/
static void application_start_evidence_integrity_verification(
Application *application,
const char *evidence_identifier
)
{
const InvestigationProject *project =
NULL;
Database *database =
NULL;
EvidenceDao *evidence_dao =
NULL;
EvidenceRecord *evidence_record =
NULL;
ApplicationEvidenceIntegrityContext *context =
NULL;
EvidenceIntegrityTaskRequest request =
{0};
BackgroundTask *task =
NULL;
const char *root_path =
NULL;
const char *database_path =
NULL;
const char *relative_path =
NULL;
const char *expected_sha256 =
NULL;
const char *original_name =
NULL;
char *status_message =
NULL;
GError *error =
NULL;
if (application == NULL ||
application->main_window == NULL ||
application->session == NULL ||
application->task_manager == NULL ||
evidence_identifier == NULL ||
!g_uuid_string_is_valid(
evidence_identifier
))
{
return;
}
project =
investigation_session_get_project(
application->session
);
database =
investigation_session_get_database(
application->session
);
if (project == NULL ||
database == NULL)
{
application_present_error(
application,
"Vérification impossible",
"La session d'enquête est invalide."
);
return;
}
root_path =
investigation_project_get_root_path(
project
);
database_path =
investigation_project_get_database_path(
project
);
if (root_path == NULL ||
root_path[0] == '\0' ||
database_path == NULL ||
database_path[0] == '\0')
{
application_present_error(
application,
"Vérification impossible",
"Les chemins de l'enquête sont invalides."
);
return;
}
evidence_dao =
evidence_dao_new(
database,
&error
);
if (evidence_dao == NULL)
{
application_present_error(
application,
"Vérification impossible",
error != NULL
? error->message
: "Impossible d'accéder aux preuves."
);
g_clear_error(
&error
);
return;
}
evidence_record =
evidence_dao_find_by_identifier(
evidence_dao,
evidence_identifier,
&error
);
evidence_dao_free(
evidence_dao
);
evidence_dao =
NULL;
if (error != NULL)
{
application_present_error(
application,
"Vérification impossible",
error->message
);
g_clear_error(
&error
);
return;
}
if (evidence_record == NULL)
{
application_present_error(
application,
"Preuve introuvable",
"La preuve n'existe plus dans la base SQLite."
);
return;
}
relative_path =
evidence_record_get_relative_path(
evidence_record
);
expected_sha256 =
evidence_record_get_sha256(
evidence_record
);
original_name =
evidence_record_get_original_name(
evidence_record
);
if (relative_path == NULL ||
relative_path[0] == '\0' ||
expected_sha256 == NULL ||
expected_sha256[0] == '\0')
{
application_present_error(
application,
"Vérification impossible",
"La preuve ne contient pas les données nécessaires."
);
evidence_record_free(
evidence_record
);
return;
}
context =
g_try_new0(
ApplicationEvidenceIntegrityContext,
1
);
if (context == NULL)
{
application_present_error(
application,
"Vérification impossible",
"Impossible d'allouer le contexte de la tâche."
);
evidence_record_free(
evidence_record
);
return;
}
context->application =
application;
context->investigation_root_path =
g_strdup(
root_path
);
context->database_path =
g_strdup(
database_path
);
context->evidence_identifier =
g_strdup(
evidence_identifier
);
context->original_name =
g_strdup(
original_name
);
if (context->investigation_root_path == NULL ||
context->database_path == NULL ||
context->evidence_identifier == NULL)
{
application_evidence_integrity_context_free(
context
);
evidence_record_free(
evidence_record
);
application_present_error(
application,
"Vérification impossible",
"Impossible de copier les paramètres de la tâche."
);
return;
}
request.investigation_root_path =
root_path;
request.relative_path =
relative_path;
request.expected_sha256 =
expected_sha256;
task =
evidence_integrity_task_start(
application->task_manager,
&request,
application_on_evidence_integrity_completed,
context,
application_evidence_integrity_context_free,
&error
);
if (task == NULL)
{
/*
* La propriété du contexte n'a pas é transférée.
*/
application_evidence_integrity_context_free(
context
);
application_present_error(
application,
"Vérification impossible",
error != NULL
? error->message
: "La tâche n'a pas pu être démarrée."
);
g_clear_error(
&error
);
evidence_record_free(
evidence_record
);
return;
}
/*
* BackgroundTask et TaskManager possèdent désormais leurs références.
*/
background_task_unref(
task
);
status_message =
g_strdup_printf(
"Vérification en cours : %s",
original_name != NULL &&
original_name[0] != '\0'
? original_name
: evidence_identifier
);
main_window_set_status(
application->main_window,
status_message
);
g_free(
status_message
);
evidence_record_free(
evidence_record
);
}
/**
* @brief Reçoit la demande de vérification d'une preuve.
*
* Cette première étape valide uniquement le relais de l'interface.
* Le lancement de la tâche sera ajouté ensuite.
*
* @param evidence_identifier UUID de la preuve.
* @param user_data Pointeur vers Application.
*/
/**
* @brief Traite la demande de vérification provenant du Workspace.
*/
static void application_on_verify_evidence_requested(
const char *evidence_identifier,
gpointer user_data
)
{
Application *application =
user_data;
application_start_evidence_integrity_verification(
application,
evidence_identifier
);
}
/**
* @brief Ferme proprement l'application.
*
@ -2849,6 +3735,12 @@ static void application_on_activate(
application
);
main_window_set_verify_evidence_callback(
application->main_window,
application_on_verify_evidence_requested,
application
);
main_window_set_new_investigation_callback(
application->main_window,
application_on_new_investigation_requested,
@ -3089,6 +3981,11 @@ void application_free(
);
}
g_clear_pointer(
&application->selected_evidence_identifier,
g_free
);
g_free(
application
);

View file

@ -86,6 +86,12 @@ struct MainWindow
gpointer
import_evidence_user_data;
MainWindowVerifyEvidenceCallback
verify_evidence_callback;
gpointer
verify_evidence_user_data;
MainWindowQuitCallback
quit_callback;
@ -246,6 +252,31 @@ static GtkWidget *main_window_create_action_button(
return button;
}
/**
* @brief Relaie la demande de vérification provenant du Workspace.
*/
static void main_window_on_verify_evidence_requested(
const char *evidence_identifier,
gpointer user_data
)
{
MainWindow *main_window =
user_data;
if (main_window == NULL ||
main_window->verify_evidence_callback == NULL ||
evidence_identifier == NULL ||
evidence_identifier[0] == '\0')
{
return;
}
main_window->verify_evidence_callback(
evidence_identifier,
main_window->verify_evidence_user_data
);
}
MainWindow *main_window_new(
GtkApplication *application,
TaskManager *task_manager
@ -478,6 +509,12 @@ MainWindow *main_window_new(
return NULL;
}
workspace_set_verify_evidence_callback(
main_window->workspace,
main_window_on_verify_evidence_requested,
main_window
);
workspace_widget = workspace_get_widget(
main_window->workspace
);
@ -942,6 +979,24 @@ void main_window_set_import_evidence_enabled(
);
}
void main_window_set_verify_evidence_callback(
MainWindow *main_window,
MainWindowVerifyEvidenceCallback callback,
gpointer user_data
)
{
if (main_window == NULL)
{
return;
}
main_window->verify_evidence_callback =
callback;
main_window->verify_evidence_user_data =
user_data;
}
void main_window_set_quit_callback(
MainWindow *main_window,
MainWindowQuitCallback callback,
@ -1050,6 +1105,15 @@ void main_window_free(
main_window->sidebar
);
if (main_window->workspace != NULL)
{
workspace_set_verify_evidence_callback(
main_window->workspace,
NULL,
NULL
);
}
workspace_free(
main_window->workspace
);

View file

@ -41,6 +41,15 @@ struct Workspace
GtkWidget *evidence_internal_name_label;
GtkWidget *evidence_identifier_label;
GtkWidget *evidence_sha256_label;
GtkWidget *verify_evidence_button;
char *selected_evidence_identifier;
WorkspaceVerifyEvidenceCallback
verify_evidence_callback;
gpointer
verify_evidence_user_data;
GtkWidget *node_name_label;
GtkWidget *node_path_label;
@ -234,6 +243,33 @@ static void workspace_show_welcome(
);
}
/**
* @brief Transmet la demande de vérification de la preuve affichée.
*/
static void workspace_on_verify_evidence_clicked(
GtkButton *button,
gpointer user_data
)
{
Workspace *workspace =
user_data;
(void) button;
if (workspace == NULL ||
workspace->verify_evidence_callback == NULL ||
workspace->selected_evidence_identifier == NULL ||
workspace->selected_evidence_identifier[0] == '\0')
{
return;
}
workspace->verify_evidence_callback(
workspace->selected_evidence_identifier,
workspace->verify_evidence_user_data
);
}
Workspace *workspace_new(void)
{
GtkWidget *evidence_content = NULL;
@ -534,6 +570,52 @@ Workspace *workspace_new(void)
workspace->evidence_name_label
);
workspace->verify_evidence_button =
gtk_button_new_with_label(
"Vérifier l'intégrité"
);
if (workspace->verify_evidence_button == NULL)
{
workspace_free(
workspace
);
return NULL;
}
gtk_widget_set_halign(
workspace->verify_evidence_button,
GTK_ALIGN_START
);
gtk_widget_set_tooltip_text(
workspace->verify_evidence_button,
"Recalculer le SHA-256 et le comparer à l'empreinte enregistrée"
);
/*
* Aucun bouton actif tant qu'aucune preuve n'est affichée.
*/
gtk_widget_set_sensitive(
workspace->verify_evidence_button,
FALSE
);
g_signal_connect(
workspace->verify_evidence_button,
"clicked",
G_CALLBACK(
workspace_on_verify_evidence_clicked
),
workspace
);
gtk_box_append(
GTK_BOX(evidence_content),
workspace->verify_evidence_button
);
evidence_separator =
gtk_separator_new(
GTK_ORIENTATION_HORIZONTAL
@ -723,6 +805,19 @@ void workspace_set_selected_node(
return;
}
g_clear_pointer(
&workspace->selected_evidence_identifier,
g_free
);
if (workspace->verify_evidence_button != NULL)
{
gtk_widget_set_sensitive(
workspace->verify_evidence_button,
FALSE
);
}
if (node == NULL)
{
workspace_show_welcome(workspace);
@ -834,6 +929,19 @@ void workspace_set_selected_evidence(
return;
}
g_clear_pointer(
&workspace->selected_evidence_identifier,
g_free
);
if (workspace->verify_evidence_button != NULL)
{
gtk_widget_set_sensitive(
workspace->verify_evidence_button,
FALSE
);
}
if (evidence_record == NULL)
{
workspace_show_welcome(
@ -843,6 +951,27 @@ void workspace_set_selected_evidence(
return;
}
workspace->selected_evidence_identifier =
g_strdup(
evidence_record_get_identifier(
evidence_record
)
);
if (workspace->selected_evidence_identifier == NULL)
{
workspace_show_welcome(
workspace
);
return;
}
gtk_widget_set_sensitive(
workspace->verify_evidence_button,
TRUE
);
size_bytes =
evidence_record_get_size_bytes(
evidence_record
@ -973,6 +1102,24 @@ void workspace_set_selected_evidence(
);
}
void workspace_set_verify_evidence_callback(
Workspace *workspace,
WorkspaceVerifyEvidenceCallback callback,
gpointer user_data
)
{
if (workspace == NULL)
{
return;
}
workspace->verify_evidence_callback =
callback;
workspace->verify_evidence_user_data =
user_data;
}
void workspace_free(Workspace *workspace)
{
if (workspace == NULL)
@ -980,5 +1127,16 @@ void workspace_free(Workspace *workspace)
return;
}
workspace->verify_evidence_callback =
NULL;
workspace->verify_evidence_user_data =
NULL;
g_clear_pointer(
&workspace->selected_evidence_identifier,
g_free
);
g_free(workspace);
}