g10: Fix keybox-related memory leaks.
[gnupg.git] / g10 / export.c
index 82d9751..b067376 100644 (file)
@@ -1,6 +1,7 @@
 /* export.c - Export keys in the OpenPGP defined format.
  * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
  *               2005, 2010 Free Software Foundation, Inc.
+ * Copyright (C) 1998-2016  Werner Koch
  *
  * This file is part of GnuPG.
  *
@@ -23,7 +24,6 @@
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
-#include <assert.h>
 
 #include "gpg.h"
 #include "options.h"
@@ -33,6 +33,8 @@
 #include "util.h"
 #include "main.h"
 #include "i18n.h"
+#include "membuf.h"
+#include "host2net.h"
 #include "trustdb.h"
 #include "call-agent.h"
 
@@ -45,15 +47,28 @@ struct subkey_list_s
 typedef struct subkey_list_s *subkey_list_t;
 
 
-static int do_export (ctrl_t ctrl,
-                      strlist_t users, int secret, unsigned int options );
+/* An object to track statistics for export operations.  */
+struct export_stats_s
+{
+  ulong count;            /* Number of processed keys.        */
+  ulong secret_count;     /* Number of secret keys seen.      */
+  ulong exported;         /* Number of actual exported keys.  */
+};
+
+
+/* Local prototypes.  */
+static int do_export (ctrl_t ctrl, strlist_t users, int secret,
+                      unsigned int options, export_stats_t stats);
 static int do_export_stream (ctrl_t ctrl, iobuf_t out,
                              strlist_t users, int secret,
                              kbnode_t *keyblock_out, unsigned int options,
-                            int *any);
-static int build_sexp (iobuf_t out, PACKET *pkt, int *indent);
+                            export_stats_t stats, int *any);
 
+\f
 
+
+/* Option parser for export options.  See parse_options fro
+   details.  */
 int
 parse_export_options(char *str,unsigned int *options,int noisy)
 {
@@ -69,8 +84,6 @@ parse_export_options(char *str,unsigned int *options,int noisy)
        N_("remove unusable parts from key during export")},
       {"export-minimal",EXPORT_MINIMAL|EXPORT_CLEAN,NULL,
        N_("remove as much as possible from key during export")},
-      {"export-sexp-format",EXPORT_SEXP_FORMAT, NULL,
-       N_("export keys in an S-expression based format")},
       /* Aliases for backward compatibility */
       {"include-local-sigs",EXPORT_LOCAL_SIGS,NULL,NULL},
       {"include-attributes",EXPORT_ATTRIBUTES,NULL,NULL},
@@ -87,47 +100,148 @@ parse_export_options(char *str,unsigned int *options,int noisy)
 }
 
 
-/****************
- * Export the public keys (to standard out or --output).
- * Depending on opt.armor the output is armored.
- * options are defined in main.h.
- * If USERS is NULL, the complete ring will be exported.  */
-int
-export_pubkeys (ctrl_t ctrl, strlist_t users, unsigned int options )
+/* Create a new export stats object initialized to zero.  On error
+   returns NULL and sets ERRNO.  */
+export_stats_t
+export_new_stats (void)
+{
+  export_stats_t stats;
+
+  return xtrycalloc (1, sizeof *stats);
+}
+
+
+/* Release an export stats object.  */
+void
+export_release_stats (export_stats_t stats)
+{
+  xfree (stats);
+}
+
+
+/* Print export statistics using the status interface.  */
+void
+export_print_stats (export_stats_t stats)
 {
-  return do_export (ctrl, users, 0, options );
+  if (!stats)
+    return;
+
+  if (is_status_enabled ())
+    {
+      char buf[15*20];
+
+      snprintf (buf, sizeof buf, "%lu %lu %lu",
+               stats->count,
+               stats->secret_count,
+               stats->exported );
+      write_status_text (STATUS_EXPORT_RES, buf);
+    }
 }
 
-/****************
- * Export to an already opened stream; return -1 if no keys have
- * been exported
+
+/*
+ * Export public keys (to stdout or to --output FILE).
+ *
+ * Depending on opt.armor the output is armored.  OPTIONS are defined
+ * in main.h.  If USERS is NULL, all keys will be exported.  STATS is
+ * either an export stats object for update or NULL.
+ *
+ * This function is the core of "gpg --export".
  */
 int
-export_pubkeys_stream (ctrl_t ctrl, iobuf_t out, strlist_t users,
-                      kbnode_t *keyblock_out, unsigned int options )
+export_pubkeys (ctrl_t ctrl, strlist_t users, unsigned int options,
+                export_stats_t stats)
 {
-  int any, rc;
-  
-  rc = do_export_stream (ctrl, out, users, 0, keyblock_out, options, &any);
-  if (!rc && !any)
-    rc = -1;
-  return rc;
+  return do_export (ctrl, users, 0, options, stats);
 }
 
+
+/*
+ * Export secret keys (to stdout or to --output FILE).
+ *
+ * Depending on opt.armor the output is armored.  If USERS is NULL,
+ * all secret keys will be exported.  STATS is either an export stats
+ * object for update or NULL.
+ *
+ * This function is the core of "gpg --export-secret-keys".
+ */
 int
-export_seckeys (ctrl_t ctrl, strlist_t users )
+export_seckeys (ctrl_t ctrl, strlist_t users, export_stats_t stats)
 {
-  /* Use only relevant options for the secret key. */
-  unsigned int options = (opt.export_options & EXPORT_SEXP_FORMAT);
-  return do_export (ctrl, users, 1, options);
+  return do_export (ctrl, users, 1, 0, stats);
 }
 
+
+/*
+ * Export secret sub keys (to stdout or to --output FILE).
+ *
+ * This is the same as export_seckeys but replaces the primary key by
+ * a stub key.  Depending on opt.armor the output is armored.  If
+ * USERS is NULL, all secret subkeys will be exported.  STATS is
+ * either an export stats object for update or NULL.
+ *
+ * This function is the core of "gpg --export-secret-subkeys".
+ */
 int
-export_secsubkeys (ctrl_t ctrl, strlist_t users )
+export_secsubkeys (ctrl_t ctrl, strlist_t users, export_stats_t stats)
 {
-  /* Use only relevant options for the secret key. */
-  unsigned int options = (opt.export_options & EXPORT_SEXP_FORMAT);
-  return do_export (ctrl, users, 2, options);
+  return do_export (ctrl, users, 2, 0, stats);
+}
+
+
+/*
+ * Export a single key into a memory buffer.  STATS is either an
+ * export stats object for update or NULL.
+ */
+gpg_error_t
+export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options,
+                      export_stats_t stats,
+                      kbnode_t *r_keyblock, void **r_data, size_t *r_datalen)
+{
+  gpg_error_t err;
+  iobuf_t iobuf;
+  int any;
+  strlist_t helplist;
+
+  *r_keyblock = NULL;
+  *r_data = NULL;
+  *r_datalen = 0;
+
+  helplist = NULL;
+  if (!add_to_strlist_try (&helplist, keyspec))
+    return gpg_error_from_syserror ();
+
+  iobuf = iobuf_temp ();
+  err = do_export_stream (ctrl, iobuf, helplist, 0, r_keyblock, options,
+                          stats, &any);
+  if (!err && !any)
+    err = gpg_error (GPG_ERR_NOT_FOUND);
+  if (!err)
+    {
+      const void *src;
+      size_t datalen;
+
+      iobuf_flush_temp (iobuf);
+      src = iobuf_get_temp_buffer (iobuf);
+      datalen = iobuf_get_temp_length (iobuf);
+      if (!datalen)
+        err = gpg_error (GPG_ERR_NO_PUBKEY);
+      else if (!(*r_data = xtrymalloc (datalen)))
+        err = gpg_error_from_syserror ();
+      else
+        {
+          memcpy (*r_data, src, datalen);
+          *r_datalen = datalen;
+        }
+    }
+  iobuf_close (iobuf);
+  free_strlist (helplist);
+  if (err && *r_keyblock)
+    {
+      release_kbnode (*r_keyblock);
+      *r_keyblock = NULL;
+    }
+  return err;
 }
 
 
