gpg: Allow deletion of subkeys with --delete-[secret-]key.
authorWerner Koch <wk@gnupg.org>
Mon, 27 May 2019 08:40:38 +0000 (10:40 +0200)
committerDaniel Kahn Gillmor <dkg@fifthhorseman.net>
Thu, 22 Aug 2019 19:11:59 +0000 (20:11 +0100)
* common/userids.c (classify_user_id): Do not set the EXACT flag in
the default case.
* g10/export.c (exact_subkey_match_p): Make static,
* g10/delkey.c (do_delete_key): Implement subkey only deleting.
--

GnuPG-bug-id: 4457
(cherry picked from commit d9b31d3a20b89a5ad7e9a2158b6da63a9a37fa8a)

Gbp-Pq: Topic from-2.2.16
Gbp-Pq: Name gpg-Allow-deletion-of-subkeys-with-delete-secret-key.patch

common/userids.c
doc/gpg.texi
g10/delkey.c
g10/export.c
g10/main.h

index 01f2cd84b212a7d21edd500f7f55ba0663add171..00f26b720e04e9a02ba624281ae844ba95bfaa00 100644 (file)
@@ -351,8 +351,10 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack)
         }
       else if (!hexprefix)
         {
-          /* The fingerprint in an X.509 listing is often delimited by
-             colons, so we try to single this case out. */
+          /* The fingerprint of an X.509 listing is often delimited by
+           * colons, so we try to single this case out.  Note that the
+           * OpenPGP bang suffix is not supported here.  */
+          desc->exact = 0;
           mode = 0;
           hexlength = strspn (s, ":0123456789abcdefABCDEF");
           if (hexlength == 59 && (!s[hexlength] || spacep (s+hexlength)))
@@ -414,7 +416,6 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack)
             }
           if (!mode) /* Default to substring search.  */
             {
-              desc->exact = 0;
               desc->u.name = s;
               mode = KEYDB_SEARCH_MODE_SUBSTR;
             }
index 7858baf82a66516b531f7187b6eab226cf3fc03c..9853f69e4359c6a2a6d1653f606f746dfe534493 100644 (file)
@@ -404,7 +404,10 @@ functionality is also available as the subcommand "passwd" with the
 @opindex delete-keys
 Remove key from the public keyring. In batch mode either @option{--yes} is
 required or the key must be specified by fingerprint. This is a
-safeguard against accidental deletion of multiple keys.
+safeguard against accidental deletion of multiple keys.  If the
+exclamation mark syntax is used with the fingerprint of a subkey only
+that subkey is deleted; if the exclamation mark is used with the
+fingerprint of the primary key the entire public key is deleted.
 
 @item --delete-secret-keys @var{name}
 @opindex delete-secret-keys
@@ -413,7 +416,10 @@ specified by fingerprint.  The option @option{--yes} can be used to
 advice gpg-agent not to request a confirmation.  This extra
 pre-caution is done because @command{@gpgname} can't be sure that the
 secret key (as controlled by gpg-agent) is only used for the given
-OpenPGP public key.
+OpenPGP public key.  If the exclamation mark syntax is used with the
+fingerprint of a subkey only the secret part of that subkey is
+deleted; if the exclamation mark is used with the fingerprint of the
+primary key only the secret part of the primary key is deleted.
 
 
 @item --delete-secret-and-public-key @var{name}
index 461a2c886cc82a10fb1c17d4faff442409370f85..e91acb0fc706d066844c6816ccc149d90c14f213 100644 (file)
@@ -1,7 +1,7 @@
 /* delkey.c - delete keys
  * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004,
  *               2005, 2006 Free Software Foundation, Inc.
- * Copyright (C) 2014 Werner Koch
+ * Copyright (C) 2014, 2019 Werner Koch
  *
  * This file is part of GnuPG.
  *
@@ -53,13 +53,15 @@ do_delete_key (ctrl_t ctrl, const char *username, int secret, int force,
   gpg_error_t err;
   kbnode_t keyblock = NULL;
   kbnode_t node, kbctx;
+  kbnode_t targetnode;
   KEYDB_HANDLE hd;
   PKT_public_key *pk = NULL;
   u32 keyid[2];
   int okay=0;
   int yes;
   KEYDB_SEARCH_DESC desc;
-  int exactmatch;
+  int exactmatch;  /* True if key was found by fingerprint.  */
+  int thiskeyonly; /* 0 = false, 1 = is primary key, 2 = is a subkey.  */
 
   *r_sec_avail = 0;
 
