summaryrefslogtreecommitdiffstats
path: root/src/librekey/rnp_key_store.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/librekey/rnp_key_store.cpp')
-rw-r--r--src/librekey/rnp_key_store.cpp803
1 files changed, 803 insertions, 0 deletions
diff --git a/src/librekey/rnp_key_store.cpp b/src/librekey/rnp_key_store.cpp
new file mode 100644
index 0000000..002a51e
--- /dev/null
+++ b/src/librekey/rnp_key_store.cpp
@@ -0,0 +1,803 @@
+/*
+ * Copyright (c) 2017-2022 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include <sys/stat.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#else
+#include "uniwin.h"
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <errno.h>
+#include <algorithm>
+#include <stdexcept>
+
+#include <rekey/rnp_key_store.h>
+#include <librepgp/stream-packet.h>
+
+#include "key_store_pgp.h"
+#include "key_store_kbx.h"
+#include "key_store_g10.h"
+#include "kbx_blob.hpp"
+
+#include "pgp-key.h"
+#include "fingerprint.h"
+#include "crypto/hash.hpp"
+#include "crypto/mem.h"
+#include "file-utils.h"
+#ifdef _WIN32
+#include "str-utils.h"
+#endif
+
+bool
+rnp_key_store_load_from_path(rnp_key_store_t * key_store,
+ const pgp_key_provider_t *key_provider)
+{
+ pgp_source_t src = {};
+
+ if (key_store->format == PGP_KEY_STORE_G10) {
+ auto dir = rnp_opendir(key_store->path.c_str());
+ if (!dir) {
+ RNP_LOG(
+ "Can't open G10 directory %s: %s", key_store->path.c_str(), strerror(errno));
+ return false;
+ }
+
+ std::string dirname;
+ while (!((dirname = rnp_readdir_name(dir)).empty())) {
+ std::string path = rnp::path::append(key_store->path, dirname);
+
+ if (init_file_src(&src, path.c_str())) {
+ RNP_LOG("failed to read file %s", path.c_str());
+ continue;
+ }
+ // G10 may fail to read one file, so ignore it!
+ if (!rnp_key_store_g10_from_src(key_store, &src, key_provider)) {
+ RNP_LOG("Can't parse file: %s", path.c_str()); // TODO: %S ?
+ }
+ src_close(&src);
+ }
+ rnp_closedir(dir);
+ return true;
+ }
+
+ /* init file source and load from it */
+ if (init_file_src(&src, key_store->path.c_str())) {
+ RNP_LOG("failed to read file %s", key_store->path.c_str());
+ return false;
+ }
+
+ bool rc = rnp_key_store_load_from_src(key_store, &src, key_provider);
+ src_close(&src);
+ return rc;
+}
+
+bool
+rnp_key_store_load_from_src(rnp_key_store_t * key_store,
+ pgp_source_t * src,
+ const pgp_key_provider_t *key_provider)
+{
+ switch (key_store->format) {
+ case PGP_KEY_STORE_GPG:
+ return rnp_key_store_pgp_read_from_src(key_store, src) == RNP_SUCCESS;
+ case PGP_KEY_STORE_KBX:
+ return rnp_key_store_kbx_from_src(key_store, src, key_provider);
+ case PGP_KEY_STORE_G10:
+ return rnp_key_store_g10_from_src(key_store, src, key_provider);
+ default:
+ RNP_LOG("Unsupported load from memory for key-store format: %d", key_store->format);
+ }
+
+ return false;
+}
+
+bool
+rnp_key_store_write_to_path(rnp_key_store_t *key_store)
+{
+ bool rc;
+ pgp_dest_t keydst = {};
+
+ /* write g10 key store to the directory */
+ if (key_store->format == PGP_KEY_STORE_G10) {
+ char path[MAXPATHLEN];
+
+ struct stat path_stat;
+ if (rnp_stat(key_store->path.c_str(), &path_stat) != -1) {
+ if (!S_ISDIR(path_stat.st_mode)) {
+ RNP_LOG("G10 keystore should be a directory: %s", key_store->path.c_str());
+ return false;
+ }
+ } else {
+ if (errno != ENOENT) {
+ RNP_LOG("stat(%s): %s", key_store->path.c_str(), strerror(errno));
+ return false;
+ }
+ if (RNP_MKDIR(key_store->path.c_str(), S_IRWXU) != 0) {
+ RNP_LOG("mkdir(%s, S_IRWXU): %s", key_store->path.c_str(), strerror(errno));
+ return false;
+ }
+ }
+
+ for (auto &key : key_store->keys) {
+ char grip[PGP_FINGERPRINT_HEX_SIZE] = {0};
+ rnp::hex_encode(key.grip().data(), key.grip().size(), grip, sizeof(grip));
+ snprintf(path, sizeof(path), "%s/%s.key", key_store->path.c_str(), grip);
+
+ if (init_tmpfile_dest(&keydst, path, true)) {
+ RNP_LOG("failed to create file");
+ return false;
+ }
+
+ if (!rnp_key_store_gnupg_sexp_to_dst(&key, &keydst)) {
+ RNP_LOG("failed to write key to file");
+ dst_close(&keydst, true);
+ return false;
+ }
+
+ rc = dst_finish(&keydst) == RNP_SUCCESS;
+ dst_close(&keydst, !rc);
+
+ if (!rc) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /* write kbx/gpg store to the single file */
+ if (init_tmpfile_dest(&keydst, key_store->path.c_str(), true)) {
+ RNP_LOG("failed to create keystore file");
+ return false;
+ }
+
+ if (!rnp_key_store_write_to_dst(key_store, &keydst)) {
+ RNP_LOG("failed to write keys to file");
+ dst_close(&keydst, true);
+ return false;
+ }
+
+ rc = dst_finish(&keydst) == RNP_SUCCESS;
+ dst_close(&keydst, !rc);
+ return rc;
+}
+
+bool
+rnp_key_store_write_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst)
+{
+ switch (key_store->format) {
+ case PGP_KEY_STORE_GPG:
+ return rnp_key_store_pgp_write_to_dst(key_store, dst);
+ case PGP_KEY_STORE_KBX:
+ return rnp_key_store_kbx_to_dst(key_store, dst);
+ default:
+ RNP_LOG("Unsupported write to memory for key-store format: %d", key_store->format);
+ }
+
+ return false;
+}
+
+void
+rnp_key_store_clear(rnp_key_store_t *keyring)
+{
+ keyring->keybyfp.clear();
+ keyring->keys.clear();
+ keyring->blobs.clear();
+}
+
+size_t
+rnp_key_store_get_key_count(const rnp_key_store_t *keyring)
+{
+ return keyring->keys.size();
+}
+
+static bool
+rnp_key_store_refresh_subkey_grips(rnp_key_store_t *keyring, pgp_key_t *key)
+{
+ if (key->is_subkey()) {
+ RNP_LOG("wrong argument");
+ return false;
+ }
+
+ for (auto &skey : keyring->keys) {
+ bool found = false;
+
+ /* if we have primary_grip then we also added to subkey_grips */
+ if (!skey.is_subkey() || skey.has_primary_fp()) {
+ continue;
+ }
+
+ for (size_t i = 0; i < skey.sig_count(); i++) {
+ const pgp_subsig_t &subsig = skey.get_sig(i);
+
+ if (subsig.sig.type() != PGP_SIG_SUBKEY) {
+ continue;
+ }
+ if (subsig.sig.has_keyfp() && (key->fp() == subsig.sig.keyfp())) {
+ found = true;
+ break;
+ }
+ if (subsig.sig.has_keyid() && (key->keyid() == subsig.sig.keyid())) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ try {
+ key->link_subkey_fp(skey);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+static pgp_key_t *
+rnp_key_store_add_subkey(rnp_key_store_t *keyring, pgp_key_t *srckey, pgp_key_t *oldkey)
+{
+ pgp_key_t *primary = NULL;
+ if (oldkey) {
+ primary = rnp_key_store_get_primary_key(keyring, oldkey);
+ }
+ if (!primary) {
+ primary = rnp_key_store_get_primary_key(keyring, srckey);
+ }
+
+ if (oldkey) {
+ /* check for the weird case when same subkey has different primary keys */
+ if (srckey->has_primary_fp() && oldkey->has_primary_fp() &&
+ (srckey->primary_fp() != oldkey->primary_fp())) {
+ RNP_LOG_KEY("Warning: different primary keys for subkey %s", srckey);
+ pgp_key_t *srcprim = rnp_key_store_get_key_by_fpr(keyring, srckey->primary_fp());
+ if (srcprim && (srcprim != primary)) {
+ srcprim->remove_subkey_fp(srckey->fp());
+ }
+ }
+ /* in case we already have key let's merge it in */
+ if (!oldkey->merge(*srckey, primary)) {
+ RNP_LOG_KEY("failed to merge subkey %s", srckey);
+ RNP_LOG_KEY("primary key is %s", primary);
+ return NULL;
+ }
+ } else {
+ try {
+ keyring->keys.emplace_back();
+ oldkey = &keyring->keys.back();
+ keyring->keybyfp[srckey->fp()] = std::prev(keyring->keys.end());
+ *oldkey = pgp_key_t(*srckey);
+ if (primary) {
+ primary->link_subkey_fp(*oldkey);
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG_KEY("key %s copying failed", srckey);
+ RNP_LOG_KEY("primary key is %s", primary);
+ RNP_LOG("%s", e.what());
+ if (oldkey) {
+ keyring->keys.pop_back();
+ keyring->keybyfp.erase(srckey->fp());
+ }
+ return NULL;
+ }
+ }
+
+ /* validate all added keys if not disabled */
+ if (!keyring->disable_validation && !oldkey->validated()) {
+ oldkey->validate_subkey(primary, keyring->secctx);
+ }
+ if (!oldkey->refresh_data(primary, keyring->secctx)) {
+ RNP_LOG_KEY("Failed to refresh subkey %s data", srckey);
+ RNP_LOG_KEY("primary key is %s", primary);
+ }
+ return oldkey;
+}
+
+/* add a key to keyring */
+pgp_key_t *
+rnp_key_store_add_key(rnp_key_store_t *keyring, pgp_key_t *srckey)
+{
+ assert(srckey->type() && srckey->version());
+ pgp_key_t *added_key = rnp_key_store_get_key_by_fpr(keyring, srckey->fp());
+ /* we cannot merge G10 keys - so just return it */
+ if (added_key && (srckey->format == PGP_KEY_STORE_G10)) {
+ return added_key;
+ }
+ /* different processing for subkeys */
+ if (srckey->is_subkey()) {
+ return rnp_key_store_add_subkey(keyring, srckey, added_key);
+ }
+
+ if (added_key) {
+ if (!added_key->merge(*srckey)) {
+ RNP_LOG_KEY("failed to merge key %s", srckey);
+ return NULL;
+ }
+ } else {
+ try {
+ keyring->keys.emplace_back();
+ added_key = &keyring->keys.back();
+ keyring->keybyfp[srckey->fp()] = std::prev(keyring->keys.end());
+ *added_key = pgp_key_t(*srckey);
+ /* primary key may be added after subkeys, so let's handle this case correctly */
+ if (!rnp_key_store_refresh_subkey_grips(keyring, added_key)) {
+ RNP_LOG_KEY("failed to refresh subkey grips for %s", added_key);
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG_KEY("key %s copying failed", srckey);
+ RNP_LOG("%s", e.what());
+ if (added_key) {
+ keyring->keys.pop_back();
+ keyring->keybyfp.erase(srckey->fp());
+ }
+ return NULL;
+ }
+ }
+
+ /* validate all added keys if not disabled or already validated */
+ if (!keyring->disable_validation && !added_key->validated()) {
+ added_key->revalidate(*keyring);
+ } else if (!added_key->refresh_data(keyring->secctx)) {
+ RNP_LOG_KEY("Failed to refresh key %s data", srckey);
+ }
+ return added_key;
+}
+
+pgp_key_t *
+rnp_key_store_import_key(rnp_key_store_t * keyring,
+ pgp_key_t * srckey,
+ bool pubkey,
+ pgp_key_import_status_t *status)
+{
+ /* add public key */
+ pgp_key_t *exkey = rnp_key_store_get_key_by_fpr(keyring, srckey->fp());
+ size_t expackets = exkey ? exkey->rawpkt_count() : 0;
+ try {
+ pgp_key_t keycp(*srckey, pubkey);
+ keyring->disable_validation = true;
+ exkey = rnp_key_store_add_key(keyring, &keycp);
+ keyring->disable_validation = false;
+ if (!exkey) {
+ RNP_LOG("failed to add key to the keyring");
+ return NULL;
+ }
+ bool changed = exkey->rawpkt_count() > expackets;
+ if (changed || !exkey->validated()) {
+ /* this will revalidated primary key with all subkeys */
+ exkey->revalidate(*keyring);
+ }
+ if (status) {
+ *status = changed ? (expackets ? PGP_KEY_IMPORT_STATUS_UPDATED :
+ PGP_KEY_IMPORT_STATUS_NEW) :
+ PGP_KEY_IMPORT_STATUS_UNCHANGED;
+ }
+ return exkey;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ keyring->disable_validation = false;
+ return NULL;
+ }
+}
+
+pgp_key_t *
+rnp_key_store_get_signer_key(rnp_key_store_t *store, const pgp_signature_t *sig)
+{
+ pgp_key_search_t search;
+ // prefer using the issuer fingerprint when available
+ if (sig->has_keyfp()) {
+ search.by.fingerprint = sig->keyfp();
+ search.type = PGP_KEY_SEARCH_FINGERPRINT;
+ return rnp_key_store_search(store, &search, NULL);
+ }
+ // fall back to key id search
+ if (sig->has_keyid()) {
+ search.by.keyid = sig->keyid();
+ search.type = PGP_KEY_SEARCH_KEYID;
+ return rnp_key_store_search(store, &search, NULL);
+ }
+ return NULL;
+}
+
+static pgp_sig_import_status_t
+rnp_key_store_import_subkey_signature(rnp_key_store_t * keyring,
+ pgp_key_t * key,
+ const pgp_signature_t *sig)
+{
+ if ((sig->type() != PGP_SIG_SUBKEY) && (sig->type() != PGP_SIG_REV_SUBKEY)) {
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+ pgp_key_t *primary = rnp_key_store_get_signer_key(keyring, sig);
+ if (!primary || !key->has_primary_fp()) {
+ RNP_LOG("No primary grip or primary key");
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY;
+ }
+ if (primary->fp() != key->primary_fp()) {
+ RNP_LOG("Wrong subkey signature's signer.");
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+
+ try {
+ pgp_key_t tmpkey(key->pkt());
+ tmpkey.add_sig(*sig);
+ if (!tmpkey.refresh_data(primary, keyring->secctx)) {
+ RNP_LOG("Failed to add signature to the key.");
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+
+ size_t expackets = key->rawpkt_count();
+ key = rnp_key_store_add_key(keyring, &tmpkey);
+ if (!key) {
+ RNP_LOG("Failed to add key with imported sig to the keyring");
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+ return (key->rawpkt_count() > expackets) ? PGP_SIG_IMPORT_STATUS_NEW :
+ PGP_SIG_IMPORT_STATUS_UNCHANGED;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+}
+
+pgp_sig_import_status_t
+rnp_key_store_import_key_signature(rnp_key_store_t * keyring,
+ pgp_key_t * key,
+ const pgp_signature_t *sig)
+{
+ if (key->is_subkey()) {
+ return rnp_key_store_import_subkey_signature(keyring, key, sig);
+ }
+ if ((sig->type() != PGP_SIG_DIRECT) && (sig->type() != PGP_SIG_REV_KEY)) {
+ RNP_LOG("Wrong signature type: %d", (int) sig->type());
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+
+ try {
+ pgp_key_t tmpkey(key->pkt());
+ tmpkey.add_sig(*sig);
+ if (!tmpkey.refresh_data(keyring->secctx)) {
+ RNP_LOG("Failed to add signature to the key.");
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+
+ size_t expackets = key->rawpkt_count();
+ key = rnp_key_store_add_key(keyring, &tmpkey);
+ if (!key) {
+ RNP_LOG("Failed to add key with imported sig to the keyring");
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+ return (key->rawpkt_count() > expackets) ? PGP_SIG_IMPORT_STATUS_NEW :
+ PGP_SIG_IMPORT_STATUS_UNCHANGED;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+}
+
+pgp_key_t *
+rnp_key_store_import_signature(rnp_key_store_t * keyring,
+ const pgp_signature_t * sig,
+ pgp_sig_import_status_t *status)
+{
+ pgp_sig_import_status_t tmp_status = PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ if (!status) {
+ status = &tmp_status;
+ }
+ *status = PGP_SIG_IMPORT_STATUS_UNKNOWN;
+
+ /* we support only direct-key and key revocation signatures here */
+ if ((sig->type() != PGP_SIG_DIRECT) && (sig->type() != PGP_SIG_REV_KEY)) {
+ return NULL;
+ }
+
+ pgp_key_t *res_key = rnp_key_store_get_signer_key(keyring, sig);
+ if (!res_key || !res_key->is_primary()) {
+ *status = PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY;
+ return NULL;
+ }
+ *status = rnp_key_store_import_key_signature(keyring, res_key, sig);
+ return res_key;
+}
+
+bool
+rnp_key_store_remove_key(rnp_key_store_t *keyring, const pgp_key_t *key, bool subkeys)
+{
+ auto it = keyring->keybyfp.find(key->fp());
+ if (it == keyring->keybyfp.end()) {
+ return false;
+ }
+
+ /* cleanup primary_grip (or subkey)/subkey_grips */
+ if (key->is_primary() && key->subkey_count()) {
+ for (size_t i = 0; i < key->subkey_count(); i++) {
+ auto it = keyring->keybyfp.find(key->get_subkey_fp(i));
+ if (it == keyring->keybyfp.end()) {
+ continue;
+ }
+ /* if subkeys are deleted then no need to update grips */
+ if (subkeys) {
+ keyring->keys.erase(it->second);
+ keyring->keybyfp.erase(it);
+ continue;
+ }
+ it->second->unset_primary_fp();
+ }
+ }
+ if (key->is_subkey() && key->has_primary_fp()) {
+ pgp_key_t *primary = rnp_key_store_get_primary_key(keyring, key);
+ if (primary) {
+ primary->remove_subkey_fp(key->fp());
+ }
+ }
+
+ keyring->keys.erase(it->second);
+ keyring->keybyfp.erase(it);
+ return true;
+}
+
+const pgp_key_t *
+rnp_key_store_get_key_by_fpr(const rnp_key_store_t *keyring, const pgp_fingerprint_t &fpr)
+{
+ auto it = keyring->keybyfp.find(fpr);
+ if (it == keyring->keybyfp.end()) {
+ return NULL;
+ }
+ return &*it->second;
+}
+
+pgp_key_t *
+rnp_key_store_get_key_by_fpr(rnp_key_store_t *keyring, const pgp_fingerprint_t &fpr)
+{
+ auto it = keyring->keybyfp.find(fpr);
+ if (it == keyring->keybyfp.end()) {
+ return NULL;
+ }
+ return &*it->second;
+}
+
+pgp_key_t *
+rnp_key_store_get_primary_key(rnp_key_store_t *keyring, const pgp_key_t *subkey)
+{
+ if (!subkey->is_subkey()) {
+ return NULL;
+ }
+
+ if (subkey->has_primary_fp()) {
+ pgp_key_t *primary = rnp_key_store_get_key_by_fpr(keyring, subkey->primary_fp());
+ return primary && primary->is_primary() ? primary : NULL;
+ }
+
+ for (size_t i = 0; i < subkey->sig_count(); i++) {
+ const pgp_subsig_t &subsig = subkey->get_sig(i);
+ if (subsig.sig.type() != PGP_SIG_SUBKEY) {
+ continue;
+ }
+
+ pgp_key_t *primary = rnp_key_store_get_signer_key(keyring, &subsig.sig);
+ if (primary && primary->is_primary()) {
+ return primary;
+ }
+ }
+ return NULL;
+}
+
+static void
+grip_hash_mpi(rnp::Hash &hash, const pgp_mpi_t &val, const char name, bool lzero = true)
+{
+ size_t len = mpi_bytes(&val);
+ size_t idx = 0;
+ for (idx = 0; (idx < len) && !val.mpi[idx]; idx++)
+ ;
+
+ if (name) {
+ size_t hlen = idx >= len ? 0 : len - idx;
+ if ((len > idx) && lzero && (val.mpi[idx] & 0x80)) {
+ hlen++;
+ }
+
+ char buf[20] = {0};
+ snprintf(buf, sizeof(buf), "(1:%c%zu:", name, hlen);
+ hash.add(buf, strlen(buf));
+ }
+
+ if (idx < len) {
+ /* gcrypt prepends mpis with zero if higher bit is set */
+ if (lzero && (val.mpi[idx] & 0x80)) {
+ uint8_t zero = 0;
+ hash.add(&zero, 1);
+ }
+ hash.add(val.mpi + idx, len - idx);
+ }
+ if (name) {
+ hash.add(")", 1);
+ }
+}
+
+static void
+grip_hash_ecc_hex(rnp::Hash &hash, const char *hex, char name)
+{
+ pgp_mpi_t mpi = {};
+ mpi.len = rnp::hex_decode(hex, mpi.mpi, sizeof(mpi.mpi));
+ if (!mpi.len) {
+ RNP_LOG("wrong hex mpi");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ /* libgcrypt doesn't add leading zero when hashes ecc mpis */
+ return grip_hash_mpi(hash, mpi, name, false);
+}
+
+static void
+grip_hash_ec(rnp::Hash &hash, const pgp_ec_key_t &key)
+{
+ const ec_curve_desc_t *desc = get_curve_desc(key.curve);
+ if (!desc) {
+ RNP_LOG("unknown curve %d", (int) key.curve);
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ /* build uncompressed point from gx and gy */
+ pgp_mpi_t g = {};
+ g.mpi[0] = 0x04;
+ g.len = 1;
+ size_t len = rnp::hex_decode(desc->gx, g.mpi + g.len, sizeof(g.mpi) - g.len);
+ if (!len) {
+ RNP_LOG("wrong x mpi");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ g.len += len;
+ len = rnp::hex_decode(desc->gy, g.mpi + g.len, sizeof(g.mpi) - g.len);
+ if (!len) {
+ RNP_LOG("wrong y mpi");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ g.len += len;
+
+ /* p, a, b, g, n, q */
+ grip_hash_ecc_hex(hash, desc->p, 'p');
+ grip_hash_ecc_hex(hash, desc->a, 'a');
+ grip_hash_ecc_hex(hash, desc->b, 'b');
+ grip_hash_mpi(hash, g, 'g', false);
+ grip_hash_ecc_hex(hash, desc->n, 'n');
+
+ if ((key.curve == PGP_CURVE_ED25519) || (key.curve == PGP_CURVE_25519)) {
+ if (g.len < 1) {
+ RNP_LOG("wrong 25519 p");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ g.len = key.p.len - 1;
+ memcpy(g.mpi, key.p.mpi + 1, g.len);
+ grip_hash_mpi(hash, g, 'q', false);
+ } else {
+ grip_hash_mpi(hash, key.p, 'q', false);
+ }
+}
+
+/* keygrip is subjectKeyHash from pkcs#15 for RSA. */
+bool
+rnp_key_store_get_key_grip(const pgp_key_material_t *key, pgp_key_grip_t &grip)
+{
+ try {
+ auto hash = rnp::Hash::create(PGP_HASH_SHA1);
+ switch (key->alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ grip_hash_mpi(*hash, key->rsa.n, '\0');
+ break;
+ case PGP_PKA_DSA:
+ grip_hash_mpi(*hash, key->dsa.p, 'p');
+ grip_hash_mpi(*hash, key->dsa.q, 'q');
+ grip_hash_mpi(*hash, key->dsa.g, 'g');
+ grip_hash_mpi(*hash, key->dsa.y, 'y');
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ grip_hash_mpi(*hash, key->eg.p, 'p');
+ grip_hash_mpi(*hash, key->eg.g, 'g');
+ grip_hash_mpi(*hash, key->eg.y, 'y');
+ break;
+ case PGP_PKA_ECDH:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_SM2:
+ grip_hash_ec(*hash, key->ec);
+ break;
+ default:
+ RNP_LOG("unsupported public-key algorithm %d", (int) key->alg);
+ return false;
+ }
+ return hash->finish(grip.data()) == grip.size();
+ } catch (const std::exception &e) {
+ RNP_LOG("Grip calculation failed: %s", e.what());
+ return false;
+ }
+}
+
+pgp_key_t *
+rnp_key_store_search(rnp_key_store_t * keyring,
+ const pgp_key_search_t *search,
+ pgp_key_t * after)
+{
+ // since keys are distinguished by fingerprint then just do map lookup
+ if (search->type == PGP_KEY_SEARCH_FINGERPRINT) {
+ pgp_key_t *key = rnp_key_store_get_key_by_fpr(keyring, search->by.fingerprint);
+ if (after && (after != key)) {
+ RNP_LOG("searching with invalid after param");
+ return NULL;
+ }
+ // return NULL if after is specified
+ return after ? NULL : key;
+ }
+
+ // if after is provided, make sure it is a member of the appropriate list
+ auto it =
+ std::find_if(keyring->keys.begin(), keyring->keys.end(), [after](const pgp_key_t &key) {
+ return !after || (after == &key);
+ });
+ if (after && (it == keyring->keys.end())) {
+ RNP_LOG("searching with non-keyrings after param");
+ return NULL;
+ }
+ if (after) {
+ it = std::next(it);
+ }
+ it = std::find_if(it, keyring->keys.end(), [search](const pgp_key_t &key) {
+ return rnp_key_matches_search(&key, search);
+ });
+ return (it == keyring->keys.end()) ? NULL : &(*it);
+}
+
+rnp_key_store_t::rnp_key_store_t(pgp_key_store_format_t _format,
+ const std::string & _path,
+ rnp::SecurityContext & ctx)
+ : secctx(ctx)
+{
+ if (_format == PGP_KEY_STORE_UNKNOWN) {
+ RNP_LOG("Invalid key store format");
+ throw std::invalid_argument("format");
+ }
+ format = _format;
+ path = _path;
+}
+
+rnp_key_store_t::~rnp_key_store_t()
+{
+ rnp_key_store_clear(this);
+}