/*
 * Copyright (c) 2017-2023, [Ribose Inc](https://www.ribose.com).
 * Copyright (c) 2009 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is originally derived from software contributed to
 * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
 * carried further by Ribose Inc (https://www.ribose.com).
 *
 * 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 HOLDERS 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.
 */
/* Command line program to perform rnp operations */

#ifdef _MSC_VER
#include "uniwin.h"
#else
#include <getopt.h>
#endif
#include <string.h>
#include <stdarg.h>
#include "rnpkeys.h"
#include "str-utils.h"
#include <set>

const char *usage =
  "Manipulate OpenPGP keys and keyrings.\n"
  "Usage: rnpkeys --command [options] [files]\n"
  "Commands:\n"
  "  -h, --help             This help message.\n"
  "  -V, --version          Print RNP version information.\n"
  "  -g, --generate-key     Generate a new keypair (default is RSA).\n"
  "    --userid             Specify key's userid.\n"
  "    --expert             Select key type, size, and additional parameters.\n"
  "    --numbits            Override default key size (2048).\n"
  "    --expiration         Set key and subkey expiration time.\n"
  "    --cipher             Set cipher used to encrypt a secret key.\n"
  "    --hash               Set hash which is used for key derivation.\n"
  "    --allow-weak-hash    Allow usage of a weak hash algorithm.\n"
  "  -l, --list-keys        List keys in the keyrings.\n"
  "    --secret             List secret keys instead of public ones.\n"
  "    --with-sigs          List signatures as well.\n"
  "  --import               Import keys or signatures.\n"
  "  --import-keys          Import keys.\n"
  "  --import-sigs          Import signatures.\n"
  "    --permissive         Skip erroring keys/sigs instead of failing.\n"
  "  --export-key           Export a key.\n"
  "    --secret             Export a secret key instead of a public.\n"
  "  --export-rev           Export a key's revocation.\n"
  "    --rev-type           Set revocation type.\n"
  "    --rev-reason         Human-readable reason for revocation.\n"
  "  --revoke-key           Revoke a key specified.\n"
  "  --remove-key           Remove a key specified.\n"
  "  --edit-key             Edit key properties.\n"
  "    --add-subkey         Add new subkey.\n"
  "    --check-cv25519-bits Check whether Cv25519 subkey bits are correct.\n"
  "    --fix-cv25519-bits   Fix Cv25519 subkey bits.\n"
  "    --set-expire         Set key expiration time.\n"
  "\n"
  "Other options:\n"
  "  --homedir              Override home directory (default is ~/.rnp/).\n"
  "  --password             Password, which should be used during operation.\n"
  "  --pass-fd              Read password(s) from the file descriptor.\n"
  "  --force                Force operation (like secret key removal).\n"
  "  --output [file, -]     Write data to the specified file or stdout.\n"
  "  --overwrite            Overwrite output file without a prompt.\n"
  "  --notty                Do not write anything to the TTY.\n"
  "  --current-time         Override system's time.\n"
  "\n"
  "See man page for a detailed listing and explanation.\n"
  "\n";

