[PATCH] g10: implement gpg --quick-revuid

Daniel Kahn Gillmor dkg at fifthhorseman.net
Fri Jun 17 00:05:57 CEST 2016


* 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, doc/whats-new-in-2.1.txt: document --quick-revuid
* tests/openpgp/quick-key-manipulation.test: add simple test of
  --quick-* commands

--

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 <dkg at fifthhorseman.net>
---
 doc/gpg.texi                              |   9 +
 doc/whats-new-in-2.1.txt                  |  14 ++
 g10/gpg.c                                 |  17 ++
 g10/keyedit.c                             | 268 ++++++++++++++++++++++--------
 g10/main.h                                |   3 +
 g10/revoke.c                              |  10 ++
 tests/openpgp/Makefile.am                 |   1 +
 tests/openpgp/quick-key-manipulation.test |  70 ++++++++
 8 files changed, 327 insertions(+), 65 deletions(-)
 create mode 100755 tests/openpgp/quick-key-manipulation.test

diff --git a/doc/gpg.texi b/doc/gpg.texi
index 6437b90..92ff835 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.
 
+ at item --quick-revuid  @var{user-id} @var{user-id-to-revoke}
+ at 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/doc/whats-new-in-2.1.txt b/doc/whats-new-in-2.1.txt
index 6c46b04..d8b9ae1 100644
--- a/doc/whats-new-in-2.1.txt
+++ b/doc/whats-new-in-2.1.txt
@@ -437,6 +437,20 @@ https://gnupg.org/faq/whats-new-in-2.1.html
   │ uid               [ unknown] EdDSA sample key 1
   └────
 
+  Since version 2.1.14 it possible to revoke a user id on an existing
+  key:
+
+  ┌────
+  │ $ gpg2 -k 8CFDE12197965A9A
+  │ pub   ed25519/8CFDE12197965A9A 2014-08-19
+  │ uid               [ unknown] Sample 2 <me at example.org>
+  │ uid               [ unknown] EdDSA sample key 1
+  │ $ gpg2 --quick-revuid 8CFDE12197965A9A 'EdDSA sample key 1'
+  │ $ gpg2 -k 8CFDE12197965A9A
+  │ pub   ed25519/8CFDE12197965A9A 2014-08-19
+  │ uid               [ unknown] Sample 2 <me at example.org>
+  └────
+
 
 1.6 Improved Pinentry support
 ─────────────────────────────
diff --git a/g10/gpg.c b/g10/gpg.c
index 1f2d416..f6013f3 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,
@@ -430,6 +431,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")),
@@ -2432,6 +2435,7 @@ main (int argc, char **argv)
 	  case aQuickKeygen:
 	  case aQuickAddUid:
 	  case aQuickAddKey:
+	  case aQuickRevUid:
 	  case aExportOwnerTrust:
 	  case aImportOwnerTrust:
           case aRebuildKeydbCaches:
@@ -3777,6 +3781,7 @@ main (int argc, char **argv)
       case aQuickKeygen:
       case aQuickAddUid:
       case aQuickAddKey:
+      case aQuickRevUid:
       case aFullKeygen:
       case aKeygen:
       case aImport:
@@ -4196,6 +4201,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 d05ea5d..2f106dd 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,107 @@ 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 &&
+          0 == memcmp (node->pkt->pkt.user_id->name, uidtorev, revlen))
+        {
+          struct revocation_reason_info *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 +6210,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 +6325,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 7b716ff..b11a853 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 218ca59..15a91ac 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/Makefile.am b/tests/openpgp/Makefile.am
index bb1047d..020df78 100644
--- a/tests/openpgp/Makefile.am
+++ b/tests/openpgp/Makefile.am
@@ -52,6 +52,7 @@ TESTS = version.test mds.test \
 	signencrypt.test signencrypt-dsa.test \
 	armsignencrypt.test armdetach.test \
 	armdetachm.test detachm.test genkey1024.test \
+	quick-key-manipulation.test \
 	conventional.test conventional-mdc.test \
 	multisig.test verify.test armor.test \
 	import.test ecc.test 4gb-packet.test \
diff --git a/tests/openpgp/quick-key-manipulation.test b/tests/openpgp/quick-key-manipulation.test
new file mode 100755
index 0000000..4185601
--- /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 <alpha at example.net>"
+bravo="Bravo <bravo at example.net>"
+
+$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!"
-- 
2.8.1




More information about the Gnupg-devel mailing list