From 243f9176e799b2328f2e5bed93099bfc474fdc5a Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 28 Oct 2020 17:06:27 +0100 Subject: [PATCH] gpg: New command --quick-revoke-sig * g10/gpg.c (enum cmd_and_opt_values): Add aQuickRevSig. (opts): Add --quick-revoke-sig. (main): Implement. * g10/keyedit.c (quick_find_keyblock): Add arg 'want_secret' and adjust all callers. (keyedit_quick_revsig): new. * g10/revoke.c (get_default_sig_revocation_reason): New. * g10/keylist.c (cmp_signodes): Make global. -- GnuPG-bug-id: 5093 --- doc/gpg.texi | 11 +++ g10/gpg.c | 20 ++++ g10/keyedit.c | 246 ++++++++++++++++++++++++++++++++++++++++++++++++-- g10/keyedit.h | 2 + g10/keylist.c | 2 +- g10/main.h | 6 +- g10/revoke.c | 10 ++ 7 files changed, 288 insertions(+), 9 deletions(-) diff --git a/doc/gpg.texi b/doc/gpg.texi index ccb0689f6..48e52e98f 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -1147,6 +1147,17 @@ you want to specify a different revocation reason, or to supply supplementary revocation text, you should use the interactive sub-command @code{revuid} of @option{--edit-key}. +@item --quick-revoke-sig @var{fpr} @var{signing-fpr} [@var{names}] +@opindex quick-revoke-sig +This command revokes the key signatures made by @var{signing-fpr} from +the key specified by the fingerprint @var{fpr}. With @var{names} +given only the signatures on user ids of the key matching any of the +given names are affected (see @option{--quick-sign-key}). If a +revocation already exists a notice is printed instead of creating a +new revocation; no error is returned in this case. Note that key +signature revocations may be superseded by a newer key signature and +in turn again revoked. + @item --quick-set-primary-uid @var{user-id} @var{primary-user-id} @opindex quick-set-primary-uid This command sets or updates the primary user ID flag on an existing diff --git a/g10/gpg.c b/g10/gpg.c index 910977a6c..ae890ca9e 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -129,6 +129,7 @@ enum cmd_and_opt_values aLSignKey, aQuickSignKey, aQuickLSignKey, + aQuickRevSig, aQuickAddUid, aQuickAddKey, aQuickRevUid, @@ -499,6 +500,8 @@ static gpgrt_opt_t opts[] = { N_("quickly sign a key")), ARGPARSE_c (aQuickLSignKey, "quick-lsign-key", N_("quickly sign a key locally")), + ARGPARSE_c (aQuickRevSig, "quick-revoke-sig" , + N_("quickly revoke a key signature")), ARGPARSE_c (aSignKey, "sign-key" ,N_("sign a key")), ARGPARSE_c (aLSignKey, "lsign-key" ,N_("sign a key locally")), ARGPARSE_c (aEditKey, "edit-key" ,N_("sign or edit a key")), @@ -2631,6 +2634,7 @@ main (int argc, char **argv) case aSign: case aQuickSignKey: case aQuickLSignKey: + case aQuickRevSig: case aSignKey: case aLSignKey: case aStore: @@ -4446,6 +4450,22 @@ main (int argc, char **argv) } break; + case aQuickRevSig: + { + const char *userid, *siguserid; + + if (argc < 2) + wrong_args ("--quick-revoke-sig USER-ID SIG-USER-ID [userids]"); + userid = *argv++; argc--; + siguserid = *argv++; argc--; + sl = NULL; + for( ; argc; argc--, argv++) + append_to_strlist2 (&sl, *argv, utf8_strings); + keyedit_quick_revsig (ctrl, userid, siguserid, sl); + free_strlist (sl); + } + break; + case aSignKey: if( argc != 1 ) wrong_args("--sign-key user-id"); diff --git a/g10/keyedit.c b/g10/keyedit.c index 19fd0a1fc..9f4aad24a 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -2278,7 +2278,7 @@ leave: * Returns on success the key database handle at R_KDBHD and the * keyblock at R_KEYBLOCK. */ static gpg_error_t -quick_find_keyblock (ctrl_t ctrl, const char *username, +quick_find_keyblock (ctrl_t ctrl, const char *username, int want_secret, KEYDB_HANDLE *r_kdbhd, kbnode_t *r_keyblock) { gpg_error_t err; @@ -2321,7 +2321,7 @@ quick_find_keyblock (ctrl_t ctrl, const char *username, err = 0; keydb_pop_found_state (kdbhd); - if (!err) + if (!err && want_secret) { /* We require the secret primary key to set the primary UID. */ node = find_kbnode (keyblock, PKT_PUBLIC_KEY); @@ -2379,7 +2379,7 @@ keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid) #endif /* Search the key; we don't want the whole getkey stuff here. */ - err = quick_find_keyblock (ctrl, username, &kdbhd, &keyblock); + err = quick_find_keyblock (ctrl, username, 1, &kdbhd, &keyblock); if (err) goto leave; @@ -2422,7 +2422,7 @@ keyedit_quick_revuid (ctrl_t ctrl, const char *username, const char *uidtorev) #endif /* Search the key; we don't want the whole getkey stuff here. */ - err = quick_find_keyblock (ctrl, username, &kdbhd, &keyblock); + err = quick_find_keyblock (ctrl, username, 1, &kdbhd, &keyblock); if (err) goto leave; @@ -2502,7 +2502,7 @@ keyedit_quick_set_primary (ctrl_t ctrl, const char *username, check_trustdb_stale (ctrl); #endif - err = quick_find_keyblock (ctrl, username, &kdbhd, &keyblock); + err = quick_find_keyblock (ctrl, username, 1, &kdbhd, &keyblock); if (err) goto leave; @@ -2763,6 +2763,240 @@ keyedit_quick_sign (ctrl_t ctrl, const char *fpr, strlist_t uids, } +/* Unattended revocation of a key signatures. USERNAME specifies the + * key; this should best be a fingerprint. SIGTOREV is the user-id of + * the key for which the key signature shall be removed. Only + * non-self-signatures can be removed with this functions. If + * AFFECTED_UIDS is not NULL only the key signatures on these user-ids + * are revoked. */ +void +keyedit_quick_revsig (ctrl_t ctrl, const char *username, const char *sigtorev, + strlist_t affected_uids) +{ + gpg_error_t err; + int no_signing_key = 0; + KEYDB_HANDLE kdbhd = NULL; + kbnode_t keyblock = NULL; + PKT_public_key *primarypk; /* Points into KEYBLOCK. */ + u32 *primarykid; + PKT_public_key *pksigtorev = NULL; + u32 *pksigtorevkid; + kbnode_t node, n; + int skip_remaining; + int consider_sig; + strlist_t sl; + struct sign_attrib attrib = { 0 }; + +#ifdef HAVE_W32_SYSTEM + /* See keyedit_menu for why we need this. */ + check_trustdb_stale (ctrl); +#endif + + /* Search the key; we don't want the whole getkey stuff here. Noet + * that we are looking for the public key here. */ + err = quick_find_keyblock (ctrl, username, 0, &kdbhd, &keyblock); + if (err) + goto leave; + log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY + || keyblock->pkt->pkttype == PKT_SECRET_KEY); + primarypk = keyblock->pkt->pkt.public_key; + primarykid = pk_keyid (primarypk); + + /* Get the signing key we want to revoke. This must be one of our + * signing keys. We will compare only the keyid because we don't + * assume that we have duplicated keyids on our own secret keys. If + * a there is a duplicated one we will notice this when creating the + * revocation. */ + pksigtorev = xtrycalloc (1, sizeof *pksigtorev); + if (!pksigtorev) + { + err = gpg_error_from_syserror (); + goto leave; + } + pksigtorev->req_usage = PUBKEY_USAGE_CERT; + err = getkey_byname (ctrl, NULL, pksigtorev, sigtorev, 1, NULL); + if (err) + { + no_signing_key = 1; + goto leave; + } + pksigtorevkid = pk_keyid (pksigtorev); + + /* Find the signatures we want to revoke and set a mark. */ + skip_remaining = consider_sig = 0; + for (node = keyblock; node; node = node->next) + { + node->flag &= ~NODFLG_MARK_A; + if (skip_remaining) + ; + else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + skip_remaining = 1; + else if (node->pkt->pkttype == PKT_USER_ID) + { + PKT_user_id *uid = node->pkt->pkt.user_id; + + consider_sig = !affected_uids; + for (sl = affected_uids; !consider_sig && sl; sl = sl->next) + { + const char *name = sl->d; + + if (uid->attrib_data) + ; + else if (*name == '=' + && strlen (name+1) == uid->len + && !memcmp (uid->name, name + 1, uid->len)) + { /* Exact match. */ + consider_sig = 1; + } + else if (ascii_memistr (uid->name, uid->len, + *name == '*'? name+1:name)) + { /* Case-insensitive substring match. */ + consider_sig = 1; + } + } + } + else if (node->pkt->pkttype == PKT_SIGNATURE) + { + /* We need to sort the signatures so that we can figure out + * whether the key signature has been revoked or the + * revocation has been superseded by a new key + * signature. */ + PKT_signature *sig; + unsigned int sigcount = 0; + kbnode_t *sigarray; + + /* Allocate an array large enogh for all signatures. */ + for (n=node; n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next) + sigcount++; + sigarray = xtrycalloc (sigcount, sizeof *sigarray); + if (!sigarray) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Now fill the array with signatures we are interested in. + * We also move NODE forward to the end. */ + sigcount = 0; + for (n=node; n && n->pkt->pkttype == PKT_SIGNATURE; node=n, n=n->next) + { + sig = node->pkt->pkt.signature; + if (!keyid_cmp (primarykid, sig->keyid)) + continue; /* Ignore self-signatures. */ + + if (keyid_cmp (pksigtorevkid, sig->keyid)) + continue; /* Ignore non-matching signatures. */ + + n->flag &= ~NODFLG_MARK_B; /* Clear flag used by cm_signode. */ + sigarray[sigcount++] = n; + } + + if (sigcount) + { + qsort (sigarray, sigcount, sizeof *sigarray, cmp_signodes); + + /* log_debug ("Sorted signatures:\n"); */ + /* for (idx=0; idx < sigcount; idx++) */ + /* { */ + /* sig = sigarray[idx]->pkt->pkt.signature; */ + /* log_debug ("%s 0x%02x %s\n", keystr (sig->keyid), */ + /* sig->sig_class, datestr_from_sig (sig)); */ + /* } */ + sig = sigarray[sigcount-1]->pkt->pkt.signature; + if ((consider_sig || !affected_uids) && IS_UID_REV (sig)) + { + if (!opt.quiet) + log_info ("sig by %s already revoked at %s\n", + keystr (sig->keyid), datestr_from_sig (sig)); + } + else if ((consider_sig && IS_UID_SIG (sig)) + || (!affected_uids && IS_KEY_SIG (sig))) + node->flag |= NODFLG_MARK_A; /* Select signature. */ + } + + xfree (sigarray); + } + } + + /* Check whether any signatures were done by the given key. We do + * not return an error if none were found. */ + for (node = keyblock; node; node = node->next) + if ((node->flag & NODFLG_MARK_A)) + break; + if (!node) + { + if (opt.verbose) + log_info (_("Not signed by you.\n")); + err = 0; + goto leave; + } + + /* Revoke all marked signatures. */ + attrib.reason = get_default_sig_revocation_reason (); + + reloop: /* (we must repeat because we are modifying the list) */ + for (node = keyblock; node; node = node->next) + { + kbnode_t unode; + PKT_signature *sig; + PACKET *pkt; + + if (!(node->flag & NODFLG_MARK_A)) + continue; + node->flag &= ~NODFLG_MARK_A; + + if (IS_KEY_SIG (node->pkt->pkt.signature)) + unode = NULL; + else + { + unode = find_prev_kbnode (keyblock, node, PKT_USER_ID); + log_assert (unode); + } + + attrib.non_exportable = !node->pkt->pkt.signature->flags.exportable; + + err = make_keysig_packet (ctrl, &sig, primarypk, + unode? unode->pkt->pkt.user_id : NULL, + NULL, pksigtorev, 0x30, 0, 0, + sign_mk_attrib, &attrib, NULL); + if (err) + { + log_error ("signing failed: %s\n", gpg_strerror (err)); + goto leave; + } + + pkt = xmalloc_clear (sizeof *pkt); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + if (unode) + insert_kbnode (unode, new_kbnode (pkt), 0); + goto reloop; + } + + err = keydb_update_keyblock (ctrl, kdbhd, keyblock); + if (err) + { + log_error (_("update failed: %s\n"), gpg_strerror (err)); + goto leave; + } + revalidation_mark (ctrl); + + leave: + if (err) + { + log_error (_("revoking the key signature failed: %s\n"), + gpg_strerror (err)); + if (no_signing_key) + print_further_info ("error getting key used to make the key signature"); + write_status_error ("keyedit.revoke.sig", err); + } + release_revocation_reason_info (attrib.reason); + free_public_key (pksigtorev); + release_kbnode (keyblock); + keydb_release (kdbhd); +} + + /* Unattended subkey creation function. * */ @@ -5984,7 +6218,7 @@ core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node, memset (&attrib, 0, sizeof attrib); /* should not need to cast away const here; but revocation_reason_build_cb needs to take a non-const - void* in order to meet the function signutare for the + void* in order to meet the function signature for the mksubpkt argument to make_keysig_packet */ attrib.reason = (struct revocation_reason_info *)reason; diff --git a/g10/keyedit.h b/g10/keyedit.h index 89c408b63..b6e5b58b9 100644 --- a/g10/keyedit.h +++ b/g10/keyedit.h @@ -48,6 +48,8 @@ void keyedit_quick_revuid (ctrl_t ctrl, const char *username, const char *uidtorev); void keyedit_quick_sign (ctrl_t ctrl, const char *fpr, strlist_t uids, strlist_t locusr, int local); +void keyedit_quick_revsig (ctrl_t ctrl, const char *username, + const char *sigtorev, strlist_t affected_uids); void keyedit_quick_set_expire (ctrl_t ctrl, const char *fpr, const char *expirestr, char **subkeyfprs); diff --git a/g10/keylist.c b/g10/keylist.c index cb3b61e03..e222259ac 100644 --- a/g10/keylist.c +++ b/g10/keylist.c @@ -940,7 +940,7 @@ dump_attribs (const PKT_user_id *uid, PKT_public_key *pk) /* Order two signatures. We first order by keyid and then by creation * time. */ -static int +int cmp_signodes (const void *av, const void *bv) { const kbnode_t an = *(const kbnode_t *)av; diff --git a/g10/main.h b/g10/main.h index 7b5754648..7f3752aac 100644 --- a/g10/main.h +++ b/g10/main.h @@ -457,8 +457,9 @@ int gen_desig_revoke (ctrl_t ctrl, const char *uname, strlist_t locusr); int revocation_reason_build_cb( PKT_signature *sig, void *opaque ); struct revocation_reason_info * ask_revocation_reason( int key_rev, int cert_rev, int hint ); -struct revocation_reason_info * get_default_uid_revocation_reason(void); -void release_revocation_reason_info( struct revocation_reason_info *reason ); +struct revocation_reason_info * get_default_uid_revocation_reason (void); +struct revocation_reason_info * get_default_sig_revocation_reason (void); +void release_revocation_reason_info (struct revocation_reason_info *reason); /*-- keylist.c --*/ void public_key_list (ctrl_t ctrl, strlist_t list, @@ -468,6 +469,7 @@ void print_subpackets_colon(PKT_signature *sig); void reorder_keyblock (KBNODE keyblock); void list_keyblock_direct (ctrl_t ctrl, kbnode_t keyblock, int secret, int has_secret, int fpr, int no_validity); +int cmp_signodes (const void *av, const void *bv); void print_fingerprint (ctrl_t ctrl, estream_t fp, PKT_public_key *pk, int mode); void print_revokers (estream_t fp, PKT_public_key *pk); diff --git a/g10/revoke.c b/g10/revoke.c index 5262c17bf..c0a003b6f 100644 --- a/g10/revoke.c +++ b/g10/revoke.c @@ -889,6 +889,16 @@ get_default_uid_revocation_reason(void) return reason; } +struct revocation_reason_info * +get_default_sig_revocation_reason(void) +{ + struct revocation_reason_info *reason; + reason = xmalloc( sizeof *reason ); + reason->code = 0; /* No specific reason given. */ + reason->desc = strdup(""); /* no text */ + return reason; +} + void release_revocation_reason_info( struct revocation_reason_info *reason ) {