diff --git a/agent/agent.h b/agent/agent.h index 9fdbc76d3..9baf59601 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -304,11 +304,12 @@ enum typedef enum { CACHE_MODE_IGNORE = 0, /* Special mode to bypass the cache. */ - CACHE_MODE_ANY, /* Any mode except ignore matches. */ + CACHE_MODE_ANY, /* Any mode except ignore and data matches. */ CACHE_MODE_NORMAL, /* Normal cache (gpg-agent). */ CACHE_MODE_USER, /* GET_PASSPHRASE related cache. */ CACHE_MODE_SSH, /* SSH related cache. */ - CACHE_MODE_NONCE /* This is a non-predictable nonce. */ + CACHE_MODE_NONCE, /* This is a non-predictable nonce. */ + CACHE_MODE_DATA /* Arbitrary data. */ } cache_mode_t; diff --git a/agent/cache.c b/agent/cache.c index 238b6e214..799d595ab 100644 --- a/agent/cache.c +++ b/agent/cache.c @@ -28,6 +28,10 @@ #include "agent.h" +/* The default TTL for DATA items. This has no configure + * option because it is expected that clients provide a TTL. */ +#define DEF_CACHE_TTL_DATA (10 * 60) /* 10 minutes. */ + /* The size of the encryption key in bytes. */ #define ENCRYPTION_KEYSIZE (128/8) @@ -50,11 +54,12 @@ struct secret_data_s { char data[1]; /* A string. */ }; +/* The cache object. */ typedef struct cache_item_s *ITEM; struct cache_item_s { ITEM next; time_t created; - time_t accessed; + time_t accessed; /* Not updated for CACHE_MODE_DATA */ int ttl; /* max. lifetime given in seconds, -1 one means infinite */ struct secret_data_s *pw; cache_mode_t cache_mode; @@ -211,14 +216,18 @@ housekeeping (void) } } - /* Second, make sure that we also remove them based on the created stamp so - that the user has to enter it from time to time. */ + /* Second, make sure that we also remove them based on the created + * stamp so that the user has to enter it from time to time. We + * don't do this for data items which are used to storage secrets in + * meory and are not user entered passphrases etc. */ for (r=thecache; r; r = r->next) { unsigned long maxttl; switch (r->cache_mode) { + case CACHE_MODE_DATA: + continue; /* No MAX TTL here. */ case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break; default: maxttl = opt.max_cache_ttl; break; } @@ -315,8 +324,11 @@ static int cache_mode_equal (cache_mode_t a, cache_mode_t b) { /* CACHE_MODE_ANY matches any mode other than CACHE_MODE_IGNORE. */ - return ((a == CACHE_MODE_ANY && b != CACHE_MODE_IGNORE) - || (b == CACHE_MODE_ANY && a != CACHE_MODE_IGNORE) || a == b); + return ((a == CACHE_MODE_ANY + && !(b == CACHE_MODE_IGNORE || b == CACHE_MODE_DATA)) + || (b == CACHE_MODE_ANY + && !(a == CACHE_MODE_IGNORE || a == CACHE_MODE_DATA)) + || a == b); } @@ -349,6 +361,7 @@ agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, switch(cache_mode) { case CACHE_MODE_SSH: ttl = opt.def_cache_ttl_ssh; break; + case CACHE_MODE_DATA: ttl = DEF_CACHE_TTL_DATA; break; default: ttl = opt.def_cache_ttl; break; } } @@ -415,9 +428,7 @@ agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, } -/* Try to find an item in the cache. Note that we currently don't - make use of CACHE_MODE except for CACHE_MODE_NONCE and - CACHE_MODE_USER. */ +/* Try to find an item in the cache. */ char * agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode) { @@ -458,8 +469,11 @@ agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode) && r->restricted == restricted && !strcmp (r->key, key)) { - /* Note: To avoid races KEY may not be accessed anymore below. */ - r->accessed = gnupg_get_time (); + /* Note: To avoid races KEY may not be accessed anymore + * below. Note also that we don't update the accessed time + * for data items. */ + if (r->cache_mode != CACHE_MODE_DATA) + r->accessed = gnupg_get_time (); if (DBG_CACHE) log_debug ("... hit\n"); if (r->pw->totallen < 32) diff --git a/agent/command.c b/agent/command.c index 9bc3b027c..925d1f780 100644 --- a/agent/command.c +++ b/agent/command.c @@ -50,6 +50,8 @@ #define MAXLEN_KEYPARAM 1024 /* Maximum allowed size of key data as used in inquiries (bytes). */ #define MAXLEN_KEYDATA 8192 +/* Maximum length of a secret to store under one key. */ +#define MAXLEN_PUT_SECRET 4096 /* The size of the import/export KEK key (in bytes). */ #define KEYWRAP_KEYSIZE (128/8) @@ -292,6 +294,31 @@ parse_keygrip (assuan_context_t ctx, const char *string, unsigned char *buf) } +/* Parse the TTL from STRING. Leading and trailing spaces are + * skipped. The value is constrained to -1 .. MAXINT. On error 0 is + * returned, else the number of bytes scanned. */ +static size_t +parse_ttl (const char *string, int *r_ttl) +{ + const char *string_orig = string; + long ttl; + char *pend; + + ttl = strtol (string, &pend, 10); + string = pend; + if (string == string_orig || !(spacep (string) || !*string) + || ttl < -1L || (int)ttl != (long)ttl) + { + *r_ttl = 0; + return 0; + } + while (spacep (string) || *string== '\n') + string++; + *r_ttl = (int)ttl; + return string - string_orig; +} + + /* Write an Assuan status line. KEYWORD is the first item on the * status line. The following arguments are all separated by a space * in the output. The last argument must be a NULL. Linefeeds and @@ -2567,6 +2594,187 @@ cmd_keytocard (assuan_context_t ctx, char *line) } + +static const char hlp_get_secret[] = + "GET_SECRET \n" + "\n" + "Return the secret value stored under KEY\n"; +static gpg_error_t +cmd_get_secret (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + char *p, *key; + char *value = NULL; + size_t valuelen; + + /* For now we allow this only for local connections. */ + if (ctrl->restricted) + { + err = gpg_error (GPG_ERR_FORBIDDEN); + goto leave; + } + + line = skip_options (line); + + for (p=line; *p == ' '; p++) + ; + key = p; + p = strchr (key, ' '); + if (p) + { + *p++ = 0; + for (; *p == ' '; p++) + ; + if (*p) + { + err = set_error (GPG_ERR_ASS_PARAMETER, "too many arguments"); + goto leave; + } + } + if (!*key) + { + err = set_error (GPG_ERR_ASS_PARAMETER, "no key given"); + goto leave; + } + + + value = agent_get_cache (ctrl, key, CACHE_MODE_DATA); + if (!value) + { + err = gpg_error (GPG_ERR_NO_DATA); + goto leave; + } + + valuelen = percent_unescape_inplace (value, 0); + err = assuan_send_data (ctx, value, valuelen); + wipememory (value, valuelen); + + leave: + xfree (value); + return leave_cmd (ctx, err); +} + + +static const char hlp_put_secret[] = + "PUT_SECRET [--clear] []\n" + "\n" + "This commands stores a secret under KEY in gpg-agent's in-memory\n" + "cache. The TTL must be explicitly given by TTL and the options\n" + "from the configuration file are not used. The value is either given\n" + "percent-escaped as 3rd argument or if not given inquired by gpg-agent\n" + "using the keyword \"SECRET\".\n" + "The option --clear removes the secret from the cache." + ""; +static gpg_error_t +cmd_put_secret (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err = 0; + int opt_clear; + unsigned char *value = NULL; + size_t valuelen = 0; + size_t n; + char *p, *key, *ttlstr; + unsigned char *valstr; + int ttl; + char *string = NULL; + + /* For now we allow this only for local connections. */ + if (ctrl->restricted) + { + err = gpg_error (GPG_ERR_FORBIDDEN); + goto leave; + } + + opt_clear = has_option (line, "--clear"); + line = skip_options (line); + + for (p=line; *p == ' '; p++) + ; + key = p; + ttlstr = NULL; + valstr = NULL; + p = strchr (key, ' '); + if (p) + { + *p++ = 0; + for (; *p == ' '; p++) + ; + if (*p) + { + ttlstr = p; + p = strchr (ttlstr, ' '); + if (p) + { + *p++ = 0; + for (; *p == ' '; p++) + ; + if (*p) + valstr = p; + } + } + } + if (!*key) + { + err = set_error (GPG_ERR_ASS_PARAMETER, "no key given"); + goto leave; + } + if (!ttlstr || !*ttlstr || !(n = parse_ttl (ttlstr, &ttl))) + { + err = set_error (GPG_ERR_ASS_PARAMETER, "no or invalid TTL given"); + goto leave; + } + if (valstr && opt_clear) + { + err = set_error (GPG_ERR_ASS_PARAMETER, + "value not expected with --clear"); + goto leave; + } + + if (valstr) + { + valuelen = percent_unescape_inplace (valstr, 0); + value = NULL; + } + else /* Inquire the value to store */ + { + err = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u",MAXLEN_PUT_SECRET); + if (!err) + err = assuan_inquire (ctx, "SECRET", + &value, &valuelen, MAXLEN_PUT_SECRET); + if (err) + goto leave; + } + + /* Our cache expects strings and thus we need to turn the buffer + * into a string. Instead of resorting to base64 encoding we use a + * special percent escaping which only quoted the Nul and the + * percent character. */ + string = percent_data_escape (value? value : valstr, valuelen); + if (!string) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = agent_put_cache (ctrl, key, CACHE_MODE_DATA, string, ttl); + + + leave: + if (string) + { + wipememory (string, strlen (string)); + xfree (string); + } + if (value) + { + wipememory (value, valuelen); + xfree (value); + } + return leave_cmd (ctx, err); +} + + static const char hlp_getval[] = "GETVAL \n" @@ -3259,6 +3467,8 @@ register_commands (assuan_context_t ctx) { "IMPORT_KEY", cmd_import_key, hlp_import_key }, { "EXPORT_KEY", cmd_export_key, hlp_export_key }, { "DELETE_KEY", cmd_delete_key, hlp_delete_key }, + { "GET_SECRET", cmd_get_secret, hlp_get_secret }, + { "PUT_SECRET", cmd_put_secret, hlp_put_secret }, { "GETVAL", cmd_getval, hlp_getval }, { "PUTVAL", cmd_putval, hlp_putval }, { "UPDATESTARTUPTTY", cmd_updatestartuptty, hlp_updatestartuptty },