agent: New commands PUT_SECRET and GET_SECRET.
authorWerner Koch <wk@gnupg.org>
Mon, 2 Jul 2018 19:24:15 +0000 (21:24 +0200)
committerWerner Koch <wk@gnupg.org>
Mon, 2 Jul 2018 19:36:19 +0000 (21:36 +0200)
* agent/agent.h (CACHE_MODE_DATA): New const.
* agent/cache.c (DEF_CACHE_TTL_DATA): new.
(housekeeping): Tweak for CACHE_MODE_DATA.
(cache_mode_equal): Ditto.
(agent_get_cache): Ditto.
(agent_put_cache): Implement CACHE_MODE_DATA.
* agent/command.c (MAXLEN_PUT_SECRET): New.
(parse_ttl): New.
(cmd_get_secret): New.
(cmd_put_secret): New.
(register_commands): Register new commands.
--

These commands allow to store secrets in memory for the lifetime of
the gpg-agent process.

Signed-off-by: Werner Koch <wk@gnupg.org>
agent/agent.h
agent/cache.c
agent/command.c

index 9fdbc76..9baf596 100644 (file)
@@ -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;
 
index 238b6e2..799d595 100644 (file)
 
 #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)
index 9bc3b02..925d1f7 100644 (file)
@@ -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
@@ -2568,6 +2595,187 @@ cmd_keytocard (assuan_context_t ctx, char *line)
 
 
 \f
+static const char hlp_get_secret[] =
+  "GET_SECRET <key>\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] <key> <ttl> [<percent_escaped_value>]\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);
+}
+
+
+\f
 static const char hlp_getval[] =
   "GETVAL <key>\n"
   "\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 },