Calculer le SHA-256 d’un fichier de preuve #47

Closed
opened 2026-07-18 20:01:55 +02:00 by fy59 · 0 comments
Owner

Calculer le SHA-256 d’un fichier de preuve

Objectif

Ajouter un module indépendant capable de calculer de manière fiable le SHA-256 d’un fichier destiné à devenir une preuve.

Ce module constituera la première étape technique du futur import de preuves. Il ne doit ni copier, ni renommer, ni modifier le fichier source.

Contexte

EvidenceRecord exige notamment :

  • la taille du fichier en octets ;
  • un SHA-256 en minuscules sur 64 caractères ;
  • des données cohérentes avant leur insertion dans SQLite.

Le calcul doit fonctionner sur de gros fichiers sans charger leur contenu complet en mémoire. Il doit également pouvoir être interrompu proprement par un GCancellable.

Périmètre

Créer :

include/core/file_hash.h
src/core/file_hash.c
tests/test_file_hash.c

Mettre à jour le Makefile afin de compiler et d’exécuter le nouveau test avec make test.

API attendue

Le module doit exposer un type d’erreur propre et une fonction synchrone.

Proposition d’API :

typedef enum
{
    FILE_HASH_ERROR_INVALID_ARGUMENT,
    FILE_HASH_ERROR_NOT_FOUND,
    FILE_HASH_ERROR_NOT_REGULAR,
    FILE_HASH_ERROR_OPEN,
    FILE_HASH_ERROR_READ,
    FILE_HASH_ERROR_CANCELLED,
    FILE_HASH_ERROR_MEMORY
} FileHashError;

#define FILE_HASH_ERROR file_hash_error_quark()

GQuark file_hash_error_quark(void);

gboolean file_hash_compute_sha256(
    const char *file_path,
    GCancellable *cancellable,
    char **out_sha256,
    guint64 *out_size_bytes,
    GError **error
);

L’API exacte peut être légèrement adaptée si l’architecture existante l’exige, mais les responsabilités suivantes doivent rester séparées :

  • le module calcule le hash et la taille ;
  • l’appelant reste propriétaire du chemin fourni ;
  • la chaîne retournée appartient à l’appelant ;
  • aucune opération SQLite ou GTK ne doit apparaître dans ce module.

Comportement requis

Validation des paramètres

La fonction doit refuser :

  • un chemin NULL ;
  • un chemin vide ;
  • un pointeur out_sha256 absent ;
  • un pointeur out_size_bytes absent ;
  • un GError déjà initialisé.

En cas d’échec :

*out_sha256 = NULL;
*out_size_bytes = 0;

dès que les pointeurs de sortie sont valides.

Type de fichier

Le module doit accepter uniquement un fichier régulier.

Il doit refuser explicitement :

  • les dossiers ;
  • les liens symboliques ;
  • les sockets ;
  • les tubes nommés ;
  • les périphériques ;
  • tout autre type non régulier.

Le contrôle ne doit pas suivre silencieusement un lien symbolique.

Lecture

Le fichier doit être ouvert en lecture seule.

Le contenu doit être lu progressivement avec un tampon de taille fixe. Le fichier complet ne doit jamais être chargé en mémoire.

Une taille de bloc raisonnable peut être utilisée, par exemple :

64 Kio

La taille retournée doit correspondre au nombre total d’octets effectivement lus et intégrés au calcul.

SHA-256

Le résultat doit être :

  • calculé avec SHA-256 ;
  • composé de 64 caractères hexadécimaux ;
  • normalisé en minuscules ;
  • terminé par zéro ;
  • alloué dynamiquement.

Il est recommandé d’utiliser GChecksum avec G_CHECKSUM_SHA256.

Annulation

Le paramètre GCancellable est facultatif.

La fonction doit contrôler l’annulation :

  • avant l’ouverture ;
  • avant ou après chaque bloc lu ;
  • avant de retourner le résultat final.

Une annulation doit :

  • interrompre rapidement la lecture ;
  • libérer toutes les ressources ;
  • retourner FALSE ;
  • laisser les sorties dans leur état d’échec ;
  • produire une erreur identifiable comme une annulation.

Erreurs d’entrée-sortie

