get_public_key (app_t app, int keyno)
{
  gpg_error_t err = 0;
  unsigned char *buffer;
  const unsigned char *keydata, *m, *e;
  size_t buflen, keydatalen;
  size_t mlen = 0;
  size_t elen = 0;
  unsigned char *mbuf = NULL;
  unsigned char *ebuf = NULL;
  char *keybuf = NULL;
  gcry_sexp_t s_pkey;
  size_t len;

  if (keyno < 1 || keyno > 3)
    return gpg_error (GPG_ERR_INV_ID);
  keyno--;

  /* Already cached? */
  if (app->app_local->pk[keyno].read_done)
    return 0;

  xfree (app->app_local->pk[keyno].key);
  app->app_local->pk[keyno].key = NULL;
  app->app_local->pk[keyno].keylen = 0;

  m = e = NULL; /* (avoid cc warning) */

  if (app->card_version > 0x0100)
    {
      int exmode, le_value;

      /* We may simply read the public key out of these cards.  */
      if (app->app_local->cardcap.ext_lc_le)
        {
          exmode = 1;    /* Use extended length.  */
          le_value = app->app_local->extcap.max_rsp_data;
        }
      else
        {
          exmode = 0;
          le_value = 256; /* Use legacy value. */
        }

      err = iso7816_read_public_key
        (app->slot, exmode,
         (const unsigned char*)(keyno == 0? "\xB6" :
                                keyno == 1? "\xB8" : "\xA4"), 2,
         le_value,
         &buffer, &buflen);
      if (err)
        {
          log_error (_("reading public key failed: %s\n"), gpg_strerror (err));
          goto leave;
        }

      keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen);
      if (!keydata)
        {
          err = gpg_error (GPG_ERR_CARD);
          log_error (_("response does not contain the public key data\n"));
          goto leave;
        }

      if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
        {
          m = find_tlv (keydata, keydatalen, 0x0081, &mlen);
          if (!m)
            {
              err = gpg_error (GPG_ERR_CARD);
              log_error (_("response does not contain the RSA modulus\n"));
              goto leave;
            }

          e = find_tlv (keydata, keydatalen, 0x0082, &elen);
          if (!e)
            {
              err = gpg_error (GPG_ERR_CARD);
              log_error (_("response does not contain the RSA public exponent\n"));
              goto leave;
            }
        }
      else
        {
          m = find_tlv (keydata, keydatalen, 0x0086, &mlen);
          if (!m)
            {
              err = gpg_error (GPG_ERR_CARD);
              log_error (_("response does not contain the EC public point\n"));
              goto leave;
            }
        }
    }
  else
    {
      /* Due to a design problem in v1.0 cards we can't get the public
         key out of these cards without doing a verify on CHV3.
         Clearly that is not an option and thus we try to locate the
         key using an external helper.

	 The helper we use here is gpg itself, which should know about
	 the key in any case.  */

      char fpr[41];
      char *hexkeyid;
      char *command = NULL;
      FILE *fp;
      int ret;

      buffer = NULL; /* We don't need buffer.  */

      err = retrieve_fpr_from_card (app, keyno, fpr);
      if (err)
	{
	  log_error ("error while retrieving fpr from card: %s\n",
		     gpg_strerror (err));
	  goto leave;
	}
      hexkeyid = fpr + 24;

      ret = gpgrt_asprintf
        (&command, "gpg --list-keys --with-colons --with-key-data '%s'", fpr);
      if (ret < 0)
	{
	  err = gpg_error_from_syserror ();
	  goto leave;
	}

      fp = popen (command, "r");
      xfree (command);
      if (!fp)
	{
	  err = gpg_error_from_syserror ();
	  log_error ("running gpg failed: %s\n", gpg_strerror (err));
	  goto leave;
	}

      err = retrieve_key_material (fp, hexkeyid, &m, &mlen, &e, &elen);
      pclose (fp);
      if (err)
	{
	  log_error ("error while retrieving key material through pipe: %s\n",
                     gpg_strerror (err));
	  goto leave;
	}
    }


  mbuf = xtrymalloc ( mlen + 1);
  if (!mbuf)
    {
      err = gpg_error_from_syserror ();
      goto leave;
    }
  /* Prepend numbers with a 0 if needed.  */
  if (app->app_local->keyattr[keyno].key_type != KEY_TYPE_EDDSA
      && mlen && (*m & 0x80))
    {
      *mbuf = 0;
      memcpy (mbuf+1, m, mlen);
      mlen++;
    }
  else
    memcpy (mbuf, m, mlen);

  ebuf = xtrymalloc ( elen + 1);
  if (!ebuf)
    {
      err = gpg_error_from_syserror ();
      goto leave;
    }
  /* Prepend numbers with a 0 if needed.  */
  if (elen && (*e & 0x80))
    {
      *ebuf = 0;
      memcpy (ebuf+1, e, elen);
      elen++;
    }
  else
    memcpy (ebuf, e, elen);

  if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
    {
      err = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%b)(e%b)))",
                             mlen, mbuf, elen, ebuf);
      if (err)
        goto leave;

      len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
      keybuf = xtrymalloc (len);
      if (!keybuf)
        {
          gcry_sexp_release (s_pkey);
          err = gpg_error_from_syserror ();
          goto leave;
        }
      gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len);
      gcry_sexp_release (s_pkey);
    }
  else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECC)
    {
      const char *curve_name
        = get_curve_name (app->app_local->keyattr[keyno].ecc.curve);

      err = gcry_sexp_build (&s_pkey, NULL,
                             "(public-key(ecc(curve%s)(q%b)))",
                             curve_name, mlen, mbuf);
      if (err)
        goto leave;

      len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);

      keybuf = xtrymalloc (len);
      if (!keybuf)
        {
          gcry_sexp_release (s_pkey);
          err = gpg_error_from_syserror ();
          goto leave;
        }
      gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len);
      gcry_sexp_release (s_pkey);
    }
  else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_EDDSA)
    {
      const char *curve_name
        = get_curve_name (app->app_local->keyattr[keyno].eddsa.curve);

      err = gcry_sexp_build (&s_pkey, NULL,
                             "(public-key(ecc(curve%s)(flags eddsa)(q%b)))",
                             curve_name, mlen, mbuf);
      if (err)
        goto leave;

      len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);

      keybuf = xtrymalloc (len);
      if (!keybuf)
        {
          gcry_sexp_release (s_pkey);
          err = gpg_error_from_syserror ();
          goto leave;
        }
      gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len);
      gcry_sexp_release (s_pkey);
    }
  else
    {
      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
      goto leave;
    }

  app->app_local->pk[keyno].key = (unsigned char*)keybuf;
  app->app_local->pk[keyno].keylen = len - 1; /* Decrement for trailing '\0' */

 leave:
  /* Set a flag to indicate that we tried to read the key.  */
  app->app_local->pk[keyno].read_done = 1;

  xfree (buffer);
  xfree (mbuf);
  xfree (ebuf);
  return 0;
}