diff --git a/src/rnp/fficli.cpp b/src/rnp/fficli.cpp index 62ecd70127..d00afc043a 100644 --- a/src/rnp/fficli.cpp +++ b/src/rnp/fficli.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2021, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2019-2021, 2023 [Ribose Inc](https://www.ribose.com). * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, @@ -783,241 +783,182 @@ cli_rnp_t::load_keyrings(bool loadsecret) void cli_rnp_t::set_defkey() { - rnp_identifier_iterator_t it = NULL; - rnp_key_handle_t handle = NULL; - const char * grip = NULL; - cfg_.unset(CFG_KR_DEF_KEY); - if (rnp_identifier_iterator_create(ffi, &it, "grip")) { + + rnpffi::FFI ffiobj(ffi, false); + auto it = ffiobj.iterator_create("fingerprint"); + if (!it) { ERR_MSG("failed to create key iterator"); return; } - while (!rnp_identifier_iterator_next(it, &grip)) { - bool is_subkey = false; - bool is_secret = false; - - if (!grip) { - break; - } - if (rnp_locate_key(ffi, "grip", grip, &handle)) { - ERR_MSG("failed to locate key"); + std::string fp; + while (it->next(fp)) { + auto key = ffiobj.locate_key("fingerprint", fp); + if (!key) { + ERR_MSG("failed to locate key %s", fp.c_str()); continue; } - if (rnp_key_is_sub(handle, &is_subkey) || is_subkey) { - goto next; - } - if (rnp_key_have_secret(handle, &is_secret)) { - goto next; + if (!key->is_primary()) { + continue; } + bool is_secret = key->secret(); if (!cfg_.has(CFG_KR_DEF_KEY) || is_secret) { - cfg_.set_str(CFG_KR_DEF_KEY, grip); + cfg_.set_str(CFG_KR_DEF_KEY, fp); /* if we have secret primary key then use it as default */ if (is_secret) { - break; + return; } } - next: - rnp_key_handle_destroy(handle); - handle = NULL; } - rnp_key_handle_destroy(handle); - rnp_identifier_iterator_destroy(it); } bool -cli_rnp_t::is_cv25519_subkey(rnp_key_handle_t handle) +cli_rnp_t::is_cv25519_subkey(rnpffi::Key &key) { - bool primary = false; - if (rnp_key_is_primary(handle, &primary)) { - ERR_MSG("Error: failed to check for subkey."); - return false; - } - if (primary) { - return false; - } - char *alg = NULL; - if (rnp_key_get_alg(handle, &alg)) { - ERR_MSG("Error: failed to check key's alg."); - return false; - } - bool ecdh = !strcmp(alg, RNP_ALGNAME_ECDH); - rnp_buffer_destroy(alg); - if (!ecdh) { - return false; - } - char *curve = NULL; - if (rnp_key_get_curve(handle, &curve)) { - ERR_MSG("Error: failed to check key's curve."); + try { + if (key.is_primary()) { + return false; + } + if (strcmp(key.alg().c_str(), RNP_ALGNAME_ECDH)) { + return false; + } + return !strcmp(key.curve().c_str(), "Curve25519"); + } catch (const rnpffi::ffi_exception &e) { + ERR_MSG("FFI call error: %s.", e.func()); return false; } - bool cv25519 = !strcmp(curve, "Curve25519"); - rnp_buffer_destroy(curve); - return cv25519; } bool -cli_rnp_t::get_protection(rnp_key_handle_t handle, - std::string & hash, - std::string & cipher, - size_t & iterations) +cli_rnp_t::get_protection(rnpffi::Key &key, + std::string &hash, + std::string &cipher, + size_t & iterations) { - bool prot = false; - if (rnp_key_is_protected(handle, &prot)) { - ERR_MSG("Error: failed to check key's protection."); - return false; - } - if (!prot) { - hash = ""; - cipher = ""; - iterations = 0; - return true; - } - - char *val = NULL; try { - if (rnp_key_get_protection_hash(handle, &val)) { - ERR_MSG("Error: failed to retrieve key's protection hash."); - return false; - } - hash = val; - rnp_buffer_destroy(val); - if (rnp_key_get_protection_cipher(handle, &val)) { - ERR_MSG("Error: failed to retrieve key's protection cipher."); - return false; - } - cipher = val; - rnp_buffer_destroy(val); - if (rnp_key_get_protection_iterations(handle, &iterations)) { - ERR_MSG("Error: failed to retrieve key's protection iterations."); - return false; + if (!key.is_protected()) { + hash = ""; + cipher = ""; + iterations = 0; + return true; } + hash = key.protection_hash(); + cipher = key.protection_cipher(); + iterations = key.protection_iterations(); return true; - } catch (const std::exception &e) { - ERR_MSG("Error: failed to retrieve key's properties: %s", e.what()); - rnp_buffer_destroy(val); + } catch (const rnpffi::ffi_exception &e) { + ERR_MSG("FFI call error: %s", e.func()); return false; } } bool -cli_rnp_t::check_cv25519_bits(rnp_key_handle_t key, char *prot_password, bool &tweaked) +cli_rnp_t::check_cv25519_bits(rnpffi::Key &key, rnpffi::String &prot_password, bool &tweaked) { /* unlock key first to check whether bits are tweaked */ - if (prot_password && rnp_key_unlock(key, prot_password)) { + if (prot_password.c_str() && !key.unlock(prot_password.str())) { ERR_MSG("Error: failed to unlock key. Did you specify valid password?"); return false; } - rnp_result_t ret = rnp_key_25519_bits_tweaked(key, &tweaked); - if (ret) { + bool res = false; + try { + tweaked = key.is_25519_bits_tweaked(); + res = true; + } catch (...) { ERR_MSG("Error: failed to check whether key's bits are tweaked."); } - if (prot_password) { - rnp_key_lock(key); + if (prot_password.c_str()) { + key.lock(); } - return !ret; + return res; } bool -cli_rnp_t::fix_cv25519_subkey(const std::string &key, bool checkonly) +cli_rnp_t::fix_cv25519_subkey(const std::string &str, bool checkonly) { - std::vector keys; - if (!cli_rnp_keys_matching_string( - this, keys, key, CLI_SEARCH_SECRET | CLI_SEARCH_SUBKEYS)) { - ERR_MSG("Secret keys matching '%s' not found.", key.c_str()); + size_t keys = 0; + auto key = key_matching(str, CLI_SEARCH_SECRET | CLI_SEARCH_SUBKEYS, &keys); + if (!keys) { + ERR_MSG("Secret keys matching '%s' not found.", str.c_str()); return false; } - bool res = false; - std::string prot_hash; - std::string prot_cipher; - size_t prot_iterations; - char * prot_password = NULL; - bool tweaked = false; - - if (keys.size() > 1) { + if (keys > 1) { ERR_MSG( "Ambiguous input: too many keys found for '%s'. Did you use keyid or fingerprint?", - key.c_str()); - goto done; + str.c_str()); + return false; } - cli_rnp_print_key_info(userio_out, ffi, keys[0], true, false); - if (!is_cv25519_subkey(keys[0])) { + cli_rnp_print_key_info(userio_out, ffi, key->handle(), true, false); + if (!is_cv25519_subkey(*key)) { ERR_MSG("Error: specified key is not Curve25519 ECDH subkey."); - goto done; + return false; } - if (!get_protection(keys[0], prot_hash, prot_cipher, prot_iterations)) { - goto done; + std::string prot_hash; + std::string prot_cipher; + size_t prot_iterations; + if (!get_protection(*key, prot_hash, prot_cipher, prot_iterations)) { + return false; } - if (!prot_hash.empty() && - (rnp_request_password(ffi, keys[0], "unprotect", &prot_password) || !prot_password)) { + rnpffi::String prot_password(true); + rnpffi::FFI ffiobj(ffi, false); + if (!prot_hash.empty() && (!ffiobj.request_password(*key, "unprotect", prot_password) || + !prot_password.c_str())) { ERR_MSG("Error: failed to obtain protection password."); - goto done; + return false; } - if (!check_cv25519_bits(keys[0], prot_password, tweaked)) { - goto done; + bool tweaked = false; + if (!check_cv25519_bits(*key, prot_password, tweaked)) { + return false; } if (checkonly) { fprintf(userio_out, tweaked ? "Cv25519 key bits are set correctly and do not require fixing.\n" : "Warning: Cv25519 key bits need fixing.\n"); - res = tweaked; - goto done; + return tweaked; } if (tweaked) { ERR_MSG("Warning: key's bits are fixed already, no action is required."); - res = true; - goto done; + return true; } /* now unprotect so we can tweak bits */ if (!prot_hash.empty()) { - if (rnp_key_unprotect(keys[0], prot_password)) { + if (!key->unprotect(prot_password.str())) { ERR_MSG("Error: failed to unprotect key. Did you specify valid password?"); - goto done; + return false; } - if (rnp_key_unlock(keys[0], NULL)) { + if (!key->unlock()) { ERR_MSG("Error: failed to unlock key."); - goto done; + return false; } } /* tweak key bits and protect back */ - if (rnp_key_25519_bits_tweak(keys[0])) { + if (!key->do_25519_bits_tweak()) { ERR_MSG("Error: failed to tweak key's bits."); - goto done; + return false; } - if (!prot_hash.empty() && rnp_key_protect(keys[0], - prot_password, - prot_cipher.c_str(), - NULL, - prot_hash.c_str(), - prot_iterations)) { + if (!prot_hash.empty() && + !key->protect(prot_password.str(), prot_cipher, prot_hash, prot_iterations)) { ERR_MSG("Error: failed to protect key back."); - goto done; + return false; } - res = cli_rnp_save_keyrings(this); -done: - clear_key_handles(keys); - if (prot_password) { - rnp_buffer_clear(prot_password, strlen(prot_password) + 1); - rnp_buffer_destroy(prot_password); - } - return res; + return cli_rnp_save_keyrings(this); } bool cli_rnp_t::set_key_expire(const std::string &key) { std::vector keys; - if (!cli_rnp_keys_matching_string( - this, keys, key, CLI_SEARCH_SECRET | CLI_SEARCH_SUBKEYS)) { + if (!keys_matching(keys, key, CLI_SEARCH_SECRET | CLI_SEARCH_SUBKEYS)) { ERR_MSG("Secret keys matching '%s' not found.", key.c_str()); return false; } @@ -1050,7 +991,7 @@ cli_rnp_t::add_new_subkey(const std::string &key) return false; } std::vector keys; - if (!cli_rnp_keys_matching_string(this, keys, key, CLI_SEARCH_SECRET)) { + if (!keys_matching(keys, key, CLI_SEARCH_SECRET)) { ERR_MSG("Secret keys matching '%s' not found.", key.c_str()); return false; } @@ -1737,22 +1678,10 @@ cli_rnp_generate_key(cli_rnp_t *rnp, const char *username) } static bool -key_matches_string(rnp_key_handle_t handle, const std::string &str) +key_matches_string(rnpffi::Key &key, const std::string &str) { - bool matches = false; - char * id = NULL; - size_t idlen = 0; -#ifndef RNP_USE_STD_REGEX - regex_t r = {}; -#else - std::regex re; -#endif - size_t uid_count = 0; - bool boolres = false; - if (str.empty()) { - matches = true; - goto done; + return true; } if (rnp::is_hex(str) && (str.length() >= RNP_KEYID_SIZE)) { std::string hexstr = rnp::strip_hex(str); @@ -1760,131 +1689,87 @@ key_matches_string(rnp_key_handle_t handle, const std::string &str) /* check whether it's key id */ if ((len == RNP_KEYID_SIZE * 2) || (len == RNP_KEYID_SIZE)) { - if (rnp_key_get_keyid(handle, &id)) { - goto done; - } - if ((idlen = strlen(id)) < len) { - goto done; + auto keyid = key.keyid(); + if (keyid.size() < len) { + return false; } - if (strncasecmp(hexstr.c_str(), id + idlen - len, len) == 0) { - matches = true; - goto done; + if (!strncasecmp(hexstr.c_str(), keyid.c_str() + keyid.size() - len, len)) { + return true; } - rnp_buffer_destroy(id); - id = NULL; } /* check fingerprint */ - size_t fp_size = RNP_FP_V4_SIZE; -#if defined(ENABLE_CRYPTO_REFRESH) - uint32_t key_version; - rnp_key_get_version(handle, &key_version); - - if (key_version == RNP_PGP_VER_6) { - fp_size = RNP_FP_V6_SIZE; - } -#endif - if (len == fp_size * 2) { - if (rnp_key_get_fprint(handle, &id)) { - goto done; - } - if (strlen(id) != len) { - goto done; - } - if (strncasecmp(hexstr.c_str(), id, len) == 0) { - matches = true; - goto done; - } - rnp_buffer_destroy(id); - id = NULL; + auto keyfp = key.fprint(); + if ((len == keyfp.size()) && !strncasecmp(hexstr.c_str(), keyfp.c_str(), len)) { + return true; } /* check grip */ - if (len == RNP_GRIP_SIZE * 2) { - if (rnp_key_get_grip(handle, &id)) { - goto done; - } - if (strlen(id) != len) { - goto done; - } - if (strncasecmp(hexstr.c_str(), id, len) == 0) { - matches = true; - goto done; + auto grip = key.grip(); + if (len == grip.size()) { + if (!strncasecmp(hexstr.c_str(), grip.c_str(), len)) { + return true; } - rnp_buffer_destroy(id); - id = NULL; } /* let then search for hex userid */ } /* no need to check for userid over the subkey */ - if (rnp_key_is_sub(handle, &boolres) || boolres) { - goto done; + if (key.is_sub()) { + return false; } - if (rnp_key_get_uid_count(handle, &uid_count) || (uid_count == 0)) { - goto done; + auto uid_count = key.uid_count(); + if (!uid_count) { + return false; } #ifndef RNP_USE_STD_REGEX + regex_t r = {}; /* match on full name or email address as a NOSUB, ICASE regexp */ if (regcomp(&r, cli_rnp_unescape_for_regcomp(str).c_str(), REG_EXTENDED | REG_ICASE) != 0) { - goto done; + return false; } #else + std::regex re; try { re.assign(str, std::regex_constants::ECMAScript | std::regex_constants::icase); } catch (const std::exception &e) { ERR_MSG("Invalid regular expression : %s, error %s.", str.c_str(), e.what()); - goto done; + return false; } #endif + bool matches = false; for (size_t idx = 0; idx < uid_count; idx++) { - if (rnp_key_get_uid_at(handle, idx, &id)) { - goto regdone; - } + auto uid = key.uid_at(idx); #ifndef RNP_USE_STD_REGEX - if (regexec(&r, id, 0, NULL, 0) == 0) { + if (regexec(&r, uid.c_str(), 0, NULL, 0) == 0) { matches = true; - goto regdone; + break; } #else - if (std::regex_search(id, re)) { + if (std::regex_search(uid, re)) { matches = true; - goto regdone; + break; } #endif - rnp_buffer_destroy(id); - id = NULL; } - -regdone: #ifndef RNP_USE_STD_REGEX regfree(&r); #endif -done: - rnp_buffer_destroy(id); return matches; } static bool -key_matches_flags(rnp_key_handle_t key, int flags) +key_matches_flags(rnpffi::Key &key, int flags) { /* check whether secret key search is requested */ - bool secret = false; - if (rnp_key_have_secret(key, &secret)) { - return false; - } - if ((flags & CLI_SEARCH_SECRET) && !secret) { + if ((flags & CLI_SEARCH_SECRET) && !key.secret()) { return false; } /* check whether no subkeys allowed */ - bool subkey = false; - if (rnp_key_is_sub(key, &subkey)) { - return false; - } - if (!subkey) { + if (!key.is_sub()) { return true; } if (!(flags & CLI_SEARCH_SUBKEYS)) { @@ -1895,15 +1780,7 @@ key_matches_flags(rnp_key_handle_t key, int flags) return true; } - char *grip = NULL; - if (rnp_key_get_primary_grip(key, &grip)) { - return false; - } - if (!grip) { - return true; - } - rnp_buffer_destroy(grip); - return false; + return key.primary_grip().empty(); } void @@ -1964,80 +1841,85 @@ add_key_to_array(rnp_ffi_t ffi, } bool -cli_rnp_keys_matching_string(cli_rnp_t * rnp, - std::vector &keys, - const std::string & str, - int flags) +cli_rnp_t::keys_matching(std::vector &keys, + const std::string & str, + int flags) { - bool res = false; - rnp_identifier_iterator_t it = NULL; - rnp_key_handle_t handle = NULL; - const char * fp = NULL; + rnpffi::FFI ffiobj(ffi, false); /* iterate through the keys */ - if (rnp_identifier_iterator_create(rnp->ffi, &it, "fingerprint")) { + auto it = ffiobj.iterator_create("fingerprint"); + if (!it) { return false; } - while (!rnp_identifier_iterator_next(it, &fp)) { - if (!fp) { - break; - } - if (rnp_locate_key(rnp->ffi, "fingerprint", fp, &handle) || !handle) { - goto done; + std::string fp; + while (it->next(fp)) { + auto key = ffiobj.locate_key("fingerprint", fp); + if (!key) { + continue; } - if (!key_matches_flags(handle, flags) || !key_matches_string(handle, str)) { - rnp_key_handle_destroy(handle); + if (!key_matches_flags(*key, flags) || !key_matches_string(*key, str)) { continue; } - if (!add_key_to_array(rnp->ffi, keys, handle, flags)) { - rnp_key_handle_destroy(handle); - goto done; + if (!add_key_to_array(ffi, keys, key->handle(), flags)) { + return false; } + key->release(); if (flags & CLI_SEARCH_FIRST_ONLY) { - res = true; - goto done; + return true; } } - res = !keys.empty(); -done: - rnp_identifier_iterator_destroy(it); - return res; + return !keys.empty(); } bool -cli_rnp_keys_matching_strings(cli_rnp_t * rnp, - std::vector & keys, - const std::vector &strs, - int flags) +cli_rnp_t::keys_matching(std::vector & keys, + const std::vector &strs, + int flags) { - bool res = false; clear_key_handles(keys); for (const std::string &str : strs) { - if (!cli_rnp_keys_matching_string(rnp, keys, str, flags & ~CLI_SEARCH_DEFAULT)) { + if (!keys_matching(keys, str, flags & ~CLI_SEARCH_DEFAULT)) { ERR_MSG("Cannot find key matching \"%s\"", str.c_str()); - goto done; + clear_key_handles(keys); + return false; } } /* search for default key */ if (keys.empty() && (flags & CLI_SEARCH_DEFAULT)) { - if (rnp->defkey().empty()) { + if (defkey().empty()) { ERR_MSG("No userid or default key for operation"); - goto done; + return false; } - cli_rnp_keys_matching_string(rnp, keys, rnp->defkey(), flags & ~CLI_SEARCH_DEFAULT); + keys_matching(keys, defkey(), flags & ~CLI_SEARCH_DEFAULT); if (keys.empty()) { ERR_MSG("Default key not found"); } } - res = !keys.empty(); -done: - if (!res) { - clear_key_handles(keys); + return !keys.empty(); +} + +std::unique_ptr +cli_rnp_t::key_matching(const std::string &str, int flags, size_t *count) +{ + std::vector keys; + + keys_matching(keys, str, flags); + if (count) { + *count = keys.size(); } - return res; + if (keys.size() == 1) { + auto res = new (std::nothrow) rnpffi::Key(keys[0]); + if (!res) { + rnp_key_handle_destroy(keys[0]); + } + return std::unique_ptr(res); + } + clear_key_handles(keys); + return std::unique_ptr(nullptr); } static bool @@ -2243,7 +2125,7 @@ cli_rnp_export_keys(cli_rnp_t *rnp, const char *filter) int flags = secret ? CLI_SEARCH_SECRET : 0; std::vector keys; - if (!cli_rnp_keys_matching_string(rnp, keys, filter ? filter : std::string(), flags)) { + if (!rnp->keys_matching(keys, filter ? filter : std::string(), flags)) { ERR_MSG("Key(s) matching '%s' not found.", filter); return false; } @@ -2290,7 +2172,7 @@ bool cli_rnp_export_revocation(cli_rnp_t *rnp, const char *key) { std::vector keys; - if (!cli_rnp_keys_matching_string(rnp, keys, key, 0)) { + if (!rnp->keys_matching(keys, key, 0)) { ERR_MSG("Key matching '%s' not found.", key); return false; } @@ -2323,7 +2205,7 @@ bool cli_rnp_revoke_key(cli_rnp_t *rnp, const char *key) { std::vector keys; - if (!cli_rnp_keys_matching_string(rnp, keys, key, CLI_SEARCH_SUBKEYS)) { + if (!rnp->keys_matching(keys, key, CLI_SEARCH_SUBKEYS)) { ERR_MSG("Key matching '%s' not found.", key); return false; } @@ -2371,7 +2253,7 @@ cli_rnp_revoke_key(cli_rnp_t *rnp, const char *key) goto done; } clear_key_handles(keys); - if (!cli_rnp_keys_matching_string(rnp, keys, grip, CLI_SEARCH_SUBKEYS_AFTER)) { + if (!rnp->keys_matching(keys, grip, CLI_SEARCH_SUBKEYS_AFTER)) { ERR_MSG("Failed to search for revoked key."); rnp_buffer_destroy(grip); goto done; @@ -2390,7 +2272,7 @@ bool cli_rnp_remove_key(cli_rnp_t *rnp, const char *key) { std::vector keys; - if (!cli_rnp_keys_matching_string(rnp, keys, key, CLI_SEARCH_SUBKEYS)) { + if (!rnp->keys_matching(keys, key, CLI_SEARCH_SUBKEYS)) { ERR_MSG("Key matching '%s' not found.", key); return false; } @@ -2728,11 +2610,10 @@ cli_rnp_sign(const rnp_cfg &cfg, cli_rnp_t *rnp, rnp_input_t input, rnp_output_t /* signing keys */ signers = cfg.get_list(CFG_SIGNERS); - if (!cli_rnp_keys_matching_strings(rnp, - signkeys, - signers, - CLI_SEARCH_SECRET | CLI_SEARCH_DEFAULT | - CLI_SEARCH_SUBKEYS | CLI_SEARCH_FIRST_ONLY)) { + if (!rnp->keys_matching(signkeys, + signers, + CLI_SEARCH_SECRET | CLI_SEARCH_DEFAULT | CLI_SEARCH_SUBKEYS | + CLI_SEARCH_FIRST_ONLY)) { ERR_MSG("Failed to build signing keys list"); goto done; } @@ -2832,11 +2713,10 @@ cli_rnp_encrypt_and_sign(const rnp_cfg &cfg, /* adding encrypting keys if pk-encryption is used */ if (cfg.get_bool(CFG_ENCRYPT_PK)) { std::vector keynames = cfg.get_list(CFG_RECIPIENTS); - if (!cli_rnp_keys_matching_strings(rnp, - enckeys, - keynames, - CLI_SEARCH_DEFAULT | CLI_SEARCH_SUBKEYS | - CLI_SEARCH_FIRST_ONLY)) { + if (!rnp->keys_matching(enckeys, + keynames, + CLI_SEARCH_DEFAULT | CLI_SEARCH_SUBKEYS | + CLI_SEARCH_FIRST_ONLY)) { ERR_MSG("Failed to build recipients key list"); goto done; } @@ -2865,11 +2745,10 @@ cli_rnp_encrypt_and_sign(const rnp_cfg &cfg, /* signing keys */ std::vector keynames = cfg.get_list(CFG_SIGNERS); - if (!cli_rnp_keys_matching_strings(rnp, - signkeys, - keynames, - CLI_SEARCH_SECRET | CLI_SEARCH_DEFAULT | - CLI_SEARCH_SUBKEYS | CLI_SEARCH_FIRST_ONLY)) { + if (!rnp->keys_matching(signkeys, + keynames, + CLI_SEARCH_SECRET | CLI_SEARCH_DEFAULT | CLI_SEARCH_SUBKEYS | + CLI_SEARCH_FIRST_ONLY)) { ERR_MSG("Failed to build signing keys list"); goto done; } diff --git a/src/rnp/fficli.h b/src/rnp/fficli.h index 7e1b6a4771..4f7a519473 100644 --- a/src/rnp/fficli.h +++ b/src/rnp/fficli.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2021, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2019-2023, [Ribose Inc](https://www.ribose.com). * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, @@ -32,6 +32,7 @@ #include #include "rnp/rnp.h" #include "rnp/rnp_err.h" +#include "rnp/rnpcpp.hpp" #include "config.h" #include "rnpcfg.h" #include "json.h" @@ -46,12 +47,12 @@ class cli_rnp_t { char **subst_argv{}; #endif bool load_keyring(bool secret); - bool is_cv25519_subkey(rnp_key_handle_t handle); - bool get_protection(rnp_key_handle_t handle, - std::string & hash, - std::string & cipher, - size_t & iterations); - bool check_cv25519_bits(rnp_key_handle_t key, char *prot_password, bool &tweaked); + bool is_cv25519_subkey(rnpffi::Key &key); + bool get_protection(rnpffi::Key &key, + std::string &hash, + std::string &cipher, + size_t & iterations); + bool check_cv25519_bits(rnpffi::Key &key, rnpffi::String &prot_password, bool &tweaked); public: rnp_ffi_t ffi{}; @@ -123,6 +124,50 @@ class cli_rnp_t { bool set_key_expire(const std::string &key); bool edit_key(const std::string &key); + + /** + * @brief Find key(s) matching set of flags and search string. + * + * @param keys search results will be added here, leaving already existing items. + * @param str search string: may be part of the userid, keyid, fingerprint or grip. + * @param flags combination of the following flags: + * CLI_SEARCH_SECRET : require key to be secret, + * CLI_SEARCH_SUBKEYS : include subkeys to the results (see + * CLI_SEARCH_SUBKEYS_AFTER description). + * CLI_SEARCH_FIRST_ONLY : include only first key found + * CLI_SEARCH_SUBKEYS_AFTER : for each primary key add its subkeys after the + * main key. This changes behaviour of subkey search, since those will be added only if + * subkey is orphaned or primary key matches search. + * @return true if operation succeeds and at least one key is found, or false otherwise. + */ + + bool keys_matching(std::vector &keys, const std::string &str, int flags); + /** + * @brief Find key(s) matching set of flags and search string(s). + * + * @param keys search results will be put here, overwriting vector's contents. + * @param strs set of search strings, may be empty. + * @param flags the same flags as for keys_matching(), except additional one: + * CLI_SEARCH_DEFAULT : if no key is found then default key from cli_rnp_t + * will be searched. + * @return true if operation succeeds and at least one key is found for each search string, + * or false otherwise. + */ + bool keys_matching(std::vector & keys, + const std::vector &strs, + int flags); + + /** + * @brief Find exactly one key, matching set of flags and search string. + * + * @param str search string, see keys_matching() for the details. + * @param flags flags, see keys_matching() for the details. + * @param count if non-nullptr, number of found keys with be stored here. + * @return pointer to the key object if only single key was found, or nullptr otherwise. + */ + std::unique_ptr key_matching(const std::string &str, + int flags, + size_t * count = nullptr); }; typedef enum cli_search_flags_t { @@ -177,44 +222,8 @@ rnp_output_t cli_rnp_output_to_specifier(cli_rnp_t & rnp, bool cli_rnp_save_keyrings(cli_rnp_t *rnp); void cli_rnp_print_key_info( FILE *fp, rnp_ffi_t ffi, rnp_key_handle_t key, bool psecret, bool psigs); -bool cli_rnp_set_generate_params(rnp_cfg &cfg, bool subkey = false); -bool cli_rnp_generate_key(cli_rnp_t *rnp, const char *username); -/** - * @brief Find key(s) matching set of flags and search string. - * - * @param rnp initialized cli_rnp_t object. - * @param keys search results will be added here, leaving already existing items. - * @param str search string: may be part of the userid, keyid, fingerprint or grip. - * @param flags combination of the following flags: - * CLI_SEARCH_SECRET : require key to be secret, - * CLI_SEARCH_SUBKEYS : include subkeys to the results (see - * CLI_SEARCH_SUBKEYS_AFTER description). - * CLI_SEARCH_FIRST_ONLY : include only first key found - * CLI_SEARCH_SUBKEYS_AFTER : for each primary key add its subkeys after the main - * key. This changes behaviour of subkey search, since those will be added only - * if subkey is orphaned or primary key matches search. - * @return true if operation succeeds and at least one key is found, or false otherwise. - */ -bool cli_rnp_keys_matching_string(cli_rnp_t * rnp, - std::vector &keys, - const std::string & str, - int flags); -/** - * @brief Find key(s) matching set of flags and search string(s). - * - * @param rnp initialized cli_rnp_t object. - * @param keys search results will be put here, overwriting vector's contents. - * @param strs set of search strings, may be empty. - * @param flags the same flags as for cli_rnp_keys_matching_string(), except additional one: - * CLI_SEARCH_DEFAULT : if no key is found then default key from cli_rnp_t will be - * searched. - * @return true if operation succeeds and at least one key is found for each search string, or - * false otherwise. - */ -bool cli_rnp_keys_matching_strings(cli_rnp_t * rnp, - std::vector & keys, - const std::vector &strs, - int flags); +bool cli_rnp_set_generate_params(rnp_cfg &cfg, bool subkey = false); +bool cli_rnp_generate_key(cli_rnp_t *rnp, const char *username); bool cli_rnp_export_keys(cli_rnp_t *rnp, const char *filter); bool cli_rnp_export_revocation(cli_rnp_t *rnp, const char *key); bool cli_rnp_revoke_key(cli_rnp_t *rnp, const char *key); diff --git a/src/rnp/rnpcpp.hpp b/src/rnp/rnpcpp.hpp new file mode 100644 index 0000000000..ef4fe8511a --- /dev/null +++ b/src/rnp/rnpcpp.hpp @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2023, [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 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. + */ +#ifndef RNPCPP_HPP_ +#define RNPCPP_HPP_ + +#include +#include +#include +#include +#include "rnp/rnp.h" +#include "rnp/rnp_err.h" + +namespace rnpffi { + +class ffi_exception : public std::exception { + rnp_result_t code_; + const char * func_; + + public: + ffi_exception(rnp_result_t code = RNP_ERROR_GENERIC, const char *func = "") + : code_(code), func_(func){}; + virtual const char * + what() const throw() + { + return "ffi_exception"; + }; + + rnp_result_t + code() const + { + return code_; + }; + + const char * + func() const + { + return func_; + } +}; + +#define CALL_FFI(func_call) \ + do { \ + auto __ret__ = func_call; \ + if (__ret__) { \ + throw ffi_exception(__ret__, #func_call); \ + } \ + } while (0) + +/* Self-destroying char* wrapper to use in FFI API calls */ +class String { + char *c_str_; + bool secure_; + + public: + String(bool secure = false) : c_str_(nullptr), secure_(secure) + { + } + + /* Do not allow to copy/move it over */ + String(const String &) = delete; + String(String &&) = delete; + String &operator=(const String &) = delete; + String &operator=(String &&) = delete; + + ~String() + { + if (secure_ && c_str_) { + rnp_buffer_clear(c_str_, strlen(c_str_) + 1); + } + rnp_buffer_destroy(c_str_); + } + + char ** + set() + { + if (!c_str_) { + return &c_str_; + } + if (secure_) { + rnp_buffer_clear(c_str_, strlen(c_str_)); + } + rnp_buffer_destroy(c_str_); + c_str_ = NULL; + return &c_str_; + } + + const char * + c_str() const + { + return c_str_; + } + + char * + c_str() + { + return c_str_; + } + + std::string + str() const + { + return std::string(c_str_ ? c_str_ : ""); + } +}; + +class Key { + rnp_key_handle_t handle_; + bool own_; + + public: + Key() = delete; + Key(const Key &) = delete; + Key(Key &&) = delete; + Key &operator=(const Key &) = delete; + Key &operator=(Key &&) = delete; + + Key(rnp_key_handle_t handle, bool own = true) + { + if (!handle) { + throw std::invalid_argument("handle"); + } + handle_ = handle; + own_ = own; + } + + ~Key() + { + if (own_) { + rnp_key_handle_destroy(handle_); + } + } + + rnp_key_handle_t + handle() + { + return handle_; + } + + rnp_key_handle_t + release() + { + if (!own_) { + throw std::invalid_argument("own"); + } + own_ = false; + return handle_; + } + + bool + secret() + { + bool res = false; + CALL_FFI(rnp_key_have_secret(handle_, &res)); + return res; + } + + bool + is_sub() + { + bool subkey = false; + CALL_FFI(rnp_key_is_sub(handle_, &subkey)); + return subkey; + } + + bool + is_primary() + { + bool primary = false; + CALL_FFI(rnp_key_is_primary(handle_, &primary)); + return primary; + } + + std::string + alg() + { + String res; + CALL_FFI(rnp_key_get_alg(handle_, res.set())); + return res.str(); + } + + std::string + curve() + { + String res; + CALL_FFI(rnp_key_get_curve(handle_, res.set())); + return res.str(); + } + + std::string + keyid() + { + String res; + CALL_FFI(rnp_key_get_keyid(handle_, res.set())); + return res.str(); + } + + std::string + fprint() + { + String res; + CALL_FFI(rnp_key_get_fprint(handle_, res.set())); + return res.str(); + } + + std::string + grip() + { + String res; + CALL_FFI(rnp_key_get_grip(handle_, res.set())); + return res.str(); + } + + std::string + primary_grip() + { + String res; + CALL_FFI(rnp_key_get_primary_grip(handle_, res.set())); + return res.str(); + } + + size_t + uid_count() + { + size_t res = 0; + CALL_FFI(rnp_key_get_uid_count(handle_, &res)); + return res; + } + + std::string + uid_at(size_t idx) + { + String res; + CALL_FFI(rnp_key_get_uid_at(handle_, idx, res.set())); + return res.str(); + } + + bool + is_protected() + { + bool prot = false; + CALL_FFI(rnp_key_is_protected(handle_, &prot)); + return prot; + } + + std::string + protection_hash() + { + String res; + CALL_FFI(rnp_key_get_protection_hash(handle_, res.set())); + return res.str(); + } + + std::string + protection_cipher() + { + String res; + CALL_FFI(rnp_key_get_protection_cipher(handle_, res.set())); + return res.str(); + } + + size_t + protection_iterations() + { + size_t iterations = 0; + CALL_FFI(rnp_key_get_protection_iterations(handle_, &iterations)); + return iterations; + } + + bool + is_25519_bits_tweaked() + { + bool tweaked = false; + CALL_FFI(rnp_key_25519_bits_tweaked(handle_, &tweaked)); + return tweaked; + } + + bool + do_25519_bits_tweak() + { + return !rnp_key_25519_bits_tweak(handle_); + } + + bool + unlock(const std::string &password) + { + return !rnp_key_unlock(handle_, password.c_str()); + } + + bool + unlock() + { + return !rnp_key_unlock(handle_, NULL); + } + + void + lock() + { + rnp_key_lock(handle_); + } + + bool + unprotect(const std::string &password) + { + return !rnp_key_unprotect(handle_, password.c_str()); + } + + bool + protect(const std::string &password, + const std::string &cipher, + const std::string &hash, + size_t iterations) + { + return !rnp_key_protect( + handle_, password.c_str(), cipher.c_str(), NULL, hash.c_str(), iterations); + } +}; + +class IdentifierIterator { + rnp_identifier_iterator_t handle_; + + public: + IdentifierIterator() = delete; + IdentifierIterator(const IdentifierIterator &) = delete; + IdentifierIterator(IdentifierIterator &&) = delete; + IdentifierIterator &operator=(const IdentifierIterator &) = delete; + IdentifierIterator &operator=(IdentifierIterator &&) = delete; + + IdentifierIterator(rnp_identifier_iterator_t handle) + { + if (!handle) { + throw std::invalid_argument("handle"); + } + handle_ = handle; + } + + ~IdentifierIterator() + { + rnp_identifier_iterator_destroy(handle_); + } + + bool + next(std::string &val) + { + val = ""; + const char *n_val = NULL; + if (rnp_identifier_iterator_next(handle_, &n_val) || !n_val) { + return false; + } + val = n_val; + return true; + } +}; + +class FFI { + rnp_ffi_t handle_; + bool own_; + + public: + FFI() = delete; + FFI(const FFI &) = delete; + FFI(FFI &&) = delete; + FFI &operator=(const FFI &) = delete; + FFI &operator=(FFI &&) = delete; + + FFI(rnp_ffi_t handle, bool own = true) + { + if (!handle) { + throw std::invalid_argument("handle"); + } + handle_ = handle; + own_ = own; + } + + ~FFI() + { + if (own_) { + rnp_ffi_destroy(handle_); + } + } + + std::unique_ptr + locate_key(const std::string &id_type, const std::string &id) + { + rnp_key_handle_t handle = NULL; + if (rnp_locate_key(handle_, id_type.c_str(), id.c_str(), &handle)) { + return nullptr; + } + auto res = new (std::nothrow) Key(handle); + if (!res) { + rnp_key_handle_destroy(handle); + } + return std::unique_ptr(res); + } + + std::unique_ptr + iterator_create(const std::string &it_type) + { + rnp_identifier_iterator_t it = NULL; + if (rnp_identifier_iterator_create(handle_, &it, it_type.c_str())) { + return nullptr; + } + auto res = new (std::nothrow) IdentifierIterator(it); + if (!res) { + rnp_identifier_iterator_destroy(it); + } + return std::unique_ptr(res); + } + + bool + request_password(Key &key, const std::string &ctx, String &password) + { + return !rnp_request_password(handle_, key.handle(), ctx.c_str(), password.set()); + } +}; +} // namespace rnpffi + +#endif /* RNPCPP_HPP_ */ diff --git a/src/rnpkeys/rnpkeys.cpp b/src/rnpkeys/rnpkeys.cpp index 021663b8cc..d4e602dfca 100644 --- a/src/rnpkeys/rnpkeys.cpp +++ b/src/rnpkeys/rnpkeys.cpp @@ -155,7 +155,7 @@ print_keys_info(cli_rnp_t *rnp, FILE *fp, const char *filter) int flags = CLI_SEARCH_SUBKEYS_AFTER | (psecret ? CLI_SEARCH_SECRET : 0); std::vector keys; - if (!cli_rnp_keys_matching_string(rnp, keys, filter ? filter : "", flags)) { + if (!rnp->keys_matching(keys, filter ? filter : "", flags)) { fprintf(fp, "Key(s) not found.\n"); return false; } diff --git a/src/tests/exportkey.cpp b/src/tests/exportkey.cpp index ff0db751af..40faa30356 100644 --- a/src/tests/exportkey.cpp +++ b/src/tests/exportkey.cpp @@ -51,8 +51,7 @@ TEST_F(rnp_tests, rnpkeys_exportkey_verifyUserId) assert_int_equal(keycount, 2); std::vector keys; - assert_true( - cli_rnp_keys_matching_string(&rnp, keys, getenv_logname(), CLI_SEARCH_SUBKEYS_AFTER)); + assert_true(rnp.keys_matching(keys, getenv_logname(), CLI_SEARCH_SUBKEYS_AFTER)); assert_int_equal(keys.size(), 2); clear_key_handles(keys); diff --git a/src/tests/generatekey.cpp b/src/tests/generatekey.cpp index c89174315c..71361d930d 100644 --- a/src/tests/generatekey.cpp +++ b/src/tests/generatekey.cpp @@ -69,8 +69,7 @@ generate_test_key(const char *keystore, const char *userid, const char *hash, co if (rnp_get_secret_key_count(rnp.ffi, &keycount) || (keycount != 2)) { goto done; } - if (!cli_rnp_keys_matching_string( - &rnp, keys, userid ? userid : "", CLI_SEARCH_SUBKEYS_AFTER)) { + if (!rnp.keys_matching(keys, userid ? userid : "", CLI_SEARCH_SUBKEYS_AFTER)) { goto done; } if (keys.size() != 2) { diff --git a/src/tests/issues/1030.cpp b/src/tests/issues/1030.cpp index 692db5a799..d156c3c9ba 100644 --- a/src/tests/issues/1030.cpp +++ b/src/tests/issues/1030.cpp @@ -47,12 +47,11 @@ test_issue_1030(const char *keystore) assert_int_equal(keycount, 2); std::vector keys; - assert_true(cli_rnp_keys_matching_string(&rnp, keys, userid, CLI_SEARCH_SUBKEYS_AFTER)); + assert_true(rnp.keys_matching(keys, userid, CLI_SEARCH_SUBKEYS_AFTER)); assert_int_equal(keys.size(), 2); clear_key_handles(keys); - assert_true(cli_rnp_keys_matching_string( - &rnp, keys, userid, CLI_SEARCH_SECRET | CLI_SEARCH_SUBKEYS_AFTER)); + assert_true(rnp.keys_matching(keys, userid, CLI_SEARCH_SECRET | CLI_SEARCH_SUBKEYS_AFTER)); assert_int_equal(keys.size(), 2); for (auto key : keys) { bool is_protected = false;