@@ -137,32 +251,28 @@ export_secsubkeys (ctrl_t ctrl, strlist_t users )
    secret keyblock and 2 only the subkeys.  OPTIONS are the export
    options to apply.  */
 static int
-do_export (ctrl_t ctrl, strlist_t users, int secret, unsigned int options )
+do_export (ctrl_t ctrl, strlist_t users, int secret, unsigned int options,
+           export_stats_t stats)
 {
   IOBUF out = NULL;
   int any, rc;
   armor_filter_context_t *afx = NULL;
   compress_filter_context_t zfx;
-  
+
   memset( &zfx, 0, sizeof zfx);
-  
-  rc = open_outfile (GNUPG_INVALID_FD, NULL, 0, &out );
+
+  rc = open_outfile (-1, NULL, 0, !!secret, &out );
   if (rc)
     return rc;
 
-  if (!(options & EXPORT_SEXP_FORMAT))
+  if ( opt.armor )
     {
-      if ( opt.armor )
-        {
-          afx = new_armor_context ();
-          afx->what = secret? 5 : 1;
-          push_armor_filter (afx, out);
-        }
-      if ( opt.compress_keys )
-        push_compress_filter (out,&zfx,default_compress_algo());
+      afx = new_armor_context ();
+      afx->what = secret? 5 : 1;
+      push_armor_filter (afx, out);
     }
 
-  rc = do_export_stream (ctrl, out, users, secret, NULL, options, &any );
+  rc = do_export_stream (ctrl, out, users, secret, NULL, options, stats, &any);
 
   if ( rc || !any )
     iobuf_cancel (out);
@@ -197,7 +307,7 @@ subkey_in_list_p (subkey_list_t list, KBNODE node)
       u32 kid[2];
 
       keyid_from_pk (node->pkt->pkt.public_key, kid);
-      
+
       for (; list; list = list->next)
         if (list->kid[0] == kid[0] && list->kid[1] == kid[1])
           return 1;
@@ -239,17 +349,17 @@ exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, KBNODE node)
     case KEYDB_SEARCH_MODE_LONG_KID:
       keyid_from_pk (node->pkt->pkt.public_key, kid);
       break;
-      
+
     case KEYDB_SEARCH_MODE_FPR16:
     case KEYDB_SEARCH_MODE_FPR20:
     case KEYDB_SEARCH_MODE_FPR:
       fingerprint_from_pk (node->pkt->pkt.public_key, fpr,&fprlen);
       break;
-      
+
     default:
       break;
     }
-  
+
   switch(desc->mode)
     {
     case KEYDB_SEARCH_MODE_SHORT_KID:
@@ -281,26 +391,270 @@ exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, KBNODE node)
 }
 
 
+/* Return an error if the key represented by the S-expression S_KEY
+ * and the OpenPGP key represented by PK do not use the same curve. */
+static gpg_error_t
+match_curve_skey_pk (gcry_sexp_t s_key, PKT_public_key *pk)
+{
+  gcry_sexp_t curve = NULL;
+  gcry_sexp_t flags = NULL;
+  char *curve_str = NULL;
+  char *flag;
+  const char *oidstr = NULL;
+  gcry_mpi_t curve_as_mpi = NULL;
+  gpg_error_t err;
+  int is_eddsa = 0;
+  int idx = 0;
+
+  if (!(pk->pubkey_algo==PUBKEY_ALGO_ECDH
+        || pk->pubkey_algo==PUBKEY_ALGO_ECDSA
+        || pk->pubkey_algo==PUBKEY_ALGO_EDDSA))
+    return gpg_error (GPG_ERR_PUBKEY_ALGO);
+
+  curve = gcry_sexp_find_token (s_key, "curve", 0);
+  if (!curve)
+    {
+      log_error ("no reported curve\n");
+      return gpg_error (GPG_ERR_UNKNOWN_CURVE);
+    }
+  curve_str = gcry_sexp_nth_string (curve, 1);
+  gcry_sexp_release (curve); curve = NULL;
+  if (!curve_str)
+    {
+      log_error ("no curve name\n");
+      return gpg_error (GPG_ERR_UNKNOWN_CURVE);
+    }
+  oidstr = openpgp_curve_to_oid (curve_str, NULL);
+  if (!oidstr)
+    {
+      log_error ("no OID known for curve '%s'\n", curve_str);
+      xfree (curve_str);
+      return gpg_error (GPG_ERR_UNKNOWN_CURVE);
+    }
+  xfree (curve_str);
+  err = openpgp_oid_from_str (oidstr, &curve_as_mpi);
+  if (err)
+    return err;
+  if (gcry_mpi_cmp (pk->pkey[0], curve_as_mpi))
+    {
+      log_error ("curves do not match\n");
+      gcry_mpi_release (curve_as_mpi);
+      return gpg_error (GPG_ERR_INV_CURVE);
+    }
+  gcry_mpi_release (curve_as_mpi);
+  flags = gcry_sexp_find_token (s_key, "flags", 0);
+  if (flags)
+    {
+      for (idx = 1; idx < gcry_sexp_length (flags); idx++)
+        {
+          flag = gcry_sexp_nth_string (flags, idx);
+          if (flag && (strcmp ("eddsa", flag) == 0))
+            is_eddsa = 1;
+          gcry_free (flag);
+        }
+    }
+  if (is_eddsa != (pk->pubkey_algo == PUBKEY_ALGO_EDDSA))
+    {
+      log_error ("disagreement about EdDSA\n");
+      err = gpg_error (GPG_ERR_INV_CURVE);
+    }
+
+  return err;
+}
+
+
 /* Return a canonicalized public key algoithms.  This is used to
    compare different flavors of algorithms (e.g. ELG and ELG_E are
    considered the same).  */
-static int
-canon_pubkey_algo (int algo)
+static enum gcry_pk_algos
+canon_pk_algo (enum gcry_pk_algos algo)
 {
   switch (algo)
     {
     case GCRY_PK_RSA:
     case GCRY_PK_RSA_E:
     case GCRY_PK_RSA_S: return GCRY_PK_RSA;
-    case GCRY_PK_ELG:   
+    case GCRY_PK_ELG:
     case GCRY_PK_ELG_E: return GCRY_PK_ELG;
+    case GCRY_PK_ECC:
+    case GCRY_PK_ECDSA:
+    case GCRY_PK_ECDH: return GCRY_PK_ECC;
     default: return algo;
     }
 }
 
 
+/* Take a cleartext dump of a secret key in PK and change the
+ * parameter array in PK to include the secret parameters.  */
+static gpg_error_t
+cleartext_secret_key_to_openpgp (gcry_sexp_t s_key, PKT_public_key *pk)
+{
+  gpg_error_t err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+  gcry_sexp_t top_list;
+  gcry_sexp_t key = NULL;
+  char *key_type = NULL;
+  enum gcry_pk_algos pk_algo;
+  struct seckey_info *ski;
+  int idx, sec_start;
+  gcry_mpi_t pub_params[10] = { NULL };
+
+  /* we look for a private-key, then the first element in it tells us
+     the type */
+  top_list = gcry_sexp_find_token (s_key, "private-key", 0);
+  if (!top_list)
+    goto bad_seckey;
+  if (gcry_sexp_length(top_list) != 2)
+    goto bad_seckey;
+  key = gcry_sexp_nth (top_list, 1);
+  if (!key)
+    goto bad_seckey;
+  key_type = gcry_sexp_nth_string(key, 0);
+  pk_algo = gcry_pk_map_name (key_type);
+
+  log_assert (!pk->seckey_info);
+
+  pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
+  if (!ski)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  switch (canon_pk_algo (pk_algo))
+    {
+    case GCRY_PK_RSA:
+      if (!is_RSA (pk->pubkey_algo))
+        goto bad_pubkey_algo;
+      err = gcry_sexp_extract_param (key, NULL, "ne",
+                                     &pub_params[0],
+                                     &pub_params[1],
+                                     NULL);
+      for (idx=0; idx < 2 && !err; idx++)
+        if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
+          err = gpg_error (GPG_ERR_BAD_PUBKEY);
+      if (!err)
+        {
+          for (idx = 2; idx < 6 && !err; idx++)
+            {
+              gcry_mpi_release (pk->pkey[idx]);
+              pk->pkey[idx] = NULL;
+            }
+          err = gcry_sexp_extract_param (key, NULL, "dpqu",
+                                         &pk->pkey[2],
+                                         &pk->pkey[3],
+                                         &pk->pkey[4],
+                                         &pk->pkey[5],
+                                         NULL);
+        }
+      if (!err)
+        {
+          for (idx = 2; idx < 6; idx++)
+            ski->csum += checksum_mpi (pk->pkey[idx]);
+        }
+      break;
+
+    case GCRY_PK_DSA:
+      if (!is_DSA (pk->pubkey_algo))
+        goto bad_pubkey_algo;
+      err = gcry_sexp_extract_param (key, NULL, "pqgy",
+                                     &pub_params[0],
+                                     &pub_params[1],
+                                     &pub_params[2],
+                                     &pub_params[3],
+                                     NULL);
+      for (idx=0; idx < 4 && !err; idx++)
+        if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
+          err = gpg_error (GPG_ERR_BAD_PUBKEY);
+      if (!err)
+        {
+          gcry_mpi_release (pk->pkey[4]);
+          pk->pkey[4] = NULL;
+          err = gcry_sexp_extract_param (key, NULL, "x",
+                                         &pk->pkey[4],
+                                         NULL);
+        }
+      if (!err)
+        ski->csum += checksum_mpi (pk->pkey[4]);
+      break;
+
+    case GCRY_PK_ELG:
+      if (!is_ELGAMAL (pk->pubkey_algo))
+        goto bad_pubkey_algo;
+      err = gcry_sexp_extract_param (key, NULL, "pgy",
+                                     &pub_params[0],
+                                     &pub_params[1],
+                                     &pub_params[2],
+                                     NULL);
+      for (idx=0; idx < 3 && !err; idx++)
+        if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
+          err = gpg_error (GPG_ERR_BAD_PUBKEY);
+      if (!err)
+        {
+          gcry_mpi_release (pk->pkey[3]);
+          pk->pkey[3] = NULL;
+          err = gcry_sexp_extract_param (key, NULL, "x",
+                                         &pk->pkey[3],
+                                         NULL);
+        }
+      if (!err)
+        ski->csum += checksum_mpi (pk->pkey[3]);
+      break;
+
+    case GCRY_PK_ECC:
+      err = match_curve_skey_pk (key, pk);
+      if (err)
+        goto leave;
+      if (!err)
+        err = gcry_sexp_extract_param (key, NULL, "q",
+                                       &pub_params[0],
+                                       NULL);
+      if (!err && (gcry_mpi_cmp(pk->pkey[1], pub_params[0])))
+        err = gpg_error (GPG_ERR_BAD_PUBKEY);
+
+      sec_start = 2;
+      if (pk->pubkey_algo == PUBKEY_ALGO_ECDH)
+        sec_start += 1;
+      if (!err)
+        {
+          gcry_mpi_release (pk->pkey[sec_start]);
+          pk->pkey[sec_start] = NULL;
+          err = gcry_sexp_extract_param (key, NULL, "d",
+                                         &pk->pkey[sec_start],
+                                         NULL);
+        }
+
+      if (!err)
+        ski->csum += checksum_mpi (pk->pkey[sec_start]);
+      break;
+
+    default:
+      pk->seckey_info = NULL;
+      xfree (ski);
+      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+      break;
+    }
+
+ leave:
+  gcry_sexp_release (top_list);
+  gcry_sexp_release (key);
+  gcry_free (key_type);
+
+  for (idx=0; idx < DIM(pub_params); idx++)
+    gcry_mpi_release (pub_params[idx]);
+  return err;
+
+ bad_pubkey_algo:
+  err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+  goto leave;
+
+ bad_seckey:
+  err = gpg_error (GPG_ERR_BAD_SECKEY);
+  goto leave;
+}
+
+
 /* Use the key transfer format given in S_PGP to create the secinfo
-   structure in PK and chnage the parameter array in PK to include the
+   structure in PK and change the parameter array in PK to include the
    secret parameters.  */
 static gpg_error_t
 transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
@@ -308,12 +662,13 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
   gpg_error_t err;
   gcry_sexp_t top_list;
   gcry_sexp_t list = NULL;
+  char *curve = NULL;
   const char *value;
   size_t valuelen;
   char *string;
   int  idx;
   int  is_v4, is_protected;
-  int  pubkey_algo;
+  enum gcry_pk_algos pk_algo;
   int  protect_algo = 0;
   char iv[16];
   int  ivlen = 0;
@@ -321,12 +676,13 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
   int  s2k_algo = 0;
   byte s2k_salt[8];
   u32  s2k_count = 0;
+  int  is_ecdh = 0;
   size_t npkey, nskey;
   gcry_mpi_t skey[10];  /* We support up to 9 parameters.  */
-  u16 desired_csum;
   int skeyidx = 0;
   struct seckey_info *ski;
 
+  /* gcry_log_debugsxp ("transferkey", s_pgp); */
   top_list = gcry_sexp_find_token (s_pgp, "openpgp-private-key", 0);
   if (!top_list)
     goto bad_seckey;
@@ -361,7 +717,7 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
         goto bad_seckey;
       protect_algo = gcry_cipher_map_name (string);
       xfree (string);
-      
+
       value = gcry_sexp_nth_data (list, 3, &valuelen);
       if (!value || !valuelen || valuelen > sizeof iv)
         goto bad_seckey;
@@ -392,6 +748,7 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
       xfree (string);
     }
 
+  /* Parse the gcrypt PK algo and check that it is okay.  */
   gcry_sexp_release (list);
   list = gcry_sexp_find_token (top_list, "algo", 0);
   if (!list)
@@ -399,14 +756,56 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
   string = gcry_sexp_nth_string (list, 1);
   if (!string)
     goto bad_seckey;
-  pubkey_algo = gcry_pk_map_name (string);
-  xfree (string);
+  pk_algo = gcry_pk_map_name (string);
+  xfree (string); string = NULL;
+  if (gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &npkey)
+      || gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &nskey)
+      || !npkey || npkey >= nskey)
+    goto bad_seckey;
 
-  if (gcry_pk_algo_info (pubkey_algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &npkey)
-      || gcry_pk_algo_info (pubkey_algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &nskey)
-      || !npkey || npkey >= nskey || nskey > PUBKEY_MAX_NSKEY)
+  /* Check that the pubkey algo matches the one from the public key.  */
+  switch (canon_pk_algo (pk_algo))
+    {
+    case GCRY_PK_RSA:
+      if (!is_RSA (pk->pubkey_algo))
+        pk_algo = 0;  /* Does not match.  */
+      break;
+    case GCRY_PK_DSA:
+      if (!is_DSA (pk->pubkey_algo))
+        pk_algo = 0;  /* Does not match.  */
+      break;
+    case GCRY_PK_ELG:
+      if (!is_ELGAMAL (pk->pubkey_algo))
+        pk_algo = 0;  /* Does not match.  */
+      break;
+    case GCRY_PK_ECC:
+      if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
+        ;
+      else if (pk->pubkey_algo == PUBKEY_ALGO_ECDH)
+        is_ecdh = 1;
+      else if (pk->pubkey_algo == PUBKEY_ALGO_EDDSA)
+        ;
+      else
+        pk_algo = 0;  /* Does not match.  */
+      /* For ECC we do not have the domain parameters thus fix our info.  */
+      npkey = 1;
+      nskey = 2;
+      break;
+    default:
+      pk_algo = 0;   /* Oops.  */
+      break;
+    }
+  if (!pk_algo)
+    {
+      err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+      goto leave;
+    }
+
+  /* This check has to go after the ecc adjustments. */
+  if (nskey > PUBKEY_MAX_NSKEY)
     goto bad_seckey;
 
