g10: Change tofu_register & tofu_get_validity to process multiple uids.
authorNeal H. Walfield <neal@g10code.com>
Mon, 29 Aug 2016 14:16:44 +0000 (16:16 +0200)
committerNeal H. Walfield <neal@g10code.com>
Tue, 30 Aug 2016 14:06:37 +0000 (16:06 +0200)
* g10/tofu.c (tofu_register): Take a list of user ids, not a single
user id.  Only register the bindings, don't compute the trust.  Thus,
change return type to an int and remove the may_ask parameter.  Update
callers.
(tofu_get_validity): Take a list of user ids, not a single user id.
Update callers.  Observe signatures made by expired user ids, but
don't include them in the trust calculation.

--
Signed-off-by: Neal H. Walfield <neal@g10code.com>
g10/tofu.c
g10/tofu.h
g10/trustdb.c

index 809dac9..da09cd5 100644 (file)
@@ -2164,8 +2164,9 @@ email_from_user_id (const char *user_id)
   return email;
 }
 
-/* Register the signature with the binding <fingerprint, USER_ID>.
-   The fingerprint is taken from the primary key packet PK.
+/* Register the signature with the bindings <fingerprint, USER_ID>,
+   for each USER_ID in USER_ID_LIST.  The fingerprint is taken from
+   the primary key packet PK.
 
    SIG_DIGEST_BIN is the binary representation of the message's
    digest.  SIG_DIGEST_BIN_LEN is its length.
@@ -2181,159 +2182,152 @@ email_from_user_id (const char *user_id)
    This is necessary if there is a conflict or the binding's policy is
    TOFU_POLICY_ASK.
 
-   This function returns the binding's trust level on return.  If an
-   error occurs, this function returns TRUST_UNKNOWN.  */
-int
-tofu_register (ctrl_t ctrl, PKT_public_key *pk, const char *user_id,
+   This function returns 0 on success and an error code if an error
+   occured.  */
+gpg_error_t
+tofu_register (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
               const byte *sig_digest_bin, int sig_digest_bin_len,
-              time_t sig_time, const char *origin, int may_ask)
+              time_t sig_time, const char *origin)
 {
+  gpg_error_t rc;
   tofu_dbs_t dbs;
   char *fingerprint = NULL;
+  strlist_t user_id;
   char *email = NULL;
   char *err = NULL;
-  int rc;
-  int trust_level = TRUST_UNKNOWN;
   char *sig_digest;
   unsigned long c;
-  int already_verified = 0;
-
-  sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len);
 
   dbs = opendbs (ctrl);
   if (! dbs)
     {
+      rc = gpg_error (GPG_ERR_GENERAL);
       log_error (_("error opening TOFU database: %s\n"),
-                 gpg_strerror (GPG_ERR_GENERAL));
-      goto die;
+                 gpg_strerror (rc));
+      return rc;
     }
 
-  fingerprint = hexfingerprint (pk, NULL, 0);
-
-  if (! *user_id)
-    {
-      log_debug ("TOFU: user id is empty.  Can't continue.\n");
-      goto die;
-    }
+  /* We do a query and then an insert.  Make sure they are atomic
+     by wrapping them in a transaction.  */
+  rc = begin_transaction (ctrl, 0);
+  if (rc)
+    return rc;
 
-  email = email_from_user_id (user_id);
+  sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len);
+  fingerprint = hexfingerprint (pk, NULL, 0);
 
   if (! origin)
     /* The default origin is simply "unknown".  */
     origin = "unknown";
 
-  /* It's necessary to get the trust so that we are certain that the
-     binding has been registered.  */
-  trust_level = get_trust (dbs, pk, fingerprint, email, user_id, may_ask);
-  if (trust_level == _tofu_GET_TRUST_ERROR)
-    /* An error.  */
+  for (user_id = user_id_list; user_id; user_id = user_id->next)
     {
-      trust_level = TRUST_UNKNOWN;
-      goto die;
-    }
+      email = email_from_user_id (user_id->d);
 