Les erreurs suivantes doivent être distinguées autant que raisonnablement possible :

  • fichier absent ;
  • fichier non régulier ;
  • ouverture impossible ;
  • lecture impossible ;
  • annulation.

Le message doit contenir suffisamment de contexte pour comprendre l’échec, notamment le chemin concerné lorsque cela est pertinent.

Propriété des ressources

Après un succès :

  • *out_sha256 contient une nouvelle chaîne à libérer avec g_free() ;
  • *out_size_bytes contient le nombre d’octets lus.

Après un échec :

  • aucun descripteur ou flux ne reste ouvert ;
  • aucun GChecksum ne reste alloué ;
  • aucune chaîne partielle n’est rendue à l’appelant.

Contraintes de sécurité et d’intégrité

Le module ne doit jamais :

  • écrire dans le fichier ;
  • changer ses permissions ;
  • modifier ses dates ;
  • suivre volontairement un lien symbolique ;
  • exécuter le fichier ;
  • interpréter son contenu ;
  • utiliser son nom dans une commande shell.

Le chemin doit être transmis directement aux API de fichiers, sans construction de commande.

Tests obligatoires

Le fichier tests/test_file_hash.c doit utiliser des fichiers et dossiers temporaires isolés.

Fichier vide

Vérifier :

  • succès ;
  • taille égale à 0 ;
  • SHA-256 connu du fichier vide :
e3b0c44298fc1c149afbf4c8996fb924
27ae41e4649b934ca495991b7852b855

Contenu connu

Créer un fichier contenant exactement :

abc

sans saut de ligne.

Vérifier :

  • taille égale à 3 ;
  • SHA-256 :
ba7816bf8f01cfea414140de5dae2223
b00361a396177a9cb410ff61f20015ad

Fichier sur plusieurs blocs

Créer un fichier plus grand que la taille du tampon de lecture.

Vérifier :

  • que la taille complète est retournée ;
  • que le hash correspond à une valeur calculée indépendamment dans le test ou à une valeur connue ;
  • que le résultat ne dépend pas du découpage en blocs.

Le test ne doit pas nécessiter un fichier volumineux : quelques centaines de Kio suffisent.

Caractères binaires

Créer un fichier contenant notamment :

0x00
0xFF
0x80

Vérifier que le calcul traite les données comme des octets et non comme du texte.

Chemin absent

Vérifier :

  • retour FALSE ;
  • hash NULL ;
  • taille 0 ;
  • erreur du module.

Dossier

Vérifier qu’un dossier est refusé avec FILE_HASH_ERROR_NOT_REGULAR.

Lien symbolique

Créer un fichier régulier puis un lien symbolique vers celui-ci.

Vérifier que le lien est refusé, même si sa cible est un fichier valide.

Si la plateforme de test ne permet pas de créer un lien symbolique, le test peut être explicitement ignoré avec une justification claire.

Paramètres invalides

Tester au minimum :

  • chemin NULL ;
  • chemin vide ;
  • out_sha256 == NULL ;
  • out_size_bytes == NULL.

Annulation avant lecture

Créer un GCancellable, l’annuler avant l’appel, puis vérifier :

  • retour FALSE ;
  • erreur d’annulation ;
  • sorties réinitialisées.

Annulation pendant la lecture

Tester une annulation observée entre deux blocs.

Le test doit rester déterministe. Une solution acceptable consiste à prévoir une abstraction interne de lecture ou un mécanisme de test permettant de déclencher l’annulation après un nombre défini de blocs, sans ralentissement artificiel important.

Ne pas utiliser un test dépendant uniquement d’un délai aléatoire ou de la vitesse de la machine.

Appels successifs

Calculer plusieurs fois le hash du même fichier et vérifier que :

  • le résultat est identique ;
  • aucune donnée interne n’est conservée entre les appels.

Fichier modifié entre deux appels

Calculer le hash, modifier le fichier, puis recalculer.

Vérifier que le second hash est différent et que la nouvelle taille est correcte.

Tests d’erreurs internes

Les tests doivent vérifier que les sorties sont réinitialisées même lorsqu’elles contenaient auparavant des valeurs non nulles :

