gpg: Pass key origin values to import functions.
[gnupg.git] / agent / command-ssh.c
index 0e1d9fc..c5897ef 100644 (file)
@@ -1,4 +1,4 @@
-/* command-ssh.c - gpg-agent's ssh-agent emulation layer
+/* command-ssh.c - gpg-agent's implementation of the ssh-agent protocol.
  * Copyright (C) 2004-2006, 2009, 2012 Free Software Foundation, Inc.
  * Copyright (C) 2004-2006, 2009, 2012-2014 Werner Koch
  *
@@ -15,7 +15,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 /* Only v2 of the ssh-agent protocol is implemented.  Relevant RFCs
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <assert.h>
+#ifndef HAVE_W32_SYSTEM
+#include <sys/socket.h>
+#include <sys/un.h>
+#endif /*!HAVE_W32_SYSTEM*/
+#ifdef HAVE_UCRED_H
+#include <ucred.h>
+#endif
 
 #include "agent.h"
 
-#include "i18n.h"
+#include "../common/i18n.h"
+#include "../common/util.h"
 #include "../common/ssh-utils.h"
 
 
@@ -153,7 +161,7 @@ struct ssh_key_type_spec
   const char *name;
 
   /* Algorithm identifier as used by GnuPG.  */
-  const char *identifier;
+  int algo;
 
   /* List of MPI names for secret keys; order matches the one of the
      agent protocol.  */
@@ -254,7 +262,7 @@ static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **comment);
 /* Associating request types with the corresponding request
    handlers.  */
 
