summaryrefslogtreecommitdiffstats
path: root/src/tests/support.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests/support.cpp')
-rw-r--r--src/tests/support.cpp1216
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();
+}