Fix typos.
[gnupg.git] / agent / command-ssh.c
index ddf8e2c..969d898 100644 (file)
@@ -1,5 +1,6 @@
 /* command-ssh.c - gpg-agent's ssh-agent emulation layer
- * Copyright (C) 2004, 2005, 2006, 2009 Free Software Foundation, Inc.
+ * 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.
  *
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
-/* Only v2 of the ssh-agent protocol is implemented.  */
+/* Only v2 of the ssh-agent protocol is implemented.  Relevant RFCs
+   are:
+
+   RFC-4250 - Protocol Assigned Numbers
+   RFC-4251 - Protocol Architecture
+   RFC-4252 - Authentication Protocol
+   RFC-4253 - Transport Layer Protocol
+   RFC-5656 - ECC support
+
+   The protocol for the agent is defined in OpenSSH's PROTOCL.agent
+   file.
+  */
 
 #include <config.h>
 
 #include <errno.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <dirent.h>
 #include <assert.h>
 
 #include "agent.h"
 
 #include "i18n.h"
-#include "../common/ssh-utils.h"
+#include "util.h"
+#include "ssh-utils.h"
 
 
 \f
 #define SSH_DSA_SIGNATURE_PADDING 20
 #define SSH_DSA_SIGNATURE_ELEMS    2
 #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"
 
 /* The blurb we put into the header of a newly created control file.  */
 static const char sshcontrolblurb[] =
@@ -74,12 +91,11 @@ static const char sshcontrolblurb[] =
 "# the format of the entries is fixed and checked by gpg-agent. A\n"
 "# non-comment line starts with optional white spaces, followed by the\n"
 "# keygrip of the key given as 40 hex digits, optionally followed by a\n"
-"# the caching TTL in seconds and another optional field for arbitrary\n"
+"# caching TTL in seconds, and another optional field for arbitrary\n"
 "# flags.   Prepend the keygrip with an '!' mark to disable it.\n"
 "\n";
 
 
-
 /* Macros.  */
 
 /* Return a new uint32 with b0 being the most significant byte and b3
@@ -99,6 +115,10 @@ typedef gpg_error_t (*ssh_request_handler_t) (ctrl_t ctrl,
                                              estream_t request,
                                              estream_t response);
 
+
+struct ssh_key_type_spec;
+typedef struct ssh_key_type_spec ssh_key_type_spec_t;
+
 /* Type, which is used for associating request handlers with the
    appropriate request IDs.  */
 typedef struct ssh_request_spec
@@ -119,18 +139,22 @@ typedef gpg_error_t (*ssh_key_modifier_t) (const char *elems,
 /* The encoding of a generated signature is dependent on the
    algorithm; therefore algorithm specific signature encoding
    functions are necessary.  */
-typedef gpg_error_t (*ssh_signature_encoder_t) (estream_t signature_blob,
-                                               gcry_mpi_t *mpis);
+typedef gpg_error_t (*ssh_signature_encoder_t) (ssh_key_type_spec_t *spec,
+                                                estream_t signature_blob,
+                                               gcry_sexp_t sig);
 
 /* Type, which is used for boundling all the algorithm specific
    information together in a single object.  */
-typedef struct ssh_key_type_spec
+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.  */
@@ -157,9 +181,32 @@ typedef struct ssh_key_type_spec
      algorithm.  */
   ssh_signature_encoder_t signature_encoder;
 
+  /* The name of the ECC curve or NULL.  */
+  const char *curve_name;
+
+  /* The hash algorithm to be used with this key.  0 for using the
+     default.  */
+  int hash_algo;
+
   /* Misc flags.  */
   unsigned int flags;
-} ssh_key_type_spec_t;
+};
+
+
+/* Definition of an object to access the sshcontrol file.  */
+struct ssh_control_file_s
+{
+  char *fname;  /* Name of the file.  */
+  FILE *fp;     /* This is never NULL. */
+  int lnr;      /* The current line number.  */
+  struct {
+    int valid;           /* True if the data of this structure is valid.  */
+    int disabled;        /* The item is disabled.  */
+    int ttl;             /* The TTL of the item.   */
+    int confirm;         /* The confirm flag is set.  */
+    char hexgrip[40+1];  /* The hexgrip of the item (uppercase).  */
+  } item;
+};
 
 
 /* Prototypes.  */
@@ -186,10 +233,19 @@ static gpg_error_t ssh_handler_unlock (ctrl_t ctrl,
                                       estream_t response);
 
 static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis);
-static gpg_error_t ssh_signature_encoder_rsa (estream_t signature_blob,
-                                              gcry_mpi_t *mpis);
-static gpg_error_t ssh_signature_encoder_dsa (estream_t signature_blob,
-                                              gcry_mpi_t *mpis);
+static gpg_error_t ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec,
+                                              estream_t signature_blob,
+                                              gcry_sexp_t signature);
+static gpg_error_t ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec,
+                                              estream_t signature_blob,
+                                              gcry_sexp_t signature);
+static gpg_error_t ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec,
+                                                estream_t signature_blob,
+                                                gcry_sexp_t signature);
+static gpg_error_t ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
+                                                estream_t signature_blob,
+                                                gcry_sexp_t signature);
+static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **comment);
 
 
 
@@ -220,15 +276,71 @@ static ssh_request_spec_t request_specs[] =
 static ssh_key_type_spec_t ssh_key_types[] =
   {
     {
-      "ssh-rsa", "rsa", "nedupq", "en",   "s",  "nedpqu",
+      "ssh-ed25519", "Ed25519", GCRY_PK_EDDSA, "qd",  "q", "rs", "qd",
+      NULL,                 ssh_signature_encoder_eddsa,
+      "Ed25519", 0,               SPEC_FLAG_IS_EdDSA
+    },
+    {
+      "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", GCRY_PK_DSA, "pqgyx",  "pqgy", "rs", "pqgyx",
+      NULL,                 ssh_signature_encoder_dsa,
+      NULL, 0, 0
+    },
+    {
+      "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", GCRY_PK_ECC, "qd",  "q", "rs", "qd",
+      NULL,                 ssh_signature_encoder_ecdsa,
+      "nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA
+    },
+    {
+      "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,
-      SPEC_FLAG_USE_PKCS1V2
+      NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT
     },
     {
-      "ssh-dss", "dsa", "pqgyx",  "pqgy", "rs", "pqgyx",
+      "ssh-dss-cert-v01@openssh.com", "DSA",
+      GCRY_PK_DSA, "pqgyx",  "pqgy", "rs", "pqgyx",
       NULL,                 ssh_signature_encoder_dsa,
-      0
+      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
+    }
   };
 
 \f
@@ -257,26 +369,22 @@ 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)
+/* Lookup the ssh-identifier for the ECC curve CURVE_NAME.  Returns
+   NULL if not found.  */
+static const char *
+ssh_identifier_from_curve_name (const char *curve_name)
 {
-  char *s;
+  int i;
 
-  s = xtrymalloc (data_n + 1);
-  if (s)
-    {
-      memcpy (s, data, data_n);
-      s[data_n] = 0;
-    }
+  for (i = 0; i < DIM (ssh_key_types); i++)
+    if (ssh_key_types[i].curve_name
+        && !strcmp (ssh_key_types[i].curve_name, curve_name))
+      return ssh_key_types[i].ssh_identifier;
 
-  return s;
+  return NULL;
 }
 
 
-
-
 /*
    Primitive I/O functions.
  */
@@ -323,6 +431,7 @@ stream_write_byte (estream_t stream, unsigned char b)
   return err;
 }
 
+
 /* Read a uint32 from STREAM, store it in UINT32.  */
 static gpg_error_t
 stream_read_uint32 (estream_t stream, u32 *uint32)
@@ -396,6 +505,34 @@ stream_read_data (estream_t stream, unsigned char *buffer, size_t size)
   return err;
 }
 
+/* Skip over SIZE bytes from STREAM.  */
+static gpg_error_t
+stream_read_skip (estream_t stream, size_t size)
+{
+  char buffer[128];
+  size_t bytes_to_read, bytes_read;
+  int ret;
+
+  do
+    {
+      bytes_to_read = size;
+      if (bytes_to_read > sizeof buffer)
+        bytes_to_read = sizeof buffer;
+
+      ret = es_read (stream, buffer, bytes_to_read, &bytes_read);
+      if (ret)
+        return gpg_error_from_syserror ();
+      else if (bytes_read != bytes_to_read)
+        return gpg_error (GPG_ERR_EOF);
+      else
+        size -= bytes_to_read;
+    }
+  while (size);
+
+  return 0;
+}
+
+
 /* Write SIZE bytes from BUFFER to STREAM.  */
 static gpg_error_t
 stream_write_data (estream_t stream, const unsigned char *buffer, size_t size)
@@ -413,8 +550,9 @@ 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; depending on SECURE use secure memory for
-   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.  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)
@@ -423,30 +561,43 @@ stream_read_string (estream_t stream, unsigned int secure,
   unsigned char *buffer = NULL;
   u32 length = 0;
 
+  if (string_size)
+    *string_size = 0;
+
   /* Read string length.  */
   err = stream_read_uint32 (stream, &length);
   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;
 
@@ -458,21 +609,70 @@ stream_read_string (estream_t stream, unsigned int secure,
   return err;
 }
 
-/* Read a C-string from STREAM, store copy in STRING.  */
+
+/* Read a binary string from STREAM and store it as an opaque MPI at
+   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_cstring (estream_t stream, char **string)
+stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi)
 {
-  unsigned char *buffer;
   gpg_error_t err;
+  unsigned char *buffer = NULL;
+  u32 length = 0;
 
-  err = stream_read_string (stream, 0, &buffer, NULL);
+  *r_mpi = NULL;
+
+  /* Read string length.  */
+  err = stream_read_uint32 (stream, &length);
   if (err)
-    goto out;
+    goto leave;
+
+  /* To avoid excessive use of secure memory we check that an MPI is
+     not too large. */
+  if (length > (4096/8) + 8)
+    {
+      log_error (_("ssh keys greater than %d bits are not supported\n"), 4096);
+      err = GPG_ERR_TOO_LARGE;
+      goto leave;
+    }
+
+  /* Allocate space.  */
+  if (secure)
+    buffer = xtrymalloc_secure (length+1);
+  else
+    buffer = xtrymalloc (length+1);
+  if (!buffer)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  /* Read data.  */
+  err = stream_read_data (stream, buffer + 1, length);
+  if (err)
+    goto leave;
 
-  *string = (char *) buffer;
+  buffer[0] = 0x40;
+  *r_mpi = gcry_mpi_set_opaque (NULL, buffer, 8*(length+1));
+  buffer = NULL;
 
- out:
+ leave:
+  xfree (buffer);
+  return err;
+}
+
+
+/* Read a C-string from STREAM, store copy in STRING.  */
+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;
 }
 
@@ -568,6 +768,7 @@ stream_write_mpi (estream_t stream, gcry_mpi_t mpint)
   return err;
 }
 
+
 /* Copy data from SRC to DST until EOF is reached.  */
 static gpg_error_t
 stream_copy (estream_t dst, estream_t src)
@@ -597,154 +798,124 @@ 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.  */
+\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,
+   otherwise in read only mode.  On success 0 is returned and a new
+   control file object stored at R_CF.  On error an error code is
+   returned and NULL is stored at R_CF.  */
 static gpg_error_t
-file_to_buffer (const char *filename, unsigned char **buffer, size_t *buffer_n)
+open_control_file (ssh_control_file_t *r_cf, int append)
 {
-  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, "r");
-  if (! stream)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
-
-  ret = fstat (es_fileno (stream), &statbuf);
-  if (ret)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
+  ssh_control_file_t cf;
 
-  buffer_new = xtrymalloc (statbuf.st_size);
-  if (! buffer_new)
+  cf = xtrycalloc (1, sizeof *cf);
+  if (!cf)
     {
       err = gpg_error_from_syserror ();
-      goto out;
+      goto leave;
     }
 
-  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,
-   otherwise in read only mode.  On success a file pointer is stored
-   at the address of R_FP. */
-static gpg_error_t
-open_control_file (FILE **r_fp, int append)
-{
-  gpg_error_t err;
-  char *fname;
-  FILE *fp;
-
   /* 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.  */
-  *r_fp = NULL;
-  fname = make_filename (opt.homedir, "sshcontrol", NULL);
-  /* FIXME: With "a+" we are not able to check whether this will will
+  cf->fname = make_filename_try (gnupg_homedir (), SSH_CONTROL_FILE_NAME, NULL);
+  if (!cf->fname)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  /* FIXME: With "a+" we are not able to check whether this will
      be created and thus the blurb needs to be written first.  */
-  fp = fopen (fname, append? "a+":"r");
-  if (!fp && errno == ENOENT)
+  cf->fp = fopen (cf->fname, append? "a+":"r");
+  if (!cf->fp && errno == ENOENT)
     {
-      estream_t stream = es_fopen (fname, "wx,mode=-rw-r");
+      estream_t stream = es_fopen (cf->fname, "wx,mode=-rw-r");
       if (!stream)
         {
           err = gpg_error_from_syserror ();
-          log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err));
-          xfree (fname);
-          return err;
+          log_error (_("can't create '%s': %s\n"),
+                     cf->fname, gpg_strerror (err));
+          goto leave;
         }
       es_fputs (sshcontrolblurb, stream);
       es_fclose (stream);
-      fp = fopen (fname, append? "a+":"r");
+      cf->fp = fopen (cf->fname, append? "a+":"r");
     }
 
-  if (!fp)
+  if (!cf->fp)
     {
-      err = gpg_error (gpg_err_code_from_errno (errno));
-      log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
-      xfree (fname);
-      return err;
+      err = gpg_error_from_syserror ();
+      log_error (_("can't open '%s': %s\n"),
+                 cf->fname, gpg_strerror (err));
+      goto leave;
     }
 
-  *r_fp = fp;
+  err = 0;
 
-  return 0;
+ leave:
+  if (err && cf)
+    {
+      if (cf->fp)
+        fclose (cf->fp);
+      xfree (cf->fname);
+      xfree (cf);
+    }
+  else
+    *r_cf = cf;
+
+  return err;
 }
 
 
-/* Search the file at stream FP from the beginning until a matching
-   HEXGRIP is found; return success in this case and store true at
-   DISABLED if the found key has been disabled.  If R_TTL is not NULL
-   a specified TTL for that key is stored there.  If R_CONFIRM is not
-   NULL it is set to 1 if the key has the confirm flag set. */
+static void
+rewind_control_file (ssh_control_file_t cf)
+{
+  fseek (cf->fp, 0, SEEK_SET);
+  cf->lnr = 0;
+  clearerr (cf->fp);
+}
+
+
+static void
+close_control_file (ssh_control_file_t cf)
+{
+  if (!cf)
+    return;
+  fclose (cf->fp);
+  xfree (cf->fname);
+  xfree (cf);
+}
+
+
+
+/* Read the next line from the control file and store the data in CF.
+   Returns 0 on success, GPG_ERR_EOF on EOF, or other error codes. */
 static gpg_error_t
-search_control_file (FILE *fp, const char *hexgrip,
-                     int *r_disabled, int *r_ttl, int *r_confirm)
+read_control_file_item (ssh_control_file_t cf)
 {
   int c, i, n;
   char *p, *pend, line[256];
-  long ttl;
-  int lnr = 0;
-  const char fname[] = "sshcontrol";
-
-  assert (strlen (hexgrip) == 40 );
+  long ttl = 0;
 
-  if (r_confirm)
-    *r_confirm = 0;
+  cf->item.valid = 0;
+  clearerr (cf->fp);
 
-  fseek (fp, 0, SEEK_SET);
-  clearerr (fp);
-  *r_disabled = 0;
- next_line:
   do
     {
-      if (!fgets (line, DIM(line)-1, fp) )
+      if (!fgets (line, DIM(line)-1, cf->fp) )
         {
-          if (feof (fp))
+          if (feof (cf->fp))
             return gpg_error (GPG_ERR_EOF);
-          return gpg_error (gpg_err_code_from_errno (errno));
+          return gpg_error_from_syserror ();
         }
-      lnr++;
+      cf->lnr++;
 
       if (!*line || line[strlen(line)-1] != '\n')
         {
           /* Eat until end of line */
-          while ( (c=getc (fp)) != EOF && c != '\n')
+          while ( (c=getc (cf->fp)) != EOF && c != '\n')
             ;
           return gpg_error (*line? GPG_ERR_LINE_TOO_LONG
                                  : GPG_ERR_INCOMPLETE_LINE);
@@ -756,35 +927,34 @@ search_control_file (FILE *fp, const char *hexgrip,
     }
   while (!*p || *p == '\n' || *p == '#');
 
-  *r_disabled = 0;
+  cf->item.disabled = 0;
   if (*p == '!')
     {
-      *r_disabled = 1;
+      cf->item.disabled = 1;
       for (p++; spacep (p); p++)
         ;
     }
 
   for (i=0; hexdigitp (p) && i < 40; p++, i++)
-    if (hexgrip[i] != (*p >= 'a'? (*p & 0xdf): *p))
-      goto next_line;
+    cf->item.hexgrip[i] = (*p >= 'a'? (*p & 0xdf): *p);
+  cf->item.hexgrip[i] = 0;
   if (i != 40 || !(spacep (p) || *p == '\n'))
     {
-      log_error ("invalid formatted line in '%s', line %d\n", fname, lnr);
+      log_error ("%s:%d: invalid formatted line\n", cf->fname, cf->lnr);
       return gpg_error (GPG_ERR_BAD_DATA);
     }
 
   ttl = strtol (p, &pend, 10);
   p = pend;
-  if (!(spacep (p) || *p == '\n') || ttl < -1)
+  if (!(spacep (p) || *p == '\n') || (int)ttl < -1)
     {
-      log_error ("invalid TTL value in '%s', line %d; assuming 0\n",
-                 fname, lnr);
-      ttl = 0;
+      log_error ("%s:%d: invalid TTL value; assuming 0\n", cf->fname, cf->lnr);
+      cf->item.ttl = 0;
     }
-  if (r_ttl)
-    *r_ttl = ttl;
+  cf->item.ttl = ttl;
 
   /* Now check for key-value pairs of the form NAME[=VALUE]. */
+  cf->item.confirm = 0;
   while (*p)
     {
       for (; spacep (p) && *p != '\n'; p++)
@@ -794,22 +964,70 @@ search_control_file (FILE *fp, const char *hexgrip,
       n = strcspn (p, "= \t\n");
       if (p[n] == '=')
         {
-          log_error ("assigning a value to a flag is not yet supported; "
-                     "in '%s', line %d; flag ignored\n", fname, lnr);
+          log_error ("%s:%d: assigning a value to a flag is not yet supported; "
+                     "flag ignored\n", cf->fname, cf->lnr);
           p++;
         }
       else if (n == 7 && !memcmp (p, "confirm", 7))
         {
-          if (r_confirm)
-            *r_confirm = 1;
+          cf->item.confirm = 1;
         }
       else
-        log_error ("invalid flag '%.*s' in '%s', line %d; ignored\n",
-                   n, p, fname, lnr);
+        log_error ("%s:%d: invalid flag '%.*s'; ignored\n",
+                   cf->fname, cf->lnr, n, p);
       p += n;
     }
 
-  return 0; /* Okay:  found it.  */
+  /* log_debug ("%s:%d: grip=%s ttl=%d%s%s\n", */
+  /*            cf->fname, cf->lnr, */
+  /*            cf->item.hexgrip, cf->item.ttl, */
+  /*            cf->item.disabled? " disabled":"", */
+  /*            cf->item.confirm? " confirm":""); */
+
+  cf->item.valid = 1;
+  return 0; /* Okay: valid entry found.  */
+}
+
+
+
+/* Search the control file CF from the beginning until a matching
+   HEXGRIP is found; return success in this case and store true at
+   DISABLED if the found key has been disabled.  If R_TTL is not NULL
+   a specified TTL for that key is stored there.  If R_CONFIRM is not
+   NULL it is set to 1 if the key has the confirm flag set. */
+static gpg_error_t
+search_control_file (ssh_control_file_t cf, const char *hexgrip,
+                     int *r_disabled, int *r_ttl, int *r_confirm)
+{
+  gpg_error_t err;
+
+  assert (strlen (hexgrip) == 40 );
+
+  if (r_disabled)
+    *r_disabled = 0;
+  if (r_ttl)
+    *r_ttl = 0;
+  if (r_confirm)
+    *r_confirm = 0;
+
+  rewind_control_file (cf);
+  while (!(err=read_control_file_item (cf)))
+    {
+      if (!cf->item.valid)
+        continue; /* Should not happen.  */
+      if (!strcmp (hexgrip, cf->item.hexgrip))
+        break;
+    }
+  if (!err)
+    {
+      if (r_disabled)
+        *r_disabled = cf->item.disabled;
+      if (r_ttl)
+        *r_ttl = cf->item.ttl;
+      if (r_confirm)
+        *r_confirm = cf->item.confirm;
+    }
+  return err;
 }
 
 
@@ -820,20 +1038,21 @@ search_control_file (FILE *fp, 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;
-  FILE *fp;
+  ssh_control_file_t cf;
   int disabled;
 
   (void)ctrl;
 
-  err = open_control_file (&fp, 1);
+  err = open_control_file (&cf, 1);
   if (err)
     return err;
 
-  err = search_control_file (fp, hexgrip, &disabled, NULL, NULL);
+  err = search_control_file (cf, hexgrip, &disabled, NULL, NULL);
   if (err && gpg_err_code(err) == GPG_ERR_EOF)
     {
       struct tm *tp;
@@ -842,15 +1061,17 @@ add_control_entry (ctrl_t ctrl, const char *hexgrip, const char *fmtfpr,
       /* 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 (fp, ("# Key added on: %04d-%02d-%02d %02d:%02d:%02d\n"
-                    "# Fingerprint:  %s\n"
-                    "%s %d%s\n"),
+      fprintf (cf->fp,
+               ("# %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":"");
 
     }
-  fclose (fp);
+  close_control_file (cf);
   return 0;
 }
 
@@ -859,20 +1080,20 @@ add_control_entry (ctrl_t ctrl, const char *hexgrip, const char *fmtfpr,
 static int
 ttl_from_sshcontrol (const char *hexgrip)
 {
-  FILE *fp;
+  ssh_control_file_t cf;
   int disabled, ttl;
 
   if (!hexgrip || strlen (hexgrip) != 40)
     return 0;  /* Wrong input: Use global default.  */
 
-  if (open_control_file (&fp, 0))
+  if (open_control_file (&cf, 0))
     return 0; /* Error: Use the global default TTL.  */
 
-  if (search_control_file (fp, hexgrip, &disabled, &ttl, NULL)
+  if (search_control_file (cf, hexgrip, &disabled, &ttl, NULL)
       || disabled)
     ttl = 0;  /* Use the global default if not found or disabled.  */
 
-  fclose (fp);
+  close_control_file (cf);
 
   return ttl;
 }
@@ -882,75 +1103,163 @@ ttl_from_sshcontrol (const char *hexgrip)
 static int
 confirm_flag_from_sshcontrol (const char *hexgrip)
 {
-  FILE *fp;
+  ssh_control_file_t cf;
   int disabled, confirm;
 
   if (!hexgrip || strlen (hexgrip) != 40)
     return 1;  /* Wrong input: Better ask for confirmation.  */
 
-  if (open_control_file (&fp, 0))
+  if (open_control_file (&cf, 0))
     return 1; /* Error: Better ask for confirmation.  */
 
-  if (search_control_file (fp, hexgrip, &disabled, NULL, &confirm)
+  if (search_control_file (cf, hexgrip, &disabled, NULL, &confirm)
       || disabled)
     confirm = 0;  /* If not found or disabled, there is no reason to
                      ask for confirmation.  */
 
-  fclose (fp);
+  close_control_file (cf);
 
   return confirm;
 }
 
 
-
 \f
 
-/*
+/* 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 returned handle.  */
+ssh_control_file_t
+ssh_open_control_file (void)
+{
+  ssh_control_file_t cf;
 
-  MPI lists.
+  /* Then look at all the registered and non-disabled keys. */
+  if (open_control_file (&cf, 0))
+    return NULL;
+  return cf;
+}
 
- */
+/* Close an ssh control file handle.  This is the public version of
+   close_control_file.  CF may be NULL.  */
+void
+ssh_close_control_file (ssh_control_file_t cf)
+{
+  close_control_file (cf);
+}
 
-/* Free the list of MPIs MPI_LIST.  */
-static void
-mpint_list_free (gcry_mpi_t *mpi_list)
+/* Read the next item from the ssh control file.  The function returns
+   0 if a item was read, GPG_ERR_EOF on eof or another error value.
+   R_HEXGRIP shall either be null or a BUFFER of at least 41 byte.
+   R_DISABLED, R_TTLm and R_CONFIRM return flags from the control
+   file; they are only set on success. */
+gpg_error_t
+ssh_read_control_file (ssh_control_file_t cf,
+                       char *r_hexgrip,
+                       int *r_disabled, int *r_ttl, int *r_confirm)
 {
-  if (mpi_list)
-    {
-      unsigned int i;
+  gpg_error_t err;
 
-      for (i = 0; mpi_list[i]; i++)
-       gcry_mpi_release (mpi_list[i]);
-      xfree (mpi_list);
+  do
+    err = read_control_file_item (cf);
+  while (!err && !cf->item.valid);
+  if (!err)
+    {
+      if (r_hexgrip)
+        strcpy (r_hexgrip, cf->item.hexgrip);
+      if (r_disabled)
+        *r_disabled = cf->item.disabled;
+      if (r_ttl)
+        *r_ttl = cf->item.ttl;
+      if (r_confirm)
+        *r_confirm = cf->item.confirm;
     }
+  return err;
 }
 
-/* Receive key material MPIs from STREAM according to KEY_SPEC;
-   depending on SECRET expect a public key or secret key.  The newly
-   allocated list of MPIs is stored in MPI_LIST.  Returns usual error
-   code.  */
+
+/* Search for a key with HEXGRIP in sshcontrol and return all
+   info.  */
+gpg_error_t
+ssh_search_control_file (ssh_control_file_t cf,
+                         const char *hexgrip,
+                         int *r_disabled, int *r_ttl, int *r_confirm)
+{
+  gpg_error_t err;
+  int i;
+  const char *s;
+  char uphexgrip[41];
+
+  /* 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; s++, i++)
+    uphexgrip[i] = *s >= 'a'? (*s & 0xdf): *s;
+  uphexgrip[i] = 0;
+  if (i != 40)
+    err = gpg_error (GPG_ERR_INV_LENGTH);
+  else
+    err = search_control_file (cf, uphexgrip, r_disabled, r_ttl, r_confirm);
+  if (gpg_err_code (err) == GPG_ERR_EOF)
+    err = gpg_error (GPG_ERR_NOT_FOUND);
+  return err;
+}
+
+
+\f
+
+/*
+
+  MPI lists.
+
+ */
+
+/* Free the list of MPIs MPI_LIST.  */
+static void
+mpint_list_free (gcry_mpi_t *mpi_list)
+{
+  if (mpi_list)
+    {
+      unsigned int i;
+
+      for (i = 0; mpi_list[i]; i++)
+       gcry_mpi_release (mpi_list[i]);
+      xfree (mpi_list);
+    }
+}
+
+/* Receive key material MPIs from STREAM according to KEY_SPEC;
+   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)
@@ -963,18 +1272,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);
 
@@ -1019,13 +1330,64 @@ ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis)
 
 /* Signature encoder function for RSA.  */
 static gpg_error_t
-ssh_signature_encoder_rsa (estream_t signature_blob, gcry_mpi_t *mpis)
+ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec,
+                           estream_t signature_blob,
+                           gcry_sexp_t s_signature)
 {
+  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;
   size_t data_n;
-  gpg_error_t err;
   gcry_mpi_t s;
 
+  valuelist = gcry_sexp_nth (s_signature, 1);
+  if (!valuelist)
+    {
+      err = gpg_error (GPG_ERR_INV_SEXP);
+      goto out;
+    }
+
+  elems = spec->elems_signature;
+  elems_n = strlen (elems);
+
+  mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
+  if (!mpis)
+    {
+      err = gpg_error_from_syserror ();
+      goto out;
+    }
+
+  for (i = 0; i < elems_n; i++)
+    {
+      sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
+      if (!sublist)
+       {
+         err = gpg_error (GPG_ERR_INV_SEXP);
+         break;
+       }
+
+      sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
+      if (!sig_value)
+       {
+         err = gpg_error (GPG_ERR_INTERNAL); /* FIXME?  */
+         break;
+       }
+      gcry_sexp_release (sublist);
+      sublist = NULL;
+
+      mpis[i] = sig_value;
+    }
+  if (err)
+    goto out;
+
+  /* RSA specific */
   s = mpis[0];
 
   err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, s);
@@ -1036,23 +1398,76 @@ ssh_signature_encoder_rsa (estream_t signature_blob, gcry_mpi_t *mpis)
   xfree (data);
 
  out:
-
+  gcry_sexp_release (valuelist);
+  gcry_sexp_release (sublist);
+  mpint_list_free (mpis);
   return err;
 }
 
 
 /* Signature encoder function for DSA.  */
 static gpg_error_t
-ssh_signature_encoder_dsa (estream_t signature_blob, gcry_mpi_t *mpis)
+ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec,
+                           estream_t signature_blob,
+                           gcry_sexp_t s_signature)
 {
+  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 buffer[SSH_DSA_SIGNATURE_PADDING * SSH_DSA_SIGNATURE_ELEMS];
-  unsigned char *data;
+  unsigned char *data = NULL;
   size_t data_n;
-  gpg_error_t err;
-  int i;
 
-  data = NULL;
+  valuelist = gcry_sexp_nth (s_signature, 1);
+  if (!valuelist)
+    {
+      err = gpg_error (GPG_ERR_INV_SEXP);
+      goto out;
+    }
+
+  elems = spec->elems_signature;
+  elems_n = strlen (elems);
+
+  mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
+  if (!mpis)
+    {
+      err = gpg_error_from_syserror ();
+      goto out;
+    }
+
+  for (i = 0; i < elems_n; i++)
+    {
+      sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
+      if (!sublist)
+       {
+         err = gpg_error (GPG_ERR_INV_SEXP);
+         break;
+       }
+
+      sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
+      if (!sig_value)
+       {
+         err = gpg_error (GPG_ERR_INTERNAL); /* FIXME?  */
+         break;
+       }
+      gcry_sexp_release (sublist);
+      sublist = NULL;
+
+      mpis[i] = sig_value;
+    }
+  if (err)
+    goto out;
+
+  /* DSA specific code.  */
 
+  /* FIXME: Why this complicated code?  Why collecting boths mpis in a
+     buffer instead of writing them out one after the other?  */
   for (i = 0; i < 2; i++)
     {
       err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, mpis[i]);
@@ -1079,289 +1494,513 @@ ssh_signature_encoder_dsa (estream_t signature_blob, gcry_mpi_t *mpis)
   err = stream_write_string (signature_blob, buffer, sizeof (buffer));
 
  out:
-
   xfree (data);
-
+  gcry_sexp_release (valuelist);
+  gcry_sexp_release (sublist);
+  mpint_list_free (mpis);
   return err;
 }
 
-/*
-   S-Expressions.
- */
-
 
-/* This function constructs a new S-Expression for the key identified
-   by the KEY_SPEC, SECRET, MPIS and COMMENT, which is to be stored in
-   *SEXP.  Returns usual error code.  */
+/* Signature encoder function for ECDSA.  */
 static gpg_error_t
-sexp_key_construct (gcry_sexp_t *sexp,
-                   ssh_key_type_spec_t key_spec, int secret,
-                   gcry_mpi_t *mpis, const char *comment)
+ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec,
+                             estream_t stream, gcry_sexp_t s_signature)
 {
-  const char *key_identifier[] = { "public-key", "private-key" };
-  gcry_sexp_t sexp_new;
-  char *sexp_template;
-  size_t sexp_template_n;
-  gpg_error_t err;
+  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;
-  unsigned int i;
-  unsigned int j;
-  void **arg_list;
-
-  err = 0;
-  sexp_new = NULL;
-  arg_list = NULL;
-  if (secret)
-    elems = key_spec.elems_sexp_order;
-  else
-    elems = key_spec.elems_key_public;
-  elems_n = strlen (elems);
-
-  /*
-    Calculate size for sexp_template_n:
-
-    "(%s(%s<mpis>)(comment%s))" -> 20 + sizeof (<mpis>).
+  int i;
 
-    mpi: (X%m) -> 5.
+  unsigned char *data[2] = {NULL, NULL};
+  size_t data_n[2];
+  size_t innerlen;
 
-  */
-  sexp_template_n = 20 + (elems_n * 5);
-  sexp_template = xtrymalloc (sexp_template_n);
-  if (! sexp_template)
+  valuelist = gcry_sexp_nth (s_signature, 1);
+  if (!valuelist)
     {
-      err = gpg_error_from_syserror ();
+      err = gpg_error (GPG_ERR_INV_SEXP);
       goto out;
     }
 
-  /* Key identifier, algorithm identifier, mpis, comment.  */
-  arg_list = xtrymalloc (sizeof (*arg_list) * (2 + elems_n + 1));
-  if (! arg_list)
+  elems = spec->elems_signature;
+  elems_n = strlen (elems);
+
+  mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
+  if (!mpis)
     {
       err = gpg_error_from_syserror ();
       goto out;
     }
 
-  i = 0;
-  arg_list[i++] = &key_identifier[secret];
-  arg_list[i++] = &key_spec.identifier;
-
-  *sexp_template = 0;
-  sexp_template_n = 0;
-  sexp_template_n = sprintf (sexp_template + sexp_template_n, "(%%s(%%s");
   for (i = 0; i < elems_n; i++)
     {
-      sexp_template_n += sprintf (sexp_template + sexp_template_n, "(%c%%m)",
-                                 elems[i]);
-      if (secret)
+      sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
+      if (!sublist)
        {
-         for (j = 0; j < elems_n; j++)
-           if (key_spec.elems_key_secret[j] == elems[i])
-             break;
+         err = gpg_error (GPG_ERR_INV_SEXP);
+         break;
        }
-      else
-       j = i;
-      arg_list[i + 2] = &mpis[j];
-    }
-  sexp_template_n += sprintf (sexp_template + sexp_template_n,
-                             ")(comment%%s))");
 
-  arg_list[i + 2] = &comment;
+      sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
+      if (!sig_value)
+       {
+         err = gpg_error (GPG_ERR_INTERNAL); /* FIXME?  */
+         break;
+       }
+      gcry_sexp_release (sublist);
+      sublist = NULL;
 
-  err = gcry_sexp_build_array (&sexp_new, NULL, sexp_template, arg_list);
+      mpis[i] = sig_value;
+    }
   if (err)
     goto out;
 
-  *sexp = sexp_new;
+  /* ECDSA specific */
 
- out:
+  innerlen = 0;
+  for (i = 0; i < DIM(data); i++)
+    {
+      err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &data[i], &data_n[i], mpis[i]);
+      if (err)
+       goto out;
+      innerlen += 4 + data_n[i];
+    }
 
-  xfree (arg_list);
-  xfree (sexp_template);
+  err = stream_write_uint32 (stream, innerlen);
+  if (err)
+    goto out;
 
+  for (i = 0; i < DIM(data); i++)
+    {
+      err = stream_write_string (stream, data[i], data_n[i]);
+      if (err)
+        goto out;
+    }
+
+ out:
+  for (i = 0; i < DIM(data); i++)
+    xfree (data[i]);
+  gcry_sexp_release (valuelist);
+  gcry_sexp_release (sublist);
+  mpint_list_free (mpis);
   return err;
 }
 
-/* This functions breaks up the key contained in the S-Expression SEXP
-   according to KEY_SPEC.  The MPIs are bundled in a newly create
-   list, which is to be stored in MPIS; a newly allocated string
-   holding the comment will be stored in COMMENT; SECRET will be
-   filled with a boolean flag specifying what kind of key it is.
-   Returns usual error code.  */
+
+/* Signature encoder function for EdDSA.  */
 static gpg_error_t
-sexp_key_extract (gcry_sexp_t sexp,
-                 ssh_key_type_spec_t key_spec, int *secret,
-                 gcry_mpi_t **mpis, char **comment)
+ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
+                             estream_t stream, gcry_sexp_t s_signature)
 {
-  gpg_error_t err;
-  gcry_sexp_t value_list;
-  gcry_sexp_t value_pair;
-  gcry_sexp_t comment_list;
-  unsigned int i;
-  char *comment_new;
-  const char *data;
-  size_t data_n;
-  int is_secret;
-  size_t elems_n;
+  gpg_error_t err = 0;
+  gcry_sexp_t valuelist = NULL;
+  gcry_sexp_t sublist = NULL;
   const char *elems;
-  gcry_mpi_t *mpis_new;
-  gcry_mpi_t mpi;
-
-  err = 0;
-  value_list = NULL;
-  value_pair = NULL;
-  comment_list = NULL;
-  comment_new = NULL;
-  mpis_new = NULL;
+  size_t elems_n;
+  int i;
 
-  data = gcry_sexp_nth_data (sexp, 0, &data_n);
-  if (! data)
-    {
-      err = gpg_error (GPG_ERR_INV_SEXP);
-      goto out;
-    }
+  unsigned char *data[2] = {NULL, NULL};
+  size_t data_n[2];
+  size_t totallen = 0;
 
-  if ((data_n == 10 && !strncmp (data, "public-key", 10))
-      || (data_n == 21 && !strncmp (data, "protected-private-key", 21))
-      || (data_n == 20 && !strncmp (data, "shadowed-private-key", 20)))
-    {
-      is_secret = 0;
-      elems = key_spec.elems_key_public;
-    }
-  else if (data_n == 11 && !strncmp (data, "private-key", 11))
-    {
-      is_secret = 1;
-      elems = key_spec.elems_key_secret;
-    }
-  else
+  valuelist = gcry_sexp_nth (s_signature, 1);
+  if (!valuelist)
     {
       err = gpg_error (GPG_ERR_INV_SEXP);
       goto out;
     }
 
+  elems = spec->elems_signature;
   elems_n = strlen (elems);
-  mpis_new = xtrycalloc (elems_n + 1, sizeof *mpis_new );
-  if (!mpis_new)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
 
-  value_list = gcry_sexp_find_token (sexp, key_spec.identifier, 0);
-  if (! value_list)
+  if (elems_n != DIM(data))
     {
       err = gpg_error (GPG_ERR_INV_SEXP);
       goto out;
     }
 
-  for (i = 0; i < elems_n; i++)
+  for (i = 0; i < DIM(data); i++)
     {
-      value_pair = gcry_sexp_find_token (value_list, elems + i, 1);
-      if (! value_pair)
+      sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
+      if (!sublist)
        {
          err = gpg_error (GPG_ERR_INV_SEXP);
          break;
        }
 
-      /* Note that we need to use STD format; i.e. prepend a 0x00 to
-         indicate a positive number if the high bit is set. */
-      mpi = gcry_sexp_nth_mpi (value_pair, 1, GCRYMPI_FMT_STD);
-      if (! mpi)
+      data[i] = gcry_sexp_nth_buffer (sublist, 1, &data_n[i]);
+      if (!data[i])
        {
-         err = gpg_error (GPG_ERR_INV_SEXP);
+         err = gpg_error (GPG_ERR_INTERNAL); /* FIXME?  */
          break;
        }
-      mpis_new[i] = mpi;
-      gcry_sexp_release (value_pair);
-      value_pair = NULL;
+      totallen += data_n[i];
+      gcry_sexp_release (sublist);
+      sublist = NULL;
     }
   if (err)
     goto out;
 
-  /* We do not require a comment sublist to be present here.  */
-  data = NULL;
-  data_n = 0;
+  err = stream_write_uint32 (stream, totallen);
+  if (err)
+    goto out;
 
-  comment_list = gcry_sexp_find_token (sexp, "comment", 0);
-  if (comment_list)
-    data = gcry_sexp_nth_data (comment_list, 1, &data_n);
-  if (! data)
+  for (i = 0; i < DIM(data); i++)
     {
-      data = "(none)";
-      data_n = 6;
+      err = stream_write_data (stream, data[i], data_n[i]);
+      if (err)
+        goto out;
     }
 
-  comment_new = make_cstring (data, data_n);
-  if (! comment_new)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
+ out:
+  for (i = 0; i < DIM(data); i++)
+    xfree (data[i]);
+  gcry_sexp_release (valuelist);
+  gcry_sexp_release (sublist);
+  return err;
+}
 
-  if (secret)
-    *secret = is_secret;
-  *mpis = mpis_new;
-  *comment = comment_new;
 
- out:
+/*
+   S-Expressions.
+ */
 
-  gcry_sexp_release (value_list);
-  gcry_sexp_release (value_pair);
-  gcry_sexp_release (comment_list);
 
-  if (err)
+/* This function constructs a new S-Expression for the key identified
+   by the KEY_SPEC, SECRET, CURVE_NAME, MPIS, and COMMENT, which is to
+   be stored at R_SEXP.  Returns an error code.  */
+static gpg_error_t
+sexp_key_construct (gcry_sexp_t *r_sexp,
+                   ssh_key_type_spec_t key_spec, int secret,
+                   const char *curve_name, gcry_mpi_t *mpis,
+                    const char *comment)
+{
+  gpg_error_t err;
+  gcry_sexp_t sexp_new = NULL;
+  void *formatbuf = NULL;
+  void **arg_list = NULL;
+  estream_t format = NULL;
+  char *algo_name = NULL;
+
+  if ((key_spec.flags & SPEC_FLAG_IS_EdDSA))
+    {
+      /* It is much easier and more readable to use a separate code
+         path for EdDSA.  */
+      if (!curve_name)
+        err = gpg_error (GPG_ERR_INV_CURVE);
+      else if (!mpis[0] || !gcry_mpi_get_flag (mpis[0], GCRYMPI_FLAG_OPAQUE))
+        err = gpg_error (GPG_ERR_BAD_PUBKEY);
+      else if (secret
+               && (!mpis[1]
+                   || !gcry_mpi_get_flag (mpis[1], GCRYMPI_FLAG_OPAQUE)))
+        err = gpg_error (GPG_ERR_BAD_SECKEY);
+      else if (secret)
+        err = gcry_sexp_build (&sexp_new, NULL,
+                               "(private-key(ecc(curve %s)"
+                               "(flags eddsa)(q %m)(d %m))"
+                               "(comment%s))",
+                               curve_name,
+                               mpis[0], mpis[1],
+                               comment? comment:"");
+      else
+        err = gcry_sexp_build (&sexp_new, NULL,
+                               "(public-key(ecc(curve %s)"
+                               "(flags eddsa)(q %m))"
+                               "(comment%s))",
+                               curve_name,
+                               mpis[0],
+                               comment? comment:"");
+    }
+  else
     {
-      xfree (comment_new);
-      mpint_list_free (mpis_new);
+      const char *key_identifier[] = { "public-key", "private-key" };
+      int arg_idx;
+      const char *elems;
+      size_t elems_n;
+      unsigned int i, j;
+
+      if (secret)
+        elems = key_spec.elems_sexp_order;
+      else
+        elems = key_spec.elems_key_public;
+      elems_n = strlen (elems);
+
+      format = es_fopenmem (0, "a+b");
+      if (!format)
+        {
+          err = gpg_error_from_syserror ();
+          goto out;
+        }
+
+      /* Key identifier, algorithm identifier, mpis, comment, and a NULL
+         as a safeguard. */
+      arg_list = xtrymalloc (sizeof (*arg_list) * (2 + 1 + elems_n + 1 + 1));
+      if (!arg_list)
+        {
+          err = gpg_error_from_syserror ();
+          goto out;
+        }
+      arg_idx = 0;
+
+      es_fputs ("(%s(%s", format);
+      arg_list[arg_idx++] = &key_identifier[secret];
+      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);
+          arg_list[arg_idx++] = &curve_name;
+        }
+
+      for (i = 0; i < elems_n; i++)
+        {
+          es_fprintf (format, "(%c%%m)", elems[i]);
+          if (secret)
+            {
+              for (j = 0; j < elems_n; j++)
+                if (key_spec.elems_key_secret[j] == elems[i])
+                  break;
+            }
+          else
+            j = i;
+          arg_list[arg_idx++] = &mpis[j];
+        }
+      es_fputs (")(comment%s))", format);
+      arg_list[arg_idx++] = &comment;
+      arg_list[arg_idx] = NULL;
+
+      es_putc (0, format);
+      if (es_ferror (format))
+        {
+          err = gpg_error_from_syserror ();
+          goto out;
+        }
+      if (es_fclose_snatch (format, &formatbuf, NULL))
+        {
+          err = gpg_error_from_syserror ();
+          goto out;
+        }
+      format = NULL;
+
+      err = gcry_sexp_build_array (&sexp_new, NULL, formatbuf, arg_list);
     }
 
+  if (!err)
+    *r_sexp = sexp_new;
+
+ out:
+  es_fclose (format);
+  xfree (arg_list);
+  xfree (formatbuf);
+  xfree (algo_name);
+
   return err;
 }
 
-/* Extract the car from SEXP, and create a newly created C-string
-   which is to be stored in IDENTIFIER.  */
+
+/* This function extracts the key from the s-expression SEXP according
+   to KEY_SPEC and stores it in ssh format at (R_BLOB, R_BLOBLEN).  If
+   WITH_SECRET is true, the secret key parts are also extracted if
+   possible.  Returns 0 on success or an error code.  Note that data
+   stored at R_BLOB must be freed using es_free!  */
 static gpg_error_t
-sexp_extract_identifier (gcry_sexp_t sexp, char **identifier)
+ssh_key_to_blob (gcry_sexp_t sexp, int with_secret,
+                 ssh_key_type_spec_t key_spec,
+                 void **r_blob, size_t *r_blob_size)
 {
-  char *identifier_new;
-  gcry_sexp_t sublist;
+  gpg_error_t err = 0;
+  gcry_sexp_t value_list = NULL;
+  gcry_sexp_t value_pair = NULL;
+  char *curve_name = NULL;
+  estream_t stream = NULL;
+  void *blob = NULL;
+  size_t blob_size;
+  const char *elems, *p_elems;
   const char *data;
-  size_t data_n;
-  gpg_error_t err;
+  size_t datalen;
 
-  identifier_new = NULL;
-  err = 0;
+  *r_blob = NULL;
+  *r_blob_size = 0;
+
+  stream = es_fopenmem (0, "r+b");
+  if (!stream)
+    {
+      err = gpg_error_from_syserror ();
+      goto out;
+    }
+
+  /* Get the type of the key extpression.  */
+  data = gcry_sexp_nth_data (sexp, 0, &datalen);
+  if (!data)
+    {
+      err = gpg_error (GPG_ERR_INV_SEXP);
+      goto out;
+    }
 
-  sublist = gcry_sexp_nth (sexp, 1);
-  if (! sublist)
+  if ((datalen == 10 && !strncmp (data, "public-key", 10))
+      || (datalen == 21 && !strncmp (data, "protected-private-key", 21))
+      || (datalen == 20 && !strncmp (data, "shadowed-private-key", 20)))
+    elems = key_spec.elems_key_public;
+  else if (datalen == 11 && !strncmp (data, "private-key", 11))
+    elems = with_secret? key_spec.elems_key_secret : key_spec.elems_key_public;
+  else
     {
       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;
+  /* Get key value list.  */
+  value_list = gcry_sexp_cadr (sexp);
+  if (!value_list)
+    {
+      err = gpg_error (GPG_ERR_INV_SEXP);
+      goto out;
+    }
+
+  /* Write the ssh algorithm identifier.  */
+  if ((key_spec.flags & SPEC_FLAG_IS_ECDSA))
+    {
+      /* Parse the "curve" parameter.  We currently expect the curve
+         name for ECC and not the parameters of the curve.  This can
+         easily be changed but then we need to find the curve name
+         from the parameters using gcry_pk_get_curve.  */
+      const char *mapped;
+      const char *sshname;
+
+      gcry_sexp_release (value_pair);
+      value_pair = gcry_sexp_find_token (value_list, "curve", 5);
+      if (!value_pair)
+       {
+         err = gpg_error (GPG_ERR_INV_CURVE);
+         goto out;
+        }
+      curve_name = gcry_sexp_nth_string (value_pair, 1);
+      if (!curve_name)
+       {
+         err = gpg_error (GPG_ERR_INV_CURVE); /* (Or out of core.)  */
+         goto out;
+        }
+
+      /* Fixme: The mapping should be done by using gcry_pk_get_curve
+         et al to iterate over all name aliases.  */
+      if (!strcmp (curve_name, "NIST P-256"))
+        mapped = "nistp256";
+      else if (!strcmp (curve_name, "NIST P-384"))
+        mapped = "nistp384";
+      else if (!strcmp (curve_name, "NIST P-521"))
+        mapped = "nistp521";
+      else
+        mapped = NULL;
+      if (mapped)
+        {
+          xfree (curve_name);
+          curve_name = xtrystrdup (mapped);
+          if (!curve_name)
+            {
+              err = gpg_error_from_syserror ();
+              goto out;
+            }
+        }
+
+      sshname = ssh_identifier_from_curve_name (curve_name);
+      if (!sshname)
+        {
+          err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
+          goto out;
+        }
+      err = stream_write_cstring (stream, sshname);
+      if (err)
+        goto out;
+      err = stream_write_cstring (stream, curve_name);
+      if (err)
+        goto out;
+    }
+  else
+    {
+      /* Note: This is also used for EdDSA.  */
+      err = stream_write_cstring (stream, key_spec.ssh_identifier);
+      if (err)
+        goto out;
+    }
+
+  /* Write the parameters.  */
+  for (p_elems = elems; *p_elems; p_elems++)
+    {
+      gcry_sexp_release (value_pair);
+      value_pair = gcry_sexp_find_token (value_list, p_elems, 1);
+      if (!value_pair)
+       {
+         err = gpg_error (GPG_ERR_INV_SEXP);
+         goto out;
+       }
+      if ((key_spec.flags & SPEC_FLAG_IS_EdDSA))
+        {
+
+          data = gcry_sexp_nth_data (value_pair, 1, &datalen);
+          if (!data)
+            {
+              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;
+        }
+      else
+        {
+          gcry_mpi_t mpi;
+
+          /* Note that we need to use STD format; i.e. prepend a 0x00
+             to indicate a positive number if the high bit is set. */
+          mpi = gcry_sexp_nth_mpi (value_pair, 1, GCRYMPI_FMT_STD);
+          if (!mpi)
+            {
+              err = gpg_error (GPG_ERR_INV_SEXP);
+              goto out;
+            }
+          err = stream_write_mpi (stream, mpi);
+          gcry_mpi_release (mpi);
+          if (err)
+            goto out;
+        }
     }
 
-  identifier_new = make_cstring (data, data_n);
-  if (! identifier_new)
+  if (es_fclose_snatch (stream, &blob, &blob_size))
     {
-      err = gpg_err_code_from_errno (errno);
+      err = gpg_error_from_syserror ();
       goto out;
     }
+  stream = NULL;
 
-  *identifier = identifier_new;
+  *r_blob = blob;
+  blob = NULL;
+  *r_blob_size = blob_size;
 
  out:
-
-  gcry_sexp_release (sublist);
+  gcry_sexp_release (value_list);
+  gcry_sexp_release (value_pair);
+  xfree (curve_name);
+  es_fclose (stream);
+  es_free (blob);
 
   return err;
 }
-
 \f
 
 /*
@@ -1372,10 +2011,10 @@ 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;
@@ -1383,7 +2022,7 @@ ssh_key_type_lookup (const char *ssh_name, const char *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))
@@ -1397,6 +2036,7 @@ ssh_key_type_lookup (const char *ssh_name, const char *name,
   return err;
 }
 
+
 /* Receive a key from STREAM, according to the key specification given
    as KEY_SPEC.  Depending on SECRET, receive a secret or a public
    key.  If READ_COMMENT is true, receive a comment string as well.
@@ -1409,22 +2049,173 @@ 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;
   const char *elems;
+  char *curve_name = NULL;
+
 
   err = stream_read_cstring (stream, &key_type);
   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;
 
-  err = ssh_receive_mpint_list (stream, secret, spec, &mpi_list);
-  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:
+       *   string      key_type ("ssh-ed25519")
+       *   string      public_key
+       *   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
+       * we only want the real 32 byte private key - Libgcrypt expects
+       * this.
+       */
+      mpi_list = xtrycalloc (3, sizeof *mpi_list);
+      if (!mpi_list)
+        {
+          err = gpg_error_from_syserror ();
+          goto out;
+        }
+
+      err = stream_read_blob (cert? cert : stream, 0, &mpi_list[0]);
+      if (err)
+        goto out;
+      if (secret)
+        {
+          u32 len = 0;
+          unsigned char *buffer;
+
+          /* Read string length.  */
+          err = stream_read_uint32 (stream, &len);
+          if (err)
+            goto out;
+          if (len != 32 && len != 64)
+            {
+              err = gpg_error (GPG_ERR_BAD_SECKEY);
+              goto out;
+            }
+          buffer = xtrymalloc_secure (32);
+          if (!buffer)
+            {
+              err = gpg_error_from_syserror ();
+              goto out;
+            }
+          err = stream_read_data (stream, buffer, 32);
+          if (err)
+            {
+              xfree (buffer);
+              goto out;
+            }
+          mpi_list[1] = gcry_mpi_set_opaque (NULL, buffer, 8*32);
+          buffer = NULL;
+          if (len == 64)
+            {
+              err = stream_read_skip (stream, 32);
+              if (err)
+                goto out;
+            }
+        }
+    }
+  else if ((spec.flags & SPEC_FLAG_IS_ECDSA))
+    {
+      /* The format of an ECDSA key is:
+       *   string      key_type ("ecdsa-sha2-nistp256" |
+       *                          "ecdsa-sha2-nistp384" |
+       *                         "ecdsa-sha2-nistp521" )
+       *   string      ecdsa_curve_name
+       *   string      ecdsa_public_key
+       *   mpint       ecdsa_private
+       *
+       * Note that we use the mpint reader instead of the string
+       * 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 (cert? cert : stream, 0, &buffer, NULL);
+      if (err)
+        goto out;
+      curve_name = buffer;
+      /* Fixme: Check that curve_name matches the keytype.  */
+      /* Because Libgcrypt < 1.6 has no support for the "nistpNNN"
+         curve names, we need to translate them here to Libgcrypt's
+         native names.  */
+      if (!strcmp (curve_name, "nistp256"))
+        mapped = "NIST P-256";
+      else if (!strcmp (curve_name, "nistp384"))
+        mapped = "NIST P-384";
+      else if (!strcmp (curve_name, "nistp521"))
+        mapped = "NIST P-521";
+      else
+        mapped = NULL;
+      if (mapped)
+        {
+          xfree (curve_name);
+          curve_name = xtrystrdup (mapped);
+          if (!curve_name)
+            {
+              err = gpg_error_from_syserror ();
+              goto out;
+            }
+        }
+
+      err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
+      if (err)
+        goto out;
+    }
+  else
+    {
+      err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
+      if (err)
+        goto out;
+    }
 
   if (read_comment)
     {
@@ -1445,149 +2236,101 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
        goto out;
     }
 
-  err = sexp_key_construct (&key, spec, secret, mpi_list, comment? comment:"");
-  if (err)
-    goto out;
+  if ((spec.flags & SPEC_FLAG_IS_EdDSA))
+    {
+      if (secret)
+        {
+          err = gcry_sexp_build (&key, NULL,
+                                 "(private-key(ecc(curve \"Ed25519\")"
+                                 "(flags eddsa)(q %m)(d %m))"
+                                 "(comment%s))",
+                                 mpi_list[0], mpi_list[1],
+                                 comment? comment:"");
+        }
+      else
+        {
+          err = gcry_sexp_build (&key, NULL,
+                                 "(public-key(ecc(curve \"Ed25519\")"
+                                 "(flags eddsa)(q %m))"
+                                 "(comment%s))",
+                                 mpi_list[0],
+                                 comment? comment:"");
+        }
+    }
+  else
+    {
+      err = sexp_key_construct (&key, spec, secret, curve_name, mpi_list,
+                                comment? comment:"");
+      if (err)
+        goto out;
+    }
 
   if (key_spec)
     *key_spec = spec;
   *key_new = key;
 
  out:
-
+  es_fclose (cert);
   mpint_list_free (mpi_list);
+  xfree (curve_name);
   xfree (key_type);
   xfree (comment);
 
   return err;
 }
 
-/* Converts a key of type TYPE, whose key material is given in MPIS,
-   into a newly created binary blob, which is to be stored in
-   BLOB/BLOB_SIZE.  Returns zero on success or an error code.  */
-static gpg_error_t
-ssh_convert_key_to_blob (unsigned char **blob, size_t *blob_size,
-                        const char *type, gcry_mpi_t *mpis)
-{
-  unsigned char *blob_new;
-  long int blob_size_new;
-  estream_t stream;
-  gpg_error_t err;
-  unsigned int i;
-
-  *blob = NULL;
-  *blob_size = 0;
-
-  blob_new = NULL;
-  stream = NULL;
-  err = 0;
-
-  stream = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+");
-  if (! stream)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
-
-  err = stream_write_cstring (stream, type);
-  if (err)
-    goto out;
-
-  for (i = 0; mpis[i] && (! err); i++)
-    err = stream_write_mpi (stream, mpis[i]);
-  if (err)
-    goto out;
-
-  blob_size_new = es_ftell (stream);
-  if (blob_size_new == -1)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
-
-  err = es_fseek (stream, 0, SEEK_SET);
-  if (err)
-    goto out;
-
-  blob_new = xtrymalloc (blob_size_new);
-  if (! blob_new)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
-
-  err = stream_read_data (stream, blob_new, blob_size_new);
-  if (err)
-    goto out;
-
-  *blob = blob_new;
-  *blob_size = blob_size_new;
-
- out:
-
-  if (stream)
-    es_fclose (stream);
-  if (err)
-    xfree (blob_new);
-
-  return err;
-}
-
 
-/* Write the public key KEY_PUBLIC to STREAM in SSH key format.  If
+/* Write the public key from KEY to STREAM in SSH key format.  If
    OVERRIDE_COMMENT is not NULL, it will be used instead of the
    comment stored in the key.  */
 static gpg_error_t
-ssh_send_key_public (estream_t stream, gcry_sexp_t key_public,
+ssh_send_key_public (estream_t stream, gcry_sexp_t key,
                      const char *override_comment)
 {
   ssh_key_type_spec_t spec;
-  gcry_mpi_t *mpi_list;
-  char *key_type;
-  char *comment;
-  unsigned char *blob;
-  size_t blob_n;
-  gpg_error_t err;
-
-  key_type = NULL;
-  mpi_list = NULL;
-  comment = NULL;
-  blob = NULL;
+  int algo;
+  char *comment = NULL;
+  void *blob = NULL;
+  size_t bloblen;
+  gpg_error_t err = 0;
 
-  err = sexp_extract_identifier (key_public, &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;
 
-  err = sexp_key_extract (key_public, spec, NULL, &mpi_list, &comment);
+  err = ssh_key_to_blob (key, 0, spec, &blob, &bloblen);
   if (err)
     goto out;
 
-  err = ssh_convert_key_to_blob (&blob, &blob_n,
-                                 spec.ssh_identifier, mpi_list);
+  err = stream_write_string (stream, blob, bloblen);
   if (err)
     goto out;
 
-  err = stream_write_string (stream, blob, blob_n);
+  if (override_comment)
+    err = stream_write_cstring (stream, override_comment);
+  else
+    {
+      err = ssh_key_extract_comment (key, &comment);
+      if (err)
+        err = stream_write_cstring (stream, "(none)");
+      else
+        err = stream_write_cstring (stream, comment);
+    }
   if (err)
     goto out;
 
-  err = stream_write_cstring (stream,
-                              override_comment? override_comment : comment);
-
  out:
-
-  mpint_list_free (mpi_list);
-  xfree (key_type);
   xfree (comment);
-  xfree (blob);
+  es_free (blob);
 
   return err;
 }
 
+
 /* Read a public key out of BLOB/BLOB_SIZE according to the key
    specification given as KEY_SPEC, storing the new key in KEY_PUBLIC.
    Returns zero on success or an error code.  */
@@ -1596,13 +2339,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;
-
-  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;
@@ -1619,10 +2360,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;
 }
 
@@ -1635,42 +2373,15 @@ static gpg_error_t
 ssh_key_grip (gcry_sexp_t key, unsigned char *buffer)
 {
   if (!gcry_pk_get_keygrip (key, buffer))
-    return gpg_error (GPG_ERR_INTERNAL);
+    {
+      gpg_error_t err = gcry_pk_testkey (key);
+      return err? err : gpg_error (GPG_ERR_INTERNAL);
+    }
 
   return 0;
 }
 
 
-/* Converts the secret key KEY_SECRET into a public key, storing it in
-   KEY_PUBLIC.  SPEC is the according key specification.  Returns zero
-   on success or an error code.  */
-static gpg_error_t
-key_secret_to_public (gcry_sexp_t *key_public,
-                     ssh_key_type_spec_t spec, gcry_sexp_t key_secret)
-{
-  char *comment;
-  gcry_mpi_t *mpis;
-  gpg_error_t err;
-  int is_secret;
-
-  comment = NULL;
-  mpis = NULL;
-
-  err = sexp_key_extract (key_secret, spec, &is_secret, &mpis, &comment);
-  if (err)
-    goto out;
-
-  err = sexp_key_construct (key_public, spec, 0, mpis, comment);
-
- out:
-
-  mpint_list_free (mpis);
-  xfree (comment);
-
-  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
@@ -1763,39 +2474,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);
@@ -1852,22 +2533,12 @@ static gpg_error_t
 ssh_handler_request_identities (ctrl_t ctrl,
                                 estream_t request, estream_t response)
 {
-  char *key_type;
-  ssh_key_type_spec_t spec;
-  struct dirent *dir_entry;
-  char *key_directory;
-  size_t key_directory_n;
-  char *key_path;
-  unsigned char *buffer;
-  size_t buffer_n;
   u32 key_counter;
   estream_t key_blobs;
-  gcry_sexp_t key_secret;
   gcry_sexp_t key_public;
-  DIR *dir;
   gpg_error_t err;
   int ret;
-  FILE *ctrl_fp = NULL;
+  ssh_control_file_t cf = NULL;
   char *cardsn;
   gpg_error_t ret_err;
 
@@ -1875,51 +2546,17 @@ ssh_handler_request_identities (ctrl_t ctrl,
 
   /* Prepare buffer stream.  */
 
-  key_directory = NULL;
-  key_secret = NULL;
   key_public = NULL;
-  key_type = NULL;
-  key_path = NULL;
   key_counter = 0;
-  buffer = NULL;
-  dir = NULL;
   err = 0;
 
-  key_blobs = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+");
+  key_blobs = es_fopenmem (0, "r+b");
   if (! key_blobs)
     {
       err = gpg_error_from_syserror ();
       goto out;
     }
 
-  /* Open key directory.  */
-  key_directory = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, NULL);
-  if (! key_directory)
-    {
-      err = gpg_err_code_from_errno (errno);
-      goto out;
-    }
-  key_directory_n = strlen (key_directory);
-
-  key_path = xtrymalloc (key_directory_n + 46);
-  if (! key_path)
-    {
-      err = gpg_err_code_from_errno (errno);
-      goto out;
-    }
-
-  sprintf (key_path, "%s/", key_directory);
-  sprintf (key_path + key_directory_n + 41, ".key");
-
-  dir = opendir (key_directory);
-  if (! dir)
-    {
-      err = gpg_err_code_from_errno (errno);
-      goto out;
-    }
-
-
-
   /* First check whether a key is currently available in the card
      reader - this should be allowed even without being listed in
      sshcontrol. */
@@ -1937,78 +2574,40 @@ ssh_handler_request_identities (ctrl_t ctrl,
       key_counter++;
     }
 
-
-  /* Then look at all the registered an allowed keys. */
-
-
-  /* Fixme: We should better iterate over the control file and check
-     whether the key file is there.  This is better in resepct to
-     performance if tehre are a lot of key sin our key storage. */
-  /* FIXME: make sure that buffer gets deallocated properly.  */
-  err = open_control_file (&ctrl_fp, 0);
+  /* Then look at all the registered and non-disabled keys. */
+  err = open_control_file (&cf, 0);
   if (err)
     goto out;
 
-  while ( (dir_entry = readdir (dir)) )
+  while (!read_control_file_item (cf))
     {
-      if ((strlen (dir_entry->d_name) == 44)
-          && (! strncmp (dir_entry->d_name + 40, ".key", 4)))
-        {
-          char hexgrip[41];
-          int disabled;
-
-          /* We do only want to return keys listed in our control
-             file. */
-          strncpy (hexgrip, dir_entry->d_name, 40);
-          hexgrip[40] = 0;
-          if ( strlen (hexgrip) != 40 )
-            continue;
-          if (search_control_file (ctrl_fp, hexgrip, &disabled, NULL, NULL)
-              || disabled)
-            continue;
-
-          strncpy (key_path + key_directory_n + 1, dir_entry->d_name, 40);
-
-          /* Read file content.  */
-          err = file_to_buffer (key_path, &buffer, &buffer_n);
-          if (err)
-            goto out;
-
-          err = gcry_sexp_sscan (&key_secret, NULL, (char*)buffer, buffer_n);
-          if (err)
-            goto out;
-
-          xfree (buffer);
-          buffer = NULL;
-
-          err = sexp_extract_identifier (key_secret, &key_type);
-          if (err)
-            goto out;
-
-          err = ssh_key_type_lookup (NULL, key_type, &spec);
-          if (err)
-            goto out;
-
-          xfree (key_type);
-          key_type = NULL;
+      unsigned char grip[20];
 
-          err = key_secret_to_public (&key_public, spec, key_secret);
-          if (err)
-            goto out;
+      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));
 
-          gcry_sexp_release (key_secret);
-          key_secret = NULL;
-
-          err = ssh_send_key_public (key_blobs, key_public, NULL);
-          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;
+        }
 
-          gcry_sexp_release (key_public);
-          key_public = NULL;
+      err = ssh_send_key_public (key_blobs, key_public, NULL);
+      if (err)
+        goto out;
+      gcry_sexp_release (key_public);
+      key_public = NULL;
 
-          key_counter++;
-        }
+      key_counter++;
     }
+  err = 0;
 
   ret = es_fseek (key_blobs, 0, SEEK_SET);
   if (ret)
@@ -2018,44 +2617,25 @@ ssh_handler_request_identities (ctrl_t ctrl,
     }
 
  out:
-
   /* Send response.  */
 
-  gcry_sexp_release (key_secret);
   gcry_sexp_release (key_public);
 
-  if (! err)
+  if (!err)
     {
-      ret_err = stream_write_byte (response, SSH_RESPONSE_IDENTITIES_ANSWER);
-      if (ret_err)
-       goto leave;
-      ret_err = stream_write_uint32 (response, key_counter);
-      if (ret_err)
-       goto leave;
-      ret_err = stream_copy (response, key_blobs);
-      if (ret_err)
-       goto leave;
+      ret_err = stream_write_byte (response, SSH_RESPONSE_IDENTITIES_ANSWER);
+      if (!ret_err)
+        ret_err = stream_write_uint32 (response, key_counter);
+      if (!ret_err)
+        ret_err = stream_copy (response, key_blobs);
     }
   else
     {
       ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
-      goto leave;
-    };
-
- leave:
-
-  if (key_blobs)
-    es_fclose (key_blobs);
-  if (dir)
-    closedir (dir);
-
-  if (ctrl_fp)
-    fclose (ctrl_fp);
+    }
 
-  xfree (key_directory);
-  xfree (key_path);
-  xfree (buffer);
-  xfree (key_type);
+  es_fclose (key_blobs);
+  close_control_file (cf);
 
   return ret_err;
 }
@@ -2074,34 +2654,27 @@ data_hash (unsigned char *data, size_t data_n,
   return 0;
 }
 
-/* This function signs the data contained in CTRL, stores the created
-   signature in newly allocated memory in SIG and it's size in SIG_N;
-   SIG_ENCODER is the signature encoder to use.  */
+
+/* This function signs the data described by CTRL. If HASH is 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
+   stored in ssh format at R_SIG and it's size at R_SIGLEN; the caller
+   must use es_free to releaase this memory.  */
 static gpg_error_t
-data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder,
-          unsigned char **sig, size_t *sig_n)
+data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec,
+           const void *hash, size_t hashlen,
+          unsigned char **r_sig, size_t *r_siglen)
 {
   gpg_error_t err;
   gcry_sexp_t signature_sexp = NULL;
   estream_t stream = NULL;
-  gcry_sexp_t valuelist = NULL;
-  gcry_sexp_t sublist = NULL;
-  gcry_mpi_t sig_value = NULL;
-  unsigned char *sig_blob = NULL;
-  size_t sig_blob_n = 0;
-  char *identifier = NULL;
-  const char *identifier_raw;
-  size_t identifier_n;
-  ssh_key_type_spec_t spec;
-  int ret;
-  unsigned int i;
-  const char *elems;
-  size_t elems_n;
-  gcry_mpi_t *mpis = NULL;
+  void *blob = NULL;
+  size_t bloblen;
   char hexgrip[40+1];
 
-  *sig = NULL;
-  *sig_n = 0;
+  *r_sig = NULL;
+  *r_siglen = 0;
 
   /* Quick check to see whether we have a valid keygrip and convert it
      to hex.  */
@@ -2133,14 +2706,14 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder,
       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;
@@ -2149,154 +2722,66 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder,
   /* 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);
+                         CACHE_MODE_SSH, ttl_from_sshcontrol,
+                         hash, hashlen);
   ctrl->use_auth_call = 0;
   if (err)
     goto out;
 
-  valuelist = gcry_sexp_nth (signature_sexp, 1);
-  if (! valuelist)
-    {
-      err = gpg_error (GPG_ERR_INV_SEXP);
-      goto out;
-    }
-
-  stream = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+");
-  if (! stream)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
-
-  identifier_raw = gcry_sexp_nth_data (valuelist, 0, &identifier_n);
-  if (! identifier_raw)
-    {
-      err = gpg_error (GPG_ERR_INV_SEXP);
-      goto out;
-    }
-
-  identifier = make_cstring (identifier_raw, identifier_n);
-  if (! identifier)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
-
-  err = ssh_key_type_lookup (NULL, identifier, &spec);
-  if (err)
-    goto out;
-
-  err = stream_write_cstring (stream, spec.ssh_identifier);
-  if (err)
-    goto out;
-
-  elems = spec.elems_signature;
-  elems_n = strlen (elems);
-
-  mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
-  if (!mpis)
+  stream = es_fopenmem (0, "r+b");
+  if (!stream)
     {
       err = gpg_error_from_syserror ();
       goto out;
     }
 
-  for (i = 0; i < elems_n; i++)
-    {
-      sublist = gcry_sexp_find_token (valuelist, spec.elems_signature + i, 1);
-      if (! sublist)
-       {
-         err = gpg_error (GPG_ERR_INV_SEXP);
-         break;
-       }
-
-      sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
-      if (! sig_value)
-       {
-         err = gpg_error (GPG_ERR_INTERNAL); /* FIXME?  */
-         break;
-       }
-      gcry_sexp_release (sublist);
-      sublist = NULL;
-
-      mpis[i] = sig_value;
-    }
+  err = stream_write_cstring (stream, spec->ssh_identifier);
   if (err)
     goto out;
 
-  err = (*sig_encoder) (stream, mpis);
+  err = spec->signature_encoder (spec, stream, signature_sexp);
   if (err)
     goto out;
 
-  sig_blob_n = es_ftell (stream);
-  if (sig_blob_n == -1)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
-
-  sig_blob = xtrymalloc (sig_blob_n);
-  if (! sig_blob)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
-
-  ret = es_fseek (stream, 0, SEEK_SET);
-  if (ret)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
-
-  err = stream_read_data (stream, sig_blob, sig_blob_n);
+  err = es_fclose_snatch (stream, &blob, &bloblen);
   if (err)
     goto out;
+  stream = NULL;
 
-  *sig = sig_blob;
-  *sig_n = sig_blob_n;
+  *r_sig = blob; blob = NULL;
+  *r_siglen = bloblen;
 
  out:
-
-  if (err)
-    xfree (sig_blob);
-
-  if (stream)
-    es_fclose (stream);
-  gcry_sexp_release (valuelist);
+  xfree (blob);
+  es_fclose (stream);
   gcry_sexp_release (signature_sexp);
-  gcry_sexp_release (sublist);
-  mpint_list_free (mpis);
-  xfree (identifier);
 
   return err;
 }
 
+
 /* Handler for the "sign_request" command.  */
 static gpg_error_t
 ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response)
 {
-  gcry_sexp_t key;
+  gcry_sexp_t key = NULL;
   ssh_key_type_spec_t spec;
   unsigned char hash[MAX_DIGEST_LEN];
   unsigned int hash_n;
   unsigned char key_grip[20];
-  unsigned char *key_blob;
+  unsigned char *key_blob = NULL;
   u32 key_blob_size;
-  unsigned char *data;
-  unsigned char *sig;
+  unsigned char *data = NULL;
+  unsigned char *sig = NULL;
   size_t sig_n;
   u32 data_size;
   u32 flags;
   gpg_error_t err;
   gpg_error_t ret_err;
-
-  key_blob = NULL;
-  data = NULL;
-  sig = NULL;
-  key = NULL;
+  int hash_algo;
 
   /* Receive key.  */
 
@@ -2318,38 +2803,51 @@ ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response)
   if (err)
     goto out;
 
