Fix typos.
[gnupg.git] / agent / command-ssh.c
index 5427323..969d898 100644 (file)
@@ -1,6 +1,6 @@
 /* command-ssh.c - gpg-agent's ssh-agent emulation layer
- * Copyright (C) 2004, 2005, 2006, 2009, 2012 Free Software Foundation, Inc.
- * Copyright (C) 2013, 2014 Werner Koch
+ * 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.
  *
@@ -44,7 +44,8 @@
 #include "agent.h"
 
 #include "i18n.h"
-#include "../common/ssh-utils.h"
+#include "util.h"
+#include "ssh-utils.h"
 
 
 \f
@@ -75,6 +76,7 @@
 #define SPEC_FLAG_USE_PKCS1V2 (1 << 0)
 #define SPEC_FLAG_IS_ECDSA    (1 << 1)
 #define SPEC_FLAG_IS_EdDSA    (1 << 2)  /*(lowercase 'd' on purpose.)*/
+#define SPEC_FLAG_WITH_CERT   (1 << 7)
 
 /* The name of the control file.  */
 #define SSH_CONTROL_FILE_NAME "sshcontrol"
@@ -152,7 +154,7 @@ struct ssh_key_type_spec
   const char *name;
 
   /* Algorithm identifier as used by GnuPG.  */
-  const char *identifier;
+  int algo;
 
   /* List of MPI names for secret keys; order matches the one of the
      agent protocol.  */
@@ -274,34 +276,70 @@ static ssh_request_spec_t request_specs[] =
 static ssh_key_type_spec_t ssh_key_types[] =
   {
     {
-      "ssh-ed25519", "Ed25519", "ecc", "qd",  "q", "rs", "qd",
+      "ssh-ed25519", "Ed25519", GCRY_PK_EDDSA, "qd",  "q", "rs", "qd",
       NULL,                 ssh_signature_encoder_eddsa,
       "Ed25519", 0,               SPEC_FLAG_IS_EdDSA
     },
     {
-      "ssh-rsa", "RSA", "rsa", "nedupq", "en",   "s",  "nedpqu",
+      "ssh-rsa", "RSA", GCRY_PK_RSA, "nedupq", "en",   "s",  "nedpqu",
       ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
       NULL, 0,                    SPEC_FLAG_USE_PKCS1V2
     },
     {
-      "ssh-dss", "DSA", "dsa", "pqgyx",  "pqgy", "rs", "pqgyx",
+      "ssh-dss", "DSA", GCRY_PK_DSA, "pqgyx",  "pqgy", "rs", "pqgyx",
       NULL,                 ssh_signature_encoder_dsa,
       NULL, 0, 0
     },
     {
-      "ecdsa-sha2-nistp256", "ECDSA", "ecdsa", "qd",  "q", "rs", "qd",
+      "ecdsa-sha2-nistp256", "ECDSA", GCRY_PK_ECC, "qd",  "q", "rs", "qd",
       NULL,                 ssh_signature_encoder_ecdsa,
       "nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA
     },
     {
-      "ecdsa-sha2-nistp384", "ECDSA", "ecdsa", "qd",  "q", "rs", "qd",
+      "ecdsa-sha2-nistp384", "ECDSA", GCRY_PK_ECC, "qd",  "q", "rs", "qd",
       NULL,                 ssh_signature_encoder_ecdsa,
       "nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA
     },
     {
-      "ecdsa-sha2-nistp521", "ECDSA", "ecdsa", "qd",  "q", "rs", "qd",
+      "ecdsa-sha2-nistp521", "ECDSA", GCRY_PK_ECC, "qd",  "q", "rs", "qd",
       NULL,                 ssh_signature_encoder_ecdsa,
       "nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA
+    },
+    {
+      "ssh-ed25519-cert-v01@openssh.com", "Ed25519",
+      GCRY_PK_EDDSA, "qd",  "q", "rs", "qd",
+      NULL,                 ssh_signature_encoder_eddsa,
+      "Ed25519", 0, SPEC_FLAG_IS_EdDSA | SPEC_FLAG_WITH_CERT
+    },
+    {
+      "ssh-rsa-cert-v01@openssh.com", "RSA",
+      GCRY_PK_RSA, "nedupq", "en",   "s",  "nedpqu",
+      ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
+      NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT
+    },
+    {
+      "ssh-dss-cert-v01@openssh.com", "DSA",
+      GCRY_PK_DSA, "pqgyx",  "pqgy", "rs", "pqgyx",
+      NULL,                 ssh_signature_encoder_dsa,
+      NULL, 0, SPEC_FLAG_WITH_CERT | SPEC_FLAG_WITH_CERT
+    },
+    {
+      "ecdsa-sha2-nistp256-cert-v01@openssh.com", "ECDSA",
+      GCRY_PK_ECC, "qd",  "q", "rs", "qd",
+      NULL,                 ssh_signature_encoder_ecdsa,
+      "nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
+    },
+    {
+      "ecdsa-sha2-nistp384-cert-v01@openssh.com", "ECDSA",
+      GCRY_PK_ECC, "qd",  "q", "rs", "qd",
+      NULL,                 ssh_signature_encoder_ecdsa,
+      "nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
+    },
+    {
+      "ecdsa-sha2-nistp521-cert-v01@openssh.com", "ECDSA",
+      GCRY_PK_ECC, "qd",  "q", "rs", "qd",
+      NULL,                 ssh_signature_encoder_ecdsa,
+      "nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
     }
   };
 