+  /* Parse the key parameters.  */
   gcry_sexp_release (list);
   list = gcry_sexp_find_token (top_list, "skey", 0);
   if (!list)
@@ -453,21 +852,30 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
     }
   skey[skeyidx++] = NULL;
 
-  gcry_sexp_release (list);
-  list = gcry_sexp_find_token (top_list, "csum", 0);
+  gcry_sexp_release (list); list = NULL;
+
+  /* We have no need for the CSUM value thus we don't parse it.  */
+  /* list = gcry_sexp_find_token (top_list, "csum", 0); */
+  /* if (list) */
+  /*   { */
+  /*     string = gcry_sexp_nth_string (list, 1); */
+  /*     if (!string) */
+  /*       goto bad_seckey; */
+  /*     desired_csum = strtoul (string, NULL, 10); */
+  /*     xfree (string); */
+  /*   } */
+  /* else */
+  /*   desired_csum = 0; */
+  /* gcry_sexp_release (list); list = NULL; */
+
+  /* Get the curve name if any,  */
+  list = gcry_sexp_find_token (top_list, "curve", 0);
   if (list)
     {
-      string = gcry_sexp_nth_string (list, 1);
-      if (!string)
-        goto bad_seckey;
-      desired_csum = strtoul (string, NULL, 10);
-      xfree (string);
+      curve = gcry_sexp_nth_string (list, 1);
+      gcry_sexp_release (list); list = NULL;
     }
-  else
-    desired_csum = 0;
 
-
-  gcry_sexp_release (list); list = NULL;
   gcry_sexp_release (top_list); top_list = NULL;
 
   /* log_debug ("XXX is_v4=%d\n", is_v4); */
@@ -503,17 +911,72 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
       goto leave;
     }
 
-  /* Do some sanity checks.  */
-  if (s2k_count <= 1024)
+  /* We need to change the received parameters for ECC algorithms.
+     The transfer format has the curve name and the parameters
+     separate.  We put them all into the SKEY array.  */
+  if (canon_pk_algo (pk_algo) == GCRY_PK_ECC)
     {
-      /* The count must be larger so that encode_s2k_iterations does
-         not fall into a backward compatibility mode.  */
-      err = gpg_error (GPG_ERR_INV_DATA);
-      goto leave;
+      const char *oidstr;
+
+      /* Assert that all required parameters are available.  We also
+         check that the array does not contain more parameters than
+         needed (this was used by some beta versions of 2.1.  */
+      if (!curve || !skey[0] || !skey[1] || skey[2])
+        {
+          err = gpg_error (GPG_ERR_INTERNAL);
+          goto leave;
+        }
+
+      oidstr = openpgp_curve_to_oid (curve, NULL);
+      if (!oidstr)
+        {
+          log_error ("no OID known for curve '%s'\n", curve);
+          err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
+          goto leave;
+        }
+      /* Put the curve's OID into into the MPI array.  This requires
+         that we shift Q and D.  For ECDH also insert the KDF parms. */
+      if (is_ecdh)
+        {
+          skey[4] = NULL;
+          skey[3] = skey[1];
+          skey[2] = gcry_mpi_copy (pk->pkey[2]);
+        }
+      else
+        {
+          skey[3] = NULL;
+          skey[2] = skey[1];
+        }
+      skey[1] = skey[0];
+      skey[0] = NULL;
+      err = openpgp_oid_from_str (oidstr, skey + 0);
+      if (err)
+        goto leave;
+      /* Fixup the NPKEY and NSKEY to match OpenPGP reality.  */
+      npkey = 2 + is_ecdh;
+      nskey = 3 + is_ecdh;
+
+      /* for (idx=0; skey[idx]; idx++) */
+      /*   { */
+      /*     log_info ("YYY skey[%d]:", idx); */
+      /*     if (gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_OPAQUE)) */
+      /*       { */
+      /*         void *p; */
+      /*         unsigned int nbits; */
+      /*         p = gcry_mpi_get_opaque (skey[idx], &nbits); */
+      /*         log_printhex (NULL, p, (nbits+7)/8); */
+      /*       } */
+      /*     else */
+      /*       gcry_mpi_dump (skey[idx]); */
+      /*     log_printf ("\n"); */
+      /*   } */
     }
-  if (canon_pubkey_algo (pubkey_algo) != canon_pubkey_algo (pk->pubkey_algo))
+
+  /* Do some sanity checks.  */
+  if (s2k_count > 255)
     {
-      err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+      /* We expect an already encoded S2K count.  */
+      err = gpg_error (GPG_ERR_INV_DATA);
       goto leave;
     }
   err = openpgp_cipher_test_algo (protect_algo);
@@ -522,12 +985,11 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
   err = openpgp_md_test_algo (s2k_algo);
   if (err)
     goto leave;
-  
-  /* Check that the public key parameters match.  */
+
+  /* Check that the public key parameters match.  Note that since
+     Libgcrypt 1.5 gcry_mpi_cmp handles opaque MPI correctly.  */
   for (idx=0; idx < npkey; idx++)
-    if (gcry_mpi_get_flag (pk->pkey[idx], GCRYMPI_FLAG_OPAQUE)
-        || gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_OPAQUE)
-        || gcry_mpi_cmp (pk->pkey[idx], skey[idx]))
+    if (gcry_mpi_cmp (pk->pkey[idx], skey[idx]))
       {
         err = gpg_error (GPG_ERR_BAD_PUBKEY);
         goto leave;
@@ -553,16 +1015,16 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
       err = gpg_error_from_syserror ();
       goto leave;
     }
-  
+
   ski->is_protected = 1;
   ski->sha1chk = 1;
   ski->algo = protect_algo;
   ski->s2k.mode = s2k_mode;
   ski->s2k.hash_algo = s2k_algo;
-  assert (sizeof ski->s2k.salt == sizeof s2k_salt);
+  log_assert (sizeof ski->s2k.salt == sizeof s2k_salt);
   memcpy (ski->s2k.salt, s2k_salt, sizeof s2k_salt);
-  ski->s2k.count = encode_s2k_iterations (s2k_count);
-  assert (ivlen <= sizeof ski->iv);
+  ski->s2k.count = s2k_count;
+  log_assert (ivlen <= sizeof ski->iv);
   memcpy (ski->iv, iv, ivlen);
   ski->ivlen = ivlen;
 
@@ -573,6 +1035,7 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
   /* That's it.  */
 
  leave:
+  gcry_free (curve);
   gcry_sexp_release (list);
   gcry_sexp_release (top_list);
   for (idx=0; idx < skeyidx; idx++)
@@ -582,12 +1045,108 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
  bad_seckey:
   err = gpg_error (GPG_ERR_BAD_SECKEY);
   goto leave;
-  
+
  outofmem:
   err = gpg_error (GPG_ERR_ENOMEM);
   goto leave;
 }
 
