/* 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é à consulter. *
* *
* Description : Indique si une définition existe dans l'identité. *
* *
* Retour : Etat de la définition des entrées. *
* *
* Remarques : - *
* *
******************************************************************************/
bool are_x509_entries_empty(const x509_entries *entries)
{
bool result; /* Bilan à retourner */
result = true;
if (entries->country != NULL)
result = false;
if (!result && entries->state != NULL)
result = false;
if (!result && entries->locality != NULL)
result = false;
if (!result && entries->organisation != NULL)
result = false;
if (!result && entries->organisational_unit != NULL)
result = false;
if (!result && entries->common_name != NULL)
result = false;
return result;
}
/******************************************************************************
* *
* 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_sha256());
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;
}