-  /* We do a query and then an insert.  Make sure they are atomic
-     by wrapping them in a transaction.  */
-  rc = begin_transaction (ctrl, 0);
-  if (rc)
-    goto die;
-
-  /* If we've already seen this signature before, then don't add
-     it again.  */
-  rc = gpgsql_stepx
-    (dbs->db, &dbs->s.register_already_seen,
-     get_single_unsigned_long_cb2, &c, &err,
-     "select count (*)\n"
-     " from signatures left join bindings\n"
-     "  on signatures.binding = bindings.oid\n"
-     " where fingerprint = ? and email = ? and sig_time = ?\n"
-     "  and sig_digest = ?",
-     SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
-     SQLITE_ARG_LONG_LONG, (long long) sig_time,
-     SQLITE_ARG_STRING, sig_digest,
-     SQLITE_ARG_END);
-  if (rc)
-    {
-      log_error (_("error reading TOFU database: %s\n"), err);
-      print_further_info ("checking existence");
-      sqlite3_free (err);
-    }
-  else if (c > 1)
-    /* Duplicates!  This should not happen.  In particular,
-       because <fingerprint, email, sig_time, sig_digest> is the
-       primary key!  */
-    log_debug ("SIGNATURES DB contains duplicate records"
-              " <%s, %s, 0x%lx, %s, %s>."
-              "  Please report.\n",
-              fingerprint, email, (unsigned long) sig_time,
-              sig_digest, origin);
-  else if (c == 1)
-    {
-      already_verified = 1;
-      if (DBG_TRUST)
-       log_debug ("Already observed the signature"
-                  " <%s, %s, 0x%lx, %s, %s>\n",
-                  fingerprint, email, (unsigned long) sig_time,
-                  sig_digest, origin);
-    }
-  else if (opt.dry_run)
-    {
-      log_info ("TOFU database update skipped due to --dry-run\n");
-    }
-  else
-    /* This is the first time that we've seen this signature.
-       Record it.  */
-    {
       if (DBG_TRUST)
-       log_debug ("TOFU: Saving signature <%s, %s, %s>\n",
-                  fingerprint, email, sig_digest);
-
-      log_assert (c == 0);
+       log_debug ("TOFU: Registering signature %s with binding"
+                   " <key: %s, user id: %s>\n",
+                  sig_digest, fingerprint, email);
+
+      /* Make sure the binding exists and record any TOFU
+         conflicts.  */
+      if (get_trust (dbs, pk, fingerprint, email, user_id->d, 0)
+          == _tofu_GET_TRUST_ERROR)
+        {
+          rc = gpg_error (GPG_ERR_GENERAL);
+          xfree (email);
+          break;
+        }
 
+      /* If we've already seen this signature before, then don't add
+         it again.  */
       rc = gpgsql_stepx
-       (dbs->db, &dbs->s.register_insert, NULL, NULL, &err,
-        "insert into signatures\n"
-        " (binding, sig_digest, origin, sig_time, time)\n"
-        " values\n"
-        " ((select oid from bindings\n"
-        "    where fingerprint = ? and email = ?),\n"
-        "  ?, ?, ?, strftime('%s', 'now'));",
-        SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
-         SQLITE_ARG_STRING, sig_digest, SQLITE_ARG_STRING, origin,
+        (dbs->db, &dbs->s.register_already_seen,
+         get_single_unsigned_long_cb2, &c, &err,
+         "select count (*)\n"
+         " from signatures left join bindings\n"
+         "  on signatures.binding = bindings.oid\n"
+         " where fingerprint = ? and email = ? and sig_time = ?\n"
+         "  and sig_digest = ?",
+         SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
          SQLITE_ARG_LONG_LONG, (long long) sig_time,
+         SQLITE_ARG_STRING, sig_digest,
          SQLITE_ARG_END);
       if (rc)
-       {
-         log_error (_("error updating TOFU database: %s\n"), err);
-          print_further_info ("insert signatures");
-         sqlite3_free (err);
-       }
+        {
+          log_error (_("error reading TOFU database: %s\n"), err);
+          print_further_info ("checking existence");
+          sqlite3_free (err);
+        }
+      else if (c > 1)
+        /* Duplicates!  This should not happen.  In particular,
+           because <fingerprint, email, sig_time, sig_digest> is the
+           primary key!  */
+        log_debug ("SIGNATURES DB contains duplicate records"
+                   " <key: %s, fingerprint: %s, time: 0x%lx, sig: %s,"
+                   " origin: %s>."
+                   "  Please report.\n",
+                   fingerprint, email, (unsigned long) sig_time,
+                   sig_digest, origin);
+      else if (c == 1)
+        {
+          if (DBG_TRUST)
+            log_debug ("Already observed the signature and binding"
+                       " <key: %s, user id: %s, time: 0x%lx, sig: %s,"
+                       " origin: %s>\n",
+                       fingerprint, email, (unsigned long) sig_time,
+                       sig_digest, origin);
+        }
+      else if (opt.dry_run)
+        {
+          log_info ("TOFU database update skipped due to --dry-run\n");
+        }
+      else
+        /* This is the first time that we've seen this signature and
+           binding.  Record it.  */
+        {
+          if (DBG_TRUST)
+            log_debug ("TOFU: Saving signature"
+                       " <key: %s, user id: %s, sig: %s>\n",
+                       fingerprint, email, sig_digest);
+
+          log_assert (c == 0);
+
+          rc = gpgsql_stepx
+            (dbs->db, &dbs->s.register_insert, NULL, NULL, &err,
+             "insert into signatures\n"
+             " (binding, sig_digest, origin, sig_time, time)\n"
+             " values\n"
+             " ((select oid from bindings\n"
+             "    where fingerprint = ? and email = ?),\n"
+             "  ?, ?, ?, strftime('%s', 'now'));",
+             SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
+             SQLITE_ARG_STRING, sig_digest, SQLITE_ARG_STRING, origin,
+             SQLITE_ARG_LONG_LONG, (long long) sig_time,
+             SQLITE_ARG_END);
+          if (rc)
+            {
+              log_error (_("error updating TOFU database: %s\n"), err);
+              print_further_info ("insert signatures");
+              sqlite3_free (err);
+            }
+        }
+
+      xfree (email);
+
+      if (rc)
+        break;
     }
 
-  /* It only matters whether we abort or commit the transaction
-     (so long as we do something) if we execute the insert.  */
   if (rc)
-    rc = rollback_transaction (ctrl);
+    rollback_transaction (ctrl);
   else
     rc = end_transaction (ctrl, 0);
-  if (rc)
-    {
-      sqlite3_free (err);
-      goto die;
-    }
 
- die:
-  if (may_ask && trust_level != TRUST_ULTIMATE)
-    /* It's only appropriate to show the statistics in an interactive
-       context.  */
-    show_statistics (dbs, fingerprint, email, user_id,
-                    already_verified ? NULL : sig_digest, NULL);
-
-  xfree (email);
   xfree (fingerprint);
   xfree (sig_digest);
 
-  return trust_level;
+  return rc;
 }
 
 /* Combine a trust level returned from the TOFU trust model with a
@@ -2431,8 +2425,9 @@ tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
 }
 
 
-/* Return the validity (TRUST_NEVER, etc.) of the binding
-   <FINGERPRINT, USER_ID>.
+/* Return the validity (TRUST_NEVER, etc.) of the bindings
+   <FINGERPRINT, USER_ID>, for each USER_ID in USER_ID_LIST.  If
+   USER_ID_LIST->FLAG is set, then the id is considered to be expired.
 
    PK is the primary key packet.
 
@@ -2442,43 +2437,80 @@ tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
 
    Returns TRUST_UNDEFINED if an error occurs.  */
 int
-tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, const char *user_id,
+tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
                   int may_ask)
 {
   tofu_dbs_t dbs;
   char *fingerprint = NULL;
-  char *email = NULL;
-  int trust_level = TRUST_UNDEFINED;
+  strlist_t user_id;
+  int trust_level = TRUST_UNKNOWN;
 
   dbs = opendbs (ctrl);
   if (! dbs)
     {
       log_error (_("error opening TOFU database: %s\n"),
                  gpg_strerror (GPG_ERR_GENERAL));
-      goto die;
+      return TRUST_UNDEFINED;
     }
 
   fingerprint = hexfingerprint (pk, NULL, 0);
 
-  if (! *user_id)
+  begin_transaction (ctrl, 0);
+
+  for (user_id = user_id_list; user_id; user_id = user_id->next)
     {
-      log_debug ("user id is empty."
-                 "  Can't get TOFU validity for this binding.\n");
-      goto die;
-    }
+      char *email = email_from_user_id (user_id->d);
 
-  email = email_from_user_id (user_id);
+      /* Always call get_trust to make sure the binding is
+         registered.  */
+      int tl = get_trust (dbs, pk, fingerprint, email, user_id->d, may_ask);
+      if (tl == _tofu_GET_TRUST_ERROR)
+        {
+          /* An error.  */
+          trust_level = TRUST_UNDEFINED;
+          xfree (email);
+          goto die;
+        }
 
-  trust_level = get_trust (dbs, pk, fingerprint, email, user_id, may_ask);
-  if (trust_level == _tofu_GET_TRUST_ERROR)
-    /* An error.  */
-    trust_level = TRUST_UNDEFINED;
+      if (DBG_TRUST)
+       log_debug ("TOFU: validity for <key: %s, user id: %s>: %s%s.\n",
+                  fingerprint, email,
+                   trust_value_to_string (tl),
+                   user_id->flags ? " (but expired)" : "");
+
+      if (user_id->flags)
+        tl = TRUST_EXPIRED;
 
-  if (may_ask && trust_level != TRUST_ULTIMATE)
-    show_statistics (dbs, fingerprint, email, user_id, NULL, NULL);
+      if (may_ask && tl != TRUST_ULTIMATE && tl != TRUST_EXPIRED)
+        show_statistics (dbs, fingerprint, email, user_id->d, NULL, NULL);
+
+      if (tl == TRUST_NEVER)
+        trust_level = TRUST_NEVER;
+      else if (tl == TRUST_EXPIRED)
+        /* Ignore expired bindings in the trust calculation.  */
+        ;
+      else if (tl > trust_level)
+        {
+          /* The expected values: */
+          log_assert (tl == TRUST_UNKNOWN || tl == TRUST_UNDEFINED
+                      || tl == TRUST_MARGINAL || tl == TRUST_FULLY
+                      || tl == TRUST_ULTIMATE);
+
+          /* We assume the following ordering:  */
+          log_assert (TRUST_UNKNOWN < TRUST_UNDEFINED);
+          log_assert (TRUST_UNDEFINED < TRUST_MARGINAL);
+          log_assert (TRUST_MARGINAL < TRUST_FULLY);
+          log_assert (TRUST_FULLY < TRUST_ULTIMATE);
+
+          trust_level = tl;
+        }
+
+      xfree (email);
+    }
 
  die:
