/*
 * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com).
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1.  Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 * 2.  Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <algorithm>
#ifdef _MSC_VER
#include "uniwin.h"
#else
#include <unistd.h>
#endif
#include <errno.h>
#include <iterator>
#include "rnp/rnpcfg.h"
#include "rnpkeys.h"
#include "defaults.h"
#include "file-utils.h"
#include "logging.h"

/* -----------------------------------------------------------------------------
 * @brief   Reads input from file pointer and converts it securelly to ints
 *          Partially based on ERR34-C from SEI CERT C Coding Standard
 *
 * @param   fp          pointer to opened pipe
 * @param   result[out] result read from file pointer and converted to int
 *
 * @returns true and value in result if integer was parsed correctly,
 *          otherwise false
 *
-------------------------------------------------------------------------------- */
static bool
rnp_secure_get_long_from_fd(FILE *fp, long &result, bool allow_empty = true)
{
    char buff[BUFSIZ];
    if (!fgets(buff, sizeof(buff), fp)) {
        RNP_LOG("EOF or read error");
        return false;
    }

    errno = 0;
    char *end_ptr = NULL;
    long  num_long = strtol(buff, &end_ptr, 10);
    if (ERANGE == errno) {
        RNP_LOG("Number out of range");
        return false;
    }
    if (end_ptr == buff) {
        return allow_empty;
    }
    if ('\n' != *end_ptr && '\0' != *end_ptr) {
        RNP_LOG("Unexpected end of line");
        return false;
    }

    result = num_long;
    return true;
}

static bool
is_rsa_keysize_supported(uint32_t keysize)
{
    return ((keysize >= 1024) && (keysize <= 4096) && !(keysize % 8));
}

static const char *
ask_curve_name(FILE *input_fp)
{
    std::vector<const char *> curves;
    static const char *const  known_curves[] = {
      "NIST P-256",
      "NIST P-384",
      "NIST P-521",
      "brainpoolP256r1",
      "brainpoolP384r1",
      "brainpoolP512r1",
      "secp256k1",
    };
    const size_t curvenum = sizeof(known_curves) / sizeof(*known_curves);

    try {
        std::copy_if(known_curves,
                     known_curves + curvenum,
                     std::back_inserter(curves),
                     [](const char *curve) {
                         bool supported = false;
                         return !rnp_supports_feature(RNP_FEATURE_CURVE, curve, &supported) &&
                                supported;
                     });
    } catch (const std::exception &e) {
        RNP_LOG("%s", e.what());
        return NULL;
    }
    const size_t ccount = curves.size();
    if (!ccount) {
        return NULL;
    }
    bool        ok = false;
    const char *result = NULL;
    int         attempts = 0;
    do {
        if (attempts >= 10) {
            printf("Too many attempts. Aborting.\n");
            return NULL;
        }
        printf("Please select which elliptic curve you want:\n");
        for (size_t i = 0; i < ccount; i++) {
            printf("\t(%zu) %s\n", i + 1, curves[i]);
        }
        printf("(default %s)> ", DEFAULT_CURVE);
        long val = 0;
        ok = rnp_secure_get_long_from_fd(input_fp, val) && (val > 0) && (val <= (long) ccount);
        if (ok) {
            result = curves[val - 1];
        }
        attempts++;
    } while (!ok);

    return result;
}

static long
ask_rsa_bitlen(FILE *input_fp)
{
    long result = 0;
    do {
        result = DEFAULT_RSA_NUMBITS;
        printf("Please provide bit length of the key (between 1024 and 4096):\n(default %d)> ",
               DEFAULT_RSA_NUMBITS);
    } while (!rnp_secure_get_long_from_fd(input_fp, result) ||
             !is_rsa_keysize_supported(result));
    return result;
}

