agent: Add include files.
[gnupg.git] / agent / command-ssh.c
index be2ab3b..382f9e6 100644 (file)
@@ -1,6 +1,6 @@
-/* command-ssh.c - gpg-agent's ssh-agent emulation layer
- * Copyright (C) 2004, 2005, 2006, 2009, 2012 Free Software Foundation, Inc.
- * Copyright (C) 2013, 2014 Werner Koch
+/* 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
  *
  * This file is part of GnuPG.
  *
@@ -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/ssh-utils.h"
+#include "util.h"
+#include "ssh-utils.h"
 
 
 \f
@@ -75,6 +83,7 @@
 #define SPEC_FLAG_USE_PKCS1V2 (1 << 0)
 #define SPEC_FLAG_IS_ECDSA    (1 << 1)
 #define SPEC_FLAG_IS_EdDSA    (1 << 2)  /*(lowercase 'd' on purpose.)*/
+#define SPEC_FLAG_WITH_CERT   (1 << 7)
 
 /* The name of the control file.  */
 #define SSH_CONTROL_FILE_NAME "sshcontrol"
@@ -148,8 +157,11 @@ struct ssh_key_type_spec
   /* Algorithm identifier as used by OpenSSH.  */
   const char *ssh_identifier;
 
+  /* Human readable name of the algorithm.  */
+  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.  */
@@ -271,34 +283,70 @@ static ssh_request_spec_t request_specs[] =
 static ssh_key_type_spec_t ssh_key_types[] =
   {
     {
-      "ssh-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", "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", "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", "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", "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", "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",
+      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",
+      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",
+      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",
+      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",
+      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",
+      GCRY_PK_ECC, "qd",  "q", "rs", "qd",
+      NULL,                 ssh_signature_encoder_ecdsa,
+      "nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
     }
   };
 
@@ -328,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 *
@@ -528,7 +559,7 @@ stream_write_data (estream_t stream, const unsigned char *buffer, size_t size)
 /* Read a binary string from STREAM into STRING, store size of string
    in STRING_SIZE.  Append a hidden nul so that the result may
    directly be used as a C string.  Depending on SECURE use secure
-   memory for STRING.  */
+   memory for STRING.  If STRING is NULL do only a dummy read.  */
 static gpg_error_t
 stream_read_string (estream_t stream, unsigned int secure,
                    unsigned char **string, u32 *string_size)
@@ -545,25 +576,35 @@ stream_read_string (estream_t stream, unsigned int secure,
   if (err)
     goto out;
 
-  /* Allocate space.  */
-  if (secure)
-    buffer = xtrymalloc_secure (length + 1);
-  else
-    buffer = xtrymalloc (length + 1);
-  if (! buffer)
+  if (string)
     {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
+      /* Allocate space.  */
+      if (secure)
+        buffer = xtrymalloc_secure (length + 1);
+      else
+        buffer = xtrymalloc (length + 1);
+      if (! buffer)
+        {
+          err = gpg_error_from_syserror ();
+          goto out;
+        }
 
-  /* Read data.  */
-  err = stream_read_data (stream, buffer, length);
-  if (err)
-    goto out;
+      /* Read data.  */
+      err = stream_read_data (stream, buffer, length);
+      if (err)
+        goto out;
+
+      /* Finalize string object.  */
+      buffer[length] = 0;
+      *string = buffer;
+    }
+  else  /* Dummy read requested.  */
+    {
+      err = stream_read_skip (stream, length);
+      if (err)
+        goto out;
+    }
 
-  /* Finalize string object.  */
-  buffer[length] = 0;
-  *string = buffer;
   if (string_size)
     *string_size = length;
 
@@ -577,8 +618,9 @@ stream_read_string (estream_t stream, unsigned int secure,
 
 
 /* Read a binary string from STREAM and store it as an opaque MPI at
-   R_MPI.  Depending on SECURE use secure memory.  If the string is
-   too large for key material return an error.  */
+   R_MPI, adding 0x40 (this is the prefix for EdDSA key in OpenPGP).
+   Depending on SECURE use secure memory.  If the string is too large
+   for key material return an error.  */
 static gpg_error_t
 stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi)
 {
@@ -604,9 +646,9 @@ stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi)
 
   /* Allocate space.  */
   if (secure)
-    buffer = xtrymalloc_secure (length? length:1);
+    buffer = xtrymalloc_secure (length+1);
   else
-    buffer = xtrymalloc (length?length:1);
+    buffer = xtrymalloc (length+1);
   if (!buffer)
     {
       err = gpg_error_from_syserror ();
@@ -614,11 +656,12 @@ stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi)
     }
 
   /* Read data.  */