@@ -331,23 +369,6 @@ realloc_secure (void *a, size_t n)
 }
 
 
-/* Create and return a new C-string from DATA/DATA_N (i.e.: add
-   NUL-termination); return NULL on OOM.  */
-static char *
-make_cstring (const char *data, size_t data_n)
-{
-  char *s;
-
-  s = xtrymalloc (data_n + 1);
-  if (s)
-    {
-      memcpy (s, data, data_n);
-      s[data_n] = 0;
-    }
-
-  return s;
-}
-
 /* Lookup the ssh-identifier for the ECC curve CURVE_NAME.  Returns
    NULL if not found.  */
 static const char *
@@ -531,7 +552,7 @@ stream_write_data (estream_t stream, const unsigned char *buffer, size_t size)
 /* Read a binary string from STREAM into STRING, store size of string
    in STRING_SIZE.  Append a hidden nul so that the result may
    directly be used as a C string.  Depending on SECURE use secure
-   memory for STRING.  */
+   memory for STRING.  If STRING is NULL do only a dummy read.  */
 static gpg_error_t
 stream_read_string (estream_t stream, unsigned int secure,
                    unsigned char **string, u32 *string_size)
@@ -548,25 +569,35 @@ stream_read_string (estream_t stream, unsigned int secure,
   if (err)
     goto out;
 
-  /* Allocate space.  */
-  if (secure)
-    buffer = xtrymalloc_secure (length + 1);
-  else
-    buffer = xtrymalloc (length + 1);
-  if (! buffer)
+  if (string)
     {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
+      /* Allocate space.  */
+      if (secure)
+        buffer = xtrymalloc_secure (length + 1);
+      else
+        buffer = xtrymalloc (length + 1);
+      if (! buffer)
+        {
+          err = gpg_error_from_syserror ();
+          goto out;
+        }
 
-  /* Read data.  */
-  err = stream_read_data (stream, buffer, length);
-  if (err)
-    goto out;
+      /* Read data.  */
+      err = stream_read_data (stream, buffer, length);
+      if (err)
+        goto out;
+
+      /* Finalize string object.  */
+      buffer[length] = 0;
+      *string = buffer;
+    }
+  else  /* Dummy read requested.  */
+    {
+      err = stream_read_skip (stream, length);
+      if (err)
+        goto out;
+    }
 
-  /* Finalize string object.  */
-  buffer[length] = 0;
-  *string = buffer;
   if (string_size)
     *string_size = length;
 
@@ -580,8 +611,9 @@ stream_read_string (estream_t stream, unsigned int secure,
 
 
 /* Read a binary string from STREAM and store it as an opaque MPI at
-   R_MPI.  Depending on SECURE use secure memory.  If the string is
-   too large for key material return an error.  */
+   R_MPI, adding 0x40 (this is the prefix for EdDSA key in OpenPGP).
+   Depending on SECURE use secure memory.  If the string is too large
+   for key material return an error.  */
 static gpg_error_t
 stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi)
 {
@@ -607,9 +639,9 @@ stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi)
 
   /* Allocate space.  */
   if (secure)
-    buffer = xtrymalloc_secure (length? length:1);
+    buffer = xtrymalloc_secure (length+1);
   else
-    buffer = xtrymalloc (length?length:1);
+    buffer = xtrymalloc (length+1);
   if (!buffer)
     {
       err = gpg_error_from_syserror ();
@@ -617,11 +649,12 @@ stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi)
     }
 
   /* Read data.  */
