feat(pdf): add numeric range password recovery
This commit is contained in:
parent
45fed7fcd6
commit
832e3eca32
13 changed files with 994 additions and 1 deletions
12
Makefile
12
Makefile
|
|
@ -127,6 +127,7 @@ TEST_PERSON_ENTITY_SERVICE := tests/test_person_entity_service
|
|||
TEST_EML_ANALYZER := tests/test_eml_analyzer
|
||||
TEST_IBAN_ANALYZER := tests/test_iban_analyzer
|
||||
TEST_EXIFTOOL_METADATA := tests/test_exiftool_metadata
|
||||
TEST_PDF_PASSWORD_RECOVERY := tests/test_pdf_password_recovery
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
|
|
@ -656,6 +657,13 @@ $(TEST_EXIFTOOL_METADATA): tests/test_exiftool_metadata.c \
|
|||
src/core/exiftool_metadata.c
|
||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||||
|
||||
$(TEST_PDF_PASSWORD_RECOVERY): tests/test_pdf_password_recovery.c \
|
||||
src/core/pdf_password_recovery.c \
|
||||
src/core/tool_process.c \
|
||||
src/core/background_task.c \
|
||||
src/core/task_manager.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 \
|
||||
|
|
@ -737,7 +745,8 @@ test: \
|
|||
$(TEST_PERSON_ENTITY_SERVICE) \
|
||||
$(TEST_EML_ANALYZER) \
|
||||
$(TEST_IBAN_ANALYZER) \
|
||||
$(TEST_EXIFTOOL_METADATA)
|
||||
$(TEST_EXIFTOOL_METADATA) \
|
||||
$(TEST_PDF_PASSWORD_RECOVERY)
|
||||
@echo "Exécution des tests..."
|
||||
@./$(TEST_NODE)
|
||||
@./$(TEST_TREE_MODEL)
|
||||
|
|
@ -799,6 +808,7 @@ test: \
|
|||
@$(TEST_EML_ANALYZER)
|
||||
@$(TEST_IBAN_ANALYZER)
|
||||
@$(TEST_EXIFTOOL_METADATA)
|
||||
@$(TEST_PDF_PASSWORD_RECOVERY)
|
||||
@echo "Tous les tests sont valides."
|
||||
|
||||
%.o: %.c
|
||||
|
|
|
|||
72
include/core/pdf_password_recovery.h
Normal file
72
include/core/pdf_password_recovery.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/******************************************************************************
|
||||
* @file pdf_password_recovery.h
|
||||
* @brief Récupération locale du mot de passe d'une preuve PDF.
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef LABFY_INVESTIGATION_PDF_PASSWORD_RECOVERY_H
|
||||
#define LABFY_INVESTIGATION_PDF_PASSWORD_RECOVERY_H
|
||||
|
||||
#include "core/background_task.h"
|
||||
#include "core/task_manager.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/** @brief Méthode bornée utilisée par John the Ripper. */
|
||||
typedef enum
|
||||
{
|
||||
PDF_PASSWORD_RECOVERY_DICTIONARY,
|
||||
PDF_PASSWORD_RECOVERY_MASK,
|
||||
PDF_PASSWORD_RECOVERY_NUMERIC_RANGE
|
||||
} PdfPasswordRecoveryMethod;
|
||||
|
||||
/** @brief Résultat opaque d'une récupération de mot de passe. */
|
||||
typedef struct PdfPasswordRecoveryResult PdfPasswordRecoveryResult;
|
||||
|
||||
/**
|
||||
* @brief Lance la récupération dans le gestionnaire de tâches.
|
||||
* @param task_manager Gestionnaire recevant la tâche.
|
||||
* @param pdf_path Copie PDF à analyser.
|
||||
* @param output_directory Dossier de conservation du hash et du journal.
|
||||
* @param evidence_identifier UUID utilisé pour nommer les sorties.
|
||||
* @param method Méthode de récupération.
|
||||
* @param parameter Chemin du dictionnaire ou masque John.
|
||||
* @param completion_callback Callback exécuté sur le contexte principal.
|
||||
* @param completion_data Données transmises au callback.
|
||||
* @param completion_data_destroy Destructeur des données du callback.
|
||||
* @param error Emplacement facultatif pour l'erreur.
|
||||
* @return Tâche empruntée par le gestionnaire, ou NULL.
|
||||
*/
|
||||
BackgroundTask *pdf_password_recovery_start(TaskManager *task_manager,
|
||||
const char *pdf_path, const char *output_directory,
|
||||
const char *evidence_identifier, PdfPasswordRecoveryMethod method,
|
||||
const char *parameter, BackgroundTaskCompletionCallback completion_callback,
|
||||
gpointer completion_data, GDestroyNotify completion_data_destroy,
|
||||
GError **error);
|
||||
|
||||
/**
|
||||
* @brief Retourne le résultat d'une tâche terminée.
|
||||
* @param task Tâche créée par pdf_password_recovery_start().
|
||||
* @return Résultat emprunté, ou NULL.
|
||||
*/
|
||||
const PdfPasswordRecoveryResult *pdf_password_recovery_get_result(
|
||||
const BackgroundTask *task);
|
||||
|
||||
/**
|
||||
* @brief Indique si un mot de passe a été retrouvé.
|
||||
* @param result Résultat emprunté.
|
||||
* @return TRUE si un mot de passe est disponible.
|
||||
*/
|
||||
gboolean pdf_password_recovery_result_is_recovered(
|
||||
const PdfPasswordRecoveryResult *result);
|
||||
|
||||
/**
|
||||
* @brief Retourne le mot de passe retrouvé uniquement en mémoire.
|
||||
* @param result Résultat emprunté.
|
||||
* @return Mot de passe emprunté, ou NULL.
|
||||
*/
|
||||
const char *pdf_password_recovery_result_get_password(
|
||||
const PdfPasswordRecoveryResult *result);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
|
@ -107,6 +107,9 @@ typedef void (*MainWindowAnalyzeRibCallback)(const char *evidence_identifier,
|
|||
/** @brief Callback appelé pour extraire les métadonnées d'une preuve. */
|
||||
typedef void (*MainWindowExtractMetadataCallback)(const char *evidence_identifier,
|
||||
gpointer user_data);
|
||||
/** @brief Callback appelé pour récupérer le mot de passe d'un PDF. */
|
||||
typedef void (*MainWindowRecoverPdfPasswordCallback)(
|
||||
const char *evidence_identifier, gpointer user_data);
|
||||
|
||||
/**
|
||||
* @brief Callback appelé après le déplacement effectif d'un nœud.
|
||||
|
|
@ -224,6 +227,9 @@ void main_window_set_analyze_rib_callback(MainWindow *main_window,
|
|||
/** @brief Définit le callback d'extraction locale des métadonnées. */
|
||||
void main_window_set_extract_metadata_callback(MainWindow *main_window,
|
||||
MainWindowExtractMetadataCallback callback, gpointer user_data);
|
||||
/** @brief Définit le callback de récupération d'un mot de passe PDF. */
|
||||
void main_window_set_recover_pdf_password_callback(MainWindow *main_window,
|
||||
MainWindowRecoverPdfPasswordCallback callback, gpointer user_data);
|
||||
|
||||
/**
|
||||
* @brief Définit le callback de fin de déplacement d'un nœud.
|
||||
|
|
|
|||
29
include/views/pdf_password_dialog.h
Normal file
29
include/views/pdf_password_dialog.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/******************************************************************************
|
||||
* @file pdf_password_dialog.h
|
||||
* @brief Paramétrage d'une récupération de mot de passe PDF.
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef LABFY_INVESTIGATION_PDF_PASSWORD_DIALOG_H
|
||||
#define LABFY_INVESTIGATION_PDF_PASSWORD_DIALOG_H
|
||||
|
||||
#include "core/pdf_password_recovery.h"
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/** @brief Callback recevant la méthode et son paramètre validé. */
|
||||
typedef void (*PdfPasswordDialogCallback)(PdfPasswordRecoveryMethod method,
|
||||
const char *parameter, gpointer user_data);
|
||||
|
||||
/**
|
||||
* @brief Présente le formulaire de récupération ciblée.
|
||||
* @param parent Fenêtre parente facultative.
|
||||
* @param callback Callback appelé après validation.
|
||||
* @param user_data Données empruntées du callback.
|
||||
*/
|
||||
void pdf_password_dialog_present(GtkWindow *parent,
|
||||
PdfPasswordDialogCallback callback, gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
|
@ -141,6 +141,9 @@ typedef void (*WorkspaceAnalyzeRibCallback)(const char *evidence_identifier,
|
|||
/** @brief Callback appelé pour extraire les métadonnées d'une preuve. */
|
||||
typedef void (*WorkspaceExtractMetadataCallback)(const char *evidence_identifier,
|
||||
gpointer user_data);
|
||||
/** @brief Callback appelé pour récupérer le mot de passe d'un PDF. */
|
||||
typedef void (*WorkspaceRecoverPdfPasswordCallback)(
|
||||
const char *evidence_identifier, gpointer user_data);
|
||||
|
||||
/**
|
||||
* @brief Crée une nouvelle zone de travail.
|
||||
|
|
@ -280,6 +283,9 @@ void workspace_set_analyze_rib_callback(Workspace *workspace,
|
|||
/** @brief Définit le callback d'extraction locale des métadonnées. */
|
||||
void workspace_set_extract_metadata_callback(Workspace *workspace,
|
||||
WorkspaceExtractMetadataCallback callback, gpointer user_data);
|
||||
/** @brief Définit le callback de récupération d'un mot de passe PDF. */
|
||||
void workspace_set_recover_pdf_password_callback(Workspace *workspace,
|
||||
WorkspaceRecoverPdfPasswordCallback callback, gpointer user_data);
|
||||
|
||||
/**
|
||||
* @brief Définit le callback de vérification de la preuve affichée.
|
||||
|
|
|
|||
|
|
@ -65,9 +65,13 @@
|
|||
#include "core/iban_analyzer.h"
|
||||
#include "core/exiftool_metadata.h"
|
||||
#include "views/metadata_analysis_dialog.h"
|
||||
#include "core/pdf_password_recovery.h"
|
||||
#include "views/pdf_password_dialog.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* @brief Identifiant unique utilisé par GLib pour l'application.
|
||||
|
|
@ -3624,6 +3628,253 @@ cleanup:
|
|||
evidence_dao_free(dao);
|
||||
}
|
||||
|
||||
/** @brief Contexte conservé pendant la tâche de récupération PDF. */
|
||||
typedef struct
|
||||
{
|
||||
Application *application;
|
||||
char *copy_path;
|
||||
char *output_directory;
|
||||
char *evidence_identifier;
|
||||
} ApplicationPdfRecoveryContext;
|
||||
|
||||
/** @brief Libère le contexte d'une récupération PDF. */
|
||||
static void application_pdf_recovery_context_free(gpointer data)
|
||||
{
|
||||
ApplicationPdfRecoveryContext *context = data;
|
||||
if (context == NULL) return;
|
||||
g_free(context->copy_path);
|
||||
g_free(context->output_directory);
|
||||
g_free(context->evidence_identifier);
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
/** @brief Contexte secret utilisé pour créer la copie PDF déverrouillée. */
|
||||
typedef struct
|
||||
{
|
||||
Application *application;
|
||||
char *password;
|
||||
char *copy_path;
|
||||
char *unlocked_path;
|
||||
} ApplicationPdfDecryptContext;
|
||||
|
||||
/** @brief Efface le mot de passe puis libère le contexte de déchiffrement. */
|
||||
static void application_pdf_decrypt_context_free(gpointer data)
|
||||
{
|
||||
ApplicationPdfDecryptContext *context = data;
|
||||
if (context == NULL) return;
|
||||
if (context->password != NULL)
|
||||
memset(context->password, 0, strlen(context->password));
|
||||
g_free(context->password);
|
||||
g_free(context->copy_path);
|
||||
g_free(context->unlocked_path);
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
/** @brief Crée avec qpdf la copie déverrouillée demandée explicitement. */
|
||||
static void application_on_pdf_decrypt_requested(gpointer user_data)
|
||||
{
|
||||
ApplicationPdfDecryptContext *context = user_data;
|
||||
GSubprocess *process = NULL;
|
||||
char *password_path = NULL;
|
||||
char *password_argument = NULL;
|
||||
char *stderr_text = NULL;
|
||||
int descriptor = -1;
|
||||
GError *error = NULL;
|
||||
const char *arguments[6] = { "qpdf", NULL, "--decrypt", NULL, NULL, NULL };
|
||||
gssize password_length = 0;
|
||||
|
||||
if (context == NULL || context->application == NULL) return;
|
||||
descriptor = g_file_open_tmp("labfy-qpdf-password-XXXXXX",
|
||||
&password_path, &error);
|
||||
password_length = context->password != NULL ?
|
||||
(gssize) strlen(context->password) : 0;
|
||||
if (descriptor < 0 || password_length == 0 ||
|
||||
write(descriptor, context->password, (size_t) password_length) !=
|
||||
password_length)
|
||||
goto failure;
|
||||
close(descriptor);
|
||||
descriptor = -1;
|
||||
password_argument = g_strdup_printf("--password-file=%s", password_path);
|
||||
arguments[1] = password_argument;
|
||||
arguments[3] = context->copy_path;
|
||||
arguments[4] = context->unlocked_path;
|
||||
process = g_subprocess_newv(arguments,
|
||||
G_SUBPROCESS_FLAGS_STDOUT_SILENCE | G_SUBPROCESS_FLAGS_STDERR_PIPE,
|
||||
&error);
|
||||
if (process == NULL || !g_subprocess_communicate_utf8(process, NULL, NULL,
|
||||
NULL, &stderr_text, &error) || !g_subprocess_get_successful(process))
|
||||
{
|
||||
if (error == NULL)
|
||||
g_set_error(&error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"qpdf a échoué : %s", stderr_text != NULL ? stderr_text :
|
||||
"aucun détail disponible");
|
||||
goto failure;
|
||||
}
|
||||
application_message_dialog_present(
|
||||
main_window_get_window(context->application->main_window),
|
||||
APPLICATION_MESSAGE_DIALOG_INFORMATION, "PDF déverrouillé",
|
||||
"La copie déverrouillée a été créée dans les preuves traitées.");
|
||||
main_window_set_status(context->application->main_window,
|
||||
"Copie PDF déverrouillée créée.");
|
||||
goto cleanup;
|
||||
failure:
|
||||
application_present_error(context->application,
|
||||
"Déverrouillage impossible", error != NULL ? error->message :
|
||||
"qpdf n'a pas pu créer la copie déverrouillée.");
|
||||
cleanup:
|
||||
if (descriptor >= 0) close(descriptor);
|
||||
if (password_path != NULL) g_unlink(password_path);
|
||||
g_clear_error(&error);
|
||||
g_clear_object(&process);
|
||||
g_free(password_path);
|
||||
g_free(password_argument);
|
||||
g_free(stderr_text);
|
||||
}
|
||||
|
||||
/** @brief Présente le résultat final d'une récupération PDF. */
|
||||
static void application_on_pdf_recovery_completed(BackgroundTask *task,
|
||||
gpointer user_data)
|
||||
{
|
||||
ApplicationPdfRecoveryContext *context = user_data;
|
||||
const PdfPasswordRecoveryResult *result = NULL;
|
||||
const char *password = NULL;
|
||||
ApplicationPdfDecryptContext *decrypt_context = NULL;
|
||||
char *details = NULL;
|
||||
char *unlocked_name = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
if (context == NULL || context->application == NULL) return;
|
||||
if (background_task_get_state(task) == BACKGROUND_TASK_STATE_CANCELLED)
|
||||
{
|
||||
main_window_set_status(context->application->main_window,
|
||||
"Récupération du mot de passe PDF annulée.");
|
||||
return;
|
||||
}
|
||||
if (background_task_get_state(task) != BACKGROUND_TASK_STATE_COMPLETED)
|
||||
{
|
||||
error = background_task_dup_error(task);
|
||||
application_present_error(context->application,
|
||||
"Récupération PDF impossible", error != NULL ? error->message :
|
||||
"John the Ripper a échoué.");
|
||||
g_clear_error(&error);
|
||||
return;
|
||||
}
|
||||
result = pdf_password_recovery_get_result(task);
|
||||
if (!pdf_password_recovery_result_is_recovered(result))
|
||||
{
|
||||
application_message_dialog_present(
|
||||
main_window_get_window(context->application->main_window),
|
||||
APPLICATION_MESSAGE_DIALOG_WARNING, "Mot de passe non trouvé",
|
||||
"Aucun candidat n'a correspondu. Essayez un dictionnaire enrichi "
|
||||
"ou un masque plus précis.");
|
||||
return;
|
||||
}
|
||||
password = pdf_password_recovery_result_get_password(result);
|
||||
decrypt_context = g_new0(ApplicationPdfDecryptContext, 1);
|
||||
decrypt_context->application = context->application;
|
||||
decrypt_context->password = g_strdup(password);
|
||||
decrypt_context->copy_path = g_strdup(context->copy_path);
|
||||
unlocked_name = g_strdup_printf("%s-unlocked.pdf",
|
||||
context->evidence_identifier);
|
||||
decrypt_context->unlocked_path = g_build_filename(
|
||||
context->output_directory, unlocked_name, NULL);
|
||||
details = g_strdup_printf("Mot de passe retrouvé : %s\n\n"
|
||||
"Il est uniquement conservé en mémoire. Utilisez l'action ci-dessous "
|
||||
"pour créer une copie déverrouillée.", password);
|
||||
application_message_dialog_present_details_action(
|
||||
main_window_get_window(context->application->main_window),
|
||||
APPLICATION_MESSAGE_DIALOG_INFORMATION, "Mot de passe PDF retrouvé",
|
||||
"La recherche locale a réussi.", details,
|
||||
"Créer la copie déverrouillée", application_on_pdf_decrypt_requested,
|
||||
decrypt_context, application_pdf_decrypt_context_free);
|
||||
g_free(details);
|
||||
g_free(unlocked_name);
|
||||
}
|
||||
|
||||
/** @brief Prépare la copie PDF puis démarre la tâche choisie. */
|
||||
static void application_on_pdf_recovery_configured(
|
||||
PdfPasswordRecoveryMethod method, const char *parameter,
|
||||
gpointer user_data)
|
||||
{
|
||||
Application *application = user_data;
|
||||
const InvestigationProject *project = NULL;
|
||||
EvidenceDao *dao = NULL;
|
||||
EvidenceRecord *record = NULL;
|
||||
ApplicationPdfRecoveryContext *context = NULL;
|
||||
char *root = NULL;
|
||||
char *candidate = NULL;
|
||||
char *source_path = NULL;
|
||||
char *copy_name = NULL;
|
||||
GFile *source_file = NULL;
|
||||
GFile *copy_file = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
if (application == NULL || application->session == NULL ||
|
||||
application->selected_evidence_identifier == NULL) return;
|
||||
dao = evidence_dao_new(
|
||||
investigation_session_get_database(application->session), &error);
|
||||
if (dao != NULL)
|
||||
record = evidence_dao_find_by_identifier(dao,
|
||||
application->selected_evidence_identifier, &error);
|
||||
project = investigation_session_get_project(application->session);
|
||||
if (record == NULL || project == NULL) goto failure;
|
||||
root = g_canonicalize_filename(investigation_project_get_root_path(project),
|
||||
NULL);
|
||||
candidate = g_build_filename(root,
|
||||
evidence_record_get_relative_path(record), NULL);
|
||||
source_path = g_canonicalize_filename(candidate, NULL);
|
||||
if (source_path == NULL || !g_str_has_prefix(source_path, root) ||
|
||||
(source_path[strlen(root)] != G_DIR_SEPARATOR &&
|
||||
source_path[strlen(root)] != '\0')) goto failure;
|
||||
context = g_new0(ApplicationPdfRecoveryContext, 1);
|
||||
context->application = application;
|
||||
context->evidence_identifier = g_strdup(
|
||||
application->selected_evidence_identifier);
|
||||
context->output_directory = g_build_filename(root,
|
||||
"02_Preuves_Traitees", "Extractions", "PDF", NULL);
|
||||
if (g_mkdir_with_parents(context->output_directory, 0750) != 0) goto failure;
|
||||
copy_name = g_strdup_printf("%s-locked.pdf", context->evidence_identifier);
|
||||
context->copy_path = g_build_filename(context->output_directory,
|
||||
copy_name, NULL);
|
||||
source_file = g_file_new_for_path(source_path);
|
||||
copy_file = g_file_new_for_path(context->copy_path);
|
||||
if (!g_file_copy(source_file, copy_file, G_FILE_COPY_OVERWRITE,
|
||||
NULL, NULL, NULL, &error)) goto failure;
|
||||
if (pdf_password_recovery_start(application->task_manager,
|
||||
context->copy_path, context->output_directory,
|
||||
context->evidence_identifier, method, parameter,
|
||||
application_on_pdf_recovery_completed, context,
|
||||
application_pdf_recovery_context_free, &error) == NULL)
|
||||
goto failure;
|
||||
context = NULL;
|
||||
main_window_set_status(application->main_window,
|
||||
"Récupération PDF lancée ; progression disponible dans les tâches.");
|
||||
goto cleanup;
|
||||
failure:
|
||||
application_pdf_recovery_context_free(context);
|
||||
application_present_error(application, "Récupération PDF impossible",
|
||||
error != NULL ? error->message :
|
||||
"La copie de travail n'a pas pu être préparée.");
|
||||
cleanup:
|
||||
g_clear_error(&error); g_clear_object(&source_file);
|
||||
g_clear_object(©_file); g_free(root); g_free(candidate);
|
||||
g_free(source_path); g_free(copy_name); evidence_record_free(record);
|
||||
evidence_dao_free(dao);
|
||||
}
|
||||
|
||||
/** @brief Ouvre la configuration de récupération pour le PDF sélectionné. */
|
||||
static void application_on_recover_pdf_password_requested(
|
||||
const char *evidence_identifier, gpointer user_data)
|
||||
{
|
||||
Application *application = user_data;
|
||||
if (application == NULL || evidence_identifier == NULL) return;
|
||||
g_free(application->selected_evidence_identifier);
|
||||
application->selected_evidence_identifier = g_strdup(evidence_identifier);
|
||||
pdf_password_dialog_present(
|
||||
main_window_get_window(application->main_window),
|
||||
application_on_pdf_recovery_configured, application);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prépare et démarre l’import asynchrone d’un fichier.
|
||||
*/
|
||||
|
|
@ -7197,6 +7448,8 @@ static void application_on_activate(
|
|||
application_on_analyze_rib_requested, application);
|
||||
main_window_set_extract_metadata_callback(application->main_window,
|
||||
application_on_extract_metadata_requested, application);
|
||||
main_window_set_recover_pdf_password_callback(application->main_window,
|
||||
application_on_recover_pdf_password_requested, application);
|
||||
|
||||
main_window_set_graph_node_moved_callback(
|
||||
application->main_window,
|
||||
|
|
|
|||
336
src/core/pdf_password_recovery.c
Normal file
336
src/core/pdf_password_recovery.c
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
/******************************************************************************
|
||||
* @file pdf_password_recovery.c
|
||||
* @brief Récupération locale du mot de passe d'une preuve PDF.
|
||||
******************************************************************************/
|
||||
|
||||
#include "core/pdf_password_recovery.h"
|
||||
#include "core/tool_process.h"
|
||||
|
||||
#include <glib/gstdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *pdf_path;
|
||||
char *output_directory;
|
||||
char *evidence_identifier;
|
||||
PdfPasswordRecoveryMethod method;
|
||||
char *parameter;
|
||||
} PdfPasswordRecoveryData;
|
||||
|
||||
struct PdfPasswordRecoveryResult
|
||||
{
|
||||
gboolean recovered;
|
||||
char *password;
|
||||
};
|
||||
|
||||
/** @brief Libère les paramètres possédés par le worker. */
|
||||
static void pdf_password_recovery_data_free(gpointer data)
|
||||
{
|
||||
PdfPasswordRecoveryData *recovery_data = data;
|
||||
if (recovery_data == NULL) return;
|
||||
g_free(recovery_data->pdf_path);
|
||||
g_free(recovery_data->output_directory);
|
||||
g_free(recovery_data->evidence_identifier);
|
||||
g_free(recovery_data->parameter);
|
||||
g_free(recovery_data);
|
||||
}
|
||||
|
||||
/** @brief Efface puis libère le résultat contenant le secret. */
|
||||
static void pdf_password_recovery_result_free(gpointer data)
|
||||
{
|
||||
PdfPasswordRecoveryResult *result = data;
|
||||
if (result == NULL) return;
|
||||
if (result->password != NULL)
|
||||
memset(result->password, 0, strlen(result->password));
|
||||
g_free(result->password);
|
||||
g_free(result);
|
||||
}
|
||||
|
||||
/** @brief Convertit une sortie binaire d'outil en texte UTF-8. */
|
||||
static char *pdf_password_recovery_output_to_text(
|
||||
const ToolProcessResult *process_result)
|
||||
{
|
||||
GBytes *bytes = tool_process_result_ref_stdout(process_result);
|
||||
gconstpointer data = NULL;
|
||||
gsize size = 0;
|
||||
char *text = NULL;
|
||||
if (bytes != NULL) data = g_bytes_get_data(bytes, &size);
|
||||
if (data != NULL) text = g_utf8_make_valid(data, (gssize) size);
|
||||
g_clear_pointer(&bytes, g_bytes_unref);
|
||||
return text;
|
||||
}
|
||||
|
||||
/** @brief Exécute un outil et exige un code de sortie nul. */
|
||||
static gboolean pdf_password_recovery_run(const char *executable,
|
||||
const char *const arguments[], const char *working_directory,
|
||||
GCancellable *cancellable, ToolProcessResult **result, GError **error)
|
||||
{
|
||||
if (!tool_process_run(executable, arguments, working_directory,
|
||||
cancellable, result, error)) return FALSE;
|
||||
if (!tool_process_result_is_success(*result))
|
||||
{
|
||||
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"%s s'est terminé avec le code %d.", executable,
|
||||
tool_process_result_get_exit_status(*result));
|
||||
tool_process_result_free(*result);
|
||||
*result = NULL;
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/** @brief Exécute pdf2john puis John dans un thread secondaire. */
|
||||
static gboolean pdf_password_recovery_worker(BackgroundTask *task,
|
||||
GCancellable *cancellable, gpointer worker_data, gpointer *out_result,
|
||||
GError **error)
|
||||
{
|
||||
PdfPasswordRecoveryData *data = worker_data;
|
||||
PdfPasswordRecoveryResult *result = NULL;
|
||||
ToolProcessResult *process_result = NULL;
|
||||
char *hash_text = NULL;
|
||||
char *hash_name = NULL;
|
||||
char *hash_path = NULL;
|
||||
char *journal_name = NULL;
|
||||
char *journal_path = NULL;
|
||||
char *temp_directory = NULL;
|
||||
char *pot_path = NULL;
|
||||
char *session_path = NULL;
|
||||
char *method_argument = NULL;
|
||||
char *pot_argument = NULL;
|
||||
char *session_argument = NULL;
|
||||
char *home_argument = NULL;
|
||||
char *show_text = NULL;
|
||||
char *separator = NULL;
|
||||
char *line_end = NULL;
|
||||
char *journal = NULL;
|
||||
gchar **numeric_bounds = NULL;
|
||||
const char *pdf_arguments[] = { data->pdf_path, NULL };
|
||||
const char *john_arguments[7] =
|
||||
{ NULL, "john", NULL, NULL, NULL, NULL, NULL };
|
||||
const char *show_arguments[6] =
|
||||
{ NULL, "john", "--show", NULL, NULL, NULL };
|
||||
gboolean success = FALSE;
|
||||
|
||||
*out_result = NULL;
|
||||
background_task_report_progress(task, 0.05, "Extraction du hash PDF");
|
||||
if (!pdf_password_recovery_run("pdf2john", pdf_arguments, NULL,
|
||||
cancellable, &process_result, error)) goto cleanup;
|
||||
hash_text = pdf_password_recovery_output_to_text(process_result);
|
||||
tool_process_result_free(process_result);
|
||||
process_result = NULL;
|
||||
if (hash_text == NULL || strstr(hash_text, "$pdf$") == NULL)
|
||||
{
|
||||
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
||||
"pdf2john n'a produit aucun hash PDF exploitable.");
|
||||
goto cleanup;
|
||||
}
|
||||
if (g_mkdir_with_parents(data->output_directory, 0750) != 0)
|
||||
{
|
||||
g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errno),
|
||||
"Impossible de créer le dossier de récupération : %s",
|
||||
g_strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
hash_name = g_strdup_printf("%s-pdf.hash", data->evidence_identifier);
|
||||
hash_path = g_build_filename(data->output_directory, hash_name, NULL);
|
||||
if (!g_file_set_contents(hash_path, hash_text, -1, error)) goto cleanup;
|
||||
if (data->method == PDF_PASSWORD_RECOVERY_NUMERIC_RANGE)
|
||||
{
|
||||
guint64 minimum = 0;
|
||||
guint64 maximum = 0;
|
||||
guint64 candidate = 0;
|
||||
GError *qpdf_error = NULL;
|
||||
numeric_bounds = g_strsplit(data->parameter, ":", 2);
|
||||
minimum = g_ascii_strtoull(numeric_bounds[0], NULL, 10);
|
||||
maximum = g_ascii_strtoull(numeric_bounds[1], NULL, 10);
|
||||
result = g_new0(PdfPasswordRecoveryResult, 1);
|
||||
for (candidate = minimum; candidate <= maximum; candidate++)
|
||||
{
|
||||
char *password = g_strdup_printf("%" G_GUINT64_FORMAT, candidate);
|
||||
char *password_argument = g_strdup_printf("--password=%s", password);
|
||||
const char *qpdf_arguments[] =
|
||||
{ password_argument, "--decrypt", data->pdf_path,
|
||||
"/dev/null", NULL };
|
||||
background_task_report_progress(task,
|
||||
maximum == minimum ? 0.5 : 0.1 +
|
||||
(0.85 * ((double) (candidate - minimum) /
|
||||
(double) (maximum - minimum + 1))),
|
||||
"Recherche numérique en cours");
|
||||
g_clear_error(&qpdf_error);
|
||||
if (pdf_password_recovery_run("qpdf", qpdf_arguments, NULL,
|
||||
cancellable, &process_result, &qpdf_error))
|
||||
{
|
||||
result->recovered = TRUE;
|
||||
result->password = g_steal_pointer(&password);
|
||||
g_free(password_argument);
|
||||
break;
|
||||
}
|
||||
if (qpdf_error != NULL && g_error_matches(qpdf_error,
|
||||
TOOL_PROCESS_ERROR, TOOL_PROCESS_ERROR_CANCELLED))
|
||||
{
|
||||
g_propagate_error(error, qpdf_error);
|
||||
g_free(password);
|
||||
g_free(password_argument);
|
||||
goto cleanup;
|
||||
}
|
||||
g_clear_error(&qpdf_error);
|
||||
tool_process_result_free(process_result);
|
||||
process_result = NULL;
|
||||
g_free(password);
|
||||
g_free(password_argument);
|
||||
if (candidate == G_MAXUINT64) break;
|
||||
}
|
||||
journal_name = g_strdup_printf("%s-recovery.log",
|
||||
data->evidence_identifier);
|
||||
journal_path = g_build_filename(data->output_directory, journal_name, NULL);
|
||||
journal = g_strdup_printf("Outil: qpdf\nMéthode: plage numérique\n"
|
||||
"Résultat: %s\nLe mot de passe n'est pas enregistré dans ce journal.\n",
|
||||
result->recovered ? "trouvé" : "non trouvé");
|
||||
if (!g_file_set_contents(journal_path, journal, -1, error)) goto cleanup;
|
||||
*out_result = g_steal_pointer(&result);
|
||||
success = TRUE;
|
||||
goto cleanup;
|
||||
}
|
||||
temp_directory = g_dir_make_tmp("labfy-pdf-recovery-XXXXXX", error);
|
||||
if (temp_directory == NULL) goto cleanup;
|
||||
pot_path = g_build_filename(temp_directory, "john.pot", NULL);
|
||||
session_path = g_build_filename(temp_directory, "john-session", NULL);
|
||||
pot_argument = g_strdup_printf("--pot=%s", pot_path);
|
||||
session_argument = g_strdup_printf("--session=%s", session_path);
|
||||
home_argument = g_strdup_printf("HOME=%s", temp_directory);
|
||||
method_argument = g_strdup_printf(data->method ==
|
||||
PDF_PASSWORD_RECOVERY_DICTIONARY ? "--wordlist=%s" : "--mask=%s",
|
||||
data->parameter);
|
||||
john_arguments[0] = home_argument;
|
||||
john_arguments[2] = pot_argument;
|
||||
john_arguments[3] = session_argument;
|
||||
john_arguments[4] = method_argument;
|
||||
john_arguments[5] = hash_path;
|
||||
background_task_report_progress(task, 0.15,
|
||||
"Recherche du mot de passe avec John");
|
||||
if (!pdf_password_recovery_run("env", john_arguments, temp_directory,
|
||||
cancellable, &process_result, error)) goto cleanup;
|
||||
tool_process_result_free(process_result);
|
||||
process_result = NULL;
|
||||
show_arguments[0] = home_argument;
|
||||
show_arguments[3] = pot_argument;
|
||||
show_arguments[4] = hash_path;
|
||||
if (!pdf_password_recovery_run("env", show_arguments, temp_directory,
|
||||
cancellable, &process_result, error)) goto cleanup;
|
||||
show_text = pdf_password_recovery_output_to_text(process_result);
|
||||
result = g_new0(PdfPasswordRecoveryResult, 1);
|
||||
/* John peut écrire des avertissements MPI contenant ':' avant la sortie
|
||||
* de --show ; on ne considère que la ligne du fichier hash. */
|
||||
separator = show_text != NULL ? strstr(show_text, data->pdf_path) : NULL;
|
||||
if (separator != NULL)
|
||||
separator = strchr(separator, ':');
|
||||
if (separator != NULL && strstr(show_text, "0 password hashes cracked") == NULL)
|
||||
{
|
||||
separator++;
|
||||
line_end = strchr(separator, '\n');
|
||||
result->password = line_end != NULL ? g_strndup(separator,
|
||||
(gsize) (line_end - separator)) : g_strdup(separator);
|
||||
result->recovered = result->password != NULL;
|
||||
}
|
||||
journal_name = g_strdup_printf("%s-recovery.log",
|
||||
data->evidence_identifier);
|
||||
journal_path = g_build_filename(data->output_directory, journal_name, NULL);
|
||||
journal = g_strdup_printf("Outil: John the Ripper\nMéthode: %s\nRésultat: %s\n"
|
||||
"Le mot de passe n'est pas enregistré dans ce journal.\n",
|
||||
data->method == PDF_PASSWORD_RECOVERY_DICTIONARY ?
|
||||
"dictionnaire" : "masque", result->recovered ? "trouvé" : "non trouvé");
|
||||
if (!g_file_set_contents(journal_path, journal, -1, error)) goto cleanup;
|
||||
background_task_report_progress(task, 1.0, result->recovered ?
|
||||
"Mot de passe retrouvé" : "Aucun mot de passe trouvé");
|
||||
*out_result = g_steal_pointer(&result);
|
||||
success = TRUE;
|
||||
cleanup:
|
||||
tool_process_result_free(process_result);
|
||||
pdf_password_recovery_result_free(result);
|
||||
if (pot_path != NULL) g_unlink(pot_path);
|
||||
if (session_path != NULL)
|
||||
{
|
||||
char *rec_path = g_strconcat(session_path, ".rec", NULL);
|
||||
char *log_path = g_strconcat(session_path, ".log", NULL);
|
||||
g_unlink(rec_path);
|
||||
g_unlink(log_path);
|
||||
g_free(rec_path);
|
||||
g_free(log_path);
|
||||
}
|
||||
if (temp_directory != NULL)
|
||||
{
|
||||
char *john_home = g_build_filename(temp_directory, ".john", NULL);
|
||||
g_rmdir(john_home);
|
||||
g_free(john_home);
|
||||
g_rmdir(temp_directory);
|
||||
}
|
||||
g_free(hash_text); g_free(hash_name); g_free(hash_path);
|
||||
g_free(journal_name); g_free(journal_path); g_free(temp_directory);
|
||||
g_free(pot_path); g_free(session_path); g_free(method_argument);
|
||||
g_free(pot_argument); g_free(session_argument); g_free(home_argument);
|
||||
g_free(show_text);
|
||||
g_free(journal);
|
||||
g_strfreev(numeric_bounds);
|
||||
return success;
|
||||
}
|
||||
|
||||
BackgroundTask *pdf_password_recovery_start(TaskManager *task_manager,
|
||||
const char *pdf_path, const char *output_directory,
|
||||
const char *evidence_identifier, PdfPasswordRecoveryMethod method,
|
||||
const char *parameter, BackgroundTaskCompletionCallback completion_callback,
|
||||
gpointer completion_data, GDestroyNotify completion_data_destroy,
|
||||
GError **error)
|
||||
{
|
||||
PdfPasswordRecoveryData *data = NULL;
|
||||
BackgroundTask *task = NULL;
|
||||
if (task_manager == NULL || pdf_path == NULL || output_directory == NULL ||
|
||||
evidence_identifier == NULL || parameter == NULL || parameter[0] == '\0' ||
|
||||
(method != PDF_PASSWORD_RECOVERY_DICTIONARY &&
|
||||
method != PDF_PASSWORD_RECOVERY_MASK &&
|
||||
method != PDF_PASSWORD_RECOVERY_NUMERIC_RANGE))
|
||||
{
|
||||
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
|
||||
"Les paramètres de récupération du PDF sont invalides.");
|
||||
return NULL;
|
||||
}
|
||||
data = g_new0(PdfPasswordRecoveryData, 1);
|
||||
data->pdf_path = g_strdup(pdf_path);
|
||||
data->output_directory = g_strdup(output_directory);
|
||||
data->evidence_identifier = g_strdup(evidence_identifier);
|
||||
data->method = method;
|
||||
data->parameter = g_strdup(parameter);
|
||||
task = background_task_new("Récupération du mot de passe PDF");
|
||||
if (task == NULL || !task_manager_add(task_manager, task, error) ||
|
||||
!background_task_start(task, pdf_password_recovery_worker, data,
|
||||
pdf_password_recovery_data_free,
|
||||
pdf_password_recovery_result_free, completion_callback,
|
||||
completion_data, completion_data_destroy, error))
|
||||
{
|
||||
if (task != NULL) task_manager_remove(task_manager, task);
|
||||
pdf_password_recovery_data_free(data);
|
||||
background_task_unref(task);
|
||||
return NULL;
|
||||
}
|
||||
background_task_unref(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
const PdfPasswordRecoveryResult *pdf_password_recovery_get_result(
|
||||
const BackgroundTask *task)
|
||||
{
|
||||
return task != NULL ? background_task_get_result(task) : NULL;
|
||||
}
|
||||
|
||||
gboolean pdf_password_recovery_result_is_recovered(
|
||||
const PdfPasswordRecoveryResult *result)
|
||||
{
|
||||
return result != NULL && result->recovered;
|
||||
}
|
||||
|
||||
const char *pdf_password_recovery_result_get_password(
|
||||
const PdfPasswordRecoveryResult *result)
|
||||
{
|
||||
return result != NULL ? result->password : NULL;
|
||||
}
|
||||
|
|
@ -69,6 +69,21 @@ static const char *const tool_catalog_exiftool_version_arguments[] =
|
|||
"-ver",
|
||||
NULL
|
||||
};
|
||||
static const char *const tool_catalog_john_version_arguments[] =
|
||||
{
|
||||
"--list=build-info",
|
||||
NULL
|
||||
};
|
||||
static const char *const tool_catalog_pdf2john_version_arguments[] =
|
||||
{
|
||||
"--help",
|
||||
NULL
|
||||
};
|
||||
static const char *const tool_catalog_qpdf_version_arguments[] =
|
||||
{
|
||||
"--version",
|
||||
NULL
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Catalogue statique initial.
|
||||
|
|
@ -135,6 +150,30 @@ static const ToolCatalogEntry tool_catalog_entries[] =
|
|||
.requirement = TOOL_REQUIREMENT_OPTIONAL,
|
||||
.version_arguments = tool_catalog_exiftool_version_arguments,
|
||||
.version_argument_count = 1
|
||||
},
|
||||
{
|
||||
.identifier = "password.john",
|
||||
.display_name = "John the Ripper",
|
||||
.executable_name = "john",
|
||||
.requirement = TOOL_REQUIREMENT_OPTIONAL,
|
||||
.version_arguments = tool_catalog_john_version_arguments,
|
||||
.version_argument_count = 1
|
||||
},
|
||||
{
|
||||
.identifier = "password.pdf2john",
|
||||
.display_name = "pdf2john",
|
||||
.executable_name = "pdf2john",
|
||||
.requirement = TOOL_REQUIREMENT_OPTIONAL,
|
||||
.version_arguments = tool_catalog_pdf2john_version_arguments,
|
||||
.version_argument_count = 1
|
||||
},
|
||||
{
|
||||
.identifier = "pdf.qpdf",
|
||||
.display_name = "qpdf",
|
||||
.executable_name = "qpdf",
|
||||
.requirement = TOOL_REQUIREMENT_OPTIONAL,
|
||||
.version_arguments = tool_catalog_qpdf_version_arguments,
|
||||
.version_argument_count = 1
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -114,6 +114,8 @@ struct MainWindow
|
|||
gpointer analyze_rib_user_data;
|
||||
MainWindowExtractMetadataCallback extract_metadata_callback;
|
||||
gpointer extract_metadata_user_data;
|
||||
MainWindowRecoverPdfPasswordCallback recover_pdf_password_callback;
|
||||
gpointer recover_pdf_password_user_data;
|
||||
|
||||
MainWindowGraphNodeMovedCallback
|
||||
graph_node_moved_callback;
|
||||
|
|
@ -223,6 +225,15 @@ static void main_window_on_extract_metadata_requested(const char *identifier,
|
|||
window->extract_metadata_callback(identifier,
|
||||
window->extract_metadata_user_data);
|
||||
}
|
||||
/** @brief Relaie la demande de récupération du mot de passe PDF. */
|
||||
static void main_window_on_recover_pdf_password_requested(
|
||||
const char *identifier, gpointer data)
|
||||
{
|
||||
MainWindow *window = data;
|
||||
if (window != NULL && window->recover_pdf_password_callback != NULL)
|
||||
window->recover_pdf_password_callback(identifier,
|
||||
window->recover_pdf_password_user_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ouvre dans le workspace l'entité choisie dans la sidebar.
|
||||
|
|
@ -988,6 +999,8 @@ MainWindow *main_window_new(
|
|||
main_window_on_analyze_rib_requested, main_window);
|
||||
workspace_set_extract_metadata_callback(main_window->workspace,
|
||||
main_window_on_extract_metadata_requested, main_window);
|
||||
workspace_set_recover_pdf_password_callback(main_window->workspace,
|
||||
main_window_on_recover_pdf_password_requested, main_window);
|
||||
|
||||
workspace_widget = workspace_get_widget(
|
||||
main_window->workspace
|
||||
|
|
@ -1550,6 +1563,13 @@ void main_window_set_extract_metadata_callback(MainWindow *main_window,
|
|||
main_window->extract_metadata_callback = callback;
|
||||
main_window->extract_metadata_user_data = user_data;
|
||||
}
|
||||
void main_window_set_recover_pdf_password_callback(MainWindow *main_window,
|
||||
MainWindowRecoverPdfPasswordCallback callback, gpointer user_data)
|
||||
{
|
||||
if (main_window == NULL) return;
|
||||
main_window->recover_pdf_password_callback = callback;
|
||||
main_window->recover_pdf_password_user_data = user_data;
|
||||
}
|
||||
|
||||
void main_window_set_tree_selection_callback(
|
||||
MainWindow *main_window,
|
||||
|
|
|
|||
138
src/views/pdf_password_dialog.c
Normal file
138
src/views/pdf_password_dialog.c
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
/******************************************************************************
|
||||
* @file pdf_password_dialog.c
|
||||
* @brief Paramétrage d'une récupération de mot de passe PDF.
|
||||
******************************************************************************/
|
||||
|
||||
#include "views/pdf_password_dialog.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GtkWidget *window;
|
||||
GtkWidget *method;
|
||||
GtkWidget *parameter;
|
||||
GtkWidget *status;
|
||||
PdfPasswordDialogCallback callback;
|
||||
gpointer user_data;
|
||||
} PdfPasswordDialog;
|
||||
|
||||
/** @brief Libère le contexte à la destruction de la fenêtre. */
|
||||
static void pdf_password_dialog_destroyed(GtkWidget *widget, gpointer data)
|
||||
{
|
||||
(void) widget;
|
||||
g_free(data);
|
||||
}
|
||||
|
||||
/** @brief Actualise l'aide selon la méthode choisie. */
|
||||
static void pdf_password_dialog_method_changed(GObject *object,
|
||||
GParamSpec *specification, gpointer data)
|
||||
{
|
||||
PdfPasswordDialog *dialog = data;
|
||||
guint selected = gtk_drop_down_get_selected(GTK_DROP_DOWN(object));
|
||||
(void) specification;
|
||||
gtk_entry_set_placeholder_text(GTK_ENTRY(dialog->parameter), selected == 0 ?
|
||||
"/chemin/vers/dictionnaire.txt" : selected == 1 ?
|
||||
"?d?d?d?d?d?d" : "minimum:maximum (ex. 0:999999)");
|
||||
}
|
||||
|
||||
/** @brief Valide le formulaire et transmet une copie éphémère du paramètre. */
|
||||
static void pdf_password_dialog_started(GtkButton *button, gpointer data)
|
||||
{
|
||||
PdfPasswordDialog *dialog = data;
|
||||
const char *parameter = gtk_editable_get_text(
|
||||
GTK_EDITABLE(dialog->parameter));
|
||||
guint selected = gtk_drop_down_get_selected(GTK_DROP_DOWN(dialog->method));
|
||||
(void) button;
|
||||
if (parameter == NULL || parameter[0] == '\0')
|
||||
{
|
||||
gtk_label_set_text(GTK_LABEL(dialog->status),
|
||||
selected == 0 ? "Choisissez un dictionnaire." :
|
||||
"Saisissez un masque John.");
|
||||
return;
|
||||
}
|
||||
if (selected == 0 && !g_file_test(parameter, G_FILE_TEST_IS_REGULAR))
|
||||
{
|
||||
gtk_label_set_text(GTK_LABEL(dialog->status),
|
||||
"Le dictionnaire indiqué est introuvable.");
|
||||
return;
|
||||
}
|
||||
if (selected == 2)
|
||||
{
|
||||
gchar **bounds = g_strsplit(parameter, ":", 2);
|
||||
guint64 minimum = bounds[0] != NULL ? g_ascii_strtoull(bounds[0], NULL, 10) : 0;
|
||||
guint64 maximum = bounds[1] != NULL ? g_ascii_strtoull(bounds[1], NULL, 10) : 0;
|
||||
gboolean valid = bounds[0] != NULL && bounds[1] != NULL &&
|
||||
bounds[0][0] != '\0' && bounds[1][0] != '\0' && minimum <= maximum &&
|
||||
maximum <= 9999999999999ULL;
|
||||
g_strfreev(bounds);
|
||||
if (!valid)
|
||||
{
|
||||
gtk_label_set_text(GTK_LABEL(dialog->status),
|
||||
"Plage invalide : utilisez minimum:maximum, jusqu'à 9999999999999.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (dialog->callback != NULL)
|
||||
dialog->callback(selected == 0 ? PDF_PASSWORD_RECOVERY_DICTIONARY :
|
||||
selected == 1 ? PDF_PASSWORD_RECOVERY_MASK :
|
||||
PDF_PASSWORD_RECOVERY_NUMERIC_RANGE, parameter, dialog->user_data);
|
||||
gtk_window_destroy(GTK_WINDOW(dialog->window));
|
||||
}
|
||||
|
||||
void pdf_password_dialog_present(GtkWindow *parent,
|
||||
PdfPasswordDialogCallback callback, gpointer user_data)
|
||||
{
|
||||
PdfPasswordDialog *dialog = g_new0(PdfPasswordDialog, 1);
|
||||
GtkWidget *content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
||||
GtkWidget *description = gtk_label_new(
|
||||
"La recherche s'effectue localement sur une copie. Commencez par un "
|
||||
"dictionnaire ciblé ; un masque trop large peut durer très longtemps.");
|
||||
GtkStringList *methods = gtk_string_list_new((const char *[])
|
||||
{ "Dictionnaire", "Masque John", "Plage numérique", NULL });
|
||||
GtkWidget *start = NULL;
|
||||
GtkWidget *cancel = NULL;
|
||||
GtkWidget *buttons = NULL;
|
||||
|
||||
dialog->window = gtk_window_new();
|
||||
dialog->callback = callback;
|
||||
dialog->user_data = user_data;
|
||||
gtk_window_set_title(GTK_WINDOW(dialog->window),
|
||||
"Récupérer le mot de passe PDF");
|
||||
gtk_window_set_default_size(GTK_WINDOW(dialog->window), 560, 300);
|
||||
gtk_window_set_modal(GTK_WINDOW(dialog->window), TRUE);
|
||||
if (parent != NULL)
|
||||
gtk_window_set_transient_for(GTK_WINDOW(dialog->window), parent);
|
||||
gtk_widget_set_margin_top(content, 18);
|
||||
gtk_widget_set_margin_bottom(content, 18);
|
||||
gtk_widget_set_margin_start(content, 18);
|
||||
gtk_widget_set_margin_end(content, 18);
|
||||
gtk_label_set_wrap(GTK_LABEL(description), TRUE);
|
||||
gtk_widget_set_halign(description, GTK_ALIGN_START);
|
||||
gtk_box_append(GTK_BOX(content), description);
|
||||
dialog->method = gtk_drop_down_new(G_LIST_MODEL(methods), NULL);
|
||||
gtk_box_append(GTK_BOX(content), dialog->method);
|
||||
dialog->parameter = gtk_entry_new();
|
||||
gtk_entry_set_placeholder_text(GTK_ENTRY(dialog->parameter),
|
||||
"/chemin/vers/dictionnaire.txt");
|
||||
gtk_box_append(GTK_BOX(content), dialog->parameter);
|
||||
dialog->status = gtk_label_new("");
|
||||
gtk_widget_set_halign(dialog->status, GTK_ALIGN_START);
|
||||
gtk_box_append(GTK_BOX(content), dialog->status);
|
||||
buttons = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
gtk_widget_set_halign(buttons, GTK_ALIGN_END);
|
||||
cancel = gtk_button_new_with_label("Annuler");
|
||||
start = gtk_button_new_with_label("Lancer la récupération");
|
||||
gtk_box_append(GTK_BOX(buttons), cancel);
|
||||
gtk_box_append(GTK_BOX(buttons), start);
|
||||
gtk_box_append(GTK_BOX(content), buttons);
|
||||
gtk_window_set_child(GTK_WINDOW(dialog->window), content);
|
||||
g_signal_connect(dialog->method, "notify::selected",
|
||||
G_CALLBACK(pdf_password_dialog_method_changed), dialog);
|
||||
g_signal_connect(start, "clicked",
|
||||
G_CALLBACK(pdf_password_dialog_started), dialog);
|
||||
g_signal_connect_swapped(cancel, "clicked",
|
||||
G_CALLBACK(gtk_window_destroy), dialog->window);
|
||||
g_signal_connect(dialog->window, "destroy",
|
||||
G_CALLBACK(pdf_password_dialog_destroyed), dialog);
|
||||
gtk_window_present(GTK_WINDOW(dialog->window));
|
||||
/* GtkDropDown conserve le modèle pendant toute la durée de la fenêtre. */
|
||||
}
|
||||
|
|
@ -94,6 +94,7 @@ struct Workspace
|
|||
GtkWidget *analyze_eml_button;
|
||||
GtkWidget *analyze_rib_button;
|
||||
GtkWidget *extract_metadata_button;
|
||||
GtkWidget *recover_pdf_password_button;
|
||||
GtkWidget *evidence_preview_stack;
|
||||
GtkWidget *evidence_preview_status;
|
||||
GtkWidget *evidence_preview_picture;
|
||||
|
|
@ -118,6 +119,8 @@ struct Workspace
|
|||
gpointer analyze_rib_user_data;
|
||||
WorkspaceExtractMetadataCallback extract_metadata_callback;
|
||||
gpointer extract_metadata_user_data;
|
||||
WorkspaceRecoverPdfPasswordCallback recover_pdf_password_callback;
|
||||
gpointer recover_pdf_password_user_data;
|
||||
|
||||
WorkspaceGraphNodeMovedCallback
|
||||
graph_node_moved_callback;
|
||||
|
|
@ -1108,6 +1111,19 @@ static void workspace_on_extract_metadata_clicked(GtkButton *button,
|
|||
workspace->extract_metadata_user_data);
|
||||
}
|
||||
|
||||
/** @brief Transmet la demande de récupération du mot de passe PDF. */
|
||||
static void workspace_on_recover_pdf_password_clicked(GtkButton *button,
|
||||
gpointer data)
|
||||
{
|
||||
Workspace *workspace = data;
|
||||
(void) button;
|
||||
if (workspace != NULL && workspace->recover_pdf_password_callback != NULL &&
|
||||
workspace->selected_evidence_identifier != NULL)
|
||||
workspace->recover_pdf_password_callback(
|
||||
workspace->selected_evidence_identifier,
|
||||
workspace->recover_pdf_password_user_data);
|
||||
}
|
||||
|
||||
Workspace *workspace_new(void)
|
||||
{
|
||||
GtkWidget *evidence_content = NULL;
|
||||
|
|
@ -1494,6 +1510,15 @@ Workspace *workspace_new(void)
|
|||
G_CALLBACK(workspace_on_extract_metadata_clicked), workspace);
|
||||
gtk_box_append(GTK_BOX(evidence_content),
|
||||
workspace->extract_metadata_button);
|
||||
workspace->recover_pdf_password_button = gtk_button_new_with_label(
|
||||
"Récupérer le mot de passe PDF");
|
||||
gtk_widget_set_halign(workspace->recover_pdf_password_button,
|
||||
GTK_ALIGN_START);
|
||||
gtk_widget_set_sensitive(workspace->recover_pdf_password_button, FALSE);
|
||||
g_signal_connect(workspace->recover_pdf_password_button, "clicked",
|
||||
G_CALLBACK(workspace_on_recover_pdf_password_clicked), workspace);
|
||||
gtk_box_append(GTK_BOX(evidence_content),
|
||||
workspace->recover_pdf_password_button);
|
||||
|
||||
evidence_separator =
|
||||
gtk_separator_new(
|
||||
|
|
@ -2482,6 +2507,8 @@ void workspace_set_selected_node(
|
|||
gtk_widget_set_sensitive(workspace->analyze_rib_button, FALSE);
|
||||
if (workspace->extract_metadata_button != NULL)
|
||||
gtk_widget_set_sensitive(workspace->extract_metadata_button, FALSE);
|
||||
if (workspace->recover_pdf_password_button != NULL)
|
||||
gtk_widget_set_sensitive(workspace->recover_pdf_password_button, FALSE);
|
||||
|
||||
if (node == NULL)
|
||||
{
|
||||
|
|
@ -2657,6 +2684,8 @@ void workspace_set_selected_evidence(
|
|||
g_str_has_suffix(lower, ".mov") ||
|
||||
g_str_has_suffix(lower, ".mp4") ||
|
||||
g_str_has_suffix(lower, ".pdf")));
|
||||
gtk_widget_set_sensitive(workspace->recover_pdf_password_button,
|
||||
lower != NULL && g_str_has_suffix(lower, ".pdf"));
|
||||
g_free(lower);
|
||||
}
|
||||
|
||||
|
|
@ -3299,6 +3328,13 @@ void workspace_set_extract_metadata_callback(Workspace *workspace,
|
|||
workspace->extract_metadata_callback = callback;
|
||||
workspace->extract_metadata_user_data = user_data;
|
||||
}
|
||||
void workspace_set_recover_pdf_password_callback(Workspace *workspace,
|
||||
WorkspaceRecoverPdfPasswordCallback callback, gpointer user_data)
|
||||
{
|
||||
if (workspace == NULL) return;
|
||||
workspace->recover_pdf_password_callback = callback;
|
||||
workspace->recover_pdf_password_user_data = user_data;
|
||||
}
|
||||
|
||||
void workspace_set_graph_node_moved_callback(
|
||||
Workspace *workspace,
|
||||
|
|
|
|||
30
tests/test_pdf_password_recovery.c
Normal file
30
tests/test_pdf_password_recovery.c
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/******************************************************************************
|
||||
* @file test_pdf_password_recovery.c
|
||||
* @brief Tests de validation de la récupération PDF.
|
||||
******************************************************************************/
|
||||
|
||||
#include "core/pdf_password_recovery.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
/** @brief Vérifie le rejet d'une configuration incomplète. */
|
||||
static void test_pdf_password_recovery_invalid_arguments(void)
|
||||
{
|
||||
GError *error = NULL;
|
||||
g_assert_null(pdf_password_recovery_start(NULL, "preuve.pdf", "/tmp",
|
||||
"00000000-0000-4000-8000-000000000000",
|
||||
PDF_PASSWORD_RECOVERY_DICTIONARY, "mots.txt", NULL, NULL, NULL,
|
||||
&error));
|
||||
g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
|
||||
g_clear_error(&error);
|
||||
g_assert_false(pdf_password_recovery_result_is_recovered(NULL));
|
||||
g_assert_null(pdf_password_recovery_result_get_password(NULL));
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
g_test_add_func("/pdf-password-recovery/invalid-arguments",
|
||||
test_pdf_password_recovery_invalid_arguments);
|
||||
return g_test_run();
|
||||
}
|
||||
|
|
@ -68,6 +68,24 @@ static const ExpectedToolCatalogEntry expected_catalog_entries[] =
|
|||
.display_name = "ExifTool",
|
||||
.executable_name = "exiftool",
|
||||
.version_argument = "-ver"
|
||||
},
|
||||
{
|
||||
.identifier = "password.john",
|
||||
.display_name = "John the Ripper",
|
||||
.executable_name = "john",
|
||||
.version_argument = "--list=build-info"
|
||||
},
|
||||
{
|
||||
.identifier = "password.pdf2john",
|
||||
.display_name = "pdf2john",
|
||||
.executable_name = "pdf2john",
|
||||
.version_argument = "--help"
|
||||
},
|
||||
{
|
||||
.identifier = "pdf.qpdf",
|
||||
.display_name = "qpdf",
|
||||
.executable_name = "qpdf",
|
||||
.version_argument = "--version"
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue