diff --git a/g10/tofu.c b/g10/tofu.c index 6eb7f5e99..d7730a360 100644 --- a/g10/tofu.c +++ b/g10/tofu.c @@ -506,6 +506,152 @@ version_check_cb (void *cookie, int argc, char **argv, char **azColName) return 1; } +static int +check_utks (sqlite3 *db) +{ + int rc; + char *err = NULL; + struct key_item *utks; + struct key_item *ki; + int utk_count; + char *utks_string = NULL; + char keyid_str[16+1]; + long utks_unchanged = 0; + + /* An early version of the v1 format did not include the list of + * known ultimately trusted keys. + * + * This list is used to detect when the set of ultimately trusted + * keys changes. We need to detect this to invalidate the effective + * policy, which can change if an ultimately trusted key is added or + * removed. */ + rc = sqlite3_exec (db, + "create table if not exists ultimately_trusted_keys" + " (keyid);\n", + NULL, NULL, &err); + if (rc) + { + log_error (_("error creating 'ultimately_trusted_keys' TOFU table: %s\n"), + err); + sqlite3_free (err); + goto out; + } + + + utks = tdb_utks (); + for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++) + ; + + if (utk_count) + { + /* Build a list of keyids of the form "XXX","YYY","ZZZ". */ + int len = (1 + 16 + 1 + 1) * utk_count; + int o = 0; + + utks_string = xmalloc (len); + *utks_string = 0; + for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++) + { + utks_string[o ++] = '\''; + format_keyid (ki->kid, KF_LONG, + keyid_str, sizeof (keyid_str)); + memcpy (&utks_string[o], keyid_str, 16); + o += 16; + utks_string[o ++] = '\''; + utks_string[o ++] = ','; + } + utks_string[o - 1] = 0; + log_assert (o == len); + } + + rc = gpgsql_exec_printf + (db, get_single_unsigned_long_cb, &utks_unchanged, &err, + "select" + /* Removed UTKs? (Known UTKs in current UTKs.) */ + " ((select count(*) from ultimately_trusted_keys" + " where (keyid in (%s))) == %d)" + " and" + /* New UTKs? */ + " ((select count(*) from ultimately_trusted_keys" + " where keyid not in (%s)) == 0);", + utks_string ? utks_string : "", + utk_count, + utks_string ? utks_string : ""); + xfree (utks_string); + if (rc) + { + log_error (_("TOFU DB error")); + print_further_info ("checking if ultimately trusted keys changed: %s", + err); + sqlite3_free (err); + goto out; + } + + if (utks_unchanged) + goto out; + + if (DBG_TRUST) + log_debug ("TOFU: ultimately trusted keys changed.\n"); + + /* Given that the set of ultimately trusted keys + * changed, clear any cached policies. */ + rc = gpgsql_exec_printf + (db, NULL, NULL, &err, + "update bindings set effective_policy = %d;", + TOFU_POLICY_NONE); + if (rc) + { + log_error (_("TOFU DB error")); + print_further_info ("clearing cached policies: %s", err); + sqlite3_free (err); + goto out; + } + + /* Now, update the UTK table. */ + rc = sqlite3_exec (db, + "drop table ultimately_trusted_keys;", + NULL, NULL, &err); + if (rc) + { + log_error (_("TOFU DB error")); + print_further_info ("dropping ultimately_trusted_keys: %s", err); + sqlite3_free (err); + goto out; + } + + rc = sqlite3_exec (db, + "create table if not exists" + " ultimately_trusted_keys (keyid);\n", + NULL, NULL, &err); + if (rc) + { + log_error (_("TOFU DB error")); + print_further_info ("creating ultimately_trusted_keys: %s", err); + sqlite3_free (err); + goto out; + } + + for (ki = utks; ki; ki = ki->next) + { + format_keyid (ki->kid, KF_LONG, + keyid_str, sizeof (keyid_str)); + rc = gpgsql_exec_printf + (db, NULL, NULL, &err, + "insert into ultimately_trusted_keys values ('%s');", + keyid_str); + if (rc) + { + log_error (_("TOFU DB error")); + print_further_info ("updating ultimately_trusted_keys: %s", + err); + sqlite3_free (err); + goto out; + } + } + + out: + return rc; +} /* If the DB is new, initialize it. Otherwise, check the DB's version. @@ -727,6 +873,9 @@ initdb (sqlite3 *db) } } + if (! rc) + rc = check_utks (db); + if (rc) { rc = sqlite3_exec (db, "rollback;", NULL, NULL, &err); diff --git a/g10/trustdb.c b/g10/trustdb.c index edae6ef45..51a8f2217 100644 --- a/g10/trustdb.c +++ b/g10/trustdb.c @@ -324,6 +324,13 @@ tdb_keyid_is_utk (u32 *kid) return 0; } + +/* Return the list of ultimately trusted keys. */ +struct key_item * +tdb_utks (void) +{ + return utk_list; +} /********************************************* *********** TrustDB stuff ******************* diff --git a/g10/trustdb.h b/g10/trustdb.h index 77aa79da6..45ecc56ab 100644 --- a/g10/trustdb.h +++ b/g10/trustdb.h @@ -117,6 +117,9 @@ void tdb_register_trusted_keyid (u32 *keyid); void tdb_register_trusted_key (const char *string); /* Returns whether KID is on the list of ultimately trusted keys. */ int tdb_keyid_is_utk (u32 *kid); +/* Return the list of ultimately trusted keys. The caller must not + * modify this list nor must it free the list. */ +struct key_item *tdb_utks (void); void check_trustdb (ctrl_t ctrl); void update_trustdb (ctrl_t ctrl); int setup_trustdb( int level, const char *dbname ); diff --git a/tests/openpgp/tofu.scm b/tests/openpgp/tofu.scm index 2a04d13a2..e1fa00191 100755 --- a/tests/openpgp/tofu.scm +++ b/tests/openpgp/tofu.scm @@ -248,6 +248,21 @@ ;; Alice has an ultimately trusted key and she signs Bob's key. Then ;; Bob adds a new user id, "Alice". TOFU should now detect a ;; conflict, because Alice only signed Bob's "Bob" user id. +;; +;; +;; The Alice key: +;; pub rsa2048 2016-10-11 [SC] +;; 1938C3A0E4674B6C217AC0B987DB2814EC38277E +;; uid [ultimate] Spy Cow +;; sub rsa2048 2016-10-11 [E] +;; +;; The Bob key: +;; +;; pub rsa2048 2016-10-11 [SC] +;; DC463A16E42F03240D76E8BA8B48C6BD871C2247 +;; uid [ full ] Spy R. Cow +;; uid [ full ] Spy R. Cow +;; sub rsa2048 2016-10-11 [E] (display "Checking UTK sigs...\n") (define GPG `(,(tool 'gpg) --no-permission-warning @@ -279,12 +294,18 @@ (call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDB "-1.gpg")))) (display "<\n") +(checkpolicy KEYA "auto") +(checkpolicy KEYB "auto") + ;; Import the cross sigs. (display " > Adding cross signatures. ") (call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDA "-2.gpg")))) (call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDB "-2.gpg")))) (display "<\n") +(checkpolicy KEYA "auto") +(checkpolicy KEYB "auto") + ;; Make KEYA ultimately trusted. (display (string-append " > Marking " KEYA " as ultimately trusted. ")) (pipe:do