/* Chrysalide - Outil d'analyse de fichiers binaires
 * keymgn.c - mise en place et gestion des clefs cryptographiques
 *
 * Copyright (C) 2016-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 Chrysalide.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "keymgn.h"


#include <ctype.h>
#include <stdlib.h>
#include <string.h>


#include "../../common/extstr.h"
#include "../../common/pathname.h"
#include "../../common/xdg.h"
#include "../../core/params.h"



/* Mémorise en mémoire la définition d'une identité courante. */
static void store_identity(const x509_entries *, bool);

/* Fournit le répertoire de travail pour les certifications. */
static char *get_cert_working_directory(const char *, const char *);



/******************************************************************************
*                                                                             *
*  Paramètres  : client  = précise la nature de l'identité à représenter.     *
*                entries = éléments d'identité à définir en mémoire. [OUT]    *
*                                                                             *
*  Description : Charge en mémoire la définition de l'identité courante.      *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void load_identity(bool client, x509_entries *entries)
{
    GGenConfig *config;                     /* Configuration à manipuler   */
    const char *path;                       /* Accès à la configuration    */
    char *saved;                            /* Valeur sauvegardée          */
    char *username;                         /* Dénomination affichée       */
    char *lang;                             /* Langage de l'utilisateur    */
    size_t length;                          /* Taille de valeur récupérée  */
    size_t first;                           /* Indice de première majuscule*/
    size_t i;                               /* Boucle de parcours          */

    memset(entries, 0, sizeof(x509_entries));

    /* Chargement à partir des sauvegardes */

    config = get_main_configuration();

    path = (client ? MPK_IDENTITY_CLIENT_C : MPK_IDENTITY_SERVER_C);

    if (g_generic_config_get_value(get_main_configuration(), path, &saved))
        entries->country = (saved != NULL ? strdup(saved) : NULL);

    path = (client ? MPK_IDENTITY_CLIENT_ST : MPK_IDENTITY_SERVER_ST);

    if (g_generic_config_get_value(get_main_configuration(), path, &saved))
        entries->state = (saved != NULL ? strdup(saved) : NULL);

    path = (client ? MPK_IDENTITY_CLIENT_L : MPK_IDENTITY_SERVER_L);

    if (g_generic_config_get_value(get_main_configuration(), path, &saved))
        entries->locality = (saved != NULL ? strdup(saved) : NULL);

    path = (client ? MPK_IDENTITY_CLIENT_O : MPK_IDENTITY_SERVER_O);

    if (g_generic_config_get_value(get_main_configuration(), path, &saved))
        entries->organisation = (saved != NULL ? strdup(saved) : NULL);

    path = (client ? MPK_IDENTITY_CLIENT_OU : MPK_IDENTITY_SERVER_OU);

    if (g_generic_config_get_value(get_main_configuration(), path, &saved))
        entries->organisational_unit = (saved != NULL ? strdup(saved) : NULL);

    path = (client ? MPK_IDENTITY_CLIENT_CN : MPK_IDENTITY_SERVER_CN);

    if (g_generic_config_get_value(get_main_configuration(), path, &saved))
        entries->common_name = (saved != NULL ? strdup(saved) : NULL);

    /* Si les valeurs n'étaient pas définies... */

    if (are_x509_entries_empty(entries))
    {
        /* Identification de l'utilisateur */

        username = getenv("USERNAME");

        if (username == NULL)
            username = getenv("USER");

        if (username == NULL)
            username = getenv("LOGNAME");

        if (username != NULL)
            entries->common_name = strdup(username);
        else
            entries->common_name = strdup("???");

        /* Identification du pays */

        lang = getenv("LANG");

        if (lang != NULL)
        {
            length = strlen(lang);

            first = length;

            for (i = 0; i < length; i++)
            {
                if (isupper(lang[i]))
                {
                    if (first == length)
                        first = i;
                }

                else if (first != length)
                {
                    entries->country = strndup(&lang[first], i - first);
                    break;
                }

            }

        }

        if (entries->country == NULL)
            entries->country = strdup("??");

    }

}