-static ssh_request_spec_t request_specs[] =
+static const ssh_request_spec_t request_specs[] =
   {
 #define REQUEST_SPEC_DEFINE(id, name, secret_input) \
   { SSH_REQUEST_##id, ssh_handler_##name, #name, secret_input }
@@ -272,71 +280,71 @@ static ssh_request_spec_t request_specs[] =
 
 
 /* Table holding key type specifications.  */
-static ssh_key_type_spec_t ssh_key_types[] =
+static const ssh_key_type_spec_t ssh_key_types[] =
   {
     {
-      "ssh-ed25519", "Ed25519", "ecc", "qd",  "q", "rs", "qd",
+      "ssh-ed25519", "Ed25519", GCRY_PK_EDDSA, "qd",  "q", "rs", "qd",
       NULL,                 ssh_signature_encoder_eddsa,
       "Ed25519", 0,               SPEC_FLAG_IS_EdDSA
     },
     {
-      "ssh-rsa", "RSA", "rsa", "nedupq", "en",   "s",  "nedpqu",
+      "ssh-rsa", "RSA", GCRY_PK_RSA, "nedupq", "en",   "s",  "nedpqu",
       ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
       NULL, 0,                    SPEC_FLAG_USE_PKCS1V2
     },
     {
-      "ssh-dss", "DSA", "dsa", "pqgyx",  "pqgy", "rs", "pqgyx",
+      "ssh-dss", "DSA", GCRY_PK_DSA, "pqgyx",  "pqgy", "rs", "pqgyx",
       NULL,                 ssh_signature_encoder_dsa,
       NULL, 0, 0
     },
     {
-      "ecdsa-sha2-nistp256", "ECDSA", "ecdsa", "qd",  "q", "rs", "qd",
+      "ecdsa-sha2-nistp256", "ECDSA", GCRY_PK_ECC, "qd",  "q", "rs", "qd",
       NULL,                 ssh_signature_encoder_ecdsa,
       "nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA
     },
     {
-      "ecdsa-sha2-nistp384", "ECDSA", "ecdsa", "qd",  "q", "rs", "qd",
+      "ecdsa-sha2-nistp384", "ECDSA", GCRY_PK_ECC, "qd",  "q", "rs", "qd",
       NULL,                 ssh_signature_encoder_ecdsa,
       "nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA
     },
     {
-      "ecdsa-sha2-nistp521", "ECDSA", "ecdsa", "qd",  "q", "rs", "qd",
+      "ecdsa-sha2-nistp521", "ECDSA", GCRY_PK_ECC, "qd",  "q", "rs", "qd",
       NULL,                 ssh_signature_encoder_ecdsa,
       "nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA
     },
     {
       "ssh-ed25519-cert-v01@openssh.com", "Ed25519",
-      "ecc", "qd",  "q", "rs", "qd",
+      GCRY_PK_EDDSA, "qd",  "q", "rs", "qd",
       NULL,                 ssh_signature_encoder_eddsa,
       "Ed25519", 0, SPEC_FLAG_IS_EdDSA | SPEC_FLAG_WITH_CERT
     },
     {
       "ssh-rsa-cert-v01@openssh.com", "RSA",
-      "rsa", "nedupq", "en",   "s",  "nedpqu",
+      GCRY_PK_RSA, "nedupq", "en",   "s",  "nedpqu",
       ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
       NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT
     },
     {
       "ssh-dss-cert-v01@openssh.com", "DSA",
-      "dsa", "pqgyx",  "pqgy", "rs", "pqgyx",
+      GCRY_PK_DSA, "pqgyx",  "pqgy", "rs", "pqgyx",
       NULL,                 ssh_signature_encoder_dsa,
       NULL, 0, SPEC_FLAG_WITH_CERT | SPEC_FLAG_WITH_CERT
     },
     {
       "ecdsa-sha2-nistp256-cert-v01@openssh.com", "ECDSA",
-      "ecdsa", "qd",  "q", "rs", "qd",
+      GCRY_PK_ECC, "qd",  "q", "rs", "qd",
       NULL,                 ssh_signature_encoder_ecdsa,
       "nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
     },
     {
       "ecdsa-sha2-nistp384-cert-v01@openssh.com", "ECDSA",
-      "ecdsa", "qd",  "q", "rs", "qd",
+      GCRY_PK_ECC, "qd",  "q", "rs", "qd",
       NULL,                 ssh_signature_encoder_ecdsa,
       "nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
     },
     {
       "ecdsa-sha2-nistp521-cert-v01@openssh.com", "ECDSA",
-      "ecdsa", "qd",  "q", "rs", "qd",
+      GCRY_PK_ECC, "qd",  "q", "rs", "qd",
       NULL,                 ssh_signature_encoder_ecdsa,
       "nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
     }
@@ -368,23 +376,6 @@ realloc_secure (void *a, size_t n)
 }
 
 
-/* Create and return a new C-string from DATA/DATA_N (i.e.: add
-   NUL-termination); return NULL on OOM.  */
-static char *
-make_cstring (const char *data, size_t data_n)
-{
-  char *s;
-
-  s = xtrymalloc (data_n + 1);
-  if (s)
-    {
-      memcpy (s, data, data_n);
-      s[data_n] = 0;
-    }
-
-  return s;
-}
-
 /* Lookup the ssh-identifier for the ECC curve CURVE_NAME.  Returns
    NULL if not found.  */
 static const char *
@@ -683,13 +674,7 @@ stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi)
 static gpg_error_t
 stream_read_cstring (estream_t stream, char **string)
 {
-  gpg_error_t err;
-  unsigned char *buffer;
-
-  err = stream_read_string (stream, 0, &buffer, NULL);
-  if (!err)
-    *string = (char *)buffer;
-  return err;
+  return stream_read_string (stream, 0, (unsigned char **)string, NULL);
 }
 
 
@@ -814,67 +799,6 @@ stream_copy (estream_t dst, estream_t src)
 
   return err;
 }
-
-
-/* Read the content of the file specified by FILENAME into a newly
-   create buffer, which is to be stored in BUFFER; store length of
-   buffer in BUFFER_N.  */
-static gpg_error_t
-file_to_buffer (const char *filename, unsigned char **buffer, size_t *buffer_n)
-{
-  unsigned char *buffer_new;
-  struct stat statbuf;
-  estream_t stream;
-  gpg_error_t err;
-  int ret;
-
-  *buffer = NULL;
-  *buffer_n = 0;
-
-  buffer_new = NULL;
-  err = 0;
-
-  stream = es_fopen (filename, "rb");
-  if (! stream)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
-
-  ret = fstat (es_fileno (stream), &statbuf);
-  if (ret)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
-
-  buffer_new = xtrymalloc (statbuf.st_size);
-  if (! buffer_new)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
-
-  err = stream_read_data (stream, buffer_new, statbuf.st_size);
-  if (err)
-    goto out;
-
-  *buffer = buffer_new;
-  *buffer_n = statbuf.st_size;
-
- out:
-
-  if (stream)
-    es_fclose (stream);
-
-  if (err)
-    xfree (buffer_new);
-
-  return err;
-}
-
-
-
 \f
 /* Open the ssh control file and create it if not available.  With
    APPEND passed as true the file will be opened in append mode,
@@ -897,7 +821,7 @@ open_control_file (ssh_control_file_t *r_cf, int append)
   /* Note: As soon as we start to use non blocking functions here
      (i.e. where Pth might switch threads) we need to employ a
      mutex.  */