struct option options[] = {
  /* key-management commands */
  {"list-keys", no_argument, NULL, CMD_LIST_KEYS},
  {"export", no_argument, NULL, CMD_EXPORT_KEY},
  {"export-key", optional_argument, NULL, CMD_EXPORT_KEY},
  {"import", no_argument, NULL, CMD_IMPORT},
  {"import-key", no_argument, NULL, CMD_IMPORT_KEYS},
  {"import-keys", no_argument, NULL, CMD_IMPORT_KEYS},
  {"import-sigs", no_argument, NULL, CMD_IMPORT_SIGS},
  {"gen", optional_argument, NULL, CMD_GENERATE_KEY},
  {"gen-key", optional_argument, NULL, CMD_GENERATE_KEY},
  {"generate", optional_argument, NULL, CMD_GENERATE_KEY},
  {"generate-key", optional_argument, NULL, CMD_GENERATE_KEY},
  {"export-rev", no_argument, NULL, CMD_EXPORT_REV},
  {"export-revocation", no_argument, NULL, CMD_EXPORT_REV},
  {"revoke-key", no_argument, NULL, CMD_REVOKE_KEY},
  {"remove-key", no_argument, NULL, CMD_REMOVE_KEY},
  {"edit-key", no_argument, NULL, CMD_EDIT_KEY},
  /* debugging commands */
  {"help", no_argument, NULL, CMD_HELP},
  {"version", no_argument, NULL, CMD_VERSION},
  {"debug", required_argument, NULL, OPT_DEBUG},
  /* options */
  {"coredumps", no_argument, NULL, OPT_COREDUMPS},
  {"keystore-format", required_argument, NULL, OPT_KEY_STORE_FORMAT},
  {"userid", required_argument, NULL, OPT_USERID},
  {"with-sigs", no_argument, NULL, OPT_WITH_SIGS},
  {"hash", required_argument, NULL, OPT_HASH_ALG},
  {"home", required_argument, NULL, OPT_HOMEDIR},
  {"homedir", required_argument, NULL, OPT_HOMEDIR},
  {"numbits", required_argument, NULL, OPT_NUMBITS},
  {"s2k-iterations", required_argument, NULL, OPT_S2K_ITER},
  {"s2k-msec", required_argument, NULL, OPT_S2K_MSEC},
  {"expiration", required_argument, NULL, OPT_EXPIRATION},
  {"pass-fd", required_argument, NULL, OPT_PASSWDFD},
  {"password", required_argument, NULL, OPT_PASSWD},
  {"results", required_argument, NULL, OPT_RESULTS},
  {"cipher", required_argument, NULL, OPT_CIPHER},
  {"expert", no_argument, NULL, OPT_EXPERT},
  {"output", required_argument, NULL, OPT_OUTPUT},
  {"overwrite", no_argument, NULL, OPT_OVERWRITE},
  {"force", no_argument, NULL, OPT_FORCE},
  {"secret", no_argument, NULL, OPT_SECRET},
  {"rev-type", required_argument, NULL, OPT_REV_TYPE},
  {"rev-reason", required_argument, NULL, OPT_REV_REASON},
  {"permissive", no_argument, NULL, OPT_PERMISSIVE},
  {"notty", no_argument, NULL, OPT_NOTTY},
  {"fix-cv25519-bits", no_argument, NULL, OPT_FIX_25519_BITS},
  {"check-cv25519-bits", no_argument, NULL, OPT_CHK_25519_BITS},
  {"add-subkey", no_argument, NULL, OPT_ADD_SUBKEY},
  {"set-expire", required_argument, NULL, OPT_SET_EXPIRE},
  {"current-time", required_argument, NULL, OPT_CURTIME},
  {"allow-weak-hash", no_argument, NULL, OPT_ALLOW_WEAK_HASH},
  {NULL, 0, NULL, 0},
};

/* list keys */
static bool
print_keys_info(cli_rnp_t *rnp, FILE *fp, const char *filter)
{
    bool psecret = rnp->cfg().get_bool(CFG_SECRET);
    bool psigs = rnp->cfg().get_bool(CFG_WITH_SIGS);
    int  flags = CLI_SEARCH_SUBKEYS_AFTER | (psecret ? CLI_SEARCH_SECRET : 0);
    std::vector<rnp_key_handle_t> keys;

    if (!cli_rnp_keys_matching_string(rnp, keys, filter ? filter : "", flags)) {
        fprintf(fp, "Key(s) not found.\n");
        return false;
    }
    fprintf(fp, "%d key%s found\n", (int) keys.size(), (keys.size() == 1) ? "" : "s");
    for (auto key : keys) {
        cli_rnp_print_key_info(fp, rnp->ffi, key, psecret, psigs);
    }

    fprintf(fp, "\n");
    /* clean up */
    clear_key_handles(keys);
    return true;
}