/******************************************************************************
*                                                                             *
*  Paramètres  : entries = éléments d'identité à conserver en mémoire.        *
*                client  = précise la nature de l'identité représentée.       *
*                                                                             *
*  Description : Mémorise en mémoire la définition d'une identité courante.   *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void store_identity(const x509_entries *entries, bool client)
{
    GGenConfig *config;                     /* Configuration à manipuler   */
    const char *path;                       /* Accès à la configuration    */

    config = get_main_configuration();

    path = (client ? MPK_IDENTITY_CLIENT_C : MPK_IDENTITY_SERVER_C);

    g_generic_config_set_value(config, path, entries->country);

    path = (client ? MPK_IDENTITY_CLIENT_ST : MPK_IDENTITY_SERVER_ST);

    g_generic_config_set_value(config, path, entries->state);

    path = (client ? MPK_IDENTITY_CLIENT_L : MPK_IDENTITY_SERVER_L);

    g_generic_config_set_value(config, path, entries->locality);

    path = (client ? MPK_IDENTITY_CLIENT_O : MPK_IDENTITY_SERVER_O);

    g_generic_config_set_value(config, path, entries->organisation);

    path = (client ? MPK_IDENTITY_CLIENT_OU : MPK_IDENTITY_SERVER_OU);

    g_generic_config_set_value(config, path, entries->organisational_unit);

    path = (client ? MPK_IDENTITY_CLIENT_CN : MPK_IDENTITY_SERVER_CN);

    g_generic_config_set_value(config, path, entries->common_name);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : type = type de certificat à gérer.                           *
