gpg: Emit new status line KEY_CONSIDERED.
authorWerner Koch <wk@gnupg.org>
Fri, 13 May 2016 14:24:59 +0000 (16:24 +0200)
committerWerner Koch <wk@gnupg.org>
Tue, 17 May 2016 17:13:19 +0000 (19:13 +0200)
* common/status.h (STATUS_KEY_CONSIDERED): New.
* g10/getkey.c: Include status.h.
(LOOKUP_NOT_SELECTED, LOOKUP_ALL_SUBKEYS_EXPIRED): New.
(finish_lookup): Add arg R_FLAGS.  Count expired and revoked keys and
set flag.  Check a requested usage before checking for expiraion or
revocation.
(print_status_key_considered): New.
(lookup): Print new status.

Signed-off-by: Werner Koch <wk@gnupg.org>
common/status.h
doc/DETAILS
g10/getkey.c

index 730a75c..966489f 100644 (file)
@@ -105,6 +105,7 @@ enum
     STATUS_INV_SGNR,
     STATUS_NO_RECP,
     STATUS_NO_SGNR,
+    STATUS_KEY_CONSIDERED,
 
     STATUS_ALREADY_SIGNED,
     STATUS_KEYEXPIRED,
index 5ceab68..2710007 100644 (file)
@@ -548,7 +548,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB:
     that case, this status tag does not appear.
 
 *** ATTRIBUTE <arguments>
-    The list or argemnts are:
+    The list or arguments are:
     - <fpr>
     - <octets>
     - <type>
@@ -602,18 +602,29 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB:
        - 13 :: Key disabled
        - 14 :: Syntax error in specification
 
+    If no specific reason was given a previously emitted status code
+    KEY_CONSIDERED may be used to analyzed the problem.
+
     Note that for historical reasons the INV_RECP status is also used
     for gpgsm's SIGNER command where it relates to signer's of course.
     Newer GnuPG versions are using INV_SGNR; applications should
     ignore the INV_RECP during the sender's command processing once
     they have seen an INV_SGNR.  Different codes are used so that they
     can be distinguish while doing an encrypt+sign operation.
+
 *** NO_RECP <reserved>
     Issued if no recipients are usable.
 
 *** NO_SGNR <reserved>
     Issued if no senders are usable.
 
+*** KEY_CONSIDERED <fpr> <flags>
+    Issued to explian the lookup of a key.  FPR is the hexified
+    fingerprint of the primary key.  The bit values for FLAGS are:
+
+    - 1 :: The key has not been selected.
+    - 2 :: All subkeys of the key are expired or have been revoked.
+
 *** KEYEXPIRED <expire-timestamp>
     The key has expired.  expire-timestamp is the expiration time in
     seconds since Epoch.  This status line is not very useful because
index 907007b..ad0148e 100644 (file)
@@ -38,6 +38,7 @@
 #include "call-agent.h"
 #include "host2net.h"
 #include "mbox-util.h"
+#include "status.h"
 
 #define MAX_PK_CACHE_ENTRIES   PK_UID_CACHE_SIZE
 #define MAX_UID_CACHE_ENTRIES  PK_UID_CACHE_SIZE
 #error We need the cache for key creation
 #endif
 