static bool
import_keys(cli_rnp_t *rnp, rnp_input_t input, const std::string &inname)
{
    std::set<std::string> new_pub_keys;
    std::set<std::string> new_sec_keys;
    std::set<std::string> updated_keys;
    bool                  res = false;
    bool                  updated = false;
    size_t                unchanged_keys = 0;
    size_t                processed_keys = 0;

    uint32_t flags = RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS |
                     RNP_LOAD_SAVE_SINGLE | RNP_LOAD_SAVE_BASE64;

    bool permissive = rnp->cfg().get_bool(CFG_PERMISSIVE);
    if (permissive) {
        flags |= RNP_LOAD_SAVE_PERMISSIVE;
    }

    do {
        /* load keys one-by-one */
        char *       results = NULL;
        rnp_result_t ret = rnp_import_keys(rnp->ffi, input, flags, &results);
        if (ret == RNP_ERROR_EOF) {
            res = true;
            break;
        }
        if (ret && updated) {
            /* some keys were imported, but then error occurred */
            ERR_MSG("warning: not all data was processed.");
            res = true;
            break;
        }
        if (ret) {
            ERR_MSG("failed to import key(s) from %s, stopping.", inname.c_str());
            break;
        }

        // print information about imported key(s)
        json_object *jso = json_tokener_parse(results);
        rnp_buffer_destroy(results);
        if (!jso) {
            ERR_MSG("invalid key import resulting JSON");
            break;
        }
        json_object *keys = NULL;
        if (!json_object_object_get_ex(jso, "keys", &keys)) {
            ERR_MSG("invalid key import JSON contents");
            json_object_put(jso);
            break;
        }
        processed_keys += json_object_array_length(keys);
        for (size_t idx = 0; idx < (size_t) json_object_array_length(keys); idx++) {
            json_object *    keyinfo = json_object_array_get_idx(keys, idx);
            rnp_key_handle_t key = NULL;
            if (!keyinfo) {
                continue;
            }
            std::string pub_status = json_obj_get_str(keyinfo, "public");
            std::string sec_status = json_obj_get_str(keyinfo, "secret");
            const char *fphex = json_obj_get_str(keyinfo, "fingerprint");

            if (pub_status == "new") {
                new_pub_keys.insert(fphex);
                updated = true;
            }
            if (sec_status == "new") {
                new_sec_keys.insert(fphex);
                updated = true;
            }
            if (pub_status == "updated" || sec_status == "updated") {
                updated_keys.insert(fphex);
                updated = true;
            }
            if (pub_status == "unchanged" || sec_status == "unchanged") {
                if (!new_pub_keys.count(fphex) && !new_sec_keys.count(fphex) &&
                    !updated_keys.count(fphex)) {
                    unchanged_keys++;
                    continue;
                }
            }
            if (rnp_locate_key(rnp->ffi, "fingerprint", fphex, &key) || !key) {
                ERR_MSG("failed to locate key with fingerprint %s", fphex);
                continue;
            }
            cli_rnp_print_key_info(stdout, rnp->ffi, key, true, false);
            rnp_key_handle_destroy(key);
        }
        json_object_put(jso);
    } while (1);

    // print statistics
    ERR_MSG("Import finished: %zu key%s processed, %zu new public keys, %zu new secret keys, "
            "%zu updated, %zu unchanged.",
            processed_keys,
            (processed_keys != 1) ? "s" : "",
            new_pub_keys.size(),
            new_sec_keys.size(),
            updated_keys.size(),
            unchanged_keys);

    if (updated) {
        // set default key if we didn't have one
        if (rnp->defkey().empty()) {
            rnp->set_defkey();
        }

        // save public and secret keyrings
        if (!cli_rnp_save_keyrings(rnp)) {
            ERR_MSG("failed to save keyrings");
        }
    }
    return res;
}

