Imported Upstream version 2.3.8
[platform/upstream/gpg2.git] / agent / findkey.c
index 28ff617..20962bd 100644 (file)
@@ -33,7 +33,6 @@
 #include "agent.h"
 #include "../common/i18n.h"
 #include "../common/ssh-utils.h"
-#include "../common/name-value.h"
 
 #ifndef O_BINARY
 #define O_BINARY 0
@@ -50,7 +49,7 @@ struct try_unprotect_arg_s
 };
 
 
-/* Repalce all linefeeds in STRING by "%0A" and return a new malloced
+/* Replace all linefeeds in STRING by "%0A" and return a new malloced
  * string.  May return NULL on memory error.  */
 static char *
 linefeed_to_percent0A (const char *string)
@@ -173,6 +172,8 @@ write_extended_private_key (char *fname, estream_t fp, int update, int newkey,
     goto leave;
 
   err = nvc_write (pk, fp);
+  if (!err)
+    err = es_fflush (fp);
   if (err)
     {
       log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
@@ -339,6 +340,59 @@ agent_write_private_key (const unsigned char *grip,
 }
 
 
+gpg_error_t
+agent_update_private_key (const unsigned char *grip, nvc_t pk)
+{
+  char *fname, *fname0;
+  estream_t fp;
+  char hexgrip[40+8+1];
+  gpg_error_t err;
+
+  bin2hex (grip, 20, hexgrip);
+  strcpy (hexgrip+40, ".key.tmp");
+
+  fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
+                         hexgrip, NULL);
+  fname0 = xstrdup (fname);
+  if (!fname0)
+    {
+      err = gpg_error_from_syserror ();
+      xfree (fname);
+      return err;
+    }
+  fname0[strlen (fname)-4] = 0;
+
+  fp = es_fopen (fname, "wbx,mode=-rw");
+  if (!fp)
+    {
+      err = gpg_error_from_syserror ();
+
+      log_error ("can't create '%s': %s\n", fname, gpg_strerror (err));
+      xfree (fname);
+      return err;
+    }
+
+  err = nvc_write (pk, fp);
+  if (err)
+    log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
+
+  es_fclose (fp);
+
+#ifdef HAVE_W32_SYSTEM
+  /* No atomic mv on W32 systems.  */
+  gnupg_remove (fname0);
+#endif
+  if (rename (fname, fname0))
+    {
+      err = gpg_error_from_errno (errno);
+      log_error (_("error renaming '%s' to '%s': %s\n"),
+                 fname, fname0, strerror (errno));
+    }
+
+  xfree (fname);
+  return err;
+}
+
 /* Callback function to try the unprotection from the passphrase query
    code. */
 static gpg_error_t
@@ -721,7 +775,7 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
             {
               /* We need to give the other thread a chance to actually put
                  it into the cache. */
-              npth_sleep (1);
+              gnupg_sleep (1);
               goto retry;
             }
           /* Timeout - better call pinentry now the plain way. */
@@ -961,8 +1015,137 @@ remove_key_file (const unsigned char *grip)
 }
 
 
