diff options
Diffstat (limited to 'src/tests/support.cpp')
-rw-r--r-- | src/tests/support.cpp | 1216 |
1 files changed, 1216 insertions, 0 deletions
diff --git a/src/tests/support.cpp b/src/tests/support.cpp new file mode 100644 index 0000000..c94a901 --- /dev/null +++ b/src/tests/support.cpp @@ -0,0 +1,1216 @@ +/* + * Copyright (c) 2017-2019 [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 "support.h" +#include "rnp_tests.h" +#include "str-utils.h" +#include <librepgp/stream-ctx.h> +#include "pgp-key.h" +#include "librepgp/stream-armor.h" +#include "ffi-priv-types.h" + +#ifdef _MSC_VER +#include "uniwin.h" +#include <shlwapi.h> +#else +#include <sys/types.h> +#include <sys/param.h> +#endif + +#include <stdlib.h> +#include <stdbool.h> +#include <stdio.h> + +#include <crypto.h> +#include <pgp-key.h> +#include <fstream> +#include <vector> +#include <algorithm> + +#ifndef WINSHELLAPI +#include <ftw.h> +#endif + +#ifdef _WIN32 +int +setenv(const char *name, const char *value, int overwrite) +{ + if (getenv(name) && !overwrite) { + return 0; + } + char varbuf[512] = {0}; + snprintf(varbuf, sizeof(varbuf) - 1, "%s=%s", name, value); + return _putenv(varbuf); +} + +int +unsetenv(const char *name) +{ + char varbuf[512] = {0}; + snprintf(varbuf, sizeof(varbuf) - 1, "%s=", name); + return _putenv(varbuf); +} +#endif + +std::string +file_to_str(const std::string &path) +{ + // TODO: wstring path _WIN32 + std::ifstream infile(path); + return std::string(std::istreambuf_iterator<char>(infile), + std::istreambuf_iterator<char>()); +} + +std::vector<uint8_t> +file_to_vec(const std::string &path) +{ + // TODO: wstring path _WIN32 + std::ifstream stream(path, std::ios::in | std::ios::binary); + return std::vector<uint8_t>((std::istreambuf_iterator<char>(stream)), + std::istreambuf_iterator<char>()); +} + +void +str_to_file(const std::string &path, const char *str) +{ + std::ofstream stream(path, std::ios::out | std::ios::binary); + stream.write(str, strlen(str)); +} + +off_t +file_size(const char *path) +{ + struct stat path_stat; + if (rnp_stat(path, &path_stat) != -1) { + if (S_ISDIR(path_stat.st_mode)) { + return -1; + } + return path_stat.st_size; + } + return -1; +} + +/* Concatenate multiple strings into a full path. + * A directory separator is added between components. + * Must be called in between va_start and va_end. + * Final argument of calling function must be NULL. + */ +void +vpaths_concat(char *buffer, size_t buffer_size, const char *first, va_list ap) +{ + size_t length = strlen(first); + const char *s; + + assert_true(length < buffer_size); + + memset(buffer, 0, buffer_size); + + strncpy(buffer, first, buffer_size - 1); + while ((s = va_arg(ap, const char *))) { + length += strlen(s) + 1; + assert_true(length < buffer_size); + strncat(buffer, "/", buffer_size - 1); + strncat(buffer, s, buffer_size - 1); + } +} + +/* Concatenate multiple strings into a full path. + * Final argument must be NULL. + */ +char * +paths_concat(char *buffer, size_t buffer_length, const char *first, ...) +{ + va_list ap; + + va_start(ap, first); + vpaths_concat(buffer, buffer_length, first, ap); + va_end(ap); + return buffer; +} + +/* Concatenate multiple strings into a full path and + * check that the file exists. + * Final argument must be NULL. + */ +int +path_rnp_file_exists(const char *first, ...) +{ + va_list ap; + char buffer[512] = {0}; + + va_start(ap, first); + vpaths_concat(buffer, sizeof(buffer), first, ap); + va_end(ap); + return rnp_file_exists(buffer); +} + +/* Concatenate multiple strings into a full path and + * create the directory. + * Final argument must be NULL. + */ +void +path_mkdir(mode_t mode, const char *first, ...) +{ + va_list ap; + char buffer[512]; + + va_start(ap, first); + vpaths_concat(buffer, sizeof(buffer), first, ap); + va_end(ap); + + assert_int_equal(0, RNP_MKDIR(buffer, mode)); +} + +static int +remove_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) +{ + int ret = remove(fpath); + if (ret) + perror(fpath); + + return ret; +} + +static const char * +get_tmp() +{ + const char *tmp = getenv("TEMP"); + return tmp ? tmp : "/tmp"; +} + +static bool +is_tmp_path(const char *path) +{ + char *rlpath = realpath(path, NULL); + if (!rlpath) { + rlpath = strdup(path); + } + const char *tmp = get_tmp(); + char * rltmp = realpath(tmp, NULL); + if (!rltmp) { + rltmp = strdup(tmp); + } + bool res = rlpath && rltmp && !strncmp(rlpath, rltmp, strlen(rltmp)); + free(rlpath); + free(rltmp); + return res; +} + +/* Recursively remove a directory. + * The path must be located in /tmp, for safety. + */ +void +delete_recursively(const char *path) +{ + bool relative = +#ifdef _MSC_VER + PathIsRelativeA(path); +#else + *path != '/'; +#endif + std::string fullpath = path; + if (relative) { + char *cwd = getcwd(NULL, 0); + fullpath = rnp::path::append(cwd, fullpath); + free(cwd); + } + /* sanity check, we should only be purging things from /tmp/ */ + assert_true(is_tmp_path(fullpath.c_str())); + +#ifdef WINSHELLAPI + SHFILEOPSTRUCTA fileOp = {}; + fileOp.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI; + assert_true(fullpath.size() < MAX_PATH); + char newFrom[MAX_PATH + 1]; + strcpy_s(newFrom, fullpath.c_str()); + newFrom[fullpath.size() + 1] = NULL; // two NULLs are required + fileOp.pFrom = newFrom; + fileOp.pTo = NULL; + fileOp.wFunc = FO_DELETE; + fileOp.hNameMappings = NULL; + fileOp.hwnd = NULL; + fileOp.lpszProgressTitle = NULL; + SHFileOperationA(&fileOp); +#else + nftw(path, remove_cb, 64, FTW_DEPTH | FTW_PHYS); +#endif +} + +void +copy_recursively(const char *src, const char *dst) +{ + assert_true(src != nullptr); + /* sanity check, we should only be copying things to /tmp/ */ + assert_true(is_tmp_path(dst)); + +#ifdef WINSHELLAPI + SHFILEOPSTRUCTA fileOp = {}; + fileOp.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR; + fileOp.pFrom = src; + fileOp.pTo = dst; + assert_true(strlen(src) < MAX_PATH); + char newFrom[MAX_PATH + 1]; + strcpy_s(newFrom, src); + newFrom[strlen(src) + 1] = NULL; // two NULLs are required + fileOp.pFrom = newFrom; + assert_true(strlen(dst) < MAX_PATH); + char newTo[MAX_PATH + 1]; + strcpy_s(newTo, dst); + newTo[strlen(dst) + 1] = NULL; // two NULLs are required + fileOp.wFunc = FO_COPY; + fileOp.hNameMappings = NULL; + fileOp.hwnd = NULL; + fileOp.lpszProgressTitle = NULL; + assert_int_equal(0, SHFileOperationA(&fileOp)); +#else + // TODO: maybe use fts or something less hacky + char buf[2048]; +#ifndef _WIN32 + snprintf(buf, sizeof(buf), "cp -a '%s' '%s'", src, dst); +#else + snprintf(buf, sizeof(buf), "xcopy \"%s\" \"%s\" /I /Q /E /Y", src, dst); +#endif // _WIN32 + assert_int_equal(0, system(buf)); +#endif // WINSHELLAPI +} + +/* Creates and returns a temporary directory path. + * Caller must free the string. + */ +#if defined(HAVE_MKDTEMP) +char * +make_temp_dir() +{ + char rltmp[PATH_MAX] = {0}; + if (!realpath(get_tmp(), rltmp)) { + printf("Fatal: realpath on tmp folder failed. Error %d.\n", errno); + return NULL; + } + + const char *tmplate = "/rnp-gtest-XXXXXX"; + char * buffer = (char *) calloc(1, strlen(rltmp) + strlen(tmplate) + 1); + if (buffer == NULL) { + return NULL; + } + memcpy(buffer, rltmp, strlen(rltmp)); + memcpy(buffer + strlen(rltmp), tmplate, strlen(tmplate)); + buffer[strlen(rltmp) + strlen(tmplate)] = '\0'; + char *res = mkdtemp(buffer); + if (!res) { + free(buffer); + } + return res; +} +#elif defined(HAVE__TEMPNAM) +char * +make_temp_dir() +{ + const int MAX_ATTEMPTS = 10; + for (int i = 0; i < MAX_ATTEMPTS; i++) { + char *dir = _tempnam(NULL, "rnp-gtest-"); + if (!dir) { + fprintf(stderr, "_tempnam failed to generate temporary path"); + continue; + } + if (RNP_MKDIR(dir, S_IRWXU)) { + fprintf(stderr, "Failed to create temporary directory"); + free(dir); + continue; + } + return dir; + } + fprintf(stderr, "Failed to make temporary directory, aborting"); + return NULL; +} +#else +#error Unsupported platform +#endif + +void +clean_temp_dir(const char *path) +{ + if (!getenv("RNP_KEEP_TEMP")) { + delete_recursively(path); + } +} + +bool +bin_eq_hex(const uint8_t *data, size_t len, const char *val) +{ + size_t stlen = strlen(val); + if (stlen != len * 2) { + return false; + } + + std::vector<uint8_t> dec(len); + rnp::hex_decode(val, dec.data(), len); + return !memcmp(data, dec.data(), len); +} + +bool +hex2mpi(pgp_mpi_t *val, const char *hex) +{ + const size_t hex_len = strlen(hex); + size_t buf_len = hex_len / 2; + bool ok; + + uint8_t *buf = NULL; + + buf = (uint8_t *) malloc(buf_len); + + if (buf == NULL) { + return false; + } + + rnp::hex_decode(hex, buf, buf_len); + + ok = mem2mpi(val, buf, buf_len); + free(buf); + return ok; +} + +bool +cmp_keyid(const pgp_key_id_t &id, const std::string &val) +{ + return bin_eq_hex(id.data(), id.size(), val.c_str()); +} + +bool +cmp_keyfp(const pgp_fingerprint_t &fp, const std::string &val) +{ + return bin_eq_hex(fp.fingerprint, fp.length, val.c_str()); +} + +void +test_ffi_init(rnp_ffi_t *ffi) +{ + // setup FFI + assert_rnp_success(rnp_ffi_create(ffi, "GPG", "GPG")); + // load our keyrings + assert_true( + load_keys_gpg(*ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg")); +} + +bool +mpi_empty(const pgp_mpi_t &val) +{ + pgp_mpi_t zero{}; + return (val.len == 0) && !memcmp(val.mpi, zero.mpi, PGP_MPINT_SIZE); +} + +bool +write_pass_to_pipe(int fd, size_t count) +{ + const char *const password = "passwordforkeygeneration\n"; + for (size_t i = 0; i < count; i++) { + const char *p = password; + ssize_t remaining = strlen(p); + + do { + ssize_t written = write(fd, p, remaining); + if (written <= 0) { + perror("write"); + return false; + } + p += written; + remaining -= written; + } while (remaining); + } + return true; +} + +bool +setupPasswordfd(int *pipefd) +{ + bool ok = false; + + if (pipe(pipefd) == -1) { + perror("pipe"); + goto end; + } + // write it twice for normal keygen (primary+sub) + if (!write_pass_to_pipe(pipefd[1], 2)) { + close(pipefd[1]); + goto end; + } + ok = true; + +end: + close(pipefd[1]); + return ok; +} + +static bool +setup_rnp_cfg(rnp_cfg &cfg, const char *ks_format, const char *homedir, int *pipefd) +{ + bool res; + char pubpath[MAXPATHLEN]; + char secpath[MAXPATHLEN]; + char homepath[MAXPATHLEN]; + + /* set password fd if any */ + if (pipefd) { + if (!(res = setupPasswordfd(pipefd))) { + return res; + } + cfg.set_int(CFG_PASSFD, pipefd[0]); + // pipefd[0] will be closed via passfp + pipefd[0] = -1; + } + /* setup keyring paths */ + if (homedir == NULL) { + /* if we use default homedir then we append '.rnp' and create directory as well */ + homedir = getenv("HOME"); + paths_concat(homepath, sizeof(homepath), homedir, ".rnp", NULL); + if (!rnp_dir_exists(homepath)) { + path_mkdir(0700, homepath, NULL); + } + homedir = homepath; + } + + if (homedir == NULL) { + return false; + } + + cfg.set_str(CFG_KR_PUB_FORMAT, ks_format); + cfg.set_str(CFG_KR_SEC_FORMAT, ks_format); + + if (strcmp(ks_format, RNP_KEYSTORE_GPG) == 0) { + paths_concat(pubpath, MAXPATHLEN, homedir, PUBRING_GPG, NULL); + paths_concat(secpath, MAXPATHLEN, homedir, SECRING_GPG, NULL); + } else if (strcmp(ks_format, RNP_KEYSTORE_KBX) == 0) { + paths_concat(pubpath, MAXPATHLEN, homedir, PUBRING_KBX, NULL); + paths_concat(secpath, MAXPATHLEN, homedir, SECRING_KBX, NULL); + } else if (strcmp(ks_format, RNP_KEYSTORE_G10) == 0) { + paths_concat(pubpath, MAXPATHLEN, homedir, PUBRING_G10, NULL); + paths_concat(secpath, MAXPATHLEN, homedir, SECRING_G10, NULL); + } else if (strcmp(ks_format, RNP_KEYSTORE_GPG21) == 0) { + paths_concat(pubpath, MAXPATHLEN, homedir, PUBRING_KBX, NULL); + paths_concat(secpath, MAXPATHLEN, homedir, SECRING_G10, NULL); + cfg.set_str(CFG_KR_PUB_FORMAT, RNP_KEYSTORE_KBX); + cfg.set_str(CFG_KR_SEC_FORMAT, RNP_KEYSTORE_G10); + } else { + return false; + } + + cfg.set_str(CFG_KR_PUB_PATH, (char *) pubpath); + cfg.set_str(CFG_KR_SEC_PATH, (char *) secpath); + return true; +} + +bool +setup_cli_rnp_common(cli_rnp_t *rnp, const char *ks_format, const char *homedir, int *pipefd) +{ + rnp_cfg cfg; + if (!setup_rnp_cfg(cfg, ks_format, homedir, pipefd)) { + return false; + } + + /*initialize the basic RNP structure. */ + return rnp->init(cfg); +} + +void +cli_set_default_rsa_key_desc(rnp_cfg &cfg, const char *hashalg) +{ + cfg.set_int(CFG_NUMBITS, 1024); + cfg.set_str(CFG_HASH, hashalg); + cfg.set_int(CFG_S2K_ITER, 1); + cli_rnp_set_generate_params(cfg); +} + +// this is a password callback that will always fail +bool +failing_password_callback(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata) +{ + return false; +} + +bool +ffi_failing_password_provider(rnp_ffi_t ffi, + void * app_ctx, + rnp_key_handle_t key, + const char * pgp_context, + char * buf, + size_t buf_len) +{ + return false; +} + +bool +ffi_asserting_password_provider(rnp_ffi_t ffi, + void * app_ctx, + rnp_key_handle_t key, + const char * pgp_context, + char * buf, + size_t buf_len) +{ + EXPECT_TRUE(false); + return false; +} + +bool +ffi_string_password_provider(rnp_ffi_t ffi, + void * app_ctx, + rnp_key_handle_t key, + const char * pgp_context, + char * buf, + size_t buf_len) +{ + size_t pass_len = strlen((const char *) app_ctx); + if (pass_len >= buf_len) { + return false; + } + memcpy(buf, app_ctx, pass_len + 1); + return true; +} + +// this is a password callback that should never be called +bool +asserting_password_callback(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata) +{ + EXPECT_TRUE(false); + return false; +} + +// this is a password callback that just copies the string in userdata to +// the password buffer +bool +string_copy_password_callback(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata) +{ + const char *str = (const char *) userdata; + strncpy(password, str, password_size - 1); + return true; +} + +void +unused_getkeycb(rnp_ffi_t ffi, + void * app_ctx, + const char *identifier_type, + const char *identifier, + bool secret) +{ + EXPECT_TRUE(false); +} + +bool +unused_getpasscb(rnp_ffi_t ffi, + void * app_ctx, + rnp_key_handle_t key, + const char * pgp_context, + char * buf, + size_t buf_len) +{ + EXPECT_TRUE(false); + return false; +} + +bool +starts_with(const std::string &data, const std::string &match) +{ + return data.find(match) == 0; +} + +bool +ends_with(const std::string &data, const std::string &match) +{ + return data.size() >= match.size() && + data.substr(data.size() - match.size(), match.size()) == match; +} + +std::string +fmt(const char *format, ...) +{ + int size; + va_list ap; + + va_start(ap, format); + size = vsnprintf(NULL, 0, format, ap); + va_end(ap); + + // +1 for terminating null + std::string buf(size + 1, '\0'); + + va_start(ap, format); + size = vsnprintf(&buf[0], buf.size(), format, ap); + va_end(ap); + + // drop terminating null + buf.resize(size); + return buf; +} + +std::string +strip_eol(const std::string &str) +{ + size_t endpos = str.find_last_not_of("\r\n"); + if (endpos != std::string::npos) { + return str.substr(0, endpos + 1); + } + return str; +} + +std::string +lowercase(const std::string &str) +{ + std::string res = str; + std::transform( + res.begin(), res.end(), res.begin(), [](unsigned char ch) { return std::tolower(ch); }); + return res; +} + +static bool +jso_get_field(json_object *obj, json_object **fld, const std::string &name) +{ + if (!obj || !json_object_is_type(obj, json_type_object)) { + return false; + } + return json_object_object_get_ex(obj, name.c_str(), fld); +} + +bool +check_json_field_str(json_object *obj, const std::string &field, const std::string &value) +{ + json_object *fld = NULL; + if (!jso_get_field(obj, &fld, field)) { + return false; + } + if (!json_object_is_type(fld, json_type_string)) { + return false; + } + const char *jsoval = json_object_get_string(fld); + return jsoval && (value == jsoval); +} + +bool +check_json_field_int(json_object *obj, const std::string &field, int value) +{ + json_object *fld = NULL; + if (!jso_get_field(obj, &fld, field)) { + return false; + } + if (!json_object_is_type(fld, json_type_int)) { + return false; + } + return json_object_get_int(fld) == value; +} + +bool +check_json_field_bool(json_object *obj, const std::string &field, bool value) +{ + json_object *fld = NULL; + if (!jso_get_field(obj, &fld, field)) { + return false; + } + if (!json_object_is_type(fld, json_type_boolean)) { + return false; + } + return json_object_get_boolean(fld) == value; +} + +bool +check_json_pkt_type(json_object *pkt, int tag) +{ + if (!pkt || !json_object_is_type(pkt, json_type_object)) { + return false; + } + json_object *hdr = NULL; + if (!json_object_object_get_ex(pkt, "header", &hdr)) { + return false; + } + if (!json_object_is_type(hdr, json_type_object)) { + return false; + } + return check_json_field_int(hdr, "tag", tag); +} + +pgp_key_t * +rnp_tests_get_key_by_id(rnp_key_store_t *keyring, const std::string &keyid, pgp_key_t *after) +{ + if (!keyring || keyid.empty() || !rnp::is_hex(keyid)) { + return NULL; + } + pgp_key_id_t keyid_bin = {}; + size_t binlen = rnp::hex_decode(keyid.c_str(), keyid_bin.data(), keyid_bin.size()); + if (binlen > PGP_KEY_ID_SIZE) { + return NULL; + } + pgp_key_search_t search(PGP_KEY_SEARCH_KEYID); + search.by.keyid = keyid_bin; + return rnp_key_store_search(keyring, &search, after); +} + +pgp_key_t * +rnp_tests_get_key_by_grip(rnp_key_store_t *keyring, const std::string &grip) +{ + if (!keyring || grip.empty() || !rnp::is_hex(grip)) { + return NULL; + } + pgp_key_grip_t grip_bin = {}; + size_t binlen = rnp::hex_decode(grip.c_str(), grip_bin.data(), grip_bin.size()); + if (binlen > PGP_KEY_GRIP_SIZE) { + return NULL; + } + return rnp_tests_get_key_by_grip(keyring, grip_bin); +} + +pgp_key_t * +rnp_tests_get_key_by_grip(rnp_key_store_t *keyring, const pgp_key_grip_t &grip) +{ + if (!keyring) { + return NULL; + } + pgp_key_search_t search(PGP_KEY_SEARCH_GRIP); + search.by.grip = grip; + return rnp_key_store_search(keyring, &search, NULL); +} + +pgp_key_t * +rnp_tests_get_key_by_fpr(rnp_key_store_t *keyring, const std::string &keyid) +{ + if (!keyring || keyid.empty() || !rnp::is_hex(keyid)) { + return NULL; + } + std::vector<uint8_t> keyid_bin(PGP_FINGERPRINT_SIZE, 0); + size_t binlen = rnp::hex_decode(keyid.c_str(), keyid_bin.data(), keyid_bin.size()); + if (binlen > PGP_FINGERPRINT_SIZE) { + return NULL; + } + pgp_fingerprint_t fp = {{}, static_cast<unsigned>(binlen)}; + memcpy(fp.fingerprint, keyid_bin.data(), binlen); + return rnp_key_store_get_key_by_fpr(keyring, fp); +} + +pgp_key_t * +rnp_tests_key_search(rnp_key_store_t *keyring, const std::string &uid) +{ + if (!keyring || uid.empty()) { + return NULL; + } + + pgp_key_search_t srch_userid(PGP_KEY_SEARCH_USERID); + strncpy(srch_userid.by.userid, uid.c_str(), sizeof(srch_userid.by.userid)); + srch_userid.by.userid[sizeof(srch_userid.by.userid) - 1] = '\0'; + return rnp_key_store_search(keyring, &srch_userid, NULL); +} + +void +reload_pubring(rnp_ffi_t *ffi) +{ + rnp_output_t output = NULL; + assert_rnp_success(rnp_output_to_memory(&output, 0)); + assert_rnp_success(rnp_save_keys(*ffi, "GPG", output, RNP_LOAD_SAVE_PUBLIC_KEYS)); + assert_rnp_success(rnp_ffi_destroy(*ffi)); + + /* get output */ + uint8_t *buf = NULL; + size_t len = 0; + assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false)); + rnp_input_t input = NULL; + assert_rnp_success(rnp_input_from_memory(&input, buf, len, false)); + + /* re-init ffi and load keys */ + assert_rnp_success(rnp_ffi_create(ffi, "GPG", "GPG")); + assert_rnp_success(rnp_import_keys(*ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL)); + assert_rnp_success(rnp_output_destroy(output)); + assert_rnp_success(rnp_input_destroy(input)); +} + +void +reload_keyrings(rnp_ffi_t *ffi) +{ + rnp_output_t outpub = NULL; + assert_rnp_success(rnp_output_to_memory(&outpub, 0)); + assert_rnp_success(rnp_save_keys(*ffi, "GPG", outpub, RNP_LOAD_SAVE_PUBLIC_KEYS)); + rnp_output_t outsec = NULL; + assert_rnp_success(rnp_output_to_memory(&outsec, 0)); + assert_rnp_success(rnp_save_keys(*ffi, "GPG", outsec, RNP_LOAD_SAVE_SECRET_KEYS)); + assert_rnp_success(rnp_ffi_destroy(*ffi)); + /* re-init ffi and load keys */ + assert_rnp_success(rnp_ffi_create(ffi, "GPG", "GPG")); + + uint8_t *buf = NULL; + size_t len = 0; + assert_rnp_success(rnp_output_memory_get_buf(outpub, &buf, &len, false)); + rnp_input_t input = NULL; + assert_rnp_success(rnp_input_from_memory(&input, buf, len, false)); + assert_rnp_success(rnp_import_keys(*ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL)); + assert_rnp_success(rnp_input_destroy(input)); + assert_rnp_success(rnp_output_destroy(outpub)); + + assert_rnp_success(rnp_output_memory_get_buf(outsec, &buf, &len, false)); + assert_rnp_success(rnp_input_from_memory(&input, buf, len, false)); + assert_rnp_success(rnp_import_keys(*ffi, input, RNP_LOAD_SAVE_SECRET_KEYS, NULL)); + assert_rnp_success(rnp_input_destroy(input)); + assert_rnp_success(rnp_output_destroy(outsec)); +} + +static bool +load_keys_internal(rnp_ffi_t ffi, + const std::string &format, + const std::string &path, + bool secret) +{ + if (path.empty()) { + return true; + } + rnp_input_t input = NULL; + if (rnp_input_from_path(&input, path.c_str())) { + return false; + } + bool res = !rnp_load_keys(ffi, + format.c_str(), + input, + secret ? RNP_LOAD_SAVE_SECRET_KEYS : RNP_LOAD_SAVE_PUBLIC_KEYS); + rnp_input_destroy(input); + return res; +} + +bool +load_keys_gpg(rnp_ffi_t ffi, const std::string &pub, const std::string &sec) +{ + return load_keys_internal(ffi, "GPG", pub, false) && + load_keys_internal(ffi, "GPG", sec, true); +} + +bool +load_keys_kbx_g10(rnp_ffi_t ffi, const std::string &pub, const std::string &sec) +{ + return load_keys_internal(ffi, "KBX", pub, false) && + load_keys_internal(ffi, "G10", sec, true); +} + +static bool +import_keys(rnp_ffi_t ffi, const std::string &path, uint32_t flags) +{ + rnp_input_t input = NULL; + if (rnp_input_from_path(&input, path.c_str())) { + return false; + } + bool res = !rnp_import_keys(ffi, input, flags, NULL); + rnp_input_destroy(input); + return res; +} + +bool +import_all_keys(rnp_ffi_t ffi, const std::string &path) +{ + return import_keys(ffi, path, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS); +} + +bool +import_pub_keys(rnp_ffi_t ffi, const std::string &path) +{ + return import_keys(ffi, path, RNP_LOAD_SAVE_PUBLIC_KEYS); +} + +bool +import_sec_keys(rnp_ffi_t ffi, const std::string &path) +{ + return import_keys(ffi, path, RNP_LOAD_SAVE_SECRET_KEYS); +} + +static bool +import_keys(rnp_ffi_t ffi, const uint8_t *data, size_t len, uint32_t flags) +{ + rnp_input_t input = NULL; + if (rnp_input_from_memory(&input, data, len, false)) { + return false; + } + bool res = !rnp_import_keys(ffi, input, flags, NULL); + rnp_input_destroy(input); + return res; +} + +bool +import_all_keys(rnp_ffi_t ffi, const uint8_t *data, size_t len, uint32_t flags) +{ + return import_keys( + ffi, data, len, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS | flags); +} + +bool +import_pub_keys(rnp_ffi_t ffi, const uint8_t *data, size_t len) +{ + return import_keys(ffi, data, len, RNP_LOAD_SAVE_PUBLIC_KEYS); +} + +bool +import_sec_keys(rnp_ffi_t ffi, const uint8_t *data, size_t len) +{ + return import_keys(ffi, data, len, RNP_LOAD_SAVE_SECRET_KEYS); +} + +std::vector<uint8_t> +export_key(rnp_key_handle_t key, bool armored, bool secret) +{ + uint32_t flags = RNP_KEY_EXPORT_SUBKEYS; + if (armored) { + flags = flags | RNP_KEY_EXPORT_ARMORED; + } + if (secret) { + flags = flags | RNP_KEY_EXPORT_SECRET; + } else { + flags = flags | RNP_KEY_EXPORT_PUBLIC; + } + rnp_output_t output = NULL; + rnp_output_to_memory(&output, 0); + rnp_key_export(key, output, flags); + size_t len = 0; + uint8_t *buf = NULL; + rnp_output_memory_get_buf(output, &buf, &len, false); + std::vector<uint8_t> res(buf, buf + len); + rnp_output_destroy(output); + return res; +} + +#if 0 +void +dump_key_stdout(rnp_key_handle_t key, bool secret) +{ + auto pub = export_key(key, true, false); + printf("%.*s", (int) pub.size(), (char *) pub.data()); + if (!secret) { + return; + } + auto sec = export_key(key, true, true); + printf("%.*s", (int) sec.size(), (char *) sec.data()); +} +#endif + +bool +write_transferable_key(pgp_transferable_key_t &key, pgp_dest_t &dst, bool armor) +{ + pgp_key_sequence_t keys; + keys.keys.push_back(key); + return write_transferable_keys(keys, &dst, armor); +} + +bool +write_transferable_keys(pgp_key_sequence_t &keys, pgp_dest_t *dst, bool armor) +{ + std::unique_ptr<rnp::ArmoredDest> armdst; + if (armor) { + pgp_armored_msg_t msgtype = PGP_ARMORED_PUBLIC_KEY; + if (!keys.keys.empty() && is_secret_key_pkt(keys.keys.front().key.tag)) { + msgtype = PGP_ARMORED_SECRET_KEY; + } + armdst = std::unique_ptr<rnp::ArmoredDest>(new rnp::ArmoredDest(*dst, msgtype)); + dst = &armdst->dst(); + } + + for (auto &key : keys.keys) { + /* main key */ + key.key.write(*dst); + /* revocation and direct-key signatures */ + for (auto &sig : key.signatures) { + sig.write(*dst); + } + /* user ids/attrs and signatures */ + for (auto &uid : key.userids) { + uid.uid.write(*dst); + for (auto &sig : uid.signatures) { + sig.write(*dst); + } + } + /* subkeys with signatures */ + for (auto &skey : key.subkeys) { + skey.subkey.write(*dst); + for (auto &sig : skey.signatures) { + sig.write(*dst); + } + } + } + return !dst->werr; +} + +bool +check_uid_valid(rnp_key_handle_t key, size_t idx, bool valid) +{ + rnp_uid_handle_t uid = NULL; + if (rnp_key_get_uid_handle_at(key, idx, &uid)) { + return false; + } + bool val = !valid; + rnp_uid_is_valid(uid, &val); + rnp_uid_handle_destroy(uid); + return val == valid; +} + +bool +check_uid_primary(rnp_key_handle_t key, size_t idx, bool primary) +{ + rnp_uid_handle_t uid = NULL; + if (rnp_key_get_uid_handle_at(key, idx, &uid)) { + return false; + } + bool prim = !primary; + rnp_uid_is_primary(uid, &prim); + rnp_uid_handle_destroy(uid); + return prim == primary; +} + +bool +check_key_valid(rnp_key_handle_t key, bool validity) +{ + bool valid = !validity; + if (rnp_key_is_valid(key, &valid)) { + return false; + } + return valid == validity; +} + +uint32_t +get_key_expiry(rnp_key_handle_t key) +{ + uint32_t expiry = (uint32_t) -1; + rnp_key_get_expiration(key, &expiry); + return expiry; +} + +size_t +get_key_uids(rnp_key_handle_t key) +{ + size_t count = (size_t) -1; + rnp_key_get_uid_count(key, &count); + return count; +} + +bool +check_sub_valid(rnp_key_handle_t key, size_t idx, bool validity) +{ + rnp_key_handle_t sub = NULL; + if (rnp_key_get_subkey_at(key, idx, &sub)) { + return false; + } + bool valid = !validity; + rnp_key_is_valid(sub, &valid); + rnp_key_handle_destroy(sub); + return valid == validity; +} + +rnp_key_handle_t +bogus_key_handle(rnp_ffi_t ffi) +{ + rnp_key_handle_t handle = (rnp_key_handle_t) calloc(1, sizeof(*handle)); + handle->ffi = ffi; + handle->pub = NULL; + handle->sec = NULL; + handle->locator.type = PGP_KEY_SEARCH_KEYID; + return handle; +} + +bool +sm2_enabled() +{ + bool enabled = false; + return !rnp_supports_feature(RNP_FEATURE_PK_ALG, "SM2", &enabled) && enabled; +} + +bool +aead_eax_enabled() +{ + bool enabled = false; + return !rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "EAX", &enabled) && enabled; +} + +bool +aead_ocb_enabled() +{ + bool enabled = false; + return !rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "OCB", &enabled) && enabled; +} + +bool +aead_ocb_aes_only() +{ + return aead_ocb_enabled() && !strcmp(rnp_backend_string(), "OpenSSL"); +} + +bool +twofish_enabled() +{ + bool enabled = false; + return !rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "Twofish", &enabled) && enabled; +} + +bool +idea_enabled() +{ + bool enabled = false; + return !rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "IDEA", &enabled) && enabled; +} + +bool +brainpool_enabled() +{ + bool enabled = false; + return !rnp_supports_feature(RNP_FEATURE_CURVE, "brainpoolP256r1", &enabled) && enabled; +} + +bool +blowfish_enabled() +{ + bool enabled = false; + return !rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "BLOWFISH", &enabled) && enabled; +} + +bool +cast5_enabled() +{ + bool enabled = false; + return !rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "CAST5", &enabled) && enabled; +} + +bool +ripemd160_enabled() +{ + bool enabled = false; + return !rnp_supports_feature(RNP_FEATURE_HASH_ALG, "RIPEMD160", &enabled) && enabled; +} + +bool +test_load_gpg_check_key(rnp_key_store_t *pub, rnp_key_store_t *sec, const char *id) +{ + pgp_key_t *key = rnp_tests_get_key_by_id(pub, id); + if (!key) { + return false; + } + if (!(key = rnp_tests_get_key_by_id(sec, id))) { + return false; + } + pgp_password_provider_t pswd_prov(string_copy_password_callback, (void *) "password"); + return key->is_protected() && key->unlock(pswd_prov) && key->lock(); +} |