@@ -72,6 +74,7 @@ do_delete_key (ctrl_t ctrl, const char *username, int secret, int force,
   exactmatch = (desc.mode == KEYDB_SEARCH_MODE_FPR
                 || desc.mode == KEYDB_SEARCH_MODE_FPR16
                 || desc.mode == KEYDB_SEARCH_MODE_FPR20);
+  thiskeyonly = desc.exact;
   if (!err)
     err = keydb_search (hd, &desc, 1, NULL);
   if (err)
@@ -97,7 +100,35 @@ do_delete_key (ctrl_t ctrl, const char *username, int secret, int force,
       err = gpg_error (GPG_ERR_GENERAL);
       goto leave;
     }
-  pk = node->pkt->pkt.public_key;
+
+  /* If an operation only on a subkey is requested, find that subkey
+   * now.  */
+  if (thiskeyonly)
+    {
+      kbnode_t tmpnode;
+
+      for (kbctx=NULL; (tmpnode = walk_kbnode (keyblock, &kbctx, 0)); )
+        {
+          if (!(tmpnode->pkt->pkttype == PKT_PUBLIC_KEY
+                || tmpnode->pkt->pkttype == PKT_PUBLIC_SUBKEY))
+            continue;
+          if (exact_subkey_match_p (&desc, tmpnode))
+            break;
+        }
+      if (!tmpnode)
+        {
+          log_error ("Oops; requested subkey not found anymore!\n");
+          err = gpg_error (GPG_ERR_GENERAL);
+          goto leave;
+        }
+      /* Set NODE to this specific subkey or primary key.  */
+      thiskeyonly = node == tmpnode? 1 : 2;
+      targetnode = tmpnode;
+    }
+  else
+    targetnode = node;
+
+  pk = targetnode->pkt->pkt.public_key;
   keyid_from_pk (pk, keyid);
 
   if (!secret && !force)
@@ -143,6 +174,32 @@ do_delete_key (ctrl_t ctrl, const char *username, int secret, int force,
         print_pubkey_info (ctrl, NULL, pk );
       tty_printf( "\n" );
 
+      if (thiskeyonly == 1 && !secret)
+        {
+          /* We need to delete the entire public key despite the use
+           * of the thiskeyonly request.  */
+          tty_printf (_("Note: The public primary key and all its subkeys"
+                        " will be deleted.\n"));
+        }
+      else if (thiskeyonly == 2 && !secret)
+        {
+          tty_printf (_("Note: Only the shown public subkey"
+                        " will be deleted.\n"));
+        }
+      if (thiskeyonly == 1 && secret)
+        {
+          tty_printf (_("Note: Only the secret part of the shown primary"
+                        " key will be deleted.\n"));
+        }
+      else if (thiskeyonly == 2 && secret)
+        {
+          tty_printf (_("Note: Only the secret part of the shown subkey"
+                        " will be deleted.\n"));
+        }
+
+      if (thiskeyonly)
+        tty_printf ("\n");
+
       yes = cpr_get_answer_is_yes
         (secret? "delete_key.secret.okay": "delete_key.okay",
          _("Delete this key from the keyring? (y/N) "));
@@ -178,6 +235,9 @@ do_delete_key (ctrl_t ctrl, const char *username, int secret, int force,
                     || node->pkt->pkttype == PKT_PUBLIC_SUBKEY))
                 continue;
 
+              if (thiskeyonly && targetnode != node)
+                continue;
+
               if (agent_probe_secret_key (NULL, node->pkt->pkt.public_key))
                 continue;  /* No secret key for that public (sub)key.  */
 
@@ -219,9 +279,38 @@ do_delete_key (ctrl_t ctrl, const char *username, int secret, int force,
           if (firsterr)
             goto leave;
        }
+      else if (thiskeyonly == 2)
+        {
+          int selected = 0;
+
+          /* Delete the specified public subkey.  */
+          for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); )
+            {
+              if (thiskeyonly && targetnode != node)
+                continue;
+
+              if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
+                {
+                  selected = targetnode == node;
+                  if (selected)
+                    delete_kbnode (node);
+                }
+              else if (selected && node->pkt->pkttype == PKT_SIGNATURE)
+                delete_kbnode (node);
+              else
+                selected = 0;
+            }
+          commit_kbnode (&keyblock);
+          err = keydb_update_keyblock (ctrl, hd, keyblock);
+          if (err)
+            {
+              log_error (_("update failed: %s\n"), gpg_strerror (err));
+              goto leave;
+            }
+        }
       else
        {
-         err = opt.dry_run? 0 : keydb_delete_keyblock (hd);
+         err = keydb_delete_keyblock (hd);
          if (err)
             {
               log_error (_("deleting keyblock failed: %s\n"),
@@ -234,7 +323,8 @@ do_delete_key (ctrl_t ctrl, const char *username, int secret, int force,
         revalidation_mark().  This makes sense - only deleting keys
         that have ownertrust set should trigger this. */
 
-      if (!secret && pk && !opt.dry_run && clear_ownertrusts (ctrl, pk))
+      if (!secret && pk && !opt.dry_run && thiskeyonly != 2
+          && clear_ownertrusts (ctrl, pk))
         {
           if (opt.verbose)
             log_info (_("ownertrust information cleared\n"));
@@ -247,7 +337,8 @@ do_delete_key (ctrl_t ctrl, const char *username, int secret, int force,
   return err;
 }
 
-/****************
+
+/*
  * Delete a public or secret key from a keyring.
  */
 gpg_error_t
index 70f52615c8fcbafe5c2c8a870266e441b6063fb2..4216a2449f0dc11e1152c5fba14278fe1d37679d 100644 (file)
@@ -428,8 +428,8 @@ new_subkey_list_item (KBNODE node)
    (keyID or fingerprint) and does match the one at NODE.  It is
    assumed that the packet at NODE is either a public or secret
    subkey. */
-static int
-exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, KBNODE node)
+int
+exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, kbnode_t node)
 {
   u32 kid[2];
   byte fpr[MAX_FINGERPRINT_LEN];
index d3d60603b77d9eeceb3a74006d552f52a2b086eb..e14fcbba909fd3e956341817a8c40390de5bda09 100644 (file)
@@ -396,6 +396,8 @@ void export_print_stats (export_stats_t stats);
 int parse_export_options(char *str,unsigned int *options,int noisy);
 gpg_error_t parse_and_set_export_filter (const char *string);
 
+int exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, kbnode_t node);
+
 int export_pubkeys (ctrl_t ctrl, strlist_t users, unsigned int options,
                     export_stats_t stats);
 int export_seckeys (ctrl_t ctrl, strlist_t users, unsigned int options,