1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-05-31 22:18:03 +02:00

gpg: Refresh expired keys originating from the WKD.

* g10/getkey.c (getkey_ctx_s): New field found_via_akl.
(get_pubkey_byname): Set it.
(only_expired_enc_subkeys): New.
(get_best_pubkey_byname): Add support to refresh expired keys from the
WKD.
--

A little drawback of that code is that if the WKD has no update for an
expired key each access of the key will trigger a WKD lookup (unless
cached by the dirmngr).  To avoid this we need to record the last time
we have checked for an update but that would in turn require that we
update the keyring for each check.  We defer this until we have a
better key database which allows for fast updates of meta data.

Testing the code is currently a bit cumbersome because it requires to
update a key in the WKD several times.  Eventually we we need a
network emulation layer to provide sample data for the regression
tests.

GnuPG-bug-id: 2917
Signed-off-by: Werner Koch <wk@gnupg.org>
(cherry picked from commit 7f172404bf)
This commit is contained in:
Werner Koch 2018-08-28 15:22:35 +02:00
parent 11a9fe1c58
commit 0709f358cd
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
2 changed files with 99 additions and 17 deletions

View File

@ -88,6 +88,9 @@ struct getkey_ctx_s
their address used in ITEMS. */ their address used in ITEMS. */
strlist_t extra_list; strlist_t extra_list;
/* Hack to return the mechanism (AKL_foo) used to find the key. */
int found_via_akl;
/* Part of the search criteria: The low-level search specification /* Part of the search criteria: The low-level search specification
as passed to keydb_search. */ as passed to keydb_search. */
int nitems; int nitems;
@ -1265,6 +1268,7 @@ get_pubkey_byname (ctrl_t ctrl, GETKEY_CTX * retctx, PKT_public_key * pk,
int is_mbox; int is_mbox;
int nodefault = 0; int nodefault = 0;
int anylocalfirst = 0; int anylocalfirst = 0;
int mechanism_type = AKL_NODEFAULT;
/* If RETCTX is not NULL, then RET_KDBHD must be NULL. */ /* If RETCTX is not NULL, then RET_KDBHD must be NULL. */
log_assert (retctx == NULL || ret_kdbhd == NULL); log_assert (retctx == NULL || ret_kdbhd == NULL);
@ -1354,18 +1358,19 @@ get_pubkey_byname (ctrl_t ctrl, GETKEY_CTX * retctx, PKT_public_key * pk,
size_t fpr_len; size_t fpr_len;
int did_akl_local = 0; int did_akl_local = 0;
int no_fingerprint = 0; int no_fingerprint = 0;
const char *mechanism = "?"; const char *mechanism_string = "?";
switch (akl->type) mechanism_type = akl->type;
switch (mechanism_type)
{ {
case AKL_NODEFAULT: case AKL_NODEFAULT:
/* This is a dummy mechanism. */ /* This is a dummy mechanism. */
mechanism = "None"; mechanism_string = "None";
rc = GPG_ERR_NO_PUBKEY; rc = GPG_ERR_NO_PUBKEY;
break; break;
case AKL_LOCAL: case AKL_LOCAL:
mechanism = "Local"; mechanism_string = "Local";
did_akl_local = 1; did_akl_local = 1;
if (retctx) if (retctx)
{ {
@ -1379,35 +1384,35 @@ get_pubkey_byname (ctrl_t ctrl, GETKEY_CTX * retctx, PKT_public_key * pk,
break; break;
case AKL_CERT: case AKL_CERT:
mechanism = "DNS CERT"; mechanism_string = "DNS CERT";
glo_ctrl.in_auto_key_retrieve++; glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_cert (ctrl, name, 0, &fpr, &fpr_len); rc = keyserver_import_cert (ctrl, name, 0, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--; glo_ctrl.in_auto_key_retrieve--;
break; break;
case AKL_PKA: case AKL_PKA:
mechanism = "PKA"; mechanism_string = "PKA";
glo_ctrl.in_auto_key_retrieve++; glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_pka (ctrl, name, &fpr, &fpr_len); rc = keyserver_import_pka (ctrl, name, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--; glo_ctrl.in_auto_key_retrieve--;
break; break;
case AKL_DANE: case AKL_DANE:
mechanism = "DANE"; mechanism_string = "DANE";
glo_ctrl.in_auto_key_retrieve++; glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_cert (ctrl, name, 1, &fpr, &fpr_len); rc = keyserver_import_cert (ctrl, name, 1, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--; glo_ctrl.in_auto_key_retrieve--;
break; break;
case AKL_WKD: case AKL_WKD:
mechanism = "WKD"; mechanism_string = "WKD";
glo_ctrl.in_auto_key_retrieve++; glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_wkd (ctrl, name, 0, &fpr, &fpr_len); rc = keyserver_import_wkd (ctrl, name, 0, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--; glo_ctrl.in_auto_key_retrieve--;
break; break;
case AKL_LDAP: case AKL_LDAP:
mechanism = "LDAP"; mechanism_string = "LDAP";
glo_ctrl.in_auto_key_retrieve++; glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_ldap (ctrl, name, &fpr, &fpr_len); rc = keyserver_import_ldap (ctrl, name, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--; glo_ctrl.in_auto_key_retrieve--;
@ -1420,7 +1425,7 @@ get_pubkey_byname (ctrl_t ctrl, GETKEY_CTX * retctx, PKT_public_key * pk,
* and getting a whole lot of keys back. */ * and getting a whole lot of keys back. */
if (keyserver_any_configured (ctrl)) if (keyserver_any_configured (ctrl))
{ {
mechanism = "keyserver"; mechanism_string = "keyserver";
glo_ctrl.in_auto_key_retrieve++; glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_name (ctrl, name, &fpr, &fpr_len, rc = keyserver_import_name (ctrl, name, &fpr, &fpr_len,
opt.keyserver); opt.keyserver);
@ -1428,7 +1433,7 @@ get_pubkey_byname (ctrl_t ctrl, GETKEY_CTX * retctx, PKT_public_key * pk,
} }
else else
{ {
mechanism = "Unconfigured keyserver"; mechanism_string = "Unconfigured keyserver";
rc = GPG_ERR_NO_PUBKEY; rc = GPG_ERR_NO_PUBKEY;
} }
break; break;
@ -1437,7 +1442,7 @@ get_pubkey_byname (ctrl_t ctrl, GETKEY_CTX * retctx, PKT_public_key * pk,
{ {
struct keyserver_spec *keyserver; struct keyserver_spec *keyserver;
mechanism = akl->spec->uri; mechanism_string = akl->spec->uri;
keyserver = keyserver_match (akl->spec); keyserver = keyserver_match (akl->spec);
glo_ctrl.in_auto_key_retrieve++; glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_name (ctrl, rc = keyserver_import_name (ctrl,
@ -1499,13 +1504,13 @@ get_pubkey_byname (ctrl_t ctrl, GETKEY_CTX * retctx, PKT_public_key * pk,
/* Key found. */ /* Key found. */
if (opt.verbose) if (opt.verbose)
log_info (_("automatically retrieved '%s' via %s\n"), log_info (_("automatically retrieved '%s' via %s\n"),
name, mechanism); name, mechanism_string);
break; break;
} }
if (gpg_err_code (rc) != GPG_ERR_NO_PUBKEY if (gpg_err_code (rc) != GPG_ERR_NO_PUBKEY
|| opt.verbose || no_fingerprint) || opt.verbose || no_fingerprint)
log_info (_("error retrieving '%s' via %s: %s\n"), log_info (_("error retrieving '%s' via %s: %s\n"),
name, mechanism, name, mechanism_string,
no_fingerprint ? _("No fingerprint") : gpg_strerror (rc)); no_fingerprint ? _("No fingerprint") : gpg_strerror (rc));
} }
} }
@ -1521,6 +1526,7 @@ get_pubkey_byname (ctrl_t ctrl, GETKEY_CTX * retctx, PKT_public_key * pk,
{ {
log_assert (!(*retctx)->extra_list); log_assert (!(*retctx)->extra_list);
(*retctx)->extra_list = namelist; (*retctx)->extra_list = namelist;
(*retctx)->found_via_akl = mechanism_type;
} }
else else
free_strlist (namelist); free_strlist (namelist);
@ -1568,6 +1574,34 @@ subkey_is_ok (const PKT_public_key *sub)
return ! sub->flags.revoked && sub->flags.valid && ! sub->flags.disabled; return ! sub->flags.revoked && sub->flags.valid && ! sub->flags.disabled;
} }
/* Return true if KEYBLOCK has only expired encryption subkyes. Note
* that the function returns false if the key has no encryption
* subkeys at all or the subkecys are revoked. */
static int
only_expired_enc_subkeys (kbnode_t keyblock)
{
kbnode_t node;
PKT_public_key *sub;
int any = 0;
for (node = find_next_kbnode (keyblock, PKT_PUBLIC_SUBKEY);
node; node = find_next_kbnode (node, PKT_PUBLIC_SUBKEY))
{
sub = node->pkt->pkt.public_key;
if (!(sub->pubkey_usage & PUBKEY_USAGE_ENC))
continue;
if (!subkey_is_ok (sub))
continue;
any = 1;
if (!sub->has_expired)
return 0;
}
return any? 1 : 0;
}
/* Finally this function compares a NEW key to the former candidate /* Finally this function compares a NEW key to the former candidate
* OLD. Returns < 0 if the old key is worse, > 0 if the old key is * OLD. Returns < 0 if the old key is worse, > 0 if the old key is
@ -1640,10 +1674,23 @@ get_best_pubkey_byname (ctrl_t ctrl, GETKEY_CTX *retctx, PKT_public_key *pk,
{ {
gpg_error_t err; gpg_error_t err;
struct getkey_ctx_s *ctx = NULL; struct getkey_ctx_s *ctx = NULL;
int is_mbox = is_valid_mailbox (name);
int wkd_tried = 0;
if (retctx) if (retctx)
*retctx = NULL; *retctx = NULL;
start_over:
if (ctx) /* Clear in case of a start over. */
{
if (ret_keyblock)
{
release_kbnode (*ret_keyblock);
*ret_keyblock = NULL;
}
getkey_end (ctrl, ctx);
ctx = NULL;
}
err = get_pubkey_byname (ctrl, &ctx, pk, name, ret_keyblock, err = get_pubkey_byname (ctrl, &ctx, pk, name, ret_keyblock,
NULL, include_unusable, 0); NULL, include_unusable, 0);
if (err) if (err)
@ -1652,7 +1699,39 @@ get_best_pubkey_byname (ctrl_t ctrl, GETKEY_CTX *retctx, PKT_public_key *pk,
return err; return err;
} }
if (is_valid_mailbox (name) && ctx) /* If the keyblock was retrieved from the local database and the key
* has expired, do further checks. However, we can do this only if
* the caller requested a keyblock. */
if (is_mbox && ctx && ctx->found_via_akl == AKL_LOCAL && ret_keyblock)
{
u32 now = make_timestamp ();
PKT_public_key *pk2 = (*ret_keyblock)->pkt->pkt.public_key;
int found;
/* If the key has expired and its origin was the WKD then try to
* get a fresh key from the WKD. We also try this if the key
* has any only expired encryption subkeys. In case we checked
* for a fresh copy in the last 3 hours we won't do that again.
* Unfortunately that does not yet work because KEYUPDATE is
* only updated during import iff the key has actually changed
* (see import.c:import_one). */
if (!wkd_tried && pk2->keyorg == KEYORG_WKD
&& (pk2->keyupdate + 3*3600) < now
&& (pk2->has_expired || only_expired_enc_subkeys (*ret_keyblock)))
{
if (opt.verbose)
log_info (_("checking for a fresh copy of an expired key via %s\n"),
"WKD");
wkd_tried = 1;
glo_ctrl.in_auto_key_retrieve++;
found = !keyserver_import_wkd (ctrl, name, 0, NULL, NULL);
glo_ctrl.in_auto_key_retrieve--;
if (found)
goto start_over;
}
}
if (is_mbox && ctx)
{ {
/* Rank results and return only the most relevant key. */ /* Rank results and return only the most relevant key. */
struct pubkey_cmp_cookie best = { 0 }; struct pubkey_cmp_cookie best = { 0 };

View File

@ -2070,9 +2070,12 @@ import_one (ctrl_t ctrl,
keydb_release (hd); keydb_release (hd);
hd = NULL; hd = NULL;
/* Fixme: we do not track the time we last checked a key for /* FIXME: We do not track the time we last checked a key for
* updates. To do this we would need to rewrite even the * updates. To do this we would need to rewrite even the
* keys which have no changes. */ * keys which have no changes. Adding this would be useful
* for the automatic update of expired keys via the WKD in
* case the WKD still carries the expired key. See
* get_best_pubkey_byname. */
same_key = 1; same_key = 1;
if (is_status_enabled ()) if (is_status_enabled ())
print_import_ok (pk, 0); print_import_ok (pk, 0);