-  err = stream_read_data (stream, buffer, length);
+  err = stream_read_data (stream, buffer + 1, length);
   if (err)
     goto leave;
 
-  *r_mpi = gcry_mpi_set_opaque (NULL, buffer, 8*length);
+  buffer[0] = 0x40;
+  *r_mpi = gcry_mpi_set_opaque (NULL, buffer, 8*(length+1));
   buffer = NULL;
 
  leave:
@@ -762,67 +805,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,
@@ -845,7 +827,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 ();
@@ -1028,7 +1010,8 @@ search_control_file (ssh_control_file_t cf, const char *hexgrip,
 
   assert (strlen (hexgrip) == 40 );
 
-  *r_disabled = 0;
+  if (r_disabled)
+    *r_disabled = 0;
   if (r_ttl)
     *r_ttl = 0;
   if (r_confirm)
@@ -1044,7 +1027,8 @@ search_control_file (ssh_control_file_t cf, const char *hexgrip,
     }
   if (!err)
     {
-      *r_disabled = cf->item.disabled;
+      if (r_disabled)
+        *r_disabled = cf->item.disabled;
       if (r_ttl)
         *r_ttl = cf->item.ttl;
       if (r_confirm)
@@ -1061,7 +1045,8 @@ search_control_file (ssh_control_file_t cf, const char *hexgrip,
    general used to add a key received through the ssh-add function.
    We can assume that the user wants to allow ssh using this key. */
 static gpg_error_t
-add_control_entry (ctrl_t ctrl, const char *hexgrip, const char *fmtfpr,
+add_control_entry (ctrl_t ctrl, ssh_key_type_spec_t *spec,
+                   const char *hexgrip, const char *fmtfpr,
                    int ttl, int confirm)
 {
   gpg_error_t err;
@@ -1084,9 +1069,10 @@ add_control_entry (ctrl_t ctrl, const char *hexgrip, const char *fmtfpr,
          opened in append mode, we simply need to write to it.  */
       tp = localtime (&atime);
       fprintf (cf->fp,
-               ("# Key added on: %04d-%02d-%02d %02d:%02d:%02d\n"
-                "# Fingerprint:  %s\n"
+               ("# %s key added on: %04d-%02d-%02d %02d:%02d:%02d\n"
+                "# MD5 Fingerprint:  %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":"");
@@ -1148,7 +1134,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)
 {
@@ -1213,7 +1199,7 @@ ssh_search_control_file (ssh_control_file_t cf,
   /* We need to make sure that HEXGRIP is all uppercase.  The easiest
      way to do this and also check its length is by copying to a
      second buffer. */
-  for (i=0, s=hexgrip; i < 40; s++, i++)
+  for (i=0, s=hexgrip; i < 40 && *s; s++, i++)
     uphexgrip[i] = *s >= 'a'? (*s & 0xdf): *s;
   uphexgrip[i] = 0;
   if (i != 40)
@@ -1249,31 +1235,38 @@ mpint_list_free (gcry_mpi_t *mpi_list)
 }
 
 /* Receive key material MPIs from STREAM according to KEY_SPEC;
-   depending on SECRET expect a public key or secret key.  The newly
+   depending on SECRET expect a public key or secret key.  CERT is the
+   certificate blob used if KEY_SPEC indicates the certificate format;
+   it needs to be positioned to the end of the nonce.  The newly
    allocated list of MPIs is stored in MPI_LIST.  Returns usual error
    code.  */
 static gpg_error_t
 ssh_receive_mpint_list (estream_t stream, int secret,
-                       ssh_key_type_spec_t key_spec, gcry_mpi_t **mpi_list)
+                       ssh_key_type_spec_t *spec, estream_t cert,
+                        gcry_mpi_t **mpi_list)
 {
   const char *elems_public;
   unsigned int elems_n;
   const char *elems;
   int elem_is_secret;
-  gcry_mpi_t *mpis;
-  gpg_error_t err;
+  gcry_mpi_t *mpis = NULL;
+  gpg_error_t err = 0;
   unsigned int i;
 
-  mpis = NULL;
-  err = 0;
-
   if (secret)
-    elems = key_spec.elems_key_secret;
+    elems = spec->elems_key_secret;
   else
-    elems = key_spec.elems_key_public;
+    elems = spec->elems_key_public;
   elems_n = strlen (elems);
+  elems_public = spec->elems_key_public;
 
-  elems_public = key_spec.elems_key_public;
+  /* Check that either both, CERT and the WITH_CERT flag, are given or
+     none of them.  */
+  if (!(!!(spec->flags & SPEC_FLAG_WITH_CERT) ^ !cert))
+    {
+      err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+      goto out;
+    }
 
   mpis = xtrycalloc (elems_n + 1, sizeof *mpis );
   if (!mpis)
@@ -1286,18 +1279,20 @@ ssh_receive_mpint_list (estream_t stream, int secret,
   for (i = 0; i < elems_n; i++)
     {
       if (secret)
-       elem_is_secret = ! strchr (elems_public, elems[i]);
-      err = stream_read_mpi (stream, elem_is_secret, &mpis[i]);
+       elem_is_secret = !strchr (elems_public, elems[i]);
+
+      if (cert && !elem_is_secret)
+        err = stream_read_mpi (cert, elem_is_secret, &mpis[i]);
+      else
+        err = stream_read_mpi (stream, elem_is_secret, &mpis[i]);
       if (err)
-       break;
+       goto out;
     }
-  if (err)
-    goto out;
 
   *mpi_list = mpis;
+  mpis = NULL;
 
  out:
-
   if (err)
     mpint_list_free (mpis);
 
@@ -1612,15 +1607,13 @@ ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
   gpg_error_t err = 0;
   gcry_sexp_t valuelist = NULL;
   gcry_sexp_t sublist = NULL;
-  gcry_mpi_t sig_value = NULL;
-  gcry_mpi_t *mpis = NULL;
   const char *elems;
   size_t elems_n;
   int i;
 
   unsigned char *data[2] = {NULL, NULL};
   size_t data_n[2];
-  size_t totallen;
+  size_t totallen = 0;
 
   valuelist = gcry_sexp_nth (s_signature, 1);
   if (!valuelist)
@@ -1632,14 +1625,13 @@ ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
   elems = spec->elems_signature;
   elems_n = strlen (elems);
 
-  mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
-  if (!mpis)
+  if (elems_n != DIM(data))
     {
-      err = gpg_error_from_syserror ();
+      err = gpg_error (GPG_ERR_INV_SEXP);
       goto out;
     }
 
-  for (i = 0; i < elems_n; i++)
+  for (i = 0; i < DIM(data); i++)
     {
       sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
       if (!sublist)
@@ -1648,39 +1640,25 @@ ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
          break;
        }
 
-      sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
-      if (!sig_value)
+      data[i] = gcry_sexp_nth_buffer (sublist, 1, &data_n[i]);
+      if (!data[i])
        {
          err = gpg_error (GPG_ERR_INTERNAL); /* FIXME?  */
          break;
        }
+      totallen += data_n[i];
       gcry_sexp_release (sublist);
       sublist = NULL;
-
-      mpis[i] = sig_value;
     }
   if (err)
     goto out;
 
-  /* EdDSA specific.  Actually TOTALLEN will always be 64.  */
-
-  totallen = 0;
-  for (i = 0; i < DIM(data); i++)
-    {
-      err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data[i], &data_n[i], mpis[i]);
-      if (err)
-       goto out;
-      totallen += data_n[i];
-    }
-
-  gcry_log_debug ("  out: len=%zu\n", totallen);
   err = stream_write_uint32 (stream, totallen);
   if (err)
     goto out;
 
   for (i = 0; i < DIM(data); i++)
     {
-      gcry_log_debughex ("  out", data[i], data_n[i]);
       err = stream_write_data (stream, data[i], data_n[i]);
       if (err)
         goto out;
@@ -1691,7 +1669,6 @@ ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
     xfree (data[i]);
   gcry_sexp_release (valuelist);
   gcry_sexp_release (sublist);
-  mpint_list_free (mpis);
   return err;
 }
 
@@ -1715,7 +1692,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))
     {
@@ -1779,7 +1756,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);
@@ -1826,6 +1810,7 @@ sexp_key_construct (gcry_sexp_t *r_sexp,
   es_fclose (format);
   xfree (arg_list);
   xfree (formatbuf);
+  xfree (algo_name);
 
   return err;
 }
@@ -1882,8 +1867,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);
@@ -1975,6 +1960,11 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret,
               err = gpg_error (GPG_ERR_INV_SEXP);
               goto out;
             }
+          if (*p_elems == 'q' && datalen)
+            { /* Remove the prefix 0x40.  */
+              data++;
+              datalen--;
+            }
           err = stream_write_string (stream, data, datalen);
           if (err)
             goto out;
@@ -2018,51 +2008,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
 
 /*
@@ -2073,23 +2018,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))
@@ -2116,6 +2056,7 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
   gpg_error_t err;
   char *key_type = NULL;
   char *comment = NULL;
+  estream_t cert = NULL;
   gcry_sexp_t key = NULL;
   ssh_key_type_spec_t spec;
   gcry_mpi_t *mpi_list = NULL;
@@ -2127,10 +2068,48 @@ 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;
 
+  if ((spec.flags & SPEC_FLAG_WITH_CERT))
+    {
+      /* This is an OpenSSH certificate+private key.  The certificate
+         is an SSH string and which we store in an estream object. */
+      unsigned char *buffer;
+      u32 buflen;
+      char *cert_key_type;
+
+      err = stream_read_string (stream, 0, &buffer, &buflen);
+      if (err)
+        goto out;
+      cert = es_fopenmem_init (0, "rb", buffer, buflen);
+      xfree (buffer);
+      if (!cert)
+        {
+          err = gpg_error_from_syserror ();
+          goto out;
+        }
+
+      /* Check that the key type matches.  */
+      err = stream_read_cstring (cert, &cert_key_type);
+      if (err)
+        goto out;
+      if (strcmp (cert_key_type, key_type) )
+        {
+          xfree (cert_key_type);
+          log_error ("key types in received ssh certificate do not match\n");
+          err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+          goto out;
+        }
+      xfree (cert_key_type);
+
+      /* Skip the nonce.  */
+      err = stream_read_string (cert, 0, NULL, NULL);
+      if (err)
+        goto out;
+    }
+
   if ((spec.flags & SPEC_FLAG_IS_EdDSA))
     {
       /* The format of an EdDSA key is:
@@ -2150,7 +2129,7 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
           goto out;
         }
 
-      err = stream_read_blob (stream, 0, &mpi_list[0]);
+      err = stream_read_blob (cert? cert : stream, 0, &mpi_list[0]);
       if (err)
         goto out;
       if (secret)
@@ -2200,12 +2179,14 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
        *   mpint       ecdsa_private
        *
        * Note that we use the mpint reader instead of the string
-       * reader for ecsa_public_key.
+       * reader for ecsa_public_key.  For the certificate variante
+       * ecdsa_curve_name+ecdsa_public_key are replaced by the
+       * certificate.
        */
       unsigned char *buffer;
       const char *mapped;
 
-      err = stream_read_string (stream, 0, &buffer, NULL);
+      err = stream_read_string (cert? cert : stream, 0, &buffer, NULL);
       if (err)
         goto out;
       curve_name = buffer;
@@ -2232,13 +2213,13 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
             }
         }
 
-      err = ssh_receive_mpint_list (stream, secret, spec, &mpi_list);
+      err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
       if (err)
         goto out;
     }
   else
     {
-      err = ssh_receive_mpint_list (stream, secret, spec, &mpi_list);
+      err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
       if (err)
         goto out;
     }