static long
ask_elgamal_bitlen(FILE *input_fp)
{
    do {
        printf(
          "Please provide bit length of the ElGamal key (between %d and %d):\n(default %d) > ",
          ELGAMAL_MIN_P_BITLEN,
          ELGAMAL_MAX_P_BITLEN,
          DEFAULT_ELGAMAL_NUMBITS);
        long result = DEFAULT_ELGAMAL_NUMBITS;
        if (!rnp_secure_get_long_from_fd(input_fp, result)) {
            continue;
        }
        if ((result >= ELGAMAL_MIN_P_BITLEN) && (result <= ELGAMAL_MAX_P_BITLEN)) {
            // round up to multiple of 32
            result = ((result + 31) / 32) * 32;
            printf("Bitlen of the key will be %lu\n", result);
            return result;
        }
    } while (1);
}

static long
ask_dsa_bitlen(FILE *input_fp)
{
    do {
        printf(
          "Please provide bit length of the DSA key (between %d and %d):\n(default %d) > ",
          DSA_MIN_P_BITLEN,
          DSA_MAX_P_BITLEN,
          DSA_DEFAULT_P_BITLEN);
        long result = DSA_DEFAULT_P_BITLEN;
        if (!rnp_secure_get_long_from_fd(input_fp, result)) {
            continue;
        }
        if ((result >= DSA_MIN_P_BITLEN) && (result <= DSA_MAX_P_BITLEN)) {
            // round up to multiple of 64
            result = ((result + 63) / 64) * 64;
            printf("Bitlen of the key will be %lu\n", result);
            return result;
        }
    } while (1);
}

static bool
rnpkeys_ask_generate_params(rnp_cfg &cfg, FILE *input_fp)
{
    long option = 0;
    do {
        printf("Please select what kind of key you want:\n"
               "\t(1)  RSA (Encrypt or Sign)\n"
               "\t(16) DSA + ElGamal\n"
               "\t(17) DSA + RSA\n" // TODO: See #584
               "\t(19) ECDSA + ECDH\n"
               "\t(22) EDDSA + X25519\n"
               "\t(99) SM2\n"
               "> ");
        if (!rnp_secure_get_long_from_fd(input_fp, option, false)) {
            option = 0;
            continue;
        }
        switch (option) {
        case 1: {
            int bits = ask_rsa_bitlen(input_fp);
            cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_RSA);
            cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA);
            cfg.set_int(CFG_KG_PRIMARY_BITS, bits);
            cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
            break;
        }
        case 16: {
            int bits = ask_dsa_bitlen(input_fp);
            cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_DSA);
            cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ELGAMAL);
            cfg.set_int(CFG_KG_PRIMARY_BITS, bits);
            cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
            break;
        }
        case 17: {
            int bits = ask_dsa_bitlen(input_fp);
            cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_DSA);
            cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA);
            cfg.set_int(CFG_KG_PRIMARY_BITS, bits);
            cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
            break;
        }
        case 19: {
            const char *curve = ask_curve_name(input_fp);
            if (!curve) {
                return false;
            }
            cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_ECDSA);
            cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDH);
            cfg.set_str(CFG_KG_PRIMARY_CURVE, curve);
            cfg.set_str(CFG_KG_SUBKEY_CURVE, curve);
            break;
        }
        case 22: {
            cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_EDDSA);
            cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDH);
            cfg.set_str(CFG_KG_SUBKEY_CURVE, "Curve25519");
            break;
        }
        case 99: {
            cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_SM2);
            cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_SM2);
            if (!cfg.has(CFG_KG_HASH)) {
                cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SM3);
            }
            break;
        }
        default:
            option = 0;
            break;
        }
    } while (!option);

    return true;
}