static bool
import_sigs(cli_rnp_t *rnp, rnp_input_t input, const std::string &inname)
{
    bool         res = false;
    char *       results = NULL;
    json_object *jso = NULL;
    json_object *sigs = NULL;
    int          unknown_sigs = 0;
    int          new_sigs = 0;
    int          old_sigs = 0;

    if (rnp_import_signatures(rnp->ffi, input, 0, &results)) {
        ERR_MSG("Failed to import signatures from %s", inname.c_str());
        goto done;
    }
    // print information about imported signature(s)
    jso = json_tokener_parse(results);
    if (!jso || !json_object_object_get_ex(jso, "sigs", &sigs)) {
        ERR_MSG("Invalid signature import result");
        goto done;
    }

    for (size_t idx = 0; idx < (size_t) json_object_array_length(sigs); idx++) {
        json_object *siginfo = json_object_array_get_idx(sigs, idx);
        if (!siginfo) {
            continue;
        }
        const char *status = json_obj_get_str(siginfo, "public");
        std::string pub_status = status ? status : "unknown";
        status = json_obj_get_str(siginfo, "secret");
        std::string sec_status = status ? status : "unknown";

        if ((pub_status == "new") || (sec_status == "new")) {
            new_sigs++;
        } else if ((pub_status == "unchanged") || (sec_status == "unchanged")) {
            old_sigs++;
        } else {
            unknown_sigs++;
        }
    }

    // print status information
    ERR_MSG("Import finished: %d new signature%s, %d unchanged, %d unknown.",
            new_sigs,
            (new_sigs != 1) ? "s" : "",
            old_sigs,
            unknown_sigs);

    // save public and secret keyrings
    if ((new_sigs > 0) && !cli_rnp_save_keyrings(rnp)) {
        ERR_MSG("Failed to save keyrings");
        goto done;
    }
    res = true;
done:
    json_object_put(jso);
    rnp_buffer_destroy(results);
    return res;
}

static bool
import(cli_rnp_t *rnp, const std::string &spec, int cmd)
{
    if (spec.empty()) {
        ERR_MSG("Import path isn't specified");
        return false;
    }
    rnp_input_t input = cli_rnp_input_from_specifier(*rnp, spec, NULL);
    if (!input) {
        ERR_MSG("Failed to create input for %s", spec.c_str());
        return false;
    }
    if (cmd == CMD_IMPORT) {
        char *contents = NULL;
        if (rnp_guess_contents(input, &contents)) {
            ERR_MSG("Warning! Failed to guess content type to import. Assuming keys.");
        }
        cmd = (contents && !strcmp(contents, "signature")) ? CMD_IMPORT_SIGS : CMD_IMPORT_KEYS;
        rnp_buffer_destroy(contents);
    }

    bool res = false;
    switch (cmd) {
    case CMD_IMPORT_KEYS:
        res = import_keys(rnp, input, spec);
        break;
    case CMD_IMPORT_SIGS:
        res = import_sigs(rnp, input, spec);
        break;
    default:
        ERR_MSG("Unexpected command: %d", cmd);
    }
    rnp_input_destroy(input);
    return res;
}

/* print a usage message */
void
print_usage(const char *usagemsg)
{
    cli_rnp_print_praise();
    puts(usagemsg);
}