@@ -2296,6 +2277,7 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
   *key_new = key;
 
  out:
+  es_fclose (cert);
   mpint_list_free (mpi_list);
   xfree (curve_name);
   xfree (key_type);
@@ -2313,17 +2295,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;
 
@@ -2340,14 +2322,15 @@ ssh_send_key_public (estream_t stream, gcry_sexp_t key,
   else
     {
       err = ssh_key_extract_comment (key, &comment);
-      if (!err)
+      if (err)
+        err = stream_write_cstring (stream, "(none)");
+      else
         err = stream_write_cstring (stream, comment);
     }
   if (err)
     goto out;
 
  out:
-  xfree (key_type);
   xfree (comment);
   es_free (blob);
 
@@ -2363,13 +2346,11 @@ ssh_read_key_public_from_blob (unsigned char *blob, size_t blob_size,
                               gcry_sexp_t *key_public,
                               ssh_key_type_spec_t *key_spec)
 {
-  estream_t blob_stream;
   gpg_error_t err;
+  estream_t blob_stream;
 
-  err = 0;
-  /* FIXME: Use fopenmem_init */
-  blob_stream = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+");
-  if (! blob_stream)
+  blob_stream = es_fopenmem (0, "r+b");
+  if (!blob_stream)
     {
       err = gpg_error_from_syserror ();
       goto out;
@@ -2386,10 +2367,7 @@ ssh_read_key_public_from_blob (unsigned char *blob, size_t blob_size,
   err = ssh_receive_key (blob_stream, key_public, 0, 0, key_spec);
 
  out:
-
-  if (blob_stream)
-    es_fclose (blob_stream);
-
+  es_fclose (blob_stream);
   return err;
 }
 
@@ -2411,6 +2389,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
@@ -2437,7 +2443,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)
@@ -2503,39 +2509,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);
-      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);
+      err = agent_write_shadow_key (grip, serialno, authkeyid, pkbuf, 0);
       if (err)
         {
-          log_error (_("error writing key: %s\n"), gpg_strerror (err));
           xfree (pkbuf);
           gcry_sexp_release (s_pk);
           xfree (serialno);
@@ -2592,29 +2568,23 @@ 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_mopen (NULL, 0, 0, 1, NULL, NULL, "r+b");
+  key_blobs = es_fopenmem (0, "r+b");
   if (! key_blobs)
     {
       err = gpg_error_from_syserror ();
@@ -2625,42 +2595,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)
@@ -2668,52 +2653,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++;
     }