-  cf->fname = make_filename_try (opt.homedir, SSH_CONTROL_FILE_NAME, NULL);
+  cf->fname = make_filename_try (gnupg_homedir (), SSH_CONTROL_FILE_NAME, NULL);
   if (!cf->fname)
     {
       err = gpg_error_from_syserror ();
@@ -1116,12 +1040,14 @@ search_control_file (ssh_control_file_t cf, const char *hexgrip,
    We can assume that the user wants to allow ssh using this key. */
 static gpg_error_t
 add_control_entry (ctrl_t ctrl, ssh_key_type_spec_t *spec,
-                   const char *hexgrip, const char *fmtfpr,
+                   const char *hexgrip, gcry_sexp_t key,
                    int ttl, int confirm)
 {
   gpg_error_t err;
   ssh_control_file_t cf;
   int disabled;
+  char *fpr_md5 = NULL;
+  char *fpr_sha256 = NULL;
 
   (void)ctrl;
 
@@ -1135,19 +1061,31 @@ add_control_entry (ctrl_t ctrl, ssh_key_type_spec_t *spec,
       struct tm *tp;
       time_t atime = time (NULL);
 
+      err = ssh_get_fingerprint_string (key, GCRY_MD_MD5, &fpr_md5);
+      if (err)
+        goto out;
+
+      err = ssh_get_fingerprint_string (key, GCRY_MD_SHA256, &fpr_sha256);
+      if (err)
+        goto out;
+
       /* Not yet in the file - add it. Because the file has been
          opened in append mode, we simply need to write to it.  */
       tp = localtime (&atime);
       fprintf (cf->fp,
                ("# %s key added on: %04d-%02d-%02d %02d:%02d:%02d\n"
-                "# MD5 Fingerprint:  %s\n"
+                "# Fingerprints:  %s\n"
+                "#                %s\n"
                 "%s %d%s\n"),
                spec->name,
                1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
                tp->tm_hour, tp->tm_min, tp->tm_sec,
-               fmtfpr, hexgrip, ttl, confirm? " confirm":"");
+               fpr_md5, fpr_sha256, hexgrip, ttl, confirm? " confirm":"");
 
     }
+ out:
+  xfree (fpr_md5);
+  xfree (fpr_sha256);
   close_control_file (cf);
   return 0;
 }
@@ -1204,7 +1142,7 @@ confirm_flag_from_sshcontrol (const char *hexgrip)
 
 /* Open the ssh control file for reading.  This is a public version of
    open_control_file.  The caller must use ssh_close_control_file to
-   release the retruned handle.  */
+   release the returned handle.  */
 ssh_control_file_t
 ssh_open_control_file (void)
 {
@@ -1330,7 +1268,7 @@ ssh_receive_mpint_list (estream_t stream, int secret,
   elems_n = strlen (elems);
   elems_public = spec->elems_key_public;
 
-  /* Check that either noth, CERT and the WITH_CERT flag, are given or
+  /* Check that either both, CERT and the WITH_CERT flag, are given or
      none of them.  */
   if (!(!!(spec->flags & SPEC_FLAG_WITH_CERT) ^ !cert))
     {
@@ -1762,7 +1700,7 @@ sexp_key_construct (gcry_sexp_t *r_sexp,
   void *formatbuf = NULL;
   void **arg_list = NULL;
   estream_t format = NULL;
-
+  char *algo_name = NULL;
 
   if ((key_spec.flags & SPEC_FLAG_IS_EdDSA))
     {
@@ -1826,7 +1764,14 @@ sexp_key_construct (gcry_sexp_t *r_sexp,
 
       es_fputs ("(%s(%s", format);
       arg_list[arg_idx++] = &key_identifier[secret];
-      arg_list[arg_idx++] = &key_spec.identifier;
+      algo_name = xtrystrdup (gcry_pk_algo_name (key_spec.algo));
+      if (!algo_name)
+        {
+          err = gpg_error_from_syserror ();
+          goto out;
+        }
+      strlwr (algo_name);
+      arg_list[arg_idx++] = &algo_name;
       if (curve_name)
         {
           es_fputs ("(curve%s)", format);
@@ -1873,6 +1818,7 @@ sexp_key_construct (gcry_sexp_t *r_sexp,
   es_fclose (format);
   xfree (arg_list);
   xfree (formatbuf);
+  xfree (algo_name);
 
   return err;
 }
@@ -1929,8 +1875,8 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret,
       goto out;
     }
 
-  /* Get the algorithm identifier.  */
-  value_list = gcry_sexp_find_token (sexp, key_spec.identifier, 0);
+  /* Get key value list.  */
+  value_list = gcry_sexp_cadr (sexp);
   if (!value_list)
     {
       err = gpg_error (GPG_ERR_INV_SEXP);
@@ -2070,51 +2016,6 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret,
 
   return err;
 }
-
-/* Extract the car from SEXP, and create a newly created C-string
-   which is to be stored in IDENTIFIER.  */
-static gpg_error_t
-sexp_extract_identifier (gcry_sexp_t sexp, char **identifier)
-{
-  char *identifier_new;
-  gcry_sexp_t sublist;
-  const char *data;
-  size_t data_n;
-  gpg_error_t err;
-
-  identifier_new = NULL;
-  err = 0;
-
-  sublist = gcry_sexp_nth (sexp, 1);
-  if (! sublist)
-    {
-      err = gpg_error (GPG_ERR_INV_SEXP);
-      goto out;
-    }
-
-  data = gcry_sexp_nth_data (sublist, 0, &data_n);
-  if (! data)
-    {
-      err = gpg_error (GPG_ERR_INV_SEXP);
-      goto out;
-    }
-
-  identifier_new = make_cstring (data, data_n);
-  if (! identifier_new)
-    {
-      err = gpg_err_code_from_errno (errno);
-      goto out;
-    }
-
-  *identifier = identifier_new;
-
- out:
-
-  gcry_sexp_release (sublist);
-
-  return err;
-}
-
 \f
 
 /*
@@ -2125,23 +2026,18 @@ sexp_extract_identifier (gcry_sexp_t sexp, char **identifier)
 
 /* Search for a key specification entry.  If SSH_NAME is not NULL,
    search for an entry whose "ssh_name" is equal to SSH_NAME;
-   otherwise, search for an entry whose "name" is equal to NAME.
+   otherwise, search for an entry whose algorithm is equal to ALGO.
    Store found entry in SPEC on success, return error otherwise.  */
 static gpg_error_t
-ssh_key_type_lookup (const char *ssh_name, const char *name,
+ssh_key_type_lookup (const char *ssh_name, int algo,
                     ssh_key_type_spec_t *spec)
 {
   gpg_error_t err;
   unsigned int i;
 
-  /* FIXME: Although this sees to work, it not be correct if the
-     lookup is done via name which might be "ecc" but actually it need
-     to check the flags to see whether it is eddsa or ecdsa.  Maybe
-     the entire parameter controlled logic is too complicated and we
-     would do better by just switching on the ssh_name.  */
   for (i = 0; i < DIM (ssh_key_types); i++)
     if ((ssh_name && (! strcmp (ssh_name, ssh_key_types[i].ssh_identifier)))
-       || (name && (! strcmp (name, ssh_key_types[i].identifier))))
+       || algo == ssh_key_types[i].algo)
       break;
 
   if (i == DIM (ssh_key_types))
@@ -2180,7 +2076,7 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
   if (err)
     goto out;
 
-  err = ssh_key_type_lookup (key_type, NULL, &spec);
+  err = ssh_key_type_lookup (key_type, 0, &spec);
   if (err)
     goto out;
 
@@ -2230,7 +2126,7 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
        *   string      private_key
        *
        * Note that the private key is the concatenation of the private
-       * key with the public key.  Thus theres are 64 bytes; however
+       * key with the public key.  Thus there's are 64 bytes; however
        * we only want the real 32 byte private key - Libgcrypt expects
        * this.
        */
@@ -2407,17 +2303,17 @@ ssh_send_key_public (estream_t stream, gcry_sexp_t key,
                      const char *override_comment)
 {
   ssh_key_type_spec_t spec;
-  char *key_type = NULL;
+  int algo;
   char *comment = NULL;
   void *blob = NULL;
   size_t bloblen;
-  gpg_error_t err;
+  gpg_error_t err = 0;
 
-  err = sexp_extract_identifier (key, &key_type);
-  if (err)
+  algo = get_pk_algo_from_key (key);
+  if (algo == 0)
     goto out;
 
-  err = ssh_key_type_lookup (NULL, key_type, &spec);
+  err = ssh_key_type_lookup (NULL, algo, &spec);
   if (err)
     goto out;
 
@@ -2443,7 +2339,6 @@ ssh_send_key_public (estream_t stream, gcry_sexp_t key,
     goto out;
 
  out:
-  xfree (key_type);
   xfree (comment);
   es_free (blob);
 
@@ -2502,6 +2397,34 @@ ssh_key_grip (gcry_sexp_t key, unsigned char *buffer)
 }
 
 
+static gpg_error_t
+card_key_list (ctrl_t ctrl, char **r_serialno, strlist_t *result)
+{
+  gpg_error_t err;
+
+  *r_serialno = NULL;
+  *result = NULL;
+
+  err = agent_card_serialno (ctrl, r_serialno, NULL);
+  if (err)
+    {
+      if (gpg_err_code (err) != GPG_ERR_ENODEV && opt.verbose)
+        log_info (_("error getting serial number of card: %s\n"),
+                  gpg_strerror (err));
+
+      /* Nothing available.  */
+      return 0;
+    }
+
+  err = agent_card_cardlist (ctrl, result);
+  if (err)
+    {
+      xfree (*r_serialno);
+      *r_serialno = NULL;
+    }
+  return err;
+}
+
 /* Check whether a smartcard is available and whether it has a usable
    key.  Store a copy of that key at R_PK and return 0.  If no key is
    available store NULL at R_PK and return an error code.  If CARDSN
@@ -2528,7 +2451,7 @@ card_key_available (ctrl_t ctrl, gcry_sexp_t *r_pk, char **cardsn)
   if ( gpg_err_code (err) == GPG_ERR_CARD_REMOVED )
     {
       /* Ask for the serial number to reset the card.  */
-      err = agent_card_serialno (ctrl, &serialno);
+      err = agent_card_serialno (ctrl, &serialno, NULL);
       if (err)
         {
           if (opt.verbose)
@@ -2594,39 +2517,9 @@ card_key_available (ctrl_t ctrl, gcry_sexp_t *r_pk, char **cardsn)
   if ( agent_key_available (grip) )
     {
       /* (Shadow)-key is not available in our key storage.  */
-      unsigned char *shadow_info;
-      unsigned char *tmp;
-
-      shadow_info = make_shadow_info (serialno, authkeyid);
-      if (!shadow_info)
-        {
-          err = gpg_error_from_syserror ();
-          xfree (pkbuf);
-          gcry_sexp_release (s_pk);
-          xfree (serialno);
-          xfree (authkeyid);
-          return err;
-        }
-      err = agent_shadow_key (pkbuf, shadow_info, &tmp);
-      xfree (shadow_info);
+      err = agent_write_shadow_key (grip, serialno, authkeyid, pkbuf, 0);
       if (err)
         {
-          log_error (_("shadowing the key failed: %s\n"), gpg_strerror (err));
-          xfree (pkbuf);
-          gcry_sexp_release (s_pk);
-          xfree (serialno);
-          xfree (authkeyid);
-          return err;
-        }
-      xfree (pkbuf);
-      pkbuf = tmp;
-      pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
-      assert (pkbuflen);
-
-      err = agent_write_private_key (grip, pkbuf, pkbuflen, 0);
-      if (err)
-        {
-          log_error (_("error writing key: %s\n"), gpg_strerror (err));
           xfree (pkbuf);
           gcry_sexp_release (s_pk);
           xfree (serialno);
@@ -2683,27 +2576,20 @@ static gpg_error_t
 ssh_handler_request_identities (ctrl_t ctrl,
                                 estream_t request, estream_t response)
 {
-  ssh_key_type_spec_t spec;
-  char *key_fname = NULL;
-  char *fnameptr;
   u32 key_counter;
   estream_t key_blobs;
-  gcry_sexp_t key_secret;
   gcry_sexp_t key_public;
   gpg_error_t err;
   int ret;
   ssh_control_file_t cf = NULL;
-  char *cardsn;
   gpg_error_t ret_err;
 
   (void)request;
 
   /* Prepare buffer stream.  */
 
-  key_secret = NULL;
   key_public = NULL;
   key_counter = 0;
-  err = 0;
 
   key_blobs = es_fopenmem (0, "r+b");
   if (! key_blobs)
@@ -2716,42 +2602,57 @@ ssh_handler_request_identities (ctrl_t ctrl,
      reader - this should be allowed even without being listed in
      sshcontrol. */
 
-  if (!opt.disable_scdaemon
-      && !card_key_available (ctrl, &key_public, &cardsn))
+  if (!opt.disable_scdaemon)
     {
-      err = ssh_send_key_public (key_blobs, key_public, cardsn);
-      gcry_sexp_release (key_public);
-      key_public = NULL;
-      xfree (cardsn);
+      char *serialno;
+      strlist_t card_list, sl;
+
+      err = card_key_list (ctrl, &serialno, &card_list);
       if (err)
-        goto out;
+        {
+          if (opt.verbose)
+            log_info (_("error getting list of cards: %s\n"),
+                      gpg_strerror (err));
+          goto scd_out;
+        }
 
-      key_counter++;
-    }
+      for (sl = card_list; sl; sl = sl->next)
+        {
+          char *serialno0;
+          char *cardsn;
 
+          err = agent_card_serialno (ctrl, &serialno0, sl->d);
+          if (err)
+            {
+              if (opt.verbose)
+                log_info (_("error getting serial number of card: %s\n"),
+                          gpg_strerror (err));
+              continue;
+            }
 
-  /* Prepare buffer for key name construction.  */
-  {
-    char *dname;
+          xfree (serialno0);
+          if (card_key_available (ctrl, &key_public, &cardsn))
+            continue;
 
-    dname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, NULL);
-    if (!dname)
-      {
-        err = gpg_err_code_from_syserror ();
-        goto out;
-      }
+          err = ssh_send_key_public (key_blobs, key_public, cardsn);
+          gcry_sexp_release (key_public);
+          key_public = NULL;
+          xfree (cardsn);
+          if (err)
+            {
+              xfree (serialno);
+              free_strlist (card_list);
+              goto out;
+            }
 
-    key_fname = xtrymalloc (strlen (dname) + 1 + 40 + 4 + 1);
-    if (!key_fname)
-      {
-        err = gpg_err_code_from_syserror ();
-        xfree (dname);
-        goto out;
-      }
-    fnameptr = stpcpy (stpcpy (key_fname, dname), "/");
-    xfree (dname);
-  }
+          key_counter++;
+        }
+
+      xfree (serialno);
+      free_strlist (card_list);
+    }
 
+ scd_out:
   /* Then look at all the registered and non-disabled keys. */
   err = open_control_file (&cf, 0);
   if (err)
@@ -2759,52 +2660,29 @@ ssh_handler_request_identities (ctrl_t ctrl,
 
   while (!read_control_file_item (cf))
     {
+      unsigned char grip[20];
+
       if (!cf->item.valid)
         continue; /* Should not happen.  */
       if (cf->item.disabled)
         continue;
       assert (strlen (cf->item.hexgrip) == 40);
+      hex2bin (cf->item.hexgrip, grip, sizeof (grip));
 
-      stpcpy (stpcpy (fnameptr, cf->item.hexgrip), ".key");
-
-      /* Read file content.  */
-      {
-        unsigned char *buffer;
-        size_t buffer_n;
-
-        err = file_to_buffer (key_fname, &buffer, &buffer_n);
-        if (err)
-          {
-            log_error ("%s:%d: key '%s' skipped: %s\n",
-                       cf->fname, cf->lnr, cf->item.hexgrip,
-                       gpg_strerror (err));
-            continue;
-          }
-
-        err = gcry_sexp_sscan (&key_secret, NULL, (char*)buffer, buffer_n);
-        xfree (buffer);
-        if (err)
-          goto out;
-      }
-
-      {
-        char *key_type = NULL;
-
-        err = sexp_extract_identifier (key_secret, &key_type);
-        if (err)
-          goto out;
-
-        err = ssh_key_type_lookup (NULL, key_type, &spec);
-        xfree (key_type);
-        if (err)
-          goto out;
-      }
+      err = agent_public_key_from_file (ctrl, grip, &key_public);
+      if (err)
+        {
+          log_error ("%s:%d: key '%s' skipped: %s\n",
+                     cf->fname, cf->lnr, cf->item.hexgrip,
+                     gpg_strerror (err));
+          continue;
+        }
 
-      err = ssh_send_key_public (key_blobs, key_secret, NULL);
+      err = ssh_send_key_public (key_blobs, key_public, NULL);
       if (err)
         goto out;
-      gcry_sexp_release (key_secret);
-      key_secret = NULL;
+      gcry_sexp_release (key_public);
+      key_public = NULL;
 
       key_counter++;
     }
@@ -2820,7 +2698,6 @@ ssh_handler_request_identities (ctrl_t ctrl,
  out:
   /* Send response.  */
 
-  gcry_sexp_release (key_secret);
   gcry_sexp_release (key_public);
 
   if (!err)
@@ -2838,7 +2715,6 @@ ssh_handler_request_identities (ctrl_t ctrl,
 
   es_fclose (key_blobs);
   close_control_file (cf);
-  xfree (key_fname);
 
   return ret_err;
 }
@@ -2858,7 +2734,7 @@ data_hash (unsigned char *data, size_t data_n,
 }
 
 
-/* This function signs the data described by CTRL. If HASH is is not
+/* This function signs the data described by CTRL. If HASH is not
    NULL, (HASH,HASHLEN) overrides the hash stored in CTRL.  This is to
    allow the use of signature algorithms that implement the hashing
    internally (e.g. Ed25519).  On success the created signature is
@@ -2898,7 +2774,7 @@ data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec,
       err = agent_raw_key_from_file (ctrl, ctrl->keygrip, &key);
       if (err)
         goto out;
-      err = ssh_get_fingerprint_string (key, &fpr);
+      err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &fpr);
       if (!err)
         {
           gcry_sexp_t tmpsxp = gcry_sexp_find_token (key, "comment", 0);
@@ -3104,6 +2980,7 @@ ssh_key_extract_comment (gcry_sexp_t key, char **r_comment)
 
 /* This function converts the key contained in the S-Expression KEY
    into a buffer, which is protected by the passphrase PASSPHRASE.
+   If PASSPHRASE is the empty passphrase, the key is not protected.
    Returns usual error code.  */
 static gpg_error_t
 ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase,
@@ -3113,7 +2990,6 @@ ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase,
   unsigned int buffer_new_n;
   gpg_error_t err;
 
-  err = 0;
   buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, NULL, 0);
   buffer_new = xtrymalloc_secure (buffer_new_n);
   if (! buffer_new)
@@ -3125,7 +3001,17 @@ ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase,
   gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, buffer_new, buffer_new_n);
   /* FIXME: guarantee?  */
 
-  err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0, -1);
+  if (*passphrase)
+    err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0, -1);
+  else
+    {
+      /* The key derivation function does not support zero length
+       * strings.  Store key unprotected if the user wishes so.  */
+      *buffer = buffer_new;
+      *buffer_n = buffer_new_n;
+      buffer_new = NULL;
+      err = 0;
+    }
 
  out:
 
@@ -3152,8 +3038,8 @@ reenter_compare_cb (struct pin_entry_info_s *pi)
 /* Store the ssh KEY into our local key storage and protect it after
    asking for a passphrase.  Cache that passphrase.  TTL is the
    maximum caching time for that key.  If the key already exists in
-   our key storage, don't do anything.  When entering a new key also
-   add an entry to the sshcontrol file.  */
+   our key storage, don't do anything.  When entering a key also add
+   an entry to the sshcontrol file.  */
 static gpg_error_t
 ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec,
                        gcry_sexp_t key, int ttl, int confirm)
@@ -3175,15 +3061,17 @@ ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec,
   if (err)
     goto out;
 
-  /* Check whether the key is already in our key storage.  Don't do
-     anything then.  */
-  if ( !agent_key_available (key_grip_raw) )
-    goto out; /* Yes, key is available.  */
+  bin2hex (key_grip_raw, 20, key_grip);
 
-  err = ssh_get_fingerprint_string (key, &key_fpr);
+  err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &key_fpr);
   if (err)
     goto out;
 
+  /* Check whether the key is already in our key storage.  Don't do
+     anything then besides (re-)adding it to sshcontrol.  */
+  if ( !agent_key_available (key_grip_raw) )
+    goto key_exists; /* Yes, key is available.  */
+
   err = ssh_key_extract_comment (key, &comment);
   if (err)
     goto out;
@@ -3249,13 +3137,13 @@ ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec,
     goto out;
 
   /* Cache this passphrase. */
-  bin2hex (key_grip_raw, 20, key_grip);
   err = agent_put_cache (key_grip, CACHE_MODE_SSH, pi->pin, ttl);
   if (err)
     goto out;
 
+ key_exists:
   /* And add an entry to the sshcontrol file.  */
-  err = add_control_entry (ctrl, spec, key_grip, key_fpr, ttl, confirm);
+  err = add_control_entry (ctrl, spec, key_grip, key, ttl, confirm);
 
 
  out:
@@ -3417,7 +3305,7 @@ ssh_identities_remove_all (void)
   err = 0;
 
   /* FIXME: shall we remove _all_ cache entries or only those
-     registered through the ssh emulation?  */
+     registered through the ssh-agent protocol?  */
 
   return err;
 }
@@ -3513,10 +3401,10 @@ ssh_handler_unlock (ctrl_t ctrl, estream_t request, estream_t response)
 /* Return the request specification for the request identified by TYPE
    or NULL in case the requested request specification could not be
    found.  */
-static ssh_request_spec_t *
+static const ssh_request_spec_t *
 request_spec_lookup (int type)
 {
-  ssh_request_spec_t *spec;
+  const ssh_request_spec_t *spec;
   unsigned int i;
 
   for (i = 0; i < DIM (request_specs); i++)
@@ -3540,7 +3428,7 @@ request_spec_lookup (int type)
 static int
 ssh_request_process (ctrl_t ctrl, estream_t stream_sock)
 {
-  ssh_request_spec_t *spec;
+  const ssh_request_spec_t *spec;
   estream_t response = NULL;
   estream_t request = NULL;
   unsigned char request_type;
@@ -3692,6 +3580,64 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock)
 }
 
 
+/* Return the peer's pid.  */
+static unsigned long
+get_client_pid (int fd)
+{
+  pid_t client_pid = (pid_t)0;
+
+#ifdef SO_PEERCRED
+  {
+#ifdef HAVE_STRUCT_SOCKPEERCRED_PID
+    struct sockpeercred cr;
+#else
+    struct ucred cr;
+#endif
+    socklen_t cl = sizeof cr;
+
+    if (!getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl))
+      {
+#if defined (HAVE_STRUCT_SOCKPEERCRED_PID) || defined (HAVE_STRUCT_UCRED_PID)
+        client_pid = cr.pid;
+#elif defined (HAVE_STRUCT_UCRED_CR_PID)
+        client_pid = cr.cr_pid;
+#else
+#error "Unknown SO_PEERCRED struct"
+#endif
+      }
+  }
+#elif defined (LOCAL_PEERPID)
+  {
+    socklen_t len = sizeof (pid_t);
+
+    getsockopt (fd, SOL_LOCAL, LOCAL_PEERPID, &client_pid, &len);
+  }
+#elif defined (LOCAL_PEEREID)
+  {
+    struct unpcbid unp;
+    socklen_t unpl = sizeof unp;
+
+    if (getsockopt (fd, 0, LOCAL_PEEREID, &unp, &unpl) != -1)
+      client_pid = unp.unp_pid;
+  }
+#elif defined (HAVE_GETPEERUCRED)
+  {
+    ucred_t *ucred = NULL;
+
+    if (getpeerucred (fd, &ucred) != -1)
+      {
+        client_pid= ucred_getpid (ucred);
+        ucred_free (ucred);
+      }
+  }
+#else
+  (void)fd;
+#endif
+
+  return (unsigned long)client_pid;
+}
+
+
 /* Start serving client on SOCK_CLIENT.  */
 void
 start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
@@ -3704,6 +3650,8 @@ start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
   if (err)
     goto out;
 
+  ctrl->client_pid = get_client_pid (FD2INT(sock_client));
+
   /* Create stream from socket.  */
   stream_sock = es_fdopen (FD2INT(sock_client), "r+");
   if (!stream_sock)
@@ -3727,7 +3675,7 @@ start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
   /* Main processing loop. */
   while ( !ssh_request_process (ctrl, stream_sock) )
     {
-      /* Check wether we have reached EOF before trying to read
+      /* Check whether we have reached EOF before trying to read
         another request.  */
       int c;
 
@@ -3749,7 +3697,7 @@ start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
 
 #ifdef HAVE_W32_SYSTEM
 /* Serve one ssh-agent request.  This is used for the Putty support.
-   REQUEST is the the mmapped memory which may be accessed up to a
+   REQUEST is the mmapped memory which may be accessed up to a
    length of MAXREQLEN.  Returns 0 on success which also indicates
    that a valid SSH response message is now in REQUEST.  */
 int
@@ -3844,7 +3792,7 @@ serve_mmapped_ssh_request (ctrl_t ctrl,
     size_t response_size;
 
     /* NB: In contrast to the request-stream, the response stream
-       includes the the message type byte.  */
+       includes the message type byte.  */
     if (es_fclose_snatch (response_stream, &response_data, &response_size))
       {
         log_error ("snatching ssh response failed: %s",