gpg: Prepare for listing last_update and key origin data.
[gnupg.git] / g10 / keyedit.c
index 200a1ab..9a7fe13 100644 (file)
@@ -1,6 +1,6 @@
 /* keyedit.c - Edit properties of a key
  * Copyright (C) 1998-2010 Free Software Foundation, Inc.
- * Copyright (C) 1998-2015 Werner Koch
+ * Copyright (C) 1998-2017 Werner Koch
  * Copyright (C) 2015, 2016 g10 Code GmbH
  *
  * This file is part of GnuPG.
@@ -16,7 +16,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
 #include "gpg.h"
 #include "options.h"
 #include "packet.h"
-#include "status.h"
-#include "iobuf.h"
+#include "../common/status.h"
+#include "../common/iobuf.h"
 #include "keydb.h"
 #include "photoid.h"
-#include "util.h"
+#include "../common/util.h"
 #include "main.h"
 #include "trustdb.h"
 #include "filter.h"
-#include "ttyio.h"
-#include "status.h"
-#include "i18n.h"
+#include "../common/ttyio.h"
+#include "../common/status.h"
+#include "../common/i18n.h"
 #include "keyserver-internal.h"
 #include "call-agent.h"
-#include "host2net.h"
+#include "../common/host2net.h"
 #include "tofu.h"
 
 static void show_prefs (PKT_user_id * uid, PKT_signature * selfsig,
                        int verbose);
-static void show_names (estream_t fp, KBNODE keyblock, PKT_public_key * pk,
+static void show_names (ctrl_t ctrl, estream_t fp,
+                        kbnode_t keyblock, PKT_public_key * pk,
                        unsigned int flag, int with_prefs);
 static void show_key_with_all_names (ctrl_t ctrl, estream_t fp,
                                      KBNODE keyblock, int only_marked,
@@ -61,14 +62,15 @@ static void show_key_with_all_names (ctrl_t ctrl, estream_t fp,
 static void show_key_and_fingerprint (kbnode_t keyblock, int with_subkeys);
 static void show_key_and_grip (kbnode_t keyblock);
 static void subkey_expire_warning (kbnode_t keyblock);
-static int menu_adduid (KBNODE keyblock, int photo, const char *photo_name,
-                        const char *uidstr);
+static int menu_adduid (ctrl_t ctrl, kbnode_t keyblock,
+                        int photo, const char *photo_name, const char *uidstr);
 static void menu_deluid (KBNODE pub_keyblock);
 static int menu_delsig (KBNODE pub_keyblock);
 static int menu_clean (KBNODE keyblock, int self_only);
 static void menu_delkey (KBNODE pub_keyblock);
 static int menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive);
-static int menu_expire (KBNODE pub_keyblock);
+static gpg_error_t menu_expire (kbnode_t pub_keyblock,
+                                int force_mainkey, u32 newexpiration);
 static int menu_changeusage (kbnode_t keyblock);
 static int menu_backsign (KBNODE pub_keyblock);
 static int menu_set_primary_uid (KBNODE pub_keyblock);
@@ -85,13 +87,16 @@ static int count_selected_uids (KBNODE keyblock);
 static int real_uids_left (KBNODE keyblock);
 static int count_selected_keys (KBNODE keyblock);
 static int menu_revsig (KBNODE keyblock);
-static int menu_revuid (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
 static int enable_disable_key (KBNODE keyblock, int disable);
 #endif /*!NO_TRUST_MODELS*/
-static void menu_showphoto (KBNODE keyblock);
+static void menu_showphoto (ctrl_t ctrl, kbnode_t keyblock);
 
 static int update_trust = 0;
 
