diff options
author | Cyrille Bagard <nocbos@gmail.com> | 2025-02-08 15:57:23 (GMT) |
---|---|---|
committer | Cyrille Bagard <nocbos@gmail.com> | 2025-02-08 15:57:23 (GMT) |
commit | 71d0b80eca2fd2aed5883e2a6a57cb8c03aa27ff (patch) | |
tree | 74c9654c9c6d02059ba9aff4536ce0ea25e7763c | |
parent | c928f8abb669d37e77bd9056240074941a945bb9 (diff) |
Introduce a secure storage.
-rw-r--r-- | plugins/pychrysalide/Makefile.am | 1 | ||||
-rw-r--r-- | plugins/pychrysalide/convert.c | 89 | ||||
-rw-r--r-- | plugins/pychrysalide/convert.h | 38 | ||||
-rw-r--r-- | plugins/pychrysalide/core/Makefile.am | 3 | ||||
-rw-r--r-- | plugins/pychrysalide/core/module.c | 2 | ||||
-rw-r--r-- | plugins/pychrysalide/core/secstorage.c | 465 | ||||
-rw-r--r-- | plugins/pychrysalide/core/secstorage.h | 39 | ||||
-rw-r--r-- | src/common/szbin.h | 10 | ||||
-rw-r--r-- | src/core/Makefile.am | 5 | ||||
-rw-r--r-- | src/core/core.c | 5 | ||||
-rw-r--r-- | src/core/secstorage.c | 779 | ||||
-rw-r--r-- | src/core/secstorage.h | 65 | ||||
-rw-r--r-- | tests/core/re.chrysalide.tests.secstorage.gschema.xml | 15 | ||||
-rw-r--r-- | tests/core/secstorage.py | 150 |
14 files changed, 1663 insertions, 3 deletions
diff --git a/plugins/pychrysalide/Makefile.am b/plugins/pychrysalide/Makefile.am index 7b1a331..d1bf457 100644 --- a/plugins/pychrysalide/Makefile.am +++ b/plugins/pychrysalide/Makefile.am @@ -34,6 +34,7 @@ pychrysalide_la_SOURCES = \ access.h access.c \ bindings.h bindings.c \ constants.h constants.c \ + convert.h convert.c \ core-int.h \ core.h core.c \ helpers.h helpers.c \ diff --git a/plugins/pychrysalide/convert.c b/plugins/pychrysalide/convert.c new file mode 100644 index 0000000..c67c8ba --- /dev/null +++ b/plugins/pychrysalide/convert.c @@ -0,0 +1,89 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * convert.c - conversion d'arguments en éléments usuels externes + * + * 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "convert.h" + + +#include <assert.h> +#include <pygobject.h> +#ifndef NDEBUG +# include <stdbool.h> +#endif +#include <gio/gio.h> + + + +/****************************************************************************** +* * +* Paramètres : arg = argument quelconque à tenter de convertir. * +* dst = destination des valeurs récupérées en cas de succès. * +* * +* Description : Tente de convertir en instance GSettings. * +* * +* Retour : Bilan de l'opération, voire indications supplémentaires. * +* * +* Remarques : - * +* * +******************************************************************************/ + +int convert_to_gsettings(PyObject *arg, void *dst) +{ + int result; /* Bilan à retourner */ + GType type; /* Type obtenu ou 0 */ + + result = PyObject_IsInstance(arg, (PyObject *)&PyGObject_Type); + + if (result == 1) + { + type = pyg_type_from_object(arg); + + if (type != G_TYPE_SETTINGS) + result = 0; + + } + + switch (result) + { + case -1: + /* L'exception est déjà fixée par Python */ + result = 0; + break; + + case 0: + PyErr_SetString(PyExc_TypeError, "unable to convert the provided argument to GSetting instance"); + break; + + case 1: + *((GSettings **)dst) = G_SETTINGS(pygobject_get(arg)); + break; + + default: + assert(false); + break; + + } + + return result; + +} diff --git a/plugins/pychrysalide/convert.h b/plugins/pychrysalide/convert.h new file mode 100644 index 0000000..7bdf7da --- /dev/null +++ b/plugins/pychrysalide/convert.h @@ -0,0 +1,38 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * convert.h - prototypes pour la conversion d'arguments en éléments usuels externes + * + * 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef _PLUGINS_PYCHRYSALIDE_CONVERT_H +#define _PLUGINS_PYCHRYSALIDE_CONVERT_H + + +#include <Python.h> + + + +/* Tente de convertir en instance GSettings. */ +int convert_to_gsettings(PyObject *, void *); + + + +#endif /* _PLUGINS_PYCHRYSALIDE_CONVERT_H */ diff --git a/plugins/pychrysalide/core/Makefile.am b/plugins/pychrysalide/core/Makefile.am index 5588c9f..6ba9fc8 100644 --- a/plugins/pychrysalide/core/Makefile.am +++ b/plugins/pychrysalide/core/Makefile.am @@ -15,7 +15,8 @@ libpychrysacore_la_SOURCES = \ constants.h constants.c \ logs.h logs.c \ module.h module.c \ - nox.h nox.c + nox.h nox.c \ + secstorage.h secstorage.c libpychrysacore_la_CFLAGS = $(TOOLKIT_CFLAGS) $(LIBXML_CFLAGS) $(LIBPYTHON_INTERPRETER_CFLAGS) $(LIBPYGOBJECT_CFLAGS) \ -I$(top_srcdir)/src -DNO_IMPORT_PYGOBJECT diff --git a/plugins/pychrysalide/core/module.c b/plugins/pychrysalide/core/module.c index 4af0403..7eceddd 100644 --- a/plugins/pychrysalide/core/module.c +++ b/plugins/pychrysalide/core/module.c @@ -35,6 +35,7 @@ //#include "params.h" //#include "processors.h" //#include "queue.h" +#include "secstorage.h" #include "../helpers.h" @@ -110,6 +111,7 @@ bool populate_core_module(void) //if (result) result = populate_core_module_with_params(); //if (result) result = populate_core_module_with_processors(); //if (result) result = populate_core_module_with_queue(); + if (result) result = populate_core_module_with_secstorage(); assert(result); diff --git a/plugins/pychrysalide/core/secstorage.c b/plugins/pychrysalide/core/secstorage.c new file mode 100644 index 0000000..67779af --- /dev/null +++ b/plugins/pychrysalide/core/secstorage.c @@ -0,0 +1,465 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * secstorage.c - équivalent Python du fichier "core/secstorage.c" + * + * 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "secstorage.h" + + +#include <core/secstorage.h> + + +#include "../access.h" +#include "../convert.h" +#include "../helpers.h" + + + +/* Détermine si une clef de chiffrement protégée est en place. */ +static PyObject *py_secstorage_has_secret_storage_key(PyObject *, PyObject *); + +/* Définit un mot de passe pour protéger une clef maître. */ +static PyObject *py_secstorage_set_secret_storage_password(PyObject *, PyObject *); + +/* Détermine si la clef de chiffrement maître est vérouillée. */ +static PyObject *py_secstorage_is_secret_storage_locked(PyObject *, PyObject *); + +/* Déverrouille la clef de chiffrement maître. */ +static PyObject *py_secstorage_unlock_secret_storage(PyObject *, PyObject *); + +/* Verrouille la clef de chiffrement maître. */ +static PyObject *py_secstorage_lock_secret_storage(PyObject *, PyObject *); + +/* Chiffre des données avec la clef de chiffrement maître. */ +static PyObject *py_secstorage_encrypt_secret_storage_data(PyObject *, PyObject *); + +/* Déchiffre des données avec la clef de chiffrement maître. */ +static PyObject *py_secstorage_decrypt_secret_storage_data(PyObject *, PyObject *); + + + +/****************************************************************************** +* * +* Paramètres : self = objet Python concerné par l'appel. * +* args = arguments fournis à l'appel. * +* * +* Description : Détermine si un mot de passe est actuellement en place. * +* * +* Retour : Bilan de l'analyse. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_secstorage_has_secret_storage_key(PyObject *self, PyObject *args) +{ + PyObject *result; /* Conversion à retourner */ + GSettings *settings; /* Configuration à considérer */ + int ret; /* Bilan de lecture des args. */ + bool status; /* Bilan de situation */ + +#define SECSTORAGE_HAS_SECRET_STORAGE_KEY_METHOD PYTHON_METHOD_DEF \ +( \ + has_secret_storage_key, "/, settings=None", \ + METH_VARARGS, py_secstorage, \ + "Indicate if a master key used for protecting secrets seems to have"\ + " been defined.\n" \ + "\n" \ + "The *settings* arguement must point to a GSettings intance; the" \ + " main configuration settings are used by default.\n" \ + "\n" \ + "The result is a boolean status: *True* if the master key seems" \ + " to exist, *False* otherwise." \ +) + + ret = PyArg_ParseTuple(args, "O&", convert_to_gsettings, &settings); + if (!ret) return NULL; + + status = has_secret_storage_key(settings); + + result = status ? Py_True : Py_False; + Py_INCREF(result); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : self = objet Python concerné par l'appel. * +* args = arguments fournis à l'appel. * +* * +* Description : Définit un mot de passe pour protéger une clef maître. * +* * +* Retour : Bilan de la mise en place. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_secstorage_set_secret_storage_password(PyObject *self, PyObject *args) +{ + PyObject *result; /* Conversion à retourner */ + GSettings *settings; /* Configuration à considérer */ + const char *passwd; /* Mot de passe associé */ + int ret; /* Bilan de lecture des args. */ + bool status; /* Bilan de situation */ + +#define SECSTORAGE_SET_SECRET_STORAGE_PASSWORD_METHOD PYTHON_METHOD_DEF \ +( \ + set_secret_storage_password, "/, settings=None, password=''", \ + METH_VARARGS, py_secstorage, \ + "Create a master key used for protecting secrets. This key is" \ + " itself protected by the provided password.\n" \ + "\n" \ + "The *settings* arguement must point to a GSettings intance; the" \ + " main configuration settings are used by default. The supplied" \ + " *password* has to be a string.\n" \ + "\n" \ + "The result is a boolean status: *True* if the operation successed,"\ + " *False* otherwise." \ +) + + settings = NULL; + passwd = ""; + + ret = PyArg_ParseTuple(args, "|O&s", convert_to_gsettings, &settings, &passwd); + if (!ret) return NULL; + + status = set_secret_storage_password(settings, passwd); + + result = status ? Py_True : Py_False; + Py_INCREF(result); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : self = objet Python concerné par l'appel. * +* args = arguments fournis à l'appel. * +* * +* Description : Détermine si la clef de chiffrement maître est vérouillée. * +* * +* Retour : Bilan de la détermination. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_secstorage_is_secret_storage_locked(PyObject *self, PyObject *args) +{ + PyObject *result; /* Conversion à retourner */ + GSettings *settings; /* Configuration à considérer */ + int ret; /* Bilan de lecture des args. */ + bool status; /* Bilan de situation */ + +#define SECSTORAGE_IS_SECRET_STORAGE_LOCKED_METHOD PYTHON_METHOD_DEF \ +( \ + is_secret_storage_locked, "/, settings=None", \ + METH_VARARGS, py_secstorage, \ + "Indicate if the master key used for protecting secrets is" \ + " currently decrypted in memory.\n" \ + "\n" \ + "The *settings* arguement must point to a GSettings intance; the" \ + " main configuration settings are used by default.\n" \ + "\n" \ + "The result is a boolean status: *True* if the master key is" \ + " unlocked and ready for use, *False* otherwise." \ +) + + settings = NULL; + + ret = PyArg_ParseTuple(args, "|O&", convert_to_gsettings, &settings); + if (!ret) return NULL; + + status = is_secret_storage_locked(settings); + + result = status ? Py_True : Py_False; + Py_INCREF(result); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : self = objet Python concerné par l'appel. * +* args = arguments fournis à l'appel. * +* * +* Description : Déverrouille la clef de chiffrement maître. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_secstorage_unlock_secret_storage(PyObject *self, PyObject *args) +{ + PyObject *result; /* Conversion à retourner */ + GSettings *settings; /* Configuration à considérer */ + const char *passwd; /* Mot de passe associé */ + int ret; /* Bilan de lecture des args. */ + bool status; /* Bilan de situation */ + +#define SECSTORAGE_UNLOCK_SECRET_STORAGE_METHOD PYTHON_METHOD_DEF \ +( \ + unlock_secret_storage, "/, settings=None, password=''", \ + METH_VARARGS, py_secstorage, \ + "Decrypt in memory the master key used for protecting secrets.\n" \ + "\n" \ + "The *settings* arguement must point to a GSettings intance; the" \ + " main configuration settings are used by default. The supplied" \ + " *password* is the primary password used to protect this key.\n" \ + "\n" \ + "The result is a boolean status: *True* if the operation successed" \ + " or if the master key is already unlocked, *False* otherwise." \ +) + + settings = NULL; + passwd = ""; + + ret = PyArg_ParseTuple(args, "|O&s", convert_to_gsettings, &settings, &passwd); + if (!ret) return NULL; + + status = unlock_secret_storage(settings, passwd); + + result = status ? Py_True : Py_False; + Py_INCREF(result); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : self = objet Python concerné par l'appel. * +* args = arguments fournis à l'appel. * +* * +* Description : Verrouille la clef de chiffrement maître. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_secstorage_lock_secret_storage(PyObject *self, PyObject *args) +{ + PyObject *result; /* Conversion à retourner */ + GSettings *settings; /* Configuration à considérer */ + int ret; /* Bilan de lecture des args. */ + +#define SECSTORAGE_LOCK_SECRET_STORAGE_METHOD PYTHON_METHOD_DEF \ +( \ + lock_secret_storage, "/, settings=None", \ + METH_VARARGS, py_secstorage, \ + "Clear from memory the master key used for protecting secrets.\n" \ + "\n" \ + "The *settings* arguement must point to a GSettings intance; the" \ + " main configuration settings are used by default." \ +) + + settings = NULL; + + ret = PyArg_ParseTuple(args, "|O&", convert_to_gsettings, &settings); + if (!ret) return NULL; + + lock_secret_storage(settings); + + result = Py_None; + Py_INCREF(result); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : self = objet Python concerné par l'appel. * +* args = arguments fournis à l'appel. * +* * +* Description : Chiffre des données avec la clef de chiffrement maître. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_secstorage_encrypt_secret_storage_data(PyObject *self, PyObject *args) +{ + PyObject *result; /* Conversion à retourner */ + const char *data_in; /* Données d'entrée à chiffrer */ + Py_ssize_t size_in; /* Quantité de ces données */ + GSettings *settings; /* Configuration à considérer */ + int ret; /* Bilan de lecture des args. */ + sized_binary_t in; /* Données à chiffer */ + bool status; /* Bilan de situation */ + sized_binary_t out; /* Données chiffrées */ + +#define SECSTORAGE_ENCRYPT_SECRET_STORAGE_DATA_METHOD PYTHON_METHOD_DEF \ +( \ + encrypt_secret_storage_data, "data, /, settings=None", \ + METH_VARARGS, py_secstorage, \ + "Encrypt data using an unlocked the master key.\n" \ + "\n" \ + "The *settings* arguement must point to a GSettings intance; the" \ + " main configuration settings are used by default." \ + "\n" \ + "The result is either encrypted data as bytes in case of success," \ + " or *None* in case of failure." \ +) + + settings = NULL; + + ret = PyArg_ParseTuple(args, "s#|O&", &data_in, &size_in, convert_to_gsettings, &settings); + if (!ret) return NULL; + + in.static_data = data_in; + in.size = size_in; + + status = encrypt_secret_storage_data(settings, &in, &out); + + if (status) + { + result = PyBytes_FromStringAndSize(out.static_data, out.size); + exit_sized_binary(&out); + } + + else + { + result = Py_None; + Py_INCREF(result); + } + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : self = objet Python concerné par l'appel. * +* args = arguments fournis à l'appel. * +* * +* Description : Déchiffre des données avec la clef de chiffrement maître. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_secstorage_decrypt_secret_storage_data(PyObject *self, PyObject *args) +{ + PyObject *result; /* Conversion à retourner */ + const char *data_in; /* Données d'entrée à chiffrer */ + Py_ssize_t size_in; /* Quantité de ces données */ + GSettings *settings; /* Configuration à considérer */ + int ret; /* Bilan de lecture des args. */ + sized_binary_t in; /* Données à chiffer */ + bool status; /* Bilan de situation */ + sized_binary_t out; /* Données chiffrées */ + +#define SECSTORAGE_DECRYPT_SECRET_STORAGE_DATA_METHOD PYTHON_METHOD_DEF \ +( \ + decrypt_secret_storage_data, "data, /, settings=None", \ + METH_VARARGS, py_secstorage, \ + "Decrypt data using an unlocked the master key.\n" \ + "\n" \ + "The *settings* arguement must point to a GSettings intance; the" \ + " main configuration settings are used by default." \ + "\n" \ + "The result is either decrypted data as bytes in case of success," \ + " or *None* in case of failure." \ +) + + settings = NULL; + + ret = PyArg_ParseTuple(args, "s#|O&", &data_in, &size_in, convert_to_gsettings, &settings); + if (!ret) return NULL; + + in.static_data = data_in; + in.size = size_in; + + status = decrypt_secret_storage_data(settings, &in, &out); + + if (status) + { + result = PyBytes_FromStringAndSize(out.static_data, out.size); + exit_sized_binary(&out); + } + + else + { + result = Py_None; + Py_INCREF(result); + } + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Définit une extension du module 'core' à compléter. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : - * +* * +******************************************************************************/ + +bool populate_core_module_with_secstorage(void) +{ + bool result; /* Bilan à retourner */ + PyObject *module; /* Module à recompléter */ + + static PyMethodDef py_secstorage_methods[] = { + SECSTORAGE_HAS_SECRET_STORAGE_KEY_METHOD, + SECSTORAGE_SET_SECRET_STORAGE_PASSWORD_METHOD, + SECSTORAGE_IS_SECRET_STORAGE_LOCKED_METHOD, + SECSTORAGE_UNLOCK_SECRET_STORAGE_METHOD, + SECSTORAGE_LOCK_SECRET_STORAGE_METHOD, + SECSTORAGE_ENCRYPT_SECRET_STORAGE_DATA_METHOD, + SECSTORAGE_DECRYPT_SECRET_STORAGE_DATA_METHOD, + { NULL } + }; + + module = get_access_to_python_module("pychrysalide.core"); + + result = register_python_module_methods(module, py_secstorage_methods); + + return result; + +} diff --git a/plugins/pychrysalide/core/secstorage.h b/plugins/pychrysalide/core/secstorage.h new file mode 100644 index 0000000..d05d052 --- /dev/null +++ b/plugins/pychrysalide/core/secstorage.h @@ -0,0 +1,39 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * secstorage.h - prototypes pour l'équivalent Python du fichier "core/secstorage.h" + * + * 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef _PLUGINS_PYCHRYSALIDE_CORE_SECSTORAGE_H +#define _PLUGINS_PYCHRYSALIDE_CORE_SECSTORAGE_H + + +#include <Python.h> +#include <stdbool.h> + + + +/* Définit une extension du module 'core' à compléter. */ +bool populate_core_module_with_secstorage(void); + + + +#endif /* _PLUGINS_PYCHRYSALIDE_CORE_SECSTORAGE_H */ diff --git a/src/common/szbin.h b/src/common/szbin.h index 5891449..23aac67 100644 --- a/src/common/szbin.h +++ b/src/common/szbin.h @@ -97,6 +97,16 @@ typedef struct _sized_binary_t while (0) +#define resize_sized_binary(sb, s) \ + do \ + { \ + (sb)->size = s; \ + (sb)->data = realloc((sb)->data, \ + (sb)->size); \ + } \ + while (0) + + #define add_to_sized_binary(sb, d, s) \ do \ { \ diff --git a/src/core/Makefile.am b/src/core/Makefile.am index e1e3c4e..906c383 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -20,9 +20,10 @@ libcore4_la_SOURCES = \ logs.h logs.c \ nox.h nox.c \ nproc.h nproc.c \ - paths.h paths.c + paths.h paths.c \ + secstorage.h secstorage.c -libcore4_la_CFLAGS = $(TOOLKIT_CFLAGS) +libcore4_la_CFLAGS = $(TOOLKIT_CFLAGS) $(LIBSSL_CFLAGS) devdir = $(includedir)/chrysalide/$(subdir:src/%=core/%) diff --git a/src/core/core.c b/src/core/core.c index 9514ee1..eaf7763 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -25,6 +25,7 @@ #include "global.h" +#include "secstorage.h" @@ -53,6 +54,8 @@ bool load_core_components(AvailableCoreComponent flags) if ((flags & ACC_GLOBAL_VARS) != 0 && (__loaded & ACC_GLOBAL_VARS) == 0) { + init_secret_storage(); + set_work_queue(g_work_queue_new()); __loaded |= ACC_GLOBAL_VARS; @@ -82,6 +85,8 @@ void unload_core_components(AvailableCoreComponent flags) { set_work_queue(NULL); + exit_secret_storage(); + __loaded &= ~ACC_GLOBAL_VARS; } diff --git a/src/core/secstorage.c b/src/core/secstorage.c new file mode 100644 index 0000000..7f57b1c --- /dev/null +++ b/src/core/secstorage.c @@ -0,0 +1,779 @@ + +/* 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 <http://www.gnu.org/licenses/>. + */ + + +#include "secstorage.h" + + +#include <assert.h> +#include <string.h> +#include <openssl/evp.h> +#include <openssl/rand.h> + + +#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; + +} diff --git a/src/core/secstorage.h b/src/core/secstorage.h new file mode 100644 index 0000000..7c27e07 --- /dev/null +++ b/src/core/secstorage.h @@ -0,0 +1,65 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * secstorage.h - prototypes pour la 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 <http://www.gnu.org/licenses/>. + */ + + +#ifndef _CORE_SECSTORAGE_H +#define _CORE_SECSTORAGE_H + + +#include <stdbool.h> +#include <gio/gio.h> + + +#include "../common/szbin.h" + + + +/* Initialise le stockage des clefs de déchiffrement en place. */ +void init_secret_storage(void); + +/* Supprime le stockage des clefs de déchiffrement en place. */ +void exit_secret_storage(void); + +/* Détermine si une clef de chiffrement protégée est en place. */ +bool has_secret_storage_key(GSettings *); + +/* Définit un mot de passe pour protéger une clef maître. */ +bool set_secret_storage_password(GSettings *, const char *); + +/* Détermine si la clef de chiffrement maître est vérouillée. */ +bool is_secret_storage_locked(GSettings *); + +/* Déverrouille la clef de chiffrement maître. */ +bool unlock_secret_storage(GSettings *, const char *); + +/* Verrouille la clef de chiffrement maître. */ +void lock_secret_storage(GSettings *); + +/* Chiffre des données avec la clef de chiffrement maître. */ +bool encrypt_secret_storage_data(GSettings *, const sized_binary_t *, sized_binary_t *); + +/* Déchiffre des données avec la clef de chiffrement maître. */ +bool decrypt_secret_storage_data(GSettings *, const sized_binary_t *, sized_binary_t *); + + + +#endif /* _CORE_SECSTORAGE_H */ diff --git a/tests/core/re.chrysalide.tests.secstorage.gschema.xml b/tests/core/re.chrysalide.tests.secstorage.gschema.xml new file mode 100644 index 0000000..6afa96b --- /dev/null +++ b/tests/core/re.chrysalide.tests.secstorage.gschema.xml @@ -0,0 +1,15 @@ +<schemalist> + + <schema id="re.chrysalide.tests.secstorage" path="/re/chrysalide/tests/secstorage/"> + + <key name="salt" type="ay"> + <default>[]</default> + </key> + + <key name="master" type="ay"> + <default>[]</default> + </key> + + </schema> + +</schemalist> diff --git a/tests/core/secstorage.py b/tests/core/secstorage.py new file mode 100644 index 0000000..1f82388 --- /dev/null +++ b/tests/core/secstorage.py @@ -0,0 +1,150 @@ + +import gi +import os +import subprocess + +from chrysacase import ChrysalideTestCase +from pychrysalide import core +from gi.repository import Gio, GLib + + +class TestSecretStorage(ChrysalideTestCase): + """TestCase for secret storage features.""" + + @classmethod + def setUpClass(cls): + + super(TestSecretStorage, cls).setUpClass() + + cls.log('Creating GSettings schema...') + + path = os.path.dirname(os.path.realpath(__file__)) + + subprocess.run([ 'glib-compile-schemas', path ]) + + os.environ['GSETTINGS_SCHEMA_DIR'] = path + ':' + os.environ['GSETTINGS_SCHEMA_DIR'] + + + @classmethod + def tearDownClass(cls): + + super(TestSecretStorage, cls).tearDownClass() + + cls.log('Removing compiled GSettings schema...') + + os.environ['GSETTINGS_SCHEMA_DIR'] = ':'.join(os.environ['GSETTINGS_SCHEMA_DIR'].split(':')[1:]) + + path = os.path.dirname(os.path.realpath(__file__)) + + filename = os.path.join(path, 'gschemas.compiled') + + if os.path.exists(filename): + os.remove(filename) + + + def testMasterKeyDefinition(self): + """Check for cryptographic parameters for secret storage.""" + + settings = Gio.Settings.new('re.chrysalide.tests.secstorage') + + settings.reset('master') + + self.assertEqual(len(settings.get_value('master').unpack()), 0) + + self.assertFalse(core.has_secret_storage_key(settings)) + + settings.set_value('master', GLib.Variant('ay', b'ABC')) + + self.assertFalse(core.has_secret_storage_key(settings)) + + settings.set_value('master', GLib.Variant('ay', b'A' * 23)) + + self.assertTrue(core.has_secret_storage_key(settings)) + + + def testMasterKeyCreation(self): + """Create and update cryptographic parameters for secret storage.""" + + settings = Gio.Settings.new('re.chrysalide.tests.secstorage') + + settings.reset('salt') + settings.reset('master') + + status = core.has_secret_storage_key(settings) + + self.assertFalse(status); + + status = core.set_secret_storage_password(settings, '') + + self.assertTrue(status); + + status = core.has_secret_storage_key(settings) + + self.assertTrue(status); + + status = core.is_secret_storage_locked(settings) + + self.assertTrue(status) + + status = core.unlock_secret_storage(settings, '') + + self.assertTrue(status) + + status = core.is_secret_storage_locked(settings) + + self.assertFalse(status) + + core.lock_secret_storage(settings) + + status = core.is_secret_storage_locked(settings) + + self.assertTrue(status) + + status = core.unlock_secret_storage(settings, 'XXX') + + self.assertFalse(status) + + status = core.is_secret_storage_locked(settings) + + self.assertTrue(status) + + + def testDataEncryption(self): + """Create and update cryptographic parameters for secret storage.""" + + settings = Gio.Settings.new('re.chrysalide.tests.secstorage') + + settings.reset('salt') + settings.reset('master') + + status = core.set_secret_storage_password(settings, '<s3cUre>') + + self.assertTrue(status); + + status = core.unlock_secret_storage(settings, '<s3cUre>') + + self.assertTrue(status) + + + original = b'ABC' + + encrypted = core.encrypt_secret_storage_data(original, settings) + + self.assertIsNotNone(encrypted) + + plain = core.decrypt_secret_storage_data(encrypted, settings) + + self.assertIsNotNone(plain) + self.assertEqual(original, plain) + + + original = b'A' * 136 + + encrypted = core.encrypt_secret_storage_data(original, settings) + + self.assertIsNotNone(encrypted) + + plain = core.decrypt_secret_storage_data(encrypted, settings) + + self.assertIsNotNone(plain) + self.assertEqual(original, plain) |