From 319d4ce3a6e5feb9589090e8ed72b7fbaca1f6ef Mon Sep 17 00:00:00 2001 From: Nickolay Olshevsky Date: Fri, 25 Aug 2023 15:55:04 +0300 Subject: [PATCH 01/12] Add fields, related to designated revoker, to pgp_key_t and pgp_signature_t classes. --- src/lib/pgp-key.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/lib/pgp-key.h | 7 +++++-- src/lib/types.h | 3 ++- src/librepgp/stream-dump.cpp | 4 ++-- src/librepgp/stream-sig.cpp | 34 ++++++++++++++++++++++++++++++++-- src/librepgp/stream-sig.h | 13 +++++++++++++ 6 files changed, 90 insertions(+), 7 deletions(-) diff --git a/src/lib/pgp-key.cpp b/src/lib/pgp-key.cpp index f734c6e138..7599343c3e 100644 --- a/src/lib/pgp-key.cpp +++ b/src/lib/pgp-key.cpp @@ -826,6 +826,7 @@ pgp_key_t::pgp_key_t(const pgp_key_t &src, bool pubonly) uid0_set_ = src.uid0_set_; revoked_ = src.revoked_; revocation_ = src.revocation_; + revokers_ = src.revokers_; format = src.format; validity_ = src.validity_; valid_till_ = src.valid_till_; @@ -1141,6 +1142,28 @@ pgp_key_t::clear_revokes() } } +void +pgp_key_t::add_revoker(const pgp_fingerprint_t &revoker) +{ + for (auto &rev : revokers_) { + if (rev == revoker) { + return; + } + } + revokers_.push_back(revoker); +} + +bool +pgp_key_t::has_revoker(const pgp_fingerprint_t &revoker) const +{ + for (auto &rev : revokers_) { + if (rev == revoker) { + return true; + } + } + return false; +} + const pgp_key_pkt_t & pgp_key_t::pkt() const { @@ -2557,6 +2580,19 @@ pgp_key_t::refresh_data(const rnp::SecurityContext &ctx) } else { flags_ = pgp_pk_alg_capabilities(alg()); } + /* designated revokers */ + revokers_.clear(); + for (size_t i = 0; i < sig_count(); i++) { + pgp_subsig_t &sig = get_sig(i); + /* pick designated revokers only from direct-key signatures */ + if (!sig.valid() || !is_direct_self(sig)) { + continue; + } + if (!sig.sig.has_revoker()) { + continue; + } + add_revoker(sig.sig.revoker()); + } /* revocation(s) */ clear_revokes(); for (size_t i = 0; i < sig_count(); i++) { diff --git a/src/lib/pgp-key.h b/src/lib/pgp-key.h index dd325ebd7e..6a3916cd3a 100644 --- a/src/lib/pgp-key.h +++ b/src/lib/pgp-key.h @@ -156,8 +156,9 @@ struct pgp_key_t { bool uid0_set_{}; /* flag for the above */ bool revoked_{}; /* key has been revoked */ pgp_revoke_t revocation_{}; /* revocation reason */ - pgp_validity_t validity_{}; /* key's validity */ - uint64_t valid_till_{}; /* date till which key is/was valid */ + std::vector revokers_{}; + pgp_validity_t validity_{}; /* key's validity */ + uint64_t valid_till_{}; /* date till which key is/was valid */ pgp_subsig_t *latest_uid_selfcert(uint32_t uid); void validate_primary(rnp::KeyStore &keyring); @@ -203,6 +204,8 @@ struct pgp_key_t { bool revoked() const; const pgp_revoke_t &revocation() const; void clear_revokes(); + void add_revoker(const pgp_fingerprint_t &revoker); + bool has_revoker(const pgp_fingerprint_t &revoker) const; const pgp_key_pkt_t &pkt() const; pgp_key_pkt_t & pkt(); diff --git a/src/lib/types.h b/src/lib/types.h index 850c4b0e91..cf1744d1fa 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -302,9 +302,10 @@ typedef struct pgp_sig_subpkt_t { /* 5.2.3.8. Preferred Hash Algorithms */ /* 5.2.3.9. Preferred Compression Algorithms */ struct { - uint8_t klass; + uint8_t revclass; pgp_pubkey_alg_t pkalg; uint8_t * fp; + size_t fp_len; } revocation_key; /* 5.2.3.15. Revocation Key */ uint8_t *issuer; /* 5.2.3.5. Issuer */ struct { diff --git a/src/librepgp/stream-dump.cpp b/src/librepgp/stream-dump.cpp index 85b1abcee2..efa528fefa 100644 --- a/src/librepgp/stream-dump.cpp +++ b/src/librepgp/stream-dump.cpp @@ -619,7 +619,7 @@ signature_dump_subpacket(rnp_dump_ctx_t *ctx, pgp_dest_t *dst, const pgp_sig_sub break; case PGP_SIG_SUBPKT_REVOCATION_KEY: dst_printf(dst, "%s\n", sname); - dst_printf(dst, "class: %d\n", (int) subpkt.fields.revocation_key.klass); + dst_printf(dst, "class: %d\n", (int) subpkt.fields.revocation_key.revclass); dst_print_palg(dst, NULL, subpkt.fields.revocation_key.pkalg); dst_print_hex( dst, "fingerprint", subpkt.fields.revocation_key.fp, PGP_FINGERPRINT_V4_SIZE, true); @@ -1769,7 +1769,7 @@ signature_dump_subpacket_json(rnp_dump_ctx_t * ctx, subpkt.fields.preferred.len, aead_alg_map); case PGP_SIG_SUBPKT_REVOCATION_KEY: - return json_add(obj, "class", (int) subpkt.fields.revocation_key.klass) && + return json_add(obj, "class", (int) subpkt.fields.revocation_key.revclass) && json_add(obj, "algorithm", (int) subpkt.fields.revocation_key.pkalg) && json_add_hex( obj, "fingerprint", subpkt.fields.revocation_key.fp, PGP_FINGERPRINT_V4_SIZE); diff --git a/src/librepgp/stream-sig.cpp b/src/librepgp/stream-sig.cpp index 75b04ee12f..3888ccc014 100644 --- a/src/librepgp/stream-sig.cpp +++ b/src/librepgp/stream-sig.cpp @@ -308,10 +308,11 @@ pgp_sig_subpkt_t::parse() fields.preferred.len = len; break; case PGP_SIG_SUBPKT_REVOCATION_KEY: - if ((oklen = len == 22)) { - fields.revocation_key.klass = data[0]; + if ((oklen = ((len == 22) || (len == 34)))) { + fields.revocation_key.revclass = data[0]; fields.revocation_key.pkalg = (pgp_pubkey_alg_t) data[1]; fields.revocation_key.fp = &data[2]; + fields.revocation_key.fp_len = len - 2; } break; case PGP_SIG_SUBPKT_ISSUER_KEY_ID: @@ -1178,6 +1179,35 @@ pgp_signature_t::set_embedded_sig(const pgp_signature_t &esig) subpkt.parsed = true; } +const pgp_sig_subpkt_t * +pgp_signature_t::revoker_subpkt() const noexcept +{ + if (version < PGP_V4) { + return nullptr; + } + auto subpkt = get_subpkt(PGP_SIG_SUBPKT_REVOCATION_KEY); + return subpkt && subpkt->hashed ? subpkt : nullptr; +} + +bool +pgp_signature_t::has_revoker() const noexcept +{ + return revoker_subpkt(); +} + +pgp_fingerprint_t +pgp_signature_t::revoker() const noexcept +{ + pgp_fingerprint_t res{}; + auto subpkt = revoker_subpkt(); + if (!subpkt) { + return res; + } + res.length = subpkt->fields.revocation_key.fp_len; + memcpy(res.fingerprint, subpkt->fields.revocation_key.fp, res.length); + return res; +} + pgp_sig_subpkt_t & pgp_signature_t::add_subpkt(pgp_sig_subpacket_type_t type, size_t datalen, bool reuse) { diff --git a/src/librepgp/stream-sig.h b/src/librepgp/stream-sig.h index a3395dd6a6..fd96b5382a 100644 --- a/src/librepgp/stream-sig.h +++ b/src/librepgp/stream-sig.h @@ -45,6 +45,8 @@ typedef struct pgp_signature_t { bool parse_subpackets(uint8_t *buf, size_t len, bool hashed); static bool version_supported(pgp_version_t version); + const pgp_sig_subpkt_t *revoker_subpkt() const noexcept; + public: pgp_version_t version; /* common v3 and v4 fields */ @@ -331,6 +333,17 @@ typedef struct pgp_signature_t { */ void set_embedded_sig(const pgp_signature_t &esig); + /** + * @brief Check whether signature includes revocation key subpacket. + */ + bool has_revoker() const noexcept; + + /** + * @brief Get the revocation key fingerprint, if it is available. Otherwise empty + * fingerprint will be returned. + */ + pgp_fingerprint_t revoker() const noexcept; + /** * @brief Add subpacket of the specified type to v4 signature * @param type type of the subpacket From 35ded8e582a4ae34a28c9115431dab6de165ab39 Mon Sep 17 00:00:00 2001 From: Nickolay Olshevsky Date: Fri, 25 Aug 2023 15:55:55 +0300 Subject: [PATCH 02/12] Add support for validation of designated revocations. --- src/lib/pgp-key.cpp | 149 ++++++++++++++++++++++++--------- src/lib/pgp-key.h | 18 ++++ src/librekey/rnp_key_store.cpp | 12 ++- 3 files changed, 137 insertions(+), 42 deletions(-) diff --git a/src/lib/pgp-key.cpp b/src/lib/pgp-key.cpp index 7599343c3e..b0f1bab983 100644 --- a/src/lib/pgp-key.cpp +++ b/src/lib/pgp-key.cpp @@ -663,9 +663,15 @@ pgp_subsig_t::validated() const bool pgp_subsig_t::is_cert() const { - pgp_sig_type_t type = sig.type(); - return (type == PGP_CERT_CASUAL) || (type == PGP_CERT_GENERIC) || - (type == PGP_CERT_PERSONA) || (type == PGP_CERT_POSITIVE); + switch (sig.type()) { + case PGP_CERT_CASUAL: + case PGP_CERT_GENERIC: + case PGP_CERT_PERSONA: + case PGP_CERT_POSITIVE: + return true; + default: + return false; + } } bool @@ -2000,8 +2006,17 @@ pgp_key_t::validate_sig(const pgp_key_t & key, validate_binding(sinfo, key, ctx); break; case PGP_SIG_DIRECT: - case PGP_SIG_REV_KEY: + if (!is_signer(sig)) { + RNP_LOG("Invalid direct key signer."); + return; + } validate_direct(sinfo, ctx); + case PGP_SIG_REV_KEY: + if (!is_signer(sig)) { + RNP_LOG("Invalid key revocation signer."); + return; + } + validate_key_rev(sinfo, key.pkt(), ctx); break; case PGP_SIG_REV_SUBKEY: if (!is_signer(sig)) { @@ -2161,14 +2176,26 @@ pgp_key_t::validate_direct(pgp_signature_info_t &sinfo, const rnp::SecurityConte validate_sig(sinfo, *hash, ctx); } +void +pgp_key_t::validate_key_rev(pgp_signature_info_t & sinfo, + const pgp_key_pkt_t & key, + const rnp::SecurityContext &ctx) const +{ + auto hash = signature_hash_direct(*sinfo.sig, key); + validate_sig(sinfo, *hash, ctx); +} + void pgp_key_t::validate_self_signatures(const rnp::SecurityContext &ctx) { for (auto &sigid : sigs_) { - pgp_subsig_t &sig = get_sig(sigid); + auto &sig = get_sig(sigid); if (sig.validity.validated) { continue; } + if (!is_signer(sig)) { + continue; + } if (is_direct_self(sig) || is_self_cert(sig) || is_uid_revocation(sig) || is_revocation(sig)) { @@ -2192,6 +2219,42 @@ pgp_key_t::validate_self_signatures(pgp_key_t &primary, const rnp::SecurityConte } } +bool +pgp_key_t::validate_desig_revokes(rnp::KeyStore &keyring) +{ + if (revokers_.empty()) { + return false; + } + bool refresh = false; + for (auto &sigid : sigs_) { + auto &sig = get_sig(sigid); + if (!is_revocation(sig) || is_signer(sig)) { + continue; + } + /* Don't think we should deal with sigs without issuer's fingerprint */ + if (!sig.sig.has_keyfp() || !has_revoker(sig.sig.keyfp())) { + continue; + } + /* If signature was validated and valid, do not re-validate it */ + if (sig.validated() && sig.validity.valid) { + continue; + } + + auto revoker = keyring.get_signer(sig.sig); + if (!revoker) { + continue; + } + + revoker->validate_sig(*this, sig, keyring.secctx); + if (sig.valid()) { + /* return true only if new valid revocation was added */ + refresh = true; + } + } + + return refresh; +} + void pgp_key_t::validate_primary(rnp::KeyStore &keyring) { @@ -2309,13 +2372,13 @@ pgp_key_t::validate(rnp::KeyStore &keyring) validity_.reset(); if (!is_subkey()) { validate_primary(keyring); - } else { - pgp_key_t *primary = NULL; - if (has_primary_fp()) { - primary = keyring.get_key(primary_fp()); - } - validate_subkey(primary, keyring.secctx); + return; } + pgp_key_t *primary = nullptr; + if (has_primary_fp()) { + primary = keyring.get_key(primary_fp()); + } + validate_subkey(primary, keyring.secctx); } void @@ -2331,6 +2394,7 @@ pgp_key_t::revalidate(rnp::KeyStore &keyring) return; } + validate_desig_revokes(keyring); validate(keyring); if (!refresh_data(keyring.secctx)) { RNP_LOG("Failed to refresh key data"); @@ -2543,6 +2607,38 @@ pgp_key_t::add_sub_binding(pgp_key_t & subsec, subpub.add_sig(sig); } +void +pgp_key_t::refresh_revocations() +{ + clear_revokes(); + for (size_t i = 0; i < sig_count(); i++) { + pgp_subsig_t &sig = get_sig(i); + if (!sig.valid()) { + continue; + } + if (is_revocation(sig)) { + if (revoked_) { + continue; + } + revoked_ = true; + revocation_ = pgp_revoke_t(sig); + continue; + } + if (is_uid_revocation(sig)) { + if (sig.uid >= uid_count()) { + RNP_LOG("Invalid uid index"); + continue; + } + pgp_userid_t &uid = get_uid(sig.uid); + if (uid.revoked) { + continue; + } + uid.revoked = true; + uid.revocation = pgp_revoke_t(sig); + } + } +} + bool pgp_key_t::refresh_data(const rnp::SecurityContext &ctx) { @@ -2594,36 +2690,7 @@ pgp_key_t::refresh_data(const rnp::SecurityContext &ctx) add_revoker(sig.sig.revoker()); } /* revocation(s) */ - clear_revokes(); - for (size_t i = 0; i < sig_count(); i++) { - pgp_subsig_t &sig = get_sig(i); - if (!sig.valid()) { - continue; - } - try { - if (is_revocation(sig)) { - if (revoked_) { - continue; - } - revoked_ = true; - revocation_ = pgp_revoke_t(sig); - } else if (is_uid_revocation(sig)) { - if (sig.uid >= uid_count()) { - RNP_LOG("Invalid uid index"); - continue; - } - pgp_userid_t &uid = get_uid(sig.uid); - if (uid.revoked) { - continue; - } - uid.revoked = true; - uid.revocation = pgp_revoke_t(sig); - } - } catch (const std::exception &e) { - RNP_LOG("%s", e.what()); - return false; - } - } + refresh_revocations(); /* valid till */ valid_till_ = valid_till_common(expired()); /* userid validities */ diff --git a/src/lib/pgp-key.h b/src/lib/pgp-key.h index 6a3916cd3a..942df2aeb2 100644 --- a/src/lib/pgp-key.h +++ b/src/lib/pgp-key.h @@ -465,8 +465,24 @@ struct pgp_key_t { */ void validate_direct(pgp_signature_info_t &sinfo, const rnp::SecurityContext &ctx) const; + /** + * @brief Validate key revocation. + * + * @param sinfo populated signature info. Validation results will be stored here. + * @param key key to which revocation belongs. + */ + void validate_key_rev(pgp_signature_info_t & sinfo, + const pgp_key_pkt_t & key, + const rnp::SecurityContext &ctx) const; + void validate_self_signatures(const rnp::SecurityContext &ctx); void validate_self_signatures(pgp_key_t &primary, const rnp::SecurityContext &ctx); + + /* + * @brief Validate designated revocations. As those are issued by another key, this is + * handled differently from self-signatures as requires access to the whole keyring. + */ + bool validate_desig_revokes(rnp::KeyStore &keyring); void validate(rnp::KeyStore &keyring); void validate_subkey(pgp_key_t *primary, const rnp::SecurityContext &ctx); void revalidate(rnp::KeyStore &keyring); @@ -590,6 +606,8 @@ struct pgp_key_t { bool refresh_data(const rnp::SecurityContext &ctx); /** @brief Refresh internal fields after subkey is updated */ bool refresh_data(pgp_key_t *primary, const rnp::SecurityContext &ctx); + /** @brief Refresh revocation status. */ + void refresh_revocations(); /** @brief Merge primary key with the src, i.e. add all new userids/signatures/subkeys */ bool merge(const pgp_key_t &src); /** @brief Merge subkey with the source, i.e. add all new signatures */ diff --git a/src/librekey/rnp_key_store.cpp b/src/librekey/rnp_key_store.cpp index 14007b4fdc..89859de96c 100644 --- a/src/librekey/rnp_key_store.cpp +++ b/src/librekey/rnp_key_store.cpp @@ -368,6 +368,16 @@ KeyStore::add_key(pgp_key_t &srckey) } else if (!added_key->refresh_data(secctx)) { RNP_LOG_KEY("Failed to refresh key %s data", &srckey); } + /* Revalidate non-self revocations for all keys in keyring, as added_key key could be a + * revoker. Should not be time-consuming as `validate_desig_revokes()` has early exit. */ + for (auto &key : keys) { + if (&key == added_key) { + continue; + } + if (key.validate_desig_revokes(*this)) { + key.revalidate(*this); + } + } return added_key; } @@ -388,7 +398,7 @@ KeyStore::import_key(pgp_key_t &srckey, bool pubkey, pgp_key_import_status_t *st } bool changed = exkey->rawpkt_count() > expackets; if (changed || !exkey->validated()) { - /* this will revalidated primary key with all subkeys */ + /* this will revalidate primary key with all of its subkeys */ exkey->revalidate(*this); } if (status) { From 2c31c666d37cb5b0406ac3a5ca240b25e0a5759b Mon Sep 17 00:00:00 2001 From: Nickolay Olshevsky Date: Tue, 22 Aug 2023 13:56:31 +0300 Subject: [PATCH 03/12] Add tests for validation of designated revocations. --- .../ecc-p256-desig-rev-1-pub.asc | 17 +++ .../ecc-p256-desig-rev-2-pub.asc | 20 +++ .../ecc-p256-desig-wrong-revoker.pgp | Bin 0 -> 786 bytes .../ecc-p256-desigrevoked-2-revs.pgp | Bin 0 -> 1086 bytes .../ecc-p256-desigrevoked-25519-pub.asc | 17 +++ .../ecc-p256-desigrevoked-p384-pub.asc | 17 +++ .../ecc-p256-desigrevoked-sigorder.pgp | Bin 0 -> 932 bytes src/tests/ffi-key.cpp | 115 ++++++++++++++++++ src/tests/support.cpp | 10 ++ src/tests/support.h | 1 + 10 files changed, 197 insertions(+) create mode 100644 src/tests/data/test_stream_key_load/ecc-p256-desig-rev-1-pub.asc create mode 100644 src/tests/data/test_stream_key_load/ecc-p256-desig-rev-2-pub.asc create mode 100644 src/tests/data/test_stream_key_load/ecc-p256-desig-wrong-revoker.pgp create mode 100644 src/tests/data/test_stream_key_load/ecc-p256-desigrevoked-2-revs.pgp create mode 100644 src/tests/data/test_stream_key_load/ecc-p256-desigrevoked-25519-pub.asc create mode 100644 src/tests/data/test_stream_key_load/ecc-p256-desigrevoked-p384-pub.asc create mode 100644 src/tests/data/test_stream_key_load/ecc-p256-desigrevoked-sigorder.pgp diff --git a/src/tests/data/test_stream_key_load/ecc-p256-desig-rev-1-pub.asc b/src/tests/data/test_stream_key_load/ecc-p256-desig-rev-1-pub.asc new file mode 100644 index 0000000000..ed87ed5e48 --- /dev/null +++ b/src/tests/data/test_stream_key_load/ecc-p256-desig-rev-1-pub.asc @@ -0,0 +1,17 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mFIEWsOCNRMIKoZIzj0DAQcCAwQS5G6mn5dhamZ6678SXE1azavqf8BItWO9Qv8V +dS1vEEoD14urr5OQKTLuHhDRjvSQdaxRtkf0sI51T7230sT3iJAEHxMIADgWIQS1 +T967tnNCOl0KpUQjZ08hskQVJwUCZOSQ7BcMgBYh/GgnSq47XeOaQnfMeGJ4mBsH +KAIHAAAKCRAjZ08hskQVJyqHAP40HOS4MVaSbXqllcKA7pHQTJVFUKmPDg8FKZuL +5Zy6cQEA827wLw4ckYBA9J/ryxGuV73F4xFx6Skd54TJHc5AzhO0CGVjYy1wMjU2 +iJQEExMIADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEEtU/eu7Zz +QjpdCqVEI2dPIbJEFScFAlxVr+cACgkQI2dPIbJEFSfXJwD7BWD2wR8cMFoa0uqV +RIFZ0YpRGsc3sIl7ZRPPX/uhAIEBAMOLhFDIsez5N86sE1JssSzAK7WgSHxWYe39 +I3v6ao0tuFYEWsOCNRIIKoZIzj0DAQcCAwQsM4CssgbzkAFC7UjCBAymIi5TCP43 +uJuAZ6dNFyTl2QGlfjx4reVuOi9efhiQGd4DvPXiJ7Pf5J2DpQUAwVXZAwEIB4h4 +BBgTCAAgAhsMFiEEtU/eu7ZzQjpdCqVEI2dPIbJEFScFAlxVr+8ACgkQI2dPIbJE +FSdkqgEAnriC/fDbucUERG8ixGq5+9HrFyKapLsaJ/5Z6EFT4/kBALluqghKHShJ +CJ1p0jzh/rRG45qjOmO9HcHVs9mJHq7v +=9h9h +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/tests/data/test_stream_key_load/ecc-p256-desig-rev-2-pub.asc b/src/tests/data/test_stream_key_load/ecc-p256-desig-rev-2-pub.asc new file mode 100644 index 0000000000..588821eaf8 --- /dev/null +++ b/src/tests/data/test_stream_key_load/ecc-p256-desig-rev-2-pub.asc @@ -0,0 +1,20 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mFIEWsOCNRMIKoZIzj0DAQcCAwQS5G6mn5dhamZ6678SXE1azavqf8BItWO9Qv8V +dS1vEEoD14urr5OQKTLuHhDRjvSQdaxRtkf0sI51T7230sT3iJAEHxMIADgWIQS1 +T967tnNCOl0KpUQjZ08hskQVJwUCZOSQ7BcMgBYh/GgnSq47XeOaQnfMeGJ4mBsH +KAIHAAAKCRAjZ08hskQVJyqHAP40HOS4MVaSbXqllcKA7pHQTJVFUKmPDg8FKZuL +5Zy6cQEA827wLw4ckYBA9J/ryxGuV73F4xFx6Skd54TJHc5AzhOIkAQfEwgAOBYh +BLVP3ru2c0I6XQqlRCNnTyGyRBUnBQJk5JF8FwyAE6sly6BC3ZJMOsw+0yQqOqXq +hfRKAgcAAAoJECNnTyGyRBUnV6ABAJvJzohleDd/OlvDpj0ezmxCZdBe23OlU2iI +cRxKdKDMAQDwnyAnm22f5PlvwCYvK0kdegt2Q7/JVjx8Px9L5SZ8m7QIZWNjLXAy +NTaIlAQTEwgAPAIbAwULCQgHAgMiAgEGFQoJCAsCBBYCAwECHgMCF4AWIQS1T967 +tnNCOl0KpUQjZ08hskQVJwUCXFWv5wAKCRAjZ08hskQVJ9cnAPsFYPbBHxwwWhrS +6pVEgVnRilEaxzewiXtlE89f+6EAgQEAw4uEUMix7Pk3zqwTUmyxLMArtaBIfFZh +7f0je/pqjS24VgRaw4I1EggqhkjOPQMBBwIDBCwzgKyyBvOQAULtSMIEDKYiLlMI +/je4m4Bnp00XJOXZAaV+PHit5W46L15+GJAZ3gO89eIns9/knYOlBQDBVdkDAQgH +iHgEGBMIACACGwwWIQS1T967tnNCOl0KpUQjZ08hskQVJwUCXFWv7wAKCRAjZ08h +skQVJ2SqAQCeuIL98Nu5xQREbyLEarn70esXIpqkuxon/lnoQVPj+QEAuW6qCEod +KEkInWnSPOH+tEbjmqM6Y70dwdWz2Ykeru8= +=9xX8 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/tests/data/test_stream_key_load/ecc-p256-desig-wrong-revoker.pgp b/src/tests/data/test_stream_key_load/ecc-p256-desig-wrong-revoker.pgp new file mode 100644 index 0000000000000000000000000000000000000000..4bf73a73860e8664bf7c1aeca3d66c8c4146a9d7 GIT binary patch literal 786 zcmX>W#1eJ5$yAs_tIgw_Ei)rK6Elm@le}f~rzd8mRlVLX6yqCpcJ-_J10GwG_d5L- zE!E8z@M6B+y?XuR37SUlFy*St21_& zRkW;@zH0sA1#;1(cS^EMsmaN@1;!R83Pq`9`N@eTnfZANY57G8U~wZ;Gmy(*n)W0x zHr(9bp0daMak_EisYkw*LUOn3t)A!o6VkrSu~I5+(l7Ue`g@&=RFCbq{V1>T>%2h# zW5b8Z7hj7j&04ZsO8sBt3&-Hc qKN%Tz=B?uJlGX6!n45XY=Hb6BZjWazwo2YBd+_S!o1Jp&-U9&9897P- literal 0 HcmV?d00001 diff --git a/src/tests/data/test_stream_key_load/ecc-p256-desigrevoked-2-revs.pgp b/src/tests/data/test_stream_key_load/ecc-p256-desigrevoked-2-revs.pgp new file mode 100644 index 0000000000000000000000000000000000000000..65b97619ea58bb5546d833e7ea822a4c86b49932 GIT binary patch literal 1086 zcmX>W#1eJ5$yAs_tIgw_Ei)rK6Elm@le}f~rzd8mRlVLX6yqCpcJ-_J10GwG_d5L- zE!E8z@M6B+y?XuR37SUlFy*St21_& zRkW;@zH0sA1#;1(cS^EMsmaN@1;!R83Pq`9`N@eTnfZANY57G8U~wZ;Gmy(*n)W0x zHr(9bp0daMak_EisYkw*LUOn3t)A!o6VkrSu~I5+(l7Ue`g@&=RFCbq{V1>T>%2h# zW5b>t&gD3zUWmJ0yfiqYqfo}HWWgCm zh7a=<)Mw|;fATZ`fSSIxr)(8>ne+aWVKz1P^4?F?YGxmr!J;6>#o!1GOKkB64oe?p zNLU(~ni^W-3rtw7TkdE0D}G?b{%U6JC0~R$->y1UQY3XSIQsjHg&_}=x>nZs%)ia> zJ-yNCs!7vHuZ~Cazdz9YDYoaSaPM|Cw_x5=p}*9YGoR&v#nYiFEW*GPXu~AU%*xHl z0Zi0NOpI)zT$~)-Oe|td%#2KO%uM3I1c)Pn#)Phaj+_dwt26v&P55?DUdA9w>e8#J zE{%~Fy8@+-n{Vi>P8B{M|9c@rBO}A%?v{WP8{hmiKet9WC}*S20qv~|JZi!c-~Lsu z{*~3MdoB!|*MyMsnvQY9noVqgJoBa_im$0MW^>Pyhe` literal 0 HcmV?d00001 diff --git a/src/tests/data/test_stream_key_load/ecc-p256-desigrevoked-25519-pub.asc b/src/tests/data/test_stream_key_load/ecc-p256-desigrevoked-25519-pub.asc new file mode 100644 index 0000000000..0a31895539 --- /dev/null +++ b/src/tests/data/test_stream_key_load/ecc-p256-desigrevoked-25519-pub.asc @@ -0,0 +1,17 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: A designated revocation certificate should follow + +mFIEWsOCNRMIKoZIzj0DAQcCAwQS5G6mn5dhamZ6678SXE1azavqf8BItWO9Qv8V +dS1vEEoD14urr5OQKTLuHhDRjvSQdaxRtkf0sI51T7230sT3iJgEIBYKAEEWIQQh +/GgnSq47XeOaQnfMeGJ4mBsHKAUCZOSSTCMdAmVjYy0yNTUxOSByZXZvY2F0aW9u +IGZvciBlY2MtcDI1NgAKCRDMeGJ4mBsHKDm/AP0XwKi/ewMrpPQTs9t6ynRyGsFT +W/eYoVTgIoqpfEyf2wD3Z4FC1TSCyUqI4p/34Cn5FrzlE423JkZTDcpV+ianA4iQ +BB8TCAA4FiEEtU/eu7ZzQjpdCqVEI2dPIbJEFScFAmTkkOwXDIAWIfxoJ0quO13j +mkJ3zHhieJgbBygCBwAACgkQI2dPIbJEFScqhwD+NBzkuDFWkm16pZXCgO6R0EyV +RVCpjw4PBSmbi+WcunEBAPNu8C8OHJGAQPSf68sRrle9xeMRcekpHeeEyR3OQM4T +tAhlY2MtcDI1NoiUBBMTCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeAwIX +gBYhBLVP3ru2c0I6XQqlRCNnTyGyRBUnBQJcVa/nAAoJECNnTyGyRBUn1ycA+wVg +9sEfHDBaGtLqlUSBWdGKURrHN7CJe2UTz1/7oQCBAQDDi4RQyLHs+TfOrBNSbLEs +wCu1oEh8VmHt/SN7+mqNLQ== +=dVq3 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/tests/data/test_stream_key_load/ecc-p256-desigrevoked-p384-pub.asc b/src/tests/data/test_stream_key_load/ecc-p256-desigrevoked-p384-pub.asc new file mode 100644 index 0000000000..658b325ebd --- /dev/null +++ b/src/tests/data/test_stream_key_load/ecc-p256-desigrevoked-p384-pub.asc @@ -0,0 +1,17 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: A designated revocation certificate should follow + +mFIEWsOCNRMIKoZIzj0DAQcCAwQS5G6mn5dhamZ6678SXE1azavqf8BItWO9Qv8V +dS1vEEoD14urr5OQKTLuHhDRjvSQdaxRtkf0sI51T7230sT3iLgEIBMJAEAWIQSr +JcugQt2STDrMPtMkKjql6oX0SgUCZOSS7iIdAmVjYy1wMzg0IHJldm9jYXRpb24g +Zm9yIGVjYy1wMjU2AAoJECQqOqXqhfRKvGABgNm/h2S8N+NnM4HK4k15Eh7bfzrn +bv4SK9MIqRpmkvpHwS+9Q3Ilxr898R8o+s8wUAGA3KLfdqgqNJTFbMXlXUIRUXJV +KtDDGJnlToiSPAMZTBs5EUYgx+8W4FzYdigk1nS6iJAEHxMIADgWIQS1T967tnNC +Ol0KpUQjZ08hskQVJwUCZOSRfBcMgBOrJcugQt2STDrMPtMkKjql6oX0SgIHAAAK +CRAjZ08hskQVJ1egAQCbyc6IZXg3fzpbw6Y9Hs5sQmXQXttzpVNoiHEcSnSgzAEA +8J8gJ5ttn+T5b8AmLytJHXoLdkO/yVY8fD8fS+UmfJu0CGVjYy1wMjU2iJQEExMI +ADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEEtU/eu7ZzQjpdCqVE +I2dPIbJEFScFAlxVr+cACgkQI2dPIbJEFSfXJwD7BWD2wR8cMFoa0uqVRIFZ0YpR +Gsc3sIl7ZRPPX/uhAIEBAMOLhFDIsez5N86sE1JssSzAK7WgSHxWYe39I3v6ao0t +=WEVB +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/tests/data/test_stream_key_load/ecc-p256-desigrevoked-sigorder.pgp b/src/tests/data/test_stream_key_load/ecc-p256-desigrevoked-sigorder.pgp new file mode 100644 index 0000000000000000000000000000000000000000..0ab4c2c3b5faa5ef104f479f9ad9f391490b7d3b GIT binary patch literal 932 zcmX>W#1eJ5$yAs_tIgw_Ei)rK6Elm@le}f~rzd8mRlVLX6yqCpcJ-_J10GwG_d5L- zE!E8z@M6B+y?XuR37SUlFy*St21_& zRkW;@zH0sA1#;1(cS^EMsmaN@1;!R83Pq`9`N@eTnfZANY57G8U~wZ;Gmy(*n)W0x zHr(9bp0daMak_EisYkw*LUOn3t)A!o6VkrSu~I5+(l7Ue`g@&=RFCbq{V1>T>%2h# zW5b>t&gD3zUWmJ0yfiqYqfo}HWWgCm zh7a=<)Mw|;fATZ`fSSIxr)(8>ne+aWVKz1P^4?F?YG$A1fQ81PDJ;Ukc(!4ZW@hE) z8TQ7%pnZYCBnCT2z^Ic6qtU^L@M1~H-QpCiZhb#;c{tO?%^%F7r;NnLt1 z)ul1=VppKlaq|tG)v3bg<9{z?Xk=tK+}#pzV&j{i=I7Q32jy(kIiS6Dfk#bP;@iK< z)xWZOb_wNdC%8J>YMLBncKXSmEmCMO=d<8_Cpmc5^^U-@`AEm5f zWSF<3>F Date: Thu, 7 Sep 2023 11:55:49 +0300 Subject: [PATCH 04/12] Add FFI API to retrieve designated revokers. --- include/rnp/rnp.h | 21 +++++++++++++++++++++ src/lib/pgp-key.cpp | 15 +++++++++++++++ src/lib/pgp-key.h | 2 ++ src/lib/rnp.cpp | 29 +++++++++++++++++++++++++++++ src/tests/ffi-key.cpp | 18 ++++++++++++++++++ 5 files changed, 85 insertions(+) diff --git a/include/rnp/rnp.h b/include/rnp/rnp.h index 94d1e1d1d5..6bbdb46595 100644 --- a/include/rnp/rnp.h +++ b/include/rnp/rnp.h @@ -1519,6 +1519,27 @@ RNP_API rnp_result_t rnp_key_get_signature_at(rnp_key_handle_t key, size_t idx, rnp_signature_handle_t *sig); +/** + * @brief Get number of the designated revokers for the key. Designated revoker is a key, which + * is allowed to revoke this key. + * + * @param key key handle, cannot be NULL. + * @param count number of designated revokers will be stored here. + * @return RNP_SUCCESS or error code if failed. + */ +RNP_API rnp_result_t rnp_key_get_revoker_count(rnp_key_handle_t key, size_t *count); + +/** + * @brief Get the fingerprint of designated revoker's key, based on it's index. + * + * @param key key handle, cannot be NULL. + * @param idx zero-based index. + * @param revoker on success hex-encoded revoker's key fingerprint will be stored here. Must be + * later freed via rnp_buffer_destroy(). + * @return RNP_SUCCESS or error code if failed. + */ +RNP_API rnp_result_t rnp_key_get_revoker_at(rnp_key_handle_t key, size_t idx, char **revoker); + /** * @brief Get key's revocation signature handle, if any. * diff --git a/src/lib/pgp-key.cpp b/src/lib/pgp-key.cpp index b0f1bab983..6d96bd9b3f 100644 --- a/src/lib/pgp-key.cpp +++ b/src/lib/pgp-key.cpp @@ -1170,6 +1170,21 @@ pgp_key_t::has_revoker(const pgp_fingerprint_t &revoker) const return false; } +size_t +pgp_key_t::revoker_count() const +{ + return revokers_.size(); +} + +const pgp_fingerprint_t & +pgp_key_t::get_revoker(size_t idx) const +{ + if (idx >= revokers_.size()) { + throw std::out_of_range("idx"); + } + return revokers_[idx]; +} + const pgp_key_pkt_t & pgp_key_t::pkt() const { diff --git a/src/lib/pgp-key.h b/src/lib/pgp-key.h index 942df2aeb2..50d2262e6d 100644 --- a/src/lib/pgp-key.h +++ b/src/lib/pgp-key.h @@ -206,6 +206,8 @@ struct pgp_key_t { void clear_revokes(); void add_revoker(const pgp_fingerprint_t &revoker); bool has_revoker(const pgp_fingerprint_t &revoker) const; + size_t revoker_count() const; + const pgp_fingerprint_t &get_revoker(size_t idx) const; const pgp_key_pkt_t &pkt() const; pgp_key_pkt_t & pkt(); diff --git a/src/lib/rnp.cpp b/src/lib/rnp.cpp index 5a90cb2879..7e1f099653 100644 --- a/src/lib/rnp.cpp +++ b/src/lib/rnp.cpp @@ -6174,6 +6174,35 @@ try { } FFI_GUARD +rnp_result_t +rnp_key_get_revoker_count(rnp_key_handle_t handle, size_t *count) +try { + if (!handle || !count) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *count = key->revoker_count(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_revoker_at(rnp_key_handle_t handle, size_t idx, char **revoker) +try { + if (!handle || !revoker) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key || (idx >= key->revoker_count())) { + return RNP_ERROR_BAD_PARAMETERS; + } + return ret_fingerprint(key->get_revoker(idx), revoker); +} +FFI_GUARD + rnp_result_t rnp_key_get_revocation_signature(rnp_key_handle_t handle, rnp_signature_handle_t *sig) try { diff --git a/src/tests/ffi-key.cpp b/src/tests/ffi-key.cpp index 38d43f7bbc..7d4357785e 100644 --- a/src/tests/ffi-key.cpp +++ b/src/tests/ffi-key.cpp @@ -4798,6 +4798,24 @@ TEST_F(rnp_tests, test_ffi_designated_revokers) /* Key with 2 designated revokers and 2 revocations */ assert_true(load_keys_gpg(ffi, path_for("ecc-p256-desigrevoked-2-revs.pgp"))); assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p256", &key)); + /* Check designated revokers */ + size_t count = 0; + assert_rnp_failure(rnp_key_get_revoker_count(NULL, &count)); + assert_rnp_failure(rnp_key_get_revoker_count(key, NULL)); + assert_rnp_success(rnp_key_get_revoker_count(key, &count)); + assert_int_equal(count, 2); + char *revoker = NULL; + assert_rnp_failure(rnp_key_get_revoker_at(NULL, 0, &revoker)); + assert_rnp_failure(rnp_key_get_revoker_at(key, 0, NULL)); + assert_rnp_failure(rnp_key_get_revoker_at(key, 2, &revoker)); + assert_rnp_failure(rnp_key_get_revoker_at(key, (size_t) -1, &revoker)); + assert_rnp_success(rnp_key_get_revoker_at(key, 0, &revoker)); + assert_string_equal(revoker, "21FC68274AAE3B5DE39A4277CC786278981B0728"); + rnp_buffer_destroy(revoker); + assert_rnp_success(rnp_key_get_revoker_at(key, 1, &revoker)); + assert_string_equal(revoker, "AB25CBA042DD924C3ACC3ED3242A3AA5EA85F44A"); + rnp_buffer_destroy(revoker); + /* Check key validity */ assert_true(check_key_valid(key, true)); assert_true(check_key_revoked(key, false)); rnp_key_handle_destroy(key); From a03be415b15eca10b88c1326a461a8f7c5d8d776 Mon Sep 17 00:00:00 2001 From: Nickolay Olshevsky Date: Fri, 15 Sep 2023 14:05:59 +0300 Subject: [PATCH 05/12] FFI: add function rnp_signature_get_revoker(). --- include/rnp/rnp.h | 12 ++++++++++++ src/lib/rnp.cpp | 14 ++++++++++++++ src/tests/ffi-key.cpp | 17 ++++++++++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/include/rnp/rnp.h b/include/rnp/rnp.h index 6bbdb46595..c98f68934f 100644 --- a/include/rnp/rnp.h +++ b/include/rnp/rnp.h @@ -1665,6 +1665,18 @@ RNP_API rnp_result_t rnp_signature_get_key_fprint(rnp_signature_handle_t sig, ch RNP_API rnp_result_t rnp_signature_get_signer(rnp_signature_handle_t sig, rnp_key_handle_t * key); +/** + * @brief Get fingerprint of the designated revocation key, if it is available. See + * section 5.2.3.15 of the RFC 4880 for the details. + * + * @param sig signature handle, cannot be NULL. + * @param revoker on success hex-encoded revocation key fingerprint will be stored here, if + * available. Otherwise empty string will be stored. Must be freed via + * rnp_buffer_destroy(). + * @return RNP_SUCCESS or error code if failed. + */ +RNP_API rnp_result_t rnp_signature_get_revoker(rnp_signature_handle_t sig, char **revoker); + /** * @brief Get signature validity, revalidating it if didn't before. * diff --git a/src/lib/rnp.cpp b/src/lib/rnp.cpp index 7e1f099653..19e3d7e784 100644 --- a/src/lib/rnp.cpp +++ b/src/lib/rnp.cpp @@ -6379,6 +6379,20 @@ try { } FFI_GUARD +rnp_result_t +rnp_signature_get_revoker(rnp_signature_handle_t handle, char **revoker) +try { + if (!handle || !revoker) { + return RNP_ERROR_NULL_POINTER; + } + auto &sig = handle->sig->sig; + if (!sig.has_revoker()) { + return ret_str_value("", revoker); + } + return ret_fingerprint(sig.revoker(), revoker); +} +FFI_GUARD + rnp_result_t rnp_signature_is_valid(rnp_signature_handle_t sig, uint32_t flags) try { diff --git a/src/tests/ffi-key.cpp b/src/tests/ffi-key.cpp index 7d4357785e..59da2f9726 100644 --- a/src/tests/ffi-key.cpp +++ b/src/tests/ffi-key.cpp @@ -4744,6 +4744,22 @@ TEST_F(rnp_tests, test_ffi_designated_revokers) assert_string_equal(sigtype, "key revocation"); rnp_buffer_destroy(sigtype); assert_int_equal(rnp_signature_is_valid(sig, 0), RNP_ERROR_KEY_NOT_FOUND); + /* Check for empty designated revoker */ + char *revoker = NULL; + assert_rnp_failure(rnp_signature_get_revoker(NULL, &revoker)); + assert_rnp_failure(rnp_signature_get_revoker(sig, NULL)); + assert_rnp_success(rnp_signature_get_revoker(sig, &revoker)); + assert_int_equal(strcmp(revoker, ""), 0); + rnp_buffer_destroy(revoker); + rnp_signature_handle_destroy(sig); + /* Now not empty */ + assert_rnp_success(rnp_key_get_signature_at(key, 1, &sig)); + assert_rnp_success(rnp_signature_get_type(sig, &sigtype)); + assert_string_equal(sigtype, "direct"); + rnp_buffer_destroy(sigtype); + assert_rnp_success(rnp_signature_get_revoker(sig, &revoker)); + assert_int_equal(strcmp(revoker, "21FC68274AAE3B5DE39A4277CC786278981B0728"), 0); + rnp_buffer_destroy(revoker); rnp_signature_handle_destroy(sig); rnp_key_handle_destroy(key); /* Load revoker's key and recheck */ @@ -4804,7 +4820,6 @@ TEST_F(rnp_tests, test_ffi_designated_revokers) assert_rnp_failure(rnp_key_get_revoker_count(key, NULL)); assert_rnp_success(rnp_key_get_revoker_count(key, &count)); assert_int_equal(count, 2); - char *revoker = NULL; assert_rnp_failure(rnp_key_get_revoker_at(NULL, 0, &revoker)); assert_rnp_failure(rnp_key_get_revoker_at(key, 0, NULL)); assert_rnp_failure(rnp_key_get_revoker_at(key, 2, &revoker)); From 5cdf3989c40239c5c413b6e4590701d9eea8d034 Mon Sep 17 00:00:00 2001 From: Nickolay Olshevsky Date: Fri, 22 Dec 2023 16:21:39 +0200 Subject: [PATCH 06/12] Add method pgp_signature_t::set_revoker(). --- src/librepgp/stream-sig.cpp | 12 ++++++++++++ src/librepgp/stream-sig.h | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/src/librepgp/stream-sig.cpp b/src/librepgp/stream-sig.cpp index 3888ccc014..06757e20ee 100644 --- a/src/librepgp/stream-sig.cpp +++ b/src/librepgp/stream-sig.cpp @@ -1208,6 +1208,18 @@ pgp_signature_t::revoker() const noexcept return res; } +void +pgp_signature_t::set_revoker(const pgp_key_t &revoker, bool sensitive) +{ + auto &fp = revoker.fp(); + auto &subpkt = add_subpkt(PGP_SIG_SUBPKT_REVOCATION_KEY, fp.length + 2, true); + subpkt.hashed = true; + subpkt.data[0] = sensitive ? 0xC0 : 0x80; + subpkt.data[1] = revoker.alg(); + memcpy(subpkt.data + 2, fp.fingerprint, fp.length); + subpkt.parse(); +} + pgp_sig_subpkt_t & pgp_signature_t::add_subpkt(pgp_sig_subpacket_type_t type, size_t datalen, bool reuse) { diff --git a/src/librepgp/stream-sig.h b/src/librepgp/stream-sig.h index fd96b5382a..98444070e7 100644 --- a/src/librepgp/stream-sig.h +++ b/src/librepgp/stream-sig.h @@ -344,6 +344,13 @@ typedef struct pgp_signature_t { */ pgp_fingerprint_t revoker() const noexcept; + /** + * @brief Set the revocation key. + * + * @param revoker revoker's key packet. + */ + void set_revoker(const pgp_key_t &revoker, bool sensitive = false); + /** * @brief Add subpacket of the specified type to v4 signature * @param type type of the subpacket From a31ffbe4a09b81c73804be75c89e7b7764e62014 Mon Sep 17 00:00:00 2001 From: Nickolay Olshevsky Date: Fri, 22 Dec 2023 16:22:11 +0200 Subject: [PATCH 07/12] Update pgp_key_t::add_sig() to allow insertion of signature in the beginning of a list. --- src/lib/pgp-key.cpp | 26 +++++++++++++++++++------- src/lib/pgp-key.h | 6 ++++-- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/lib/pgp-key.cpp b/src/lib/pgp-key.cpp index 6d96bd9b3f..578452a4dc 100644 --- a/src/lib/pgp-key.cpp +++ b/src/lib/pgp-key.cpp @@ -720,9 +720,10 @@ pgp_userid_t::has_sig(const pgp_sig_id_t &id) const } void -pgp_userid_t::add_sig(const pgp_sig_id_t &sig) +pgp_userid_t::add_sig(const pgp_sig_id_t &sig, bool begin) { - sigs_.push_back(sig); + size_t idx = begin ? 0 : sigs_.size(); + sigs_.insert(sigs_.begin() + idx, sig); } void @@ -941,18 +942,29 @@ pgp_key_t::replace_sig(const pgp_sig_id_t &id, const pgp_signature_t &newsig) } pgp_subsig_t & -pgp_key_t::add_sig(const pgp_signature_t &sig, size_t uid) +pgp_key_t::add_sig(const pgp_signature_t &sig, size_t uid, bool begin) { const pgp_sig_id_t sigid = sig.get_id(); sigs_map_.erase(sigid); pgp_subsig_t &res = sigs_map_.emplace(std::make_pair(sigid, sig)).first->second; res.uid = uid; - sigs_.push_back(sigid); if (uid == PGP_UID_NONE) { - keysigs_.push_back(sigid); - } else { - uids_[uid].add_sig(sigid); + size_t idx = begin ? 0 : keysigs_.size(); + sigs_.insert(sigs_.begin() + idx, sigid); + keysigs_.insert(keysigs_.begin() + idx, sigid); + return res; + } + + /* Calculate correct position in sigs_ */ + size_t idx = keysigs_.size(); + for (size_t u = 0; u < uid; u++) { + idx += uids_[u].sig_count(); + } + if (!begin) { + idx += uids_[uid].sig_count(); } + sigs_.insert(sigs_.begin() + idx, sigid); + uids_[uid].add_sig(sigid, begin); return res; } diff --git a/src/lib/pgp-key.h b/src/lib/pgp-key.h index 50d2262e6d..86acfee106 100644 --- a/src/lib/pgp-key.h +++ b/src/lib/pgp-key.h @@ -122,7 +122,7 @@ typedef struct pgp_userid_t { size_t sig_count() const; const pgp_sig_id_t &get_sig(size_t idx) const; bool has_sig(const pgp_sig_id_t &id) const; - void add_sig(const pgp_sig_id_t &sig); + void add_sig(const pgp_sig_id_t &sig, bool begin = false); void replace_sig(const pgp_sig_id_t &id, const pgp_sig_id_t &newsig); bool del_sig(const pgp_sig_id_t &id); void clear_sigs(); @@ -188,7 +188,9 @@ struct pgp_key_t { pgp_subsig_t & replace_sig(const pgp_sig_id_t &id, const pgp_signature_t &newsig); pgp_subsig_t & get_sig(const pgp_sig_id_t &id); const pgp_subsig_t &get_sig(const pgp_sig_id_t &id) const; - pgp_subsig_t & add_sig(const pgp_signature_t &sig, size_t uid = PGP_UID_NONE); + pgp_subsig_t & add_sig(const pgp_signature_t &sig, + size_t uid = PGP_UID_NONE, + bool begin = false); bool del_sig(const pgp_sig_id_t &sigid); size_t del_sigs(const std::vector &sigs); size_t keysig_count() const; From d5b15cdee531b77566efc0a01a84b62c415e17c0 Mon Sep 17 00:00:00 2001 From: Nickolay Olshevsky Date: Fri, 15 Sep 2023 13:38:24 +0300 Subject: [PATCH 08/12] FFI: Add functions rnp_key_signature_create/rnp_signature_set_revoker/rnp_key_signature_sign. --- include/rnp/rnp.h | 47 +++++++++++++++ src/lib/ffi-priv-types.h | 14 ++++- src/lib/rnp.cpp | 126 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+), 2 deletions(-) diff --git a/include/rnp/rnp.h b/include/rnp/rnp.h index c98f68934f..2f5c568900 100644 --- a/include/rnp/rnp.h +++ b/include/rnp/rnp.h @@ -138,6 +138,11 @@ typedef uint32_t rnp_result_t; #define RNP_VERIFY_REQUIRE_ALL_SIGS (1U << 1) #define RNP_VERIFY_ALLOW_HIDDEN_RECIPIENT (1U << 2) +/** + * Revocation key flags. + */ +#define RNP_REVOKER_SENSITIVE (1U << 0) + /** * Return a constant string describing the result code */ @@ -1519,6 +1524,48 @@ RNP_API rnp_result_t rnp_key_get_signature_at(rnp_key_handle_t key, size_t idx, rnp_signature_handle_t *sig); +/** + * @brief Create new direct-key signature over the target, issued by signer. It may be + * customized via the rnp_signature_set_* calls, and finalized via the + * rnp_signature_sign() call. + * + * @param signer signing key, must be secret, and must exist in the keyring up to the + * rnp_signature_sign() call. Cannot be NULL. + * @param target target key for which signature should be made. May be NULL, then signature + * over the signer (self-signature) will be made. + * + * @param sig on success signature handle will be stored here. It is initialized with current + * creation time, default hash algorithm and version. Cannot be NULL. + * @return RNP_SUCCESS or error code if failued. + */ +RNP_API rnp_result_t rnp_key_direct_signature_create(rnp_key_handle_t signer, + rnp_key_handle_t target, + rnp_signature_handle_t *sig); + +/** + * @brief Add designated revoker subpacket to the signature. See RFC 4880, section 5.2.3.15. + * Only single revoker could be set - subsequent calls would overwrite the previous one. + * + * @param sig editable key signature handle, i.e. created with rnp_key_*_signature_create(). + * @param revoker revoker's key. + * @param flags additional flags. The following flag is currently supported: + * RNP_REVOKER_SENSITIVE: information about the revocation key should be + * considered as sensitive. See RFC for the details. + * @return RNP_SUCCESS or error code if failed. + */ +RNP_API rnp_result_t rnp_key_signature_set_revoker(rnp_signature_handle_t sig, + rnp_key_handle_t revoker, + uint32_t flags); + +/** + * @brief Finalize populating and sign signature, created with one of the + * rnp_key_*_signature_create functions, and add it to the corresponding key. + * + * @param sig signature handle. + * @return RNP_SUCCESS or error code if failed. + */ +RNP_API rnp_result_t rnp_key_signature_sign(rnp_signature_handle_t sig); + /** * @brief Get number of the designated revokers for the key. Designated revoker is a key, which * is allowed to revoke this key. diff --git a/src/lib/ffi-priv-types.h b/src/lib/ffi-priv-types.h index 26cf0214c3..24abf6166f 100644 --- a/src/lib/ffi-priv-types.h +++ b/src/lib/ffi-priv-types.h @@ -46,10 +46,20 @@ struct rnp_uid_handle_st { }; struct rnp_signature_handle_st { - rnp_ffi_t ffi; + rnp_ffi_t ffi; + /** + * @brief Key to which this signature belongs, if available. + */ const pgp_key_t *key; pgp_subsig_t * sig; - bool own_sig; + /** + * @brief sig pointer is owned by structure and should be deallocated. + */ + bool own_sig; + /** + * @brief This is a new signature, which is being populated. + */ + bool new_sig; }; struct rnp_recipient_handle_st { diff --git a/src/lib/rnp.cpp b/src/lib/rnp.cpp index 19e3d7e784..4f6464060a 100644 --- a/src/lib/rnp.cpp +++ b/src/lib/rnp.cpp @@ -6174,6 +6174,132 @@ try { } FFI_GUARD +rnp_result_t +rnp_key_direct_signature_create(rnp_key_handle_t signer, + rnp_key_handle_t target, + rnp_signature_handle_t *sig) +try { + if (!signer || !sig) { + return RNP_ERROR_NULL_POINTER; + } + if (!target) { + target = signer; + } + pgp_key_t *sigkey = get_key_require_secret(signer); + pgp_key_t *tgkey = get_key_prefer_public(target); + if (!sigkey || !tgkey) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!tgkey->is_primary()) { + return RNP_ERROR_BAD_PARAMETERS; + } + *sig = (rnp_signature_handle_t) calloc(1, sizeof(**sig)); + if (!*sig) { + return RNP_ERROR_OUT_OF_MEMORY; + } + try { + pgp_signature_t sigpkt; + sigkey->sign_init(signer->ffi->rng(), + sigpkt, + DEFAULT_PGP_HASH_ALG, + signer->ffi->context.time(), + sigkey->version()); + sigpkt.set_type(PGP_SIG_DIRECT); + (*sig)->sig = new pgp_subsig_t(sigpkt); + (*sig)->ffi = signer->ffi; + (*sig)->key = tgkey; + (*sig)->own_sig = true; + (*sig)->new_sig = true; + } catch (const std::exception &e) { + FFI_LOG(signer->ffi, "%s", e.what()); + free(*sig); + *sig = NULL; + return RNP_ERROR_OUT_OF_MEMORY; + } + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_signature_set_revoker(rnp_signature_handle_t sig, + rnp_key_handle_t revoker, + uint32_t flags) +try { + if (!sig || !revoker) { + return RNP_ERROR_NULL_POINTER; + } + if (!sig->new_sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + bool sensitive = extract_flag(flags, RNP_REVOKER_SENSITIVE); + if (flags) { + FFI_LOG(sig->ffi, "Unsupported flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + auto key = get_key_prefer_public(revoker); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + sig->sig->sig.set_revoker(*key, sensitive); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_signature_sign(rnp_signature_handle_t sig) +try { + if (!sig) { + return RNP_ERROR_NULL_POINTER; + } + if (!sig->new_sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + auto &sigpkt = sig->sig->sig; + /* Get signer and target */ + auto signer = sig->ffi->secring->get_signer(sigpkt); + if (!signer) { + return RNP_ERROR_BAD_STATE; + } + /* Unlock if needed */ + rnp::KeyLocker seclock(*signer); + if (signer->is_locked() && !signer->unlock(sig->ffi->pass_provider)) { + FFI_LOG(sig->ffi, "Failed to unlock secret key"); + return RNP_ERROR_BAD_PASSWORD; + } + /* Sign */ + if (sigpkt.type() == PGP_SIG_DIRECT) { + signer->sign_direct(sig->key->pkt(), sigpkt, sig->ffi->context); + } else { + FFI_LOG(sig->ffi, "Not yet supported signature type."); + return RNP_ERROR_BAD_STATE; + } + /* Add to the keyring(s) */ + pgp_subsig_t *newsig = NULL; + pgp_key_t * key = sig->ffi->secring->get_key(sig->key->fp()); + if (key) { + newsig = &key->add_sig(sigpkt, PGP_UID_NONE, true); + key->refresh_data(sig->ffi->context); + } + key = sig->ffi->pubring->get_key(sig->key->fp()); + if (key) { + newsig = &key->add_sig(sigpkt, PGP_UID_NONE, true); + key->refresh_data(sig->ffi->context); + } + /* Should not happen but let's check */ + if (!newsig) { + return RNP_ERROR_BAD_STATE; + } + /* Replace owned sig with pointer to the key's one */ + delete sig->sig; + sig->sig = newsig; + sig->own_sig = false; + sig->new_sig = false; + + return RNP_SUCCESS; +} +FFI_GUARD + rnp_result_t rnp_key_get_revoker_count(rnp_key_handle_t handle, size_t *count) try { From 2197052707a732e00d3813497b73dbac25524f49 Mon Sep 17 00:00:00 2001 From: Nickolay Olshevsky Date: Wed, 27 Dec 2023 16:36:53 +0200 Subject: [PATCH 09/12] Add tests for setting of designated revoker. --- .../ecc-25519-2subs-sec.asc | 36 +++++ src/tests/ffi-key-sig.cpp | 126 ++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 src/tests/data/test_stream_key_load/ecc-25519-2subs-sec.asc diff --git a/src/tests/data/test_stream_key_load/ecc-25519-2subs-sec.asc b/src/tests/data/test_stream_key_load/ecc-25519-2subs-sec.asc new file mode 100644 index 0000000000..ac48ee0cd5 --- /dev/null +++ b/src/tests/data/test_stream_key_load/ecc-25519-2subs-sec.asc @@ -0,0 +1,36 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +xYYEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6d+O201bdKhdowbielOougCpCfj+BwMC +kuClwXrc7H3i9J2+l5bS6+TGJVRP2/yrh9tCcsgmUf0Z1T7uwS7ABadlAPIokvZ3aLmU5ahSJY7S +pK/EV3vEG76FMCxxXOJTDIKfsHoS880JZWNjLTI1NTE5wpQEExYIADwCGwMFCwkIBwIDIgIBBhUK +CQgLAgQWAgMBAh4DAheAFiEEIfxoJ0quO13jmkJ3zHhieJgbBygFAlxVr80ACgkQzHhieJgbByiU +UAD+My3dFRRvnG3rclbocVytirRGsMBxgyxcBjveJmk+wRwBAOYpsfbUuTCgKVT1GtQlJhmcyVr+ +lB2A7F3v8+NEKlsKx8MGBGWKu+MBCADrrci5Yes25POd5AtX2fLQyJFiVfH6fRL9UrgmTGaHa7uZ +hGaUhAjigKi7ZD4XEAVr3a9OvagiLmF/DyhRGtBFkmxtMQi0feqgOW0XY6+mLfghUKDgrvzP0B2y +FhlA4VbtpWhJ8XUNSx5+Fg84H6DR597ELfo5vckQfXMQKR2XsWLV513aFVcnDx4QZVSLfTHmUNN4 +AKyY/MclO+vpWCpOaGvDcrXgxsjzqMuko7BpGd8I9+aAV8VOYruO49Wdv/PEok2+fAzmYwDcq0Uc +o5y4cEKxeKUBvEaVssZMGqWZwMPhy90M8/hMr+oA1IMZImV/MKosVeCAS4dqL4WS2lbNABEBAAH+ +CQMINg2MvEJ8tybFzJappxxEV60vuiRlZ2WoFGJVwNfHEBGAs78iD7ZZThOpetdlVAzLGCnwbog1 +LGwzedB7T4s7oVgrZtVtdTat4Humeok8YljJZ/wpz4dAnTERP1bU0E+uBkiGcxMUjQ/lpbT+0mL6 +Zz/oZSQ+qwlm3WxWmN+K9YfmgO/77t0VrOeVrA9xXyH8/EnxRVAV2JmVHR4GRim3uijIkNSF6aIX +wpsANlvlC7hnPy5znquTUS7kyyXwBE/ajyBRTEASpITVaTgcXYcOWkYMKpg6E9k1umCHAFDi1nEU +I27w3ju1IAmfxkMjkXk0TSlGdXZeWWv7qyN8BDE25qpL1gRjcTgvec/oFkuV6dCXSZ6HlCL8r2mn +I0T8BMR7pc581Alv8mVfkAdxGI3Jtb0Eg85z5BIxcn4UYpqOJQa5pcSr67pMEVQm+SobEogDIUR4 +Wp1CJqoIVaN6gOetx6nJZQZCmPw4Hw/HttakZX4cDKk3pOzYtZOXXYA21+laUoWum2nfX3IC1VKK +Ci7qJVGtusrLLdKopOkBg/xOwrL6zqN4gtkLQs/dYBBX72KhH2121MkoOovdE3kobsgwxvnuMjVr +7vCNxk8H11CnLc+Pz71z5L+nNnuDr6eJkpzsR5OGa/9HUPHWjnBRvw14f4CmOAuhcnmQeXxVtR8O +Vk1imVlcRe5atST4O4Ie3ZEfqDY/mE1fMr0cvv4hcaoX98Oe+krLWkpABrF0sQOKOeqiO95a8nqG +xcS/dGwoK2anfFiueWLgT3waSfgIgBup5KlfoV9/68IQlV8C8WLWq+zlmKJce3rzIaKtyqN/RrVh +eGvWWxQD2a880ZyZ5+rjnu4yEluvEfuLnqC92AR1Z9V5NOCq3wepMUYiyx7+fhpzcoZx/jljPbR3 +QdPZFCkau/J081sPwn4EGBYIACYWIQQh/GgnSq47XeOaQnfMeGJ4mBsHKAUCZYq75AUJA8JnAAIb +DAAKCRDMeGJ4mBsHKOWaAP4n9VVjJVfaP6u93+M6gu6xxxyrHvO/C1COuNM5O/oC7AEAyIC2CZOK +z/oPFb4XXb9K9IFMmW8AsoHJgbKVzE6c9gvHhgRlirwDFgkrBgEEAdpHDwEBB0C8c/Z5jhqLKKja +39fiaENXaS/QSFg/uVi3soP7xwsVPf4JAwg28w+7+7oyKcQgMOnU82KBCb2dX8KrVFbIeruyNepX +ZAcFFr2UBT3Z3FKx7wLP1B4qm3w6Abst3f9Hr8S2pMIETX44WJkaCdb4Zu7oom5IwsA1BBgWCAAm +FiEEIfxoJ0quO13jmkJ3zHhieJgbBygFAmWKvAMFCQPCZwACGwIAgQkQzHhieJgbByh2IAQZFggA +HRYhBG0gfcwKwoHb/ChXhVc0NsIxrmM4BQJlirwDAAoJEFc0NsIxrmM4Sl4BAKZNuM4q2CNR0xtY +OBI3XoYxAMDgcLjKNlXRz/jlHr9NAP4s/CslyoQu9hj2WChlLwEEutYtUWY+stbAUi9pW4yHDsUi +AQCRUSL2WmjCIteNT1jC2oDyD7rqIlLW6kKaSS6xbZo2QwEAjMwIsEU066czOQwkzv/ftFhDKOcx +gMXZMeceVrvq+wM= +=rHQj +-----END PGP PRIVATE KEY BLOCK----- diff --git a/src/tests/ffi-key-sig.cpp b/src/tests/ffi-key-sig.cpp index 3e2ee78643..54c749ecb0 100644 --- a/src/tests/ffi-key-sig.cpp +++ b/src/tests/ffi-key-sig.cpp @@ -1624,3 +1624,129 @@ TEST_F(rnp_tests, test_ffi_key_import_invalid_issuer) rnp_ffi_destroy(ffi); } + +TEST_F(rnp_tests, test_ffi_add_revoker_signature) +{ + rnp_ffi_t ffi = NULL; + assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); + assert_true(import_all_keys(ffi, "data/test_stream_key_load/ecc-25519-2subs-sec.asc")); + assert_true(import_pub_keys(ffi, "data/test_stream_key_load/ecc-p256-pub.asc")); + assert_true(import_pub_keys(ffi, "data/test_stream_key_load/ecc-p384-pub.asc")); + rnp_key_handle_t key = NULL; + /* Locate key and make sure it doesn't have designated revokers */ + assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-25519", &key)); + size_t count = 10; + assert_rnp_success(rnp_key_get_revoker_count(key, &count)); + assert_int_equal(count, 0); + /* Add designated revoker */ + rnp_key_handle_t revoker = NULL; + assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p256", &revoker)); + rnp_signature_handle_t newsig = NULL; + /* Create signature, including edge cases checks */ + assert_rnp_failure(rnp_key_direct_signature_create(NULL, NULL, &newsig)); + assert_rnp_failure(rnp_key_direct_signature_create(key, key, NULL)); + assert_rnp_failure(rnp_key_direct_signature_create(revoker, key, &newsig)); + assert_rnp_success(rnp_key_direct_signature_create(key, NULL, &newsig)); + /* Set revoker, including edge cases */ + assert_rnp_failure(rnp_key_signature_set_revoker(NULL, revoker, 0)); + assert_rnp_failure(rnp_key_signature_set_revoker(newsig, NULL, 0)); + assert_rnp_failure(rnp_key_signature_set_revoker(newsig, revoker, 0x33)); + assert_rnp_success(rnp_key_signature_set_revoker(newsig, revoker, 0)); + assert_rnp_success(rnp_key_signature_set_revoker(newsig, revoker, RNP_REVOKER_SENSITIVE)); + /* Attempt to validate non-finished signature */ + assert_rnp_failure(rnp_signature_is_valid(newsig, 0)); + /* Populate signature */ + assert_rnp_failure(rnp_key_signature_sign(NULL)); + assert_int_equal(rnp_key_signature_sign(newsig), RNP_ERROR_BAD_PASSWORD); + rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "wrong1"); + assert_int_equal(rnp_key_signature_sign(newsig), RNP_ERROR_BAD_PASSWORD); + rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"); + assert_rnp_success(rnp_key_signature_sign(newsig)); + /* Check signature and key properties */ + char *revfp = NULL; + assert_rnp_success(rnp_signature_get_revoker(newsig, &revfp)); + assert_string_equal(revfp, "B54FDEBBB673423A5D0AA54423674F21B2441527"); + rnp_buffer_destroy(revfp); + assert_rnp_success(rnp_key_get_revoker_count(key, &count)); + assert_int_equal(count, 1); + assert_rnp_success(rnp_key_get_revoker_at(key, 0, &revfp)); + assert_string_equal(revfp, "B54FDEBBB673423A5D0AA54423674F21B2441527"); + rnp_buffer_destroy(revfp); + assert_rnp_success(rnp_signature_is_valid(newsig, 0)); + /* Attempt to sign already populated signature */ + assert_rnp_failure(rnp_key_signature_set_revoker(newsig, revoker, 0)); + assert_rnp_failure(rnp_key_signature_sign(newsig)); + rnp_signature_handle_destroy(newsig); + /* Make sure that newly added signature is first of the key's signatures */ + assert_rnp_success(rnp_key_get_signature_at(key, 0, &newsig)); + assert_rnp_failure(rnp_key_signature_sign(newsig)); + char *type = NULL; + assert_rnp_success(rnp_signature_get_type(newsig, &type)); + assert_string_equal(type, "direct"); + rnp_buffer_destroy(type); + assert_rnp_success(rnp_signature_get_revoker(newsig, &revfp)); + assert_string_equal(revfp, "B54FDEBBB673423A5D0AA54423674F21B2441527"); + rnp_buffer_destroy(revfp); + rnp_signature_handle_destroy(newsig); + /* Export key and make sure signature is exported */ + auto keydata = export_key(key, true); + rnp_key_handle_destroy(key); + rnp_key_handle_destroy(revoker); + rnp_ffi_t newffi = NULL; + assert_rnp_success(rnp_ffi_create(&newffi, "GPG", "GPG")); + assert_true(import_all_keys(newffi, keydata.data(), keydata.size())); + assert_rnp_success(rnp_locate_key(newffi, "userid", "ecc-25519", &key)); + assert_rnp_success(rnp_key_get_revoker_count(key, &count)); + assert_int_equal(count, 1); + assert_rnp_success(rnp_key_get_revoker_at(key, 0, &revfp)); + assert_string_equal(revfp, "B54FDEBBB673423A5D0AA54423674F21B2441527"); + rnp_buffer_destroy(revfp); + assert_rnp_success(rnp_key_get_signature_at(key, 0, &newsig)); + assert_rnp_success(rnp_signature_get_type(newsig, &type)); + assert_string_equal(type, "direct"); + rnp_buffer_destroy(type); + assert_rnp_success(rnp_signature_get_revoker(newsig, &revfp)); + assert_string_equal(revfp, "B54FDEBBB673423A5D0AA54423674F21B2441527"); + rnp_buffer_destroy(revfp); + rnp_signature_handle_destroy(newsig); + rnp_key_handle_destroy(key); + /* Reload keyrings and make sure data is saved */ + assert_rnp_success(rnp_unload_keys(newffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); + rnp_ffi_destroy(newffi); + /* Add second designated revoker and make sure it works */ + assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-25519", &key)); + assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p384", &revoker)); + assert_rnp_success(rnp_key_direct_signature_create(key, NULL, &newsig)); + assert_rnp_success(rnp_key_signature_set_revoker(newsig, revoker, 0)); + assert_rnp_success(rnp_key_signature_sign(newsig)); + assert_rnp_success(rnp_signature_get_revoker(newsig, &revfp)); + assert_string_equal(revfp, "AB25CBA042DD924C3ACC3ED3242A3AA5EA85F44A"); + rnp_buffer_destroy(revfp); + assert_rnp_success(rnp_key_get_revoker_count(key, &count)); + assert_int_equal(count, 2); + assert_rnp_success(rnp_key_get_revoker_at(key, 0, &revfp)); + assert_string_equal(revfp, "AB25CBA042DD924C3ACC3ED3242A3AA5EA85F44A"); + rnp_buffer_destroy(revfp); + assert_rnp_success(rnp_signature_is_valid(newsig, 0)); + rnp_signature_handle_destroy(newsig); + rnp_key_handle_destroy(key); + rnp_key_handle_destroy(revoker); + /* Attempt to add designatured revoker to subkey */ + rnp_key_handle_t subkey = NULL; + assert_rnp_success( + rnp_locate_key(ffi, "fingerprint", "6D207DCC0AC281DBFC285785573436C231AE6338", &subkey)); + assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p384", &revoker)); + assert_rnp_failure(rnp_key_direct_signature_create(subkey, NULL, &newsig)); + rnp_key_handle_destroy(revoker); + /* Standard doesn't seem to answer whether subkey should be allowed here */ + assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-25519", &key)); + assert_rnp_success(rnp_key_direct_signature_create(key, NULL, &newsig)); + assert_rnp_success(rnp_key_signature_set_revoker(newsig, subkey, 0)); + assert_rnp_success(rnp_key_signature_sign(newsig)); + rnp_signature_handle_destroy(newsig); + rnp_key_handle_destroy(key); + rnp_key_handle_destroy(subkey); + /* Check v5 key */ + + rnp_ffi_destroy(ffi); +} \ No newline at end of file From d215025d63ff83edbab7316a45e2e90b7ec5b245 Mon Sep 17 00:00:00 2001 From: Nickolay Olshevsky Date: Fri, 29 Dec 2023 18:10:00 +0200 Subject: [PATCH 10/12] FFI: Add function rnp_signature_get_revocation_rason(). --- include/rnp/rnp.h | 15 +++++++++++++++ src/lib/rnp.cpp | 31 +++++++++++++++++++++++++++++++ src/librepgp/stream-sig.cpp | 6 ++++++ src/librepgp/stream-sig.h | 5 +++++ 4 files changed, 57 insertions(+) diff --git a/include/rnp/rnp.h b/include/rnp/rnp.h index 2f5c568900..8d0336d1a7 100644 --- a/include/rnp/rnp.h +++ b/include/rnp/rnp.h @@ -1724,6 +1724,21 @@ RNP_API rnp_result_t rnp_signature_get_signer(rnp_signature_handle_t sig, */ RNP_API rnp_result_t rnp_signature_get_revoker(rnp_signature_handle_t sig, char **revoker); +/** + * @brief Get revocation reason data, if it is available in the signature. + * + * @param sig signature handle, cannot be NULL. + * @param code string with revocation code will be stored here, if not NULL. See description of + * function rnp_key_revoke() for possible values. If information is not available, + * empty string will be stored here. + * @param reason revocation reason will be stored here, if available. Otherwise empty string + * will be stored here. May be NULL if this information is not needed. + * @return RNP_SUCCESS or error code if failed. + */ +RNP_API rnp_result_t rnp_signature_get_revocation_reason(rnp_signature_handle_t sig, + char ** code, + char ** reason); + /** * @brief Get signature validity, revalidating it if didn't before. * diff --git a/src/lib/rnp.cpp b/src/lib/rnp.cpp index 4f6464060a..8ec088a494 100644 --- a/src/lib/rnp.cpp +++ b/src/lib/rnp.cpp @@ -6519,6 +6519,37 @@ try { } FFI_GUARD +rnp_result_t +rnp_signature_get_revocation_reason(rnp_signature_handle_t sig, char **code, char **reason) +{ + if (!sig) { + return RNP_ERROR_NULL_POINTER; + } + std::string rcode; + std::string rreason; + if (sig->sig->sig.has_revocation_reason()) { + rcode = id_str_pair::lookup(revocation_code_map, sig->sig->sig.revocation_code(), ""); + rreason = sig->sig->sig.revocation_reason(); + } + if (code) { + rnp_result_t ret = ret_str_value("", code); + if (ret) { + return ret; + } + } + if (reason) { + rnp_result_t ret = ret_str_value("", reason); + if (ret) { + if (code) { + free(*code); + *code = NULL; + } + return ret; + } + } + return RNP_SUCCESS; +} + rnp_result_t rnp_signature_is_valid(rnp_signature_handle_t sig, uint32_t flags) try { diff --git a/src/librepgp/stream-sig.cpp b/src/librepgp/stream-sig.cpp index 06757e20ee..72496c04f8 100644 --- a/src/librepgp/stream-sig.cpp +++ b/src/librepgp/stream-sig.cpp @@ -1077,6 +1077,12 @@ pgp_signature_t::revocation_code() const return subpkt ? subpkt->fields.revocation_reason.code : PGP_REVOCATION_NO_REASON; } +bool +pgp_signature_t::has_revocation_reason() const +{ + return get_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON); +} + void pgp_signature_t::set_revocation_reason(pgp_revocation_type_t code, const std::string &reason) { diff --git a/src/librepgp/stream-sig.h b/src/librepgp/stream-sig.h index 98444070e7..b95d0adc11 100644 --- a/src/librepgp/stream-sig.h +++ b/src/librepgp/stream-sig.h @@ -279,6 +279,11 @@ typedef struct pgp_signature_t { */ pgp_revocation_type_t revocation_code() const; + /** + * @brief Check whether signature has revocation reason and code subpacket. + */ + bool has_revocation_reason() const; + /** @brief Set the revocation reason and code for key/subkey revocation signature. See the * RFC 4880, 5.2.3.24 for the detailed explanation. */ From 3569e6abed72e041441602f65ce0ddef919a048b Mon Sep 17 00:00:00 2001 From: Nickolay Olshevsky Date: Fri, 5 Jan 2024 14:46:39 +0200 Subject: [PATCH 11/12] FFI: add functions rnp_key_revocation_signature_create(), rnp_key_signature_set_hash() and rnp_key_signature_set_revocation_reason(). --- include/rekey/rnp_key_store.h | 14 ++++ include/rnp/rnp.h | 39 +++++++++ src/lib/rnp.cpp | 148 ++++++++++++++++++++++++--------- src/librekey/rnp_key_store.cpp | 33 ++++++++ 4 files changed, 195 insertions(+), 39 deletions(-) diff --git a/include/rekey/rnp_key_store.h b/include/rekey/rnp_key_store.h index e6198d4661..07735e93e5 100644 --- a/include/rekey/rnp_key_store.h +++ b/include/rekey/rnp_key_store.h @@ -177,6 +177,20 @@ class KeyStore { */ pgp_key_t *add_key(pgp_key_t &key); + /** + * @brief Add signature of the specific key to the keystore, revalidating and refresing + * key's data. Currently supports only direct-key or subkey binding signature. + * + * @param keyfp key's fingerprint. + * @param sig signature packet. + * @param front set to true if signature should be added to the beggining of the signature + * list. + * @return pgp_subsig_t* + */ + pgp_subsig_t *add_key_sig(const pgp_fingerprint_t &keyfp, + const pgp_signature_t & sig, + bool front); + /** * @brief Add transferable key to the keystore. * diff --git a/include/rnp/rnp.h b/include/rnp/rnp.h index 8d0336d1a7..ff4d4c868a 100644 --- a/include/rnp/rnp.h +++ b/include/rnp/rnp.h @@ -1542,6 +1542,45 @@ RNP_API rnp_result_t rnp_key_direct_signature_create(rnp_key_handle_t sig rnp_key_handle_t target, rnp_signature_handle_t *sig); +/** + * @brief Create new key or subkey revocation signature. It may be + * customized via the rnp_signature_set_* calls, and finalized via the + * rnp_signature_sign() call. + * + * @param signer revoker's key, must be secret, and must exist in the keyring up to the + * rnp_signature_sign() call. Cannot be NULL. + * @param target target key for which signature should be made. May be NULL, then signer will + * revoke itself. + * + * @param sig on success signature handle will be stored here. It is initialized with current + * creation time, default hash algorithm and version. Cannot be NULL. + * @return RNP_SUCCESS or error code if failed. + */ +RNP_API rnp_result_t rnp_key_revocation_signature_create(rnp_key_handle_t signer, + rnp_key_handle_t target, + rnp_signature_handle_t *sig); + +/** + * @brief Set hash algorithm, used during signing. + * + * @param sig editable key signature handle, i.e. created with rnp_key_*_signature_create(). + * @param hash hash algorithm name, i.e. "SHA256" or others. + * @return RNP_SUCCESS or error code if failed. + */ +RNP_API rnp_result_t rnp_key_signature_set_hash(rnp_signature_handle_t sig, const char *hash); + +/** + * @brief Set revocation reason and code for the revocation signature. + * See `rnp_key_revoke()` for the details. + * @param sig editable key signature handle, i.e. created with rnp_key_*_signature_create(). + * @param code revocation reason code. Could be NULL, then default one will be set. + * @param reason human-readable reason for revocation. Could be NULL or empty string. + * @return RNP_SUCCESS or error code if failed. + */ +RNP_API rnp_result_t rnp_key_signature_set_revocation_reason(rnp_signature_handle_t sig, + const char * code, + const char * reason); + /** * @brief Add designated revoker subpacket to the signature. See RFC 4880, section 5.2.3.15. * Only single revoker could be set - subsequent calls would overwrite the previous one. diff --git a/src/lib/rnp.cpp b/src/lib/rnp.cpp index 8ec088a494..d8c07244e7 100644 --- a/src/lib/rnp.cpp +++ b/src/lib/rnp.cpp @@ -4080,6 +4080,27 @@ rnp_key_get_revoker(rnp_key_handle_t key) return get_key_require_secret(key); } +static bool +fill_revocation_reason(rnp_ffi_t ffi, + pgp_revoke_t &revinfo, + const char * code, + const char * reason) +{ + revinfo = {}; + if (code && !str_to_revocation_type(code, &revinfo.code)) { + FFI_LOG(ffi, "Wrong revocation code: %s", code); + return false; + } + if (revinfo.code > PGP_REVOCATION_RETIRED) { + FFI_LOG(ffi, "Wrong key revocation code: %d", (int) revinfo.code); + return false; + } + if (reason) { + revinfo.reason = reason; + } + return true; +} + static rnp_result_t rnp_key_get_revocation(rnp_ffi_t ffi, pgp_key_t * key, @@ -4098,22 +4119,9 @@ rnp_key_get_revocation(rnp_ffi_t ffi, return RNP_ERROR_BAD_PARAMETERS; } pgp_revoke_t revinfo = {}; - if (code && !str_to_revocation_type(code, &revinfo.code)) { - FFI_LOG(ffi, "Wrong revocation code: %s", code); + if (!fill_revocation_reason(ffi, revinfo, code, reason)) { return RNP_ERROR_BAD_PARAMETERS; } - if (revinfo.code > PGP_REVOCATION_RETIRED) { - FFI_LOG(ffi, "Wrong key revocation code: %d", (int) revinfo.code); - return RNP_ERROR_BAD_PARAMETERS; - } - if (reason) { - try { - revinfo.reason = reason; - } catch (const std::exception &e) { - FFI_LOG(ffi, "%s", e.what()); - return RNP_ERROR_OUT_OF_MEMORY; - } - } /* unlock the secret key if needed */ rnp::KeyLocker revlock(*revoker); if (revoker->is_locked() && !revoker->unlock(ffi->pass_provider)) { @@ -6174,11 +6182,12 @@ try { } FFI_GUARD -rnp_result_t -rnp_key_direct_signature_create(rnp_key_handle_t signer, - rnp_key_handle_t target, - rnp_signature_handle_t *sig) -try { +static rnp_result_t +create_key_signature(rnp_key_handle_t signer, + rnp_key_handle_t target, + rnp_signature_handle_t *sig, + pgp_sig_type_t type) +{ if (!signer || !sig) { return RNP_ERROR_NULL_POINTER; } @@ -6190,8 +6199,19 @@ try { if (!sigkey || !tgkey) { return RNP_ERROR_BAD_PARAMETERS; } - if (!tgkey->is_primary()) { - return RNP_ERROR_BAD_PARAMETERS; + switch (type) { + case PGP_SIG_DIRECT: + if (!tgkey->is_primary()) { + return RNP_ERROR_BAD_PARAMETERS; + } + break; + case PGP_SIG_REV_KEY: + if (!tgkey->is_primary()) { + type = PGP_SIG_REV_SUBKEY; + } + break; + default: + return RNP_ERROR_NOT_IMPLEMENTED; } *sig = (rnp_signature_handle_t) calloc(1, sizeof(**sig)); if (!*sig) { @@ -6204,7 +6224,7 @@ try { DEFAULT_PGP_HASH_ALG, signer->ffi->context.time(), sigkey->version()); - sigpkt.set_type(PGP_SIG_DIRECT); + sigpkt.set_type(type); (*sig)->sig = new pgp_subsig_t(sigpkt); (*sig)->ffi = signer->ffi; (*sig)->key = tgkey; @@ -6219,6 +6239,57 @@ try { return RNP_SUCCESS; } + +rnp_result_t +rnp_key_direct_signature_create(rnp_key_handle_t signer, + rnp_key_handle_t target, + rnp_signature_handle_t *sig) +try { + return create_key_signature(signer, target, sig, PGP_SIG_DIRECT); +} +FFI_GUARD + +rnp_result_t +rnp_key_revocation_signature_create(rnp_key_handle_t signer, + rnp_key_handle_t target, + rnp_signature_handle_t *sig) +try { + return create_key_signature(signer, target, sig, PGP_SIG_REV_KEY); +} +FFI_GUARD + +rnp_result_t +rnp_key_signature_set_hash(rnp_signature_handle_t sig, const char *hash) +try { + if (!sig || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!sig->new_sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!str_to_hash_alg(hash, &sig->sig->sig.halg)) { + FFI_LOG(sig->ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_signature_set_revocation_reason(rnp_signature_handle_t sig, + const char * code, + const char * reason) +try { + if (!sig) { + return RNP_ERROR_NULL_POINTER; + } + pgp_revoke_t revinfo = {}; + if (!fill_revocation_reason(sig->ffi, revinfo, code, reason)) { + return RNP_ERROR_BAD_PARAMETERS; + } + sig->sig->sig.set_revocation_reason(revinfo.code, revinfo.reason); + return RNP_SUCCESS; +} FFI_GUARD rnp_result_t @@ -6268,31 +6339,30 @@ try { return RNP_ERROR_BAD_PASSWORD; } /* Sign */ - if (sigpkt.type() == PGP_SIG_DIRECT) { + switch (sigpkt.type()) { + case PGP_SIG_DIRECT: signer->sign_direct(sig->key->pkt(), sigpkt, sig->ffi->context); - } else { + break; + case PGP_SIG_REV_KEY: + signer->sign_direct(sig->key->pkt(), sigpkt, sig->ffi->context); + break; + case PGP_SIG_REV_SUBKEY: + signer->sign_binding(sig->key->pkt(), sigpkt, sig->ffi->context); + break; + default: FFI_LOG(sig->ffi, "Not yet supported signature type."); return RNP_ERROR_BAD_STATE; } /* Add to the keyring(s) */ - pgp_subsig_t *newsig = NULL; - pgp_key_t * key = sig->ffi->secring->get_key(sig->key->fp()); - if (key) { - newsig = &key->add_sig(sigpkt, PGP_UID_NONE, true); - key->refresh_data(sig->ffi->context); - } - key = sig->ffi->pubring->get_key(sig->key->fp()); - if (key) { - newsig = &key->add_sig(sigpkt, PGP_UID_NONE, true); - key->refresh_data(sig->ffi->context); - } + pgp_subsig_t *secsig = sig->ffi->secring->add_key_sig(sig->key->fp(), sigpkt, true); + pgp_subsig_t *pubsig = sig->ffi->pubring->add_key_sig(sig->key->fp(), sigpkt, true); /* Should not happen but let's check */ - if (!newsig) { + if (!secsig && !pubsig) { return RNP_ERROR_BAD_STATE; } /* Replace owned sig with pointer to the key's one */ delete sig->sig; - sig->sig = newsig; + sig->sig = pubsig ? pubsig : secsig; sig->own_sig = false; sig->new_sig = false; @@ -6532,13 +6602,13 @@ rnp_signature_get_revocation_reason(rnp_signature_handle_t sig, char **code, cha rreason = sig->sig->sig.revocation_reason(); } if (code) { - rnp_result_t ret = ret_str_value("", code); + rnp_result_t ret = ret_str_value(rcode.c_str(), code); if (ret) { return ret; } } if (reason) { - rnp_result_t ret = ret_str_value("", reason); + rnp_result_t ret = ret_str_value(rreason.c_str(), reason); if (ret) { if (code) { free(*code); diff --git a/src/librekey/rnp_key_store.cpp b/src/librekey/rnp_key_store.cpp index 89859de96c..e21187acfd 100644 --- a/src/librekey/rnp_key_store.cpp +++ b/src/librekey/rnp_key_store.cpp @@ -381,6 +381,39 @@ KeyStore::add_key(pgp_key_t &srckey) return added_key; } +pgp_subsig_t * +KeyStore::add_key_sig(const pgp_fingerprint_t &keyfp, const pgp_signature_t &sig, bool front) +{ + pgp_key_t *key = get_key(keyfp); + if (!key) { + return NULL; + } + + bool desig_rev = false; + pgp_key_t *signer = get_signer(sig); + switch (sig.type()) { + case PGP_SIG_REV_KEY: + desig_rev = signer && (signer->fp() != key->fp()); + break; + case PGP_SIG_REV_SUBKEY: + desig_rev = signer && (signer->fp() != key->primary_fp()); + break; + default: + break; + } + /* Add to the keyring(s) */ + pgp_subsig_t &newsig = key->add_sig(sig, PGP_UID_NONE, front); + if (desig_rev) { + key->validate_desig_revokes(*this); + } + if (key->is_primary()) { + key->refresh_data(secctx); + } else { + key->refresh_data(primary_key(*key), secctx); + } + return &newsig; +} + pgp_key_t * KeyStore::import_key(pgp_key_t &srckey, bool pubkey, pgp_key_import_status_t *status) { From 3c958aa2f9be0d1032bfff51825766c68377ebec Mon Sep 17 00:00:00 2001 From: Nickolay Olshevsky Date: Fri, 5 Jan 2024 14:47:00 +0200 Subject: [PATCH 12/12] Add tests for rnp_revocation_signature_create() and related functions. --- .../ecc-25519-subkey-revoker.asc | 23 ++ .../ecc-p256-subkey-revoker.asc | 15 ++ src/tests/ffi-key-sig.cpp | 229 +++++++++++++++++- src/tests/support.cpp | 76 ++++++ src/tests/support.h | 10 + 5 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 src/tests/data/test_stream_key_load/ecc-25519-subkey-revoker.asc create mode 100644 src/tests/data/test_stream_key_load/ecc-p256-subkey-revoker.asc diff --git a/src/tests/data/test_stream_key_load/ecc-25519-subkey-revoker.asc b/src/tests/data/test_stream_key_load/ecc-25519-subkey-revoker.asc new file mode 100644 index 0000000000..6c185a6dd8 --- /dev/null +++ b/src/tests/data/test_stream_key_load/ecc-25519-subkey-revoker.asc @@ -0,0 +1,23 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xjMEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6d+O201bdKhdowbielOougCpCfjCjAQf +FggANRYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJlqSVwFwzAFm0gfcwKwoHb/ChXhVc0NsIxrmM4 +AAoJEMx4YniYGwcoqQUA+ISdd4T1BVL156ysu5Kx0nndOCDwyt/hU3be8OgcVYQA/3o/+jRGeNIJ +JWQzm2Zg9NL2OERz4x8c6niN7FzyN58JzQllY2MtMjU1MTnClAQTFggAPAIbAwULCQgHAgMiAgEG +FQoJCAsCBBYCAwECHgMCF4AWIQQh/GgnSq47XeOaQnfMeGJ4mBsHKAUCXFWvzQAKCRDMeGJ4mBsH +KJRQAP4zLd0VFG+cbetyVuhxXK2KtEawwHGDLFwGO94maT7BHAEA5imx9tS5MKApVPUa1CUmGZzJ +Wv6UHYDsXe/z40QqWwrOwE0EZYq74wEIAOutyLlh6zbk853kC1fZ8tDIkWJV8fp9Ev1SuCZMZodr +u5mEZpSECOKAqLtkPhcQBWvdr069qCIuYX8PKFEa0EWSbG0xCLR96qA5bRdjr6Yt+CFQoOCu/M/Q +HbIWGUDhVu2laEnxdQ1LHn4WDzgfoNHn3sQt+jm9yRB9cxApHZexYtXnXdoVVycPHhBlVIt9MeZQ +03gArJj8xyU76+lYKk5oa8NyteDGyPOoy6SjsGkZ3wj35oBXxU5iu47j1Z2/88SiTb58DOZjANyr +RRyjnLhwQrF4pQG8RpWyxkwapZnAw+HL3Qzz+Eyv6gDUgxkiZX8wqixV4IBLh2ovhZLaVs0AEQEA +AcJ+BBgWCAAmFiEEIfxoJ0quO13jmkJ3zHhieJgbBygFAmWKu+QFCQPCZwACGwwACgkQzHhieJgb +ByjlmgD+J/VVYyVX2j+rvd/jOoLuscccqx7zvwtQjrjTOTv6AuwBAMiAtgmTis/6DxW+F12/SvSB +TJlvALKByYGylcxOnPYLzjMEZYq8AxYJKwYBBAHaRw8BAQdAvHP2eY4aiyio2t/X4mhDV2kv0EhY +P7lYt7KD+8cLFT3CwDUEGBYIACYWIQQh/GgnSq47XeOaQnfMeGJ4mBsHKAUCZYq8AwUJA8JnAAIb +AgCBCRDMeGJ4mBsHKHYgBBkWCAAdFiEEbSB9zArCgdv8KFeFVzQ2wjGuYzgFAmWKvAMACgkQVzQ2 +wjGuYzhKXgEApk24zirYI1HTG1g4EjdehjEAwOBwuMo2VdHP+OUev00A/iz8KyXKhC72GPZYKGUv +AQS61i1RZj6y1sBSL2lbjIcOxSIBAJFRIvZaaMIi141PWMLagPIPuuoiUtbqQppJLrFtmjZDAQCM +zAiwRTTrpzM5DCTO/9+0WEMo5zGAxdkx5x5Wu+r7Aw== +=gH76 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/tests/data/test_stream_key_load/ecc-p256-subkey-revoker.asc b/src/tests/data/test_stream_key_load/ecc-p256-subkey-revoker.asc new file mode 100644 index 0000000000..823e6b4431 --- /dev/null +++ b/src/tests/data/test_stream_key_load/ecc-p256-subkey-revoker.asc @@ -0,0 +1,15 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xlIEWsOCNRMIKoZIzj0DAQcCAwQS5G6mn5dhamZ6678SXE1azavqf8BItWO9Qv8VdS1vEEoD14ur +r5OQKTLuHhDRjvSQdaxRtkf0sI51T7230sT3wo0EHxMIADUWIQS1T967tnNCOl0KpUQjZ08hskQV +JwUCZakbnxcMgBZtIH3MCsKB2/woV4VXNDbCMa5jOAAKCRAjZ08hskQVJ9mpAP98Yg5M9Hdja9Jj +JnvpCG6OzE07L/Ny3J5QPGXVN/YrfwEA3k7PnUcMYtlRccaMQ7bYpqsqTX3S1dNk5idCEoKIrXjN +CGVjYy1wMjU2wpQEExMIADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEEtU/eu7Zz +QjpdCqVEI2dPIbJEFScFAlxVr+cACgkQI2dPIbJEFSfXJwD7BWD2wR8cMFoa0uqVRIFZ0YpRGsc3 +sIl7ZRPPX/uhAIEBAMOLhFDIsez5N86sE1JssSzAK7WgSHxWYe39I3v6ao0tzlYEWsOCNRIIKoZI +zj0DAQcCAwQsM4CssgbzkAFC7UjCBAymIi5TCP43uJuAZ6dNFyTl2QGlfjx4reVuOi9efhiQGd4D +vPXiJ7Pf5J2DpQUAwVXZAwEIB8J4BBgTCAAgAhsMFiEEtU/eu7ZzQjpdCqVEI2dPIbJEFScFAlxV +r+8ACgkQI2dPIbJEFSdkqgEAnriC/fDbucUERG8ixGq5+9HrFyKapLsaJ/5Z6EFT4/kBALluqghK +HShJCJ1p0jzh/rRG45qjOmO9HcHVs9mJHq7v +=2xRK +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/tests/ffi-key-sig.cpp b/src/tests/ffi-key-sig.cpp index 54c749ecb0..92990ddfd5 100644 --- a/src/tests/ffi-key-sig.cpp +++ b/src/tests/ffi-key-sig.cpp @@ -1749,4 +1749,231 @@ TEST_F(rnp_tests, test_ffi_add_revoker_signature) /* Check v5 key */ rnp_ffi_destroy(ffi); -} \ No newline at end of file +} + +TEST_F(rnp_tests, test_ffi_create_revocation_signature) +{ + rnp_ffi_t ffi = NULL; + assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); + assert_true(import_all_keys(ffi, "data/test_stream_key_load/ecc-25519-2subs-sec.asc")); + assert_true(import_pub_keys(ffi, "data/test_stream_key_load/ecc-p256-sec.asc")); + rnp_key_handle_t key = NULL; + assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-25519", &key)); + /* Create self revocation */ + rnp_signature_handle_t newsig = NULL; + assert_rnp_failure(rnp_key_revocation_signature_create(NULL, key, &newsig)); + assert_rnp_failure(rnp_key_revocation_signature_create(key, key, NULL)); + assert_rnp_failure(rnp_key_revocation_signature_create(key, NULL, NULL)); + assert_rnp_success(rnp_key_revocation_signature_create(key, NULL, &newsig)); + const char *revcode = "compromised"; + const char *revreason = "custom revocation reason"; + const char *hash = "SHA512"; + assert_rnp_failure(rnp_key_signature_set_hash(NULL, hash)); + assert_rnp_failure(rnp_key_signature_set_hash(newsig, NULL)); + assert_rnp_failure(rnp_key_signature_set_hash(newsig, "wrong")); + assert_rnp_success(rnp_key_signature_set_hash(newsig, hash)); + assert_rnp_failure(rnp_key_signature_set_revocation_reason(NULL, revcode, revreason)); + assert_rnp_success(rnp_key_signature_set_revocation_reason(newsig, NULL, NULL)); + assert_rnp_success(rnp_key_signature_set_revocation_reason(newsig, NULL, "wrong reason")); + assert_rnp_failure(rnp_key_signature_set_revocation_reason(newsig, "wrong code", NULL)); + assert_rnp_success(rnp_key_signature_set_revocation_reason(newsig, revcode, revreason)); + assert_rnp_failure(rnp_key_signature_sign(newsig)); + rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"); + assert_rnp_success(rnp_key_signature_sign(newsig)); + rnp_signature_handle_destroy(newsig); + /* Check signature parameters */ + assert_rnp_success(rnp_key_get_signature_at(key, 0, &newsig)); + assert_true(check_sig_hash(newsig, hash)); + assert_true(check_sig_type(newsig, "key revocation")); + char *sigcode = NULL; + char *sigreason = NULL; + /* Some edge cases of the new function */ + assert_rnp_failure(rnp_signature_get_revocation_reason(NULL, &sigcode, &sigreason)); + assert_rnp_success(rnp_signature_get_revocation_reason(newsig, &sigcode, NULL)); + assert_string_equal(sigcode, revcode); + rnp_buffer_destroy(sigcode); + assert_rnp_success(rnp_signature_get_revocation_reason(newsig, NULL, &sigreason)); + assert_string_equal(sigreason, revreason); + rnp_buffer_destroy(sigreason); + assert_true(check_sig_revreason(newsig, revcode, revreason)); + rnp_signature_handle_destroy(newsig); + assert_true(check_key_revoked(key, true)); + assert_true(check_key_revreason(key, revreason)); + bool compromised = false; + assert_rnp_success(rnp_key_is_compromised(key, &compromised)); + assert_true(compromised); + /* Export key and make sure data is saved */ + auto keydata = export_key(key, true); + rnp_key_handle_destroy(key); + rnp_ffi_t newffi = NULL; + assert_rnp_success(rnp_ffi_create(&newffi, "GPG", "GPG")); + assert_true(import_all_keys(newffi, keydata.data(), keydata.size())); + assert_rnp_success(rnp_locate_key(newffi, "userid", "ecc-25519", &key)); + assert_true(check_key_revoked(key, true)); + assert_true(check_key_revreason(key, revreason)); + assert_rnp_success(rnp_key_get_signature_at(key, 0, &newsig)); + assert_true(check_sig_type(newsig, "key revocation")); + rnp_signature_handle_destroy(newsig); + rnp_key_handle_destroy(key); + rnp_ffi_destroy(newffi); + /* Create revocation for other key, using the designated revoker */ + rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET); + assert_true(import_all_keys(ffi, "data/test_stream_key_load/ecc-25519-sec.asc")); + assert_true( + import_pub_keys(ffi, "data/test_stream_key_load/ecc-p256-desig-rev-1-pub.asc")); + key = get_key_by_uid(ffi, "ecc-p256"); + rnp_key_handle_t revoker = get_key_by_uid(ffi, "ecc-25519"); + assert_true(check_key_locked(revoker, true)); + assert_true(check_key_revoked(key, false)); + assert_rnp_success(rnp_key_revocation_signature_create(revoker, key, &newsig)); + assert_rnp_success(rnp_key_signature_set_hash(newsig, hash)); + assert_rnp_success(rnp_key_signature_set_revocation_reason(newsig, revcode, revreason)); + assert_rnp_success(rnp_key_signature_sign(newsig)); + assert_true(check_key_locked(revoker, true)); + rnp_signature_handle_destroy(newsig); + rnp_key_handle_destroy(revoker); + assert_true(check_key_revoked(key, true)); + assert_true(check_key_revreason(key, revreason)); + assert_rnp_success(rnp_key_is_compromised(key, &compromised)); + assert_true(compromised); + /* Export/import and check */ + keydata = export_key(key); + rnp_key_handle_destroy(key); + assert_rnp_success(rnp_ffi_create(&newffi, "GPG", "GPG")); + assert_true(import_all_keys(newffi, keydata.data(), keydata.size())); + assert_true(import_all_keys(newffi, "data/test_stream_key_load/ecc-25519-pub.asc")); + key = get_key_by_uid(ffi, "ecc-p256"); + assert_true(check_key_revoked(key, true)); + assert_true(check_key_revreason(key, revreason)); + assert_rnp_success(rnp_key_get_signature_at(key, 0, &newsig)); + assert_true(check_sig_type(newsig, "key revocation")); + rnp_signature_handle_destroy(newsig); + rnp_key_handle_destroy(key); + rnp_ffi_destroy(newffi); + /* Create self subkey revocation */ + rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET); + assert_true(import_all_keys(ffi, "data/test_stream_key_load/ecc-p256-sec.asc")); + key = get_key_by_uid(ffi, "ecc-p256"); + rnp_key_handle_t subkey = get_key_by_fp(ffi, "40E608AFBC8D62CDCC08904F37E285E9E9851491"); + assert_rnp_success(rnp_key_revocation_signature_create(key, subkey, &newsig)); + assert_rnp_success(rnp_key_signature_sign(newsig)); + assert_true(check_key_revoked(subkey, true)); + assert_true(check_key_revreason(subkey, "No reason specified")); + assert_rnp_success(rnp_key_is_compromised(subkey, &compromised)); + assert_false(compromised); + rnp_signature_handle_destroy(newsig); + assert_rnp_success(rnp_key_get_signature_at(subkey, 0, &newsig)); + assert_true(check_sig_type(newsig, "subkey revocation")); + assert_rnp_success(rnp_signature_get_revocation_reason(newsig, &sigcode, &sigreason)); + assert_string_equal(sigcode, ""); + assert_string_equal(sigreason, ""); + rnp_buffer_destroy(sigcode); + rnp_buffer_destroy(sigreason); + rnp_signature_handle_destroy(newsig); + rnp_key_handle_destroy(subkey); + /* Export and recheck */ + keydata = export_key(key); + rnp_key_handle_destroy(key); + assert_rnp_success(rnp_ffi_create(&newffi, "GPG", "GPG")); + assert_true(import_all_keys(newffi, keydata.data(), keydata.size())); + subkey = get_key_by_fp(newffi, "40E608AFBC8D62CDCC08904F37E285E9E9851491"); + assert_true(check_key_revoked(subkey, true)); + assert_true(check_key_revreason(subkey, "No reason specified")); + assert_rnp_success(rnp_key_is_compromised(subkey, &compromised)); + assert_false(compromised); + rnp_key_handle_destroy(subkey); + rnp_ffi_destroy(newffi); + /* Revoke other key using subkey as revoker with designated revoker */ + rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET); + assert_true(import_all_keys(ffi, "data/test_stream_key_load/ecc-25519-2subs-sec.asc")); + assert_true(import_all_keys(ffi, "data/test_stream_key_load/ecc-p256-subkey-revoker.asc")); + key = get_key_by_uid(ffi, "ecc-p256"); + revoker = get_key_by_fp(ffi, "6D207DCC0AC281DBFC285785573436C231AE6338"); + assert_rnp_success(rnp_key_revocation_signature_create(revoker, key, &newsig)); + assert_rnp_success(rnp_key_signature_set_hash(newsig, hash)); + assert_rnp_success(rnp_key_signature_set_revocation_reason(newsig, revcode, revreason)); + assert_rnp_success(rnp_key_signature_sign(newsig)); + rnp_signature_handle_destroy(newsig); + assert_true(check_key_revoked(key, true)); + assert_true(check_key_revreason(key, revreason)); + assert_rnp_success(rnp_key_is_compromised(key, &compromised)); + assert_true(compromised); + rnp_key_handle_destroy(revoker); + /* Export and recheck */ + keydata = export_key(key); + rnp_key_handle_destroy(key); + assert_rnp_success(rnp_ffi_create(&newffi, "GPG", "GPG")); + assert_true(import_all_keys(newffi, keydata.data(), keydata.size())); + key = get_key_by_uid(ffi, "ecc-p256"); + assert_true(check_key_revoked(key, true)); + assert_true(check_key_revreason(key, revreason)); + assert_rnp_success(rnp_key_is_compromised(key, &compromised)); + assert_true(compromised); + rnp_key_handle_destroy(key); + rnp_ffi_destroy(newffi); + /* Attempt to revoke primary key using the subkey without designated revoker */ + key = get_key_by_uid(ffi, "ecc-25519"); + revoker = get_key_by_fp(ffi, "6D207DCC0AC281DBFC285785573436C231AE6338"); + assert_rnp_success(rnp_key_revocation_signature_create(revoker, key, &newsig)); + assert_rnp_success(rnp_key_signature_set_revocation_reason(newsig, revcode, revreason)); + assert_rnp_success(rnp_key_signature_sign(newsig)); + rnp_signature_handle_destroy(newsig); + assert_true(check_key_revoked(key, false)); + rnp_key_handle_destroy(revoker); + /* Export and recheck */ + keydata = export_key(key); + rnp_key_handle_destroy(key); + assert_rnp_success(rnp_ffi_create(&newffi, "GPG", "GPG")); + assert_true(import_all_keys(newffi, keydata.data(), keydata.size())); + key = get_key_by_uid(newffi, "ecc-25519"); + assert_true(check_key_revoked(key, false)); + rnp_key_handle_destroy(key); + rnp_ffi_destroy(newffi); + /* Attempt to revoke primary key using the subkey with designated revoker */ + rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET); + assert_true( + import_all_keys(ffi, "data/test_stream_key_load/ecc-25519-subkey-revoker.asc")); + assert_true(import_all_keys(ffi, "data/test_stream_key_load/ecc-25519-2subs-sec.asc")); + key = get_key_by_uid(ffi, "ecc-25519"); + revoker = get_key_by_fp(ffi, "6D207DCC0AC281DBFC285785573436C231AE6338"); + assert_rnp_success(rnp_key_revocation_signature_create(revoker, key, &newsig)); + assert_rnp_success(rnp_key_signature_set_revocation_reason(newsig, revcode, revreason)); + assert_rnp_success(rnp_key_signature_sign(newsig)); + rnp_signature_handle_destroy(newsig); + assert_true(check_key_revoked(key, true)); + assert_true(check_key_revreason(key, revreason)); + assert_rnp_success(rnp_key_is_compromised(key, &compromised)); + assert_true(compromised); + rnp_key_handle_destroy(revoker); + /* Export and recheck */ + keydata = export_key(key); + rnp_key_handle_destroy(key); + assert_rnp_success(rnp_ffi_create(&newffi, "GPG", "GPG")); + assert_true(import_all_keys(newffi, keydata.data(), keydata.size())); + key = get_key_by_uid(newffi, "ecc-25519"); + assert_true(check_key_revoked(key, true)); + assert_true(check_key_revreason(key, revreason)); + assert_rnp_success(rnp_key_is_compromised(key, &compromised)); + assert_true(compromised); + rnp_key_handle_destroy(key); + rnp_ffi_destroy(newffi); + /* Attempt to revoke signing subkey using itself as revoker */ + rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET); + assert_true(import_all_keys(ffi, "data/test_stream_key_load/ecc-25519-2subs-sec.asc")); + key = get_key_by_fp(ffi, "6D207DCC0AC281DBFC285785573436C231AE6338"); + assert_rnp_success(rnp_key_revocation_signature_create(key, NULL, &newsig)); + assert_rnp_success(rnp_key_signature_sign(newsig)); + assert_true(check_key_revoked(key, false)); + rnp_signature_handle_destroy(newsig); + assert_rnp_success(rnp_key_get_signature_at(key, 0, &newsig)); + assert_true(check_sig_type(newsig, "subkey revocation")); + assert_rnp_success(rnp_signature_get_revocation_reason(newsig, &sigcode, &sigreason)); + assert_string_equal(sigcode, ""); + assert_string_equal(sigreason, ""); + rnp_buffer_destroy(sigcode); + rnp_buffer_destroy(sigreason); + rnp_signature_handle_destroy(newsig); + rnp_key_handle_destroy(key); + + rnp_ffi_destroy(ffi); +} diff --git a/src/tests/support.cpp b/src/tests/support.cpp index 1189aa7ffa..ec71f698f3 100644 --- a/src/tests/support.cpp +++ b/src/tests/support.cpp @@ -1106,6 +1106,16 @@ check_key_revoked(rnp_key_handle_t key, bool revoked) return rev == revoked; } +bool +check_key_locked(rnp_key_handle_t key, bool locked) +{ + bool lock = !locked; + if (rnp_key_is_locked(key, &lock)) { + return false; + } + return lock == locked; +} + uint32_t get_key_expiry(rnp_key_handle_t key) { @@ -1159,6 +1169,18 @@ check_key_fp(rnp_key_handle_t key, const std::string &expected) return res; } +bool +check_key_revreason(rnp_key_handle_t key, const char *reason) +{ + char *rstr = NULL; + if (rnp_key_get_revocation_reason(key, &rstr)) { + return false; + } + bool res = !strcmp(rstr, reason); + rnp_buffer_destroy(rstr); + return res; +} + bool check_has_key(rnp_ffi_t ffi, const std::string &id, bool secret, bool valid) { @@ -1196,6 +1218,60 @@ check_has_key(rnp_ffi_t ffi, const std::string &id, bool secret, bool valid) return res; } +bool +check_sig_hash(rnp_signature_handle_t sig, const char *hash) +{ + char *sighash = NULL; + if (rnp_signature_get_hash_alg(sig, &sighash)) { + return false; + } + bool res = !strcmp(sighash, hash); + rnp_buffer_destroy(sighash); + return res; +} + +bool +check_sig_type(rnp_signature_handle_t sig, const char *type) +{ + char *sigtype = NULL; + if (rnp_signature_get_type(sig, &sigtype)) { + return false; + } + bool res = !strcmp(sigtype, type); + rnp_buffer_destroy(sigtype); + return res; +} + +bool +check_sig_revreason(rnp_signature_handle_t sig, const char *revcode, const char *revreason) +{ + char *sigcode = NULL; + char *sigreason = NULL; + if (rnp_signature_get_revocation_reason(sig, &sigcode, &sigreason)) { + return false; + } + bool res = !strcmp(sigcode, revcode) && !strcmp(sigreason, revreason); + rnp_buffer_destroy(sigcode); + rnp_buffer_destroy(sigreason); + return res; +} + +rnp_key_handle_t +get_key_by_fp(rnp_ffi_t ffi, const char *fp) +{ + rnp_key_handle_t key = NULL; + rnp_locate_key(ffi, "fingerprint", fp, &key); + return key; +} + +rnp_key_handle_t +get_key_by_uid(rnp_ffi_t ffi, const char *uid) +{ + rnp_key_handle_t key = NULL; + rnp_locate_key(ffi, "userid", uid, &key); + return key; +} + rnp_key_handle_t bogus_key_handle(rnp_ffi_t ffi) { diff --git a/src/tests/support.h b/src/tests/support.h index a1ffa56e0a..5b4e2c54b7 100644 --- a/src/tests/support.h +++ b/src/tests/support.h @@ -251,6 +251,7 @@ void dump_key_stdout(rnp_key_handle_t key, bool secret = false); /* some shortcuts for less code */ bool check_key_valid(rnp_key_handle_t key, bool validity); bool check_key_revoked(rnp_key_handle_t key, bool revoked); +bool check_key_locked(rnp_key_handle_t key, bool locked); uint32_t get_key_expiry(rnp_key_handle_t key); size_t get_key_uids(rnp_key_handle_t key); bool check_sub_valid(rnp_key_handle_t key, size_t idx, bool validity); @@ -265,10 +266,19 @@ void check_loaded_keys(const char * format, bool secret); bool check_key_grip(rnp_key_handle_t key, const std::string &expected); bool check_key_fp(rnp_key_handle_t key, const std::string &expected); +bool check_key_revreason(rnp_key_handle_t key, const char *reason); bool check_has_key(rnp_ffi_t ffi, const std::string &id, bool secret = false, bool valid = true); +bool check_sig_hash(rnp_signature_handle_t sig, const char *hash); +bool check_sig_type(rnp_signature_handle_t sig, const char *type); +bool check_sig_revreason(rnp_signature_handle_t sig, + const char * revcode, + const char * revreason); + +rnp_key_handle_t get_key_by_fp(rnp_ffi_t ffi, const char *fp); +rnp_key_handle_t get_key_by_uid(rnp_ffi_t ffi, const char *uid); /* create bogus key handle with NULL pub/sec keys */ rnp_key_handle_t bogus_key_handle(rnp_ffi_t ffi);