-  xfree (email);
+  end_transaction (ctrl, 0);
+
   xfree (fingerprint);
   return trust_level;
 }
index d6854e9..b9826c9 100644 (file)
@@ -59,7 +59,7 @@ enum tofu_policy
     TOFU_POLICY_ASK = 5,
 
 
-    /* Privat evalue used only within tofu.c.  */
+    /* Privatvalue used only within tofu.c.  */
     _tofu_GET_POLICY_ERROR = 100
   };
 
@@ -72,16 +72,19 @@ const char *tofu_policy_str (enum tofu_policy policy);
    (e.g., TRUST_BAD) in light of the current configuration.  */
 int tofu_policy_to_trust_level (enum tofu_policy policy);
 
-/* Register the binding <PK, USER_ID> and the signature
-   described by SIGS_DIGEST and SIG_TIME, which it generated.  Origin
-   describes where the signed data came from, e.g., "email:claws"
-   (default: "unknown").  If MAY_ASK is 1, then this function may
-   interact with the user in the case of a conflict or if the
-   binding's policy is ask.  This function returns the binding's trust
-   level.  If an error occurs, it returns TRUST_UNKNOWN.  */
-int tofu_register (ctrl_t ctrl, PKT_public_key *pk, const char *user_id,
-                  const byte *sigs_digest, int sigs_digest_len,
-                  time_t sig_time, const char *origin, int may_ask);
+/* Register the bindings <PK, USER_ID>, for each USER_ID in
+   USER_ID_LIST, and the signature described by SIGS_DIGEST and
+   SIG_TIME, which it generated.  Origin describes where the signed
+   data came from, e.g., "email:claws" (default: "unknown").  Note:
+   this function does not interact with the user, If there is a
+   conflict, or if the binding's policy is ask, the actual interaction
+   is deferred until tofu_get_validity is called..  Set the string
+   list FLAG to indicate that a specified user id is expired.  This
+   function returns 0 on success and an error code on failure.  */
+gpg_error_t tofu_register (ctrl_t ctrl, PKT_public_key *pk,
+                           strlist_t user_id_list,
+                           const byte *sigs_digest, int sigs_digest_len,
+                           time_t sig_time, const char *origin);
 
 /* Combine a trust level returned from the TOFU trust model with a
    trust level returned by the PGP trust model.  This is primarily of
@@ -92,12 +95,15 @@ int tofu_wot_trust_combine (int tofu, int wot);
 gpg_error_t tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
                                    PKT_public_key *pk, const char *user_id);
 
-/* Determine the validity (TRUST_NEVER, etc.) of the binding
-   <PK, USER_ID>.  If MAY_ASK is 1, then this function may
-   interact with the user.  If not, TRUST_UNKNOWN is returned.  If an
-   error occurs, TRUST_UNDEFINED is returned.  */
+/* Determine the validity (TRUST_NEVER, etc.) of the binding <PK,
+   USER_ID>.  If MAY_ASK is 1, then this function may interact with
+   the user.  If not, TRUST_UNKNOWN is returned if an interaction is
+   required.  Set the string list FLAGS to indicate that a specified
+   user id is expired.  If an error occurs, TRUST_UNDEFINED is
+   returned.  */
 int tofu_get_validity (ctrl_t ctrl,
-                       PKT_public_key *pk, const char *user_id, int may_ask);
+                       PKT_public_key *pk, strlist_t user_id_list,
+                       int may_ask);
 
 /* Set the policy for all non-revoked user ids in the keyblock KB to
    POLICY.  */
