/* Chrysalide - Outil d'analyse de fichiers binaires * secstorage.c - conservation sécurisée d'éléments de configuration * * Copyright (C) 2025 Cyrille Bagard * * This file is part of Chrysalide. * * Chrysalide is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Chrysalide is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Chrysalide. If not, see . */ #include "secstorage.h" #include #include #include #include #include "../core/logs.h" #include "../glibext/helpers.h" /** * Les mécanismes de hachage de mot de passe doivent être utilisés avec un sel, * et la longueur du sel doit être d’au moins 128 bits. * * Cette note concerne le hachage de mots de passe et non la dérivation de secrets * cependant. * * Source : https://cyber.gouv.fr/sites/default/files/2021/03/anssi-guide-selection_crypto-1.0.pdf */ #define SECRET_STORAGE_SALT_SIZE (256 / 8) /** * Nombre d'itérations pour PBKDF2 : en 2023, OWASP recommande 600000 itérations * pour PBKDF2-HMAC-SHA256 (et 210000 pour PBKDF2-HMAC-SHA512). * * Source : https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 */ #define PBKDF2_HMAC_SHA256_ITERATIONS (2 << 20) /** * AES 256 : clef de 256 bits, IV de 128 bits */ #define SECRET_STORAGE_KEK_SIZE (256 / 8) #define SECRET_STORAGE_KEY_SIZE (256 / 8) #define SECRET_STORAGE_BLOCK_SIZE (128 / 8) #define SECRET_STORAGE_IV_SIZE SECRET_STORAGE_BLOCK_SIZE /* Conservation des clefs de déchiffrement maîtres par configuration. */ static GHashTable *__unlocked_keys = NULL; /* Fournit l'espace de configuration réel à manipuler. */ static GSettings *get_secret_storage_settings(GSettings *); /****************************************************************************** * * * Paramètres : - * * * * Description : Initialise le stockage des clefs de déchiffrement en place. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void init_secret_storage(void) { __unlocked_keys = g_hash_table_new_full(g_direct_hash, g_direct_equal, g_object_unref, free); } /****************************************************************************** * * * Paramètres : - * * * * Description : Supprime le stockage des clefs de déchiffrement en place. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void exit_secret_storage(void) { assert(__unlocked_keys != NULL); g_hash_table_remove_all(__unlocked_keys); g_hash_table_unref(__unlocked_keys); __unlocked_keys = NULL; } /****************************************************************************** * * * Paramètres : settings = éventuel espace de configuration à manipuler. * * * * Description : Fournit l'espace de configuration réel à manipuler. * * * * Retour : Instance de travail à employer avant libération. * * * * Remarques : - * * * ******************************************************************************/ static GSettings *get_secret_storage_settings(GSettings *settings) { GSettings *result; /* Instance à retourner */ if (settings != NULL) { ref_object(settings); result = settings; } else result = NULL; // TODO return result; } /****************************************************************************** * * * Paramètres : settings = éventuel espace de configuration à manipuler. * * * * Description : Détermine si une clef de chiffrement protégée est en place. * * * * Retour : Bilan de l'analyse. * * * * Remarques : - * * * ******************************************************************************/ bool has_secret_storage_key(GSettings *settings) { bool result; /* Bilan à retourner */ GVariant *value; /* Valeur de configuration */ gsize length; /* Taille d'une valeur donnée */ result = false; settings = get_secret_storage_settings(settings); assert(settings != NULL); value = g_settings_get_value(settings, "master"); g_variant_get_fixed_array(value, &length, 1); result = (length > SECRET_STORAGE_IV_SIZE); g_variant_unref(value); unref_object(settings); return result;; } /****************************************************************************** * * * Paramètres : settings = éventuel espace de configuration à manipuler. * * password = mot de passe principal à appliquer. * * * * Description : Définit un mot de passe pour protéger une clef maître. * * * * Retour : Bilan de la mise en place. * * * * Remarques : - * * * ******************************************************************************/ bool set_secret_storage_password(GSettings *settings, const char *passwd) { bool result; /* Bilan à retourner */ unsigned char salt[SECRET_STORAGE_SALT_SIZE]; /* Sel pour la dérivation*/ int ret; /* Bilan à d'un appel */ GVariant *value; /* Valeur de configuration */ unsigned char kek[SECRET_STORAGE_KEK_SIZE]; /* Clef de protection */ unsigned char key[SECRET_STORAGE_KEY_SIZE]; /* Clef maître */ unsigned char iv[SECRET_STORAGE_IV_SIZE]; /* IV associé */ EVP_CIPHER_CTX *ctx; /* Contexte pour le chiffrement*/ unsigned char encrypted[64]; /* Partie chiffrée à conserver */ unsigned char *iter; /* Tête d'écriture */ int outlen; /* Taille des données utiles */ result = false; settings = get_secret_storage_settings(settings); assert(settings != NULL); if (has_secret_storage_key(settings)) goto exit; /* Création d'un sel pour la dérivation du mot de passe */ ret = RAND_bytes(salt, sizeof(salt)); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit; } /** * La fonction g_variant_new_fixed_array() retourne un variant * avec un décompte de référence flottant. */ value = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, salt, SECRET_STORAGE_SALT_SIZE, sizeof(unsigned char)); /** * Comme le variant à une référence flottante, la fonction * g_settings_set_value() consomme cette référence. * * Il n'y a donc pas lieu d'appeler g_variant_unref(). */ g_settings_set_value(settings, "salt", value); /* Dérivation du mot de passe */ ret = PKCS5_PBKDF2_HMAC(passwd, strlen(passwd), salt, SECRET_STORAGE_SALT_SIZE, PBKDF2_HMAC_SHA256_ITERATIONS, EVP_sha256(), SECRET_STORAGE_KEK_SIZE, kek); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit; } /* Définition de la clef maître et de son IV de chiffrement */ ret = RAND_bytes(key, sizeof(key)); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit; } ret = RAND_bytes(iv, sizeof(iv)); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit; } /* Chiffrement de la clef maître */ ctx = EVP_CIPHER_CTX_new(); if (ctx == NULL) { LOG_ERROR_OPENSSL; goto exit; } EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); ret = EVP_EncryptInit_ex2(ctx, EVP_aes_256_wrap_pad(), kek, iv, NULL); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit_with_ctx; } memcpy(encrypted, iv, SECRET_STORAGE_IV_SIZE); iter = encrypted + SECRET_STORAGE_IV_SIZE; ret = EVP_EncryptUpdate(ctx, iter, &outlen, key, SECRET_STORAGE_KEY_SIZE); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit_with_ctx; } iter += outlen; ret = EVP_EncryptFinal_ex(ctx, iter, &outlen); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit_with_ctx; } iter += outlen; assert((iter - encrypted) < 64); /* Conservation de la clef protégée */ value = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, encrypted, iter - encrypted, sizeof(unsigned char)); g_settings_set_value(settings, "master", value); result = true; exit_with_ctx: EVP_CIPHER_CTX_free(ctx); exit: unref_object(settings); return result; } /****************************************************************************** * * * Paramètres : settings = éventuel espace de configuration à manipuler. * * * * Description : Détermine si la clef de chiffrement maître est vérouillée. * * * * Retour : Bilan de la détermination. * * * * Remarques : - * * * ******************************************************************************/ bool is_secret_storage_locked(GSettings *settings) { bool result; /* Bilan à retourner */ settings = get_secret_storage_settings(settings); assert(settings != NULL); result = (g_hash_table_lookup(__unlocked_keys, settings) == NULL); unref_object(settings); return result; } /****************************************************************************** * * * Paramètres : settings = éventuel espace de configuration à manipuler. * * password = mot de passe principal à utiliser. * * * * Description : Déverrouille la clef de chiffrement maître. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool unlock_secret_storage(GSettings *settings, const char *passwd) { bool result; /* Bilan à retourner */ GVariant *salt_value; /* Valeur du sel configuré */ gsize salt_length; /* Taille du sel conservé */ gconstpointer salt; /* Données associées #1 */ unsigned char kek[SECRET_STORAGE_KEK_SIZE]; /* Clef de protection */ int ret; /* Bilan à d'un appel */ GVariant *enc_value; /* Paramètres de chiffrement */ gsize enc_length; /* Taille de ces paramètrs */ gconstpointer encrypted; /* Données associées #2 */ EVP_CIPHER_CTX *ctx; /* Contexte de déchiffrement */ unsigned char iv[SECRET_STORAGE_IV_SIZE]; /* IV associé */ unsigned char key[SECRET_STORAGE_KEY_SIZE]; /* Clef maître */ unsigned char *iter; /* Tête d'écriture */ int outlen; /* Taille des données utiles */ void *unlocked; /* Zone de conservation */ #ifndef NDEBUG gboolean new; /* Validation de la création */ #endif result = false; settings = get_secret_storage_settings(settings); assert(settings != NULL); if (!is_secret_storage_locked(settings)) { result = true; goto quick_exit; } /* Récupération du sel mis en place */ salt_value = g_settings_get_value(settings, "salt"); salt = g_variant_get_fixed_array(salt_value, &salt_length, sizeof(bin_t)); if (salt_length != SECRET_STORAGE_SALT_SIZE) goto exit_with_salt; /* Dérivation du mot de passe */ ret = PKCS5_PBKDF2_HMAC(passwd, strlen(passwd), salt, SECRET_STORAGE_SALT_SIZE, PBKDF2_HMAC_SHA256_ITERATIONS, EVP_sha256(), SECRET_STORAGE_KEK_SIZE, kek); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit_with_salt; } /* Récupération des paramètres chiffrés */ enc_value = g_settings_get_value(settings, "master"); encrypted = g_variant_get_fixed_array(enc_value, &enc_length, sizeof(bin_t)); if (enc_length <= SECRET_STORAGE_IV_SIZE) goto exit_with_enc; /* Déhiffrement de la clef maître */ ctx = EVP_CIPHER_CTX_new(); if (ctx == NULL) { LOG_ERROR_OPENSSL; goto exit_with_enc; } EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); memcpy(iv, encrypted, SECRET_STORAGE_IV_SIZE); ret = EVP_DecryptInit_ex2(ctx, EVP_aes_256_wrap_pad(), kek, iv, NULL); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit_with_ctx; } iter = key; ret = EVP_DecryptUpdate(ctx, iter, &outlen, ((unsigned char *)encrypted) + SECRET_STORAGE_IV_SIZE, enc_length - SECRET_STORAGE_IV_SIZE); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit_with_ctx; } iter += outlen; ret = EVP_DecryptFinal_ex(ctx, iter, &outlen); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit_with_ctx; } assert((iter - key) == SECRET_STORAGE_KEY_SIZE); /* Stockage de la clef maître en mémoire */ ref_object(settings); unlocked = malloc(SECRET_STORAGE_KEY_SIZE); memcpy(unlocked, key, SECRET_STORAGE_KEY_SIZE); #ifndef NDEBUG new = g_hash_table_replace(__unlocked_keys, settings, unlocked); assert(new); #else g_hash_table_replace(__unlocked_keys, settings, unlocked); #endif result = true; /* Sortie */ exit_with_ctx: EVP_CIPHER_CTX_free(ctx); exit_with_enc: g_variant_unref(enc_value); exit_with_salt: g_variant_unref(salt_value); quick_exit: unref_object(settings); return result; } /****************************************************************************** * * * Paramètres : settings = éventuel espace de configuration à manipuler. * * * * Description : Verrouille la clef de chiffrement maître. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void lock_secret_storage(GSettings *settings) { settings = get_secret_storage_settings(settings); assert(settings != NULL); g_hash_table_remove(__unlocked_keys, settings); unref_object(settings); } /****************************************************************************** * * * Paramètres : settings = éventuel espace de configuration à manipuler. * * in = séquence d'octets à traiter. * * out = séquence d'octets résultantes. [OUT] * * * * Description : Chiffre des données avec la clef de chiffrement maître. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool encrypt_secret_storage_data(GSettings *settings, const sized_binary_t *in, sized_binary_t *out) { bool result; /* Bilan à retourner */ gpointer key; /* Clef de chiffrement */ unsigned char iv[SECRET_STORAGE_IV_SIZE]; /* IV associé */ int ret; /* Bilan à d'un appel */ EVP_CIPHER_CTX *ctx; /* Contexte pour le chiffrement*/ size_t needed; /* Taille de la sortie */ unsigned char *iter; /* Tête d'écriture */ int outlen; /* Taille des données utiles */ result = false; settings = get_secret_storage_settings(settings); assert(settings != NULL); if (is_secret_storage_locked(settings)) goto quick_exit; /* Récupération de la clef maître et d'un IV de chiffrement */ key = g_hash_table_lookup(__unlocked_keys, settings); ret = RAND_bytes(iv, sizeof(iv)); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit; } /* Préparation de la zone de réception */ needed = SECRET_STORAGE_IV_SIZE + ((in->size / SECRET_STORAGE_BLOCK_SIZE) + 1) * SECRET_STORAGE_BLOCK_SIZE; setup_sized_binary(out, needed); /* Chiffrement des données */ ctx = EVP_CIPHER_CTX_new(); if (ctx == NULL) { LOG_ERROR_OPENSSL; goto exit; } ret = EVP_EncryptInit_ex2(ctx, EVP_aes_256_cbc(), key, iv, NULL); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit_with_ctx; } memcpy(out->data, iv, SECRET_STORAGE_IV_SIZE); iter = out->bin_data + SECRET_STORAGE_IV_SIZE; ret = EVP_EncryptUpdate(ctx, iter, &outlen, in->bin_data, in->size); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit_with_ctx; } iter += outlen; ret = EVP_EncryptFinal_ex(ctx, iter, &outlen); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit_with_ctx; } iter += outlen; assert((iter - out->bin_data) == out->size); result = true; /* Sortie */ exit_with_ctx: EVP_CIPHER_CTX_free(ctx); if (!result) exit_sized_binary(out); exit: quick_exit: unref_object(settings); return result; } /****************************************************************************** * * * Paramètres : settings = éventuel espace de configuration à manipuler. * * in = séquence d'octets à traiter. * * out = séquence d'octets résultantes. [OUT] * * * * Description : Déchiffre des données avec la clef de chiffrement maître. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool decrypt_secret_storage_data(GSettings *settings, const sized_binary_t *in, sized_binary_t *out) { bool result; /* Bilan à retourner */ gpointer key; /* Clef de chiffrement */ unsigned char iv[SECRET_STORAGE_IV_SIZE]; /* IV associé */ int ret; /* Bilan à d'un appel */ EVP_CIPHER_CTX *ctx; /* Contexte pour le chiffrement*/ size_t needed; /* Taille de la sortie */ unsigned char *iter; /* Tête d'écriture */ int outlen; /* Taille des données utiles */ result = false; settings = get_secret_storage_settings(settings); assert(settings != NULL); if (is_secret_storage_locked(settings)) goto quick_exit; /* Récupération de la clef maître et d'un IV de chiffrement */ key = g_hash_table_lookup(__unlocked_keys, settings); if (in->size < SECRET_STORAGE_IV_SIZE) goto exit; memcpy(iv, in->data, SECRET_STORAGE_IV_SIZE); /* Préparation de la zone de réception */ needed = in->size - SECRET_STORAGE_IV_SIZE; setup_sized_binary(out, needed); /* Chiffrement des données */ ctx = EVP_CIPHER_CTX_new(); if (ctx == NULL) { LOG_ERROR_OPENSSL; goto exit; } ret = EVP_DecryptInit_ex2(ctx, EVP_aes_256_cbc(), key, iv, NULL); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit_with_ctx; } iter = out->bin_data; ret = EVP_DecryptUpdate(ctx, iter, &outlen, in->bin_data + SECRET_STORAGE_IV_SIZE, in->size - SECRET_STORAGE_IV_SIZE); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit_with_ctx; } iter += outlen; ret = EVP_DecryptFinal_ex(ctx, iter, &outlen); if (ret != 1) { LOG_ERROR_OPENSSL; goto exit_with_ctx; } iter += outlen; assert((iter - out->bin_data) <= out->size); resize_sized_binary(out, iter - out->bin_data); result = true; /* Sortie */ exit_with_ctx: EVP_CIPHER_CTX_free(ctx); if (!result) exit_sized_binary(out); exit: quick_exit: unref_object(settings); return result; }