/* do a command once for a specified file 'f' */
bool
rnp_cmd(cli_rnp_t *rnp, optdefs_t cmd, const char *f)
{
    std::string fs;

    switch (cmd) {
    case CMD_LIST_KEYS:
        if (!f && rnp->cfg().get_count(CFG_USERID)) {
            fs = rnp->cfg().get_str(CFG_USERID, 0);
            f = fs.c_str();
        }
        return print_keys_info(rnp, stdout, f);
    case CMD_EXPORT_KEY: {
        if (!f && rnp->cfg().get_count(CFG_USERID)) {
            fs = rnp->cfg().get_str(CFG_USERID, 0);
            f = fs.c_str();
        }
        if (!f) {
            ERR_MSG("No key specified.");
            return 0;
        }
        return cli_rnp_export_keys(rnp, f);
    }
    case CMD_IMPORT:
    case CMD_IMPORT_KEYS:
    case CMD_IMPORT_SIGS:
        return import(rnp, f ? f : "", cmd);
    case CMD_GENERATE_KEY: {
        if (!f) {
            size_t count = rnp->cfg().get_count(CFG_USERID);
            if (count == 1) {
                fs = rnp->cfg().get_str(CFG_USERID, 0);
                f = fs.c_str();
            } else if (count > 1) {
                ERR_MSG("Only single userid is supported for generated keys");
                return false;
            }
        }
        return cli_rnp_generate_key(rnp, f);
    }
    case CMD_EXPORT_REV: {
        if (!f) {
            ERR_MSG("You need to specify key to generate revocation for.");
            return false;
        }
        return cli_rnp_export_revocation(rnp, f);
    }
    case CMD_REVOKE_KEY: {
        if (!f) {
            ERR_MSG("You need to specify key or subkey to revoke.");
            return false;
        }
        return cli_rnp_revoke_key(rnp, f);
    }
    case CMD_REMOVE_KEY: {
        if (!f) {
            ERR_MSG("You need to specify key or subkey to remove.");
            return false;
        }
        return cli_rnp_remove_key(rnp, f);
    }
    case CMD_EDIT_KEY: {
        if (!f) {
            ERR_MSG("You need to specify a key or subkey to edit.");
            return false;
        }
        return rnp->edit_key(f);
    }
    case CMD_VERSION:
        cli_rnp_print_praise();
        return true;
    case CMD_HELP:
    default:
        print_usage(usage);
        return true;
    }
}