char *sha256 = g_strdup("ancienne-valeur");
guint64 size_bytes = 123;

Le test devra libérer cette ancienne valeur avant l’appel ou respecter précisément le contrat choisi. Le module ne doit pas libérer arbitrairement une chaîne dont il n’est pas propriétaire.

Le contrat recommandé est d’exiger :

*out_sha256 == NULL

à l’entrée, conformément aux conventions GLib des paramètres de sortie alloués. Si ce contrat est retenu, il doit être documenté et testé.

Critères d’acceptation

Le ticket est validé lorsque :

  • le module compile avec -std=c17 -Wall -Wextra -Werror ;
  • aucun appel shell n’est utilisé ;
  • les fichiers sont lus par blocs ;
  • les fichiers non réguliers et les liens symboliques sont refusés ;
  • l’annulation est gérée proprement ;
  • le hash du fichier vide et de abc est exact ;
  • les données binaires sont correctement traitées ;
  • les sorties sont cohérentes après chaque échec ;
  • tous les tests du projet passent ;
  • git diff --check ne signale rien ;
  • Valgrind ne détecte aucune perte définie ou indirecte.

Commandes de validation

make clean
make
make test
git diff --check

Test ciblé :

./tests/test_file_hash

Valgrind :

valgrind \
    --leak-check=full \
    --show-leak-kinds=all \
    --track-origins=yes \
    --errors-for-leak-kinds=definite,indirect \
    --error-exitcode=1 \
    ./tests/test_file_hash

Résultats indispensables :

definitely lost: 0 bytes
indirectly lost: 0 bytes
ERROR SUMMARY: 0 errors

Hors périmètre

Ce ticket ne doit pas encore :

  • copier le fichier dans l’enquête ;
  • créer un EvidenceRecord ;
  • insérer une preuve dans SQLite ;
  • choisir un nom interne ;
  • détecter le type de preuve ;
  • extraire des métadonnées ;
  • créer une interface GTK ;
  • lancer le calcul dans un BackgroundTask.

Ces responsabilités seront traitées dans les tickets suivants.

Suite prévue

Le ticket suivant utilisera ce module pour réaliser une copie sûre du fichier vers le dossier des preuves originales, sans écrasement et avec vérification du SHA-256 après copie.