+/* Flags values returned by the lookup code.  Note that the values are
+ * directly used by the KEY_CONSIDERED status line.  */
+#define LOOKUP_NOT_SELECTED        (1<<0)
+#define LOOKUP_ALL_SUBKEYS_EXPIRED (1<<1)  /* or revoked */
+
+
+/* A context object used by the lookup functions.  */
 struct getkey_ctx_s
 {
   /* Part of the search criteria: whether the search is an exact
@@ -3045,51 +3053,54 @@ merge_selfsigs (KBNODE keyblock)
 
 \f
 /* See whether the key satisfies any additional requirements specified
-   in CTX.  If so, return 1 and set CTX->FOUND_KEY to an appropriate
-   key or subkey.  Otherwise, return 0 if there was no appropriate
-   key.
-
-   In case the primary key is not required, select a suitable subkey.
-   We need the primary key if PUBKEY_USAGE_CERT is set in
-   CTX->REQ_USAGE or we are in PGP6 or PGP7 mode and PUBKEY_USAGE_SIG
-   is set in CTX->REQ_USAGE.
-
-   If any of PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT
-   are set in CTX->REQ_USAGE, we filter by the key's function.
-   Concretely, if PUBKEY_USAGE_SIG and PUBKEY_USAGE_CERT are set, then
-   we only return a key if it is (at least) either a signing or a
-   certification key.
-
-   If CTX->REQ_USAGE is set, then we reject any keys that are not good
-   (i.e., valid, not revoked, not expired, etc.).  This allows the
-   getkey functions to be used for plain key listings.
-
-   Sets the matched key's user id field (pk->user_id) to the user id
-   that matched the low-level search criteria or NULL.
-
-
-   This function needs to handle several different cases:
-
-    1. No requested usage and no primary key requested
-       Examples for this case are that we have a keyID to be used
-       for decrytion or verification.
-    2. No usage but primary key requested
-       This is the case for all functions which work on an
-       entire keyblock, e.g. for editing or listing
-    3. Usage and primary key requested
-       FXME
-    4. Usage but no primary key requested
-       FIXME
-
+ * in CTX.  If so, return 1 and set CTX->FOUND_KEY to an appropriate
+ * key or subkey.  Otherwise, return 0 if there was no appropriate
+ * key.
+ *
+ * In case the primary key is not required, select a suitable subkey.
+ * We need the primary key if PUBKEY_USAGE_CERT is set in
+ * CTX->REQ_USAGE or we are in PGP6 or PGP7 mode and PUBKEY_USAGE_SIG
+ * is set in CTX->REQ_USAGE.
+ *
+ * If any of PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT
+ * are set in CTX->REQ_USAGE, we filter by the key's function.
+ * Concretely, if PUBKEY_USAGE_SIG and PUBKEY_USAGE_CERT are set, then
+ * we only return a key if it is (at least) either a signing or a
+ * certification key.
+ *
+ * If CTX->REQ_USAGE is set, then we reject any keys that are not good
+ * (i.e., valid, not revoked, not expired, etc.).  This allows the
+ * getkey functions to be used for plain key listings.
+ *
+ * Sets the matched key's user id field (pk->user_id) to the user id
+ * that matched the low-level search criteria or NULL.  If R_FLAGS is
+ * not NULL set certain flags for more detailed error reporting.  Used
+ * flags are:
+ * - LOOKUP_ALL_SUBKEYS_EXPIRED :: All Subkeys are expired or have
+ *                                 been revoked.
+ *
+ * This function needs to handle several different cases:
+ *
+ *  1. No requested usage and no primary key requested
+ *     Examples for this case are that we have a keyID to be used
+ *     for decrytion or verification.
+ *  2. No usage but primary key requested
+ *     This is the case for all functions which work on an
+ *     entire keyblock, e.g. for editing or listing
+ *  3. Usage and primary key requested
+ *     FIXME
+ *  4. Usage but no primary key requested
+ *     FIXME
+ *
  */
-static KBNODE
-finish_lookup (GETKEY_CTX ctx, KBNODE keyblock)
+static kbnode_t
+finish_lookup (getkey_ctx_t ctx, kbnode_t keyblock, unsigned int *r_flags)
 {
-  KBNODE k;
+  kbnode_t k;
 
   /* If CTX->EXACT is set, the key or subkey that actually matched the
      low-level search criteria.  */
-  KBNODE foundk = NULL;
+  kbnode_t foundk = NULL;
   /* The user id (if any) that matched the low-level search criteria.  */
   PKT_user_id *foundu = NULL;
 
@@ -3100,21 +3111,23 @@ finish_lookup (GETKEY_CTX ctx, KBNODE keyblock)
      if signing data while --pgp6 or --pgp7 is on since pgp 6 and 7
      do not understand signatures made by a signing subkey.  PGP 8
      does. */
-  int req_prim = (ctx->req_usage & PUBKEY_USAGE_CERT) ||
-    ((PGP6 || PGP7) && (ctx->req_usage & PUBKEY_USAGE_SIG));
+  int req_prim = ((ctx->req_usage & PUBKEY_USAGE_CERT)
+                  || ((PGP6 || PGP7) && (ctx->req_usage & PUBKEY_USAGE_SIG)));
 
   u32 curtime = make_timestamp ();
 
   u32 latest_date;
-  KBNODE latest_key;
+  kbnode_t latest_key;
   PKT_public_key *pk;
 
-
   log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
 
+  if (r_flags)
+    *r_flags = 0;
+
+  /* For an exact match mark the primary or subkey that matched the
+     low-level search criteria.  */
   if (ctx->exact)
-    /* Get the key or subkey that matched the low-level search
-       criteria.  */
     {
       for (k = keyblock; k; k = k->next)
        {
@@ -3154,24 +3167,28 @@ finish_lookup (GETKEY_CTX ctx, KBNODE keyblock)
 
   latest_date = 0;
   latest_key = NULL;
-  /* Set latest_key to the latest (the one with the most recent
-     timestamp) good (valid, not revoked, not expired, etc.) subkey.
-
-     Don't bother if we are only looking for a primary key or we need
-     an exact match and the exact match is not a subkey.  */
+  /* Set LATEST_KEY to the latest (the one with the most recent
+   * timestamp) good (valid, not revoked, not expired, etc.) subkey.
+   *
+   * Don't bother if we are only looking for a primary key or we need
+   * an exact match and the exact match is not a subkey.  */
   if (req_prim || (foundk && foundk->pkt->pkttype != PKT_PUBLIC_SUBKEY))
     ;
   else
     {
-      KBNODE nextk;
+      kbnode_t nextk;
+      int n_subkeys = 0;
+      int n_revoked_or_expired = 0;
 
       /* Either start a loop or check just this one subkey.  */
       for (k = foundk ? foundk : keyblock; k; k = nextk)
        {
          if (foundk)
-           /* If FOUNDK is not NULL, then only consider that exact
-              key, i.e., don't iterate.  */
-           nextk = NULL;
+            {
+              /* If FOUNDK is not NULL, then only consider that exact
+                 key, i.e., don't iterate.  */
+              nextk = NULL;
+            }
          else
            nextk = k->next;
 
@@ -3182,24 +3199,35 @@ finish_lookup (GETKEY_CTX ctx, KBNODE keyblock)
          if (DBG_LOOKUP)
            log_debug ("\tchecking subkey %08lX\n",
                       (ulong) keyid_from_pk (pk, NULL));
+
          if (!pk->flags.valid)
            {
              if (DBG_LOOKUP)
                log_debug ("\tsubkey not valid\n");
              continue;
            }
+         if (!((pk->pubkey_usage & USAGE_MASK) & req_usage))
+           {
+             if (DBG_LOOKUP)
+               log_debug ("\tusage does not match: want=%x have=%x\n",
+                          req_usage, pk->pubkey_usage);
+             continue;
+           }
+
+          n_subkeys++;
          if (pk->flags.revoked)
            {
              if (DBG_LOOKUP)
                log_debug ("\tsubkey has been revoked\n");
+              n_revoked_or_expired++;
              continue;
            }
          if (pk->has_expired)
            {
              if (DBG_LOOKUP)
                log_debug ("\tsubkey has expired\n");
+              n_revoked_or_expired++;
              continue;
-
            }
          if (pk->timestamp > curtime && !opt.ignore_valid_from)
            {
@@ -3208,14 +3236,6 @@ finish_lookup (GETKEY_CTX ctx, KBNODE keyblock)
              continue;
            }
 
-         if (!((pk->pubkey_usage & USAGE_MASK) & req_usage))
-           {
-             if (DBG_LOOKUP)
-               log_debug ("\tusage does not match: want=%x have=%x\n",
-                          req_usage, pk->pubkey_usage);
-             continue;
-           }
-
          if (DBG_LOOKUP)
            log_debug ("\tsubkey might be fine\n");
          /* In case a key has a timestamp of 0 set, we make sure
@@ -3228,18 +3248,20 @@ finish_lookup (GETKEY_CTX ctx, KBNODE keyblock)
              latest_key = k;
            }
        }
+      if (n_subkeys == n_revoked_or_expired && r_flags)
+        *r_flags |= LOOKUP_ALL_SUBKEYS_EXPIRED;
     }
 
   /* Check if the primary key is ok (valid, not revoke, not expire,
-     matches requested usage) if:
-
-       - we didn't find an appropriate subkey and we're not doing an
-         exact search,
-
-       - we're doing an exact match and the exact match was the
-         primary key, or,
-
-       - we're just considering the primary key.  */
+   * matches requested usage) if:
+   *
+   *   - we didn't find an appropriate subkey and we're not doing an
+   *     exact search,
+   *
+   *   - we're doing an exact match and the exact match was the
+   *     primary key, or,
+   *
+   *   - we're just considering the primary key.  */
   if ((!latest_key && !ctx->exact) || foundk == keyblock || req_prim)
     {
       if (DBG_LOOKUP && !foundk && !req_prim)
@@ -3250,6 +3272,12 @@ finish_lookup (GETKEY_CTX ctx, KBNODE keyblock)
          if (DBG_LOOKUP)
            log_debug ("\tprimary key not valid\n");
        }
+      else if (!((pk->pubkey_usage & USAGE_MASK) & req_usage))
+       {
+         if (DBG_LOOKUP)
+           log_debug ("\tprimary key usage does not match: "
+                      "want=%x have=%x\n", req_usage, pk->pubkey_usage);
+       }
       else if (pk->flags.revoked)
        {
          if (DBG_LOOKUP)
@@ -3260,12 +3288,6 @@ finish_lookup (GETKEY_CTX ctx, KBNODE keyblock)
          if (DBG_LOOKUP)
            log_debug ("\tprimary key has expired\n");
        }
-      else if (!((pk->pubkey_usage & USAGE_MASK) & req_usage))
-       {
-         if (DBG_LOOKUP)
-           log_debug ("\tprimary key usage does not match: "
-                      "want=%x have=%x\n", req_usage, pk->pubkey_usage);
-       }
       else /* Okay.  */
        {
          if (DBG_LOOKUP)
@@ -3309,6 +3331,34 @@ found:
 }
 
 
+/* Print a KEY_CONSIDERED status line.  */
+static void
+print_status_key_considered (kbnode_t keyblock, unsigned int flags)
+{
+  char hexfpr[2*MAX_FINGERPRINT_LEN + 1];
+  kbnode_t node;
+  char flagbuf[20];
+
+  if (!is_status_enabled ())
+    return;
+
+  for (node=keyblock; node; node = node->next)
+    if (node->pkt->pkttype == PKT_PUBLIC_KEY
+        || node->pkt->pkttype == PKT_SECRET_KEY)
+      break;
+  if (!node)
+    {
+      log_error ("%s: keyblock w/o primary key\n", __func__);
+      return;
+    }
+
+  hexfingerprint (node->pkt->pkt.public_key, hexfpr, sizeof hexfpr);
+  snprintf (flagbuf, sizeof flagbuf, " %u", flags);
+  write_status_strings (STATUS_KEY_CONSIDERED, hexfpr, flagbuf, NULL);
+}
+
+
+
 /* A high-level function to lookup keys.
 
    This function builds on top of the low-level keydb API.  It first
@@ -3329,6 +3379,7 @@ lookup (getkey_ctx_t ctx, kbnode_t *ret_keyblock, kbnode_t *ret_found_key,
   int no_suitable_key = 0;
   KBNODE keyblock = NULL;
   KBNODE found_key = NULL;
+  unsigned int infoflags;
 
   if (ret_keyblock)
     *ret_keyblock = NULL;
@@ -3360,14 +3411,19 @@ lookup (getkey_ctx_t ctx, kbnode_t *ret_keyblock, kbnode_t *ret_found_key,
        * merge_selfsigs.  For secret keys, premerge transferred the
        * keys to the keyblock.  */
       merge_selfsigs (keyblock);
-      found_key = finish_lookup (ctx, keyblock);
+      found_key = finish_lookup (ctx, keyblock, &infoflags);
+      if (!found_key)
+        infoflags |= LOOKUP_NOT_SELECTED;
+      print_status_key_considered (keyblock, infoflags);
       if (found_key)
        {
          no_suitable_key = 0;
          goto found;
        }
       else
-       no_suitable_key = 1;
+        {
+          no_suitable_key = 1;
+        }
 
     skip:
       /* Release resources and continue search. */
@@ -3381,7 +3437,7 @@ lookup (getkey_ctx_t ctx, kbnode_t *ret_keyblock, kbnode_t *ret_found_key,
       keydb_disable_caching (ctx->kr_handle);
     }
 
-found:
+ found:
   if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
     log_error ("keydb_search failed: %s\n", gpg_strerror (rc));