@@ -2729,7 +2691,6 @@ ssh_handler_request_identities (ctrl_t ctrl,
  out:
   /* Send response.  */
 
-  gcry_sexp_release (key_secret);
   gcry_sexp_release (key_public);
 
   if (!err)
@@ -2747,7 +2708,6 @@ ssh_handler_request_identities (ctrl_t ctrl,
 
   es_fclose (key_blobs);
   close_control_file (cf);
-  xfree (key_fname);
 
   return ret_err;
 }
@@ -2767,7 +2727,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
@@ -2818,14 +2778,14 @@ data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec,
       gcry_sexp_release (key);
       if (err)
         goto out;
-      prompt = xtryasprintf (_("An ssh process requested the use of key%%0A"
-                               "  %s%%0A"
-                               "  (%s)%%0A"
-                               "Do you want to allow this?"),
+      prompt = xtryasprintf (L_("An ssh process requested the use of key%%0A"
+                                "  %s%%0A"
+                                "  (%s)%%0A"
+                                "Do you want to allow this?"),
                              fpr, comment? comment:"");
       xfree (fpr);
       gcry_free (comment);
-      err = agent_get_confirmation (ctrl, prompt, _("Allow"), _("Deny"), 0);
+      err = agent_get_confirmation (ctrl, prompt, L_("Allow"), L_("Deny"), 0);
       xfree (prompt);
       if (err)
         goto out;
@@ -2834,8 +2794,8 @@ data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec,
   /* Create signature.  */
   ctrl->use_auth_call = 1;
   err = agent_pksign_do (ctrl, NULL,
-                         _("Please enter the passphrase "
-                           "for the ssh key%%0A  %F%%0A  (%c)"),
+                         L_("Please enter the passphrase "
+                            "for the ssh key%%0A  %F%%0A  (%c)"),
                          &signature_sexp,
                          CACHE_MODE_SSH, ttl_from_sshcontrol,
                          hash, hashlen);
@@ -3034,7 +2994,7 @@ 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);
+  err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0, -1);
 
  out:
 