-  err = stream_read_data (stream, buffer, length);
+  err = stream_read_data (stream, buffer + 1, length);
   if (err)
     goto leave;
 
-  *r_mpi = gcry_mpi_set_opaque (NULL, buffer, 8*length);
+  buffer[0] = 0x40;
+  *r_mpi = gcry_mpi_set_opaque (NULL, buffer, 8*(length+1));
   buffer = NULL;
 
  leave:
@@ -765,67 +798,6 @@ stream_copy (estream_t dst, estream_t src)
 
   return err;
 }
-
-
-/* Read the content of the file specified by FILENAME into a newly
-   create buffer, which is to be stored in BUFFER; store length of
-   buffer in BUFFER_N.  */
-static gpg_error_t
-file_to_buffer (const char *filename, unsigned char **buffer, size_t *buffer_n)
-{
-  unsigned char *buffer_new;
-  struct stat statbuf;
-  estream_t stream;
-  gpg_error_t err;
-  int ret;
-
-  *buffer = NULL;
-  *buffer_n = 0;
-
-  buffer_new = NULL;
-  err = 0;
-
-  stream = es_fopen (filename, "rb");
-  if (! stream)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
-
-  ret = fstat (es_fileno (stream), &statbuf);
-  if (ret)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
-
-  buffer_new = xtrymalloc (statbuf.st_size);
-  if (! buffer_new)
-    {
-      err = gpg_error_from_syserror ();
-      goto out;
-    }
-
-  err = stream_read_data (stream, buffer_new, statbuf.st_size);
-  if (err)
-    goto out;
-
-  *buffer = buffer_new;
-  *buffer_n = statbuf.st_size;
-
- out:
-
-  if (stream)
-    es_fclose (stream);
-
-  if (err)
-    xfree (buffer_new);
-
-  return err;
-}
-
-
-
 \f
 /* Open the ssh control file and create it if not available.  With
    APPEND passed as true the file will be opened in append mode,
@@ -848,7 +820,7 @@ open_control_file (ssh_control_file_t *r_cf, int append)
   /* Note: As soon as we start to use non blocking functions here
      (i.e. where Pth might switch threads) we need to employ a
      mutex.  */