/* set the option */
bool
setoption(rnp_cfg &cfg, optdefs_t *cmd, int val, const char *arg)
{
    switch (val) {
    case OPT_COREDUMPS:
#ifdef _WIN32
        ERR_MSG("warning: --coredumps doesn't make sense on windows systems.");
#endif
        cfg.set_bool(CFG_COREDUMPS, true);
        return true;
    case CMD_GENERATE_KEY:
        cfg.set_bool(CFG_NEEDSSECKEY, true);
        *cmd = (optdefs_t) val;
        return true;
    case OPT_EXPERT:
        cfg.set_bool(CFG_EXPERT, true);
        return true;
    case CMD_LIST_KEYS:
    case CMD_EXPORT_KEY:
    case CMD_EXPORT_REV:
    case CMD_REVOKE_KEY:
    case CMD_REMOVE_KEY:
    case CMD_EDIT_KEY:
    case CMD_IMPORT:
    case CMD_IMPORT_KEYS:
    case CMD_IMPORT_SIGS:
    case CMD_HELP:
    case CMD_VERSION:
        *cmd = (optdefs_t) val;
        return true;
    /* options */
    case OPT_KEY_STORE_FORMAT:
        cfg.set_str(CFG_KEYSTOREFMT, arg);
        return true;
    case OPT_USERID:
        cfg.add_str(CFG_USERID, arg);
        return true;
    case OPT_HOMEDIR:
        cfg.set_str(CFG_HOMEDIR, arg);
        return true;
    case OPT_NUMBITS: {
        int bits = 0;
        if (!rnp::str_to_int(arg, bits) || (bits < 1024) || (bits > 16384)) {
            ERR_MSG("wrong bits value: %s", arg);
            return false;
        }
        cfg.set_int(CFG_NUMBITS, bits);
        return true;
    }
    case OPT_ALLOW_WEAK_HASH:
        cfg.set_bool(CFG_WEAK_HASH, true);
        return true;
    case OPT_HASH_ALG:
        return cli_rnp_set_hash(cfg, arg);
    case OPT_S2K_ITER: {
        int iterations = atoi(arg);
        if (!iterations) {
            ERR_MSG("Wrong iterations value: %s", arg);
            return false;
        }
        cfg.set_int(CFG_S2K_ITER, iterations);
        return true;
    }
    case OPT_EXPIRATION:
        cfg.set_str(CFG_KG_PRIMARY_EXPIRATION, arg);
        cfg.set_str(CFG_KG_SUBKEY_EXPIRATION, arg);
        return true;
    case OPT_S2K_MSEC: {
        int msec = 0;
        if (!rnp::str_to_int(arg, msec) || !msec) {
            ERR_MSG("Invalid s2k msec value: %s", arg);
            return false;
        }
        cfg.set_int(CFG_S2K_MSEC, msec);
        return true;
    }
    case OPT_PASSWDFD:
        cfg.set_str(CFG_PASSFD, arg);
        return true;
    case OPT_PASSWD:
        cfg.set_str(CFG_PASSWD, arg);
        return true;
    case OPT_RESULTS:
        cfg.set_str(CFG_IO_RESS, arg);
        return true;
    case OPT_CIPHER:
        return cli_rnp_set_cipher(cfg, arg);
    case OPT_DEBUG:
        ERR_MSG("Option --debug is deprecated, ignoring.");
        return true;
    case OPT_OUTPUT:
        if (!arg) {
            ERR_MSG("No output filename argument provided");
            return false;
        }
        cfg.set_str(CFG_OUTFILE, arg);
        return true;
    case OPT_OVERWRITE:
        cfg.set_bool(CFG_OVERWRITE, true);
        return true;
    case OPT_FORCE:
        cfg.set_bool(CFG_FORCE, true);
        return true;
    case OPT_SECRET:
        cfg.set_bool(CFG_SECRET, true);
        return true;
    case OPT_WITH_SIGS:
        cfg.set_bool(CFG_WITH_SIGS, true);
        return true;
    case OPT_REV_TYPE: {
        std::string revtype = arg;
        if (revtype == "0") {
            revtype = "no";
        } else if (revtype == "1") {
            revtype = "superseded";
        } else if (revtype == "2") {
            revtype = "compromised";
        } else if (revtype == "3") {
            revtype = "retired";
        }
        cfg.set_str(CFG_REV_TYPE, revtype);
        return true;
    }
    case OPT_REV_REASON:
        cfg.set_str(CFG_REV_REASON, arg);
        return true;
    case OPT_PERMISSIVE:
        cfg.set_bool(CFG_PERMISSIVE, true);
        return true;
    case OPT_NOTTY:
        cfg.set_bool(CFG_NOTTY, true);
        return true;
    case OPT_FIX_25519_BITS:
        cfg.set_bool(CFG_FIX_25519_BITS, true);
        return true;
    case OPT_CHK_25519_BITS:
        cfg.set_bool(CFG_CHK_25519_BITS, true);
        return true;
    case OPT_CURTIME:
        cfg.set_str(CFG_CURTIME, arg);
        return true;
    case OPT_ADD_SUBKEY:
        cfg.set_bool(CFG_ADD_SUBKEY, true);
        return true;
    case OPT_SET_EXPIRE:
        cfg.set_str(CFG_SET_KEY_EXPIRE, arg);
        return true;
    default:
        *cmd = CMD_HELP;
        return true;
    }
}

bool
rnpkeys_init(cli_rnp_t &rnp, const rnp_cfg &cfg)
{
    rnp_cfg rnpcfg;
    rnpcfg.load_defaults();
    rnpcfg.set_int(CFG_NUMBITS, DEFAULT_RSA_NUMBITS);
    rnpcfg.set_str(CFG_IO_RESS, "<stdout>");
    rnpcfg.copy(cfg);

    if (!cli_cfg_set_keystore_info(rnpcfg)) {
        ERR_MSG("fatal: cannot set keystore info");
        return false;
    }
    if (!rnp.init(rnpcfg)) {
        ERR_MSG("fatal: failed to initialize rnpkeys");
        return false;
    }
    if (!cli_rnp_check_weak_hash(&rnp)) {
        ERR_MSG("Weak hash algorithm detected. Pass --allow-weak-hash option if you really "
                "want to use it.");
        return false;
    }
    /* TODO: at some point we should check for error here */
    (void) rnp.load_keyrings(true);
    return true;
}