+
+/* Print an "EXPORTED" status line.  PK is the primary public key.  */
+static void
+print_status_exported (PKT_public_key *pk)
+{
+  char *hexfpr;
+
+  if (!is_status_enabled ())
+    return;
+
+  hexfpr = hexfingerprint (pk, NULL, 0);
+  write_status_text (STATUS_EXPORTED, hexfpr? hexfpr : "[?]");
+  xfree (hexfpr);
+}
+
+
+/*
+ * Receive a secret key from agent specified by HEXGRIP.
+ *
+ * Since the key data from agant is encrypted, decrypt it by CIPHERHD.
+ * Then, parse the decrypted key data in transfer format, and put
+ * secret parameters into PK.
+ *
+ * If CLEARTEXT is 0, store the secret key material
+ * passphrase-protected.  Otherwise, store secret key material in the
+ * clear.
+ *
+ * CACHE_NONCE_ADDR is used to share nonce for multple key retrievals.
+ */
+gpg_error_t
+receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd,
+                           int cleartext,
+                           char **cache_nonce_addr, const char *hexgrip,
+                           PKT_public_key *pk)
+{
+  gpg_error_t err = 0;
+  unsigned char *wrappedkey = NULL;
+  size_t wrappedkeylen;
+  unsigned char *key = NULL;
+  size_t keylen, realkeylen;
+  gcry_sexp_t s_skey;
+  char *prompt;
+
+  if (opt.verbose)
+    log_info ("key %s: asking agent for the secret parts\n", hexgrip);
+
+  prompt = gpg_format_keydesc (pk, FORMAT_KEYDESC_EXPORT,1);
+  err = agent_export_key (ctrl, hexgrip, prompt, !cleartext, cache_nonce_addr,
+                          &wrappedkey, &wrappedkeylen);
+  xfree (prompt);
+
+  if (err)
+    goto unwraperror;
+  if (wrappedkeylen < 24)
+    {
+      err = gpg_error (GPG_ERR_INV_LENGTH);
+      goto unwraperror;
+    }
+  keylen = wrappedkeylen - 8;
+  key = xtrymalloc_secure (keylen);
+  if (!key)
+    {
+      err = gpg_error_from_syserror ();
+      goto unwraperror;
+    }
+  err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
+  if (err)
+    goto unwraperror;
+  realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
+  if (!realkeylen)
+    goto unwraperror; /* Invalid csexp.  */
+
+  err = gcry_sexp_sscan (&s_skey, NULL, key, realkeylen);
+  if (!err)
+    {
+      if (cleartext)
+        err = cleartext_secret_key_to_openpgp (s_skey, pk);
+      else
+        err = transfer_format_to_openpgp (s_skey, pk);
+      gcry_sexp_release (s_skey);
+    }
+
+ unwraperror:
+  xfree (key);
+  xfree (wrappedkey);
+  if (err)
+    {
+      log_error ("key %s: error receiving key from agent:"
+                 " %s%s\n", hexgrip, gpg_strerror (err),
+                 gpg_err_code (err) == GPG_ERR_FULLY_CANCELED?
+                 "":_(" - skipped"));
+    }
+  return err;
+}
+
+
 /* Export the keys identified by the list of strings in USERS to the
    stream OUT.  If Secret is false public keys will be exported.  With
    secret true secret keys will be exported; in this case 1 means the
@@ -595,11 +1154,12 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
    export options to apply.  If KEYBLOCK_OUT is not NULL, AND the exit
    code is zero, a pointer to the first keyblock found and exported
    will be stored at this address; no other keyblocks are exported in
-   this case.  The caller must free it the returned keyblock.  If any
+   this case.  The caller must free the returned keyblock.  If any
    key has been exported true is stored at ANY. */
 static int
 do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
-                 kbnode_t *keyblock_out, unsigned int options, int *any)
+                 kbnode_t *keyblock_out, unsigned int options,
+                  export_stats_t stats, int *any)
 {
   gpg_error_t err = 0;
   PACKET pkt;
@@ -610,14 +1170,25 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
   subkey_list_t subkey_list = NULL;  /* Track already processed subkeys. */
   KEYDB_HANDLE kdbhd;
   strlist_t sl;
-  int indent = 0;
   gcry_cipher_hd_t cipherhd = NULL;
+  char *cache_nonce = NULL;
+  struct export_stats_s dummystats;
+  int cleartext = 0;
 
+  if (!stats)
+    stats = &dummystats;
   *any = 0;
   init_packet (&pkt);
   kdbhd = keydb_new ();
+  if (!kdbhd)
+    return gpg_error_from_syserror ();
+
+  /* For the DANE format override the options.  */
+  if ((options & EXPORT_DANE_FORMAT))
+    options = (EXPORT_DANE_FORMAT | EXPORT_MINIMAL | EXPORT_CLEAN);
 
-  if (!users) 
+
+  if (!users)
     {
       ndesc = 1;
       desc = xcalloc (ndesc, sizeof *desc);
@@ -625,19 +1196,21 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
     }
   else
     {
-      for (ndesc=0, sl=users; sl; sl = sl->next, ndesc++) 
+      for (ndesc=0, sl=users; sl; sl = sl->next, ndesc++)
         ;
       desc = xmalloc ( ndesc * sizeof *desc);
-        
+
       for (ndesc=0, sl=users; sl; sl = sl->next)
         {
-          if (!(err=classify_user_id (sl->d, desc+ndesc)))
+          if (!(err=classify_user_id (sl->d, desc+ndesc, 1)))
             ndesc++;
           else
             log_error (_("key \"%s\" not found: %s\n"),
                        sl->d, gpg_strerror (err));
         }
 
+      keydb_disable_caching (kdbhd);  /* We are looping the search.  */
+
       /* It would be nice to see which of the given users did actually
          match one in the keyring.  To implement this we need to have
          a found flag for each entry in desc.  To set this flag we
@@ -654,7 +1227,7 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
       goto leave;
     }
 #endif
-  
+
   /* For secret key export we need to setup a decryption context.  */
   if (secret)
     {
@@ -667,7 +1240,7 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
           log_error ("error getting the KEK: %s\n", gpg_strerror (err));
           goto leave;
         }
-      
+
       /* Prepare a cipher context.  */
       err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
                               GCRY_CIPHER_MODE_AESWRAP, 0);
@@ -683,20 +1256,23 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
       kek = NULL;
     }
 
-  while (!(err = keydb_search2 (kdbhd, desc, ndesc, &descindex))) 
+  for (;;)
     {
       int skip_until_subkey = 0;
       u32 keyid[2];
       PKT_public_key *pk;
 
-      if (!users) 
+      err = keydb_search (kdbhd, desc, ndesc, &descindex);
+      if (!users)
         desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
+      if (err)
+        break;
 
       /* Read the keyblock. */
       release_kbnode (keyblock);
       keyblock = NULL;
       err = keydb_get_keyblock (kdbhd, &keyblock);
-      if (err) 
+      if (err)
         {
           log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
           goto leave;
@@ -708,6 +1284,8 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
           log_error ("public key packet not found in keyblock - skipped\n");
           continue;
         }
+      stats->count++;
+      setup_main_keyids (keyblock);  /* gpg_format_keydesc needs it.  */
       pk = node->pkt->pkt.public_key;
       keyid_from_pk (pk, keyid);
 
@@ -736,6 +1314,7 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
                         "not yet supported - skipped\n", keystr (keyid));
               continue;
             }
+          stats->secret_count++;
         }
 
       /* Always do the cleaning on the public key part if requested.
@@ -748,7 +1327,9 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
         clean_key (keyblock, opt.verbose, (options&EXPORT_MINIMAL), NULL, NULL);
 
       /* And write it. */
-      for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); ) 
+      xfree (cache_nonce);
+      cache_nonce = NULL;
+      for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); )
         {
           if (skip_until_subkey)
             {
@@ -781,7 +1362,7 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
                      subkey and include that subkey into the output
                      too.  Need to add this subkey to a list so that
                      it won't get processed a second time.
-                   
+
                      So the first step here is to check that list and
                      skip in any case if the key is in that list.
 
@@ -789,7 +1370,7 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
                      function of GnuPG < 2.1 is not able to merge
                      secret keys and thus it is useless to output them
                      as two separate keys and have import merge them.  */
-                  if (subkey_in_list_p (subkey_list, node))  
+                  if (subkey_in_list_p (subkey_list, node))
                     skip_until_subkey = 1; /* Already processed this one. */
                   else
                     {
@@ -800,7 +1381,7 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
                             && exact_subkey_match_p (desc+j, node))
                           break;
                       if (!(j < ndesc))
-                        skip_until_subkey = 1; /* No other one matching. */ 
+                        skip_until_subkey = 1; /* No other one matching. */
                     }
                 }
 
@@ -831,9 +1412,9 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
                   && node->pkt->pkt.signature->revkey)
                 {
                   int i;
-                  
+
                   for (i=0;i<node->pkt->pkt.signature->numrevkeys;i++)
-                    if ( (node->pkt->pkt.signature->revkey[i]->class & 0x40))
+                    if ( (node->pkt->pkt.signature->revkey[i].class & 0x40))
                       break;
 
                   if (i < node->pkt->pkt.signature->numrevkeys)
@@ -850,7 +1431,7 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
                 or a signature on an attrib */
              while (kbctx->next && kbctx->next->pkt->pkttype==PKT_SIGNATURE)
                 kbctx = kbctx->next;
+
              continue;
            }
 
@@ -859,7 +1440,7 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
             {
               u32 subkidbuf[2], *subkid;
               char *hexgrip, *serialno;
-              
+
               pk = node->pkt->pkt.public_key;
               if (node->pkt->pkttype == PKT_PUBLIC_KEY)
                 subkid = NULL;
@@ -876,7 +1457,7 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
                   skip_until_subkey = 1;
                   continue;
                 }
-              
+
               err = hexkeygrip_from_pk (pk, &hexgrip);
               if (err)
                 {
@@ -897,7 +1478,7 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
                   serialno = NULL;
                 }
               else
-                err = agent_get_keyinfo (ctrl, hexgrip, &serialno);
+                err = agent_get_keyinfo (ctrl, hexgrip, &serialno, &cleartext);
 
               if ((!err && serialno)
                   && secret == 2 && node->pkt->pkttype == PKT_PUBLIC_KEY)
@@ -916,7 +1497,7 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
                   /* Create a key stub.  */
                   struct seckey_info *ski;
                   const char *s;
-                  
+
                   pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
                   if (!ski)
                     {
@@ -935,84 +1516,35 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
                            ski->ivlen++, s += 2)
                         ski->iv[ski->ivlen] = xtoi_2 (s);
                     }
-                  
-                  if ((options&EXPORT_SEXP_FORMAT))
-                    err = build_sexp (out, node->pkt, &indent);
-                  else
-                    err = build_packet (out, node->pkt);
+
+                  err = build_packet (out, node->pkt);
+                  if (!err && node->pkt->pkttype == PKT_PUBLIC_KEY)
+                    {
+                      stats->exported++;
+                      print_status_exported (node->pkt->pkt.public_key);
+                    }
                 }
               else if (!err)
                 {
-                  /* FIXME: Move this spaghetti code into a separate
-                     function.  */
-                  unsigned char *wrappedkey = NULL;
-                  size_t wrappedkeylen;
-                  unsigned char *key = NULL;
-                  size_t keylen, realkeylen;
-                  gcry_sexp_t s_skey;
-
-                  if (opt.verbose)
-                    log_info ("key %s: asking agent for the secret parts\n",
-                              keystr_with_sub (keyid, subkid));
-
-                  err = agent_export_key (ctrl, hexgrip, "Key foo", NULL,
-                                          &wrappedkey, &wrappedkeylen);
-                  if (err)
-                    goto unwraperror;
-                  if (wrappedkeylen < 24)
-                    {
-                      err = gpg_error (GPG_ERR_INV_LENGTH);
-                      goto unwraperror;
-                    }
-                  keylen = wrappedkeylen - 8;
-                  key = xtrymalloc_secure (keylen);
-                  if (!key)
-                    {
-                      err = gpg_error_from_syserror ();
-                      goto unwraperror;
-                    }
-                  err = gcry_cipher_decrypt (cipherhd, key, keylen,
-                                             wrappedkey, wrappedkeylen);
-                  if (err)
-                    goto unwraperror;
-                  realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
-                  if (!realkeylen)
-                    goto unwraperror; /* Invalid csexp.  */
-                  
-                  err = gcry_sexp_sscan (&s_skey, NULL, key, realkeylen);
-                  xfree (key);
-                  key = NULL;
-                  if (err)
-                    goto unwraperror;
-                  err = transfer_format_to_openpgp (s_skey, pk);
-                  gcry_sexp_release (s_skey);
-                  if (err)
-                    goto unwraperror;
-
-                  if ((options&EXPORT_SEXP_FORMAT))
-                    err = build_sexp (out, node->pkt, &indent);
-                  else
-                    err = build_packet (out, node->pkt);
-                  goto unwraperror_leave;
-
-                unwraperror:
-                  xfree (wrappedkey);
-                  xfree (key);
+                  err = receive_seckey_from_agent (ctrl, cipherhd,
+                                                   cleartext, &cache_nonce,
+                                                   hexgrip, pk);
                   if (err)
                     {
-                      log_error ("key %s: error receiving key from agent:"
-                                 " %s%s\n",
-                                 keystr_with_sub (keyid, subkid),
-                                 gpg_strerror (err),
-                                 gpg_err_code (err) == GPG_ERR_FULLY_CANCELED?
-                                 "":_(" - skipped"));
                       if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
                         goto leave;
                       skip_until_subkey = 1;
                       err = 0;
                     }
-                unwraperror_leave:
-                  ;
+                  else
+                    {
+                      err = build_packet (out, node->pkt);
+                      if (node->pkt->pkttype == PKT_PUBLIC_KEY)
+                        {
+                          stats->exported++;
+                          print_status_exported (node->pkt->pkt.public_key);
+                        }
+                    }
                 }
               else
                 {
@@ -1029,12 +1561,15 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
             }
           else
             {
-              if ((options&EXPORT_SEXP_FORMAT))
-                err = build_sexp (out, node->pkt, &indent);
-              else
-                err = build_packet (out, node->pkt);
+              err = build_packet (out, node->pkt);
+              if (!err && node->pkt->pkttype == PKT_PUBLIC_KEY)
+                {
+                  stats->exported++;
+                  print_status_exported (node->pkt->pkt.public_key);
+                }
             }
 
+
           if (err)
             {
               log_error ("build_packet(%d) failed: %s\n",
@@ -1046,26 +1581,13 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
             *any = 1;
        }
 
-      if ((options&EXPORT_SEXP_FORMAT) && indent)
-        {
-          for (; indent; indent--)
-            iobuf_put (out, ')');
-          iobuf_put (out, '\n');
-        }
-
       if (keyblock_out)
         {
           *keyblock_out = keyblock;
           break;
         }
     }
-  if ((options&EXPORT_SEXP_FORMAT) && indent)
-    {
-      for (; indent; indent--)
-        iobuf_put (out, ')');
-      iobuf_put (out, '\n');
-    }
-  if (err == -1)
+  if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
     err = 0;
 
  leave:
@@ -1075,6 +1597,7 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
   keydb_release (kdbhd);
   if (err || !keyblock_out)
     release_kbnode( keyblock );
+  xfree (cache_nonce);
   if( !*any )
     log_info(_("WARNING: nothing exported\n"));
   return err;
@@ -1082,138 +1605,298 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
 
 
 