@@ -276,11 +281,11 @@ print_one_sig (int rc, KBNODE keyblock, KBNODE node,
 
       if (sig->flags.policy_url
           && ((opt.list_options & LIST_SHOW_POLICY_URLS) || extended))
-       show_policy_url (sig, 3, 0);
+       show_policy_url (sig, 3, -1);
 
       if (sig->flags.notation
           && ((opt.list_options & LIST_SHOW_NOTATIONS) || extended))
-       show_notation (sig, 3, 0,
+       show_notation (sig, 3, -1,
                       ((opt.
                         list_options & LIST_SHOW_STD_NOTATIONS) ? 1 : 0) +
                       ((opt.
@@ -288,7 +293,7 @@ print_one_sig (int rc, KBNODE keyblock, KBNODE node,
 
       if (sig->flags.pref_ks
           && ((opt.list_options & LIST_SHOW_KEYSERVER_URLS) || extended))
-       show_keyserver_url (sig, 3, 0);
+       show_keyserver_url (sig, 3, -1);
 
       if (extended)
         {
@@ -404,20 +409,31 @@ check_all_keysigs (KBNODE kb, int only_selected, int only_selfsigs)
 
   /* First we look for duplicates.  */
   {
-    int nsigs = 0;
-    KBNODE *sigs;
+    int nsigs;
+    kbnode_t *sigs;
     int i;
     int last_i;
 
     /* Count the sigs.  */
-    for (n = kb; n; n = n->next)
-      if (is_deleted_kbnode (n))
-        continue;
-      else if (n->pkt->pkttype == PKT_SIGNATURE)
-        nsigs ++;
+    for (nsigs = 0, n = kb; n; n = n->next)
+      {
+        if (is_deleted_kbnode (n))
+          continue;
+        else if (n->pkt->pkttype == PKT_SIGNATURE)
+          nsigs ++;
+      }
+
+    if (!nsigs)
+      return 0; /* No signatures at all.  */
 
     /* Add them all to the SIGS array.  */
-    sigs = xmalloc_clear (sizeof (*sigs) * nsigs);
+    sigs = xtrycalloc (nsigs, sizeof *sigs);
+    if (!sigs)
+      {
+        log_error (_("error allocating memory: %s\n"),
+                   gpg_strerror (gpg_error_from_syserror ()));
+        return 0;
+      }
 
     i = 0;
     for (n = kb; n; n = n->next)
@@ -571,7 +587,8 @@ check_all_keysigs (KBNODE kb, int only_selected, int only_selfsigs)
 
           sig = n->pkt->pkt.signature;
 
-          pending_desc = xasprintf ("  sig: class: 0x%x, issuer: %s, timestamp: %s (%lld), digest: %02x %02x",
+          pending_desc = xasprintf ("  sig: class: 0x%x, issuer: %s,"
+                                    " timestamp: %s (%lld), digest: %02x %02x",
                                     sig->sig_class,
                                     keystr (sig->keyid),
                                     isotimestamp (sig->timestamp),
@@ -597,8 +614,9 @@ check_all_keysigs (KBNODE kb, int only_selected, int only_selfsigs)
                     {
                       if (pending_desc)
                         log_debug ("%s", pending_desc);
-                      log_debug ("    Can't check signature allegedly issued by %s: %s\n",
-                                keystr (sig->keyid), gpg_strerror (err));
+                      log_debug ("    Can't check signature allegedly"
+                                 " issued by %s: %s\n",
+                                 keystr (sig->keyid), gpg_strerror (err));
                     }
                   missing_issuer ++;
                   break;
@@ -1062,7 +1080,7 @@ trustsig_prompt (byte * trust_value, byte * trust_depth, char **regexp)
 
 
 /*
- * Loop over all LOCUSR and and sign the uids after asking.  If no
+ * Loop over all LOCUSR and sign the uids after asking.  If no
  * user id is marked, all user ids will be signed; if some user_ids
  * are marked only those will be signed.  If QUICK is true the
  * function won't ask the user and use sensible defaults.
@@ -1146,7 +1164,7 @@ sign_uids (ctrl_t ctrl, estream_t fp,
                       uidnode->flag &= ~NODFLG_MARK_A;
                       uidnode = NULL;
                     }
-                 else if (uidnode->pkt->pkt.user_id->is_revoked)
+                 else if (uidnode->pkt->pkt.user_id->flags.revoked)
                    {
                      tty_fprintf (fp, _("User ID \"%s\" is revoked."), user);
 
@@ -1174,7 +1192,7 @@ sign_uids (ctrl_t ctrl, estream_t fp,
                          tty_fprintf (fp, _("  Unable to sign.\n"));
                        }
                    }
-                 else if (uidnode->pkt->pkt.user_id->is_expired)
+                 else if (uidnode->pkt->pkt.user_id->flags.expired)
                    {
                      tty_fprintf (fp, _("User ID \"%s\" is expired."), user);
 
@@ -1332,7 +1350,7 @@ sign_uids (ctrl_t ctrl, estream_t fp,
                    }
 
                  /* Fixme: see whether there is a revocation in which
-                  * case we should allow to sign it again. */
+                  * case we should allow signing it again. */
                  if (!node->pkt->pkt.signature->flags.exportable && local)
                    tty_fprintf ( fp,
                        _("\"%s\" was already locally signed by key %s\n"),
@@ -1689,7 +1707,7 @@ change_passphrase (ctrl_t ctrl, kbnode_t keyblock)
           err = hexkeygrip_from_pk (pk, &hexgrip);
           if (err)
             goto leave;
-          err = agent_get_keyinfo (ctrl, hexgrip, &serialno);
+          err = agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL);
           if (!err && serialno)
             ; /* Key on card.  */
           else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
@@ -1727,7 +1745,8 @@ change_passphrase (ctrl_t ctrl, kbnode_t keyblock)
             goto leave;
 
           desc = gpg_format_keydesc (pk, FORMAT_KEYDESC_NORMAL, 1);
-          err = agent_passwd (ctrl, hexgrip, desc, &cache_nonce, &passwd_nonce);
+          err = agent_passwd (ctrl, hexgrip, desc, 0,
+                              &cache_nonce, &passwd_nonce);
           xfree (desc);
 
           if (err)
@@ -2022,7 +2041,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
      and run the stale check as early as possible.  Note, that for
      non- W32 platforms it is run indirectly trough a call to
      get_validity ().  */
-  check_trustdb_stale ();
+  check_trustdb_stale (ctrl);
 #endif
 
   /* Get the public key */
@@ -2295,7 +2314,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
          photo = 1;
          /* fall through */
        case cmdADDUID:
-         if (menu_adduid (keyblock, photo, arg_string, NULL))
+         if (menu_adduid (ctrl, keyblock, photo, arg_string, NULL))
            {
              update_trust = 1;
              redisplay = 1;
@@ -2348,7 +2367,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
          break;
 
        case cmdADDKEY:
-         if (!generate_subkeypair (ctrl, keyblock))
+         if (!generate_subkeypair (ctrl, keyblock, NULL, NULL, NULL))
            {
              redisplay = 1;
              modified = 1;
@@ -2424,7 +2443,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
             else if (*arg_string == '~')
               fname = make_filename (arg_string, NULL);
             else
-              fname = make_filename (opt.homedir, arg_string, NULL);
+              fname = make_filename (gnupg_homedir (), arg_string, NULL);
 
            /* Open that file.  */
            a = iobuf_open (fname);
@@ -2537,7 +2556,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
                       n1 > 1 ? _("Really revoke all selected user IDs? (y/N) ")
                      :        _("Really revoke this user ID? (y/N) ")))
              {
-               if (menu_revuid (keyblock))
+               if (menu_revuid (ctrl, keyblock))
                  {
                    modified = 1;
                    redisplay = 1;
@@ -2581,7 +2600,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
          break;
 
        case cmdEXPIRE:
-         if (menu_expire (keyblock))
+         if (gpg_err_code (menu_expire (keyblock, 0, 0)) == GPG_ERR_TRUE)
            {
              merge_keys_and_selfsig (keyblock);
               run_subkey_warnings = 1;
@@ -2631,7 +2650,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
 
          show_key_with_all_names (ctrl, NULL, keyblock, 0, 0, 0, 1, 0, 0);
          tty_printf ("\n");
-         if (edit_ownertrust (find_kbnode (keyblock,
+         if (edit_ownertrust (ctrl, find_kbnode (keyblock,
                                            PKT_PUBLIC_KEY)->pkt->pkt.
                               public_key, 1))
            {
@@ -2648,7 +2667,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
          {
            int count = count_selected_uids (keyblock);
            log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
-           show_names (NULL, keyblock, keyblock->pkt->pkt.public_key,
+           show_names (ctrl, NULL, keyblock, keyblock->pkt->pkt.public_key,
                        count ? NODFLG_SELUID : 0, 1);
          }
          break;
@@ -2657,7 +2676,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
          {
            int count = count_selected_uids (keyblock);
            log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
-           show_names (NULL, keyblock, keyblock->pkt->pkt.public_key,
+           show_names (ctrl, NULL, keyblock, keyblock->pkt->pkt.public_key,
                        count ? NODFLG_SELUID : 0, 2);
          }
          break;
@@ -2733,7 +2752,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
 #endif /*!NO_TRUST_MODELS*/
 
        case cmdSHOWPHOTO:
-         menu_showphoto (keyblock);
+         menu_showphoto (ctrl, keyblock);
          break;
 
        case cmdCLEAN:
@@ -2760,11 +2779,11 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
                goto leave;
              break;
            }
-         /* fall thru */
+         /* fall through */
        case cmdSAVE:
          if (modified)
            {
-              err = keydb_update_keyblock (kdbhd, keyblock);
+              err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
               if (err)
                 {
                   log_error (_("update failed: %s\n"), gpg_strerror (err));
@@ -2841,36 +2860,28 @@ leave:
 }
 
 
-/* Unattended adding of a new keyid.  USERNAME specifies the
-   key. NEWUID is the new user id to add to the key.  */
-void
-keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid)
+/* Helper for quick commands to find the keyblock for USERNAME.
+ * 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,
+                     KEYDB_HANDLE *r_kdbhd, kbnode_t *r_keyblock)
 {
   gpg_error_t err;
   KEYDB_HANDLE kdbhd = NULL;
-  KEYDB_SEARCH_DESC desc;
   kbnode_t keyblock = NULL;
+  KEYDB_SEARCH_DESC desc;
   kbnode_t node;
-  char *uidstring = NULL;
 
-  uidstring = xstrdup (newuid);
-  trim_spaces (uidstring);
-  if (!*uidstring)
-    {
-      log_error ("%s\n", gpg_strerror (GPG_ERR_INV_USER_ID));
-      goto leave;
-    }
-
-#ifdef HAVE_W32_SYSTEM
-  /* See keyedit_menu for why we need this.  */
-  check_trustdb_stale ();
-#endif
+  *r_kdbhd = NULL;
+  *r_keyblock = NULL;
 
   /* 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.  */
+      err = gpg_error_from_syserror ();
       goto leave;
     }
 
@@ -2898,25 +2909,68 @@ keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid)
 
       if (!err)
         {
-          /* We require the secret primary key to add a UID.  */
+          /* We require the secret primary key to set the primary UID.  */
           node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
-          if (!node)
-            BUG ();
+          log_assert (node);
           err = agent_probe_secret_key (ctrl, node->pkt->pkt.public_key);
         }
     }
+  else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+    err = gpg_error (GPG_ERR_NO_PUBKEY);
+
   if (err)
     {
-      log_error (_("secret key \"%s\" not found: %s\n"),
+      log_error (_("key \"%s\" not found: %s\n"),
                  username, gpg_strerror (err));
       goto leave;
     }
 
   fix_keyblock (&keyblock);
+  merge_keys_and_selfsig (keyblock);
+
+  *r_keyblock = keyblock;
+  keyblock = NULL;
+  *r_kdbhd = kdbhd;
+  kdbhd = NULL;
+
+ leave:
+  release_kbnode (keyblock);
+  keydb_release (kdbhd);
+  return err;
+}
 
-  if (menu_adduid (keyblock, 0, NULL, uidstring))
+
+/* Unattended adding of a new keyid.  USERNAME specifies the
+   key. NEWUID is the new user id to add to the key.  */
+void
+keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid)
+{
+  gpg_error_t err;
+  KEYDB_HANDLE kdbhd = NULL;
+  kbnode_t keyblock = NULL;
+  char *uidstring = NULL;
+
+  uidstring = xstrdup (newuid);
+  trim_spaces (uidstring);
+  if (!*uidstring)
     {
-      err = keydb_update_keyblock (kdbhd, keyblock);
+      log_error ("%s\n", gpg_strerror (GPG_ERR_INV_USER_ID));
+      goto leave;
+    }
+
+#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.  */
+  err = quick_find_keyblock (ctrl, username, &kdbhd, &keyblock);
+  if (err)
+    goto leave;
+
+  if (menu_adduid (ctrl, keyblock, 0, NULL, uidstring))
+    {
+      err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
       if (err)
         {
           log_error (_("update failed: %s\n"), gpg_strerror (err));
@@ -2934,40 +2988,175 @@ keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid)
 }
 
 
-/* Unattended key signing function.  If the key specifified by FPR is
-   available and FPR is the primary fingerprint all user ids of the
-   key are signed using the default signing key.  If UIDS is an empty
-   list all usable UIDs are signed, if it is not empty, only those
-   user ids matching one of the entries of the list are signed.  With
-   LOCAL being true the signatures are marked as non-exportable.  */
+/* Unattended revocation of a keyid.  USERNAME specifies the
+   key. UIDTOREV is the user id revoke from the key.  */
 void
-keyedit_quick_sign (ctrl_t ctrl, const char *fpr, strlist_t uids,
-                    strlist_t locusr, int local)
+keyedit_quick_revuid (ctrl_t ctrl, const char *username, const char *uidtorev)
 {
   gpg_error_t err;
-  kbnode_t keyblock = NULL;
   KEYDB_HANDLE kdbhd = NULL;
+  kbnode_t keyblock = NULL;
+  kbnode_t node;
   int modified = 0;
-  KEYDB_SEARCH_DESC desc;
-  PKT_public_key *pk;
+  size_t revlen;
+  size_t valid_uids;
+
+#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.  */
+  err = quick_find_keyblock (ctrl, username, &kdbhd, &keyblock);
+  if (err)
+    goto leave;
+
+  /* Too make sure that we do not revoke the last valid UID, we first
+     count how many valid UIDs there are.  */
+  valid_uids = 0;
+  for (node = keyblock; node; node = node->next)
+    valid_uids += (node->pkt->pkttype == PKT_USER_ID
+                   && !node->pkt->pkt.user_id->flags.revoked
+                   && !node->pkt->pkt.user_id->flags.expired);
+
+  /* Find the right UID. */
+  revlen = strlen (uidtorev);
+  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;
+
+          /* Make sure that we do not revoke the last valid UID.  */
+          if (valid_uids == 1
+              && ! node->pkt->pkt.user_id->flags.revoked
+              && ! node->pkt->pkt.user_id->flags.expired)
+            {
+              log_error (_("cannot revoke the last valid user ID.\n"));
+              err = gpg_error (GPG_ERR_INV_USER_ID);
+              goto leave;
+            }
+
+          reason = get_default_uid_revocation_reason ();
+          err = core_revuid (ctrl, keyblock, node, reason, &modified);
+          release_revocation_reason_info (reason);
+          if (err)
+            goto leave;
+          err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
+          if (err)
+            {
+              log_error (_("update failed: %s\n"), gpg_strerror (err));
+              goto leave;
+            }
+
+          revalidation_mark ();
+          goto leave;
+        }
+    }
+  err = gpg_error (GPG_ERR_NO_USER_ID);
+
+
+ leave:
+  if (err)
+    log_error (_("revoking the user ID failed: %s\n"), gpg_strerror (err));
+  release_kbnode (keyblock);
+  keydb_release (kdbhd);
+}
+
+
+/* Unattended setting of the primary uid.  USERNAME specifies the key.
+   PRIMARYUID is the user id which shall be primary.  */
+void
+keyedit_quick_set_primary (ctrl_t ctrl, const char *username,
+                           const char *primaryuid)
+{
+  gpg_error_t err;
+  KEYDB_HANDLE kdbhd = NULL;
+  kbnode_t keyblock = NULL;
   kbnode_t node;
-  strlist_t sl;
+  size_t primaryuidlen;
   int any;
 
 #ifdef HAVE_W32_SYSTEM
   /* See keyedit_menu for why we need this.  */
-  check_trustdb_stale ();
+  check_trustdb_stale (ctrl);
 #endif
 
-  /* We require a fingerprint because only this uniquely identifies a
-     key and may thus be used to select a key for unattended key
-     signing.  */
+  err = quick_find_keyblock (ctrl, username, &kdbhd, &keyblock);
+  if (err)
+    goto leave;
+
+  /* Find and mark the UID - we mark only the first valid one. */
+  primaryuidlen = strlen (primaryuid);
+  any = 0;
+  for (node = keyblock; node; node = node->next)
+    {
+      if (node->pkt->pkttype == PKT_USER_ID
+          && !any
+          && !node->pkt->pkt.user_id->flags.revoked
+          && !node->pkt->pkt.user_id->flags.expired
+          && primaryuidlen == node->pkt->pkt.user_id->len
+          && !memcmp (node->pkt->pkt.user_id->name, primaryuid, primaryuidlen))
+        {
+          node->flag |= NODFLG_SELUID;
+          any = 1;
+        }
+      else
+        node->flag &= ~NODFLG_SELUID;
+    }
+
+  if (!any)
+    err = gpg_error (GPG_ERR_NO_USER_ID);
+  else if (menu_set_primary_uid (keyblock))
+    {
+      merge_keys_and_selfsig (keyblock);
+      err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
+      if (err)
+        {
+          log_error (_("update failed: %s\n"), gpg_strerror (err));
+          goto leave;
+        }
+      revalidation_mark ();
+    }
+  else
+    err = gpg_error (GPG_ERR_GENERAL);
+
+  if (err)
+    log_error (_("setting the primary user ID failed: %s\n"),
+               gpg_strerror (err));
+
+ 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
+ * unattended subkey creation os key signing.  */
+static gpg_error_t
+find_by_primary_fpr (ctrl_t ctrl, const char *fpr,
+                     kbnode_t *r_keyblock, KEYDB_HANDLE *r_kdbhd)
+{
+  gpg_error_t err;
+  kbnode_t keyblock = NULL;
+  KEYDB_HANDLE kdbhd = NULL;
+  KEYDB_SEARCH_DESC desc;
+  byte fprbin[MAX_FINGERPRINT_LEN];
+  size_t fprlen;
+
+  *r_keyblock = NULL;
+  *r_kdbhd = NULL;
+
   if (classify_user_id (fpr, &desc, 1)
       || !(desc.mode == KEYDB_SEARCH_MODE_FPR
            || desc.mode == KEYDB_SEARCH_MODE_FPR16
            || desc.mode == KEYDB_SEARCH_MODE_FPR20))
     {
       log_error (_("\"%s\" is not a fingerprint\n"), fpr);
+      err = gpg_error (GPG_ERR_INV_NAME);
       goto leave;
     }
   err = get_pubkey_byname (ctrl, NULL, NULL, fpr, &keyblock, &kdbhd, 1, 1);
@@ -2978,31 +3167,70 @@ keyedit_quick_sign (ctrl_t ctrl, const char *fpr, strlist_t uids,
     }
 
   /* Check that the primary fingerprint has been given. */
-  {
-    byte fprbin[MAX_FINGERPRINT_LEN];
-    size_t fprlen;
-
-    fingerprint_from_pk (keyblock->pkt->pkt.public_key, fprbin, &fprlen);
-    if (fprlen == 16 && desc.mode == KEYDB_SEARCH_MODE_FPR16
-        && !memcmp (fprbin, desc.u.fpr, 16))
-      ;
-    else if (fprlen == 16 && desc.mode == KEYDB_SEARCH_MODE_FPR
-             && !memcmp (fprbin, desc.u.fpr, 16)
-             && !desc.u.fpr[16]
-             && !desc.u.fpr[17]
-             && !desc.u.fpr[18]
-             && !desc.u.fpr[19])
-      ;
-    else if (fprlen == 20 && (desc.mode == KEYDB_SEARCH_MODE_FPR20
-                              || desc.mode == KEYDB_SEARCH_MODE_FPR)
-             && !memcmp (fprbin, desc.u.fpr, 20))
-      ;
-    else
-      {
-        log_error (_("\"%s\" is not the primary fingerprint\n"), fpr);
-        goto leave;
-      }
-  }
+  fingerprint_from_pk (keyblock->pkt->pkt.public_key, fprbin, &fprlen);
+  if (fprlen == 16 && desc.mode == KEYDB_SEARCH_MODE_FPR16
+      && !memcmp (fprbin, desc.u.fpr, 16))
+    ;
+  else if (fprlen == 16 && desc.mode == KEYDB_SEARCH_MODE_FPR
+           && !memcmp (fprbin, desc.u.fpr, 16)
+           && !desc.u.fpr[16]
+           && !desc.u.fpr[17]
+           && !desc.u.fpr[18]
+           && !desc.u.fpr[19])
+    ;
+  else if (fprlen == 20 && (desc.mode == KEYDB_SEARCH_MODE_FPR20
+                            || desc.mode == KEYDB_SEARCH_MODE_FPR)
+           && !memcmp (fprbin, desc.u.fpr, 20))
+    ;
+  else
+    {
+      log_error (_("\"%s\" is not the primary fingerprint\n"), fpr);
+      err = gpg_error (GPG_ERR_INV_NAME);
+      goto leave;
+    }
+
+  *r_keyblock = keyblock;
+  keyblock = NULL;
+  *r_kdbhd = kdbhd;
+  kdbhd = NULL;
+  err = 0;
+
+ leave:
+  release_kbnode (keyblock);
+  keydb_release (kdbhd);
+  return err;
+}
+
+
+/* Unattended key signing function.  If the key specifified by FPR is
+   available and FPR is the primary fingerprint all user ids of the
+   key are signed using the default signing key.  If UIDS is an empty
+   list all usable UIDs are signed, if it is not empty, only those
+   user ids matching one of the entries of the list are signed.  With
+   LOCAL being true the signatures are marked as non-exportable.  */
+void
+keyedit_quick_sign (ctrl_t ctrl, const char *fpr, strlist_t uids,
+                    strlist_t locusr, int local)
+{
+  gpg_error_t err;
+  kbnode_t keyblock = NULL;
+  KEYDB_HANDLE kdbhd = NULL;
+  int modified = 0;
+  PKT_public_key *pk;
+  kbnode_t node;
+  strlist_t sl;
+  int any;
+
+#ifdef HAVE_W32_SYSTEM
+  /* See keyedit_menu for why we need this.  */
+  check_trustdb_stale (ctrl);
+#endif
+
+  /* We require a fingerprint because only this uniquely identifies a
+     key and may thus be used to select a key for unattended key
+     signing.  */
+  if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd))
+    goto leave;
 
   if (fix_keyblock (&keyblock))
     modified++;
@@ -3108,7 +3336,7 @@ keyedit_quick_sign (ctrl_t ctrl, const char *fpr, strlist_t uids,
 
   if (modified)
     {
-      err = keydb_update_keyblock (kdbhd, keyblock);
+      err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
       if (err)
         {
           log_error (_("update failed: %s\n"), gpg_strerror (err));
@@ -3128,6 +3356,147 @@ keyedit_quick_sign (ctrl_t ctrl, const char *fpr, strlist_t uids,
 }
 
 
+/* Unattended subkey creation function.
+ *
+ */
+void
+keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr,
+                      const char *usagestr, const char *expirestr)
+{
+  gpg_error_t err;
+  kbnode_t keyblock;
+  KEYDB_HANDLE kdbhd;
+  int modified = 0;
+  PKT_public_key *pk;
+
+#ifdef HAVE_W32_SYSTEM
+  /* See keyedit_menu for why we need this.  */
+  check_trustdb_stale (ctrl);
+#endif
+
+  /* We require a fingerprint because only this uniquely identifies a
+   * key and may thus be used to select a key for unattended subkey
+   * creation.  */
+  if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd))
+    goto leave;
+
+  if (fix_keyblock (&keyblock))
+    modified++;
+
+  pk = keyblock->pkt->pkt.public_key;
+  if (pk->flags.revoked)
+    {
+      if (!opt.verbose)
+        show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
+      log_error ("%s%s", _("Key is revoked."), "\n");
+      goto leave;
+    }
+
+  /* Create the subkey.  Note that the called function already prints
+   * an error message. */
+  if (!generate_subkeypair (ctrl, keyblock, algostr, usagestr, expirestr))
+    modified = 1;
+  es_fflush (es_stdout);
+
+  /* Store.  */
+  if (modified)
+    {
+      err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
+      if (err)
+        {
+          log_error (_("update failed: %s\n"), gpg_strerror (err));
+          goto leave;
+        }
+    }
+  else
+    log_info (_("Key not changed so no update needed.\n"));
+
+ leave:
+  release_kbnode (keyblock);
+  keydb_release (kdbhd);
+}
+
+
+/* Unattended expiration setting function for the main key.
+ *
+ */
+void
+keyedit_quick_set_expire (ctrl_t ctrl, const char *fpr, const char *expirestr)
+{
+  gpg_error_t err;
+  kbnode_t keyblock;
+  KEYDB_HANDLE kdbhd;
+  int modified = 0;
+  PKT_public_key *pk;
+  u32 expire;
+
+#ifdef HAVE_W32_SYSTEM
+  /* See keyedit_menu for why we need this.  */
+  check_trustdb_stale (ctrl);
+#endif
+
+  /* We require a fingerprint because only this uniquely identifies a
+   * key and may thus be used to select a key for unattended
+   * expiration setting.  */
+  err = find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd);
+  if (err)
+    goto leave;
+
+  if (fix_keyblock (&keyblock))
+    modified++;
+
+  pk = keyblock->pkt->pkt.public_key;
+  if (pk->flags.revoked)
+    {
+      if (!opt.verbose)
+        show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
+      log_error ("%s%s", _("Key is revoked."), "\n");
+      err = gpg_error (GPG_ERR_CERT_REVOKED);
+      goto leave;
+    }
+
+
+  expire = parse_expire_string (expirestr);
+  if (expire == (u32)-1 )
+    {
+      log_error (_("'%s' is not a valid expiration time\n"), expirestr);
+      err = gpg_error (GPG_ERR_INV_VALUE);
+      goto leave;
+    }
+  if (expire)
+    expire += make_timestamp ();
+
+  /* Set the new expiration date.  */
+  err = menu_expire (keyblock, 1, expire);
+  if (gpg_err_code (err) == GPG_ERR_TRUE)
+    modified = 1;
+  else if (err)
+    goto leave;
+  es_fflush (es_stdout);
+
+  /* Store.  */
+  if (modified)
+    {
+      err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
+      if (err)
+        {
+          log_error (_("update failed: %s\n"), gpg_strerror (err));
+          goto leave;
+        }
+      if (update_trust)
+        revalidation_mark ();
+    }
+  else
+    log_info (_("Key not changed so no update needed.\n"));
+
+ leave:
+  release_kbnode (keyblock);
+  keydb_release (kdbhd);
+  if (err)
+    write_status_error ("set_expire", err);
+}
+
+
 \f
 static void
 tty_print_notations (int indent, PKT_signature * sig)
@@ -3371,7 +3740,7 @@ show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock)
            es_putc ('e', fp);
          else if (!(opt.fast_list_mode || opt.no_expensive_trust_checks))
            {
-             int trust = get_validity_info (pk, NULL);
+             int trust = get_validity_info (ctrl, keyblock, pk, NULL);
              if (trust == 'u')
                ulti_hack = 1;
              es_putc (trust, fp);
@@ -3384,7 +3753,7 @@ show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock)
                       (ulong) pk->timestamp, (ulong) pk->expiredate);
          if (node->pkt->pkttype == PKT_PUBLIC_KEY
              && !(opt.fast_list_mode || opt.no_expensive_trust_checks))
-           es_putc (get_ownertrust_info (pk), fp);
+           es_putc (get_ownertrust_info (pk, 0), fp);
          es_putc (':', fp);
          es_putc (':', fp);
          es_putc (':', fp);
@@ -3419,9 +3788,9 @@ show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock)
          else
            es_fputs ("uid:", fp);
 
-         if (uid->is_revoked)
+         if (uid->flags.revoked)
            es_fputs ("r::::::::", fp);
-         else if (uid->is_expired)
+         else if (uid->flags.expired)
            es_fputs ("e::::::::", fp);
          else if (opt.fast_list_mode || opt.no_expensive_trust_checks)
            es_fputs ("::::::::", fp);
@@ -3430,7 +3799,7 @@ show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock)
              int uid_validity;
 
              if (primary && !ulti_hack)
-               uid_validity = get_validity_info (primary, uid);
+               uid_validity = get_validity_info (ctrl, keyblock, primary, uid);
              else
                uid_validity = 'u';
              es_fprintf (fp, "%c::::::::", uid_validity);
@@ -3469,11 +3838,11 @@ show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock)
          es_putc (':', fp);
          /* flags */
          es_fprintf (fp, "%d,", i);
-         if (uid->is_primary)
+         if (uid->flags.primary)
            es_putc ('p', fp);
-         if (uid->is_revoked)
+         if (uid->flags.revoked)
            es_putc ('r', fp);
-         if (uid->is_expired)
+         if (uid->flags.expired)
            es_putc ('e', fp);
          if ((node->flag & NODFLG_SELUID))
            es_putc ('s', fp);
@@ -3484,7 +3853,7 @@ show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock)
            {
 #ifdef USE_TOFU
              enum tofu_policy policy;
-             if (! tofu_get_policy (primary, uid, &policy)
+             if (! tofu_get_policy (ctrl, primary, uid, &policy)
                  && policy != TOFU_POLICY_NONE)
                es_fprintf (fp, "%s", tofu_policy_str (policy));
 #endif /*USE_TOFU*/
@@ -3497,8 +3866,8 @@ show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock)
 
 
 static void
-show_names (estream_t fp,
-            KBNODE keyblock, PKT_public_key * pk, unsigned int flag,
+show_names (ctrl_t ctrl, estream_t fp,
+            kbnode_t keyblock, PKT_public_key * pk, unsigned int flag,
            int with_prefs)
 {
   KBNODE node;
@@ -3513,13 +3882,13 @@ show_names (estream_t fp,
          if (!flag || (flag && (node->flag & flag)))
            {
              if (!(flag & NODFLG_MARK_A) && pk)
-               tty_fprintf (fp, "%s ", uid_trust_string_fixed (pk, uid));
+               tty_fprintf (fp, "%s ", uid_trust_string_fixed (ctrl, pk, uid));
 
              if (flag & NODFLG_MARK_A)
                tty_fprintf (fp, "     ");
              else if (node->flag & NODFLG_SELUID)
                tty_fprintf (fp, "(%d)* ", i);
-             else if (uid->is_primary)
+             else if (uid->flags.primary)
                tty_fprintf (fp, "(%d). ", i);
              else
                tty_fprintf (fp, "(%d)  ", i);
@@ -3600,12 +3969,12 @@ show_key_with_all_names (ctrl_t ctrl, estream_t fp,
               * output */
              static int did_warn = 0;
 
-             trust = get_validity_string (pk, NULL);
-             otrust = get_ownertrust_string (pk);
+             trust = get_validity_string (ctrl, pk, NULL);
+             otrust = get_ownertrust_string (pk, 0);
 
              /* Show a warning once */
              if (!did_warn
-                 && (get_validity (pk, NULL, NULL, 0)
+                 && (get_validity (ctrl, keyblock, pk, NULL, NULL, 0)
                      & TRUST_FLAG_PENDING_CHECK))
                {
                  did_warn = 1;
@@ -3672,7 +4041,7 @@ show_key_with_all_names (ctrl_t ctrl, estream_t fp,
                 have_seckey = 0;
               }
             else
-              have_seckey = !agent_get_keyinfo (ctrl, hexgrip, &serialno);
+              have_seckey = !agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL);
             xfree (hexgrip);
           }
 
@@ -3792,7 +4161,7 @@ show_key_with_all_names (ctrl_t ctrl, estream_t fp,
        }
     }
 
-  show_names (fp,
+  show_names (ctrl, fp,
               keyblock, primary, only_marked ? NODFLG_MARK_A : 0, with_prefs);
 
   if (do_warn && !nowarn)
@@ -3851,9 +4220,9 @@ show_basic_key_info (KBNODE keyblock)
          ++i;
 
          tty_printf ("     ");
-         if (uid->is_revoked)
+         if (uid->flags.revoked)
            tty_printf ("[%s] ", _("revoked"));
-         else if (uid->is_expired)
+         else if (uid->flags.expired)
            tty_printf ("[%s] ", _("expired"));
          tty_print_utf8_string (uid->name, uid->len);
          tty_printf ("\n");
@@ -3961,7 +4330,7 @@ no_primary_warning (KBNODE keyblock)
        {
          uid_count++;
 
-         if (node->pkt->pkt.user_id->is_primary == 2)
+         if (node->pkt->pkt.user_id->flags.primary == 2)
            {
              have_primary = 1;
              break;
@@ -4037,8 +4406,8 @@ subkey_expire_warning (kbnode_t keyblock)
  * user id.
  */
 static int
-menu_adduid (kbnode_t pub_keyblock, int photo, const char *photo_name,
-             const char *uidstring)
+menu_adduid (ctrl_t ctrl, kbnode_t pub_keyblock,
+             int photo, const char *photo_name, const char *uidstring)
 {
   PKT_user_id *uid;
   PKT_public_key *pk = NULL;
@@ -4100,14 +4469,17 @@ menu_adduid (kbnode_t pub_keyblock, int photo, const char *photo_name,
            }
        }
 
-      uid = generate_photo_id (pk, photo_name);
+      uid = generate_photo_id (ctrl, pk, photo_name);
     }
   else
     uid = generate_user_id (pub_keyblock, uidstring);
   if (!uid)
     {
       if (uidstring)
-        log_error ("%s", _("Such a user ID already exists on this key!\n"));
+        {
+          write_status_error ("adduid", gpg_error (304));
+          log_error ("%s", _("Such a user ID already exists on this key!\n"));
+        }
       return 0;
     }
 
@@ -4132,7 +4504,7 @@ menu_adduid (kbnode_t pub_keyblock, int photo, const char *photo_name,
     add_kbnode (pub_keyblock, node);
   pkt = xmalloc_clear (sizeof *pkt);
   pkt->pkttype = PKT_SIGNATURE;
-  pkt->pkt.signature = copy_signature (NULL, sig);
+  pkt->pkt.signature = sig;
   if (pub_where)
     insert_kbnode (node, new_kbnode (pkt), 0);
   else
@@ -4159,7 +4531,7 @@ menu_deluid (KBNODE pub_keyblock)
            {
              /* Only cause a trust update if we delete a
                 non-revoked user id */
-             if (!node->pkt->pkt.user_id->is_revoked)
+             if (!node->pkt->pkt.user_id->flags.revoked)
                update_trust = 1;
              delete_kbnode (node);
            }
@@ -4279,9 +4651,9 @@ menu_clean (KBNODE keyblock, int self_only)
            {
              const char *reason;
 
-             if (uidnode->pkt->pkt.user_id->is_revoked)
+             if (uidnode->pkt->pkt.user_id->flags.revoked)
                reason = _("revoked");
-             else if (uidnode->pkt->pkt.user_id->is_expired)
+             else if (uidnode->pkt->pkt.user_id->flags.expired)
                reason = _("expired");
              else
                reason = _("invalid");
@@ -4519,36 +4891,50 @@ fail:
 }
 
 
-static int
-menu_expire (KBNODE pub_keyblock)
+/* With FORCE_MAINKEY cleared this function handles the interactive
+ * menu option "expire".  With FORCE_MAINKEY set this functions only
+ * sets the expiration date of the primary key to NEWEXPIRATION and
+ * avoid all interactivity.  Retirns 0 if nothing was done,
+ * GPG_ERR_TRUE if the key was modified, or any other error code. */
+static gpg_error_t
+menu_expire (kbnode_t pub_keyblock, int force_mainkey, u32 newexpiration)
 {
-  int n1, signumber, rc;
+  int signumber, rc;
   u32 expiredate;
   int mainkey = 0;
   PKT_public_key *main_pk, *sub_pk;
   PKT_user_id *uid;
-  KBNODE node;
+  kbnode_t node;
   u32 keyid[2];
 
-  n1 = count_selected_keys (pub_keyblock);
-  if (n1 > 1)
+  if (force_mainkey)
     {
-      if (!cpr_get_answer_is_yes
-          ("keyedit.expire_multiple_subkeys.okay",
-           _("Are you sure you want to change the"
-             " expiration time for multiple subkeys? (y/N) ")))
-       return 0;
+      mainkey = 1;
+      expiredate = newexpiration;
     }
-  else if (n1)
-    tty_printf (_("Changing expiration time for a subkey.\n"));
   else
     {
-      tty_printf (_("Changing expiration time for the primary key.\n"));
-      mainkey = 1;
-      no_primary_warning (pub_keyblock);
+      int n1 = count_selected_keys (pub_keyblock);
+      if (n1 > 1)
+        {
+          if (!cpr_get_answer_is_yes
+              ("keyedit.expire_multiple_subkeys.okay",
+               _("Are you sure you want to change the"
+                 " expiration time for multiple subkeys? (y/N) ")))
+            return gpg_error (GPG_ERR_CANCELED);;
+        }
+      else if (n1)
+        tty_printf (_("Changing expiration time for a subkey.\n"));
+      else
+        {
+          tty_printf (_("Changing expiration time for the primary key.\n"));
+          mainkey = 1;
+          no_primary_warning (pub_keyblock);
+        }
+
+      expiredate = ask_expiredate ();
     }
 
-  expiredate = ask_expiredate ();
 
   /* Now we can actually change the self-signature(s) */
   main_pk = sub_pk = NULL;
@@ -4564,7 +4950,7 @@ menu_expire (KBNODE pub_keyblock)
        }
       else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
        {
-          if (node->flag & NODFLG_SELKEY)
+          if ((node->flag & NODFLG_SELKEY) && !force_mainkey)
             {
               sub_pk = node->pkt->pkt.public_key;
               sub_pk->expiredate = expiredate;
@@ -4578,6 +4964,7 @@ menu_expire (KBNODE pub_keyblock)
               && (mainkey || sub_pk))
        {
          PKT_signature *sig = node->pkt->pkt.signature;
+
          if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
              && ((mainkey && uid
                   && uid->created && (sig->sig_class & ~3) == 0x10)
@@ -4595,7 +4982,7 @@ menu_expire (KBNODE pub_keyblock)
                {
                  log_info
                     (_("You can't change the expiration date of a v3 key\n"));
-                 return 0;
+                 return gpg_error (GPG_ERR_LEGACY_KEY);
                }
 
              if (mainkey)
@@ -4610,7 +4997,9 @@ menu_expire (KBNODE pub_keyblock)
                {
                  log_error ("make_keysig_packet failed: %s\n",
                             gpg_strerror (rc));
-                 return 0;
+                  if (gpg_err_code (rc) == GPG_ERR_TRUE)
+                    rc = GPG_ERR_GENERAL;
+                 return rc;
                }
 
              /* Replace the packet.  */
@@ -4626,7 +5015,7 @@ menu_expire (KBNODE pub_keyblock)
     }
 
   update_trust = 1;
-  return 1;
+  return gpg_error (GPG_ERR_TRUE);
 }
 
 
@@ -4869,9 +5258,9 @@ change_primary_uid_cb (PKT_signature * sig, void *opaque)
 
 /*
  * Set the primary uid flag for the selected UID.  We will also reset
- * all other primary uid flags.  For this to work with have to update
+ * all other primary uid flags.  For this to work we have to update
  * all the signature timestamps.  If we would do this with the current
- * time, we lose quite a lot of information, so we use a kludge to
+ * time, we lose quite a lot of information, so we use a kludge to
  * do this: Just increment the timestamp by one second which is
  * sufficient to updated a signature during import.
  */
@@ -5999,7 +6388,7 @@ reloop:                   /* (must use this, because we are modifing the list) */
       /* Are we revoking our own uid? */
       if (primary_pk->keyid[0] == sig->keyid[0] &&
          primary_pk->keyid[1] == sig->keyid[1])
-       unode->pkt->pkt.user_id->is_revoked = 1;
+       unode->pkt->pkt.user_id->flags.revoked = 1;
       pkt = xmalloc_clear (sizeof *pkt);
       pkt->pkttype = PKT_SIGNATURE;
       pkt->pkt.signature = sig;
@@ -6012,16 +6401,107 @@ 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->flags.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, keyblock, pk, uid, NULL, 0)
+                       & TRUST_MASK)
+                      >= TRUST_UNDEFINED))
+                update_trust = 1;
+#endif /*!NO_TRUST_MODELS*/
+
+              node->pkt->pkt.user_id->flags.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
-menu_revuid (KBNODE pub_keyblock)
+menu_revuid (ctrl_t ctrl, kbnode_t pub_keyblock)
 {
   PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key;
   KBNODE node;
   int changed = 0;
   int rc;
   struct revocation_reason_info *reason = NULL;
+  size_t valid_uids;
 
   /* Note that this is correct as per the RFCs, but nevertheless
      somewhat meaningless in the real world.  1991 did define the 0x30
@@ -6038,75 +6518,39 @@ menu_revuid (KBNODE pub_keyblock)
          goto leave;
       }
 
- reloop: /* (better this way because we are modifing the keyring) */
+  /* Too make sure that we do not revoke the last valid UID, we first
+     count how many valid UIDs there are.  */
+  valid_uids = 0;
+  for (node = pub_keyblock; node; node = node->next)
+    valid_uids +=
+      node->pkt->pkttype == PKT_USER_ID
+      && ! node->pkt->pkt.user_id->flags.revoked
+      && ! node->pkt->pkt.user_id->flags.expired;
+
+ 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);
+        int modified = 0;
 
-               timestamp = uid->created + 1;
-             }
-
-           memset (&attrib, 0, sizeof attrib);
-           attrib.reason = reason;
+        /* Make sure that we do not revoke the last valid UID.  */
+        if (valid_uids == 1
+            && ! node->pkt->pkt.user_id->flags.revoked
+            && ! node->pkt->pkt.user_id->flags.expired)
+          {
+            log_error (_("Cannot revoke the last valid user ID.\n"));
+            goto leave;
+          }
 
+        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 (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)
@@ -6258,7 +6702,7 @@ enable_disable_key (KBNODE keyblock, int disable)
 
 
 static void
-menu_showphoto (KBNODE keyblock)
+menu_showphoto (ctrl_t ctrl, kbnode_t keyblock)
 {
   KBNODE node;
   int select_all = !count_selected_uids (keyblock);
@@ -6295,7 +6739,7 @@ menu_showphoto (KBNODE keyblock)
                                    "key %s (uid %d)\n"),
                                  image_type_to_string (type, 1),
                                  (ulong) size, keystr_from_pk (pk), count);
-                     show_photos (&uid->attribs[i], 1, pk, uid);
+                     show_photos (ctrl, &uid->attribs[i], 1, pk, uid);
                    }
                }
            }