summaryrefslogtreecommitdiffstats
path: root/src/rnpkeys/tui.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rnpkeys/tui.cpp')
-rw-r--r--src/rnpkeys/tui.cpp413
1 files changed, 413 insertions, 0 deletions
diff --git a/src/rnpkeys/tui.cpp b/src/rnpkeys/tui.cpp
new file mode 100644
index 0000000..73f26dc
--- /dev/null
+++ b/src/rnpkeys/tui.cpp
@@ -0,0 +1,413 @@
+/*
+ * 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;
+}