g10: Fix keybox-related memory leaks.
[gnupg.git] / g10 / export.c
index 3c2aa57..b067376 100644 (file)
@@ -1,7 +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-2015  Werner Koch
+ * Copyright (C) 1998-2016  Werner Koch
  *
  * This file is part of GnuPG.
  *
@@ -24,7 +24,6 @@
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
-#include <assert.h>
 
 #include "gpg.h"
 #include "options.h"
@@ -34,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"
 
@@ -390,6 +391,78 @@ 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).  */
@@ -411,6 +484,175 @@ canon_pk_algo (enum gcry_pk_algos 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 change the parameter array in PK to include the
    secret parameters.  */
@@ -779,10 +1021,10 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
   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 = s2k_count;
-  assert (ivlen <= sizeof ski->iv);
+  log_assert (ivlen <= sizeof ski->iv);
   memcpy (ski->iv, iv, ivlen);
   ski->ivlen = ivlen;
 
@@ -825,6 +1067,86 @@ print_status_exported (PKT_public_key *pk)
 }
 
 
+/*
+ * 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
@@ -851,12 +1173,15 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
   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))
@@ -940,8 +1265,6 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
       err = keydb_search (kdbhd, desc, ndesc, &descindex);
       if (!users)
         desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
-      if (gpg_err_code (err) == GPG_ERR_LEGACY_KEY)
-        continue;  /* Skip PGP2 keys.  */
       if (err)
         break;
 
@@ -949,8 +1272,6 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
       release_kbnode (keyblock);
       keyblock = NULL;
       err = keydb_get_keyblock (kdbhd, &keyblock);
-      if (gpg_err_code (err) == GPG_ERR_LEGACY_KEY)
-        continue;  /* Skip PGP2 keys.  */
       if (err)
         {
           log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
@@ -1157,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)
@@ -1205,83 +1526,25 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
                 }
               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));
-
-                  {
-                    char *prompt = gpg_format_keydesc (pk,
-                                                       FORMAT_KEYDESC_EXPORT,1);
-                    err = agent_export_key (ctrl, hexgrip, prompt, &cache_nonce,
-                                            &wrappedkey, &wrappedkeylen);
-                    xfree (prompt);
-                  }
+                  err = receive_seckey_from_agent (ctrl, cipherhd,
+                                                   cleartext, &cache_nonce,
+                                                   hexgrip, pk);
                   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;
-
-                  err = build_packet (out, node->pkt);
-                  if (!err && node->pkt->pkttype == PKT_PUBLIC_KEY)
-                    {
-                      stats->exported++;
-                      print_status_exported (node->pkt->pkt.public_key);
-                    }
-                  goto unwraperror_leave;
-
-                unwraperror:
-                  xfree (wrappedkey);
-                  xfree (key);
-                  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
                 {
@@ -1339,3 +1602,301 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
     log_info(_("WARNING: nothing exported\n"));
   return err;
 }
+
+
+
+\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;
+}
+
+/* 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;
+
+      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;
+    }
+
+  /* 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;
+
+          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;
+            }
+        }
+    }
+
+  if (!latest_key)
+    {
+      err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
+      log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err));
+      goto leave;
+    }
+
+  pk = latest_key->pkt->pkt.public_key;
+  if (DBG_LOOKUP)
+    log_debug ("\tusing key %08lX\n", (ulong) keyid_from_pk (pk, NULL));
+
+  switch (pk->pubkey_algo)
+    {
+    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;
+
+    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;
+
+    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:
+      err = GPG_ERR_PUBKEY_ALGO;
+      break;
+    }
+
+  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;
+}