diff --git a/doc/gpg.texi b/doc/gpg.texi index b8fda9638..6f0249ab1 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -1041,6 +1041,15 @@ the interactive sub-command @code{adduid} of @option{--edit-key} the white space removed, it is expected to be UTF-8 encoded, and no checks on its form are applied. +@item --quick-revuid @var{user-id} @var{user-id-to-revoke} +@opindex quick-revuid +This command revokes a User ID on an existing key. It cannot be used +to revoke the last User ID on key (some non-revoked User ID must +remain), with revocation reason ``User ID is no longer valid''. If +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 --passwd @var{user_id} @opindex passwd Change the passphrase of the secret key belonging to the certificate diff --git a/g10/gpg.c b/g10/gpg.c index 9750c57ce..b1d6c3464 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -118,6 +118,7 @@ enum cmd_and_opt_values aQuickLSignKey, aQuickAddUid, aQuickAddKey, + aQuickRevUid, aListConfig, aListGcryptConfig, aGPGConfList, @@ -431,6 +432,8 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_c (aQuickAddUid, "quick-adduid", N_("quickly add a new user-id")), ARGPARSE_c (aQuickAddKey, "quick-addkey", "@"), + ARGPARSE_c (aQuickRevUid, "quick-revuid", + N_("quickly revoke a user-id")), ARGPARSE_c (aFullKeygen, "full-gen-key" , N_("full featured key pair generation")), ARGPARSE_c (aGenRevoke, "gen-revoke",N_("generate a revocation certificate")), @@ -2434,6 +2437,7 @@ main (int argc, char **argv) case aQuickKeygen: case aQuickAddUid: case aQuickAddKey: + case aQuickRevUid: case aExportOwnerTrust: case aImportOwnerTrust: case aRebuildKeydbCaches: @@ -3785,6 +3789,7 @@ main (int argc, char **argv) case aQuickKeygen: case aQuickAddUid: case aQuickAddKey: + case aQuickRevUid: case aFullKeygen: case aKeygen: case aImport: @@ -4204,6 +4209,18 @@ main (int argc, char **argv) } break; + case aQuickRevUid: + { + const char *uid, *uidtorev; + + if (argc != 2) + wrong_args ("--quick-revuid USER-ID USER-ID-TO-REVOKE"); + uid = *argv++; argc--; + uidtorev = *argv++; argc--; + keyedit_quick_revuid (ctrl, uid, uidtorev); + } + break; + case aFastImport: opt.import_options |= IMPORT_FAST; case aImport: diff --git a/g10/keyedit.c b/g10/keyedit.c index d05ea5d01..65f671eee 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -87,6 +87,9 @@ static int real_uids_left (KBNODE keyblock); static int count_selected_keys (KBNODE keyblock); static int menu_revsig (KBNODE keyblock); static int menu_revuid (ctrl_t ctrl, kbnode_t keyblock); +static int core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node, + const struct revocation_reason_info *reason, + int *modified); static int menu_revkey (KBNODE pub_keyblock); static int menu_revsubkey (KBNODE pub_keyblock); #ifndef NO_TRUST_MODELS @@ -2937,6 +2940,110 @@ keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid) keydb_release (kdbhd); } +/* Unattended revokation of a keyid. USERNAME specifies the + key. UIDTOREV is the user id revoke from the key. */ +void +keyedit_quick_revuid (ctrl_t ctrl, const char *username, const char *uidtorev) +{ + gpg_error_t err; + KEYDB_HANDLE kdbhd = NULL; + KEYDB_SEARCH_DESC desc; + kbnode_t keyblock = NULL; + kbnode_t node; + int modified = 0; + size_t revlen; + +#ifdef HAVE_W32_SYSTEM + /* See keyedit_menu for why we need this. */ + check_trustdb_stale (); +#endif + + /* Search the key; we don't want the whole getkey stuff here. */ + kdbhd = keydb_new (); + if (!kdbhd) + { + /* Note that keydb_new has already used log_error. */ + goto leave; + } + + err = classify_user_id (username, &desc, 1); + if (!err) + err = keydb_search (kdbhd, &desc, 1, NULL); + if (!err) + { + err = keydb_get_keyblock (kdbhd, &keyblock); + if (err) + { + log_error (_("error reading keyblock: %s\n"), gpg_strerror (err)); + goto leave; + } + /* Now with the keyblock retrieved, search again to detect an + ambiguous specification. We need to save the found state so + that we can do an update later. */ + keydb_push_found_state (kdbhd); + err = keydb_search (kdbhd, &desc, 1, NULL); + if (!err) + err = gpg_error (GPG_ERR_AMBIGUOUS_NAME); + else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + err = 0; + keydb_pop_found_state (kdbhd); + + if (!err) + { + /* We require the secret primary key to revoke a UID. */ + node = find_kbnode (keyblock, PKT_PUBLIC_KEY); + if (!node) + BUG (); + err = agent_probe_secret_key (ctrl, node->pkt->pkt.public_key); + } + } + if (err) + { + log_error (_("secret key \"%s\" not found: %s\n"), + username, gpg_strerror (err)); + goto leave; + } + + fix_keyblock (&keyblock); + setup_main_keyids (keyblock); + + revlen = strlen (uidtorev); + /* find the right UID */ + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_USER_ID + && revlen == node->pkt->pkt.user_id->len + && !memcmp (node->pkt->pkt.user_id->name, uidtorev, revlen)) + { + struct revocation_reason_info *reason; + + reason = get_default_uid_revocation_reason (); + err = core_revuid (ctrl, keyblock, node, reason, &modified); + release_revocation_reason_info (reason); + if (err) + { + log_error (_("User ID revocation failed: %s\n"), + gpg_strerror (err)); + goto leave; + } + err = keydb_update_keyblock (kdbhd, keyblock); + if (err) + { + log_error (_("update failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + if (update_trust) + revalidation_mark (); + goto leave; + } + } + + leave: + release_kbnode (keyblock); + keydb_release (kdbhd); +} + /* Find a keyblock by fingerprint because only this uniquely * identifies a key and may thus be used to select a key for @@ -6106,6 +6213,95 @@ reloop: /* (must use this, because we are modifing the list) */ } +/* return 0 if revocation of NODE (which must be a User ID) was + successful, non-zero if there was an error. *modified will be set + to 1 if a change was made. */ +static int +core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node, + const struct revocation_reason_info *reason, int *modified) +{ + PKT_public_key *pk = keyblock->pkt->pkt.public_key; + gpg_error_t rc; + + if (node->pkt->pkttype != PKT_USER_ID) + { + rc = gpg_error (GPG_ERR_NO_USER_ID); + write_status_error ("keysig", rc); + log_error (_("tried to revoke a non-user ID: %s\n"), gpg_strerror (rc)); + return 1; + } + else + { + PKT_user_id *uid = node->pkt->pkt.user_id; + + if (uid->is_revoked) + { + char *user = utf8_to_native (uid->name, uid->len, 0); + log_info (_("user ID \"%s\" is already revoked\n"), user); + xfree (user); + } + else + { + PACKET *pkt; + PKT_signature *sig; + struct sign_attrib attrib; + u32 timestamp = make_timestamp (); + + if (uid->created >= timestamp) + { + /* Okay, this is a problem. The user ID selfsig was + created in the future, so we need to warn the user and + set our revocation timestamp one second after that so + everything comes out clean. */ + + log_info (_("WARNING: a user ID signature is dated %d" + " seconds in the future\n"), + uid->created - timestamp); + + timestamp = uid->created + 1; + } + + 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 signtuare for the + mksubpkt argument to make_keysig_packet */ + attrib.reason = (struct revocation_reason_info *)reason; + + rc = make_keysig_packet (&sig, pk, uid, NULL, pk, 0x30, 0, + timestamp, 0, + sign_mk_attrib, &attrib, NULL); + if (rc) + { + write_status_error ("keysig", rc); + log_error (_("signing failed: %s\n"), gpg_strerror (rc)); + return 1; + } + else + { + pkt = xmalloc_clear (sizeof *pkt); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + insert_kbnode (node, new_kbnode (pkt), 0); + +#ifndef NO_TRUST_MODELS + /* If the trustdb has an entry for this key+uid then the + trustdb needs an update. */ + if (!update_trust + && ((get_validity (ctrl, pk, uid, NULL, 0) & TRUST_MASK) + >= TRUST_UNDEFINED)) + update_trust = 1; +#endif /*!NO_TRUST_MODELS*/ + + node->pkt->pkt.user_id->is_revoked = 1; + if (modified) + *modified = 1; + } + } + return 0; + } +} + /* Revoke a user ID (i.e. revoke a user ID selfsig). Return true if keyblock changed. */ static int @@ -6132,75 +6328,20 @@ menu_revuid (ctrl_t ctrl, kbnode_t pub_keyblock) goto leave; } - reloop: /* (better this way because we are modifing the keyring) */ + reloop: /* (better this way because we are modifying the keyring) */ for (node = pub_keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID && (node->flag & NODFLG_SELUID)) { - PKT_user_id *uid = node->pkt->pkt.user_id; - - if (uid->is_revoked) - { - char *user = utf8_to_native (uid->name, uid->len, 0); - log_info (_("user ID \"%s\" is already revoked\n"), user); - xfree (user); - } - else - { - PACKET *pkt; - PKT_signature *sig; - struct sign_attrib attrib; - u32 timestamp = make_timestamp (); - - if (uid->created >= timestamp) - { - /* Okay, this is a problem. The user ID selfsig was - created in the future, so we need to warn the user and - set our revocation timestamp one second after that so - everything comes out clean. */ - - log_info (_("WARNING: a user ID signature is dated %d" - " seconds in the future\n"), - uid->created - timestamp); - - timestamp = uid->created + 1; - } - - memset (&attrib, 0, sizeof attrib); - attrib.reason = reason; - + int modified = 0; + rc = core_revuid (ctrl, pub_keyblock, node, reason, &modified); + if (rc) + goto leave; + if (modified) + { node->flag &= ~NODFLG_SELUID; - - rc = make_keysig_packet (&sig, pk, uid, NULL, pk, 0x30, 0, - timestamp, 0, - sign_mk_attrib, &attrib, NULL); - if (rc) - { - write_status_error ("keysig", rc); - log_error (_("signing failed: %s\n"), gpg_strerror (rc)); - goto leave; - } - else - { - pkt = xmalloc_clear (sizeof *pkt); - pkt->pkttype = PKT_SIGNATURE; - pkt->pkt.signature = sig; - insert_kbnode (node, new_kbnode (pkt), 0); - -#ifndef NO_TRUST_MODELS - /* If the trustdb has an entry for this key+uid then the - trustdb needs an update. */ - if (!update_trust - && (get_validity (ctrl, pk, uid, NULL, 0) & TRUST_MASK) >= - TRUST_UNDEFINED) - update_trust = 1; -#endif /*!NO_TRUST_MODELS*/ - - changed = 1; - node->pkt->pkt.user_id->is_revoked = 1; - - goto reloop; - } - } + changed = 1; + goto reloop; + } } if (changed) diff --git a/g10/main.h b/g10/main.h index e6f20700a..322f43c53 100644 --- a/g10/main.h +++ b/g10/main.h @@ -289,6 +289,8 @@ void keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid); void keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr, const char *usagestr, const char *expirestr); +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 show_basic_key_info (KBNODE keyblock); @@ -407,6 +409,7 @@ 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 ); /*-- keylist.c --*/ diff --git a/g10/revoke.c b/g10/revoke.c index 218ca59f0..15a91acbf 100644 --- a/g10/revoke.c +++ b/g10/revoke.c @@ -862,6 +862,16 @@ ask_revocation_reason( int key_rev, int cert_rev, int hint ) return reason; } +struct revocation_reason_info * +get_default_uid_revocation_reason(void) +{ + struct revocation_reason_info *reason; + reason = xmalloc( sizeof *reason ); + reason->code = 0x20; /* uid is no longer valid */ + reason->desc = strdup(""); /* no text */ + return reason; +} + void release_revocation_reason_info( struct revocation_reason_info *reason ) { diff --git a/tests/openpgp/quick-key-manipulation.test b/tests/openpgp/quick-key-manipulation.test new file mode 100755 index 000000000..4185601bb --- /dev/null +++ b/tests/openpgp/quick-key-manipulation.test @@ -0,0 +1,70 @@ +#!/bin/sh +# Copyright 2016 Free Software Foundation, Inc. +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. This file is +# distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY, to the extent permitted by law; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +. $srcdir/defs.inc || exit 3 + +export PINENTRY_USER_DATA=test + +alpha="Alpha " +bravo="Bravo " + +$GPG --with-colons --with-fingerprint --list-secret-keys ="$alpha" && + error "User ID '$alpha'exists when it should not!" +$GPG --with-colons --with-fingerprint --list-secret-keys ="$bravo" && + error "User ID '$bravo' exists when it should not!" + +#info verify that key creation works +$GPG --quick-gen-key "$alpha" || \ + error "failed to generate key" + +fpr=$($GPG --with-colons --with-fingerprint --list-secret-keys ="$alpha" | \ + grep '^fpr:' | cut -f10 -d: | head -n1) + +$GPG --check-trustdb + +cleanup() { + $GPG --batch --yes --delete-secret-key "0x$fpr" + $GPG --batch --yes --delete-key "0x$fpr" +} + +count_uids_of_secret() { + if ! [ $($GPG --with-colons --list-secret-keys ="$1" | \ + grep -c '^uid:u:') = "$2" ] ; then + cleanup + error "wrong number of user IDs for '$1' after $3" + fi +} + +count_uids_of_secret "$alpha" 1 "key generation" + +#info verify that we can add a user ID +if ! $GPG --quick-adduid ="$alpha" "$bravo" ; then + cleanup + error "failed to add user id" +fi + +$GPG --check-trustdb + +count_uids_of_secret "$alpha" 2 "adding User ID" +count_uids_of_secret "$bravo" 2 "adding User ID" + +#info verify that we can revoke a user ID +if ! $GPG --quick-revuid ="$bravo" "$alpha"; then + cleanup + error "failed to revoke user id" +fi + +$GPG --check-trustdb + +count_uids_of_secret "$bravo" 1 "revoking user ID" + +cleanup + +! $GPG --with-colons --list-secret-keys ="$bravo" || + error "key still exists when it should not!"