-  /* Hash data.  */
-  hash_n = gcry_md_get_algo_dlen (GCRY_MD_SHA1);
-  if (! hash_n)
-    {
-      err = gpg_error (GPG_ERR_INTERNAL);
-      goto out;
-    }
-  err = data_hash (data, data_size, GCRY_MD_SHA1, hash);
-  if (err)
-    goto out;
+  hash_algo = spec.hash_algo;
+  if (!hash_algo)
+    hash_algo = GCRY_MD_SHA1;  /* Use the default.  */
+  ctrl->digest.algo = hash_algo;
+  if ((spec.flags & SPEC_FLAG_USE_PKCS1V2))
+    ctrl->digest.raw_value = 0;
+  else
+    ctrl->digest.raw_value = 1;
 
   /* Calculate key grip.  */
   err = ssh_key_grip (key, key_grip);
   if (err)
     goto out;
-
-  /* Sign data.  */
-
-  ctrl->digest.algo = GCRY_MD_SHA1;
-  memcpy (ctrl->digest.value, hash, hash_n);
-  ctrl->digest.valuelen = hash_n;
-  ctrl->digest.raw_value = ! (spec.flags & SPEC_FLAG_USE_PKCS1V2);
   ctrl->have_keygrip = 1;
   memcpy (ctrl->keygrip, key_grip, 20);
 
-  err = data_sign (ctrl, spec.signature_encoder, &sig, &sig_n);
+  /* Hash data unless we use EdDSA.  */
+  if ((spec.flags & SPEC_FLAG_IS_EdDSA))
+    {
+      ctrl->digest.valuelen = 0;
+    }
+  else
+    {
+      hash_n = gcry_md_get_algo_dlen (hash_algo);
+      if (!hash_n)
+        {
+          err = gpg_error (GPG_ERR_INTERNAL);
+          goto out;
+        }
+      err = data_hash (data, data_size, hash_algo, hash);
+      if (err)
+        goto out;
+      memcpy (ctrl->digest.value, hash, hash_n);
+      ctrl->digest.valuelen = hash_n;
+    }
+
+  /* Sign data.  */
+  if ((spec.flags & SPEC_FLAG_IS_EdDSA))
+    err = data_sign (ctrl, &spec, data, data_size, &sig, &sig_n);
+  else
+    err = data_sign (ctrl, &spec, NULL, 0, &sig, &sig_n);
 
  out:
-
   /* Done.  */
-
-  if (! err)
+  if (!err)
     {
       ret_err = stream_write_byte (response, SSH_RESPONSE_SIGN_RESPONSE);
       if (ret_err)
@@ -2360,6 +2858,8 @@ ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response)
     }
   else
     {
+      log_error ("ssh sign request failed: %s <%s>\n",
+                 gpg_strerror (err), gpg_strsource (err));
       ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
       if (ret_err)
        goto leave;
@@ -2370,54 +2870,35 @@ ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response)
   gcry_sexp_release (key);
   xfree (key_blob);
   xfree (data);
-  xfree (sig);
+  es_free (sig);
 
   return ret_err;
 }
 
+
 /* This function extracts the comment contained in the key
-   S-Expression KEY and stores a copy in COMMENT.  Returns usual error
+   s-expression KEY and stores a copy in COMMENT.  Returns usual error
    code.  */
 static gpg_error_t
-ssh_key_extract_comment (gcry_sexp_t key, char **comment)
+ssh_key_extract_comment (gcry_sexp_t key, char **r_comment)
 {
   gcry_sexp_t comment_list;
-  char *comment_new;
-  const char *data;
-  size_t data_n;
-  gpg_error_t err;
-
-  comment_list = gcry_sexp_find_token (key, "comment", 0);
-  if (! comment_list)
-    {
-      err = gpg_error (GPG_ERR_INV_SEXP);
-      goto out;
-    }
-
-  data = gcry_sexp_nth_data (comment_list, 1, &data_n);
-  if (! data)
-    {
-      err = gpg_error (GPG_ERR_INV_SEXP);
-      goto out;
-    }
-
-  comment_new = make_cstring (data, data_n);
-  if (! comment_new)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
 
-  *comment = comment_new;
-  err = 0;
+  *r_comment = NULL;
 
- out:
+  comment_list = gcry_sexp_find_token (key, "comment", 0);
+  if (!comment_list)
+    return gpg_error (GPG_ERR_INV_SEXP);
 
+  *r_comment = gcry_sexp_nth_string (comment_list, 1);
   gcry_sexp_release (comment_list);
+  if (!*r_comment)
+    return gpg_error (GPG_ERR_INV_SEXP);
 
-  return err;
+  return 0;
 }
 
+
 /* This function converts the key contained in the S-Expression KEY
    into a buffer, which is protected by the passphrase PASSPHRASE.
    Returns usual error code.  */
@@ -2441,7 +2922,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:
 
@@ -2454,23 +2935,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];
@@ -2478,71 +2961,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;
        }
     }
@@ -2557,18 +3048,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);
@@ -2609,6 +3101,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;
@@ -2620,7 +3113,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;
 
@@ -2659,7 +3152,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:
 
@@ -2847,21 +3340,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
@@ -2900,9 +3388,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 ();
@@ -2919,7 +3407,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 ();
@@ -2978,7 +3466,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)
@@ -2995,53 +3483,25 @@ 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;
 }
 
+
 /* Start serving client on SOCK_CLIENT.  */
 void
 start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
 {
   estream_t stream_sock = NULL;
-  gpg_error_t err = 0;
+  gpg_error_t err;
   int ret;
 
-  /* Because the ssh protocol does not send us information about the
-     the current TTY setting, we resort here to use those from startup
-     or those explictly set.  */
-  {
-    static const char *names[] =
-      {"GPG_TTY", "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL};
-    int idx;
-    const char *value;
-
-    for (idx=0; !err && names[idx]; idx++)
-      if (!session_env_getenv (ctrl->session_env, names[idx])
-          && (value = session_env_getenv (opt.startup_env, names[idx])))
-        err = session_env_setenv (ctrl->session_env, names[idx], value);
-
-    if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype)
-      if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype)))
-        err = gpg_error_from_syserror ();
-
-    if (!err && !ctrl->lc_messages && opt.startup_lc_messages)
-      if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages)))
-        err = gpg_error_from_syserror ();
-
-    if (err)
-      {
-        log_error ("error setting default session environment: %s\n",
-                   gpg_strerror (err));
-        goto out;
-      }
-  }
-
+  err = agent_copy_startup_env (ctrl);
+  if (err)
+    goto out;
 
   /* Create stream from socket.  */
   stream_sock = es_fdopen (FD2INT(sock_client), "r+");
@@ -3084,3 +3544,149 @@ start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
   if (stream_sock)
     es_fclose (stream_sock);
 }
+
+
+#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
+   length of MAXREQLEN.  Returns 0 on success which also indicates
+   that a valid SSH response message is now in REQUEST.  */
+int
+serve_mmapped_ssh_request (ctrl_t ctrl,
+                           unsigned char *request, size_t maxreqlen)
+{
+  gpg_error_t err;
+  int send_err = 0;
+  int valid_response = 0;
+  ssh_request_spec_t *spec;
+  u32 msglen;
+  estream_t request_stream, response_stream;
+
+  if (agent_copy_startup_env (ctrl))
+    goto leave; /* Error setting up the environment.  */
+
+  if (maxreqlen < 5)
+    goto leave; /* Caller error.  */
+
+  msglen = uint32_construct (request[0], request[1], request[2], request[3]);
+  if (msglen < 1 || msglen > maxreqlen - 4)
+    {
+      log_error ("ssh message len (%u) out of range", (unsigned int)msglen);
+      goto leave;
+    }
+
+  spec = request_spec_lookup (request[4]);
+  if (!spec)
+    {
+      send_err = 1;  /* Unknown request type.  */
+      goto leave;
+    }
+
+  /* Create a stream object with the data part of the request.  */
+  if (spec->secret_input)
+    request_stream = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+");
+  else
+    request_stream = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+");
+  if (!request_stream)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  /* We have to disable the estream buffering, because the estream
+     core doesn't know about secure memory.  */
+  if (es_setvbuf (request_stream, NULL, _IONBF, 0))
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  /* Copy the request to the stream but omit the request type.  */
+  err = stream_write_data (request_stream, request + 5, msglen - 1);
+  if (err)
+    goto leave;
+  es_rewind (request_stream);
+
+  response_stream = es_fopenmem (0, "r+b");
+  if (!response_stream)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  if (opt.verbose)
+    log_info ("ssh request handler for %s (%u) started\n",
+              spec->identifier, spec->type);
+
+  err = (*spec->handler) (ctrl, request_stream, response_stream);
+
+  if (opt.verbose)
+    {
+      if (err)
+        log_info ("ssh request handler for %s (%u) failed: %s\n",
+                  spec->identifier, spec->type, gpg_strerror (err));
+      else
+        log_info ("ssh request handler for %s (%u) ready\n",
+                  spec->identifier, spec->type);
+    }
+
+  es_fclose (request_stream);
+  request_stream = NULL;
+
+  if (err)
+    {
+      send_err = 1;
+      goto leave;
+    }
+
+  /* Put the response back into the mmapped buffer.  */
+  {
+    void *response_data;
+    size_t response_size;
+
+    /* NB: In contrast to the request-stream, the response stream
+       includes the the message type byte.  */
+    if (es_fclose_snatch (response_stream, &response_data, &response_size))
+      {
+        log_error ("snatching ssh response failed: %s",
+                   gpg_strerror (gpg_error_from_syserror ()));
+        send_err = 1; /* Ooops.  */
+        goto leave;
+      }
+
+    if (opt.verbose > 1)
+      log_info ("sending ssh response of length %u\n",
+                (unsigned int)response_size);
+    if (response_size > maxreqlen - 4)
+      {
+        log_error ("invalid length of the ssh response: %s",
+                   gpg_strerror (GPG_ERR_INTERNAL));
+        es_free (response_data);
+        send_err = 1;
+        goto leave;
+      }
+
+    request[0] = response_size >> 24;
+    request[1] = response_size >> 16;
+    request[2] = response_size >>  8;
+    request[3] = response_size >>  0;
+    memcpy (request+4, response_data, response_size);
+    es_free (response_data);
+    valid_response = 1;
+  }
+
+ leave:
+  if (send_err)
+    {
+      request[0] = 0;
+      request[1] = 0;
+      request[2] = 0;
+      request[3] = 1;
+      request[4] = SSH_RESPONSE_FAILURE;
+      valid_response = 1;
+    }
+
+  /* Reset the SCD in case it has been used. */
+  agent_reset_scd (ctrl);
+
+  return valid_response? 0 : -1;
+}
+#endif /*HAVE_W32_SYSTEM*/