From 7778a0c082c4969ed6184883b2d96d8a851def99 Mon Sep 17 00:00:00 2001 From: Cyrille Bagard Date: Tue, 7 Feb 2017 23:41:07 +0100 Subject: Provided a way to create SSL certificates. --- ChangeLog | 23 ++ plugins/pychrysa/analysis/db/Makefile.am | 1 + plugins/pychrysa/analysis/db/certs.c | 327 +++++++++++++++ plugins/pychrysa/analysis/db/certs.h | 42 ++ plugins/pychrysa/analysis/db/module.c | 2 + src/analysis/db/Makefile.am | 1 + src/analysis/db/certs.c | 678 +++++++++++++++++++++++++++++++ src/analysis/db/certs.h | 59 +++ tests/analysis/db/__init__.py | 0 tests/analysis/db/certs.py | 112 +++++ 10 files changed, 1245 insertions(+) create mode 100644 plugins/pychrysa/analysis/db/certs.c create mode 100644 plugins/pychrysa/analysis/db/certs.h create mode 100644 src/analysis/db/certs.c create mode 100644 src/analysis/db/certs.h create mode 100644 tests/analysis/db/__init__.py create mode 100644 tests/analysis/db/certs.py diff --git a/ChangeLog b/ChangeLog index babdb96..95add10 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,26 @@ +17-02-07 Cyrille Bagard + + * 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 * 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 +#include + + +#include +#include + + +#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 +#include + + + +/* 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 +#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 . + */ + + +#include "certs.h" + + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include + + +#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 . + */ + + +#ifndef _ANALYSIS_DB_CERTS_H +#define _ANALYSIS_DB_CERTS_H + + +#include + + + +/* 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 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) + -- cgit v0.11.2-87-g4458