diff --git a/NEWS b/NEWS index c19c1cd12..2ee35e6c8 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ Noteworthy changes in version 2.1.11 (unreleased) ------------------------------------------------- + * gpg: Don't check for ambigious or non-matching key specification in + the config file or given to --encrypt-to. This feature will return + in 2.3.x. Noteworthy changes in version 2.1.10 (2015-12-04) ------------------------------------------------- diff --git a/g10/getkey.c b/g10/getkey.c index ad0c207bf..45c3f512b 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -366,6 +366,282 @@ getkey_disable_caches () } +void +pubkey_free (struct pubkey *key) +{ + xfree (key->pk); + release_kbnode (key->keyblock); + xfree (key); +} + +void +pubkeys_free (struct pubkey *keys) +{ + while (keys) + { + struct pubkey *next = keys->next; + pubkey_free (keys); + keys = next; + } +} + +/* Returns all keys that match the search specfication SEARCH_TERMS. + + This function also checks for and warns about duplicate entries in + the keydb, which can occur if the user has configured multiple + keyrings or keyboxes or if a keyring or keybox was corrupted. + + Note: SEARCH_TERMS will not be expanded (i.e., it may not be a + group). + + USE is the operation for which the key is required. It must be + either PUBKEY_USAGE_ENC, PUBKEY_USAGE_SIG, PUBKEY_USAGE_CERT or + PUBKEY_USAGE_AUTH. + + XXX: Currently, only PUBKEY_USAGE_ENC and PUBKEY_USAGE_SIG are + implemented. + + INCLUDE_UNUSABLE indicates whether disabled keys are allowed. + (Recipients specified with --encrypt-to and --hidden-encrypt-to may + be disabled. It is possible to edit disabled keys.) + + SOURCE is the context in which SEARCH_TERMS was specified, e.g., + "--encrypt-to", etc. If this function is called interactively, + then this should be NULL. + + If WARN_POSSIBLY_AMBIGUOUS is set, then emits a warning if the user + does not specify a long key id or a fingerprint. + + The results are placed in *KEYS. *KEYS must be NULL! */ +gpg_error_t +get_pubkeys (ctrl_t ctrl, + char *search_terms, int use, int include_unusable, char *source, + int warn_possibly_ambiguous, + struct pubkey **keys) +{ + /* We show a warning when a key appears multiple times in the DB. + This can happen for two reasons: + + - The user has configured multiple keyrings or keyboxes. + + - The keyring or keybox has been corrupted in some way, e.g., a + bug or a random process changing them. + + For each duplicate, we only want to show the key once. Hence, + this list. */ + static strlist_t key_dups; + + /* USE transformed to a string. */ + char *use_str; + + gpg_error_t err; + + KEYDB_SEARCH_DESC desc; + + GETKEY_CTX ctx; + struct pubkey *results = NULL; + struct pubkey *r; + + int count; + + char fingerprint[2 * MAX_FINGERPRINT_LEN + 1]; + + if (DBG_LOOKUP) + { + log_debug ("\n"); + log_debug ("%s: Checking %s=%s\n", + __func__, source ? source : "user input", search_terms); + } + + if (*keys) + log_bug ("%s: KEYS should be NULL!\n", __func__); + + switch (use) + { + case PUBKEY_USAGE_ENC: use_str = "encrypt"; break; + case PUBKEY_USAGE_SIG: use_str = "sign"; break; + case PUBKEY_USAGE_CERT: use_str = "cetify"; break; + case PUBKEY_USAGE_AUTH: use_str = "authentication"; break; + default: log_bug ("%s: Bad value for USE (%d)\n", __func__, use); + } + + if (use == PUBKEY_USAGE_CERT || use == PUBKEY_USAGE_AUTH) + log_bug ("%s: use=%s is unimplemented.\n", __func__, use_str); + + err = classify_user_id (search_terms, &desc, 1); + if (err) + { + log_info (_("key \"%s\" not found: %s\n"), + search_terms, gpg_strerror (err)); + if (!opt.quiet && source) + log_info (_("(check argument of option '%s')\n"), source); + goto out; + } + + if (warn_possibly_ambiguous + && ! (desc.mode == KEYDB_SEARCH_MODE_LONG_KID + || desc.mode == KEYDB_SEARCH_MODE_FPR16 + || desc.mode == KEYDB_SEARCH_MODE_FPR20 + || desc.mode == KEYDB_SEARCH_MODE_FPR)) + { + log_info (_("Warning: '%s' should be a long key ID or a fingerprint\n"), + search_terms); + if (!opt.quiet && source) + log_info (_("(check argument of option '%s')\n"), source); + } + + /* Gather all of the results. */ + ctx = NULL; + count = 0; + do + { + PKT_public_key *pk = xmalloc_clear (sizeof *pk); + KBNODE kb; + pk->req_usage = use; + + if (! ctx) + err = get_pubkey_byname (ctrl, &ctx, pk, search_terms, &kb, NULL, + include_unusable, 1); + else + err = getkey_next (ctx, pk, &kb); + + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + /* No more results. */ + { + xfree (pk); + break; + } + else if (err) + /* An error (other than "not found"). */ + { + log_error (_("error looking up: %s\n"), + gpg_strerror (err)); + xfree (pk); + break; + } + + /* Another result! */ + count ++; + + r = xmalloc_clear (sizeof (*r)); + r->pk = pk; + r->keyblock = kb; + r->next = results; + results = r; + } + while (ctx); + getkey_end (ctx); + + if (DBG_LOOKUP) + { + log_debug ("%s resulted in %d matches.\n", search_terms, count); + for (r = results; r; r = r->next) + log_debug (" %s\n", + hexfingerprint (r->keyblock->pkt->pkt.public_key, + fingerprint, sizeof (fingerprint))); + } + + if (! results && gpg_err_code (err) == GPG_ERR_NOT_FOUND) + /* No match. */ + { + if (DBG_LOOKUP) + log_debug ("%s: '%s' not found.\n", __func__, search_terms); + + log_info (_("key \"%s\" not found\n"), search_terms); + if (!opt.quiet && source) + log_info (_("(check argument of option '%s')\n"), source); + + goto out; + } + else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + /* No more matches. */ + ; + else if (err) + /* Some other error. An error message was already printed + out. Free RESULTS and continue. */ + goto out; + + /* Check for duplicates. */ + if (DBG_LOOKUP) + log_debug ("%s: Checking results of %s='%s' for dups\n", + __func__, source ? source : "user input", search_terms); + count = 0; + for (r = results; r; r = r->next) + { + struct pubkey **prevp; + struct pubkey *next; + struct pubkey *r2; + int dups = 0; + + prevp = &r->next; + next = r->next; + while ((r2 = next)) + { + if (cmp_public_keys (r->keyblock->pkt->pkt.public_key, + r2->keyblock->pkt->pkt.public_key) != 0) + /* Not a dup. */ + { + prevp = &r2->next; + next = r2->next; + continue; + } + + dups ++; + count ++; + + /* Remove R2 from the list. */ + *prevp = r2->next; + release_kbnode (r2->keyblock); + next = r2->next; + xfree (r2); + } + + if (dups) + { + hexfingerprint (r->keyblock->pkt->pkt.public_key, + fingerprint, sizeof fingerprint); + if (! strlist_find (key_dups, fingerprint)) + { + char fingerprint_formatted[MAX_FORMATTED_FINGERPRINT_LEN + 1]; + + log_info (_("Warning: %s appears in the keyring %d times.\n"), + format_hexfingerprint (fingerprint, + fingerprint_formatted, + sizeof fingerprint_formatted), + 1 + dups); + add_to_strlist (&key_dups, fingerprint); + } + } + } + + if (DBG_LOOKUP && count) + { + log_debug ("After removing %d dups:\n", count); + for (r = results, count = 0; r; r = r->next) + log_debug (" %d: %s\n", + count, + hexfingerprint (r->keyblock->pkt->pkt.public_key, + fingerprint, sizeof fingerprint)); + } + + out: + if (err) + { + while ((r = results)) + { + results = results->next; + pubkey_free (r); + release_kbnode (r->keyblock); + xfree (r); + } + } + else + *keys = results; + + return err; +} + + static void pk_from_block (GETKEY_CTX ctx, PKT_public_key * pk, KBNODE keyblock, KBNODE found_key) diff --git a/g10/gpg.c b/g10/gpg.c index 71f44eddf..0e61238a3 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -2101,506 +2101,6 @@ get_default_configname (void) return configname; } -struct result -{ - struct result *next; - kbnode_t keyblock; - int processed; -}; - -/* We show a warning when a key appears multiple times in the DB. */ -static strlist_t key_dups; - -static gpg_error_t -check_user_ids (strlist_t *sp, - int warn_possibly_ambiguous, - int error_if_not_found) -{ - strlist_t s = *sp; - strlist_t s2 = NULL; - strlist_t t; - - gpg_error_t rc = 0; - gpg_error_t err; - - KEYDB_HANDLE hd = NULL; - - char fingerprint_formatted[MAX_FORMATTED_FINGERPRINT_LEN + 1]; - - /* A quick check to avoid allocating a new strlist if we can skip - all keys. Handles also the case of !SP. See below for details. */ - for (t = s; t && (!(t->flags & PK_LIST_CONFIG) - && !(t->flags & PK_LIST_ENCRYPT_TO)); t = t->next) - ; - if (!t) - return 0; - - for (t = s; t; t = t->next) - { - const char *option_str; - int option; - - KEYDB_SEARCH_DESC desc; - struct result *results = NULL; - struct result *r; - - int count; - - /* We also potentially need a ! at the end. */ - char fingerprint[2 * MAX_FINGERPRINT_LEN + 1 + 1]; - char fingerprint2[2 * MAX_FINGERPRINT_LEN + 1]; - - KBNODE best_kb; - PKT_public_key *best_pk; - - /* Whether the key is for encryption or signing. */ - int encrypt = 1; - - /* If the key has been given on the command line and it has not - been given by one of the encrypt-to options, we skip the - checks. The reason is that the actual key selection code - does its own checks and provides proper status message to the - caller to detect the wrong keys. */ - if (!(t->flags & PK_LIST_CONFIG) && !(t->flags & PK_LIST_ENCRYPT_TO)) - { - add_to_strlist (&s2, t->d); - s2->flags = t->flags; - continue; - } - - option = t->flags >> PK_LIST_SHIFT; - switch (option) - { - case oDefaultKey: - option_str = "--default-key"; - encrypt = 0; - break; - case oLocalUser: - option_str = "--local-user"; - encrypt = 0; - break; - - case oEncryptTo: option_str = "--encrypt-to"; break; - case oHiddenEncryptTo: option_str = "--hidden-encrypt-to"; break; - case oEncryptToDefaultKey: option_str = "--encrypt-to-default-key"; break; - case oRecipient: option_str = "--recipient"; break; - case oHiddenRecipient: option_str = "--hidden-recipient"; break; - default: - log_bug ("Unsupport option: %d\n", (t->flags >> PK_LIST_SHIFT)); - } - - if (DBG_LOOKUP) - { - log_debug ("\n"); - log_debug ("%s: Checking %s=%s\n", __func__, option_str, t->d); - } - - err = classify_user_id (t->d, &desc, 1); - if (err) - { - if (! rc) - rc = err; - - log_error (_("key \"%s\" not found: %s\n"), - t->d, gpg_strerror (err)); - if (!opt.quiet) - log_info (_("(check argument of option '%s')\n"), option_str); - continue; - } - - if (warn_possibly_ambiguous - && ! (desc.mode == KEYDB_SEARCH_MODE_LONG_KID - || desc.mode == KEYDB_SEARCH_MODE_FPR16 - || desc.mode == KEYDB_SEARCH_MODE_FPR20 - || desc.mode == KEYDB_SEARCH_MODE_FPR)) - log_info (_("Warning: value '%s' for option '%s'" - " should be a long key ID or a fingerprint\n"), - t->d, option_str); - - if (! hd) - { - hd = keydb_new (); - if (!hd) - { - rc = gpg_error_from_syserror (); - break; - } - } - else - keydb_search_reset (hd); - - /* Gather all of the results. */ - count = 0; - while (1) - { - KBNODE kb; - - err = keydb_search (hd, &desc, 1, NULL); - if (gpg_err_code (err) == GPG_ERR_NOT_FOUND - || gpg_err_code (err) == GPG_ERR_EOF) - /* No more results. */ - break; - else if (err) - /* An error (other than "not found"). */ - { - log_error (_("error searching the keyring: %s\n"), - gpg_strerror (err)); - break; - } - - err = keydb_get_keyblock (hd, &kb); - if (err) - { - log_error (_("error reading keyblock: %s\n"), gpg_strerror (err)); - break; - } - - /* Another result! */ - count ++; - - r = xmalloc_clear (sizeof (*r)); - r->keyblock = kb; - r->next = results; - results = r; - } - - if (DBG_LOOKUP) - { - log_debug ("%s resulted in %d matches.\n", t->d, count); - for (r = results; r; r = r->next) - log_debug (" %s\n", - hexfingerprint (r->keyblock->pkt->pkt.public_key, - fingerprint, sizeof (fingerprint))); - } - - if (! results && (gpg_err_code (err) == GPG_ERR_NOT_FOUND - || gpg_err_code (err) == GPG_ERR_EOF)) - /* No match. */ - { - if (DBG_LOOKUP) - log_debug ("%s: '%s' not found.\n", __func__, t->d); - - if (error_if_not_found) - { - if (! rc) - rc = err; - - log_error (_("key \"%s\" not found\n"), t->d); - if (!opt.quiet) - log_info (_("(check argument of option '%s')\n"), option_str); - } - continue; - } - else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND - || gpg_err_code (err) == GPG_ERR_EOF) - /* No more matches. */ - ; - else if (err) - /* Some other error. An error message was already printed - out. Free RESULTS and continue. */ - { - if (! rc) - rc = err; - - while ((r = results)) - { - results = results->next; - release_kbnode (r->keyblock); - xfree (r); - } - - continue; - } - - /* Check for duplicates. */ - - if (DBG_LOOKUP) - log_debug ("%s: Checking results of %s='%s' for dups\n", - __func__, option_str, t->d); - while (1) - { - struct result **prevp; - struct result *next; - struct result *r2; - int dups = 0; - - /* After checking a result, we set R->PROCESSED. Find the - next unprocessed result. */ - for (r = results; r; r = r->next) - if (! r->processed) - break; - - if (! r) - /* There is nothing left to check. */ - break; - - hexfingerprint (r->keyblock->pkt->pkt.public_key, - fingerprint, sizeof fingerprint); - r->processed = 1; - - prevp = &results; - next = results; - while ((r2 = next)) - { - if (r2->processed) - { - prevp = &r2->next; - next = r2->next; - continue; - } - - hexfingerprint (r2->keyblock->pkt->pkt.public_key, - fingerprint2, sizeof fingerprint2); - - if (strcmp (fingerprint, fingerprint2) != 0) - /* Not a dup. */ - { - prevp = &r2->next; - next = r2->next; - continue; - } - - dups ++; - - /* Remove R2 from the list. */ - *prevp = r2->next; - release_kbnode (r2->keyblock); - next = r2->next; - xfree (r2); - } - - if (dups && ! strlist_find (key_dups, fingerprint)) - { - log_info (_("Warning: %s appears in the keyring %d times.\n"), - format_hexfingerprint (fingerprint, - fingerprint_formatted, - sizeof fingerprint_formatted), - 1 + dups); - add_to_strlist (&key_dups, fingerprint); - } - } - - if (DBG_LOOKUP) - { - log_debug ("After removing dups:\n"); - for (r = results, count = 0; r; r = r->next) - { - count ++; - log_debug (" %d: %s\n", - count, - hexfingerprint (r->keyblock->pkt->pkt.public_key, - fingerprint, sizeof fingerprint)); - } - } - - /* Now we find the best key. */ - assert (results); - /* Prune invalid keys. */ - { - int ambiguous = 0; - - if (DBG_LOOKUP) - log_debug ("Pruning bad keys.\n"); - - best_pk = NULL; - for (r = results; r; r = r->next) - { - KBNODE kb = r->keyblock; - PKT_public_key *pk = kb->pkt->pkt.public_key; - KBNODE n; - - /* Merge in the data from the self sigs so that things - like the revoked status are available. */ - merge_keys_and_selfsig (kb); - - if (/* Using disabled keys with --encrypt-to is allowed. */ - ! (option == oEncryptTo || option == oHiddenEncryptTo) - && pk_is_disabled (pk)) - { - if (DBG_LOOKUP) - log_debug (" Skipping disabled key: %s\n", - hexfingerprint (pk, fingerprint, - sizeof fingerprint)); - continue; - } - if (pk->flags.revoked) - { - if (DBG_LOOKUP) - log_debug (" Skipping revoked key: %s\n", - hexfingerprint (pk, fingerprint, - sizeof fingerprint)); - continue; - } - if (pk->has_expired) - { - if (DBG_LOOKUP) - log_debug (" Skipping expired key: %s\n", - hexfingerprint (pk, fingerprint, - sizeof fingerprint)); - continue; - } - - /* Check for the required encryption or signing - capability. */ - n = kb; - do - { - PKT_public_key *key = n->pkt->pkt.public_key; - - if ((/* Using disabled keys with --encrypt-to is allowed. */ - pk_is_disabled (key) - && ! (option == oEncryptTo - || option == oHiddenEncryptTo)) - || key->flags.revoked - || key->has_expired) - /* Invalid. */ - continue; - - if (encrypt && ! (key->pubkey_usage & PUBKEY_USAGE_ENC)) - continue; - if (! encrypt && ! (key->pubkey_usage & PUBKEY_USAGE_SIG)) - continue; - - /* Key passes basic tests. */ - break; - } - while ((n = find_next_kbnode (n, PKT_PUBLIC_SUBKEY))); - - if (! n) - { - if (DBG_LOOKUP) - log_debug (" Skipping %s, which does not have %s capability.\n", - hexfingerprint (r->keyblock->pkt->pkt.public_key, - fingerprint, sizeof fingerprint), - encrypt ? "encrypt" : "sign"); - continue; - } - else if (DBG_LOOKUP) - log_debug (" %s is valid and has %s capability.\n", - hexfingerprint (r->keyblock->pkt->pkt.public_key, - fingerprint, sizeof fingerprint), - encrypt ? "encrypt" : "sign"); - - - if (! best_pk) - { - best_pk = pk; - best_kb = kb; - continue; - } - - /* We have multiple candidates. Prefer the newer key. - - XXX: we should also consider key capabilities (if we - are encrypting to the key, does it have an encryption - capability?). - - XXX: if we are signing, then we should consider the - key that is actually available (e.g., if one is on a - smart card). */ - ambiguous = 1; - if (best_pk->timestamp < pk->timestamp) - best_pk = pk; - } - - if (! results) - { - if (encrypt) - log_error (_("%s: no matching keys are valid encryption keys"), - t->d); - else - log_error (_("%s: no matching keys are valid signing keys"), - t->d); - if (!opt.quiet) - log_info (_("(check argument of option '%s')\n"), option_str); - continue; - } - - if (ambiguous) - { - /* TRANSLATORS: The %s prints a key specification which - for example has been given at the command line. - Lines with fingerprints are printed after this - message. */ - log_error (_("key specification '%s' is ambiguous\n"), - t->d); - if (!opt.quiet) - log_info (_("(check argument of option '%s')\n"), - option_str); - - log_info (_("'%s' matches at least:\n"), t->d); - - for (r = results; r; r = r->next) - log_info (" %s\n", - format_hexfingerprint - (hexfingerprint (r->keyblock->pkt->pkt.public_key, - fingerprint, sizeof fingerprint), - fingerprint_formatted, - sizeof fingerprint_formatted)); - - if (! rc) - rc = GPG_ERR_AMBIGUOUS_NAME; - } - } - - if ((desc.mode == KEYDB_SEARCH_MODE_SHORT_KID - || desc.mode == KEYDB_SEARCH_MODE_LONG_KID - || desc.mode == KEYDB_SEARCH_MODE_FPR16 - || desc.mode == KEYDB_SEARCH_MODE_FPR20) - && strchr (t->d, '!')) - /* Exact search. In this case we want to set FINGERPRINT not - to the primary key, but the key (primary or sub) that - matched the search criteria. Note: there will always be - exactly one match. */ - { - kbnode_t n = best_kb; - PKT_public_key *match = NULL; - int i; - - do - { - if ((n->flag & 1)) - /* The matched node. */ - { - assert (! match); - match = n->pkt->pkt.public_key; - } - } - while ((n = find_next_kbnode (n, PKT_PUBLIC_SUBKEY))); - assert (match); - - hexfingerprint (match, fingerprint, sizeof fingerprint); - i = strlen (fingerprint); - fingerprint[i] = '!'; - fingerprint[i + 1] = '\0'; - } - else - hexfingerprint (best_pk, fingerprint, sizeof fingerprint); - - add_to_strlist (&s2, fingerprint); - s2->flags = s->flags; - - { - struct result *next = results; - while ((r = next)) - { - next = r->next; - release_kbnode (r->keyblock); - xfree (r); - } - } - } - - strlist_rev (&s2); - - keydb_release (hd); - - free_strlist (s); - *sp = s2; - return rc; -} - - int main (int argc, char **argv) { @@ -4280,18 +3780,6 @@ main (int argc, char **argv) break; } - { - rc = check_user_ids (&locusr, 1, 1); - if (rc) - g10_exit (1); - rc = check_user_ids (&remusr, 0, 1); - if (rc) - g10_exit (1); - rc = check_user_ids (&opt.def_secret_key, 1, 0); - if (rc) - g10_exit (1); - } - /* The command dispatcher. */ switch( cmd ) { diff --git a/g10/keydb.h b/g10/keydb.h index 556b53726..66bfa57c0 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -485,6 +485,29 @@ int get_pubkey_fast ( PKT_public_key *pk, u32 *keyid ); using merge_selfsigs. */ KBNODE get_pubkeyblock( u32 *keyid ); +/* A list used by get_pubkeys to gather all of the matches. */ +struct pubkey +{ + struct pubkey *next; + /* The key to use (either the public key or the subkey). */ + PKT_public_key *pk; + kbnode_t keyblock; +}; + +/* Free a single key. This does not remove key from any list! */ +void pubkey_free (struct pubkey *key); + +/* Free a list of public keys. */ +void pubkeys_free (struct pubkey *keys); + +/* Returns all keys that match the search specfication SEARCH_TERMS. + The returned keys should be freed using pubkeys_free. */ +gpg_error_t +get_pubkeys (ctrl_t ctrl, + char *search_terms, int use, int include_unusable, char *source, + int warn_possibly_ambiguous, + struct pubkey **keys); + /* Find a public key identified by the name NAME. If name appears to be a valid valid RFC822 mailbox (i.e., email diff --git a/g10/pkclist.c b/g10/pkclist.c index b6a213fe2..d7e78cb7c 100644 --- a/g10/pkclist.c +++ b/g10/pkclist.c @@ -914,6 +914,13 @@ build_pk_list (ctrl_t ctrl, strlist_t rcpts, PK_LIST *ret_pk_list) else remusr = rcpts; + /* XXX: Change this function to use get_pubkeys instead of + get_pubkey_byname to detect ambiguous key specifications and warn + about duplicate keyblocks. For ambiguous key specifications on + the command line or provided interactively, prompt the user to + select the best key. If a key specification is ambiguous and we + are in batch mode, die. */ + if (opt.encrypt_to_default_key) { static int warned; diff --git a/g10/skclist.c b/g10/skclist.c index 3d137b261..1eb0633fd 100644 --- a/g10/skclist.c +++ b/g10/skclist.c @@ -120,6 +120,13 @@ build_sk_list (ctrl_t ctrl, gpg_error_t err; SK_LIST sk_list = NULL; + /* XXX: Change this function to use get_pubkeys instead of + getkey_byname to detect ambiguous key specifications and warn + about duplicate keyblocks. For ambiguous key specifications on + the command line or provided interactively, prompt the user to + select the best key. If a key specification is ambiguous and we + are in batch mode, die. */ + if (!locusr) /* No user ids given - use the default key. */ { PKT_public_key *pk;