-/* static int */
-/* write_sexp_line (iobuf_t out, int *indent, const char *text) */
-/* { */
-/*   int i; */
+\f
+static gpg_error_t
+key_to_sshblob (membuf_t *mb, const char *identifier, ...)
+{
+  va_list arg_ptr;
+  gpg_error_t err = 0;
+  unsigned char nbuf[4];
+  unsigned char *buf;
+  size_t buflen;
+  gcry_mpi_t a;
+
+  ulongtobuf (nbuf, (ulong)strlen (identifier));
+  put_membuf (mb, nbuf, 4);
+  put_membuf_str (mb, identifier);
+  if (!strncmp (identifier, "ecdsa-sha2-", 11))
+    {
+      ulongtobuf (nbuf, (ulong)strlen (identifier+11));
+      put_membuf (mb, nbuf, 4);
+      put_membuf_str (mb, identifier+11);
+    }
+  va_start (arg_ptr, identifier);
+  while ((a = va_arg (arg_ptr, gcry_mpi_t)))
+    {
+      err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, &buf, &buflen, a);
+      if (err)
+        break;
+      if (!strcmp (identifier, "ssh-ed25519")
+          && buflen > 5 && buf[4] == 0x40)
+        {
+          /* We need to strip our 0x40 prefix.  */
+          put_membuf (mb, "\x00\x00\x00\x20", 4);
+          put_membuf (mb, buf+5, buflen-5);
+        }
+      else
+        put_membuf (mb, buf, buflen);
+      gcry_free (buf);
+    }
+  va_end (arg_ptr);
+  return err;
+}
 
-/*   for (i=0; i < *indent; i++) */
-/*     iobuf_put (out, ' '); */
-/*   iobuf_writestr (out, text); */
-/*   return 0; */
-/* } */
+/* Export the key identified by USERID in the SSH public key format.
+   The function exports the latest subkey with Authentication
+   capability unless the '!' suffix is used to export a specific
+   key.  */
+gpg_error_t
+export_ssh_key (ctrl_t ctrl, const char *userid)
+{
+  gpg_error_t err;
+  kbnode_t keyblock = NULL;
+  KEYDB_SEARCH_DESC desc;
+  u32 latest_date;
+  u32 curtime = make_timestamp ();
+  kbnode_t latest_key, node;
+  PKT_public_key *pk;
+  const char *identifier;
+  membuf_t mb;
+  estream_t fp = NULL;
+  struct b64state b64_state;
+  const char *fname = "-";
+
+  init_membuf (&mb, 4096);
+
+  /* We need to know whether the key has been specified using the
+     exact syntax ('!' suffix).  Thus we need to run a
+     classify_user_id on our own.  */
+  err = classify_user_id (userid, &desc, 1);
+
+  /* Get the public key.  */
+  if (!err)
+    {
+      getkey_ctx_t getkeyctx;
 
-/* static int */
-/* write_sexp_keyparm (iobuf_t out, int *indent, const char *name, gcry_mpi_t a) */
-/* { */
-/*   int rc; */
-/*   unsigned char *buffer; */
+      err = get_pubkey_byname (ctrl, &getkeyctx, NULL, userid, &keyblock,
+                               NULL,
+                               0  /* Only usable keys or given exact. */,
+                               1  /* No AKL lookup.  */);
+      if (!err)
+        {
+          err = getkey_next (getkeyctx, NULL, NULL);
+          if (!err)
+            err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
+          else if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
+            err = 0;
+        }
+      getkey_end (getkeyctx);
+    }
+  if (err)
+    {
+      log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err));
+      return err;
+    }
 
-/*   write_sexp_line (out, indent, "("); */
-/*   iobuf_writestr (out, name); */
-/*   iobuf_writestr (out, " #"); */
+  /* The finish_lookup code in getkey.c does not handle auth keys,
+     thus we have to duplicate the code here to find the latest
+     subkey.  However, if the key has been found using an exact match
+     ('!' notation) we use that key without any further checks and
+     even allow the use of the primary key. */
+  latest_date = 0;
+  latest_key = NULL;
+  for (node = keyblock; node; node = node->next)
+    {
+      if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
+           || node->pkt->pkttype == PKT_PUBLIC_KEY)
+          && node->pkt->pkt.public_key->flags.exact)
+        {
+          latest_key = node;
+          break;
+        }
+    }
+  if (!latest_key)
+    {
+      for (node = keyblock; node; node = node->next)
+        {
+          if (node->pkt->pkttype != PKT_PUBLIC_SUBKEY)
+            continue;
 
-/*   rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, &buffer, NULL, a); */
-/*   assert (!rc); */
-/*   iobuf_writestr (out, buffer); */
-/*   iobuf_writestr (out, "#)"); */
-/*   gcry_free (buffer); */
-/*   return 0; */
-/* } */
+          pk = node->pkt->pkt.public_key;
+          if (DBG_LOOKUP)
+            log_debug ("\tchecking subkey %08lX\n",
+                       (ulong) keyid_from_pk (pk, NULL));
+          if (!(pk->pubkey_usage & PUBKEY_USAGE_AUTH))
+            {
+              if (DBG_LOOKUP)
+                log_debug ("\tsubkey not usable for authentication\n");
+              continue;
+            }
+          if (!pk->flags.valid)
+            {
+              if (DBG_LOOKUP)
+                log_debug ("\tsubkey not valid\n");
+              continue;
+            }
+          if (pk->flags.revoked)
+            {
+              if (DBG_LOOKUP)
+                log_debug ("\tsubkey has been revoked\n");
+              continue;
+            }
+          if (pk->has_expired)
+            {
+              if (DBG_LOOKUP)
+                log_debug ("\tsubkey has expired\n");
+              continue;
+            }
+          if (pk->timestamp > curtime && !opt.ignore_valid_from)
+            {
+              if (DBG_LOOKUP)
+                log_debug ("\tsubkey not yet valid\n");
+              continue;
+            }
+          if (DBG_LOOKUP)
+            log_debug ("\tsubkey might be fine\n");
+          /* In case a key has a timestamp of 0 set, we make sure that it
+             is used.  A better change would be to compare ">=" but that
+             might also change the selected keys and is as such a more
+             intrusive change.  */
+          if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date))
+            {
+              latest_date = pk->timestamp;
+              latest_key = node;
+            }
+        }
+    }
 
-static int
-build_sexp_seckey (iobuf_t out, PACKET *pkt, int *indent)
-{
-  (void)out;
-  (void)pkt;
-  (void)indent;
+  if (!latest_key)
+    {
+      err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
+      log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err));
+      goto leave;
+    }
 
-  /* FIXME: Not yet implemented.  */
-  return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
-  /* PKT_secret_key *sk = pkt->pkt.secret_key; */
-  /* char tmpbuf[100]; */
+  pk = latest_key->pkt->pkt.public_key;
+  if (DBG_LOOKUP)
+    log_debug ("\tusing key %08lX\n", (ulong) keyid_from_pk (pk, NULL));
 