+/*
+ * Prompt a user the card insertion, when it's not available yet.
+ */
+static gpg_error_t
+prompt_for_card (ctrl_t ctrl, const unsigned char *grip,
+                 nvc_t keymeta, const unsigned char *shadow_info)
+{
+  char *serialno;
+  char *desc;
+  char *want_sn = NULL;
+  int len;
+  gpg_error_t err;
+  char hexgrip[41];
+  char *comment_buffer = NULL;
+  const char *comment = NULL;
+  int refuse_prompt = 0;
+
+  bin2hex (grip, 20, hexgrip);
+
+  if (keymeta)
+    {
+      const char *p;
+
+      if ((p = nvc_get_string (keymeta, "Prompt:")) && !strcmp (p, "no"))
+        refuse_prompt = 1;
+
+      if ((p = nvc_get_string (keymeta, "Label:")))
+        {
+          if (strchr (p, '\n')
+              && (comment_buffer = linefeed_to_percent0A (p)))
+            comment = comment_buffer;
+          else
+            comment = p;
+        }
+    }
+
+  err = parse_shadow_info (shadow_info, &want_sn, NULL, NULL);
+  if (err)
+    return err;
+
+  len = want_sn? strlen (want_sn) : 0;
+  if (len == 32 && !strncmp (want_sn, "D27600012401", 12))
+    {
+      /* This is an OpenPGP card - reformat  */
+      if (!strncmp (want_sn+16, "0006", 4))
+        {
+          /* This is a Yubikey.  Print the s/n as it would be printed
+           * on Yubikey 5. Example: D2760001240100000006120808620000
+           *                                        mmmm^^^^^^^^      */
+          unsigned long sn;
+
+          sn  = atoi_4 (want_sn+20) * 10000;
+          sn += atoi_4 (want_sn+24);
+          snprintf (want_sn, 32, "%lu %03lu %03lu",
+                    (sn/1000000ul), (sn/1000ul % 1000ul), (sn % 1000ul));
+        }
+      else  /* Default is the Zeitcontrol card print format.  */
+        {
+          memmove (want_sn, want_sn+16, 4);
+          want_sn[4] = ' ';
+          memmove (want_sn+5, want_sn+20, 8);
+          want_sn[13] = 0;
+        }
+    }
+  else if (len == 20 && want_sn[19] == '0')
+    {
+      /* We assume that a 20 byte serial number is a standard one
+       * which has the property to have a zero in the last nibble (Due
+       * to BCD representation).  We don't display this '0' because it
+       * may confuse the user.  */
+      want_sn[19] = 0;
+    }
+
+  for (;;)
+    {
+      /* Scan device(s), and check if key for GRIP is available.  */
+      err = agent_card_serialno (ctrl, &serialno, NULL);
+      if (!err)
+        {
+          struct card_key_info_s *keyinfo;
+
+          xfree (serialno);
+          err = agent_card_keyinfo (ctrl, hexgrip, 0, &keyinfo);
+          if (!err)
+            {
+              /* Key for GRIP found, use it.  */
+              agent_card_free_keyinfo (keyinfo);
+              break;
+            }
+        }
+
+      /* Card is not available.  Prompt the insertion.  */
+      if (refuse_prompt)
+        {
+          err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+          break;
+        }
+
+      if (asprintf (&desc,
+                    "%s:%%0A%%0A"
+                    "  %s%%0A"
+                    "  %s",
+                    L_("Please insert the card with serial number"),
+                    want_sn ? want_sn : "",
+                    comment? comment:"") < 0)
+        err = out_of_core ();
+      else
+        {
+          err = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
+          if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK &&
+              gpg_err_code (err) == GPG_ERR_NO_PIN_ENTRY)
+            err = gpg_error (GPG_ERR_CARD_NOT_PRESENT);
+
+          xfree (desc);
+        }
+
+      if (err)
+        break;
+    }
+
+  xfree (want_sn);
+  gcry_free (comment_buffer);
+  return err;
+}
+
+
 /* Return the secret key as an S-Exp in RESULT after locating it using
-   the GRIP.  If the operation shall be diverted to a token, an
+   the GRIP.  Caller should set GRIP=NULL, when a key in a file is
+   intended to be used for cryptographic operation.  In this case,
+   CTRL->keygrip is used to locate the file, and it may ask a user for
+   confirmation.  If the operation shall be diverted to a token, an
    allocated S-expression with the shadow_info part from the file is
    stored at SHADOW_INFO; if not NULL will be stored at SHADOW_INFO.
    CACHE_MODE defines now the cache shall be used.  DESC_TEXT may be
@@ -981,7 +1164,8 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
                      const char *desc_text,
                      const unsigned char *grip, unsigned char **shadow_info,
                      cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
-                     gcry_sexp_t *result, char **r_passphrase)
+                     gcry_sexp_t *result, char **r_passphrase,
+                     time_t *r_timestamp)
 {
   gpg_error_t err;
   unsigned char *buf;
@@ -995,14 +1179,13 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
     *shadow_info = NULL;
   if (r_passphrase)
     *r_passphrase = NULL;
+  if (r_timestamp)
+    *r_timestamp = (time_t)(-1);
 
-  err = read_key_file (grip, &s_skey, &keymeta);
-  if (err)
-    {
-      if (gpg_err_code (err) == GPG_ERR_ENOENT)
-        err = gpg_error (GPG_ERR_NO_SECKEY);
-      return err;
-    }
+  if (!grip && !ctrl->have_keygrip)
+    return gpg_error (GPG_ERR_NO_SECKEY);
+
+  err = read_key_file (grip? grip : ctrl->keygrip, &s_skey, &keymeta);
 
   /* For use with the protection functions we also need the key as an
      canonical encoded S-expression in a buffer.  Create this buffer
@@ -1015,6 +1198,53 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
       return err;
     }
 
+  if (r_timestamp && keymeta)
+    {
+      const char *created = nvc_get_string (keymeta, "Created:");
+
+      if (created)
+        *r_timestamp = isotime2epoch (created);
+    }
+
+  if (!grip && keymeta)
+    {
+      const char *ask_confirmation = nvc_get_string (keymeta, "Confirm:");
+
+      if (ask_confirmation
+          && ((!strcmp (ask_confirmation, "restricted") && ctrl->restricted)
+              || !strcmp (ask_confirmation, "yes")))
+        {
+          char hexgrip[40+4+1];
+          char *prompt;
+          char *comment_buffer = NULL;
+          const char *comment = NULL;
+
+          bin2hex (ctrl->keygrip, 20, hexgrip);
+
+          if ((comment = nvc_get_string (keymeta, "Label:")))
+            {
+              if (strchr (comment, '\n')
+                  && (comment_buffer = linefeed_to_percent0A (comment)))
+                comment = comment_buffer;
+            }
+
+          prompt = xtryasprintf (L_("Requested the use of key%%0A"
+                                    "  %s%%0A"
+                                    "  %s%%0A"
+                                    "Do you want to allow this?"),
+                                 hexgrip, comment? comment:"");
+
+          gcry_free (comment_buffer);
+
+          err = agent_get_confirmation (ctrl, prompt,
+                                        L_("Allow"), L_("Deny"), 0);
+          xfree (prompt);
+
+          if (err)
+            return err;
+        }
+    }
+
   switch (agent_private_key_type (buf))
     {
     case PRIVATE_KEY_CLEAR:
@@ -1076,14 +1306,15 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
                                           &desc_text_final);
         gcry_free (comment_buffer);
 
-       if (!err)
-         {
-           err = unprotect (ctrl, cache_nonce, desc_text_final, &buf, grip,
-                            cache_mode, lookup_ttl, r_passphrase);
-           if (err)
-             log_error ("failed to unprotect the secret key: %s\n",
-                        gpg_strerror (err));
-         }
+        if (!err)
+          {
+            err = unprotect (ctrl, cache_nonce, desc_text_final, &buf,
+                             grip? grip : ctrl->keygrip,
+                             cache_mode, lookup_ttl, r_passphrase);
+            if (err)
+              log_error ("failed to unprotect the secret key: %s\n",
+                         gpg_strerror (err));
+          }
 
        xfree (desc_text_final);
       }
@@ -1092,24 +1323,37 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
       if (shadow_info)
         {
           const unsigned char *s;
+          unsigned char *shadow_type;
           size_t n;
 
-          err = agent_get_shadow_info (buf, &s);
+          err = agent_get_shadow_info_type (buf, &s, &shadow_type);
           if (!err)
             {
               n = gcry_sexp_canon_len (s, 0, NULL,NULL);
               log_assert (n);
               *shadow_info = xtrymalloc (n);
               if (!*shadow_info)
-                err = out_of_core ();
+                {
+                  err = out_of_core ();
+                  goto shadow_error;
+                }
               else
                 {
                   memcpy (*shadow_info, s, n);
-                  err = 0;
+                  /*
+                   * When it's a key on card (not on tpm2), maks sure
+                   * it's available.
+                   */
+                  if (strcmp (shadow_type, "t1-v1") == 0 && !grip)
+                    err = prompt_for_card (ctrl, ctrl->keygrip,
+                                           keymeta, *shadow_info);
                 }
             }