static bool
rnpkeys_ask_generate_params_subkey(rnp_cfg &cfg, FILE *input_fp)
{
    long option = 0;
    do {
        printf("Please select subkey algorithm you want:\n"
               "\t(1)  RSA\n"
               "\t(16) ElGamal\n"
               "\t(17) DSA\n"
               "\t(18) ECDH\n"
               "\t(19) ECDSA\n"
               "\t(22) EDDSA\n"
               "\t(99) SM2"
               "> ");
        if (!rnp_secure_get_long_from_fd(input_fp, option, false)) {
            option = 0;
            continue;
        }
        switch (option) {
        case 1: {
            int bits = ask_rsa_bitlen(input_fp);
            cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA);
            cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
            break;
        }
        case 16: {
            int bits = ask_elgamal_bitlen(input_fp);
            cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ELGAMAL);
            cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
            break;
        }
        case 17: {
            int bits = ask_dsa_bitlen(input_fp);
            cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_DSA);
            cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
            break;
        }
        case 18: {
            const char *curve = ask_curve_name(input_fp);
            if (!curve) {
                return false;
            }
            cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDH);
            cfg.set_str(CFG_KG_SUBKEY_CURVE, curve);
            break;
        }
        case 19: {
            const char *curve = ask_curve_name(input_fp);
            if (!curve) {
                return false;
            }
            cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDSA);
            cfg.set_str(CFG_KG_SUBKEY_CURVE, curve);
            break;
        }
        case 22: {
            cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_EDDSA);
            break;
        }
        case 99: {
            cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_SM2);
            if (!cfg.has(CFG_KG_HASH)) {
                cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SM3);
            }
            break;
        }
        default:
            option = 0;
            break;
        }
    } while (!option);

    return true;
}

bool
cli_rnp_set_generate_params(rnp_cfg &cfg, bool subkey)
{
    bool res = true;
    // hash algorithms for signing and protection
    if (cfg.has(CFG_HASH)) {
        cfg.set_str(CFG_KG_HASH, cfg.get_str(CFG_HASH));
        cfg.set_str(CFG_KG_PROT_HASH, cfg.get_str(CFG_HASH));
    }

    // key and subkey algorithms, bit length/curve
    if (!cfg.get_bool(CFG_EXPERT)) {
        cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_RSA);
        cfg.set_int(CFG_KG_PRIMARY_BITS, cfg.get_int(CFG_NUMBITS));
        cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA);
        cfg.set_int(CFG_KG_SUBKEY_BITS, cfg.get_int(CFG_NUMBITS));
    } else {
        FILE *input = stdin;
        if (cfg.has(CFG_USERINPUTFD)) {
            int inputfd = dup(cfg.get_int(CFG_USERINPUTFD));
            if (inputfd != -1) {
                input = rnp_fdopen(inputfd, "r");
                if (!input) {
                    close(inputfd);
                }
            }
        }
        if (!input) {
            return false;
        }
        if (subkey) {
            res = rnpkeys_ask_generate_params_subkey(cfg, input);
        } else {
            res = rnpkeys_ask_generate_params(cfg, input);
        }
        if (input != stdin) {
            fclose(input);
        }
        if (!res) {
            return false;
        }
    }

    // make sure hash algorithms are set
    if (!cfg.has(CFG_KG_HASH)) {
        cfg.set_str(CFG_KG_HASH, DEFAULT_HASH_ALG);
    }
    if (!cfg.has(CFG_KG_PROT_HASH)) {
        cfg.set_str(CFG_KG_PROT_HASH, DEFAULT_HASH_ALG);
    }

    // protection symmetric algorithm
    cfg.set_str(CFG_KG_PROT_ALG,
                cfg.has(CFG_CIPHER) ? cfg.get_str(CFG_CIPHER) : DEFAULT_SYMM_ALG);
    // protection iterations count
    size_t iterations = cfg.get_int(CFG_S2K_ITER);
    if (!iterations) {
        res = res && !rnp_calculate_iterations(cfg.get_str(CFG_KG_PROT_HASH).c_str(),
                                               cfg.get_int(CFG_S2K_MSEC),
                                               &iterations);
    }
    cfg.set_int(CFG_KG_PROT_ITERATIONS, iterations);
    return res;
}
