summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCyrille Bagard <nocbos@gmail.com>2017-02-07 22:41:07 (GMT)
committerCyrille Bagard <nocbos@gmail.com>2017-02-07 22:41:07 (GMT)
commit7778a0c082c4969ed6184883b2d96d8a851def99 (patch)
tree296cde845f5a086688f2e9b5c8555a06f55d99a8
parent2834917e0e3b5e9ea3e6ea0fb90cdbf066ea9da7 (diff)
Provided a way to create SSL certificates.
-rw-r--r--ChangeLog23
-rw-r--r--plugins/pychrysa/analysis/db/Makefile.am1
-rw-r--r--plugins/pychrysa/analysis/db/certs.c327
-rw-r--r--plugins/pychrysa/analysis/db/certs.h42
-rw-r--r--plugins/pychrysa/analysis/db/module.c2
-rwxr-xr-xsrc/analysis/db/Makefile.am1
-rw-r--r--src/analysis/db/certs.c678
-rw-r--r--src/analysis/db/certs.h59
-rw-r--r--tests/analysis/db/__init__.py0
-rw-r--r--tests/analysis/db/certs.py112
10 files changed, 1245 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
index babdb96..95add10 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,26 @@
+17-02-07 Cyrille Bagard <nocbos@gmail.com>
+
+ * plugins/pychrysa/analysis/db/Makefile.am:
+ Add the 'certs.[ch]' files to libpychrysaanalysisdb_la_SOURCES.
+
+ * plugins/pychrysa/analysis/db/certs.c:
+ * plugins/pychrysa/analysis/db/certs.h:
+ New entries: add some Python bindings for creating certificates.
+
+ * plugins/pychrysa/analysis/db/module.c:
+ Update code.
+
+ * src/analysis/db/Makefile.am:
+ Add the 'certs.[ch]' files to libanalysisdb_la_SOURCES.
+
+ * src/analysis/db/certs.c:
+ * src/analysis/db/certs.h:
+ New entries: provide a way to create SSL certificates.
+
+ * tests/analysis/db/__init__.py:
+ * tests/analysis/db/certs.py:
+ New entries: provide some tests.
+
17-01-31 Cyrille Bagard <nocbos@gmail.com>
* plugins/pychrysa/analysis/db/items/comment.c:
diff --git a/plugins/pychrysa/analysis/db/Makefile.am b/plugins/pychrysa/analysis/db/Makefile.am
index 2de2a16..a6bb701 100644
--- a/plugins/pychrysa/analysis/db/Makefile.am
+++ b/plugins/pychrysa/analysis/db/Makefile.am
@@ -2,6 +2,7 @@
noinst_LTLIBRARIES = libpychrysaanalysisdb.la
libpychrysaanalysisdb_la_SOURCES = \
+ certs.h certs.c \
collection.h collection.c \
item.h item.c \
module.h module.c
diff --git a/plugins/pychrysa/analysis/db/certs.c b/plugins/pychrysa/analysis/db/certs.c
new file mode 100644
index 0000000..e0358d1
--- /dev/null
+++ b/plugins/pychrysa/analysis/db/certs.c
@@ -0,0 +1,327 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * certs.c - équivalent Python du fichier "analysis/db/certs.c"
+ *
+ * Copyright (C) 2017 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 "certs.h"
+
+
+#include <pygobject.h>
+#include <string.h>
+
+
+#include <i18n.h>
+#include <analysis/db/certs.h>
+
+
+#include "../../helpers.h"
+
+
+
+/* Traduit en version native une identité de certificat. */
+static bool py_certs_fill_x509_entries(PyObject *, x509_entries *);
+
+/* Crée un certificat de signature racine. */
+static PyObject *py_certs_make_ca(PyObject *, PyObject *);
+
+/* Crée un certificat pour application. */
+static PyObject *py_certs_make_request(PyObject *, PyObject *);
+
+/* Signe un certificat pour application. */
+static PyObject *py_certs_sign_cert(PyObject *, PyObject *);
+
+
+
+/******************************************************************************
+* *
+* Paramètres : dict = ensemble de propriétés renseignées. *
+* out = résumé des entrées regroupées. [OUT] *
+* *
+* Description : Traduit en version native une identité de certificat. *
+* *
+* Retour : Bilan de l'opération. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static bool py_certs_fill_x509_entries(PyObject *dict, x509_entries *out)
+{
+ bool result; /* Bilan à retourner */
+ PyObject *value; /* Valeur au format Python */
+
+#define TRANSLATE_ENTRY(name, dest) \
+ do \
+ { \
+ value = PyDict_GetItemString(dict, name); \
+ if (value != NULL) \
+ { \
+ result = PyUnicode_Check(value); \
+ if (result) \
+ out->dest = strdup((char *)PyUnicode_DATA(value)); \
+ else \
+ PyErr_Format(PyExc_TypeError, _("The %s property must be a string."), name); \
+ } \
+ } \
+ while (0)
+
+ result = true;
+
+ memset(out, 0, sizeof(x509_entries));
+
+ TRANSLATE_ENTRY("C", country);
+
+ if (result)
+ TRANSLATE_ENTRY("ST", state);
+
+ if (result)
+ TRANSLATE_ENTRY("L", locality);
+
+ if (result)
+ TRANSLATE_ENTRY("O", organisation);
+
+ if (result)
+ TRANSLATE_ENTRY("OU", organisational_unit);
+
+ if (result)
+ TRANSLATE_ENTRY("CN", common_name);
+
+ if (!result)
+ free_x509_entries(out);
+
+ return result;
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : self = NULL car méthode statique. *
+* args = paramètres à transmettre à l'appel natif. *
+* *
+* Description : Crée un certificat de signature racine. *
+* *
+* Retour : Bilan de l'opération. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static PyObject *py_certs_make_ca(PyObject *self, PyObject *args)
+{
+ PyObject *result; /* Désignation à retourner */
+ const char *dir; /* Répertoire de sortie */
+ const char *label; /* Nom principal du certificat */
+ unsigned long valid; /* Durée de validité en sec. */
+ PyObject *dict; /* Détails identitaires */
+ int ret; /* Bilan de lecture des args. */
+ x509_entries entries; /* Définition d'une identité */
+ bool status; /* Bilan d'une constitution */
+
+ ret = PyArg_ParseTuple(args, "sskO!", &dir, &label, &valid, &PyDict_Type, &dict);
+ if (!ret) return NULL;
+
+ status = py_certs_fill_x509_entries(dict, &entries);
+ if (!status) return NULL;
+
+ status = make_ca(dir, label, valid, &entries);
+
+ free_x509_entries(&entries);
+
+ result = status ? Py_True : Py_False;
+
+ Py_INCREF(result);
+
+ return result;
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : self = NULL car méthode statique. *
+* args = paramètres à transmettre à l'appel natif. *
+* *
+* Description : Crée un certificat pour application. *
+* *
+* Retour : Bilan de l'opération. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static PyObject *py_certs_make_request(PyObject *self, PyObject *args)
+{
+ PyObject *result; /* Désignation à retourner */
+ const char *dir; /* Répertoire de sortie */
+ const char *label; /* Nom principal du certificat */
+ PyObject *dict; /* Détails identitaires */
+ int ret; /* Bilan de lecture des args. */
+ x509_entries entries; /* Définition d'une identité */
+ bool status; /* Bilan d'une constitution */
+
+ ret = PyArg_ParseTuple(args, "ssO!", &dir, &label, &PyDict_Type, &dict);
+ if (!ret) return NULL;
+
+ status = py_certs_fill_x509_entries(dict, &entries);
+ if (!status) return NULL;
+
+ status = make_request(dir, label, &entries);
+
+ free_x509_entries(&entries);
+
+ result = status ? Py_True : Py_False;
+
+ Py_INCREF(result);
+
+ return result;
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : self = NULL car méthode statique. *
+* args = paramètres à transmettre à l'appel natif. *
+* *
+* Description : Signe un certificat pour application. *
+* *
+* Retour : Bilan de l'opération. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static PyObject *py_certs_sign_cert(PyObject *self, PyObject *args)
+{
+ PyObject *result; /* Désignation à retourner */
+ const char *csr; /* Requête à satisfaire */
+ const char *cacert; /* Certificat de confiance */
+ const char *cakey; /* Clef de ce certificat */
+ const char *cert; /* Certificat en sortie */
+ unsigned long valid; /* Durée de validité en sec. */
+ int ret; /* Bilan de lecture des args. */
+ bool status; /* Bilan de l'opération */
+
+ ret = PyArg_ParseTuple(args, "ssssk", &csr, &cacert, &cakey, &cert, &valid);
+ if (!ret) return NULL;
+
+ status = sign_cert(csr, cacert, cakey, cert, valid);
+
+ result = status ? Py_True : Py_False;
+
+ Py_INCREF(result);
+
+ return result;
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : - *
+* *
+* Description : Fournit un accès à une définition de type à diffuser. *
+* *
+* Retour : Définition d'objet pour Python. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+PyTypeObject *get_python_certs_type(void)
+{
+ static PyMethodDef py_certs_methods[] = {
+
+ { "make_ca", py_certs_make_ca,
+ METH_VARARGS | METH_STATIC,
+ "make_ca(dir, label, valid, entries, /)\n--\n\nCreate a certificate authority."
+ },
+ { "make_request", py_certs_make_request,
+ METH_VARARGS | METH_STATIC,
+ "make_request(dir, label, entries, /)\n--\n\nCreate a certificate sign request."
+ },
+ { "sign_cert", py_certs_sign_cert,
+ METH_VARARGS | METH_STATIC,
+ "sign_cert(csr, cacert, cakey, cert, valid, /)\n--\n\nSign a certificate sign request.."
+ },
+ { NULL }
+
+ };
+
+ static PyGetSetDef py_certs_getseters[] = {
+
+ { NULL }
+
+ };
+
+ static PyTypeObject py_certs_type = {
+
+ PyVarObject_HEAD_INIT(NULL, 0)
+
+ .tp_name = "pychrysalide.analysis.db.certs",
+ .tp_basicsize = sizeof(PyGObject),
+
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+
+ .tp_doc = "PyChrysalide support for DataBase certicates",
+
+ .tp_methods = py_certs_methods,
+ .tp_getset = py_certs_getseters,
+
+ };
+
+ return &py_certs_type;
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : module = module dont la définition est à compléter. *
+* *
+* Description : Prend en charge l'objet 'pychrysalide....db.certs'. *
+* *
+* Retour : Bilan de l'opération. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+bool register_python_certs(PyObject *module)
+{
+ PyTypeObject *py_certs_type; /* Type Python pour 'certs' */
+ int ret; /* Bilan d'un appel */
+
+ py_certs_type = get_python_certs_type();
+
+ py_certs_type->tp_new = PyType_GenericNew;
+
+ if (PyType_Ready(py_certs_type) != 0)
+ return false;
+
+ Py_INCREF(py_certs_type);
+ ret = PyModule_AddObject(module, "certs", (PyObject *)py_certs_type);
+
+ return (ret == 0);
+
+}
diff --git a/plugins/pychrysa/analysis/db/certs.h b/plugins/pychrysa/analysis/db/certs.h
new file mode 100644
index 0000000..f7537e5
--- /dev/null
+++ b/plugins/pychrysa/analysis/db/certs.h
@@ -0,0 +1,42 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * certs.h - prototypes pour l'équivalent Python du fichier "analysis/db/certs.h"
+ *
+ * Copyright (C) 2017 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_PYCHRYSA_ANALYSIS_DB_CERTS_H
+#define _PLUGINS_PYCHRYSA_ANALYSIS_DB_CERTS_H
+
+
+#include <Python.h>
+#include <stdbool.h>
+
+
+
+/* Fournit un accès à une définition de type à diffuser. */
+PyTypeObject *get_python_certs_type(void);
+
+/* Prend en charge l'objet 'pychrysalide.analysis.db.certs'. */
+bool register_python_certs(PyObject *);
+
+
+
+#endif /* _PLUGINS_PYCHRYSA_ANALYSIS_DB_CERTSS_H */
diff --git a/plugins/pychrysa/analysis/db/module.c b/plugins/pychrysa/analysis/db/module.c
index 0ae6dda..eac3641 100644
--- a/plugins/pychrysa/analysis/db/module.c
+++ b/plugins/pychrysa/analysis/db/module.c
@@ -28,6 +28,7 @@
#include <assert.h>
+#include "certs.h"
#include "collection.h"
#include "item.h"
#include "items/module.h"
@@ -80,6 +81,7 @@ bool add_analysis_db_module_to_python_module(PyObject *super)
result = true;
+ result &= register_python_certs(module);
result &= register_python_db_collection(module);
result &= register_python_db_item(module);
diff --git a/src/analysis/db/Makefile.am b/src/analysis/db/Makefile.am
index 7e9f177..9dee122 100755
--- a/src/analysis/db/Makefile.am
+++ b/src/analysis/db/Makefile.am
@@ -4,6 +4,7 @@ noinst_LTLIBRARIES = libanalysisdb.la libanalysiskeys.la
libanalysisdb_la_SOURCES = \
cdb.h cdb.c \
+ certs.h certs.c \
client.h client.c \
collection-int.h \
collection.h collection.c \
diff --git a/src/analysis/db/certs.c b/src/analysis/db/certs.c
new file mode 100644
index 0000000..ac2ff39
--- /dev/null
+++ b/src/analysis/db/certs.c
@@ -0,0 +1,678 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * certs.h - prototypes pour la gestion des certificats des échanges
+ *
+ * Copyright (C) 2017 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 Foobar. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "certs.h"
+
+
+#include <malloc.h>
+#include <stdio.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+
+#include <i18n.h>
+
+
+#include "../../gui/panels/log.h"
+
+
+
+/* Ajoute une extension à un certificat. */
+static bool add_extension_to_cert(X509 *, X509 *, /*const */char *, /*const */char *);
+
+/* Ajoute une extension à une requête de signature. */
+static bool add_extension_to_req(STACK_OF(X509_EXTENSION) *, int, /*const */char *);
+
+
+
+/******************************************************************************
+* *
+* Paramètres : entries = éléments d'identité à supprimer de la mémoire. *
+* *
+* Description : Libère la mémoire occupée par une définition d'identité. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+void free_x509_entries(x509_entries *entries)
+{
+ if (entries->country != NULL)
+ free(entries->country);
+
+ if (entries->state != NULL)
+ free(entries->state);
+
+ if (entries->locality != NULL)
+ free(entries->locality);
+
+ if (entries->organisation != NULL)
+ free(entries->organisation);
+
+ if (entries->organisational_unit != NULL)
+ free(entries->organisational_unit);
+
+ if (entries->common_name != NULL)
+ free(entries->common_name);
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : issuer = certificat de l'autorité émettrice. *
+* subj = certificat à la reception. *
+* name = nom de l'extension. *
+* value = valeur portée par l'extension. *
+* *
+* Description : Ajoute une extension à un certificat. *
+* *
+* Retour : Bilan de l'opération. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static bool add_extension_to_cert(X509 *issuer, X509 *subj, /*const */char *name, /*const */char *value)
+{
+ bool result; /* Bilan à retourner */
+ X509V3_CTX ctx; /* Contexte à conserver */
+ X509_EXTENSION *ext; /* Définition d'une extension */
+ int ret; /* Bilan d'un ajout */
+
+ result = false;
+
+ X509V3_set_ctx_nodb(&ctx);
+ X509V3_set_ctx(&ctx, issuer, subj, NULL, NULL, 0);
+
+ ext = X509V3_EXT_conf(NULL, &ctx, name, value);
+
+ if (ext != NULL)
+ {
+ ret = X509_add_ext(subj, ext, -1);
+
+ result = (ret != 0);
+
+ X509_EXTENSION_free(ext);
+
+ }
+
+ return result;
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : dir = répertoire d'enregistrement de la création. *
+* label = étiquette à coller au certificat produit. *
+* valid = durée de validité en secondes. *
+* entries = éléments de l'identité à constituer. *
+* *
+* Description : Crée un certificat de signature racine. *
+* *
+* Retour : Bilan de l'opération. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+bool make_ca(const char *dir, const char *label, unsigned long valid, const x509_entries *entries)
+{
+ RSA *rsa; /* Clef RSA pour le certificat */
+ EVP_PKEY *pk; /* Enveloppe pour clef publique*/
+ int ret; /* Bilan d'un appel */
+ X509 *x509; /* Certificat X509 à définir */
+ X509_NAME *name; /* Désignation du certificat */
+ char *filename; /* Chemin d'accès à un fichier */
+ FILE *stream; /* Flux ouvert en écriture */
+
+ rsa = RSA_generate_key(4096, 17, NULL, NULL);
+ if (rsa == NULL)
+ {
+ log_variadic_message(LMT_ERROR, _("Unable to generate RSA key (error=%lu)"), ERR_get_error());
+ goto rsa_failed;
+ }
+
+ pk = EVP_PKEY_new();
+ if (pk == NULL) goto pk_failed;
+
+ ret = EVP_PKEY_assign_RSA(pk, rsa);
+ if (ret != 1) goto asign_failed;
+
+ x509 = X509_new();
+ if (x509 == NULL) goto x509_failed;
+
+ ret = X509_set_pubkey(x509, pk);
+ if (ret != 1) goto ca_failed;
+
+ ret = X509_set_version(x509, 2);
+ if (ret != 1) goto ca_failed;
+
+ ret = ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
+ if (ret != 1) goto ca_failed;
+
+ X509_gmtime_adj(X509_get_notBefore(x509), 0);
+ X509_gmtime_adj(X509_get_notAfter(x509), valid);
+
+ /* Etablissement d'une identité */
+
+ name = X509_get_subject_name(x509);
+
+#define SET_NAME_ENTRY(key, value) \
+ do \
+ { \
+ if (entries->value != NULL) \
+ { \
+ ret = X509_NAME_add_entry_by_txt(name, key, MBSTRING_UTF8, \
+ (unsigned char *)entries->value, -1, -1, 0); \
+ if (ret != 1) goto ca_failed; \
+ } \
+ } \
+ while (0)
+
+ SET_NAME_ENTRY("C", country);
+
+ SET_NAME_ENTRY("ST", state);
+
+ SET_NAME_ENTRY("L", locality);
+
+ SET_NAME_ENTRY("O", organisation);
+
+ SET_NAME_ENTRY("OU", organisational_unit);
+
+ SET_NAME_ENTRY("CN", common_name);
+
+#undef SET_NAME_ENTRY
+
+ ret = X509_set_issuer_name(x509, name);
+ if (ret != 1) goto ca_failed;
+
+ /* Extensions */
+
+ if (!add_extension_to_cert(x509, x509, "basicConstraints", "CA:TRUE"))
+ goto ca_failed;
+
+ if (!add_extension_to_cert(x509, x509, "keyUsage", "critical,keyCertSign,cRLSign"))
+ goto ca_failed;
+
+ if (!add_extension_to_cert(x509, x509, "subjectKeyIdentifier", "hash"))
+ goto ca_failed;
+
+ if (!add_extension_to_cert(x509, x509, "nsComment", "\"OpenSSL Generated Certificate\""))
+ goto ca_failed;
+
+ /* Signature */
+
+ ret = X509_sign(x509, pk, EVP_sha256());
+ if (ret == 0) goto ca_failed;
+
+ /* Ecriture dans des fichiers */
+
+ asprintf(&filename, "%s%c%s-key.pem", dir, G_DIR_SEPARATOR, label);
+
+ stream = fopen(filename, "wb");
+ if (stream == NULL) goto ca_failed;
+
+ ret = PEM_write_PrivateKey(stream, pk, NULL, NULL, 0, NULL, NULL);
+
+ if (ret != 1)
+ log_variadic_message(LMT_ERROR, _("Unable to write the CA key into '%s'"), filename);
+
+ fclose(stream);
+
+ free(filename);
+
+ if (ret != 1)
+ goto ca_failed;
+
+ asprintf(&filename, "%s%c%s-cert.pem", dir, G_DIR_SEPARATOR, label);
+
+ stream = fopen(filename, "wb");
+ if (stream == NULL) goto ca_failed;
+
+ ret = PEM_write_X509(stream, x509);
+
+ if (ret != 1)
+ log_variadic_message(LMT_ERROR, _("Unable to write the CA certificate into '%s'"), filename);
+
+ fclose(stream);
+
+ free(filename);
+
+ if (ret != 1)
+ goto ca_failed;
+
+ /* Libérations finales */
+
+ X509_free(x509);
+ EVP_PKEY_free(pk);
+
+ return true;
+
+ ca_failed:
+
+ X509_free(x509);
+
+ x509_failed:
+
+ EVP_PKEY_free(pk);
+
+ return false;
+
+ asign_failed:
+
+ EVP_PKEY_free(pk);
+
+ pk_failed:
+
+ RSA_free(rsa);
+
+ rsa_failed:
+
+ return false;
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : sk = pile d'extension à agrandir. *
+* nid = identifiant de l'extension à apporter. *
+* value = valeur portée par l'extension. *
+* *
+* Description : Ajoute une extension à une requête de signature. *
+* *
+* Retour : Bilan de l'opération. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static bool add_extension_to_req(STACK_OF(X509_EXTENSION) *sk, int nid, /*const */char *value)
+{
+ bool result; /* Bilan à retourner */
+ X509_EXTENSION *ext; /* Définition d'une extension */
+ int ret; /* Bilan d'un ajout */
+
+ result = false;
+
+ ext = X509V3_EXT_conf_nid(NULL, NULL, nid, value);
+
+ if (ext != NULL)
+ {
+ ret = sk_X509_EXTENSION_push(sk, ext);
+ result = (ret == 1);
+ }
+
+ return result;
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : dir = répertoire d'enregistrement de la création. *
+* label = étiquette à coller au certificat produit. *
+* entries = éléments de l'identité à constituer. *
+* *
+* Description : Crée un certificat pour application. *
+* *
+* Retour : Bilan de l'opération. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+bool make_request(const char *dir, const char *label, const x509_entries *entries)
+{
+ RSA *rsa; /* Clef RSA pour le certificat */
+ EVP_PKEY *pk; /* Enveloppe pour clef publique*/
+ int ret; /* Bilan d'un appel */
+ X509_REQ *x509; /* Certificat X509 à définir */
+ X509_NAME *name; /* Désignation du certificat */
+ STACK_OF(X509_EXTENSION) *exts; /* Extensions du certificat */
+ char *filename; /* Chemin d'accès à un fichier */
+ FILE *stream; /* Flux ouvert en écriture */
+
+ rsa = RSA_generate_key(2048, 17, NULL, NULL);
+ if (rsa == NULL)
+ {
+ log_variadic_message(LMT_ERROR, _("Unable to generate RSA key (error=%lu)"), ERR_get_error());
+ goto rsa_failed;
+ }
+
+ pk = EVP_PKEY_new();
+ if (pk == NULL) goto pk_failed;
+
+ ret = EVP_PKEY_assign_RSA(pk, rsa);
+ if (ret != 1) goto asign_failed;
+
+ x509 = X509_REQ_new();
+ if (x509 == NULL) goto x509_failed;
+
+ ret = X509_REQ_set_pubkey(x509, pk);
+ if (ret != 1) goto asign_failed;
+
+ /* Etablissement d'une identité */
+
+ name = X509_REQ_get_subject_name(x509);
+
+#define SET_NAME_ENTRY(key, value) \
+ do \
+ { \
+ if (entries->value != NULL) \
+ { \
+ ret = X509_NAME_add_entry_by_txt(name, key, MBSTRING_UTF8, \
+ (unsigned char *)entries->value, -1, -1, 0); \
+ if (ret != 1) goto req_failed; \
+ } \
+ } \
+ while (0)
+
+ SET_NAME_ENTRY("C", country);
+
+ SET_NAME_ENTRY("ST", state);
+
+ SET_NAME_ENTRY("L", locality);
+
+ SET_NAME_ENTRY("O", organisation);
+
+ SET_NAME_ENTRY("OU", organisational_unit);
+
+ SET_NAME_ENTRY("CN", common_name);
+
+#undef SET_NAME_ENTRY
+
+ /* Extensions */
+
+ exts = sk_X509_EXTENSION_new_null();
+ if (exts == NULL) goto req_failed;
+
+ if (!add_extension_to_req(exts, NID_key_usage, "critical,digitalSignature,keyEncipherment"))
+ goto exts_failed;
+
+ ret = X509_REQ_add_extensions(x509, exts);
+ if (ret != 1) goto exts_failed;
+
+ sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
+
+ /* Signature */
+
+ ret = X509_REQ_sign(x509, pk, EVP_sha1());
+ if (ret == 0) goto req_failed_2;
+
+ ret = X509_REQ_verify(x509, pk);
+ if (ret != 1) goto req_failed_2;
+
+ /* Ecriture dans des fichiers */
+
+ asprintf(&filename, "%s%c%s-key.pem", dir, G_DIR_SEPARATOR, label);
+
+ stream = fopen(filename, "wb");
+ if (stream == NULL) goto req_failed_2;
+
+ ret = PEM_write_PrivateKey(stream, pk, NULL, NULL, 0, NULL, NULL);
+
+ if (ret != 1)
+ log_variadic_message(LMT_ERROR, _("Unable to write the CA key into '%s'"), filename);
+
+ fclose(stream);
+
+ free(filename);
+
+ if (ret != 1)
+ goto req_failed_2;
+
+ asprintf(&filename, "%s%c%s-csr.pem", dir, G_DIR_SEPARATOR, label);
+
+ stream = fopen(filename, "wb");
+ if (stream == NULL) goto req_failed_2;
+
+ ret = PEM_write_X509_REQ(stream, x509);
+
+ if (ret != 1)
+ log_variadic_message(LMT_ERROR, _("Unable to write the CA certificate into '%s'"), filename);
+
+ fclose(stream);
+
+ free(filename);
+
+ if (ret != 1)
+ goto req_failed_2;
+
+ return true;
+
+ req_failed_2:
+
+ exts_failed:
+
+ sk_X509_EXTENSION_free(exts);
+
+ req_failed:
+
+ X509_REQ_free(x509);
+
+ x509_failed:
+
+ EVP_PKEY_free(pk);
+
+ return false;
+
+ asign_failed:
+
+ EVP_PKEY_free(pk);
+
+ pk_failed:
+
+ RSA_free(rsa);
+
+ rsa_failed:
+
+ return false;
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : csr = fichier contenant le certificat à signer. *
+* cacert = fichier contenant le certificat de l'autorité. *
+* cakey = fichier contenant la clef privée du CA. *
+* cert = fichier contenant le certificat signé. *
+* valid = durée de validité en secondes. *
+* *
+* Description : Signe un certificat pour application. *
+* *
+* Retour : Bilan de l'opération. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+bool sign_cert(const char *csr, const char *cacert, const char *cakey, const char *cert, unsigned long valid)
+{
+ FILE *stream; /* Flux ouvert en écriture */
+ X509_REQ *req; /* Certificat X509 à signer */
+ EVP_PKEY *pk; /* Enveloppe pour clef publique*/
+ X509 *ca_cert; /* Certificat de l'autorité */
+ EVP_PKEY *ca_pk; /* Enveloppe pour clef privée */
+ X509 *x509; /* Certificat X509 à définir */
+ int ret; /* Bilan d'un appel */
+ X509_NAME *name; /* Désignation du certificat */
+
+ /* Chargement de la requête */
+
+ stream = fopen(csr, "rb");
+ if (stream == NULL) goto csr_read_failed;
+
+ req = PEM_read_X509_REQ(stream, NULL, NULL, NULL);
+
+ fclose(stream);
+
+ if (req == NULL)
+ {
+ log_variadic_message(LMT_ERROR, _("Unable to read the certificate signing request from '%s'"), cert);
+ goto csr_read_failed;
+ }
+
+ pk = X509_REQ_get_pubkey(req);
+ if (pk == NULL) goto csr_no_pk;
+
+ ret = X509_REQ_verify(req, pk);
+ if (ret != 1) goto csr_bad_pk;
+
+ /* Chargement des éléments de l'autorité */
+
+ stream = fopen(cacert, "rb");
+ if (stream == NULL) goto cacert_read_failed;
+
+ ca_cert = PEM_read_X509(stream, NULL, NULL, NULL);
+
+ fclose(stream);
+
+ if (ca_cert == NULL)
+ {
+ log_variadic_message(LMT_ERROR, _("Unable to read the certificate from '%s'"), cert);
+ goto cacert_read_failed;
+ }
+
+ stream = fopen(cakey, "rb");
+ if (stream == NULL) goto cakey_read_failed;
+
+ ca_pk = PEM_read_PrivateKey(stream, NULL, NULL, NULL);
+
+ fclose(stream);
+
+ if (ca_pk == NULL)
+ {
+ log_variadic_message(LMT_ERROR, _("Unable to read the CA private key from %s"), cakey);
+ goto cakey_read_failed;
+ }
+
+ /* Création d'un nouveau certificat */
+
+ x509 = X509_new();
+ if (x509 == NULL) goto x509_failed;
+
+ ret = X509_set_version(x509, 2);
+ if (ret != 1) goto signing_failed;
+
+ ret = ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
+ if (ret != 1) goto signing_failed;
+
+ X509_gmtime_adj(X509_get_notBefore(x509), 0);
+ X509_gmtime_adj(X509_get_notAfter(x509), valid);
+
+ /* Transfert des informations existantes */
+
+ ret = X509_set_pubkey(x509, pk);
+ if (ret != 1) goto signing_failed;
+
+ name = X509_REQ_get_subject_name(req);
+
+ ret = X509_set_subject_name(x509, name);
+ if (ret != 1) goto signing_failed;
+
+ name = X509_get_subject_name(ca_cert);
+
+ ret = X509_set_issuer_name(x509, name);
+ if (ret != 1) goto signing_failed;
+
+ /* Extensions */
+
+ if (!add_extension_to_cert(ca_cert, x509, "basicConstraints", "CA:FALSE"))
+ goto signing_failed;
+
+ if (!add_extension_to_cert(ca_cert, x509, "keyUsage", "nonRepudiation,digitalSignature,keyEncipherment"))
+ goto signing_failed;
+
+ if (!add_extension_to_cert(ca_cert, x509, "subjectKeyIdentifier", "hash"))
+ goto signing_failed;
+
+ if (!add_extension_to_cert(ca_cert, x509, "authorityKeyIdentifier", "keyid,issuer:always"))
+ goto signing_failed;
+
+ if (!add_extension_to_cert(ca_cert, x509, "nsComment", "\"OpenSSL Generated Certificate\""))
+ goto signing_failed;
+
+ /* Signature */
+
+ ret = X509_sign(x509, ca_pk, EVP_sha256());
+ if (ret == 0) goto signing_failed;
+
+ /* Ecriture dans un fichier */
+
+ stream = fopen(cert, "wb");
+ if (stream == NULL) goto signing_failed;
+
+ ret = PEM_write_X509(stream, x509);
+
+ if (ret != 1)
+ log_variadic_message(LMT_ERROR, _("Unable to write the signed certificate into '%s'"), cert);
+
+ fclose(stream);
+
+ /* Libérations finales */
+
+ X509_free(x509);
+ EVP_PKEY_free(ca_pk);
+ X509_free(ca_cert);
+ EVP_PKEY_free(pk);
+ X509_REQ_free(req);
+
+ return true;
+
+ signing_failed:
+
+ X509_free(x509);
+
+ x509_failed:
+
+ EVP_PKEY_free(ca_pk);
+
+ cakey_read_failed:
+
+ X509_free(ca_cert);
+
+ cacert_read_failed:
+
+ csr_bad_pk:
+
+ EVP_PKEY_free(pk);
+
+ csr_no_pk:
+
+ X509_REQ_free(req);
+
+ csr_read_failed:
+
+ return false;
+
+}
diff --git a/src/analysis/db/certs.h b/src/analysis/db/certs.h
new file mode 100644
index 0000000..0f7c51d
--- /dev/null
+++ b/src/analysis/db/certs.h
@@ -0,0 +1,59 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * certs.h - prototypes pour la gestion des certificats des échanges
+ *
+ * Copyright (C) 2017 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 Foobar. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _ANALYSIS_DB_CERTS_H
+#define _ANALYSIS_DB_CERTS_H
+
+
+#include <stdbool.h>
+
+
+
+/* Informations pour les certificats X509 */
+typedef struct _x509_entries
+{
+ char *country; /* Pays */
+ char *state; /* Etat */
+ char *locality; /* Localité */
+ char *organisation; /* Organisation */
+ char *organisational_unit; /* Département */
+ char *common_name; /* Désignation commune */
+
+} x509_entries;
+
+
+/* Libère la mémoire occupée par une définition d'identité. */
+void free_x509_entries(x509_entries *);
+
+/* Crée un certificat de signature racine. */
+bool make_ca(const char *, const char *, unsigned long, const x509_entries *);
+
+/* Crée un certificat pour application. */
+bool make_request(const char *, const char *, const x509_entries *);
+
+/* Signe un certificat pour application. */
+bool sign_cert(const char *, const char *, const char *, const char *, unsigned long);
+
+
+
+#endif /* _ANALYSIS_DB_CERTS_H */
diff --git a/tests/analysis/db/__init__.py b/tests/analysis/db/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/analysis/db/__init__.py
diff --git a/tests/analysis/db/certs.py b/tests/analysis/db/certs.py
new file mode 100644
index 0000000..c4dfa32
--- /dev/null
+++ b/tests/analysis/db/certs.py
@@ -0,0 +1,112 @@
+#!/usr/bin/python3-dbg
+# -*- coding: utf-8 -*-
+
+
+# Tests validant la génération de certificats
+
+
+from chrysacase import ChrysalideTestCase
+from pychrysalide.analysis.db import certs
+import shutil
+import subprocess
+import tempfile
+
+
+class TestRestrictedContent(ChrysalideTestCase):
+ """TestCase for analysis.db.certs."""
+
+ @classmethod
+ def setUpClass(cls):
+
+ super(TestRestrictedContent, cls).setUpClass()
+
+ cls._tmppath = tempfile.mkdtemp()
+
+ cls.log('Using temporary directory "%s"' % cls._tmppath)
+
+
+ @classmethod
+ def tearDownClass(cls):
+
+ super(TestRestrictedContent, cls).tearDownClass()
+
+ cls.log('Delete directory "%s"' % cls._tmppath)
+
+ shutil.rmtree(cls._tmppath)
+
+
+ def checkOutput(self, cmd, expected):
+ """Run a command and check its output."""
+
+ output = ''
+
+ try:
+ output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
+ except:
+ pass
+
+ self.assertEqual(output, expected)
+
+
+ def testMakeCA(self):
+ """Check for building a valid CA."""
+
+ identity = {
+
+ 'C': 'UK',
+ 'CN': 'OpenSSL Group'
+
+ }
+
+ ret = certs.make_ca(self._tmppath, 'ca', 3650 * 24 * 60 * 60, identity)
+ self.assertTrue(ret)
+
+ cmd = 'openssl x509 -in %s/ca-cert.pem -subject -noout' % self._tmppath
+
+ expected = b'subject= /C=UK/CN=OpenSSL Group\n'
+
+ self.checkOutput(cmd, expected)
+
+ cmd = 'openssl verify -CApath %s -CAfile %s/ca-cert.pem %s/ca-cert.pem' \
+ % (self._tmppath, self._tmppath, self._tmppath)
+
+ expected = bytes('%s/ca-cert.pem: OK\n' % self._tmppath, 'utf-8')
+
+ self.checkOutput(cmd, expected)
+
+
+ def testMakeCSR(self):
+ """Check for requesting a valid signing request."""
+
+ identity = {
+
+ 'C': 'UK',
+ 'CN': 'OpenSSL Group'
+
+ }
+
+ ret = certs.make_request(self._tmppath, 'server', identity);
+ self.assertTrue(ret)
+
+
+ def testSignCert(self):
+ """Check for properly signing a certificate."""
+
+ ret = certs.sign_cert('%s/server-csr.pem' % self._tmppath, '%s/ca-cert.pem' % self._tmppath, \
+ '%s/ca-key.pem' % self._tmppath, '%s/server-cert.pem' % self._tmppath, \
+ 3650 * 24 * 60 * 60)
+ self.assertTrue(ret)
+
+ cmd = 'openssl x509 -in %s/server-cert.pem -subject -noout' % self._tmppath
+
+ expected = b'subject= /C=UK/CN=OpenSSL Group\n'
+
+ self.checkOutput(cmd, expected)
+
+ cmd = 'openssl verify -CApath %s -CAfile %s/ca-cert.pem %s/server-cert.pem' \
+ % (self._tmppath, self._tmppath, self._tmppath)
+
+ expected = bytes('%s/server-cert.pem: OK\n' % self._tmppath, 'utf-8')
+
+ self.checkOutput(cmd, expected)
+