summaryrefslogtreecommitdiffstats
path: root/src/librekey/key_store_kbx.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/librekey/key_store_kbx.cpp')
-rw-r--r--src/librekey/key_store_kbx.cpp706
1 files changed, 706 insertions, 0 deletions
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;
+ }
+}