@@ -3047,24 +3007,25 @@ ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase,
 
 /* Callback function to compare the first entered PIN with the one
    currently being entered. */
-static int
+static gpg_error_t
 reenter_compare_cb (struct pin_entry_info_s *pi)
 {
   const char *pin1 = pi->check_cb_arg;
 
   if (!strcmp (pin1, pi->pin))
     return 0; /* okay */
-  return -1;
+  return gpg_error (GPG_ERR_BAD_PASSPHRASE);
 }
 
 
 /* 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, gcry_sexp_t key, int ttl, int confirm)
+ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec,
+                       gcry_sexp_t key, int ttl, int confirm)
 {
   gpg_error_t err;
   unsigned char key_grip_raw[20];
@@ -3072,70 +3033,79 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl, int confirm)
   unsigned char *buffer = NULL;
   size_t buffer_n;
   char *description = NULL;
-  const char *description2 = _("Please re-enter this passphrase");
+  const char *description2 = L_("Please re-enter this passphrase");
   char *comment = NULL;
   char *key_fpr = NULL;
   const char *initial_errtext = NULL;
-  unsigned int i;
-  struct pin_entry_info_s *pi = NULL, *pi2;
+  struct pin_entry_info_s *pi = NULL;
+  struct pin_entry_info_s *pi2 = NULL;
 
   err = ssh_key_grip (key, key_grip_raw);
   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);
   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;
 
   if ( asprintf (&description,
-                 _("Please enter a passphrase to protect"
-                   " the received secret key%%0A"
-                   "   %s%%0A"
-                   "   %s%%0A"
-                   "within gpg-agent's key storage"),
+                 L_("Please enter a passphrase to protect"
+                    " the received secret key%%0A"
+                    "   %s%%0A"
+                    "   %s%%0A"
+                    "within gpg-agent's key storage"),
                  key_fpr, comment ? comment : "") < 0)
     {
       err = gpg_error_from_syserror ();
       goto out;
     }
 
-  pi = gcry_calloc_secure (2, sizeof (*pi) + 100 + 1);
+  pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
   if (!pi)
     {
       err = gpg_error_from_syserror ();
       goto out;
     }
-  pi2 = pi + (sizeof *pi + 100 + 1);
-  pi->max_length = 100;
+  pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1);
+  if (!pi2)
+    {
+      err = gpg_error_from_syserror ();
+      goto out;
+    }
+  pi->max_length = MAX_PASSPHRASE_LEN + 1;
   pi->max_tries = 1;
-  pi2->max_length = 100;
+  pi->with_repeat = 1;
+  pi2->max_length = MAX_PASSPHRASE_LEN + 1;
   pi2->max_tries = 1;
   pi2->check_cb = reenter_compare_cb;
   pi2->check_cb_arg = pi->pin;
 
  next_try:
-  err = agent_askpin (ctrl, description, NULL, initial_errtext, pi);
+  err = agent_askpin (ctrl, description, NULL, initial_errtext, pi, NULL, 0);
   initial_errtext = NULL;
   if (err)
     goto out;
 
-  /* Unless the passphrase is empty, ask to confirm it.  */
-  if (pi->pin && *pi->pin)
+  /* Unless the passphrase is empty or the pinentry told us that
+     it already did the repetition check, ask to confirm it.  */
+  if (*pi->pin && !pi->repeat_okay)
     {
-      err = agent_askpin (ctrl, description2, NULL, NULL, pi2);
-      if (err == -1)
+      err = agent_askpin (ctrl, description2, NULL, NULL, pi2, NULL, 0);
+      if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
        { /* The re-entered one did not match and the user did not
             hit cancel. */
-         initial_errtext = _("does not match - try again");
+         initial_errtext = L_("does not match - try again");
          goto next_try;
        }
     }
