diff --git a/NEWS b/NEWS index f2e715f1..b7309454 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,11 @@ Noteworthy changes in version 1.24.0 (unrelease) * Extended gpgme_op_encrypt*, gpgme_op_encrypt_sign*, and gpgme_op_sign* to allow reading the input data directly from a file. [T6550] + * Add information about designated revocation keys. [T7118] + + * cpp: Provide information about designated revocation keys for a Key. + [T7118] + * qt: Allow reading the data to decrypt/encrypt/sign/verify directly from files. [T6550] @@ -17,8 +22,14 @@ Noteworthy changes in version 1.24.0 (unrelease) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GPGME_ENCRYPT_FILE NEW. GPGME_SIG_MODE_FILE NEW. + gpgme_key_t EXTENDED: New field 'revkeys'. + gpgme_revocation_key_t NEW. cpp: Context::EncryptFile NEW. cpp: SignatureMode::SignFile NEW. + cpp: RevocationKey NEW. + cpp: Key::revocationKey NEW. + cpp: Key::numRevocationKeys NEW. + cpp: Key::revocationKeys NEW. qt: DecryptVerifyJob::setInputFile NEW. qt: DecryptVerifyJob::inputFile NEW. qt: DecryptVerifyJob::setOutputFile NEW. diff --git a/doc/gpgme.texi b/doc/gpgme.texi index 258ef0a5..48cca032 100644 --- a/doc/gpgme.texi +++ b/doc/gpgme.texi @@ -3566,6 +3566,10 @@ be missing but this field may be set nevertheless. Reserved for the time of the last update of this key. +@item gpgme_revocation_key_t revkeys +@since{1.24.0} +This is a linked list with the revocation keys for the key. + @end table @end deftp @@ -3908,6 +3912,37 @@ This is a linked list with the notation data and policy URLs. @end deftp +@deftp {Data type} gpgme_revocation_key_t +@since{1.24.0} + +The @code{gpgme_revocation_key_t} type is a pointer to a revocation key +structure. Revocation key structures are one component of a +@code{gpgme_key_t} object. They provide information about the designated +revocation keys for a key. + +The revocation key structure has the following members: + +@table @code +@item gpgme_revocation_key_t next +This is a pointer to the next revocation key structure in the linked list, +or @code{NULL} if this is the last element. + +@item gpgme_pubkey_algo_t pubkey_algo +This is the public key algorithm of the revocation key. + +@item char *fpr +This is the fingerprint of the revocation_key in hexadecimal digits. + +@item unsigned int key_class +This is the class of the revocation key signature subpacket. + +@item unsigned int sensitive : 1 +This is true if the revocation key is marked as sensitive. + +@end table +@end deftp + + @node Listing Keys @subsection Listing Keys diff --git a/lang/cpp/src/gpgmefw.h b/lang/cpp/src/gpgmefw.h index fdad7bf0..c6166d2c 100644 --- a/lang/cpp/src/gpgmefw.h +++ b/lang/cpp/src/gpgmefw.h @@ -75,4 +75,7 @@ typedef struct _gpgme_tofu_info *gpgme_tofu_info_t; struct _gpgme_op_query_swdb_result; typedef struct _gpgme_op_query_swdb_result *gpgme_query_swdb_result_t; +struct _gpgme_revocation_key; +typedef struct _gpgme_revocation_key *gpgme_revocation_key_t; + #endif // __GPGMEPP_GPGMEFW_H__ diff --git a/lang/cpp/src/key.cpp b/lang/cpp/src/key.cpp index 9cbd188b..2465cf06 100644 --- a/lang/cpp/src/key.cpp +++ b/lang/cpp/src/key.cpp @@ -122,6 +122,37 @@ std::vector Key::subkeys() const return v; } +RevocationKey Key::revocationKey(unsigned int index) const +{ + return RevocationKey(key, index); +} + +unsigned int Key::numRevocationKeys() const +{ + if (!key) { + return 0; + } + unsigned int count = 0; + for (auto revkey = key->revocation_keys; revkey; revkey = revkey->next) { + ++count; + } + return count; +} + +std::vector Key::revocationKeys() const +{ + if (!key) { + return std::vector(); + } + + std::vector v; + v.reserve(numRevocationKeys()); + for (auto revkey = key->revocation_keys; revkey; revkey = revkey->next) { + v.push_back(RevocationKey(key, revkey)); + } + return v; +} + Key::OwnerTrust Key::ownerTrust() const { if (!key) { @@ -1256,6 +1287,68 @@ bool UserID::Signature::isBad() const return isNull() || isExpired() || isInvalid(); } +// +// +// class RevocationKey +// +// + +static gpgme_revocation_key_t find_revkey(const shared_gpgme_key_t &key, unsigned int idx) +{ + if (key) { + for (gpgme_revocation_key_t s = key->revocation_keys; s; s = s->next, --idx) { + if (idx == 0) { + return s; + } + } + } + return nullptr; +} + +static gpgme_revocation_key_t verify_revkey(const shared_gpgme_key_t &key, gpgme_revocation_key_t revkey) +{ + if (key) { + for (gpgme_revocation_key_t s = key->revocation_keys; s; s = s->next) { + if (s == revkey) { + return revkey; + } + } + } + return nullptr; +} + +RevocationKey::RevocationKey() : key(), revkey(nullptr) {} + +RevocationKey::RevocationKey(const shared_gpgme_key_t &k, unsigned int idx) + : key(k), revkey(find_revkey(k, idx)) +{ +} + +RevocationKey::RevocationKey(const shared_gpgme_key_t &k, gpgme_revocation_key_t sk) + : key(k), revkey(verify_revkey(k, sk)) +{ +} + +Key RevocationKey::parent() const +{ + return Key(key); +} + +const char *RevocationKey::fingerprint() const +{ + return revkey ? revkey->fpr : nullptr; +} + +bool RevocationKey::isSensitive() const +{ + return revkey ? revkey->sensitive : false; +} + +int RevocationKey::algorithm() const +{ + return revkey ? revkey->pubkey_algo : 0; +} + std::ostream &operator<<(std::ostream &os, const UserID &uid) { os << "GpgME::UserID("; @@ -1325,6 +1418,20 @@ std::ostream &operator<<(std::ostream &os, const Key &key) const std::vector subkeys = key.subkeys(); std::copy(subkeys.begin(), subkeys.end(), std::ostream_iterator(os, "\n")); + os << " revocationKeys:\n"; + const std::vector revkeys = key.revocationKeys(); + std::copy(revkeys.begin(), revkeys.end(), + std::ostream_iterator(os, "\n")); + } + return os << ')'; +} + +std::ostream &operator<<(std::ostream &os, const RevocationKey &revkey) +{ + os << "GpgME::RevocationKey("; + if (!revkey.isNull()) { + os << "\n fingerprint: " << protect(revkey.fingerprint()) + << "\n isSensitive: " << revkey.isSensitive(); } return os << ')'; } diff --git a/lang/cpp/src/key.h b/lang/cpp/src/key.h index bdcc18d2..a1648884 100644 --- a/lang/cpp/src/key.h +++ b/lang/cpp/src/key.h @@ -44,6 +44,7 @@ class Context; class Subkey; class UserID; class TofuInfo; +class RevocationKey; typedef std::shared_ptr< std::remove_pointer::type > shared_gpgme_key_t; @@ -100,6 +101,10 @@ public: std::vector userIDs() const; std::vector subkeys() const; + RevocationKey revocationKey(unsigned int index) const; + unsigned int numRevocationKeys() const; + std::vector revocationKeys() const; + bool isRevoked() const; bool isExpired() const; bool isDisabled() const; @@ -547,9 +552,53 @@ private: gpgme_key_sig_t sig; }; +// +// class RevocationKey +// + +class GPGMEPP_EXPORT RevocationKey +{ +public: + RevocationKey(); + RevocationKey(const shared_gpgme_key_t &key, gpgme_revocation_key_t revkey); + RevocationKey(const shared_gpgme_key_t &key, unsigned int idx); + + // Rule of Zero + + void swap(RevocationKey &other) + { + using std::swap; + swap(this->key, other.key); + swap(this->revkey, other.revkey); + } + + bool isNull() const + { + return !key || !revkey; + } + + Key parent() const; + + const char *fingerprint() const; + + bool isSensitive() const; + + int algorithm() const; + +private: + shared_gpgme_key_t key; + gpgme_revocation_key_t revkey; +}; + +inline void swap(RevocationKey& v1, RevocationKey& v2) +{ + v1.swap(v2); +} + GPGMEPP_EXPORT std::ostream &operator<<(std::ostream &os, const UserID &uid); GPGMEPP_EXPORT std::ostream &operator<<(std::ostream &os, const Subkey &subkey); GPGMEPP_EXPORT std::ostream &operator<<(std::ostream &os, const Key &key); +GPGMEPP_EXPORT std::ostream &operator<<(std::ostream &os, const RevocationKey &revkey); } // namespace GpgME diff --git a/lang/cpp/tests/run-keylist.cpp b/lang/cpp/tests/run-keylist.cpp index 9e7d763c..b46a815a 100644 --- a/lang/cpp/tests/run-keylist.cpp +++ b/lang/cpp/tests/run-keylist.cpp @@ -160,7 +160,10 @@ main (int argc, char **argv) std::stringstream ss; do { key = ctx->nextKey(err); - ss << key << "\n\n"; + if (!err) + { + ss << key << "\n\n"; + } } while (!err && !key.isNull()); std::cout << ss.str(); diff --git a/src/gpgme-json.c b/src/gpgme-json.c index 3d18eee2..c88973a8 100644 --- a/src/gpgme-json.c +++ b/src/gpgme-json.c @@ -1024,6 +1024,24 @@ subkey_to_json (gpgme_subkey_t sub) return result; } +/* Create a revocation key json object */ +static cjson_t +revocation_key_to_json (gpgme_revocation_key_t revkey) +{ + cjson_t result = xjson_CreateObject (); + + xjson_AddBoolToObject (result, "sensitive", revkey->sensitive); + + xjson_AddStringToObject0 (result, "fingerprint", revkey->fpr); + xjson_AddStringToObject0 (result, "pubkey_algo_name", + gpgme_pubkey_algo_name (revkey->pubkey_algo)); + + xjson_AddNumberToObject (result, "pubkey_algo", revkey->pubkey_algo); + xjson_AddNumberToObject (result, "key_class", revkey->key_class); + + return result; +} + /* Create a key json object */ static cjson_t key_to_json (gpgme_key_t key) @@ -1075,6 +1093,16 @@ key_to_json (gpgme_key_t key) xjson_AddItemToObject (result, "userids", uid_array); } + /* Revocation keys */ + if (key->revocation_keys) + { + gpgme_revocation_key_t revkey; + cjson_t array = xjson_CreateArray (); + for (revkey = key->revocation_keys; revkey; revkey = revkey->next) + cJSON_AddItemToArray (array, revocation_key_to_json (revkey)); + xjson_AddItemToObject (result, "revocation_keys", array); + } + return result; } diff --git a/src/gpgme.h.in b/src/gpgme.h.in index bfd6f72b..0ee3e088 100644 --- a/src/gpgme.h.in +++ b/src/gpgme.h.in @@ -763,6 +763,28 @@ struct _gpgme_user_id typedef struct _gpgme_user_id *gpgme_user_id_t; +/* A designated revocation key for a key. + * This structure shall be considered read-only and an application + * must not allocate such a structure on its own. */ +struct _gpgme_revocation_key +{ + struct _gpgme_revocation_key *next; + + /* The public key algorithm of the revocation key. */ + gpgme_pubkey_algo_t pubkey_algo; + + /* The fingerprint of the revocation_key in hex digit form. */ + char *fpr; + + /* The class of the revocation key. */ + unsigned int key_class; + + /* True if the revocation key should not be exported. */ + unsigned int sensitive : 1; +}; +typedef struct _gpgme_revocation_key *gpgme_revocation_key_t; + + /* A key from the keyring. * This structure shall be considered read-only and an application * must not allocate such a structure on its own. */ @@ -860,6 +882,12 @@ struct _gpgme_key /* Time of the last refresh of the entire key. 0 if unknown. */ unsigned long last_update; + + /* The revocation keys of the key. */ + gpgme_revocation_key_t revocation_keys; + + /* Internal to GPGME, do not use. */ + gpgme_revocation_key_t _last_revkey; }; typedef struct _gpgme_key *gpgme_key_t; diff --git a/src/key.c b/src/key.c index 93cdc1c8..fec9eb16 100644 --- a/src/key.c +++ b/src/key.c @@ -304,6 +304,35 @@ _gpgme_key_add_sig (gpgme_key_t key, char *src) } +gpgme_error_t +_gpgme_key_add_rev_key (gpgme_key_t key, const char *src) +{ + gpgme_revocation_key_t revkey; + int src_len = src ? strlen (src) : 0; + + assert (key); + /* malloc a buffer for the revocation key and the fingerprint. */ + revkey = malloc (sizeof (*revkey) + src_len + 1); + if (!revkey) + return gpg_error_from_syserror (); + memset (revkey, 0, sizeof *revkey); + + revkey->fpr = ((char *) revkey) + sizeof (*revkey); + if (src) + memcpy (revkey->fpr, src, src_len + 1); + else + revkey->fpr[0] = '\0'; + + if (!key->revocation_keys) + key->revocation_keys = revkey; + if (key->_last_revkey) + key->_last_revkey->next = revkey; + key->_last_revkey = revkey; + + return 0; +} + + /* Acquire a reference to KEY. */ void gpgme_key_ref (gpgme_key_t key) @@ -324,6 +353,7 @@ gpgme_key_unref (gpgme_key_t key) { gpgme_user_id_t uid; gpgme_subkey_t subkey; + gpgme_revocation_key_t revkey; if (!key) return; @@ -392,6 +422,14 @@ gpgme_key_unref (gpgme_key_t key) uid = next_uid; } + revkey = key->revocation_keys; + while (revkey) + { + gpgme_revocation_key_t next = revkey->next; + free (revkey); + revkey = next; + } + free (key->issuer_serial); free (key->issuer_name); free (key->chain_id); diff --git a/src/keylist.c b/src/keylist.c index 1d8c8184..f8dd2962 100644 --- a/src/keylist.c +++ b/src/keylist.c @@ -603,7 +603,7 @@ keylist_colon_handler (void *priv, char *line) enum { RT_NONE, RT_SIG, RT_UID, RT_TFS, RT_SUB, RT_PUB, RT_FPR, RT_FP2, RT_GRP, - RT_SSB, RT_SEC, RT_CRT, RT_CRS, RT_REV, RT_SPK + RT_SSB, RT_SEC, RT_CRT, RT_CRS, RT_REV, RT_SPK, RT_RVK } rectype = RT_NONE; #define NR_FIELDS 20 @@ -669,6 +669,8 @@ keylist_colon_handler (void *priv, char *line) rectype = RT_SSB; else if (!strcmp (field[0], "spk") && key) rectype = RT_SPK; + else if (!strcmp (field[0], "rvk") && key) + rectype = RT_RVK; else rectype = RT_NONE; @@ -1124,6 +1126,39 @@ keylist_colon_handler (void *priv, char *line) keysig->_last_notation = notation; } } + break; + + case RT_RVK: + /* Ignore revocation keys without fingerprint */ + if (fields >= 10 && *field[9]) + { + gpgme_revocation_key_t revkey = NULL; + + err = _gpgme_key_add_rev_key (key, field[9]); + if (err) + return err; + + revkey = key->_last_revkey; + assert (revkey); + + /* Field 4 has the public key algorithm. */ + { + int i = atoi (field[3]); + if (i >= 1 && i < 128) + revkey->pubkey_algo = _gpgme_map_pk_algo (i, ctx->protocol); + } + + /* Field 11 has the class (eg, 0x40 means sensitive). */ + if (fields >= 11 && field[10][0] && field[10][1]) + { + int key_class = _gpgme_hextobyte (field[10]); + if (key_class >= 0) + revkey->key_class = key_class; + if (field[10][2] == 's') + revkey->sensitive = 1; + } + } + break; case RT_NONE: /* Unknown record. */ diff --git a/src/ops.h b/src/ops.h index cde63a4e..aa8d9c94 100644 --- a/src/ops.h +++ b/src/ops.h @@ -145,6 +145,7 @@ gpgme_error_t _gpgme_key_add_subkey (gpgme_key_t key, gpgme_error_t _gpgme_key_append_name (gpgme_key_t key, const char *src, int convert); gpgme_key_sig_t _gpgme_key_add_sig (gpgme_key_t key, char *src); +gpgme_error_t _gpgme_key_add_rev_key (gpgme_key_t key, const char *src); diff --git a/tests/json/Makefile.am b/tests/json/Makefile.am index 90fba79e..0484cb74 100644 --- a/tests/json/Makefile.am +++ b/tests/json/Makefile.am @@ -88,9 +88,13 @@ gpg-sample.stamp: $(private_keys) done echo x > ./gpg-sample.stamp -pubring-stamp: $(top_srcdir)/tests/gpg/pubdemo.asc gpg-sample.stamp +pubring-stamp: $(top_srcdir)/tests/gpg/pubdemo.asc \ + $(top_srcdir)/tests/json/key-with-revokers.asc \ + gpg-sample.stamp $(TESTS_ENVIRONMENT) $(GPG) --batch --no-permission-warning \ --import $(top_srcdir)/tests/gpg/pubdemo.asc + $(TESTS_ENVIRONMENT) $(GPG) --batch --no-permission-warning \ + --import $(top_srcdir)/tests/json/key-with-revokers.asc -$(TESTS_ENVIRONMENT) $(GPG) --batch --no-permission-warning \ --import $(top_srcdir)/tests/gpg/secdemo.asc -$(TESTS_ENVIRONMENT) gpgconf --kill all diff --git a/tests/json/key-with-revokers.asc b/tests/json/key-with-revokers.asc new file mode 100644 index 00000000..3fa9011c --- /dev/null +++ b/tests/json/key-with-revokers.asc @@ -0,0 +1,19 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEZkTFQxYJKwYBBAHaRw8BAQdATFay6x2u19PsF5P7YDj2WW9KproKIqAMAqHR +cnkiebiIkAQfFgoAOBYhBMlchkt3X3HQL1LO9r0HuCiy91BxBQJmRMVDFwzAEaD/ +RZC7YSLt7248VC1yfMdoaXc0AgcAAAoJEL0HuCiy91BxhCMBAKtymj/0Q/XDKO3c +9mlz5ycll8MfT/a6H2WFWGV6L9SEAPwN3/n4qR9bBiReT9Xo3q58e2efoXoFXz86 +BXv1rCYJAYiQBB8WCgA4FiEEyVyGS3dfcdAvUs72vQe4KLL3UHEFAmZExUMXDIAR +I/00ekGUKbrM1ecta8R3gFSs0kYCBwAACgkQvQe4KLL3UHFlBwEA1JL4yE/sWOKr +BfbHbUI4ffS6s+oh7sQxHEQy0pJN7LoA/RSKUIThOl5apOVhd/dYOPj+4aGZ9ZP1 +ARqeXIWYxpwCtB1rZXktd2l0aC1yZXZva2Vyc0BleGFtcGxlLm5ldIiZBBMWCgBB +FiEEyVyGS3dfcdAvUs72vQe4KLL3UHEFAmZExUMCGwMFCQWjmoAFCwkIBwICIgIG +FQoJCAsCBBYCAwECHgcCF4AACgkQvQe4KLL3UHFcjwD/ZpSNHKpGV99sKlxbzABg +msIGKLgcuFLUsT1QCV3yE4cA/iS4NW9Y508uUqoJfFH0lBpJ4+US6VQevVpRNe6N +KqYEuDgEZkTFQxIKKwYBBAGXVQEFAQEHQFDZXf9Y1Y6A00CDcYw8RO73idcn/d7B +ifTuBfYpXVJjAwEIB4h4BBgWCgAgFiEEyVyGS3dfcdAvUs72vQe4KLL3UHEFAmZE +xUMCGwwACgkQvQe4KLL3UHEZrQD/SWYkwCFtqaxbYQUcSJ+v2+wA28RUm7KrgJ+A +PsdiNJsBAIjUUsYlf+J/d4Ia0tccfzPBqVpyeWZ52bBD0pH/Eu8N +=JTll +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/json/t-json.c b/tests/json/t-json.c index 4fe74ed7..80aeaabc 100644 --- a/tests/json/t-json.c +++ b/tests/json/t-json.c @@ -41,6 +41,7 @@ static const char*tests[] = { "t-config", "t-version", "t-encrypt", "t-encrypt-sign", "t-sign", "t-verify", "t-decrypt-verify", "t-export", "t-createkey", "t-export-secret-info", "t-chunking", "t-sig-notations", + "t-keylist-revokers", /* For these two the order is important * as t-import imports the deleted key from t-delete */ "t-delete", "t-import", diff --git a/tests/json/t-keylist-revokers.in.json b/tests/json/t-keylist-revokers.in.json new file mode 100644 index 00000000..a3f28296 --- /dev/null +++ b/tests/json/t-keylist-revokers.in.json @@ -0,0 +1,5 @@ +{ + "op": "keylist", + "keys": [ "key-with-revokers@example.net" ], + "with-secret": false +} diff --git a/tests/json/t-keylist-revokers.out.json b/tests/json/t-keylist-revokers.out.json new file mode 100644 index 00000000..be8f633b --- /dev/null +++ b/tests/json/t-keylist-revokers.out.json @@ -0,0 +1,97 @@ +{ + "keys": [ + { + "revoked": false, + "expired": false, + "disabled": false, + "invalid": false, + "can_encrypt": true, + "can_sign": true, + "can_certify": true, + "can_authenticate": false, + "secret": false, + "is_qualified": false, + "protocol": "OpenPGP", + "fingerprint": "C95C864B775F71D02F52CEF6BD07B828B2F75071", + "owner_trust": "unknown", + "origin": 0, + "last_update": 0, + "subkeys": [ + { + "revoked": false, + "expired": false, + "disabled": false, + "invalid": false, + "can_encrypt": false, + "can_sign": true, + "can_certify": true, + "can_authenticate": false, + "secret": false, + "is_qualified": false, + "is_cardkey": false, + "is_de_vs": false, + "pubkey_algo_name": "EdDSA", + "pubkey_algo_string": "ed25519", + "keyid": "BD07B828B2F75071", + "curve": "ed25519", + "pubkey_algo": 303, + "length": 255, + "timestamp": 1715782979, + "expires": 1810390979 + }, + { + "revoked": false, + "expired": false, + "disabled": false, + "invalid": false, + "can_encrypt": true, + "can_sign": false, + "can_certify": false, + "can_authenticate": false, + "secret": false, + "is_qualified": false, + "is_cardkey": false, + "is_de_vs": false, + "pubkey_algo_name": "ECDH", + "pubkey_algo_string": "cv25519", + "keyid": "94C2E4C722CADAF9", + "curve": "cv25519", + "pubkey_algo": 302, + "length": 255, + "timestamp": 1715782979, + "expires": 0 + } + ], + "userids": [ + { + "revoked": false, + "invalid": false, + "validity": "unknown", + "uid": "key-with-revokers@example.net", + "name": "", + "email": "key-with-revokers@example.net", + "comment": "", + "address": "key-with-revokers@example.net", + "origin": 0, + "last_update": 0 + } + ], + "revocation_keys": [ + { + "sensitive": true, + "fingerprint": "A0FF4590BB6122EDEF6E3C542D727CC768697734", + "pubkey_algo_name": "DSA", + "pubkey_algo": 17, + "key_class": 192 + }, + { + "sensitive": false, + "fingerprint": "23FD347A419429BACCD5E72D6BC4778054ACD246", + "pubkey_algo_name": "DSA", + "pubkey_algo": 17, + "key_class": 128 + } + ] + } + ] +} diff --git a/tests/run-keylist.c b/tests/run-keylist.c index a9d4b6aa..9ecf380b 100644 --- a/tests/run-keylist.c +++ b/tests/run-keylist.c @@ -308,9 +308,11 @@ main (int argc, char **argv) gpgme_user_id_t uid; gpgme_tofu_info_t ti; gpgme_key_sig_t ks; + gpgme_revocation_key_t revkey; int nuids; int nsub; int nsigs; + int nrevkeys; printf ("keyid : %s\n", key->subkeys?nonnull (key->subkeys->keyid):"?"); printf ("can_cap : %s%s%s%s\n", @@ -425,6 +427,13 @@ main (int argc, char **argv) } } + revkey = key->revocation_keys; + for (nrevkeys=0; revkey; revkey = revkey->next, nrevkeys++) + { + printf ("revkey%2d: %s\n", nrevkeys, revkey->fpr); + printf (" class: %x\n", revkey->key_class); + } + putchar ('\n'); if (import)