# Calculer le SHA-256 d’un fichier de preuve ## Objectif Ajouter un module indépendant capable de calculer de manière fiable le SHA-256 d’un fichier destiné à devenir une preuve. Ce module constituera la première étape technique du futur import de preuves. Il ne doit ni copier, ni renommer, ni modifier le fichier source. ## Contexte `EvidenceRecord` exige notamment : - la taille du fichier en octets ; - un SHA-256 en minuscules sur 64 caractères ; - des données cohérentes avant leur insertion dans SQLite. Le calcul doit fonctionner sur de gros fichiers sans charger leur contenu complet en mémoire. Il doit également pouvoir être interrompu proprement par un `GCancellable`. ## Périmètre Créer : ```text include/core/file_hash.h src/core/file_hash.c tests/test_file_hash.c ``` Mettre à jour le `Makefile` afin de compiler et d’exécuter le nouveau test avec `make test`. ## API attendue Le module doit exposer un type d’erreur propre et une fonction synchrone. Proposition d’API : ```c typedef enum { FILE_HASH_ERROR_INVALID_ARGUMENT, FILE_HASH_ERROR_NOT_FOUND, FILE_HASH_ERROR_NOT_REGULAR, FILE_HASH_ERROR_OPEN, FILE_HASH_ERROR_READ, FILE_HASH_ERROR_CANCELLED, FILE_HASH_ERROR_MEMORY } FileHashError; #define FILE_HASH_ERROR file_hash_error_quark() GQuark file_hash_error_quark(void); gboolean file_hash_compute_sha256( const char *file_path, GCancellable *cancellable, char **out_sha256, guint64 *out_size_bytes, GError **error ); ``` L’API exacte peut être légèrement adaptée si l’architecture existante l’exige, mais les responsabilités suivantes doivent rester séparées : - le module calcule le hash et la taille ; - l’appelant reste propriétaire du chemin fourni ; - la chaîne retournée appartient à l’appelant ; - aucune opération SQLite ou GTK ne doit apparaître dans ce module. ## Comportement requis ### Validation des paramètres La fonction doit refuser : - un chemin `NULL` ; - un chemin vide ; - un pointeur `out_sha256` absent ; - un pointeur `out_size_bytes` absent ; - un `GError` déjà initialisé. En cas d’échec : ```c *out_sha256 = NULL; *out_size_bytes = 0; ``` dès que les pointeurs de sortie sont valides. ### Type de fichier Le module doit accepter uniquement un fichier régulier. Il doit refuser explicitement : - les dossiers ; - les liens symboliques ; - les sockets ; - les tubes nommés ; - les périphériques ; - tout autre type non régulier. Le contrôle ne doit pas suivre silencieusement un lien symbolique. ### Lecture Le fichier doit être ouvert en lecture seule. Le contenu doit être lu progressivement avec un tampon de taille fixe. Le fichier complet ne doit jamais être chargé en mémoire. Une taille de bloc raisonnable peut être utilisée, par exemple : ```text 64 Kio ``` La taille retournée doit correspondre au nombre total d’octets effectivement lus et intégrés au calcul. ### SHA-256 Le résultat doit être : - calculé avec SHA-256 ; - composé de 64 caractères hexadécimaux ; - normalisé en minuscules ; - terminé par zéro ; - alloué dynamiquement. Il est recommandé d’utiliser `GChecksum` avec `G_CHECKSUM_SHA256`. ### Annulation Le paramètre `GCancellable` est facultatif. La fonction doit contrôler l’annulation : - avant l’ouverture ; - avant ou après chaque bloc lu ; - avant de retourner le résultat final. Une annulation doit : - interrompre rapidement la lecture ; - libérer toutes les ressources ; - retourner `FALSE` ; - laisser les sorties dans leur état d’échec ; - produire une erreur identifiable comme une annulation. ### Erreurs d’entrée-sortie Les erreurs suivantes doivent être distinguées autant que raisonnablement possible : - fichier absent ; - fichier non régulier ; - ouverture impossible ; - lecture impossible ; - annulation. Le message doit contenir suffisamment de contexte pour comprendre l’échec, notamment le chemin concerné lorsque cela est pertinent. ### Propriété des ressources Après un succès : - `*out_sha256` contient une nouvelle chaîne à libérer avec `g_free()` ; - `*out_size_bytes` contient le nombre d’octets lus. Après un échec : - aucun descripteur ou flux ne reste ouvert ; - aucun `GChecksum` ne reste alloué ; - aucune chaîne partielle n’est rendue à l’appelant. ## Contraintes de sécurité et d’intégrité Le module ne doit jamais : - écrire dans le fichier ; - changer ses permissions ; - modifier ses dates ; - suivre volontairement un lien symbolique ; - exécuter le fichier ; - interpréter son contenu ; - utiliser son nom dans une commande shell. Le chemin doit être transmis directement aux API de fichiers, sans construction de commande. ## Tests obligatoires Le fichier `tests/test_file_hash.c` doit utiliser des fichiers et dossiers temporaires isolés. ### Fichier vide Vérifier : - succès ; - taille égale à `0` ; - SHA-256 connu du fichier vide : ```text e3b0c44298fc1c149afbf4c8996fb924 27ae41e4649b934ca495991b7852b855 ``` ### Contenu connu Créer un fichier contenant exactement : ```text abc ``` sans saut de ligne. Vérifier : - taille égale à `3` ; - SHA-256 : ```text ba7816bf8f01cfea414140de5dae2223 b00361a396177a9cb410ff61f20015ad ``` ### Fichier sur plusieurs blocs Créer un fichier plus grand que la taille du tampon de lecture. Vérifier : - que la taille complète est retournée ; - que le hash correspond à une valeur calculée indépendamment dans le test ou à une valeur connue ; - que le résultat ne dépend pas du découpage en blocs. Le test ne doit pas nécessiter un fichier volumineux : quelques centaines de Kio suffisent. ### Caractères binaires Créer un fichier contenant notamment : ```text 0x00 0xFF 0x80 ``` Vérifier que le calcul traite les données comme des octets et non comme du texte. ### Chemin absent Vérifier : - retour `FALSE` ; - hash `NULL` ; - taille `0` ; - erreur du module. ### Dossier Vérifier qu’un dossier est refusé avec `FILE_HASH_ERROR_NOT_REGULAR`. ### Lien symbolique Créer un fichier régulier puis un lien symbolique vers celui-ci. Vérifier que le lien est refusé, même si sa cible est un fichier valide. Si la plateforme de test ne permet pas de créer un lien symbolique, le test peut être explicitement ignoré avec une justification claire. ### Paramètres invalides Tester au minimum : - chemin `NULL` ; - chemin vide ; - `out_sha256 == NULL` ; - `out_size_bytes == NULL`. ### Annulation avant lecture Créer un `GCancellable`, l’annuler avant l’appel, puis vérifier : - retour `FALSE` ; - erreur d’annulation ; - sorties réinitialisées. ### Annulation pendant la lecture Tester une annulation observée entre deux blocs. Le test doit rester déterministe. Une solution acceptable consiste à prévoir une abstraction interne de lecture ou un mécanisme de test permettant de déclencher l’annulation après un nombre défini de blocs, sans ralentissement artificiel important. Ne pas utiliser un test dépendant uniquement d’un délai aléatoire ou de la vitesse de la machine. ### Appels successifs Calculer plusieurs fois le hash du même fichier et vérifier que : - le résultat est identique ; - aucune donnée interne n’est conservée entre les appels. ### Fichier modifié entre deux appels Calculer le hash, modifier le fichier, puis recalculer. Vérifier que le second hash est différent et que la nouvelle taille est correcte. ## Tests d’erreurs internes Les tests doivent vérifier que les sorties sont réinitialisées même lorsqu’elles contenaient auparavant des valeurs non nulles : ```c char *sha256 = g_strdup("ancienne-valeur"); guint64 size_bytes = 123; ``` Le test devra libérer cette ancienne valeur avant l’appel ou respecter précisément le contrat choisi. Le module ne doit pas libérer arbitrairement une chaîne dont il n’est pas propriétaire. Le contrat recommandé est d’exiger : ```c *out_sha256 == NULL ``` à l’entrée, conformément aux conventions GLib des paramètres de sortie alloués. Si ce contrat est retenu, il doit être documenté et testé. ## Critères d’acceptation Le ticket est validé lorsque : - le module compile avec `-std=c17 -Wall -Wextra -Werror` ; - aucun appel shell n’est utilisé ; - les fichiers sont lus par blocs ; - les fichiers non réguliers et les liens symboliques sont refusés ; - l’annulation est gérée proprement ; - le hash du fichier vide et de `abc` est exact ; - les données binaires sont correctement traitées ; - les sorties sont cohérentes après chaque échec ; - tous les tests du projet passent ; - `git diff --check` ne signale rien ; - Valgrind ne détecte aucune perte définie ou indirecte. ## Commandes de validation ```bash make clean make make test git diff --check ``` Test ciblé : ```bash ./tests/test_file_hash ``` Valgrind : ```bash valgrind \ --leak-check=full \ --show-leak-kinds=all \ --track-origins=yes \ --errors-for-leak-kinds=definite,indirect \ --error-exitcode=1 \ ./tests/test_file_hash ``` Résultats indispensables : ```text definitely lost: 0 bytes indirectly lost: 0 bytes ERROR SUMMARY: 0 errors ``` ## Hors périmètre Ce ticket ne doit pas encore : - copier le fichier dans l’enquête ; - créer un `EvidenceRecord` ; - insérer une preuve dans SQLite ; - choisir un nom interne ; - détecter le type de preuve ; - extraire des métadonnées ; - créer une interface GTK ; - lancer le calcul dans un `BackgroundTask`. Ces responsabilités seront traitées dans les tickets suivants. ## Suite prévue Le ticket suivant utilisera ce module pour réaliser une copie sûre du fichier vers le dossier des preuves originales, sans écrasement et avec vérification du SHA-256 après copie.
fy59 closed this issue 2026-07-19 13:49:48 +02:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: fy59/labfy-investigation#47
No description provided.