-          if (err)
+          else
+          shadow_error:
             log_error ("get_shadow_info failed: %s\n", gpg_strerror (err));
+
+          xfree (shadow_type);
         }
       else
         err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
@@ -1159,7 +1403,7 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
    failure an error code is returned and NULL stored at RESULT. */
 gpg_error_t
 agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
-                         gcry_sexp_t *result)
+                         gcry_sexp_t *result, nvc_t *r_keymeta)
 {
   gpg_error_t err;
   gcry_sexp_t s_skey;
@@ -1168,7 +1412,7 @@ agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
 
   *result = NULL;
 
-  err = read_key_file (grip, &s_skey, NULL);
+  err = read_key_file (grip, &s_skey, r_keymeta);
   if (!err)
     *result = s_skey;
   return err;
@@ -1179,14 +1423,14 @@ agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
    at RESULT.  This function extracts the public key from the private
    key database.  On failure an error code is returned and NULL stored
    at RESULT. */
-gpg_error_t
-agent_public_key_from_file (ctrl_t ctrl,
-                            const unsigned char *grip,
-                            gcry_sexp_t *result)
+static gpg_error_t
+public_key_from_file (ctrl_t ctrl, const unsigned char *grip,
+                      gcry_sexp_t *result, int for_ssh)
 {
   gpg_error_t err;
   int i, idx;
   gcry_sexp_t s_skey;
+  nvc_t keymeta = NULL;
   const char *algoname, *elems;
   int npkey;
   gcry_mpi_t array[10];
@@ -1208,10 +1452,26 @@ agent_public_key_from_file (ctrl_t ctrl,
 
   *result = NULL;
 
-  err = read_key_file (grip, &s_skey, NULL);
+  err = read_key_file (grip, &s_skey, for_ssh? &keymeta : NULL);
   if (err)
     return err;
 
+  if (for_ssh)
+    {
+      /* Use-for-ssh: yes */
+      int is_ssh = 0;
+
+      if (keymeta == NULL)
+        return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+
+      is_ssh = nvc_get_boolean (keymeta, "Use-for-ssh:");
+      nvc_release (keymeta);
+      keymeta = NULL;
+
+      if (!is_ssh)
+        return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+    }
+
   for (i=0; i < DIM (array); i++)
     array[i] = NULL;
 
@@ -1300,6 +1560,22 @@ agent_public_key_from_file (ctrl_t ctrl,
   return err;
 }
 
+gpg_error_t
+agent_public_key_from_file (ctrl_t ctrl,
+                            const unsigned char *grip,
+                            gcry_sexp_t *result)
+{
+  return public_key_from_file (ctrl, grip, result, 0);
+}
+
+gpg_error_t
+agent_ssh_key_from_file (ctrl_t ctrl,
+                         const unsigned char *grip,
+                         gcry_sexp_t *result)
+{
+  return public_key_from_file (ctrl, grip, result, 1);
+}
+
 
 /* Check whether the secret key identified by GRIP is available.
    Returns 0 is the key is available.  */