@@ -3150,18 +3120,19 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl, int confirm)
     goto out;
 
   /* Cache this passphrase. */
-  for (i = 0; i < 20; i++)
-    sprintf (key_grip + 2 * i, "%02X", key_grip_raw[i]);
-
   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, key_grip, key_fpr, ttl, confirm);
+  err = add_control_entry (ctrl, spec, key_grip, key_fpr, ttl, confirm);
 
 
  out:
+  if (pi2 && pi2->max_length)
+    wipememory (pi2->pin, pi2->max_length);
+  xfree (pi2);
   if (pi && pi->max_length)
     wipememory (pi->pin, pi->max_length);
   xfree (pi);
@@ -3202,6 +3173,7 @@ static gpg_error_t
 ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response)
 {
   gpg_error_t ret_err;
+  ssh_key_type_spec_t spec;
   gpg_error_t err;
   gcry_sexp_t key;
   unsigned char b;
@@ -3213,7 +3185,7 @@ ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response)
   ttl = 0;
 
   /* FIXME?  */
-  err = ssh_receive_key (request, &key, 1, 1, NULL);
+  err = ssh_receive_key (request, &key, 1, 1, &spec);
   if (err)
     goto out;
 
@@ -3252,7 +3224,7 @@ ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response)
   if (err)
     goto out;
 
-  err = ssh_identity_register (ctrl, key, ttl, confirm);
+  err = ssh_identity_register (ctrl, &spec, key, ttl, confirm);
 
  out:
 
@@ -3316,7 +3288,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;
 }