-  cf->fname = make_filename_try (opt.homedir, SSH_CONTROL_FILE_NAME, NULL);
+  cf->fname = make_filename_try (gnupg_homedir (), SSH_CONTROL_FILE_NAME, NULL);
   if (!cf->fname)
     {
       err = gpg_error_from_syserror ();
@@ -1155,7 +1127,7 @@ confirm_flag_from_sshcontrol (const char *hexgrip)
 
 /* Open the ssh control file for reading.  This is a public version of
    open_control_file.  The caller must use ssh_close_control_file to
-   release the retruned handle.  */
+   release the returned handle.  */
 ssh_control_file_t
 ssh_open_control_file (void)
 {
@@ -1220,7 +1192,7 @@ ssh_search_control_file (ssh_control_file_t cf,
   /* We need to make sure that HEXGRIP is all uppercase.  The easiest
      way to do this and also check its length is by copying to a
      second buffer. */
-  for (i=0, s=hexgrip; i < 40; s++, i++)
+  for (i=0, s=hexgrip; i < 40 && *s; s++, i++)
     uphexgrip[i] = *s >= 'a'? (*s & 0xdf): *s;
   uphexgrip[i] = 0;
   if (i != 40)
@@ -1256,31 +1228,38 @@ mpint_list_free (gcry_mpi_t *mpi_list)
 }
 
 /* Receive key material MPIs from STREAM according to KEY_SPEC;
-   depending on SECRET expect a public key or secret key.  The newly
+   depending on SECRET expect a public key or secret key.  CERT is the
+   certificate blob used if KEY_SPEC indicates the certificate format;
+   it needs to be positioned to the end of the nonce.  The newly
    allocated list of MPIs is stored in MPI_LIST.  Returns usual error
    code.  */
 static gpg_error_t
 ssh_receive_mpint_list (estream_t stream, int secret,
-                       ssh_key_type_spec_t key_spec, gcry_mpi_t **mpi_list)
+                       ssh_key_type_spec_t *spec, estream_t cert,
+                        gcry_mpi_t **mpi_list)
 {
   const char *elems_public;
   unsigned int elems_n;
   const char *elems;
   int elem_is_secret;
-  gcry_mpi_t *mpis;
-  gpg_error_t err;
+  gcry_mpi_t *mpis = NULL;
+  gpg_error_t err = 0;
   unsigned int i;
 
-  mpis = NULL;
-  err = 0;
-
   if (secret)
-    elems = key_spec.elems_key_secret;
+    elems = spec->elems_key_secret;
   else
-    elems = key_spec.elems_key_public;
+    elems = spec->elems_key_public;
   elems_n = strlen (elems);
+  elems_public = spec->elems_key_public;
 
-  elems_public = key_spec.elems_key_public;
+  /* Check that either both, CERT and the WITH_CERT flag, are given or
+     none of them.  */
+  if (!(!!(spec->flags & SPEC_FLAG_WITH_CERT) ^ !cert))
+    {
+      err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+      goto out;
+    }
 
   mpis = xtrycalloc (elems_n + 1, sizeof *mpis );
   if (!mpis)
@@ -1293,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);
 
@@ -1704,7 +1685,7 @@ sexp_key_construct (gcry_sexp_t *r_sexp,
   void *formatbuf = NULL;
   void **arg_list = NULL;
   estream_t format = NULL;
-
+  char *algo_name = NULL;
 
   if ((key_spec.flags & SPEC_FLAG_IS_EdDSA))
     {
@@ -1768,7 +1749,14 @@ sexp_key_construct (gcry_sexp_t *r_sexp,
 
       es_fputs ("(%s(%s", format);
       arg_list[arg_idx++] = &key_identifier[secret];
-      arg_list[arg_idx++] = &key_spec.identifier;
+      algo_name = xtrystrdup (gcry_pk_algo_name (key_spec.algo));
+      if (!algo_name)
+        {
+          err = gpg_error_from_syserror ();
+          goto out;
+        }
+      strlwr (algo_name);
+      arg_list[arg_idx++] = &algo_name;
       if (curve_name)
         {
           es_fputs ("(curve%s)", format);
@@ -1815,6 +1803,7 @@ sexp_key_construct (gcry_sexp_t *r_sexp,
   es_fclose (format);
   xfree (arg_list);
   xfree (formatbuf);
+  xfree (algo_name);
 
   return err;
 }
@@ -1871,8 +1860,8 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret,
       goto out;
     }
 
-  /* Get the algorithm identifier.  */
-  value_list = gcry_sexp_find_token (sexp, key_spec.identifier, 0);
+  /* Get key value list.  */
+  value_list = gcry_sexp_cadr (sexp);
   if (!value_list)
     {
       err = gpg_error (GPG_ERR_INV_SEXP);
@@ -1964,6 +1953,11 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret,
               err = gpg_error (GPG_ERR_INV_SEXP);
               goto out;
             }
+          if (*p_elems == 'q' && datalen)
+            { /* Remove the prefix 0x40.  */
+              data++;
+              datalen--;
+            }
           err = stream_write_string (stream, data, datalen);
           if (err)
             goto out;
@@ -2007,51 +2001,6 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret,
 
   return err;
 }
-
-/* Extract the car from SEXP, and create a newly created C-string
-   which is to be stored in IDENTIFIER.  */
-static gpg_error_t
-sexp_extract_identifier (gcry_sexp_t sexp, char **identifier)
-{
-  char *identifier_new;
-  gcry_sexp_t sublist;
-  const char *data;
-  size_t data_n;
-  gpg_error_t err;
-
-  identifier_new = NULL;
-  err = 0;
-
-  sublist = gcry_sexp_nth (sexp, 1);
-  if (! sublist)
-    {
-      err = gpg_error (GPG_ERR_INV_SEXP);
-      goto out;
-    }
-
-  data = gcry_sexp_nth_data (sublist, 0, &data_n);
-  if (! data)
-    {
-      err = gpg_error (GPG_ERR_INV_SEXP);
-      goto out;
-    }
-
-  identifier_new = make_cstring (data, data_n);
-  if (! identifier_new)
-    {
-      err = gpg_err_code_from_errno (errno);
-      goto out;
-    }
-
-  *identifier = identifier_new;
-
- out:
-
-  gcry_sexp_release (sublist);
-
-  return err;
-}
-
 \f
 
 /*
@@ -2062,23 +2011,18 @@ sexp_extract_identifier (gcry_sexp_t sexp, char **identifier)
 
 /* Search for a key specification entry.  If SSH_NAME is not NULL,
    search for an entry whose "ssh_name" is equal to SSH_NAME;
-   otherwise, search for an entry whose "name" is equal to NAME.
+   otherwise, search for an entry whose algorithm is equal to ALGO.
    Store found entry in SPEC on success, return error otherwise.  */
 static gpg_error_t
-ssh_key_type_lookup (const char *ssh_name, const char *name,
+ssh_key_type_lookup (const char *ssh_name, int algo,
                     ssh_key_type_spec_t *spec)
 {
   gpg_error_t err;
   unsigned int i;
 
-  /* FIXME: Although this sees to work, it not be correct if the
-     lookup is done via name which might be "ecc" but actually it need
-     to check the flags to see whether it is eddsa or ecdsa.  Maybe
-     the entire parameter controlled logic is too complicated and we
-     would do better by just switching on the ssh_name.  */
   for (i = 0; i < DIM (ssh_key_types); i++)
     if ((ssh_name && (! strcmp (ssh_name, ssh_key_types[i].ssh_identifier)))
-       || (name && (! strcmp (name, ssh_key_types[i].identifier))))
+       || algo == ssh_key_types[i].algo)
       break;
 
   if (i == DIM (ssh_key_types))
@@ -2105,6 +2049,7 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
   gpg_error_t err;
   char *key_type = NULL;
   char *comment = NULL;
+  estream_t cert = NULL;
   gcry_sexp_t key = NULL;
   ssh_key_type_spec_t spec;
   gcry_mpi_t *mpi_list = NULL;
@@ -2116,10 +2061,48 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
   if (err)
     goto out;
 
-  err = ssh_key_type_lookup (key_type, NULL, &spec);
+  err = ssh_key_type_lookup (key_type, 0, &spec);
   if (err)
     goto out;
 
+  if ((spec.flags & SPEC_FLAG_WITH_CERT))
+    {
+      /* This is an OpenSSH certificate+private key.  The certificate
+         is an SSH string and which we store in an estream object. */
+      unsigned char *buffer;
+      u32 buflen;
+      char *cert_key_type;
+
+      err = stream_read_string (stream, 0, &buffer, &buflen);
+      if (err)
+        goto out;
+      cert = es_fopenmem_init (0, "rb", buffer, buflen);
+      xfree (buffer);
+      if (!cert)
+        {
+          err = gpg_error_from_syserror ();
+          goto out;
+        }
+
+      /* Check that the key type matches.  */
+      err = stream_read_cstring (cert, &cert_key_type);
+      if (err)
+        goto out;
+      if (strcmp (cert_key_type, key_type) )
+        {
+          xfree (cert_key_type);
+          log_error ("key types in received ssh certificate do not match\n");
+          err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+          goto out;
+        }
+      xfree (cert_key_type);
+
+      /* Skip the nonce.  */
+      err = stream_read_string (cert, 0, NULL, NULL);
+      if (err)
+        goto out;
+    }
+
   if ((spec.flags & SPEC_FLAG_IS_EdDSA))
     {
       /* The format of an EdDSA key is:
@@ -2139,7 +2122,7 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
           goto out;
         }
 
-      err = stream_read_blob (stream, 0, &mpi_list[0]);
+      err = stream_read_blob (cert? cert : stream, 0, &mpi_list[0]);
       if (err)
         goto out;
       if (secret)
@@ -2189,12 +2172,14 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
        *   mpint       ecdsa_private
        *
        * Note that we use the mpint reader instead of the string
-       * reader for ecsa_public_key.
+       * reader for ecsa_public_key.  For the certificate variante
+       * ecdsa_curve_name+ecdsa_public_key are replaced by the
+       * certificate.
        */
       unsigned char *buffer;
       const char *mapped;
 
-      err = stream_read_string (stream, 0, &buffer, NULL);
+      err = stream_read_string (cert? cert : stream, 0, &buffer, NULL);
       if (err)
         goto out;
       curve_name = buffer;
@@ -2221,13 +2206,13 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
             }
         }
 
-      err = ssh_receive_mpint_list (stream, secret, spec, &mpi_list);
+      err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
       if (err)
         goto out;
     }
   else
     {
-      err = ssh_receive_mpint_list (stream, secret, spec, &mpi_list);
+      err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
       if (err)
         goto out;
     }
@@ -2285,6 +2270,7 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
   *key_new = key;
 
  out:
+  es_fclose (cert);
   mpint_list_free (mpi_list);
   xfree (curve_name);
   xfree (key_type);
@@ -2302,17 +2288,17 @@ ssh_send_key_public (estream_t stream, gcry_sexp_t key,
                      const char *override_comment)
 {
   ssh_key_type_spec_t spec;
-  char *key_type = NULL;
+  int algo;
   char *comment = NULL;
   void *blob = NULL;
   size_t bloblen;
-  gpg_error_t err;
+  gpg_error_t err = 0;
 
-  err = sexp_extract_identifier (key, &key_type);
-  if (err)
+  algo = get_pk_algo_from_key (key);
+  if (algo == 0)
     goto out;
 
-  err = ssh_key_type_lookup (NULL, key_type, &spec);
+  err = ssh_key_type_lookup (NULL, algo, &spec);
   if (err)
     goto out;
 
@@ -2338,7 +2324,6 @@ ssh_send_key_public (estream_t stream, gcry_sexp_t key,
     goto out;
 
  out:
-  xfree (key_type);
   xfree (comment);
   es_free (blob);
 
@@ -2489,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);
+      err = agent_write_shadow_key (grip, serialno, authkeyid, pkbuf, 0);
       if (err)
         {
-          log_error (_("shadowing the key failed: %s\n"), gpg_strerror (err));
-          xfree (pkbuf);
-          gcry_sexp_release (s_pk);
-          xfree (serialno);
-          xfree (authkeyid);
-          return err;
-        }
-      xfree (pkbuf);
-      pkbuf = tmp;
-      pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
-      assert (pkbuflen);
-
-      err = agent_write_private_key (grip, pkbuf, pkbuflen, 0);
-      if (err)
-        {
-          log_error (_("error writing key: %s\n"), gpg_strerror (err));
           xfree (pkbuf);
           gcry_sexp_release (s_pk);
           xfree (serialno);
@@ -2578,12 +2533,8 @@ static gpg_error_t
 ssh_handler_request_identities (ctrl_t ctrl,
                                 estream_t request, estream_t response)
 {
-  ssh_key_type_spec_t spec;
-  char *key_fname = NULL;
-  char *fnameptr;
   u32 key_counter;
   estream_t key_blobs;
-  gcry_sexp_t key_secret;
   gcry_sexp_t key_public;
   gpg_error_t err;
   int ret;
@@ -2595,7 +2546,6 @@ ssh_handler_request_identities (ctrl_t ctrl,
 
   /* Prepare buffer stream.  */
 
-  key_secret = NULL;
   key_public = NULL;
   key_counter = 0;
   err = 0;
@@ -2624,29 +2574,6 @@ ssh_handler_request_identities (ctrl_t ctrl,
       key_counter++;
     }
 
-
-  /* Prepare buffer for key name construction.  */
-  {
-    char *dname;
-
-    dname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, NULL);
-    if (!dname)
-      {
-        err = gpg_err_code_from_syserror ();
-        goto out;
-      }
-
-    key_fname = xtrymalloc (strlen (dname) + 1 + 40 + 4 + 1);
-    if (!key_fname)
-      {
-        err = gpg_err_code_from_syserror ();
-        xfree (dname);
-        goto out;
-      }
-    fnameptr = stpcpy (stpcpy (key_fname, dname), "/");
-    xfree (dname);
-  }
-
   /* Then look at all the registered and non-disabled keys. */
   err = open_control_file (&cf, 0);
   if (err)
@@ -2654,52 +2581,29 @@ ssh_handler_request_identities (ctrl_t ctrl,
 
   while (!read_control_file_item (cf))
     {
+      unsigned char grip[20];
+
       if (!cf->item.valid)
         continue; /* Should not happen.  */
       if (cf->item.disabled)
         continue;
       assert (strlen (cf->item.hexgrip) == 40);
+      hex2bin (cf->item.hexgrip, grip, sizeof (grip));
 
-      stpcpy (stpcpy (fnameptr, cf->item.hexgrip), ".key");
-
-      /* Read file content.  */
-      {
-        unsigned char *buffer;
-        size_t buffer_n;
-
-        err = file_to_buffer (key_fname, &buffer, &buffer_n);
-        if (err)
-          {
-            log_error ("%s:%d: key '%s' skipped: %s\n",
-                       cf->fname, cf->lnr, cf->item.hexgrip,
-                       gpg_strerror (err));
-            continue;
-          }
-
-        err = gcry_sexp_sscan (&key_secret, NULL, (char*)buffer, buffer_n);
-        xfree (buffer);
-        if (err)
-          goto out;
-      }
-
-      {
-        char *key_type = NULL;
-
-        err = sexp_extract_identifier (key_secret, &key_type);
-        if (err)
-          goto out;
-
-        err = ssh_key_type_lookup (NULL, key_type, &spec);
-        xfree (key_type);
-        if (err)
-          goto out;
-      }
+      err = agent_public_key_from_file (ctrl, grip, &key_public);
+      if (err)
+        {
+          log_error ("%s:%d: key '%s' skipped: %s\n",
+                     cf->fname, cf->lnr, cf->item.hexgrip,
+                     gpg_strerror (err));
+          continue;
+        }
 
-      err = ssh_send_key_public (key_blobs, key_secret, NULL);
+      err = ssh_send_key_public (key_blobs, key_public, NULL);
       if (err)
         goto out;
-      gcry_sexp_release (key_secret);
-      key_secret = NULL;
+      gcry_sexp_release (key_public);
+      key_public = NULL;
 
       key_counter++;
     }
@@ -2715,7 +2619,6 @@ ssh_handler_request_identities (ctrl_t ctrl,
  out:
   /* Send response.  */
 
-  gcry_sexp_release (key_secret);
   gcry_sexp_release (key_public);
 
   if (!err)
@@ -2733,7 +2636,6 @@ ssh_handler_request_identities (ctrl_t ctrl,
 
   es_fclose (key_blobs);
   close_control_file (cf);
-  xfree (key_fname);
 
   return ret_err;
 }
@@ -2804,14 +2706,14 @@ data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec,
       gcry_sexp_release (key);
       if (err)
         goto out;
-      prompt = xtryasprintf (_("An ssh process requested the use of key%%0A"
-                               "  %s%%0A"
-                               "  (%s)%%0A"
-                               "Do you want to allow this?"),
+      prompt = xtryasprintf (L_("An ssh process requested the use of key%%0A"
+                                "  %s%%0A"
+                                "  (%s)%%0A"
+                                "Do you want to allow this?"),
                              fpr, comment? comment:"");
       xfree (fpr);
       gcry_free (comment);
-      err = agent_get_confirmation (ctrl, prompt, _("Allow"), _("Deny"), 0);
+      err = agent_get_confirmation (ctrl, prompt, L_("Allow"), L_("Deny"), 0);
       xfree (prompt);
       if (err)
         goto out;
@@ -2820,8 +2722,8 @@ data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec,
   /* Create signature.  */
   ctrl->use_auth_call = 1;
   err = agent_pksign_do (ctrl, NULL,
-                         _("Please enter the passphrase "
-                           "for the ssh key%%0A  %F%%0A  (%c)"),
+                         L_("Please enter the passphrase "
+                            "for the ssh key%%0A  %F%%0A  (%c)"),
                          &signature_sexp,
                          CACHE_MODE_SSH, ttl_from_sshcontrol,
                          hash, hashlen);
@@ -3020,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:
 
@@ -3033,22 +2935,22 @@ 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, ssh_key_type_spec_t *spec,
                        gcry_sexp_t key, int ttl, int confirm)
@@ -3059,70 +2961,79 @@ ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec,
   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;
        }
     }
@@ -3137,18 +3048,19 @@ ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec,
     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, 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);
@@ -3554,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)
@@ -3579,38 +3491,6 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock)
 }
 
 
-/* Because the ssh protocol does not send us information about the
-   current TTY setting, we use this function to use those from startup
-   or those explictly set.  */
-static gpg_error_t
-setup_ssh_env (ctrl_t ctrl)
-{
-  static const char *names[] =
-    {"GPG_TTY", "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL};
-  gpg_error_t err = 0;
-  int idx;
-  const char *value;
-
-  for (idx=0; !err && names[idx]; idx++)
-      if ((value = session_env_getenv (opt.startup_env, names[idx])))
-      err = session_env_setenv (ctrl->session_env, names[idx], value);
-
-  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));
-
-  return err;
-}
-
-
 /* Start serving client on SOCK_CLIENT.  */
 void
 start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
@@ -3619,7 +3499,7 @@ start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
   gpg_error_t err;
   int ret;
 
-  err = setup_ssh_env (ctrl);
+  err = agent_copy_startup_env (ctrl);
   if (err)
     goto out;
 
@@ -3682,7 +3562,7 @@ serve_mmapped_ssh_request (ctrl_t ctrl,
   u32 msglen;
   estream_t request_stream, response_stream;
 
-  if (setup_ssh_env (ctrl))
+  if (agent_copy_startup_env (ctrl))
     goto leave; /* Error setting up the environment.  */
 
   if (maxreqlen < 5)