*                name = dénomination du serveur visé.                         *
*                                                                             *
*  Description : Fournit le répertoire de travail pour les certifications.    *
*                                                                             *
*  Retour      : Définition d'emplacement à libérer de la mémoire après usage.*
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static char *get_cert_working_directory(const char *type, const char *name)
{
    char *result;                           /* Chemin à retourner          */
    char *suffix;                           /* Fin de la destination       */

    suffix = strdup("chrysalide");
    suffix = stradd(suffix, G_DIR_SEPARATOR_S);
    suffix = stradd(suffix, type);
    suffix = stradd(suffix, G_DIR_SEPARATOR_S);
    suffix = stradd(suffix, name);
    suffix = stradd(suffix, G_DIR_SEPARATOR_S);

    result = get_xdg_config_dir(suffix);

    free(suffix);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : entries = éléments d'identité à utiliser pour l'opération.   *
*                                                                             *
*  Description : Définit les certificats utilisés pour les échanges internes. *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool register_standalone_certs(const x509_entries *entries)
{
    bool result;                            /* Bilan de l'opération        */
    char *working;                          /* Répertoire par le client    */
    char *csr;                              /* Requête de signature        */
    char *cert;                             /* Certificat signé en sortie  */

    /* Certificats côtés serveur */

    result = register_server_cert("standalone", entries);

    if (result)
    {
        /* Demande de signature */

        result = make_client_sign_request("standalone", entries);

        /* Signature */

        if (result)
        {
            working = get_cert_working_directory("clients", "standalone");

            csr = build_absolute_filename(working, "client-csr.pem");
            cert = build_absolute_filename(working, "client-cert.pem");

            result = sign_client_request("standalone", csr, cert);

            free(csr);
            free(cert);

            free(working);

        }

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : name    = dénomination du serveur visé.                      *
*                entries = éléments d'identité à utiliser pour l'opération.   *
*                                                                             *
*  Description : Définit les certificats utilisés pour par un serveur.        *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool register_server_cert(const char *name, const x509_entries *entries)
{
    bool result;                            /* Bilan de l'opération        */
    char *suffix;                           /* Fin de la destination       */
    char *working;                          /* Répertoire de travail       */
    unsigned long valid;                    /* Durée de validité           */
    char *csr;                              /* Requête de signature        */
    char *cacert;                           /* Certificat d'autorité       */
    char *cakey;                            /* Clef de cette autorité      */
    char *cert;                             /* Certificat signé en sortie  */

    result = false;

    working = get_cert_working_directory("servers", name);

    if (working != NULL)
    {
        result = mkpath(working);
        if (!result) goto rsc_quick_exit;

        result = g_generic_config_get_value(get_main_configuration(), MPK_IDENTITY_VALIDITY, &valid);

        if (!result)
            goto rsc_quick_exit;

        result = make_ca(working, "ca", valid, entries);
        if (!result) goto rsc_quick_exit;

        result = make_request(working, "server", entries);
        if (!result) goto rsc_quick_exit;

        csr = build_absolute_filename(working, "server-csr.pem");
        cacert = build_absolute_filename(working, "ca-cert.pem");
        cakey = build_absolute_filename(working, "ca-key.pem");
        cert = build_absolute_filename(working, "server-cert.pem");

        result = sign_cert(csr, cacert, cakey, cert, valid);

        if (result)
            store_identity(entries, false);

        free(csr);
        free(cacert);
        free(cakey);
        free(cert);

 rsc_quick_exit:

        free(working);

    }

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : name    = dénomination du serveur visé.                      *
*                entries = éléments d'identité à utiliser pour l'opération.   *
*                                                                             *
*  Description : Elabore une demande de signature de certificat.              *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool make_client_sign_request(const char *name, const x509_entries *entries)
{
    bool result;                            /* Bilan de l'opération        */
    char *working;                          /* Répertoire par le client    */

    working = get_cert_working_directory("clients", name);

    result = mkpath(working);

    if (result)
        result = make_request(working, "client", entries);

    if (result)
        store_identity(entries, true);

    free(working);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : name = dénomination du serveur visé.                         *
*                csr  = fichier contenant le certificat à signer.             *
*                cert = fichier contenant le certificat signé.                *
*                                                                             *
*  Description : Signe un certificat client pour un accès un serveur donné.   *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool sign_client_request(const char *name, const char *csr, const char *cert)
{
    bool result;                            /* Bilan de l'opération        */
    char *working;                          /* Répertoire par le serveur   */
    unsigned long valid;                    /* Durée de validité           */
    char *cacert;                           /* Certificat d'autorité       */
    char *cakey;                            /* Clef de cette autorité      */

    working = get_cert_working_directory("servers", name);

    result = g_generic_config_get_value(get_main_configuration(), MPK_IDENTITY_VALIDITY, &valid);

    if (!result)
        goto scr_exit;

    cacert = build_absolute_filename(working, "ca-cert.pem");
    cakey = build_absolute_filename(working, "ca-key.pem");

    result = sign_cert(csr, cacert, cakey, cert, valid);

    free(cacert);
    free(cakey);

 scr_exit:

    free(working);

    return result;

}










#include <assert.h>
#include <glib.h>
#include <malloc.h>
#include <stdio.h>
#include <unistd.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>


#include <i18n.h>


#include "../../common/xdg.h"



/* Met en place de nouvelles clefs RSA. */
static bool generate_user_rsa_keys(const char *, const char *);



/******************************************************************************
*                                                                             *
*  Paramètres  : -                                                            *
*                                                                             *
*  Description : S'assure que l'utilisateur dispose de clefs RSA.             *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool ensure_user_has_rsa_keys(void)
{
    bool result;                            /* Bilan à retourner           */
    char *priv;                             /* Chemin de la clef privée    */
    char *pub;                              /* Chemin de la clef publique  */
    int priv_check;                         /* Bilan d'une vérification #1 */
    int pub_check;                          /* Bilan d'une vérification #2 */

    result = NULL;

    priv = get_xdg_config_dir("chrysalide" G_DIR_SEPARATOR_S "id_rsa.priv");
    pub = get_xdg_config_dir("chrysalide" G_DIR_SEPARATOR_S "id_rsa.pub");

    priv_check = access(priv, R_OK);
    pub_check = access(pub, R_OK);

    result = (priv_check == 0 && pub_check == 0);

    if (!result)
    {
        result = generate_user_rsa_keys(priv, pub);

        if (!result)
            fprintf(stderr, _("Unable to create new user RSA key pair.\n"));

    }

    free(priv);
    free(pub);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : priv = chemin d'accès pour la clef privée.                   *
*                pub  = chemin d'accès pour la clef publique.                 *
*                                                                             *
*  Description : Met en place de nouvelles clefs RSA.                         *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static bool generate_user_rsa_keys(const char *priv, const char *pub)
{
    bool result;                            /* Bilan à retourner           */
    EVP_PKEY_CTX *ctx;                      /* Contexte de génération      */
    int ret;                                /* Bilan d'un appel            */
    EVP_PKEY *pair;                         /* Paire de clefs RSA générée  */
    FILE *stream;                           /* Flux ouvert en écriture     */

    result = false;

    /**
     * Cf. https://www.openssl.org/docs/manmaster/crypto/EVP_PKEY_keygen.html
     */

    ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
    if (ctx == NULL) goto euhrk_exit;

    ret = EVP_PKEY_keygen_init(ctx);
    if (ret != 1) goto euhrk_exit;

    ret = EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, RSA_USED_SIZE * 8);
    if (ret != 1) goto euhrk_exit;

    pair = NULL;

    ret = EVP_PKEY_keygen(ctx, &pair);
    if (ret != 1) goto euhrk_exit;

    /* Clef privée */

    stream = fopen(priv, "wt");
    if (stream == NULL) goto euhrk_bad_write;

    ret = PEM_write_PrivateKey(stream, pair, NULL, NULL, 0, NULL, NULL);
    if (ret != 1) goto euhrk_bad_write;

    fclose(stream);

    /* Clef publique */

    stream = fopen(pub, "wt");
    if (stream == NULL) goto euhrk_bad_write;

    ret = PEM_write_PUBKEY(stream, pair);
    if (ret != 1) goto euhrk_bad_write;

    result = true;

 euhrk_bad_write:

    fclose(stream);

    EVP_PKEY_free(pair);

 euhrk_exit:

    EVP_PKEY_CTX_free(ctx);

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : filename = chemin d'accès à la clef à charger.               *
*                private  = nature de la clef visée.                          *
*                                                                             *
*  Description : Charge une clef RSA à partir d'un fichier PEM.               *
*                                                                             *
*  Retour      : Clef RSA ou NULL en cas de soucis.                           *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

RSA *load_rsa_key(const char *filename, bool private)
{
    RSA *result;                            /* Clef à retourner            */
    FILE *stream;                           /* Flux ouvert en lecture      */
    int bits;                               /* Taille de la clef en bits   */

    result = NULL;

    stream = fopen(filename, "r");
    if (stream == NULL) goto lrk_exit;

    if (private)
        result = PEM_read_RSAPrivateKey(stream, &result, NULL, NULL);
    else
        result = PEM_read_RSA_PUBKEY(stream, &result, NULL, NULL);

    fclose(stream);

    if (result == NULL)
        fprintf(stderr, _("Unable to read the RSA key from '%s'.\n"), filename);

    else
    {
        bits = RSA_size(result);

        if (bits != RSA_USED_SIZE)
        {
            fprintf(stderr, _("Wrong RSA key size for %s: expected %d, got %d.\n"), filename, RSA_USED_SIZE, bits);
            RSA_free(result);
            result = NULL;
        }

    }

 lrk_exit:

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : key  = clef RSA à utiliser.                                  *
*                hash = empreinte à signer.                                   *
*                sig  = signature calculée.                                   *
*                                                                             *
*  Description : Signe une empreinte MD5 à l'aide d'une clef RSA.             *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool sign_md5_hash(RSA *key, const unsigned char *hash, unsigned char *sig)
{
    int ret;                                /* Bilan de l'opération        */
    unsigned int siglen;                    /* Taille de la signature      */

    siglen = RSA_USED_SIZE;

    ret = RSA_sign(NID_md5, hash, 16, sig, &siglen, key);

    assert(siglen == RSA_USED_SIZE);

    if (ret != 1)
        fprintf(stderr, "Unable to sign hash (error=%lu).\n", ERR_get_error());

    return (ret == 1);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : key  = clef RSA à utiliser.                                  *
*                hash = empreinte à signer.                                   *
*                sig  = signature calculée.                                   *
*                                                                             *
*  Description : Vérifie la signature d'une empreinte MD5 avec une clef RSA.  *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool verify_md5_hash(RSA *key, const unsigned char *hash, unsigned char *sig)
{
    int ret;                                /* Bilan de l'opération        */

    ret = RSA_verify(NID_md5, hash, 16, sig, RSA_USED_SIZE, key);

    return (ret == 1);

}