@@ -3440,21 +3412,16 @@ static int
 ssh_request_process (ctrl_t ctrl, estream_t stream_sock)
 {
   ssh_request_spec_t *spec;
-  estream_t response;
-  estream_t request;
+  estream_t response = NULL;
+  estream_t request = NULL;
   unsigned char request_type;
   gpg_error_t err;
-  int send_err;
+  int send_err = 0;
   int ret;
-  unsigned char *request_data;
+  unsigned char *request_data = NULL;
   u32 request_data_size;
   u32 response_size;
 
-  request_data = NULL;
-  response = NULL;
-  request = NULL;
-  send_err = 0;
-
   /* Create memory streams for request/response data.  The entire
      request will be stored in secure memory, since it might contain
      secret key material.  The response does not have to be stored in
@@ -3493,9 +3460,9 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock)
     }
 
   if (spec->secret_input)
-    request = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+");
+    request = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+b");
   else
-    request = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+");
+    request = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+b");
   if (! request)
     {
       err = gpg_error_from_syserror ();
@@ -3512,7 +3479,7 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock)
     goto out;
   es_rewind (request);
 
-  response = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+");
+  response = es_fopenmem (0, "r+b");
   if (! response)
     {
       err = gpg_error_from_syserror ();
@@ -3571,7 +3538,7 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock)
  out:
 
   if (err && es_feof (stream_sock))
-    log_error ("error occured while processing request: %s\n",
+    log_error ("error occurred while processing request: %s\n",
               gpg_strerror (err));
 
   if (send_err)
@@ -3588,45 +3555,67 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock)
 
  leave:
 
-  if (request)
-    es_fclose (request);
-  if (response)
-    es_fclose (response);
-  xfree (request_data);                /* FIXME?  */
+  es_fclose (request);
+  es_fclose (response);
+  xfree (request_data);
 
   return !!err;
 }
 
 
-/* Because the ssh protocol does not send us information about the
-   current TTY setting, we use this function to use those from startup
-   or those explictly set.  */
-static gpg_error_t
-setup_ssh_env (ctrl_t ctrl)
+/* Return the peer's pid.  */
+static unsigned long
+get_client_pid (int fd)
 {
-  static const char *names[] =
-    {"GPG_TTY", "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL};
-  gpg_error_t err = 0;
-  int idx;
-  const char *value;
+  pid_t client_pid = (pid_t)(-1);
 
-  for (idx=0; !err && names[idx]; idx++)
-      if ((value = session_env_getenv (opt.startup_env, names[idx])))
-      err = session_env_setenv (ctrl->session_env, names[idx], value);
+#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);
 
-  if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype)
-    if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype)))
-      err = gpg_error_from_syserror ();
+    getsockopt(fd, SOL_LOCAL, LOCAL_PEERPID, &client_pid, &len);
+  }
+#elif defined (LOCAL_PEEREID)
+  {
+    struct unpcbid unp;
+    socklen_t unpl = sizeof unp;
 
-  if (!err && !ctrl->lc_messages && opt.startup_lc_messages)
-    if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages)))
-      err = gpg_error_from_syserror ();
+    if (getsockopt (fd, 0, LOCAL_PEEREID, &unp, &unpl) != -1)
+      client_pid = unp.unp_pid;
+  }
+#elif defined (HAVE_GETPEERUCRED)
+  {
+    ucred_t *ucred = NULL;
 
-  if (err)
-    log_error ("error setting default session environment: %s\n",
-               gpg_strerror (err));
+    if (getpeerucred (fd, &ucred) != -1)
+      {
+        client_pid= ucred_getpid (ucred);
+        ucred_free (ucred);
+      }
+  }
+#endif
 
-  return err;
+  return client_pid == (pid_t)(-1)? 0 : (unsigned long)client_pid;
 }
 
 
@@ -3638,10 +3627,12 @@ start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
   gpg_error_t err;
   int ret;
 
-  err = setup_ssh_env (ctrl);
+  err = agent_copy_startup_env (ctrl);
   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)
@@ -3687,7 +3678,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
@@ -3701,7 +3692,7 @@ serve_mmapped_ssh_request (ctrl_t ctrl,
   u32 msglen;
   estream_t request_stream, response_stream;
 
-  if (setup_ssh_env (ctrl))
+  if (agent_copy_startup_env (ctrl))
     goto leave; /* Error setting up the environment.  */
 
   if (maxreqlen < 5)
@@ -3782,7 +3773,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",