aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Kahn Gillmor <[email protected]>2016-06-16 22:05:57 +0000
committerWerner Koch <[email protected]>2016-06-30 09:45:13 +0000
commit55d112eeb0743e90be46d15dbae67368ee7d4b50 (patch)
treeb1003a33d68ffa514179c391313ba693a93cb640
parenttools: Add gpg-wks-client and gpg-wks-server. (diff)
downloadgnupg-55d112eeb0743e90be46d15dbae67368ee7d4b50.tar.gz
gnupg-55d112eeb0743e90be46d15dbae67368ee7d4b50.zip
g10: Implement gpg --quick-revuid
* g10/revoke.c (get_default_uid_revocation_reason): New. * g10/keyedit.c (menu_revuid): Break out creation of uid revocation into new function core_revuid. * g10/keyedit.c (keyedit_quick_revuid): New. Selects key and uid, invokes core_revuid. * g10/gpg.c (main): Handle --quick-revuid argument. * doc/gpg.texi: Document --quick-revuid. -- This functionality is a counterpart to --quick-adduid, and will be useful for projects that depend programmatically on gpg to revoke user IDs (one such example is "monkeysphere-host revoke-servicename"). Signed-off-by: Daniel Kahn Gillmor <[email protected]> - Minor re-indentation work. - Changed a "0 == memcmp" to "!memcmp" - Removed tests/openpgp/quick-key-manipulation.test from the Makefile. This test needs to be converted to gpgscm. - Removed example from whats-new-in-2.1.txt because that is generated. Signed-off-by: Werner Koch <[email protected]>
-rw-r--r--doc/gpg.texi9
-rw-r--r--g10/gpg.c17
-rw-r--r--g10/keyedit.c271
-rw-r--r--g10/main.h3
-rw-r--r--g10/revoke.c10
-rwxr-xr-xtests/openpgp/quick-key-manipulation.test70
6 files changed, 315 insertions, 65 deletions
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 <[email protected]>"
+bravo="Bravo <[email protected]>"
+
+$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!"