diff options
Diffstat (limited to 'src/librepgp/stream-key.cpp')
-rw-r--r-- | src/librepgp/stream-key.cpp | 1469 |
1 files changed, 1469 insertions, 0 deletions
diff --git a/src/librepgp/stream-key.cpp b/src/librepgp/stream-key.cpp new file mode 100644 index 0000000..8090ff7 --- /dev/null +++ b/src/librepgp/stream-key.cpp @@ -0,0 +1,1469 @@ +/* + * Copyright (c) 2018-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 OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#else +#include "uniwin.h" +#endif +#include <string.h> +#include <time.h> +#include <inttypes.h> +#include "stream-def.h" +#include "stream-key.h" +#include "stream-armor.h" +#include "stream-packet.h" +#include "stream-sig.h" +#include "types.h" +#include "fingerprint.h" +#include "pgp-key.h" +#include "crypto.h" +#include "crypto/signatures.h" +#include "crypto/mem.h" +#include "../librekey/key_store_pgp.h" +#include <set> +#include <algorithm> +#include <cassert> + +/** + * @brief Add signatures from src to dst, skipping the duplicates. + * + * @param dst Vector which will contain all distinct signatures from src and dst + * @param src Vector to merge signatures from + * @return true on success or false otherwise. On failure dst may have some sigs appended. + */ +static rnp_result_t +merge_signatures(pgp_signature_list_t &dst, const pgp_signature_list_t &src) +{ + for (auto &sig : src) { + try { + if (std::find(dst.begin(), dst.end(), sig) != dst.end()) { + continue; + } + dst.emplace_back(sig); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + return RNP_SUCCESS; +} + +static rnp_result_t +transferable_userid_merge(pgp_transferable_userid_t &dst, const pgp_transferable_userid_t &src) +{ + if (dst.uid != src.uid) { + RNP_LOG("wrong userid merge attempt"); + return RNP_ERROR_BAD_PARAMETERS; + } + return merge_signatures(dst.signatures, src.signatures); +} + +rnp_result_t +transferable_subkey_from_key(pgp_transferable_subkey_t &dst, const pgp_key_t &key) +{ + try { + auto vec = rnp_key_to_vec(key); + rnp::MemorySource mem(vec); + return process_pgp_subkey(mem.src(), dst, false); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +rnp_result_t +transferable_subkey_merge(pgp_transferable_subkey_t &dst, const pgp_transferable_subkey_t &src) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (!dst.subkey.equals(src.subkey, true)) { + RNP_LOG("wrong subkey merge call"); + return RNP_ERROR_BAD_PARAMETERS; + } + if ((ret = merge_signatures(dst.signatures, src.signatures))) { + RNP_LOG("failed to merge signatures"); + } + return ret; +} + +rnp_result_t +transferable_key_from_key(pgp_transferable_key_t &dst, const pgp_key_t &key) +{ + try { + auto vec = rnp_key_to_vec(key); + rnp::MemorySource mem(vec); + return process_pgp_key(mem.src(), dst, false); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +static pgp_transferable_userid_t * +transferable_key_has_userid(pgp_transferable_key_t &src, const pgp_userid_pkt_t &userid) +{ + for (auto &uid : src.userids) { + if (uid.uid == userid) { + return &uid; + } + } + return NULL; +} + +static pgp_transferable_subkey_t * +transferable_key_has_subkey(pgp_transferable_key_t &src, const pgp_key_pkt_t &subkey) +{ + for (auto &srcsub : src.subkeys) { + if (srcsub.subkey.equals(subkey, true)) { + return &srcsub; + } + } + return NULL; +} + +rnp_result_t +transferable_key_merge(pgp_transferable_key_t &dst, const pgp_transferable_key_t &src) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (!dst.key.equals(src.key, true)) { + RNP_LOG("wrong key merge call"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* direct-key signatures */ + if ((ret = merge_signatures(dst.signatures, src.signatures))) { + RNP_LOG("failed to merge signatures"); + return ret; + } + /* userids */ + for (auto &srcuid : src.userids) { + pgp_transferable_userid_t *dstuid = transferable_key_has_userid(dst, srcuid.uid); + if (dstuid) { + if ((ret = transferable_userid_merge(*dstuid, srcuid))) { + RNP_LOG("failed to merge userid"); + return ret; + } + continue; + } + /* add userid */ + try { + dst.userids.emplace_back(srcuid); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + + /* subkeys */ + for (auto &srcsub : src.subkeys) { + pgp_transferable_subkey_t *dstsub = transferable_key_has_subkey(dst, srcsub.subkey); + if (dstsub) { + if ((ret = transferable_subkey_merge(*dstsub, srcsub))) { + RNP_LOG("failed to merge subkey"); + return ret; + } + continue; + } + /* add subkey */ + if (is_public_key_pkt(dst.key.tag) != is_public_key_pkt(srcsub.subkey.tag)) { + RNP_LOG("warning: adding public/secret subkey to secret/public key"); + } + try { + dst.subkeys.emplace_back(srcsub); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + return RNP_SUCCESS; +} + +static bool +skip_pgp_packets(pgp_source_t &src, const std::set<pgp_pkt_type_t> &pkts) +{ + do { + int pkt = stream_pkt_type(src); + if (!pkt) { + break; + } + if (pkt < 0) { + return false; + } + if (pkts.find((pgp_pkt_type_t) pkt) == pkts.end()) { + return true; + } + uint64_t ppos = src.readb; + if (stream_skip_packet(&src)) { + RNP_LOG("failed to skip packet at %" PRIu64, ppos); + return false; + } + } while (1); + + return true; +} + +static rnp_result_t +process_pgp_key_signatures(pgp_source_t &src, pgp_signature_list_t &sigs, bool skiperrors) +{ + int ptag; + while ((ptag = stream_pkt_type(src)) == PGP_PKT_SIGNATURE) { + uint64_t sigpos = src.readb; + try { + pgp_signature_t sig; + rnp_result_t ret = sig.parse(src); + if (ret) { + RNP_LOG("failed to parse signature at %" PRIu64, sigpos); + if (!skiperrors) { + return ret; + } + } else { + sigs.emplace_back(std::move(sig)); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + } + return ptag < 0 ? RNP_ERROR_BAD_FORMAT : RNP_SUCCESS; +} + +static rnp_result_t +process_pgp_userid(pgp_source_t &src, pgp_transferable_userid_t &uid, bool skiperrors) +{ + rnp_result_t ret; + uint64_t uidpos = src.readb; + try { + ret = uid.uid.parse(src); + } catch (const std::exception &e) { + ret = RNP_ERROR_GENERIC; + } + if (ret) { + RNP_LOG("failed to parse userid at %" PRIu64, uidpos); + return ret; + } + if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + return process_pgp_key_signatures(src, uid.signatures, skiperrors); +} + +rnp_result_t +process_pgp_subkey(pgp_source_t &src, pgp_transferable_subkey_t &subkey, bool skiperrors) +{ + int ptag; + subkey = pgp_transferable_subkey_t(); + uint64_t keypos = src.readb; + if (!is_subkey_pkt(ptag = stream_pkt_type(src))) { + RNP_LOG("wrong subkey ptag: %d at %" PRIu64, ptag, keypos); + return RNP_ERROR_BAD_FORMAT; + } + + rnp_result_t ret = RNP_ERROR_BAD_FORMAT; + try { + ret = subkey.subkey.parse(src); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { + RNP_LOG("failed to parse subkey at %" PRIu64, keypos); + subkey.subkey = {}; + return ret; + } + + if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + + return process_pgp_key_signatures(src, subkey.signatures, skiperrors); +} + +rnp_result_t +process_pgp_key_auto(pgp_source_t & src, + pgp_transferable_key_t &key, + bool allowsub, + bool skiperrors) +{ + key = {}; + uint64_t srcpos = src.readb; + int ptag = stream_pkt_type(src); + if (is_subkey_pkt(ptag) && allowsub) { + pgp_transferable_subkey_t subkey; + rnp_result_t ret = process_pgp_subkey(src, subkey, skiperrors); + if (subkey.subkey.tag != PGP_PKT_RESERVED) { + try { + key.subkeys.push_back(std::move(subkey)); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_OUT_OF_MEMORY; + } + } + /* change error code if we didn't process anything at all */ + if (srcpos == src.readb) { + ret = RNP_ERROR_BAD_STATE; + } + return ret; + } + + rnp_result_t ret = RNP_ERROR_BAD_FORMAT; + if (!is_primary_key_pkt(ptag)) { + RNP_LOG("wrong key tag: %d at pos %" PRIu64, ptag, src.readb); + } else { + try { + ret = process_pgp_key(src, key, skiperrors); + } catch (const rnp::rnp_exception &e) { + RNP_LOG("%s", e.what()); + ret = e.code(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + } + if (skiperrors && (ret == RNP_ERROR_BAD_FORMAT) && + !skip_pgp_packets(src, + {PGP_PKT_TRUST, + PGP_PKT_SIGNATURE, + PGP_PKT_USER_ID, + PGP_PKT_USER_ATTR, + PGP_PKT_PUBLIC_SUBKEY, + PGP_PKT_SECRET_SUBKEY})) { + ret = RNP_ERROR_READ; + } + /* change error code if we didn't process anything at all */ + if (srcpos == src.readb) { + ret = RNP_ERROR_BAD_STATE; + } + return ret; +} + +rnp_result_t +process_pgp_keys(pgp_source_t &src, pgp_key_sequence_t &keys, bool skiperrors) +{ + bool has_secret = false; + bool has_public = false; + + keys.keys.clear(); + /* create maybe-armored stream */ + rnp::ArmoredSource armor( + src, rnp::ArmoredSource::AllowBinary | rnp::ArmoredSource::AllowMultiple); + + /* read sequence of transferable OpenPGP keys as described in RFC 4880, 11.1 - 11.2 */ + while (!armor.error()) { + /* Allow multiple armored messages in a single stream */ + if (armor.eof() && armor.multiple()) { + armor.restart(); + } + if (armor.eof()) { + break; + } + /* Attempt to read the next key */ + pgp_transferable_key_t curkey; + rnp_result_t ret = process_pgp_key_auto(armor.src(), curkey, false, skiperrors); + if (ret && (!skiperrors || (ret != RNP_ERROR_BAD_FORMAT))) { + keys.keys.clear(); + return ret; + } + /* check whether we actually read any key or just skipped erroneous packets */ + if (curkey.key.tag == PGP_PKT_RESERVED) { + continue; + } + has_secret |= (curkey.key.tag == PGP_PKT_SECRET_KEY); + has_public |= (curkey.key.tag == PGP_PKT_PUBLIC_KEY); + + keys.keys.emplace_back(std::move(curkey)); + } + + if (has_secret && has_public) { + RNP_LOG("warning! public keys are mixed together with secret ones!"); + } + + if (armor.error()) { + keys.keys.clear(); + return RNP_ERROR_READ; + } + return RNP_SUCCESS; +} + +rnp_result_t +process_pgp_key(pgp_source_t &src, pgp_transferable_key_t &key, bool skiperrors) +{ + key = pgp_transferable_key_t(); + /* create maybe-armored stream */ + rnp::ArmoredSource armor( + src, rnp::ArmoredSource::AllowBinary | rnp::ArmoredSource::AllowMultiple); + + /* main key packet */ + uint64_t keypos = armor.readb(); + int ptag = stream_pkt_type(armor.src()); + if ((ptag <= 0) || !is_primary_key_pkt(ptag)) { + RNP_LOG("wrong key packet tag: %d at %" PRIu64, ptag, keypos); + return RNP_ERROR_BAD_FORMAT; + } + + rnp_result_t ret = key.key.parse(armor.src()); + if (ret) { + RNP_LOG("failed to parse key pkt at %" PRIu64, keypos); + key.key = {}; + return ret; + } + + if (!skip_pgp_packets(armor.src(), {PGP_PKT_TRUST})) { + return RNP_ERROR_READ; + } + + /* direct-key signatures */ + if ((ret = process_pgp_key_signatures(armor.src(), key.signatures, skiperrors))) { + return ret; + } + + /* user ids/attrs with signatures */ + while ((ptag = stream_pkt_type(armor.src())) > 0) { + if ((ptag != PGP_PKT_USER_ID) && (ptag != PGP_PKT_USER_ATTR)) { + break; + } + + pgp_transferable_userid_t uid; + ret = process_pgp_userid(armor.src(), uid, skiperrors); + if ((ret == RNP_ERROR_BAD_FORMAT) && skiperrors && + skip_pgp_packets(armor.src(), {PGP_PKT_TRUST, PGP_PKT_SIGNATURE})) { + /* skip malformed uid */ + continue; + } + if (ret) { + return ret; + } + key.userids.push_back(std::move(uid)); + } + + /* subkeys with signatures */ + while ((ptag = stream_pkt_type(armor.src())) > 0) { + if (!is_subkey_pkt(ptag)) { + break; + } + + pgp_transferable_subkey_t subkey; + ret = process_pgp_subkey(armor.src(), subkey, skiperrors); + if ((ret == RNP_ERROR_BAD_FORMAT) && skiperrors && + skip_pgp_packets(armor.src(), {PGP_PKT_TRUST, PGP_PKT_SIGNATURE})) { + /* skip malformed subkey */ + continue; + } + if (ret) { + return ret; + } + key.subkeys.emplace_back(std::move(subkey)); + } + return ptag >= 0 ? RNP_SUCCESS : RNP_ERROR_BAD_FORMAT; +} + +static rnp_result_t +decrypt_secret_key_v3(pgp_crypt_t *crypt, uint8_t *dec, const uint8_t *enc, size_t len) +{ + size_t idx; + size_t pos = 0; + size_t mpilen; + size_t blsize; + + if (!(blsize = pgp_cipher_block_size(crypt))) { + RNP_LOG("wrong crypto"); + return RNP_ERROR_BAD_STATE; + } + + /* 4 RSA secret mpis with cleartext header */ + for (idx = 0; idx < 4; idx++) { + if (pos + 2 > len) { + RNP_LOG("bad v3 secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + mpilen = (read_uint16(enc + pos) + 7) >> 3; + memcpy(dec + pos, enc + pos, 2); + pos += 2; + if (pos + mpilen > len) { + RNP_LOG("bad v3 secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + pgp_cipher_cfb_decrypt(crypt, dec + pos, enc + pos, mpilen); + pos += mpilen; + if (mpilen < blsize) { + RNP_LOG("bad rsa v3 mpi len"); + return RNP_ERROR_BAD_FORMAT; + } + pgp_cipher_cfb_resync(crypt, enc + pos - blsize); + } + + /* sum16 */ + if (pos + 2 != len) { + return RNP_ERROR_BAD_FORMAT; + } + memcpy(dec + pos, enc + pos, 2); + return RNP_SUCCESS; +} + +static rnp_result_t +parse_secret_key_mpis(pgp_key_pkt_t &key, const uint8_t *mpis, size_t len) +{ + if (!mpis) { + return RNP_ERROR_NULL_POINTER; + } + + /* check the cleartext data */ + switch (key.sec_protection.s2k.usage) { + case PGP_S2KU_NONE: + case PGP_S2KU_ENCRYPTED: { + /* calculate and check sum16 of the cleartext */ + if (len < 2) { + RNP_LOG("No space for checksum."); + return RNP_ERROR_BAD_FORMAT; + } + uint16_t sum = 0; + len -= 2; + for (size_t idx = 0; idx < len; idx++) { + sum += mpis[idx]; + } + uint16_t expsum = read_uint16(mpis + len); + if (sum != expsum) { + RNP_LOG("Wrong key checksum, got 0x%X instead of 0x%X.", (int) sum, (int) expsum); + return RNP_ERROR_DECRYPT_FAILED; + } + break; + } + case PGP_S2KU_ENCRYPTED_AND_HASHED: { + if (len < PGP_SHA1_HASH_SIZE) { + RNP_LOG("No space for hash"); + return RNP_ERROR_BAD_FORMAT; + } + /* calculate and check sha1 hash of the cleartext */ + uint8_t hval[PGP_SHA1_HASH_SIZE]; + try { + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + assert(hash->size() == sizeof(hval)); + len -= PGP_SHA1_HASH_SIZE; + hash->add(mpis, len); + if (hash->finish(hval) != PGP_SHA1_HASH_SIZE) { + return RNP_ERROR_BAD_STATE; + } + } catch (const std::exception &e) { + RNP_LOG("hash calculation failed: %s", e.what()); + return RNP_ERROR_BAD_STATE; + } + if (memcmp(hval, mpis + len, PGP_SHA1_HASH_SIZE)) { + return RNP_ERROR_DECRYPT_FAILED; + } + break; + } + default: + RNP_LOG("unknown s2k usage: %d", (int) key.sec_protection.s2k.usage); + return RNP_ERROR_BAD_PARAMETERS; + } + + try { + /* parse mpis depending on algorithm */ + pgp_packet_body_t body(mpis, len); + + switch (key.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!body.get(key.material.rsa.d) || !body.get(key.material.rsa.p) || + !body.get(key.material.rsa.q) || !body.get(key.material.rsa.u)) { + RNP_LOG("failed to parse rsa secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_DSA: + if (!body.get(key.material.dsa.x)) { + RNP_LOG("failed to parse dsa secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + if (!body.get(key.material.ec.x)) { + RNP_LOG("failed to parse ecc secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!body.get(key.material.eg.x)) { + RNP_LOG("failed to parse eg secret key data"); + return RNP_ERROR_BAD_FORMAT; + } + break; + default: + RNP_LOG("unknown pk alg : %d", (int) key.alg); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (body.left()) { + RNP_LOG("extra data in sec key"); + return RNP_ERROR_BAD_FORMAT; + } + key.material.secret = true; + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +rnp_result_t +decrypt_secret_key(pgp_key_pkt_t *key, const char *password) +{ + if (!key) { + return RNP_ERROR_NULL_POINTER; + } + if (!is_secret_key_pkt(key->tag)) { + return RNP_ERROR_BAD_PARAMETERS; + } + /* mark material as not validated as it may be valid for public part */ + key->material.validity.reset(); + + /* check whether data is not encrypted */ + if (!key->sec_protection.s2k.usage) { + return parse_secret_key_mpis(*key, key->sec_data, key->sec_len); + } + + /* check whether secret key data present */ + if (!key->sec_len) { + RNP_LOG("No secret key data"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* data is encrypted */ + if (!password) { + return RNP_ERROR_NULL_POINTER; + } + + if (key->sec_protection.cipher_mode != PGP_CIPHER_MODE_CFB) { + RNP_LOG("unsupported secret key encryption mode"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> keybuf; + size_t keysize = pgp_key_size(key->sec_protection.symm_alg); + if (!keysize || + !pgp_s2k_derive_key(&key->sec_protection.s2k, password, keybuf.data(), keysize)) { + RNP_LOG("failed to derive key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + try { + rnp::secure_vector<uint8_t> decdata(key->sec_len); + pgp_crypt_t crypt; + if (!pgp_cipher_cfb_start( + &crypt, key->sec_protection.symm_alg, keybuf.data(), key->sec_protection.iv)) { + RNP_LOG("failed to start cfb decryption"); + return RNP_ERROR_DECRYPT_FAILED; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + switch (key->version) { + case PGP_V3: + if (!is_rsa_key_alg(key->alg)) { + RNP_LOG("non-RSA v3 key"); + ret = RNP_ERROR_BAD_PARAMETERS; + break; + } + ret = decrypt_secret_key_v3(&crypt, decdata.data(), key->sec_data, key->sec_len); + break; + case PGP_V4: + pgp_cipher_cfb_decrypt(&crypt, decdata.data(), key->sec_data, key->sec_len); + ret = RNP_SUCCESS; + break; + default: + ret = RNP_ERROR_BAD_PARAMETERS; + } + + pgp_cipher_cfb_finish(&crypt); + if (ret) { + return ret; + } + + return parse_secret_key_mpis(*key, decdata.data(), key->sec_len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +static void +write_secret_key_mpis(pgp_packet_body_t &body, pgp_key_pkt_t &key) +{ + /* add mpis */ + switch (key.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + body.add(key.material.rsa.d); + body.add(key.material.rsa.p); + body.add(key.material.rsa.q); + body.add(key.material.rsa.u); + break; + case PGP_PKA_DSA: + body.add(key.material.dsa.x); + break; + case PGP_PKA_EDDSA: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + body.add(key.material.ec.x); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + body.add(key.material.eg.x); + break; + default: + RNP_LOG("unknown pk alg : %d", (int) key.alg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + /* add sum16 if sha1 is not used */ + if (key.sec_protection.s2k.usage != PGP_S2KU_ENCRYPTED_AND_HASHED) { + uint16_t sum = 0; + for (size_t i = 0; i < body.size(); i++) { + sum += body.data()[i]; + } + body.add_uint16(sum); + return; + } + + /* add sha1 hash */ + auto hash = rnp::Hash::create(PGP_HASH_SHA1); + hash->add(body.data(), body.size()); + uint8_t hval[PGP_SHA1_HASH_SIZE]; + assert(sizeof(hval) == hash->size()); + if (hash->finish(hval) != PGP_SHA1_HASH_SIZE) { + RNP_LOG("failed to finish hash"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + body.add(hval, PGP_SHA1_HASH_SIZE); +} + +rnp_result_t +encrypt_secret_key(pgp_key_pkt_t *key, const char *password, rnp::RNG &rng) +{ + if (!is_secret_key_pkt(key->tag) || !key->material.secret) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (key->sec_protection.s2k.usage && + (key->sec_protection.cipher_mode != PGP_CIPHER_MODE_CFB)) { + RNP_LOG("unsupported secret key encryption mode"); + return RNP_ERROR_BAD_PARAMETERS; + } + + try { + /* build secret key data */ + pgp_packet_body_t body(PGP_PKT_RESERVED); + body.mark_secure(); + write_secret_key_mpis(body, *key); + + /* check whether data is not encrypted */ + if (key->sec_protection.s2k.usage == PGP_S2KU_NONE) { + secure_clear(key->sec_data, key->sec_len); + free(key->sec_data); + key->sec_data = (uint8_t *) malloc(body.size()); + if (!key->sec_data) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(key->sec_data, body.data(), body.size()); + key->sec_len = body.size(); + return RNP_SUCCESS; + } + if (key->version < PGP_V4) { + RNP_LOG("encryption of v3 keys is not supported"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* data is encrypted */ + size_t keysize = pgp_key_size(key->sec_protection.symm_alg); + size_t blsize = pgp_block_size(key->sec_protection.symm_alg); + if (!keysize || !blsize) { + RNP_LOG("wrong symm alg"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* generate iv and s2k salt */ + rng.get(key->sec_protection.iv, blsize); + if ((key->sec_protection.s2k.specifier != PGP_S2KS_SIMPLE)) { + rng.get(key->sec_protection.s2k.salt, PGP_SALT_SIZE); + } + /* derive key */ + rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> keybuf; + if (!pgp_s2k_derive_key(&key->sec_protection.s2k, password, keybuf.data(), keysize)) { + RNP_LOG("failed to derive key"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* encrypt sec data */ + pgp_crypt_t crypt; + if (!pgp_cipher_cfb_start( + &crypt, key->sec_protection.symm_alg, keybuf.data(), key->sec_protection.iv)) { + RNP_LOG("failed to start cfb encryption"); + return RNP_ERROR_DECRYPT_FAILED; + } + pgp_cipher_cfb_encrypt(&crypt, body.data(), body.data(), body.size()); + pgp_cipher_cfb_finish(&crypt); + secure_clear(key->sec_data, key->sec_len); + free(key->sec_data); + key->sec_data = (uint8_t *) malloc(body.size()); + if (!key->sec_data) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(key->sec_data, body.data(), body.size()); + key->sec_len = body.size(); + /* cleanup cleartext fields */ + forget_secret_key_fields(&key->material); + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_GENERIC; + } +} + +void +forget_secret_key_fields(pgp_key_material_t *key) +{ + if (!key || !key->secret) { + return; + } + + switch (key->alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + mpi_forget(&key->rsa.d); + mpi_forget(&key->rsa.p); + mpi_forget(&key->rsa.q); + mpi_forget(&key->rsa.u); + break; + case PGP_PKA_DSA: + mpi_forget(&key->dsa.x); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + mpi_forget(&key->eg.x); + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + case PGP_PKA_ECDH: + mpi_forget(&key->ec.x); + break; + default: + RNP_LOG("unknown key algorithm: %d", (int) key->alg); + } + + key->secret = false; +} + +pgp_userid_pkt_t::pgp_userid_pkt_t(const pgp_userid_pkt_t &src) +{ + tag = src.tag; + uid_len = src.uid_len; + uid = NULL; + if (src.uid) { + uid = (uint8_t *) malloc(uid_len); + if (!uid) { + throw std::bad_alloc(); + } + memcpy(uid, src.uid, uid_len); + } +} + +pgp_userid_pkt_t::pgp_userid_pkt_t(pgp_userid_pkt_t &&src) +{ + tag = src.tag; + uid_len = src.uid_len; + uid = src.uid; + src.uid = NULL; +} + +pgp_userid_pkt_t & +pgp_userid_pkt_t::operator=(pgp_userid_pkt_t &&src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + uid_len = src.uid_len; + free(uid); + uid = src.uid; + src.uid = NULL; + return *this; +} + +pgp_userid_pkt_t & +pgp_userid_pkt_t::operator=(const pgp_userid_pkt_t &src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + uid_len = src.uid_len; + free(uid); + uid = NULL; + if (src.uid) { + uid = (uint8_t *) malloc(uid_len); + if (!uid) { + throw std::bad_alloc(); + } + memcpy(uid, src.uid, uid_len); + } + return *this; +} + +bool +pgp_userid_pkt_t::operator==(const pgp_userid_pkt_t &src) const +{ + return (tag == src.tag) && (uid_len == src.uid_len) && !memcmp(uid, src.uid, uid_len); +} + +bool +pgp_userid_pkt_t::operator!=(const pgp_userid_pkt_t &src) const +{ + return !(*this == src); +} + +pgp_userid_pkt_t::~pgp_userid_pkt_t() +{ + free(uid); +} + +void +pgp_userid_pkt_t::write(pgp_dest_t &dst) const +{ + if ((tag != PGP_PKT_USER_ID) && (tag != PGP_PKT_USER_ATTR)) { + RNP_LOG("wrong userid tag"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (uid_len && !uid) { + RNP_LOG("null but non-empty userid"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + pgp_packet_body_t pktbody(tag); + if (uid) { + pktbody.add(uid, uid_len); + } + pktbody.write(dst); +} + +rnp_result_t +pgp_userid_pkt_t::parse(pgp_source_t &src) +{ + /* check the tag */ + int stag = stream_pkt_type(src); + if ((stag != PGP_PKT_USER_ID) && (stag != PGP_PKT_USER_ATTR)) { + RNP_LOG("wrong userid tag: %d", stag); + return RNP_ERROR_BAD_FORMAT; + } + + pgp_packet_body_t pkt(PGP_PKT_RESERVED); + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + + /* userid type, i.e. tag */ + tag = (pgp_pkt_type_t) stag; + free(uid); + uid = (uint8_t *) malloc(pkt.size()); + if (!uid) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(uid, pkt.data(), pkt.size()); + uid_len = pkt.size(); + return RNP_SUCCESS; +} + +pgp_key_pkt_t::pgp_key_pkt_t(const pgp_key_pkt_t &src, bool pubonly) +{ + if (pubonly && is_secret_key_pkt(src.tag)) { + tag = (src.tag == PGP_PKT_SECRET_KEY) ? PGP_PKT_PUBLIC_KEY : PGP_PKT_PUBLIC_SUBKEY; + } else { + tag = src.tag; + } + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + hashed_data = NULL; + if (src.hashed_data) { + hashed_data = (uint8_t *) malloc(hashed_len); + if (!hashed_data) { + throw std::bad_alloc(); + } + memcpy(hashed_data, src.hashed_data, hashed_len); + } + material = src.material; + if (pubonly) { + forget_secret_key_fields(&material); + sec_len = 0; + sec_data = NULL; + sec_protection = {}; + return; + } + sec_len = src.sec_len; + sec_data = NULL; + if (src.sec_data) { + sec_data = (uint8_t *) malloc(sec_len); + if (!sec_data) { + free(hashed_data); + hashed_data = NULL; + throw std::bad_alloc(); + } + memcpy(sec_data, src.sec_data, sec_len); + } + sec_protection = src.sec_protection; +} + +pgp_key_pkt_t::pgp_key_pkt_t(pgp_key_pkt_t &&src) +{ + tag = src.tag; + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + hashed_data = src.hashed_data; + src.hashed_data = NULL; + material = src.material; + forget_secret_key_fields(&src.material); + sec_len = src.sec_len; + sec_data = src.sec_data; + src.sec_data = NULL; + sec_protection = src.sec_protection; +} + +pgp_key_pkt_t & +pgp_key_pkt_t::operator=(pgp_key_pkt_t &&src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + free(hashed_data); + hashed_data = src.hashed_data; + src.hashed_data = NULL; + material = src.material; + forget_secret_key_fields(&src.material); + secure_clear(sec_data, sec_len); + free(sec_data); + sec_len = src.sec_len; + sec_data = src.sec_data; + src.sec_data = NULL; + src.sec_len = 0; + sec_protection = src.sec_protection; + return *this; +} + +pgp_key_pkt_t & +pgp_key_pkt_t::operator=(const pgp_key_pkt_t &src) +{ + if (this == &src) { + return *this; + } + tag = src.tag; + version = src.version; + creation_time = src.creation_time; + alg = src.alg; + v3_days = src.v3_days; + hashed_len = src.hashed_len; + free(hashed_data); + hashed_data = NULL; + if (src.hashed_data) { + hashed_data = (uint8_t *) malloc(hashed_len); + if (!hashed_data) { + throw std::bad_alloc(); + } + memcpy(hashed_data, src.hashed_data, hashed_len); + } + material = src.material; + secure_clear(sec_data, sec_len); + free(sec_data); + sec_data = NULL; + sec_len = src.sec_len; + if (src.sec_data) { + sec_data = (uint8_t *) malloc(sec_len); + if (!sec_data) { + free(hashed_data); + hashed_data = NULL; + throw std::bad_alloc(); + } + memcpy(sec_data, src.sec_data, sec_len); + } + sec_protection = src.sec_protection; + return *this; +} + +pgp_key_pkt_t::~pgp_key_pkt_t() +{ + forget_secret_key_fields(&material); + free(hashed_data); + secure_clear(sec_data, sec_len); + free(sec_data); +} + +void +pgp_key_pkt_t::write(pgp_dest_t &dst) +{ + if (!is_key_pkt(tag)) { + RNP_LOG("wrong key tag"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (!hashed_data) { + fill_hashed_data(); + } + + pgp_packet_body_t pktbody(tag); + /* all public key data is written in hashed_data */ + pktbody.add(hashed_data, hashed_len); + /* if we have public key then we do not need further processing */ + if (!is_secret_key_pkt(tag)) { + pktbody.write(dst); + return; + } + + /* secret key fields should be pre-populated in sec_data field */ + if ((sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) && (!sec_data || !sec_len)) { + RNP_LOG("secret key data is not populated"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + pktbody.add_byte(sec_protection.s2k.usage); + + switch (sec_protection.s2k.usage) { + case PGP_S2KU_NONE: + break; + case PGP_S2KU_ENCRYPTED_AND_HASHED: + case PGP_S2KU_ENCRYPTED: { + pktbody.add_byte(sec_protection.symm_alg); + pktbody.add(sec_protection.s2k); + if (sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) { + size_t blsize = pgp_block_size(sec_protection.symm_alg); + if (!blsize) { + RNP_LOG("wrong block size"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + pktbody.add(sec_protection.iv, blsize); + } + break; + } + default: + RNP_LOG("wrong s2k usage"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + if (sec_len) { + /* if key is stored on card, or exported via gpg --export-secret-subkeys, then + * sec_data is empty */ + pktbody.add(sec_data, sec_len); + } + pktbody.write(dst); +} + +rnp_result_t +pgp_key_pkt_t::parse(pgp_source_t &src) +{ + /* check the key tag */ + int atag = stream_pkt_type(src); + if (!is_key_pkt(atag)) { + RNP_LOG("wrong key packet tag: %d", atag); + return RNP_ERROR_BAD_FORMAT; + } + + pgp_packet_body_t pkt((pgp_pkt_type_t) atag); + /* Read the packet into memory */ + rnp_result_t res = pkt.read(src); + if (res) { + return res; + } + /* key type, i.e. tag */ + tag = (pgp_pkt_type_t) atag; + /* version */ + uint8_t ver = 0; + if (!pkt.get(ver) || (ver < PGP_V2) || (ver > PGP_V4)) { + RNP_LOG("wrong key packet version"); + return RNP_ERROR_BAD_FORMAT; + } + version = (pgp_version_t) ver; + /* creation time */ + if (!pkt.get(creation_time)) { + return RNP_ERROR_BAD_FORMAT; + } + /* v3: validity days */ + if ((version < PGP_V4) && !pkt.get(v3_days)) { + return RNP_ERROR_BAD_FORMAT; + } + /* key algorithm */ + uint8_t analg = 0; + if (!pkt.get(analg)) { + return RNP_ERROR_BAD_FORMAT; + } + alg = (pgp_pubkey_alg_t) analg; + material.alg = (pgp_pubkey_alg_t) analg; + /* v3 keys must be RSA-only */ + if ((version < PGP_V4) && !is_rsa_key_alg(alg)) { + RNP_LOG("wrong v3 pk algorithm"); + return RNP_ERROR_BAD_FORMAT; + } + /* algorithm specific fields */ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + if (!pkt.get(material.rsa.n) || !pkt.get(material.rsa.e)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_DSA: + if (!pkt.get(material.dsa.p) || !pkt.get(material.dsa.q) || !pkt.get(material.dsa.g) || + !pkt.get(material.dsa.y)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + if (!pkt.get(material.eg.p) || !pkt.get(material.eg.g) || !pkt.get(material.eg.y)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + if (!pkt.get(material.ec.curve) || !pkt.get(material.ec.p)) { + return RNP_ERROR_BAD_FORMAT; + } + break; + case PGP_PKA_ECDH: { + if (!pkt.get(material.ec.curve) || !pkt.get(material.ec.p)) { + return RNP_ERROR_BAD_FORMAT; + } + /* read KDF parameters. At the moment should be 0x03 0x01 halg ealg */ + uint8_t len = 0, halg = 0, walg = 0; + if (!pkt.get(len) || (len != 3)) { + return RNP_ERROR_BAD_FORMAT; + } + if (!pkt.get(len) || (len != 1)) { + return RNP_ERROR_BAD_FORMAT; + } + if (!pkt.get(halg) || !pkt.get(walg)) { + return RNP_ERROR_BAD_FORMAT; + } + material.ec.kdf_hash_alg = (pgp_hash_alg_t) halg; + material.ec.key_wrap_alg = (pgp_symm_alg_t) walg; + break; + } + default: + RNP_LOG("unknown key algorithm: %d", (int) alg); + return RNP_ERROR_BAD_FORMAT; + } + /* fill hashed data used for signatures */ + if (!(hashed_data = (uint8_t *) malloc(pkt.size() - pkt.left()))) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(hashed_data, pkt.data(), pkt.size() - pkt.left()); + hashed_len = pkt.size() - pkt.left(); + + /* secret key fields if any */ + if (is_secret_key_pkt(tag)) { + uint8_t usage = 0; + if (!pkt.get(usage)) { + RNP_LOG("failed to read key protection"); + return RNP_ERROR_BAD_FORMAT; + } + sec_protection.s2k.usage = (pgp_s2k_usage_t) usage; + sec_protection.cipher_mode = PGP_CIPHER_MODE_CFB; + + switch (sec_protection.s2k.usage) { + case PGP_S2KU_NONE: + break; + case PGP_S2KU_ENCRYPTED: + case PGP_S2KU_ENCRYPTED_AND_HASHED: { + /* we have s2k */ + uint8_t salg = 0; + if (!pkt.get(salg) || !pkt.get(sec_protection.s2k)) { + RNP_LOG("failed to read key protection"); + return RNP_ERROR_BAD_FORMAT; + } + sec_protection.symm_alg = (pgp_symm_alg_t) salg; + break; + } + default: + /* old-style: usage is symmetric algorithm identifier */ + sec_protection.symm_alg = (pgp_symm_alg_t) usage; + sec_protection.s2k.usage = PGP_S2KU_ENCRYPTED; + sec_protection.s2k.specifier = PGP_S2KS_SIMPLE; + sec_protection.s2k.hash_alg = PGP_HASH_MD5; + break; + } + + /* iv */ + if (sec_protection.s2k.usage && + (sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL)) { + size_t bl_size = pgp_block_size(sec_protection.symm_alg); + if (!bl_size || !pkt.get(sec_protection.iv, bl_size)) { + RNP_LOG("failed to read iv"); + return RNP_ERROR_BAD_FORMAT; + } + } + + /* encrypted/cleartext secret MPIs are left */ + size_t asec_len = pkt.left(); + if (!asec_len) { + sec_data = NULL; + } else { + if (!(sec_data = (uint8_t *) calloc(1, asec_len))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!pkt.get(sec_data, asec_len)) { + return RNP_ERROR_BAD_STATE; + } + } + sec_len = asec_len; + } + + if (pkt.left()) { + RNP_LOG("extra %d bytes in key packet", (int) pkt.left()); + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +void +pgp_key_pkt_t::fill_hashed_data() +{ + /* we don't have a need to write v2-v3 signatures */ + if (version != PGP_V4) { + RNP_LOG("unknown key version %d", (int) version); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + pgp_packet_body_t hbody(PGP_PKT_RESERVED); + hbody.add_byte(version); + hbody.add_uint32(creation_time); + hbody.add_byte(alg); + /* Algorithm specific fields */ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + hbody.add(material.rsa.n); + hbody.add(material.rsa.e); + break; + case PGP_PKA_DSA: + hbody.add(material.dsa.p); + hbody.add(material.dsa.q); + hbody.add(material.dsa.g); + hbody.add(material.dsa.y); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + hbody.add(material.eg.p); + hbody.add(material.eg.g); + hbody.add(material.eg.y); + break; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + hbody.add(material.ec.curve); + hbody.add(material.ec.p); + break; + case PGP_PKA_ECDH: + hbody.add(material.ec.curve); + hbody.add(material.ec.p); + hbody.add_byte(3); + hbody.add_byte(1); + hbody.add_byte(material.ec.kdf_hash_alg); + hbody.add_byte(material.ec.key_wrap_alg); + break; + default: + RNP_LOG("unknown key algorithm: %d", (int) alg); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + hashed_data = (uint8_t *) malloc(hbody.size()); + if (!hashed_data) { + RNP_LOG("allocation failed"); + throw rnp::rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } + memcpy(hashed_data, hbody.data(), hbody.size()); + hashed_len = hbody.size(); +} + +bool +pgp_key_pkt_t::equals(const pgp_key_pkt_t &key, bool pubonly) const noexcept +{ + /* check tag. We allow public/secret key comparison here */ + if (pubonly) { + if (is_subkey_pkt(tag) && !is_subkey_pkt(key.tag)) { + return false; + } + if (is_key_pkt(tag) && !is_key_pkt(key.tag)) { + return false; + } + } else if (tag != key.tag) { + return false; + } + /* check basic fields */ + if ((version != key.version) || (alg != key.alg) || (creation_time != key.creation_time)) { + return false; + } + /* check key material */ + return key_material_equal(&material, &key.material); +} + +pgp_transferable_subkey_t::pgp_transferable_subkey_t(const pgp_transferable_subkey_t &src, + bool pubonly) +{ + subkey = pgp_key_pkt_t(src.subkey, pubonly); + signatures = src.signatures; +} + +pgp_transferable_key_t::pgp_transferable_key_t(const pgp_transferable_key_t &src, bool pubonly) +{ + key = pgp_key_pkt_t(src.key, pubonly); + userids = src.userids; + subkeys = src.subkeys; + signatures = src.signatures; +} |