summaryrefslogtreecommitdiffstats
path: root/src/librekey
diff options
context:
space:
mode:
Diffstat (limited to 'src/librekey')
-rw-r--r--src/librekey/g23_sexp.hpp71
-rw-r--r--src/librekey/kbx_blob.hpp162
-rw-r--r--src/librekey/key_store_g10.cpp1243
-rw-r--r--src/librekey/key_store_g10.h42
-rw-r--r--src/librekey/key_store_kbx.cpp706
-rw-r--r--src/librekey/key_store_kbx.h36
-rw-r--r--src/librekey/key_store_pgp.cpp241
-rw-r--r--src/librekey/key_store_pgp.h83
-rw-r--r--src/librekey/rnp_key_store.cpp803
9 files changed, 3387 insertions, 0 deletions
diff --git a/src/librekey/g23_sexp.hpp b/src/librekey/g23_sexp.hpp
new file mode 100644
index 0000000..b888680
--- /dev/null
+++ b/src/librekey/g23_sexp.hpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2021, [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 RIBOSE, INC. AND CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * 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 RNP_G23_SEXP_HPP
+#define RNP_G23_SEXP_HPP
+
+#include "sexp/sexp.h"
+#include "sexp/ext-key-format.h"
+
+#define SXP_MAX_DEPTH 30
+
+class gnupg_sexp_t;
+typedef std::shared_ptr<gnupg_sexp_t> p_gnupg_sexp;
+
+class gnupg_sexp_t : public sexp::sexp_list_t {
+ /* write gnupg_sexp_t contents, adding padding, for the further encryption */
+ rnp::secure_vector<uint8_t> write_padded(size_t padblock) const;
+
+ public:
+ void
+ add(const std::string &str)
+ {
+ push_back(std::shared_ptr<sexp::sexp_string_t>(new sexp::sexp_string_t(str)));
+ };
+ void
+ add(const uint8_t *data, size_t size)
+ {
+ push_back(std::shared_ptr<sexp::sexp_string_t>(new sexp::sexp_string_t(data, size)));
+ };
+ void add(unsigned u);
+ p_gnupg_sexp add_sub();
+ void add_mpi(const std::string &name, const pgp_mpi_t &val);
+ void add_curve(const std::string &name, const pgp_ec_key_t &key);
+ void add_pubkey(const pgp_key_pkt_t &key);
+ void add_seckey(const pgp_key_pkt_t &key);
+ void add_protected_seckey(pgp_key_pkt_t & seckey,
+ const std::string & password,
+ rnp::SecurityContext &ctx);
+ bool parse(const char *r_bytes, size_t r_length, size_t depth = 1);
+ bool write(pgp_dest_t &dst) const noexcept;
+};
+
+class gnupg_extended_private_key_t : public ext_key_format::extended_private_key_t {
+ public:
+ bool parse(const char *r_bytes, size_t r_length, size_t depth = 1);
+};
+
+#endif
diff --git a/src/librekey/kbx_blob.hpp b/src/librekey/kbx_blob.hpp
new file mode 100644
index 0000000..274413c
--- /dev/null
+++ b/src/librekey/kbx_blob.hpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2021, [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 RIBOSE, INC. AND CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * 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 RNP_KBX_BLOB_HPP
+#define RNP_KBX_BLOB_HPP
+
+typedef enum : uint8_t {
+ KBX_EMPTY_BLOB = 0,
+ KBX_HEADER_BLOB = 1,
+ KBX_PGP_BLOB = 2,
+ KBX_X509_BLOB = 3
+} kbx_blob_type_t;
+
+class kbx_blob_t {
+ protected:
+ kbx_blob_type_t type_;
+ std::vector<uint8_t> image_;
+
+ uint8_t ru8(size_t idx);
+ uint16_t ru16(size_t idx);
+ uint32_t ru32(size_t idx);
+
+ public:
+ virtual ~kbx_blob_t() = default;
+ kbx_blob_t(std::vector<uint8_t> &data);
+ virtual bool
+ parse()
+ {
+ return true;
+ };
+
+ kbx_blob_type_t
+ type()
+ {
+ return type_;
+ }
+
+ std::vector<uint8_t> &
+ image()
+ {
+ return image_;
+ }
+
+ uint32_t
+ length() const noexcept
+ {
+ return image_.size();
+ }
+};
+
+class kbx_header_blob_t : public kbx_blob_t {
+ protected:
+ uint8_t version_{};
+ uint16_t flags_{};
+ uint32_t file_created_at_{};
+ uint32_t last_maintenance_run_{};
+
+ public:
+ kbx_header_blob_t(std::vector<uint8_t> &data) : kbx_blob_t(data){};
+ bool parse();
+
+ uint32_t
+ file_created_at()
+ {
+ return file_created_at_;
+ }
+};
+
+typedef struct {
+ uint8_t fp[PGP_FINGERPRINT_SIZE];
+ uint32_t keyid_offset;
+ uint16_t flags;
+} kbx_pgp_key_t;
+
+typedef struct {
+ uint32_t offset;
+ uint32_t length;
+ uint16_t flags;
+ uint8_t validity;
+} kbx_pgp_uid_t;
+
+typedef struct {
+ uint32_t expired;
+} kbx_pgp_sig_t;
+
+class kbx_pgp_blob_t : public kbx_blob_t {
+ protected:
+ uint8_t version_{};
+ uint16_t flags_{};
+ uint32_t keyblock_offset_{};
+ uint32_t keyblock_length_{};
+
+ std::vector<uint8_t> sn_{};
+ std::vector<kbx_pgp_key_t> keys_{};
+ std::vector<kbx_pgp_uid_t> uids_{};
+ std::vector<kbx_pgp_sig_t> sigs_{};
+
+ uint8_t ownertrust_{};
+ uint8_t all_validity_{};
+
+ uint32_t recheck_after_{};
+ uint32_t latest_timestamp_{};
+ uint32_t blob_created_at_{};
+
+ public:
+ kbx_pgp_blob_t(std::vector<uint8_t> &data) : kbx_blob_t(data){};
+
+ uint32_t
+ keyblock_offset()
+ {
+ return keyblock_offset_;
+ }
+
+ uint32_t
+ keyblock_length()
+ {
+ return keyblock_length_;
+ }
+
+ size_t
+ nkeys()
+ {
+ return keys_.size();
+ }
+ size_t
+ nuids()
+ {
+ return uids_.size();
+ }
+ size_t
+ nsigs()
+ {
+ return sigs_.size();
+ }
+
+ bool parse();
+};
+
+#endif
diff --git a/src/librekey/key_store_g10.cpp b/src/librekey/key_store_g10.cpp
new file mode 100644
index 0000000..dcf3fe1
--- /dev/null
+++ b/src/librekey/key_store_g10.cpp
@@ -0,0 +1,1243 @@
+/*
+ * Copyright (c) 2017-2022, [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.
+ */
+
+#include <memory>
+#include <sstream>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <time.h>
+#include "config.h"
+
+#include <librepgp/stream-packet.h>
+#include "key_store_pgp.h"
+#include "key_store_g10.h"
+
+#include "crypto/common.h"
+#include "crypto/mem.h"
+#include "crypto/cipher.hpp"
+#include "pgp-key.h"
+#include "time-utils.h"
+
+#include "g23_sexp.hpp"
+using namespace ext_key_format;
+using namespace sexp;
+
+#define G10_CBC_IV_SIZE 16
+
+#define G10_OCB_NONCE_SIZE 12
+
+#define G10_SHA1_HASH_SIZE 20
+
+#define G10_PROTECTED_AT_SIZE 15
+
+typedef struct format_info {
+ pgp_symm_alg_t cipher;
+ pgp_cipher_mode_t cipher_mode;
+ pgp_hash_alg_t hash_alg;
+ size_t cipher_block_size;
+ const char * g10_type;
+ size_t iv_size;
+ size_t tag_length;
+ bool with_associated_data;
+ bool disable_padding;
+} format_info;
+
+static bool g10_calculated_hash(const pgp_key_pkt_t &key,
+ const char * protected_at,
+ uint8_t * checksum);
+
+static const format_info formats[] = {{PGP_SA_AES_128,
+ PGP_CIPHER_MODE_CBC,
+ PGP_HASH_SHA1,
+ 16,
+ "openpgp-s2k3-sha1-aes-cbc",
+ G10_CBC_IV_SIZE,
+ 0,
+ false,
+ true},
+ {PGP_SA_AES_256,
+ PGP_CIPHER_MODE_CBC,
+ PGP_HASH_SHA1,
+ 16,
+ "openpgp-s2k3-sha1-aes256-cbc",
+ G10_CBC_IV_SIZE,
+ 0,
+ false,
+ true},
+ {PGP_SA_AES_128,
+ PGP_CIPHER_MODE_OCB,
+ PGP_HASH_SHA1,
+ 16,
+ "openpgp-s2k3-ocb-aes",
+ G10_OCB_NONCE_SIZE,
+ 16,
+ true,
+ true}};
+
+static const id_str_pair g10_alg_aliases[] = {
+ {PGP_PKA_RSA, "rsa"},
+ {PGP_PKA_RSA, "openpgp-rsa"},
+ {PGP_PKA_RSA, "oid.1.2.840.113549.1.1.1"},
+ {PGP_PKA_RSA, "oid.1.2.840.113549.1.1.1"},
+ {PGP_PKA_ELGAMAL, "elg"},
+ {PGP_PKA_ELGAMAL, "elgamal"},
+ {PGP_PKA_ELGAMAL, "openpgp-elg"},
+ {PGP_PKA_ELGAMAL, "openpgp-elg-sig"},
+ {PGP_PKA_DSA, "dsa"},
+ {PGP_PKA_DSA, "openpgp-dsa"},
+ {PGP_PKA_ECDSA, "ecc"},
+ {PGP_PKA_ECDSA, "ecdsa"},
+ {PGP_PKA_ECDH, "ecdh"},
+ {PGP_PKA_EDDSA, "eddsa"},
+ {0, NULL},
+};
+
+static const id_str_pair g10_curve_aliases[] = {
+ {PGP_CURVE_NIST_P_256, "NIST P-256"},
+ {PGP_CURVE_NIST_P_256, "1.2.840.10045.3.1.7"},
+ {PGP_CURVE_NIST_P_256, "prime256v1"},
+ {PGP_CURVE_NIST_P_256, "secp256r1"},
+ {PGP_CURVE_NIST_P_256, "nistp256"},
+ {PGP_CURVE_NIST_P_384, "NIST P-384"},
+ {PGP_CURVE_NIST_P_384, "secp384r1"},
+ {PGP_CURVE_NIST_P_384, "1.3.132.0.34"},
+ {PGP_CURVE_NIST_P_384, "nistp384"},
+ {PGP_CURVE_NIST_P_521, "NIST P-521"},
+ {PGP_CURVE_NIST_P_521, "secp521r1"},
+ {PGP_CURVE_NIST_P_521, "1.3.132.0.35"},
+ {PGP_CURVE_NIST_P_521, "nistp521"},
+ {PGP_CURVE_25519, "Curve25519"},
+ {PGP_CURVE_25519, "1.3.6.1.4.1.3029.1.5.1"},
+ {PGP_CURVE_ED25519, "Ed25519"},
+ {PGP_CURVE_ED25519, "1.3.6.1.4.1.11591.15.1"},
+ {PGP_CURVE_BP256, "brainpoolP256r1"},
+ {PGP_CURVE_BP256, "1.3.36.3.3.2.8.1.1.7"},
+ {PGP_CURVE_BP384, "brainpoolP384r1"},
+ {PGP_CURVE_BP384, "1.3.36.3.3.2.8.1.1.11"},
+ {PGP_CURVE_BP512, "brainpoolP512r1"},
+ {PGP_CURVE_BP512, "1.3.36.3.3.2.8.1.1.13"},
+ {PGP_CURVE_P256K1, "secp256k1"},
+ {PGP_CURVE_P256K1, "1.3.132.0.10"},
+ {0, NULL},
+};
+
+static const id_str_pair g10_curve_names[] = {
+ {PGP_CURVE_NIST_P_256, "NIST P-256"},
+ {PGP_CURVE_NIST_P_384, "NIST P-384"},
+ {PGP_CURVE_NIST_P_521, "NIST P-521"},
+ {PGP_CURVE_ED25519, "Ed25519"},
+ {PGP_CURVE_25519, "Curve25519"},
+ {PGP_CURVE_BP256, "brainpoolP256r1"},
+ {PGP_CURVE_BP384, "brainpoolP384r1"},
+ {PGP_CURVE_BP512, "brainpoolP512r1"},
+ {PGP_CURVE_P256K1, "secp256k1"},
+ {0, NULL},
+};
+
+static const format_info *
+find_format(pgp_symm_alg_t cipher, pgp_cipher_mode_t mode, pgp_hash_alg_t hash_alg)
+{
+ for (size_t i = 0; i < ARRAY_SIZE(formats); i++) {
+ if (formats[i].cipher == cipher && formats[i].cipher_mode == mode &&
+ formats[i].hash_alg == hash_alg) {
+ return &formats[i];
+ }
+ }
+ return NULL;
+}
+
+static const format_info *
+parse_format(const char *format, size_t format_len)
+{
+ for (size_t i = 0; i < ARRAY_SIZE(formats); i++) {
+ if (strlen(formats[i].g10_type) == format_len &&
+ !strncmp(formats[i].g10_type, format, format_len)) {
+ return &formats[i];
+ }
+ }
+ return NULL;
+}
+
+#define STR_HELPER(x) #x
+#define STR(x) STR_HELPER(x)
+
+void
+gnupg_sexp_t::add(unsigned u)
+{
+ char s[sizeof(STR(UINT_MAX)) + 1];
+ snprintf(s, sizeof(s), "%u", u);
+ push_back(std::make_shared<sexp_string_t>(s));
+}
+
+std::shared_ptr<gnupg_sexp_t>
+gnupg_sexp_t::add_sub()
+{
+ auto res = std::make_shared<gnupg_sexp_t>();
+ push_back(res);
+ return res;
+}
+
+/*
+ * Parse S-expression
+ * https://people.csail.mit.edu/rivest/Sexp.txt
+ * sexp library supports canonical and advanced transport formats
+ * as well as base64 encoding of canonical
+ */
+
+bool
+gnupg_sexp_t::parse(const char *r_bytes, size_t r_length, size_t depth)
+{
+ bool res = false;
+ std::istringstream iss(std::string(r_bytes, r_length));
+ try {
+ sexp_input_stream_t sis(&iss, depth);
+ sexp_list_t::parse(sis.set_byte_size(8)->get_char());
+ res = true;
+ } catch (sexp_exception_t &e) {
+ RNP_LOG("%s", e.what());
+ }
+ return res;
+}
+
+/*
+ * Parse gnupg extended private key file ("G23")
+ * https://github.com/gpg/gnupg/blob/main/agent/keyformat.txt
+ */
+
+bool
+gnupg_extended_private_key_t::parse(const char *r_bytes, size_t r_length, size_t depth)
+{
+ bool res = false;
+ std::istringstream iss(std::string(r_bytes, r_length));
+ try {
+ ext_key_input_stream_t g23_is(&iss, depth);
+ g23_is.scan(*this);
+ res = true;
+ } catch (sexp_exception_t &e) {
+ RNP_LOG("%s", e.what());
+ }
+ return res;
+}
+
+static const sexp_list_t *
+lookup_var(const sexp_list_t *list, const std::string &name) noexcept
+{
+ const sexp_list_t *res = nullptr;
+ // We are looking for a list element (condition 1)
+ // that:
+ // -- has at least two SEXP elements (condition 2)
+ // -- has a SEXP string at 0 postion (condition 3)
+ // matching given name (condition 4)
+ auto match = [name](const std::shared_ptr<sexp_object_t> &ptr) {
+ bool r = false;
+ auto r1 = ptr->sexp_list_view();
+ if (r1 && r1->size() >= 2) { // conditions (1) and (2)
+ auto r2 = r1->sexp_string_at(0);
+ if (r2 && r2 == name) // conditions (3) and (4)
+ r = true;
+ }
+ return r;
+ };
+ auto r3 = std::find_if(list->begin(), list->end(), match);
+ if (r3 == list->end())
+ RNP_LOG("Haven't got variable '%s'", name.c_str());
+ else
+ res = (*r3)->sexp_list_view();
+ return res;
+}
+
+static const sexp_string_t *
+lookup_var_data(const sexp_list_t *list, const std::string &name) noexcept
+{
+ const sexp_list_t *var = lookup_var(list, name);
+ if (!var) {
+ return NULL;
+ }
+
+ if (!var->at(1)->is_sexp_string()) {
+ RNP_LOG("Expected block value");
+ return NULL;
+ }
+
+ return var->sexp_string_at(1);
+}
+
+static bool
+read_mpi(const sexp_list_t *list, const std::string &name, pgp_mpi_t &val) noexcept
+{
+ const sexp_string_t *data = lookup_var_data(list, name);
+ if (!data) {
+ return false;
+ }
+
+ /* strip leading zero */
+ const auto &bytes = data->get_string();
+ if ((bytes.size() > 1) && !bytes[0] && (bytes[1] & 0x80)) {
+ return mem2mpi(&val, bytes.data() + 1, bytes.size() - 1);
+ }
+ return mem2mpi(&val, bytes.data(), bytes.size());
+}
+
+static bool
+read_curve(const sexp_list_t *list, const std::string &name, pgp_ec_key_t &key) noexcept
+{
+ const sexp_string_t *data = lookup_var_data(list, name);
+ if (!data) {
+ return false;
+ }
+
+ const auto &bytes = data->get_string();
+ pgp_curve_t curve = static_cast<pgp_curve_t>(
+ id_str_pair::lookup(g10_curve_aliases, data->get_string(), PGP_CURVE_UNKNOWN));
+ if (curve != PGP_CURVE_UNKNOWN) {
+ key.curve = curve;
+ return true;
+ }
+ RNP_LOG("Unknown curve: %.*s", (int) bytes.size(), (char *) bytes.data());
+ return false;
+}
+
+void
+gnupg_sexp_t::add_mpi(const std::string &name, const pgp_mpi_t &mpi)
+{
+ auto sub_s_exp = add_sub();
+ sub_s_exp->push_back(std::make_shared<sexp_string_t>(name));
+ auto value_block = std::make_shared<sexp_string_t>();
+ sub_s_exp->push_back(value_block);
+
+ sexp_simple_string_t data;
+ size_t len = mpi_bytes(&mpi);
+ size_t idx;
+
+ for (idx = 0; (idx < len) && !mpi.mpi[idx]; idx++)
+ ;
+
+ if (idx < len) {
+ if (mpi.mpi[idx] & 0x80) {
+ data.append(0);
+ data.std::basic_string<uint8_t>::append(mpi.mpi + idx, len - idx);
+ } else {
+ data.assign(mpi.mpi + idx, mpi.mpi + len);
+ }
+ value_block->set_string(data);
+ }
+}
+
+void
+gnupg_sexp_t::add_curve(const std::string &name, const pgp_ec_key_t &key)
+{
+ const char *curve = id_str_pair::lookup(g10_curve_names, key.curve, NULL);
+ if (!curve) {
+ RNP_LOG("unknown curve");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ auto psub_s_exp = add_sub();
+ psub_s_exp->add(name);
+ psub_s_exp->add(curve);
+
+ if ((key.curve != PGP_CURVE_ED25519) && (key.curve != PGP_CURVE_25519)) {
+ return;
+ }
+
+ psub_s_exp = add_sub();
+ psub_s_exp->add("flags");
+ psub_s_exp->add((key.curve == PGP_CURVE_ED25519) ? "eddsa" : "djb-tweak");
+}
+
+static bool
+parse_pubkey(pgp_key_pkt_t &pubkey, const sexp_list_t *s_exp, pgp_pubkey_alg_t alg)
+{
+ pubkey.version = PGP_V4;
+ pubkey.alg = alg;
+ pubkey.material.alg = alg;
+ switch (alg) {
+ case PGP_PKA_DSA:
+ if (!read_mpi(s_exp, "p", pubkey.material.dsa.p) ||
+ !read_mpi(s_exp, "q", pubkey.material.dsa.q) ||
+ !read_mpi(s_exp, "g", pubkey.material.dsa.g) ||
+ !read_mpi(s_exp, "y", pubkey.material.dsa.y)) {
+ return false;
+ }
+ break;
+
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ if (!read_mpi(s_exp, "n", pubkey.material.rsa.n) ||
+ !read_mpi(s_exp, "e", pubkey.material.rsa.e)) {
+ return false;
+ }
+ break;
+
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ if (!read_mpi(s_exp, "p", pubkey.material.eg.p) ||
+ !read_mpi(s_exp, "g", pubkey.material.eg.g) ||
+ !read_mpi(s_exp, "y", pubkey.material.eg.y)) {
+ return false;
+ }
+ break;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_ECDH:
+ case PGP_PKA_EDDSA:
+ if (!read_curve(s_exp, "curve", pubkey.material.ec) ||
+ !read_mpi(s_exp, "q", pubkey.material.ec.p)) {
+ return false;
+ }
+ if (pubkey.material.ec.curve == PGP_CURVE_ED25519) {
+ /* need to adjust it here since 'ecc' key type defaults to ECDSA */
+ pubkey.alg = PGP_PKA_EDDSA;
+ pubkey.material.alg = PGP_PKA_EDDSA;
+ }
+ break;
+ default:
+ RNP_LOG("Unsupported public key algorithm: %d", (int) alg);
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+parse_seckey(pgp_key_pkt_t &seckey, const sexp_list_t *s_exp, pgp_pubkey_alg_t alg)
+{
+ switch (alg) {
+ case PGP_PKA_DSA:
+ if (!read_mpi(s_exp, "x", seckey.material.dsa.x)) {
+ return false;
+ }
+ break;
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ if (!read_mpi(s_exp, "d", seckey.material.rsa.d) ||
+ !read_mpi(s_exp, "p", seckey.material.rsa.p) ||
+ !read_mpi(s_exp, "q", seckey.material.rsa.q) ||
+ !read_mpi(s_exp, "u", seckey.material.rsa.u)) {
+ return false;
+ }
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ if (!read_mpi(s_exp, "x", seckey.material.eg.x)) {
+ return false;
+ }
+ break;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_ECDH:
+ case PGP_PKA_EDDSA:
+ if (!read_mpi(s_exp, "d", seckey.material.ec.x)) {
+ return false;
+ }
+ break;
+ default:
+ RNP_LOG("Unsupported public key algorithm: %d", (int) alg);
+ return false;
+ }
+
+ seckey.material.secret = true;
+ return true;
+}
+
+static bool
+decrypt_protected_section(const sexp_simple_string_t &encrypted_data,
+ const pgp_key_pkt_t & seckey,
+ const std::string & password,
+ gnupg_sexp_t & r_s_exp,
+ uint8_t * associated_data,
+ size_t associated_data_len)
+{
+ const format_info * info = NULL;
+ unsigned keysize = 0;
+ uint8_t derived_key[PGP_MAX_KEY_SIZE];
+ uint8_t * decrypted_data = NULL;
+ size_t decrypted_data_len = 0;
+ size_t output_written = 0;
+ size_t input_consumed = 0;
+ std::unique_ptr<Cipher> dec;
+ bool ret = false;
+
+ const char *decrypted_bytes;
+ size_t s_exp_len;
+
+ // sanity checks
+ const pgp_key_protection_t &prot = seckey.sec_protection;
+ keysize = pgp_key_size(prot.symm_alg);
+ if (!keysize) {
+ RNP_LOG("parse_seckey: unknown symmetric algo");
+ goto done;
+ }
+ // find the protection format in our table
+ info = find_format(prot.symm_alg, prot.cipher_mode, prot.s2k.hash_alg);
+ if (!info) {
+ RNP_LOG("Unsupported format, alg: %d, chiper_mode: %d, hash: %d",
+ prot.symm_alg,
+ prot.cipher_mode,
+ prot.s2k.hash_alg);
+ goto done;
+ }
+
+ // derive the key
+ if (pgp_s2k_iterated(prot.s2k.hash_alg,
+ derived_key,
+ keysize,
+ password.c_str(),
+ prot.s2k.salt,
+ prot.s2k.iterations)) {
+ RNP_LOG("pgp_s2k_iterated failed");
+ goto done;
+ }
+
+ // decrypt
+ decrypted_data = (uint8_t *) malloc(encrypted_data.size());
+ if (decrypted_data == NULL) {
+ RNP_LOG("can't allocate memory");
+ goto done;
+ }
+ dec = Cipher::decryption(
+ info->cipher, info->cipher_mode, info->tag_length, info->disable_padding);
+ if (!dec || !dec->set_key(derived_key, keysize)) {
+ goto done;
+ }
+ if (associated_data != nullptr && associated_data_len != 0) {
+ if (!dec->set_ad(associated_data, associated_data_len)) {
+ goto done;
+ }
+ }
+ // Nonce shall be the last chunk of associated data
+ if (!dec->set_iv(prot.iv, info->iv_size)) {
+ goto done;
+ }
+ if (!dec->finish(decrypted_data,
+ encrypted_data.size(),
+ &output_written,
+ encrypted_data.data(),
+ encrypted_data.size(),
+ &input_consumed)) {
+ goto done;
+ }
+ decrypted_data_len = output_written;
+ s_exp_len = decrypted_data_len;
+ decrypted_bytes = (const char *) decrypted_data;
+
+ // parse and validate the decrypted s-exp
+
+ if (!r_s_exp.parse(decrypted_bytes, s_exp_len, SXP_MAX_DEPTH)) {
+ goto done;
+ }
+ if (!r_s_exp.size() || r_s_exp.at(0)->is_sexp_string()) {
+ RNP_LOG("Hasn't got sub s-exp with key data.");
+ goto done;
+ }
+ ret = true;
+done:
+ if (!ret) {
+ r_s_exp.clear();
+ }
+ secure_clear(decrypted_data, decrypted_data_len);
+ free(decrypted_data);
+ return ret;
+}
+
+static bool
+parse_protected_seckey(pgp_key_pkt_t &seckey, const sexp_list_t *list, const char *password)
+{
+ // find and validate the protected section
+ const sexp_list_t *protected_key = lookup_var(list, "protected");
+ if (!protected_key) {
+ RNP_LOG("missing protected section");
+ return false;
+ }
+ if (protected_key->size() != 4 || !protected_key->at(1)->is_sexp_string() ||
+ protected_key->at(2)->is_sexp_string() || !protected_key->at(3)->is_sexp_string()) {
+ RNP_LOG("Wrong protected format, expected: (protected mode (params) "
+ "encrypted_octet_string)\n");
+ return false;
+ }
+
+ // lookup the protection format
+ auto & fmt_bt = protected_key->sexp_string_at(1)->get_string();
+ const format_info *format = parse_format((const char *) fmt_bt.data(), fmt_bt.size());
+ if (!format) {
+ RNP_LOG("Unsupported protected mode: '%.*s'\n",
+ (int) fmt_bt.size(),
+ (const char *) fmt_bt.data());
+ return false;
+ }
+
+ // fill in some fields based on the lookup above
+ pgp_key_protection_t &prot = seckey.sec_protection;
+ prot.symm_alg = format->cipher;
+ prot.cipher_mode = format->cipher_mode;
+ prot.s2k.hash_alg = format->hash_alg;
+
+ // locate and validate the protection parameters
+ auto params = protected_key->sexp_list_at(2);
+ if (params->size() != 2 || params->at(0)->is_sexp_string() ||
+ !params->at(1)->is_sexp_string()) {
+ RNP_LOG("Wrong params format, expected: ((hash salt no_of_iterations) iv)\n");
+ return false;
+ }
+
+ // locate and validate the (hash salt no_of_iterations) exp
+ auto alg = params->sexp_list_at(0);
+ if (alg->size() != 3 || !alg->at(0)->is_sexp_string() || !alg->at(1)->is_sexp_string() ||
+ !alg->at(2)->is_sexp_string()) {
+ RNP_LOG("Wrong params sub-level format, expected: (hash salt no_of_iterations)\n");
+ return false;
+ }
+ auto &hash_bt = alg->sexp_string_at(0)->get_string();
+ if (hash_bt != "sha1") {
+ RNP_LOG("Wrong hashing algorithm, should be sha1 but %.*s\n",
+ (int) hash_bt.size(),
+ (const char *) hash_bt.data());
+ return false;
+ }
+
+ // fill in some constant values
+ prot.s2k.hash_alg = PGP_HASH_SHA1;
+ prot.s2k.usage = PGP_S2KU_ENCRYPTED_AND_HASHED;
+ prot.s2k.specifier = PGP_S2KS_ITERATED_AND_SALTED;
+
+ // check salt size
+ auto &salt_bt = alg->sexp_string_at(1)->get_string();
+ if (salt_bt.size() != PGP_SALT_SIZE) {
+ RNP_LOG("Wrong salt size, should be %d but %d\n", PGP_SALT_SIZE, (int) salt_bt.size());
+ return false;
+ }
+
+ // salt
+ memcpy(prot.s2k.salt, salt_bt.data(), salt_bt.size());
+ // s2k iterations
+ auto iter = alg->sexp_string_at(2);
+ prot.s2k.iterations = iter->as_unsigned();
+ if (prot.s2k.iterations == UINT_MAX) {
+ RNP_LOG("Wrong numbers of iteration, %.*s\n",
+ (int) iter->get_string().size(),
+ (const char *) iter->get_string().data());
+ return false;
+ }
+
+ // iv
+ auto &iv_bt = params->sexp_string_at(1)->get_string();
+ if (iv_bt.size() != format->iv_size) {
+ RNP_LOG("Wrong nonce size, should be %zu but %zu\n", format->iv_size, iv_bt.size());
+ return false;
+ }
+ memcpy(prot.iv, iv_bt.data(), iv_bt.size());
+
+ // we're all done if no password was provided (decryption not requested)
+ if (!password) {
+ seckey.material.secret = false;
+ return true;
+ }
+
+ // password was provided, so decrypt
+ auto & enc_bt = protected_key->sexp_string_at(3)->get_string();
+ gnupg_sexp_t decrypted_s_exp;
+
+ // Build associated data (AD) that is not included in the ciphertext but that should be
+ // authenticated. gnupg builds AD as follows (file 'protect.c' do_encryption/do_decryption
+ // functions)
+ // -- "protected-private-key" section content
+ // -- less "protected" subsection
+ // -- serialized in canonical format
+ std::string associated_data;
+ if (format->with_associated_data) {
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.var_put_char('(');
+ for_each(list->begin(), list->end(), [&](const std::shared_ptr<sexp_object_t> &obj) {
+ if (obj->sexp_list_view() != protected_key)
+ obj->print_canonical(&os);
+ });
+ os.var_put_char(')');
+ associated_data = oss.str();
+ }
+
+ if (!decrypt_protected_section(
+ enc_bt,
+ seckey,
+ password,
+ decrypted_s_exp,
+ format->with_associated_data ? (uint8_t *) associated_data.data() : nullptr,
+ format->with_associated_data ? associated_data.length() : 0)) {
+ return false;
+ }
+ // see if we have a protected-at section
+ char protected_at[G10_PROTECTED_AT_SIZE] = {0};
+ auto protected_at_data = lookup_var_data(list, "protected-at");
+ if (protected_at_data) {
+ if (protected_at_data->get_string().size() != G10_PROTECTED_AT_SIZE) {
+ RNP_LOG("protected-at has wrong length: %zu, expected, %d\n",
+ protected_at_data->get_string().size(),
+ G10_PROTECTED_AT_SIZE);
+ return false;
+ }
+ memcpy(protected_at,
+ protected_at_data->get_string().data(),
+ protected_at_data->get_string().size());
+ }
+ // parse MPIs
+ if (!parse_seckey(seckey, decrypted_s_exp.sexp_list_at(0), seckey.alg)) {
+ RNP_LOG("failed to parse seckey");
+ return false;
+ }
+ // check hash, if present
+ if (decrypted_s_exp.size() > 1) {
+ if (decrypted_s_exp.at(1)->is_sexp_string()) {
+ RNP_LOG("Wrong hash block type.");
+ return false;
+ }
+ auto sub_el = decrypted_s_exp.sexp_list_at(1);
+ if (sub_el->size() < 3 || !sub_el->at(0)->is_sexp_string() ||
+ !sub_el->at(1)->is_sexp_string() || !sub_el->at(2)->is_sexp_string()) {
+ RNP_LOG("Wrong hash block structure.");
+ return false;
+ }
+
+ auto &hkey = sub_el->sexp_string_at(0)->get_string();
+ if (hkey != "hash") {
+ RNP_LOG("Has got wrong hash block at encrypted key data.");
+ return false;
+ }
+ auto &halg = sub_el->sexp_string_at(1)->get_string();
+ if (halg != "sha1") {
+ RNP_LOG("Supported only sha1 hash at encrypted private key.");
+ return false;
+ }
+ uint8_t checkhash[G10_SHA1_HASH_SIZE];
+ if (!g10_calculated_hash(seckey, protected_at, checkhash)) {
+ RNP_LOG("failed to calculate hash");
+ return false;
+ }
+ auto &hval = sub_el->sexp_string_at(2)->get_string();
+ if (hval.size() != G10_SHA1_HASH_SIZE ||
+ memcmp(checkhash, hval.data(), G10_SHA1_HASH_SIZE)) {
+ RNP_LOG("Incorrect hash at encrypted private key.");
+ return false;
+ }
+ }
+ seckey.material.secret = true;
+ return true;
+}
+
+static bool
+g23_parse_seckey(pgp_key_pkt_t &seckey,
+ const uint8_t *data,
+ size_t data_len,
+ const char * password)
+{
+ gnupg_extended_private_key_t g23_extended_key;
+
+ const char *bytes = (const char *) data;
+ if (!g23_extended_key.parse(bytes, data_len, SXP_MAX_DEPTH)) {
+ RNP_LOG("Failed to parse s-exp.");
+ return false;
+ }
+ // Although the library parses full g23 extended key
+ // we extract and use g10 part only
+ const sexp_list_t &g10_key = g23_extended_key.key;
+
+ /* expected format:
+ * (<type>
+ * (<algo>
+ * (x <mpi>)
+ * (y <mpi>)
+ * )
+ * )
+ */
+
+ if (g10_key.size() != 2 || !g10_key.at(0)->is_sexp_string() ||
+ !g10_key.at(1)->is_sexp_list()) {
+ RNP_LOG("Wrong format, expected: (<type> (...))");
+ return false;
+ }
+
+ bool is_protected = false;
+
+ auto &name = g10_key.sexp_string_at(0)->get_string();
+ if (name == "private-key") {
+ is_protected = false;
+ } else if (name == "protected-private-key") {
+ is_protected = true;
+ } else {
+ RNP_LOG("Unsupported top-level block: '%.*s'",
+ (int) name.size(),
+ (const char *) name.data());
+ return false;
+ }
+
+ auto alg_s_exp = g10_key.sexp_list_at(1);
+ if (alg_s_exp->size() < 2) {
+ RNP_LOG("Wrong count of algorithm-level elements: %zu", alg_s_exp->size());
+ return false;
+ }
+
+ if (!alg_s_exp->at(0)->is_sexp_string()) {
+ RNP_LOG("Expected block with algorithm name, but has s-exp");
+ return false;
+ }
+
+ auto & alg_bt = alg_s_exp->sexp_string_at(0)->get_string();
+ pgp_pubkey_alg_t alg = static_cast<pgp_pubkey_alg_t>(
+ id_str_pair::lookup(g10_alg_aliases, alg_bt.c_str(), PGP_PKA_NOTHING));
+ if (alg == PGP_PKA_NOTHING) {
+ RNP_LOG(
+ "Unsupported algorithm: '%.*s'", (int) alg_bt.size(), (const char *) alg_bt.data());
+ return false;
+ }
+
+ bool ret = false;
+ if (!parse_pubkey(seckey, alg_s_exp, alg)) {
+ RNP_LOG("failed to parse pubkey");
+ goto done;
+ }
+
+ if (is_protected) {
+ if (!parse_protected_seckey(seckey, alg_s_exp, password)) {
+ goto done;
+ }
+ } else {
+ seckey.sec_protection.s2k.usage = PGP_S2KU_NONE;
+ seckey.sec_protection.symm_alg = PGP_SA_PLAINTEXT;
+ seckey.sec_protection.s2k.hash_alg = PGP_HASH_UNKNOWN;
+ if (!parse_seckey(seckey, alg_s_exp, alg)) {
+ RNP_LOG("failed to parse seckey");
+ goto done;
+ }
+ }
+ ret = true;
+done:
+ if (!ret) {
+ seckey = pgp_key_pkt_t();
+ }
+ return ret;
+}
+
+pgp_key_pkt_t *
+g10_decrypt_seckey(const pgp_rawpacket_t &raw,
+ const pgp_key_pkt_t & pubkey,
+ const char * password)
+{
+ if (!password) {
+ return NULL;
+ }
+ auto seckey = std::unique_ptr<pgp_key_pkt_t>(new pgp_key_pkt_t(pubkey, false));
+ if (!g23_parse_seckey(*seckey, raw.raw.data(), raw.raw.size(), password)) {
+ return NULL;
+ }
+ /* g10 has the same 'ecc' algo for ECDSA/ECDH/EDDSA. Probably should be better place to fix
+ * this. */
+ seckey->alg = pubkey.alg;
+ seckey->material.alg = pubkey.material.alg;
+ return seckey.release();
+}
+
+static bool
+copy_secret_fields(pgp_key_pkt_t &dst, const pgp_key_pkt_t &src)
+{
+ switch (src.alg) {
+ case PGP_PKA_DSA:
+ dst.material.dsa.x = src.material.dsa.x;
+ break;
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ dst.material.rsa.d = src.material.rsa.d;
+ dst.material.rsa.p = src.material.rsa.p;
+ dst.material.rsa.q = src.material.rsa.q;
+ dst.material.rsa.u = src.material.rsa.u;
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ dst.material.eg.x = src.material.eg.x;
+ break;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_ECDH:
+ case PGP_PKA_EDDSA:
+ dst.material.ec.x = src.material.ec.x;
+ break;
+ default:
+ RNP_LOG("Unsupported public key algorithm: %d", (int) src.alg);
+ return false;
+ }
+
+ dst.material.secret = src.material.secret;
+ dst.sec_protection = src.sec_protection;
+ dst.tag = is_subkey_pkt(dst.tag) ? PGP_PKT_SECRET_SUBKEY : PGP_PKT_SECRET_KEY;
+ return true;
+}
+
+bool
+rnp_key_store_g10_from_src(rnp_key_store_t * key_store,
+ pgp_source_t * src,
+ const pgp_key_provider_t *key_provider)
+{
+ try {
+ /* read src to the memory */
+ rnp::MemorySource memsrc(*src);
+ /* parse secret key: fills material and sec_protection only */
+ pgp_key_pkt_t seckey;
+ if (!g23_parse_seckey(seckey, (uint8_t *) memsrc.memory(), memsrc.size(), NULL)) {
+ return false;
+ }
+ /* copy public key fields if any */
+ pgp_key_t key;
+ if (key_provider) {
+ pgp_key_request_ctx_t req_ctx(PGP_OP_MERGE_INFO, false, PGP_KEY_SEARCH_GRIP);
+ if (!rnp_key_store_get_key_grip(&seckey.material, req_ctx.search.by.grip)) {
+ return false;
+ }
+
+ const pgp_key_t *pubkey = pgp_request_key(key_provider, &req_ctx);
+ if (!pubkey) {
+ return false;
+ }
+
+ /* public key packet has some more info then the secret part */
+ key = pgp_key_t(*pubkey, true);
+ if (!copy_secret_fields(key.pkt(), seckey)) {
+ return false;
+ }
+ } else {
+ key.set_pkt(std::move(seckey));
+ }
+ /* set rawpkt */
+ key.set_rawpkt(
+ pgp_rawpacket_t((uint8_t *) memsrc.memory(), memsrc.size(), PGP_PKT_RESERVED));
+ key.format = PGP_KEY_STORE_G10;
+ if (!rnp_key_store_add_key(key_store, &key)) {
+ return false;
+ }
+ return true;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+}
+
+/*
+ * Write G10 S-exp to buffer
+ *
+ * Supported format: (1:a2:ab(3:asd1:a))
+ */
+bool
+gnupg_sexp_t::write(pgp_dest_t &dst) const noexcept
+{
+ bool res = false;
+ try {
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ print_canonical(&os);
+ const std::string &s = oss.str();
+ const char * ss = s.c_str();
+ dst_write(&dst, ss, s.size());
+ res = (dst.werr == RNP_SUCCESS);
+
+ } catch (...) {
+ }
+
+ return res;
+}
+
+void
+gnupg_sexp_t::add_pubkey(const pgp_key_pkt_t &key)
+{
+ switch (key.alg) {
+ case PGP_PKA_DSA:
+ add("dsa");
+ add_mpi("p", key.material.dsa.p);
+ add_mpi("q", key.material.dsa.q);
+ add_mpi("g", key.material.dsa.g);
+ add_mpi("y", key.material.dsa.y);
+ break;
+ case PGP_PKA_RSA_SIGN_ONLY:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA:
+ add("rsa");
+ add_mpi("n", key.material.rsa.n);
+ add_mpi("e", key.material.rsa.e);
+ break;
+ case PGP_PKA_ELGAMAL:
+ add("elg");
+ add_mpi("p", key.material.eg.p);
+ add_mpi("g", key.material.eg.g);
+ add_mpi("y", key.material.eg.y);
+ break;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_ECDH:
+ case PGP_PKA_EDDSA:
+ add("ecc");
+ add_curve("curve", key.material.ec);
+ add_mpi("q", key.material.ec.p);
+ break;
+ default:
+ RNP_LOG("Unsupported public key algorithm: %d", (int) key.alg);
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+}
+
+void
+gnupg_sexp_t::add_seckey(const pgp_key_pkt_t &key)
+{
+ switch (key.alg) {
+ case PGP_PKA_DSA:
+ add_mpi("x", key.material.dsa.x);
+ break;
+ case PGP_PKA_RSA_SIGN_ONLY:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA:
+ add_mpi("d", key.material.rsa.d);
+ add_mpi("p", key.material.rsa.p);
+ add_mpi("q", key.material.rsa.q);
+ add_mpi("u", key.material.rsa.u);
+ break;
+ case PGP_PKA_ELGAMAL:
+ add_mpi("x", key.material.eg.x);
+ break;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_ECDH:
+ case PGP_PKA_EDDSA: {
+ add_mpi("d", key.material.ec.x);
+ break;
+ }
+ default:
+ RNP_LOG("Unsupported public key algorithm: %d", (int) key.alg);
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+}
+
+rnp::secure_vector<uint8_t>
+gnupg_sexp_t::write_padded(size_t padblock) const
+{
+ rnp::MemoryDest raw;
+ raw.set_secure(true);
+
+ if (!write(raw.dst())) {
+ RNP_LOG("failed to serialize s_exp");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ // add padding!
+ size_t padding = padblock - raw.writeb() % padblock;
+ for (size_t i = 0; i < padding; i++) {
+ raw.write("X", 1);
+ }
+ if (raw.werr()) {
+ RNP_LOG("failed to write padding");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+ const uint8_t *mem = (uint8_t *) raw.memory();
+ return rnp::secure_vector<uint8_t>(mem, mem + raw.writeb());
+}
+
+void
+gnupg_sexp_t::add_protected_seckey(pgp_key_pkt_t & seckey,
+ const std::string & password,
+ rnp::SecurityContext &ctx)
+{
+ pgp_key_protection_t &prot = seckey.sec_protection;
+ if (prot.s2k.specifier != PGP_S2KS_ITERATED_AND_SALTED) {
+ RNP_LOG("Bad s2k specifier: %d", (int) prot.s2k.specifier);
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ const format_info *format =
+ find_format(prot.symm_alg, prot.cipher_mode, prot.s2k.hash_alg);
+ if (!format) {
+ RNP_LOG("Unknown protection format.");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ // randomize IV and salt
+ ctx.rng.get(prot.iv, sizeof(prot.iv));
+ ctx.rng.get(prot.s2k.salt, sizeof(prot.s2k.salt));
+
+ // write seckey
+ gnupg_sexp_t raw_s_exp;
+ auto psub_s_exp = raw_s_exp.add_sub();
+ psub_s_exp->add_seckey(seckey);
+
+ // calculate hash
+ char protected_at[G10_PROTECTED_AT_SIZE + 1];
+ uint8_t checksum[G10_SHA1_HASH_SIZE];
+ // TODO: how critical is it if we have a skewed timestamp here due to y2k38 problem?
+ struct tm tm = {};
+ rnp_gmtime(ctx.time(), tm);
+ strftime(protected_at, sizeof(protected_at), "%Y%m%dT%H%M%S", &tm);
+ if (!g10_calculated_hash(seckey, protected_at, checksum)) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ psub_s_exp = raw_s_exp.add_sub();
+ psub_s_exp->add("hash");
+ psub_s_exp->add("sha1");
+ psub_s_exp->add(checksum, sizeof(checksum));
+
+ /* write raw secret key to the memory */
+ rnp::secure_vector<uint8_t> rawkey = raw_s_exp.write_padded(format->cipher_block_size);
+
+ /* derive encrypting key */
+ unsigned keysize = pgp_key_size(prot.symm_alg);
+ if (!keysize) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> derived_key;
+ if (pgp_s2k_iterated(format->hash_alg,
+ derived_key.data(),
+ keysize,
+ password.c_str(),
+ prot.s2k.salt,
+ prot.s2k.iterations)) {
+ RNP_LOG("s2k key derivation failed");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ /* encrypt raw key */
+ std::unique_ptr<Cipher> enc(
+ Cipher::encryption(format->cipher, format->cipher_mode, 0, true));
+ if (!enc || !enc->set_key(derived_key.data(), keysize) ||
+ !enc->set_iv(prot.iv, format->iv_size)) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ size_t output_written, input_consumed;
+ std::vector<uint8_t> enckey(rawkey.size());
+
+ if (!enc->finish(enckey.data(),
+ enckey.size(),
+ &output_written,
+ rawkey.data(),
+ rawkey.size(),
+ &input_consumed)) {
+ RNP_LOG("Encryption failed");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ /* build s_exp with encrypted key */
+ psub_s_exp = add_sub();
+ psub_s_exp->add("protected");
+ psub_s_exp->add(format->g10_type);
+ /* protection params: s2k, iv */
+ auto psub_sub_s_exp = psub_s_exp->add_sub();
+ /* s2k params: hash, salt, iterations */
+ auto psub_sub_sub_s_exp = psub_sub_s_exp->add_sub();
+ psub_sub_sub_s_exp->add("sha1");
+ psub_sub_sub_s_exp->add(prot.s2k.salt, PGP_SALT_SIZE);
+ psub_sub_sub_s_exp->add(prot.s2k.iterations);
+ psub_sub_s_exp->add(prot.iv, format->iv_size);
+ /* encrypted key data itself */
+ psub_s_exp->add(enckey.data(), enckey.size());
+ /* protected-at */
+ psub_s_exp = add_sub();
+ psub_s_exp->add("protected-at");
+ psub_s_exp->add((uint8_t *) protected_at, G10_PROTECTED_AT_SIZE);
+}
+
+bool
+g10_write_seckey(pgp_dest_t * dst,
+ pgp_key_pkt_t * seckey,
+ const char * password,
+ rnp::SecurityContext &ctx)
+{
+ bool is_protected = true;
+
+ switch (seckey->sec_protection.s2k.usage) {
+ case PGP_S2KU_NONE:
+ is_protected = false;
+ break;
+ case PGP_S2KU_ENCRYPTED_AND_HASHED:
+ is_protected = true;
+ // TODO: these are forced for now, until openpgp-native is implemented
+ seckey->sec_protection.symm_alg = PGP_SA_AES_128;
+ seckey->sec_protection.cipher_mode = PGP_CIPHER_MODE_CBC;
+ seckey->sec_protection.s2k.hash_alg = PGP_HASH_SHA1;
+ break;
+ default:
+ RNP_LOG("unsupported s2k usage");
+ return false;
+ }
+
+ try {
+ gnupg_sexp_t s_exp;
+ s_exp.add(is_protected ? "protected-private-key" : "private-key");
+ auto pkey = s_exp.add_sub();
+ pkey->add_pubkey(*seckey);
+
+ if (is_protected) {
+ pkey->add_protected_seckey(*seckey, password, ctx);
+ } else {
+ pkey->add_seckey(*seckey);
+ }
+ return s_exp.write(*dst) && !dst->werr;
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to write g10 key: %s", e.what());
+ return false;
+ }
+}
+
+static bool
+g10_calculated_hash(const pgp_key_pkt_t &key, const char *protected_at, uint8_t *checksum)
+{
+ try {
+ /* populate s_exp */
+ gnupg_sexp_t s_exp;
+ s_exp.add_pubkey(key);
+ s_exp.add_seckey(key);
+ auto s_sub_exp = s_exp.add_sub();
+ s_sub_exp->add("protected-at");
+ s_sub_exp->add((uint8_t *) protected_at, G10_PROTECTED_AT_SIZE);
+ /* write it to memdst */
+ rnp::MemoryDest memdst;
+ memdst.set_secure(true);
+ if (!s_exp.write(memdst.dst())) {
+ RNP_LOG("Failed to write s_exp");
+ return false;
+ }
+ auto hash = rnp::Hash::create(PGP_HASH_SHA1);
+ hash->add(memdst.memory(), memdst.writeb());
+ hash->finish(checksum);
+ return true;
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to build s_exp: %s", e.what());
+ return false;
+ }
+}
+
+bool
+rnp_key_store_gnupg_sexp_to_dst(pgp_key_t *key, pgp_dest_t *dest)
+{
+ if (key->format != PGP_KEY_STORE_G10) {
+ RNP_LOG("incorrect format: %d", key->format);
+ return false;
+ }
+ pgp_rawpacket_t &packet = key->rawpkt();
+ dst_write(dest, packet.raw.data(), packet.raw.size());
+ return dest->werr == RNP_SUCCESS;
+}
diff --git a/src/librekey/key_store_g10.h b/src/librekey/key_store_g10.h
new file mode 100644
index 0000000..f770628
--- /dev/null
+++ b/src/librekey/key_store_g10.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2017, [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 RIBOSE, INC. AND CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * 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 RNP_KEY_STORE_G10_H
+#define RNP_KEY_STORE_G10_H
+
+#include <rekey/rnp_key_store.h>
+
+bool rnp_key_store_g10_from_src(rnp_key_store_t *, pgp_source_t *, const pgp_key_provider_t *);
+bool rnp_key_store_gnupg_sexp_to_dst(pgp_key_t *, pgp_dest_t *);
+bool g10_write_seckey(pgp_dest_t * dst,
+ pgp_key_pkt_t * seckey,
+ const char * password,
+ rnp::SecurityContext &ctx);
+pgp_key_pkt_t *g10_decrypt_seckey(const pgp_rawpacket_t &raw,
+ const pgp_key_pkt_t & pubkey,
+ const char * password);
+
+#endif // RNP_KEY_STORE_G10_H
diff --git a/src/librekey/key_store_kbx.cpp b/src/librekey/key_store_kbx.cpp
new file mode 100644
index 0000000..bc504f6
--- /dev/null
+++ b/src/librekey/key_store_kbx.cpp
@@ -0,0 +1,706 @@
+/*
+ * Copyright (c) 2017-2022, [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.
+ */
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#else
+#include "uniwin.h"
+#endif
+#include <string.h>
+#include <stdint.h>
+#include <time.h>
+#include <inttypes.h>
+#include <cassert>
+
+#include "key_store_pgp.h"
+#include "key_store_kbx.h"
+#include "pgp-key.h"
+#include <librepgp/stream-sig.h>
+
+/* same limit with GnuPG 2.1 */
+#define BLOB_SIZE_LIMIT (5 * 1024 * 1024)
+/* limit the number of keys/sigs/uids in the blob */
+#define BLOB_OBJ_LIMIT 0x8000
+
+#define BLOB_HEADER_SIZE 0x5
+#define BLOB_FIRST_SIZE 0x20
+#define BLOB_KEY_SIZE 0x1C
+#define BLOB_UID_SIZE 0x0C
+#define BLOB_SIG_SIZE 0x04
+#define BLOB_VALIDITY_SIZE 0x10
+
+uint8_t
+kbx_blob_t::ru8(size_t idx)
+{
+ return image_[idx];
+}
+
+uint16_t
+kbx_blob_t::ru16(size_t idx)
+{
+ return read_uint16(image_.data() + idx);
+}
+
+uint32_t
+kbx_blob_t::ru32(size_t idx)
+{
+ return read_uint32(image_.data() + idx);
+}
+
+kbx_blob_t::kbx_blob_t(std::vector<uint8_t> &data)
+{
+ if (data.size() < BLOB_HEADER_SIZE) {
+ RNP_LOG("Too small KBX blob.");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ uint32_t len = read_uint32(data.data());
+ if (len > BLOB_SIZE_LIMIT) {
+ RNP_LOG("Too large KBX blob.");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ if (len != data.size()) {
+ RNP_LOG("KBX blob size mismatch.");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ image_ = data;
+ type_ = (kbx_blob_type_t) ru8(4);
+}
+
+bool
+kbx_header_blob_t::parse()
+{
+ if (length() != BLOB_FIRST_SIZE) {
+ RNP_LOG("The first blob has wrong length: %" PRIu32 " but expected %d",
+ length(),
+ (int) BLOB_FIRST_SIZE);
+ return false;
+ }
+
+ size_t idx = BLOB_HEADER_SIZE;
+ version_ = ru8(idx++);
+ if (version_ != 1) {
+ RNP_LOG("Wrong version, expect 1 but has %" PRIu8, version_);
+ return false;
+ }
+
+ flags_ = ru16(idx);
+ idx += 2;
+
+ // blob should contains a magic KBXf
+ if (memcmp(image_.data() + idx, "KBXf", 4)) {
+ RNP_LOG("The first blob hasn't got a KBXf magic string");
+ return false;
+ }
+ idx += 4;
+ // RFU
+ idx += 4;
+ // File creation time
+ file_created_at_ = ru32(idx);
+ idx += 4;
+ // Duplicated?
+ file_created_at_ = ru32(idx);
+ // RFU +4 bytes
+ // RFU +4 bytes
+ return true;
+}
+
+bool
+kbx_pgp_blob_t::parse()
+{
+ if (image_.size() < 15 + BLOB_HEADER_SIZE) {
+ RNP_LOG("Too few data in the blob.");
+ return false;
+ }
+
+ size_t idx = BLOB_HEADER_SIZE;
+ /* version */
+ version_ = ru8(idx++);
+ if (version_ != 1) {
+ RNP_LOG("Wrong version: %" PRIu8, version_);
+ return false;
+ }
+ /* flags */
+ flags_ = ru16(idx);
+ idx += 2;
+ /* keyblock offset */
+ keyblock_offset_ = ru32(idx);
+ idx += 4;
+ /* keyblock length */
+ keyblock_length_ = ru32(idx);
+ idx += 4;
+
+ if ((keyblock_offset_ > image_.size()) ||
+ (keyblock_offset_ > (UINT32_MAX - keyblock_length_)) ||
+ (image_.size() < (keyblock_offset_ + keyblock_length_))) {
+ RNP_LOG("Wrong keyblock offset/length, blob size: %zu"
+ ", keyblock offset: %" PRIu32 ", length: %" PRIu32,
+ image_.size(),
+ keyblock_offset_,
+ keyblock_length_);
+ return false;
+ }
+ /* number of key blocks */
+ size_t nkeys = ru16(idx);
+ idx += 2;
+ if (nkeys < 1) {
+ RNP_LOG("PGP blob should contains at least 1 key");
+ return false;
+ }
+ if (nkeys > BLOB_OBJ_LIMIT) {
+ RNP_LOG("Too many keys in the PGP blob");
+ return false;
+ }
+
+ /* Size of the single key record */
+ size_t keys_len = ru16(idx);
+ idx += 2;
+ if (keys_len < BLOB_KEY_SIZE) {
+ RNP_LOG(
+ "PGP blob needs %d bytes, but contains: %zu bytes", (int) BLOB_KEY_SIZE, keys_len);
+ return false;
+ }
+
+ for (size_t i = 0; i < nkeys; i++) {
+ if (image_.size() - idx < keys_len) {
+ RNP_LOG("Too few bytes left for key blob");
+ return false;
+ }
+
+ kbx_pgp_key_t nkey = {};
+ /* copy fingerprint */
+ memcpy(nkey.fp, &image_[idx], 20);
+ idx += 20;
+ /* keyid offset */
+ nkey.keyid_offset = ru32(idx);
+ idx += 4;
+ /* flags */
+ nkey.flags = ru16(idx);
+ idx += 2;
+ /* RFU */
+ idx += 2;
+ /* skip padding bytes if it existed */
+ idx += keys_len - BLOB_KEY_SIZE;
+ keys_.push_back(std::move(nkey));
+ }
+
+ if (image_.size() - idx < 2) {
+ RNP_LOG("No data for sn_size");
+ return false;
+ }
+ size_t sn_size = ru16(idx);
+ idx += 2;
+
+ if (image_.size() - idx < sn_size) {
+ RNP_LOG("SN is %zu, while bytes left are %zu", sn_size, image_.size() - idx);
+ return false;
+ }
+
+ if (sn_size) {
+ sn_ = {image_.begin() + idx, image_.begin() + idx + sn_size};
+ idx += sn_size;
+ }
+
+ if (image_.size() - idx < 4) {
+ RNP_LOG("Too few data for uids");
+ return false;
+ }
+ size_t nuids = ru16(idx);
+ if (nuids > BLOB_OBJ_LIMIT) {
+ RNP_LOG("Too many uids in the PGP blob");
+ return false;
+ }
+
+ size_t uids_len = ru16(idx + 2);
+ idx += 4;
+
+ if (uids_len < BLOB_UID_SIZE) {
+ RNP_LOG("Too few bytes for uid struct: %zu", uids_len);
+ return false;
+ }
+
+ for (size_t i = 0; i < nuids; i++) {
+ if (image_.size() - idx < uids_len) {
+ RNP_LOG("Too few bytes to read uid struct.");
+ return false;
+ }
+ kbx_pgp_uid_t nuid = {};
+ /* offset */
+ nuid.offset = ru32(idx);
+ idx += 4;
+ /* length */
+ nuid.length = ru32(idx);
+ idx += 4;
+ /* flags */
+ nuid.flags = ru16(idx);
+ idx += 2;
+ /* validity */
+ nuid.validity = ru8(idx);
+ idx++;
+ /* RFU */
+ idx++;
+ // skip padding bytes if it existed
+ idx += uids_len - BLOB_UID_SIZE;
+
+ uids_.push_back(std::move(nuid));
+ }
+
+ if (image_.size() - idx < 4) {
+ RNP_LOG("No data left for sigs");
+ return false;
+ }
+
+ size_t nsigs = ru16(idx);
+ if (nsigs > BLOB_OBJ_LIMIT) {
+ RNP_LOG("Too many sigs in the PGP blob");
+ return false;
+ }
+
+ size_t sigs_len = ru16(idx + 2);
+ idx += 4;
+
+ if (sigs_len < BLOB_SIG_SIZE) {
+ RNP_LOG("Too small SIGN structure: %zu", uids_len);
+ return false;
+ }
+
+ for (size_t i = 0; i < nsigs; i++) {
+ if (image_.size() - idx < sigs_len) {
+ RNP_LOG("Too few data for sig");
+ return false;
+ }
+
+ kbx_pgp_sig_t nsig = {};
+ nsig.expired = ru32(idx);
+ idx += 4;
+
+ // skip padding bytes if it existed
+ idx += (sigs_len - BLOB_SIG_SIZE);
+
+ sigs_.push_back(nsig);
+ }
+
+ if (image_.size() - idx < BLOB_VALIDITY_SIZE) {
+ RNP_LOG("Too few data for trust/validities");
+ return false;
+ }
+
+ ownertrust_ = ru8(idx);
+ idx++;
+ all_validity_ = ru8(idx);
+ idx++;
+ // RFU
+ idx += 2;
+ recheck_after_ = ru32(idx);
+ idx += 4;
+ latest_timestamp_ = ru32(idx);
+ idx += 4;
+ blob_created_at_ = ru32(idx);
+ // do not forget to idx += 4 on further expansion
+
+ // here starts keyblock, UID and reserved space for future usage
+
+ // Maybe we should add checksum verify but GnuPG never checked it
+ // Checksum is last 20 bytes of blob and it is SHA-1, if it invalid MD5 and starts from 4
+ // zero it is MD5.
+
+ return true;
+}
+
+static std::unique_ptr<kbx_blob_t>
+rnp_key_store_kbx_parse_blob(const uint8_t *image, size_t image_len)
+{
+ std::unique_ptr<kbx_blob_t> blob;
+ // a blob shouldn't be less of length + type
+ if (image_len < BLOB_HEADER_SIZE) {
+ RNP_LOG("Blob size is %zu but it shouldn't be less of header", image_len);
+ return blob;
+ }
+
+ try {
+ std::vector<uint8_t> data(image, image + image_len);
+ kbx_blob_type_t type = (kbx_blob_type_t) image[4];
+
+ switch (type) {
+ case KBX_EMPTY_BLOB:
+ blob = std::unique_ptr<kbx_blob_t>(new kbx_blob_t(data));
+ break;
+ case KBX_HEADER_BLOB:
+ blob = std::unique_ptr<kbx_blob_t>(new kbx_header_blob_t(data));
+ break;
+ case KBX_PGP_BLOB:
+ blob = std::unique_ptr<kbx_blob_t>(new kbx_pgp_blob_t(data));
+ break;
+ case KBX_X509_BLOB:
+ // current we doesn't parse X509 blob, so, keep it as is
+ blob = std::unique_ptr<kbx_blob_t>(new kbx_blob_t(data));
+ break;
+ // unsupported blob type
+ default:
+ RNP_LOG("Unsupported blob type: %d", (int) type);
+ return blob;
+ }
+
+ if (!blob->parse()) {
+ return NULL;
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return NULL;
+ }
+ return blob;
+}
+
+bool
+rnp_key_store_kbx_from_src(rnp_key_store_t * key_store,
+ pgp_source_t * src,
+ const pgp_key_provider_t *key_provider)
+{
+ try {
+ rnp::MemorySource mem(*src);
+ size_t has_bytes = mem.size();
+ uint8_t * buf = (uint8_t *) mem.memory();
+
+ while (has_bytes > 4) {
+ size_t blob_length = read_uint32(buf);
+ if (blob_length > BLOB_SIZE_LIMIT) {
+ RNP_LOG("Blob size is %zu bytes but limit is %d bytes",
+ blob_length,
+ (int) BLOB_SIZE_LIMIT);
+ return false;
+ }
+ if (blob_length < BLOB_HEADER_SIZE) {
+ RNP_LOG("Too small blob header size");
+ return false;
+ }
+ if (has_bytes < blob_length) {
+ RNP_LOG("Blob have size %zu bytes but file contains only %zu bytes",
+ blob_length,
+ has_bytes);
+ return false;
+ }
+ auto blob = rnp_key_store_kbx_parse_blob(buf, blob_length);
+ if (!blob.get()) {
+ RNP_LOG("Failed to parse blob");
+ return false;
+ }
+ kbx_blob_t *pblob = blob.get();
+ key_store->blobs.push_back(std::move(blob));
+
+ if (pblob->type() == KBX_PGP_BLOB) {
+ // parse keyblock if it existed
+ kbx_pgp_blob_t &pgp_blob = dynamic_cast<kbx_pgp_blob_t &>(*pblob);
+ if (!pgp_blob.keyblock_length()) {
+ RNP_LOG("PGP blob have zero size");
+ return false;
+ }
+
+ rnp::MemorySource blsrc(pgp_blob.image().data() + pgp_blob.keyblock_offset(),
+ pgp_blob.keyblock_length(),
+ false);
+ if (rnp_key_store_pgp_read_from_src(key_store, &blsrc.src())) {
+ return false;
+ }
+ }
+
+ has_bytes -= blob_length;
+ buf += blob_length;
+ }
+ return true;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+}
+
+static bool
+pbuf(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ dst_write(dst, buf, len);
+ return dst->werr == RNP_SUCCESS;
+}
+
+static bool
+pu8(pgp_dest_t *dst, uint8_t p)
+{
+ return pbuf(dst, &p, 1);
+}
+
+static bool
+pu16(pgp_dest_t *dst, uint16_t f)
+{
+ uint8_t p[2];
+ p[0] = (uint8_t)(f >> 8);
+ p[1] = (uint8_t) f;
+ return pbuf(dst, p, 2);
+}
+
+static bool
+pu32(pgp_dest_t *dst, uint32_t f)
+{
+ uint8_t p[4];
+ STORE32BE(p, f);
+ return pbuf(dst, p, 4);
+}
+
+static bool
+rnp_key_store_kbx_write_header(rnp_key_store_t *key_store, pgp_dest_t *dst)
+{
+ uint16_t flags = 0;
+ uint32_t file_created_at = key_store->secctx.time();
+
+ if (!key_store->blobs.empty() && (key_store->blobs[0]->type() == KBX_HEADER_BLOB)) {
+ kbx_header_blob_t &blob = dynamic_cast<kbx_header_blob_t &>(*key_store->blobs[0]);
+ file_created_at = blob.file_created_at();
+ }
+
+ return !(!pu32(dst, BLOB_FIRST_SIZE) || !pu8(dst, KBX_HEADER_BLOB) ||
+ !pu8(dst, 1) // version
+ || !pu16(dst, flags) || !pbuf(dst, "KBXf", 4) || !pu32(dst, 0) // RFU
+ || !pu32(dst, 0) // RFU
+ || !pu32(dst, file_created_at) || !pu32(dst, key_store->secctx.time()) ||
+ !pu32(dst, 0)); // RFU
+}
+
+static bool
+rnp_key_store_kbx_write_pgp(rnp_key_store_t *key_store, pgp_key_t *key, pgp_dest_t *dst)
+{
+ rnp::MemoryDest mem(NULL, BLOB_SIZE_LIMIT);
+
+ if (!pu32(&mem.dst(), 0)) { // length, we don't know length of blob yet, so it's 0
+ return false;
+ }
+
+ if (!pu8(&mem.dst(), KBX_PGP_BLOB) || !pu8(&mem.dst(), 1)) { // type, version
+ return false;
+ }
+
+ if (!pu16(&mem.dst(), 0)) { // flags, not used by GnuPG
+ return false;
+ }
+
+ if (!pu32(&mem.dst(), 0) ||
+ !pu32(&mem.dst(), 0)) { // offset and length of keyblock, update later
+ return false;
+ }
+
+ if (!pu16(&mem.dst(), 1 + key->subkey_count())) { // number of keys in keyblock
+ return false;
+ }
+ if (!pu16(&mem.dst(), 28)) { // size of key info structure)
+ return false;
+ }
+
+ if (!pbuf(&mem.dst(), key->fp().fingerprint, PGP_FINGERPRINT_SIZE) ||
+ !pu32(&mem.dst(), mem.writeb() - 8) || // offset to keyid (part of fpr for V4)
+ !pu16(&mem.dst(), 0) || // flags, not used by GnuPG
+ !pu16(&mem.dst(), 0)) { // RFU
+ return false;
+ }
+
+ // same as above, for each subkey
+ std::vector<uint32_t> subkey_sig_expirations;
+ for (auto &sfp : key->subkey_fps()) {
+ pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(key_store, sfp);
+ if (!subkey || !pbuf(&mem.dst(), subkey->fp().fingerprint, PGP_FINGERPRINT_SIZE) ||
+ !pu32(&mem.dst(), mem.writeb() - 8) || // offset to keyid (part of fpr for V4)
+ !pu16(&mem.dst(), 0) || // flags, not used by GnuPG
+ !pu16(&mem.dst(), 0)) { // RFU
+ return false;
+ }
+ // load signature expirations while we're at it
+ for (size_t i = 0; i < subkey->sig_count(); i++) {
+ uint32_t expiration = subkey->get_sig(i).sig.key_expiration();
+ subkey_sig_expirations.push_back(expiration);
+ }
+ }
+
+ if (!pu16(&mem.dst(), 0)) { // Zero size of serial number
+ return false;
+ }
+
+ // skip serial number
+ if (!pu16(&mem.dst(), key->uid_count()) || !pu16(&mem.dst(), 12)) {
+ return false;
+ }
+
+ size_t uid_start = mem.writeb();
+ for (size_t i = 0; i < key->uid_count(); i++) {
+ if (!pu32(&mem.dst(), 0) ||
+ !pu32(&mem.dst(), 0)) { // UID offset and length, update when blob has done
+ return false;
+ }
+
+ if (!pu16(&mem.dst(), 0)) { // flags, (not yet used)
+ return false;
+ }
+
+ if (!pu8(&mem.dst(), 0) || !pu8(&mem.dst(), 0)) { // Validity & RFU
+ return false;
+ }
+ }
+
+ if (!pu16(&mem.dst(), key->sig_count() + subkey_sig_expirations.size()) ||
+ !pu16(&mem.dst(), 4)) {
+ return false;
+ }
+
+ for (size_t i = 0; i < key->sig_count(); i++) {
+ if (!pu32(&mem.dst(), key->get_sig(i).sig.key_expiration())) {
+ return false;
+ }
+ }
+ for (auto &expiration : subkey_sig_expirations) {
+ if (!pu32(&mem.dst(), expiration)) {
+ return false;
+ }
+ }
+
+ if (!pu8(&mem.dst(), 0) ||
+ !pu8(&mem.dst(), 0)) { // Assigned ownertrust & All_Validity (not yet used)
+ return false;
+ }
+
+ if (!pu16(&mem.dst(), 0) || !pu32(&mem.dst(), 0)) { // RFU & Recheck_after
+ return false;
+ }
+
+ if (!pu32(&mem.dst(), key_store->secctx.time()) ||
+ !pu32(&mem.dst(), key_store->secctx.time())) { // Latest timestamp && created
+ return false;
+ }
+
+ if (!pu32(&mem.dst(), 0)) { // Size of reserved space
+ return false;
+ }
+
+ // wrtite UID, we might redesign PGP write and use this information from keyblob
+ for (size_t i = 0; i < key->uid_count(); i++) {
+ const pgp_userid_t &uid = key->get_uid(i);
+ uint8_t * p = (uint8_t *) mem.memory() + uid_start + (12 * i);
+ /* store absolute uid offset in the output stream */
+ uint32_t pt = mem.writeb() + dst->writeb;
+ STORE32BE(p, pt);
+ /* and uid length */
+ pt = uid.str.size();
+ STORE32BE(p + 4, pt);
+ /* uid data itself */
+ if (!pbuf(&mem.dst(), uid.str.c_str(), pt)) {
+ return false;
+ }
+ }
+
+ /* write keyblock and fix the offset/length */
+ size_t key_start = mem.writeb();
+ uint32_t pt = key_start;
+ uint8_t *p = (uint8_t *) mem.memory() + 8;
+ STORE32BE(p, pt);
+
+ key->write(mem.dst());
+ if (mem.werr()) {
+ return false;
+ }
+
+ for (auto &sfp : key->subkey_fps()) {
+ const pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(key_store, sfp);
+ if (!subkey) {
+ return false;
+ }
+ subkey->write(mem.dst());
+ if (mem.werr()) {
+ return false;
+ }
+ }
+
+ /* key blob length */
+ pt = mem.writeb() - key_start;
+ p = (uint8_t *) mem.memory() + 12;
+ STORE32BE(p, pt);
+
+ // fix the length of blob
+ pt = mem.writeb() + 20;
+ p = (uint8_t *) mem.memory();
+ STORE32BE(p, pt);
+
+ // checksum
+ auto hash = rnp::Hash::create(PGP_HASH_SHA1);
+ hash->add(mem.memory(), mem.writeb());
+ uint8_t checksum[PGP_SHA1_HASH_SIZE];
+ assert(hash->size() == sizeof(checksum));
+ hash->finish(checksum);
+
+ if (!(pbuf(&mem.dst(), checksum, PGP_SHA1_HASH_SIZE))) {
+ return false;
+ }
+
+ /* finally write to the output */
+ dst_write(dst, mem.memory(), mem.writeb());
+ return !dst->werr;
+}
+
+static bool
+rnp_key_store_kbx_write_x509(rnp_key_store_t *key_store, pgp_dest_t *dst)
+{
+ for (auto &blob : key_store->blobs) {
+ if (blob->type() != KBX_X509_BLOB) {
+ continue;
+ }
+ if (!pbuf(dst, blob->image().data(), blob->length())) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+rnp_key_store_kbx_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst)
+{
+ try {
+ if (!rnp_key_store_kbx_write_header(key_store, dst)) {
+ RNP_LOG("Can't write KBX header");
+ return false;
+ }
+
+ for (auto &key : key_store->keys) {
+ if (!key.is_primary()) {
+ continue;
+ }
+ if (!rnp_key_store_kbx_write_pgp(key_store, &key, dst)) {
+ RNP_LOG("Can't write PGP blobs for key %p", &key);
+ return false;
+ }
+ }
+
+ if (!rnp_key_store_kbx_write_x509(key_store, dst)) {
+ RNP_LOG("Can't write X509 blobs");
+ return false;
+ }
+ return true;
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to write KBX store: %s", e.what());
+ return false;
+ }
+}
diff --git a/src/librekey/key_store_kbx.h b/src/librekey/key_store_kbx.h
new file mode 100644
index 0000000..68d725d
--- /dev/null
+++ b/src/librekey/key_store_kbx.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2017, [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 RNP_KEY_STORE_KBX_H
+#define RNP_KEY_STORE_KBX_H
+
+#include <rekey/rnp_key_store.h>
+#include "sec_profile.hpp"
+
+bool rnp_key_store_kbx_from_src(rnp_key_store_t *, pgp_source_t *, const pgp_key_provider_t *);
+bool rnp_key_store_kbx_to_dst(rnp_key_store_t *, pgp_dest_t *);
+
+#endif // RNP_KEY_STORE_KBX_H
diff --git a/src/librekey/key_store_pgp.cpp b/src/librekey/key_store_pgp.cpp
new file mode 100644
index 0000000..6edc099
--- /dev/null
+++ b/src/librekey/key_store_pgp.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 2005-2008 Nominet UK (www.nic.uk)
+ * All rights reserved.
+ * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted
+ * their moral rights under the UK Copyright Design and Patents Act 1988 to
+ * be recorded as the authors of this copyright work.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if defined(__NetBSD__)
+__COPYRIGHT("@(#) Copyright (c) 2009 The NetBSD Foundation, Inc. All rights reserved.");
+__RCSID("$NetBSD: keyring.c,v 1.50 2011/06/25 00:37:44 agc Exp $");
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <librepgp/stream-common.h>
+#include <librepgp/stream-sig.h>
+#include <librepgp/stream-packet.h>
+#include <librepgp/stream-key.h>
+#include "crypto/mem.h"
+
+#include "types.h"
+#include "key_store_pgp.h"
+#include "pgp-key.h"
+
+bool
+rnp_key_store_add_transferable_subkey(rnp_key_store_t * keyring,
+ pgp_transferable_subkey_t *tskey,
+ pgp_key_t * pkey)
+{
+ try {
+ /* create subkey */
+ pgp_key_t skey(*tskey, pkey);
+ /* add it to the storage */
+ return rnp_key_store_add_key(keyring, &skey);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ RNP_LOG_KEY_PKT("failed to create subkey %s", tskey->subkey);
+ RNP_LOG_KEY("primary key is %s", pkey);
+ return false;
+ }
+}
+
+bool
+rnp_key_store_add_transferable_key(rnp_key_store_t *keyring, pgp_transferable_key_t *tkey)
+{
+ pgp_key_t *addkey = NULL;
+
+ /* create key from transferable key */
+ try {
+ pgp_key_t key(*tkey);
+ /* temporary disable key validation */
+ keyring->disable_validation = true;
+ /* add key to the storage before subkeys */
+ addkey = rnp_key_store_add_key(keyring, &key);
+ } catch (const std::exception &e) {
+ keyring->disable_validation = false;
+ RNP_LOG_KEY_PKT("failed to add key %s", tkey->key);
+ return false;
+ }
+
+ if (!addkey) {
+ keyring->disable_validation = false;
+ RNP_LOG("Failed to add key to key store.");
+ return false;
+ }
+
+ /* add subkeys */
+ for (auto &subkey : tkey->subkeys) {
+ if (!rnp_key_store_add_transferable_subkey(keyring, &subkey, addkey)) {
+ RNP_LOG("Failed to add subkey to key store.");
+ keyring->disable_validation = false;
+ goto error;
+ }
+ }
+
+ /* now validate/refresh the whole key with subkeys */
+ keyring->disable_validation = false;
+ addkey->revalidate(*keyring);
+ return true;
+error:
+ /* during key addition all fields are copied so will be cleaned below */
+ rnp_key_store_remove_key(keyring, addkey, false);
+ return false;
+}
+
+rnp_result_t
+rnp_key_store_pgp_read_key_from_src(rnp_key_store_t &keyring,
+ pgp_source_t & src,
+ bool skiperrors)
+{
+ pgp_transferable_key_t key;
+ rnp_result_t ret = process_pgp_key_auto(src, key, true, skiperrors);
+
+ if (ret && (!skiperrors || (ret != RNP_ERROR_BAD_FORMAT))) {
+ return ret;
+ }
+
+ /* check whether we have primary key */
+ if (key.key.tag != PGP_PKT_RESERVED) {
+ return rnp_key_store_add_transferable_key(&keyring, &key) ? RNP_SUCCESS :
+ RNP_ERROR_BAD_STATE;
+ }
+
+ /* we just skipped some unexpected packets and read nothing */
+ if (key.subkeys.empty()) {
+ return RNP_SUCCESS;
+ }
+
+ return rnp_key_store_add_transferable_subkey(&keyring, &key.subkeys.front(), NULL) ?
+ RNP_SUCCESS :
+ RNP_ERROR_BAD_STATE;
+}
+
+rnp_result_t
+rnp_key_store_pgp_read_from_src(rnp_key_store_t *keyring, pgp_source_t *src, bool skiperrors)
+{
+ /* check whether we have transferable subkey in source */
+ if (is_subkey_pkt(stream_pkt_type(*src))) {
+ pgp_transferable_subkey_t tskey;
+ rnp_result_t ret = process_pgp_subkey(*src, tskey, skiperrors);
+ if (ret) {
+ return ret;
+ }
+ return rnp_key_store_add_transferable_subkey(keyring, &tskey, NULL) ?
+ RNP_SUCCESS :
+ RNP_ERROR_BAD_STATE;
+ }
+
+ /* process armored or raw transferable key packets sequence(s) */
+ try {
+ pgp_key_sequence_t keys;
+ rnp_result_t ret = process_pgp_keys(*src, keys, skiperrors);
+ if (ret) {
+ return ret;
+ }
+ for (auto &key : keys.keys) {
+ if (!rnp_key_store_add_transferable_key(keyring, &key)) {
+ return RNP_ERROR_BAD_STATE;
+ }
+ }
+ return RNP_SUCCESS;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+}
+
+std::vector<uint8_t>
+rnp_key_to_vec(const pgp_key_t &key)
+{
+ rnp::MemoryDest dst;
+ key.write(dst.dst());
+ return dst.to_vector();
+}
+
+static bool
+do_write(rnp_key_store_t *key_store, pgp_dest_t *dst, bool secret)
+{
+ for (auto &key : key_store->keys) {
+ if (key.is_secret() != secret) {
+ continue;
+ }
+ // skip subkeys, they are written below (orphans are ignored)
+ if (!key.is_primary()) {
+ continue;
+ }
+
+ if (key.format != PGP_KEY_STORE_GPG) {
+ RNP_LOG("incorrect format (conversions not supported): %d", key.format);
+ return false;
+ }
+ key.write(*dst);
+ if (dst->werr) {
+ return false;
+ }
+ for (auto &sfp : key.subkey_fps()) {
+ pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(key_store, sfp);
+ if (!subkey) {
+ RNP_LOG("Missing subkey");
+ continue;
+ }
+ subkey->write(*dst);
+ if (dst->werr) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool
+rnp_key_store_pgp_write_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst)
+{
+ // two separate passes (public keys, then secret keys)
+ return do_write(key_store, dst, false) && do_write(key_store, dst, true);
+}
diff --git a/src/librekey/key_store_pgp.h b/src/librekey/key_store_pgp.h
new file mode 100644
index 0000000..d3dcd06
--- /dev/null
+++ b/src/librekey/key_store_pgp.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 2005-2008 Nominet UK (www.nic.uk)
+ * All rights reserved.
+ * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted
+ * their moral rights under the UK Copyright Design and Patents Act 1988 to
+ * be recorded as the authors of this copyright work.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** \file
+ */
+
+#ifndef KEY_STORE_PGP_H_
+#define KEY_STORE_PGP_H_
+
+#include <rekey/rnp_key_store.h>
+#include <librepgp/stream-common.h>
+#include <librepgp/stream-key.h>
+
+/* Read the whole keyring from the src, processing all available keys or subkeys */
+rnp_result_t rnp_key_store_pgp_read_from_src(rnp_key_store_t *keyring,
+ pgp_source_t * src,
+ bool skiperrors = false);
+
+/* Read the first key or subkey from the src */
+rnp_result_t rnp_key_store_pgp_read_key_from_src(rnp_key_store_t &keyring,
+ pgp_source_t & src,
+ bool skiperrors = false);
+
+bool rnp_key_store_pgp_write_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst);
+
+bool rnp_key_store_add_transferable_subkey(rnp_key_store_t * keyring,
+ pgp_transferable_subkey_t *tskey,
+ pgp_key_t * pkey);
+
+bool rnp_key_store_add_transferable_key(rnp_key_store_t * keyring,
+ pgp_transferable_key_t *tkey);
+
+std::vector<uint8_t> rnp_key_to_vec(const pgp_key_t &key);
+
+#endif /* KEY_STORE_PGP_H_ */
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);
+}