-  /* if (pkt->pkttype == PKT_SECRET_KEY) */
-  /*   { */
-  /*     iobuf_writestr (out, "(openpgp-key\n"); */
-  /*     (*indent)++; */
-  /*   } */
-  /* else */
-  /*   { */
-  /*     iobuf_writestr (out, " (subkey\n"); */
-  /*     (*indent)++; */
-  /*   } */
-  /* (*indent)++; */
-  /* write_sexp_line (out, indent, "(private-key\n"); */
-  /* (*indent)++; */
-  /* if (is_RSA (sk->pubkey_algo) && !sk->is_protected) */
-  /*   { */
-  /*     write_sexp_line (out, indent, "(rsa\n"); */
-  /*     (*indent)++; */
-  /*     write_sexp_keyparm (out, indent, "n", sk->skey[0]); iobuf_put (out,'\n'); */
-  /*     write_sexp_keyparm (out, indent, "e", sk->skey[1]); iobuf_put (out,'\n'); */
-  /*     write_sexp_keyparm (out, indent, "d", sk->skey[2]); iobuf_put (out,'\n'); */
-  /*     write_sexp_keyparm (out, indent, "p", sk->skey[3]); iobuf_put (out,'\n'); */
-  /*     write_sexp_keyparm (out, indent, "q", sk->skey[4]); iobuf_put (out,'\n'); */
-  /*     write_sexp_keyparm (out, indent, "u", sk->skey[5]);  */
-  /*     iobuf_put (out,')'); iobuf_put (out,'\n'); */
-  /*     (*indent)--; */
-  /*   } */
-  /* else if (sk->pubkey_algo == PUBKEY_ALGO_DSA && !sk->is_protected) */
-  /*   { */
-  /*     write_sexp_line (out, indent, "(dsa\n"); */
-  /*     (*indent)++; */
-  /*     write_sexp_keyparm (out, indent, "p", sk->skey[0]); iobuf_put (out,'\n'); */
-  /*     write_sexp_keyparm (out, indent, "q", sk->skey[1]); iobuf_put (out,'\n'); */
-  /*     write_sexp_keyparm (out, indent, "g", sk->skey[2]); iobuf_put (out,'\n'); */
-  /*     write_sexp_keyparm (out, indent, "y", sk->skey[3]); iobuf_put (out,'\n'); */
-  /*     write_sexp_keyparm (out, indent, "x", sk->skey[4]); */
-  /*     iobuf_put (out,')'); iobuf_put (out,'\n'); */
-  /*     (*indent)--; */
-  /*   } */
-/*
-  else if (sk->pubkey_algo == PUBKEY_ALGO_ECDSA && !sk->is_protected)
+  switch (pk->pubkey_algo)
     {
-      write_sexp_line (out, indent, "(ecdsa\n");
-      (*indent)++; 
-      write_sexp_keyparm (out, indent, "c", sk->skey[0]); iobuf_put (out,'\n');
-      write_sexp_keyparm (out, indent, "q", sk->skey[6]); iobuf_put (out,'\n');
-      write_sexp_keyparm (out, indent, "d", sk->skey[7]);
-      iobuf_put (out,')'); iobuf_put (out,'\n');
-      (*indent)--;
-    }
-*/
-  /* else if (is_ELGAMAL (sk->pubkey_algo) && !sk->is_protected) */
-  /*   { */
-  /*     write_sexp_line (out, indent, "(elg\n"); */
-  /*     (*indent)++; */
-  /*     write_sexp_keyparm (out, indent, "p", sk->skey[0]); iobuf_put (out,'\n'); */
-  /*     write_sexp_keyparm (out, indent, "g", sk->skey[2]); iobuf_put (out,'\n'); */
-  /*     write_sexp_keyparm (out, indent, "y", sk->skey[3]); iobuf_put (out,'\n'); */
-  /*     write_sexp_keyparm (out, indent, "x", sk->skey[4]); */
-  /*     iobuf_put (out,')'); iobuf_put (out,'\n'); */
-  /*     (*indent)--; */
-  /*   } */
-  /* write_sexp_line (out, indent,  "(attrib\n"); (*indent)++; */
-  /* sprintf (tmpbuf, "(created \"%lu\"", (unsigned long)sk->timestamp); */
-  /* write_sexp_line (out, indent, tmpbuf); */
-  /* iobuf_put (out,')'); (*indent)--; /\* close created *\/ */
-  /* iobuf_put (out,')'); (*indent)--; /\* close attrib *\/ */
-  /* iobuf_put (out,')'); (*indent)--; /\* close private-key *\/ */
-  /* if (pkt->pkttype != PKT_SECRET_KEY) */
-  /*   iobuf_put (out,')'), (*indent)--; /\* close subkey *\/ */
-  /* iobuf_put (out,'\n'); */
-
-  /* return 0; */
-}
+    case PUBKEY_ALGO_DSA:
+      identifier = "ssh-dss";
+      err = key_to_sshblob (&mb, identifier,
+                            pk->pkey[0], pk->pkey[1], pk->pkey[2], pk->pkey[3],
+                            NULL);
+      break;
 
+    case PUBKEY_ALGO_RSA:
+    case PUBKEY_ALGO_RSA_S:
+      identifier = "ssh-rsa";
+      err = key_to_sshblob (&mb, identifier, pk->pkey[1], pk->pkey[0], NULL);
+      break;
 
-/* For some packet types we write them in a S-expression format.  This
-   is still EXPERIMENTAL and subject to change.  */
-static int 
-build_sexp (iobuf_t out, PACKET *pkt, int *indent)
-{
-  int rc;
+    case PUBKEY_ALGO_ECDSA:
+      {
+        char *curveoid;
+        const char *curve;
+
+        curveoid = openpgp_oid_to_str (pk->pkey[0]);
+        if (!curveoid)
+          err = gpg_error_from_syserror ();
+        else if (!(curve = openpgp_oid_to_curve (curveoid, 0)))
+          err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
+        else
+          {
+            if (!strcmp (curve, "nistp256"))
+              identifier = "ecdsa-sha2-nistp256";
+            else if (!strcmp (curve, "nistp384"))
+              identifier = "ecdsa-sha2-nistp384";
+            else if (!strcmp (curve, "nistp521"))
+              identifier = "ecdsa-sha2-nistp521";
+            else
+              identifier = NULL;
+
+            if (!identifier)
+              err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
+            else
+              err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL);
+          }
+        xfree (curveoid);
+      }
+      break;
 
-  switch (pkt->pkttype)
-    {
-    case PKT_SECRET_KEY:
-    case PKT_SECRET_SUBKEY:
-      rc = build_sexp_seckey (out, pkt, indent);
+    case PUBKEY_ALGO_EDDSA:
+      if (!openpgp_oid_is_ed25519 (pk->pkey[0]))
+        err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
+      else
+        {
+          identifier = "ssh-ed25519";
+          err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL);
+        }
+      break;
+
+    case PUBKEY_ALGO_ELGAMAL_E:
+    case PUBKEY_ALGO_ELGAMAL:
+      err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
       break;
+
     default:
-      rc = 0;
+      err = GPG_ERR_PUBKEY_ALGO;
       break;
     }
-  return rc;
+
+  if (err)
+    goto leave;
+
+  if (opt.outfile && *opt.outfile && strcmp (opt.outfile, "-"))
+    fp = es_fopen ((fname = opt.outfile), "w");
+  else
+    fp = es_stdout;
+  if (!fp)
+    {
+      err = gpg_error_from_syserror ();
+      log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err));
+      goto leave;
+    }
+
+  es_fprintf (fp, "%s ", identifier);
+  err = b64enc_start_es (&b64_state, fp, "");
+  if (err)
+    goto leave;
+  {
+    void *blob;
+    size_t bloblen;
+
+    blob = get_membuf (&mb, &bloblen);
+    if (!blob)
+      err = gpg_error_from_syserror ();
+    else
+      err = b64enc_write (&b64_state, blob, bloblen);
+    xfree (blob);
+    if (err)
+      goto leave;
+  }
+  err = b64enc_finish (&b64_state);
+  if (err)
+    goto leave;
+  es_fprintf (fp, " openpgp:0x%08lX\n", (ulong)keyid_from_pk (pk, NULL));
+
+  if (es_ferror (fp))
+    err = gpg_error_from_syserror ();
+  else
+    {
+      if (es_fclose (fp))
+        err = gpg_error_from_syserror ();
+      fp = NULL;
+    }
+
+  if (err)
+    log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
+
+ leave:
+  es_fclose (fp);
+  xfree (get_membuf (&mb, NULL));
+  release_kbnode (keyblock);
+  return err;
 }