index dd74d18..4181240 100644 (file)
@@ -988,7 +988,7 @@ tdb_get_validity_core (ctrl_t ctrl,
                       int may_ask)
 {
   TRUSTREC trec, vrec;
-  gpg_error_t err;
+  gpg_error_t err = 0;
   ulong recno;
 #ifdef USE_TOFU
   unsigned int tofu_validity = TRUST_UNKNOWN;
@@ -1022,21 +1022,18 @@ tdb_get_validity_core (ctrl_t ctrl,
 #ifdef USE_TOFU
   if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
     {
-      kbnode_t user_id_node = NULL;
-      kbnode_t n = NULL;       /* Silence -Wmaybe-uninitialized.  */
-      int user_ids = 0;
-      int user_ids_expired = 0;
+      kbnode_t kb = NULL;
+      kbnode_t n = NULL;
+      strlist_t user_id_list = NULL;
 
-      /* If the caller didn't supply a user id then iterate over all
-        uids.  */
+      /* If the caller didn't supply a user id then use all uids.  */
       if (! uid)
-       user_id_node = n = get_pubkeyblock (main_pk->keyid);
+       kb = n = get_pubkeyblock (main_pk->keyid);
 
-      while (uid
-            || (n = find_next_kbnode (n, PKT_USER_ID)))
+      while (uid || (n = find_next_kbnode (n, PKT_USER_ID)))
        {
-         unsigned int tl;
          PKT_user_id *user_id;
+          int expired = 0;
 
          if (uid)
            user_id = uid;
@@ -1044,8 +1041,8 @@ tdb_get_validity_core (ctrl_t ctrl,
            user_id = n->pkt->pkt.user_id;
 
           /* If the user id is revoked or expired, then skip it.  */
-         if (user_id->is_revoked || user_id->is_expired)
-           {
+          if (user_id->is_revoked || user_id->is_expired)
+            {
               if (DBG_TRUST)
                 {
                   char *s;
@@ -1060,42 +1057,48 @@ tdb_get_validity_core (ctrl_t ctrl,
                              s, user_id->name);
                 }
 
-             continue;
-           }
+              if (user_id->is_revoked)
+                continue;
 
-         user_ids ++;
+              expired = 1;
+            }
 
-         if (sig)
-           tl = tofu_register (ctrl, main_pk, user_id->name,
-                               sig->digest, sig->digest_len,
-                               sig->timestamp, "unknown",
-                               may_ask);
-         else
-           tl = tofu_get_validity (ctrl, main_pk, user_id->name, may_ask);
-
-         if (tl == TRUST_EXPIRED)
-           user_ids_expired ++;
-         else if (tl == TRUST_UNDEFINED || tl == TRUST_UNKNOWN)
-           ;
-         else if (tl == TRUST_NEVER)
-           tofu_validity = TRUST_NEVER;
-         else
-           {
-             log_assert (tl == TRUST_MARGINAL
-                          || tl == TRUST_FULLY
-                          || tl == TRUST_ULTIMATE);
+          add_to_strlist (&user_id_list, user_id->name);
+          user_id_list->flags = expired;
 
-             if (tl > tofu_validity)
-               /* XXX: We we really want the max?  */
-               tofu_validity = tl;
-           }
+          if (uid)
+            /* If the caller specified a user id, then we stop
+               now.  */
+            break;
+        }
 
-         if (uid)
-           /* If the caller specified a user id, then we stop
-              now.  */
-           break;
-       }
-      release_kbnode (user_id_node);
+      /* Process the user ids in the order they appear in the key
+         block.  */
+      strlist_rev (&user_id_list);
+
+      /* It only makes sense to observe any signature before getting
+         the validity.  This is because if the current signature
+         results in a conflict, then we damn well want to take that
+         into account.  */
+      if (sig)
+        {
+          err = tofu_register (ctrl, main_pk, user_id_list,
+                               sig->digest, sig->digest_len,
+                               sig->timestamp, "unknown");
+          if (err)
+            {
+              log_error ("TOFU: error registering signature: %s\n",
+                         gpg_strerror (err));
+
+              tofu_validity = TRUST_UNKNOWN;
+            }
+        }
+      if (! err)
+        tofu_validity = tofu_get_validity (ctrl, main_pk, user_id_list,
+                                           may_ask);
+
+      free_strlist (user_id_list);
+      release_kbnode (kb);
     }
 #endif /*USE_TOFU*/