parse_key (IOBUF inp, int pkttype, unsigned long pktlen,
	   byte * hdr, int hdrlen, PACKET * pkt)
{
  gpg_error_t err = 0;
  int i, version, algorithm;
  unsigned long timestamp, expiredate, max_expiredate;
  int npkey, nskey;
  u32 keyid[2];
  PKT_public_key *pk;

  (void) hdr;

  pk = pkt->pkt.public_key; /* PK has been cleared. */

  version = iobuf_get_noeof (inp);
  pktlen--;
  if (pkttype == PKT_PUBLIC_SUBKEY && version == '#')
    {
      /* Early versions of G10 used the old PGP comments packets;
       * luckily all those comments are started by a hash.  */
      if (list_mode)
	{
	  es_fprintf (listfp, ":rfc1991 comment packet: \"");
	  for (; pktlen; pktlen--)
	    {
	      int c;
	      c = iobuf_get (inp);
              if (c == -1)
                break; /* Ooops: shorter than indicated.  */
	      if (c >= ' ' && c <= 'z')
		es_putc (c, listfp);
	      else
		es_fprintf (listfp, "\\x%02x", c);
	    }
	  es_fprintf (listfp, "\"\n");
	}
      iobuf_skip_rest (inp, pktlen, 0);
      return 0;
    }
  else if (version == 4)
    {
      /* The only supported version.  Use an older gpg
         version (i.e. gpg 1.4) to parse v3 packets.  */
    }
  else if (version == 2 || version == 3)
    {
      if (opt.verbose > 1)
        log_info ("packet(%d) with obsolete version %d\n", pkttype, version);
      if (list_mode)
        es_fprintf (listfp, ":key packet: [obsolete version %d]\n", version);
      pk->version = version;
      err = gpg_error (GPG_ERR_LEGACY_KEY);
      goto leave;
    }
  else
    {
      log_error ("packet(%d) with unknown version %d\n", pkttype, version);
      if (list_mode)
        es_fputs (":key packet: [unknown version]\n", listfp);
      err = gpg_error (GPG_ERR_INV_PACKET);
      goto leave;
    }

  if (pktlen < 11)
    {
      log_error ("packet(%d) too short\n", pkttype);
      if (list_mode)
        es_fputs (":key packet: [too short]\n", listfp);
      err = gpg_error (GPG_ERR_INV_PACKET);
      goto leave;
    }
  else if (pktlen > MAX_KEY_PACKET_LENGTH)
    {
      log_error ("packet(%d) too large\n", pkttype);
      if (list_mode)
        es_fputs (":key packet: [too larget]\n", listfp);
      err = gpg_error (GPG_ERR_INV_PACKET);
      goto leave;
    }

  timestamp = read_32 (inp);
  pktlen -= 4;
  expiredate = 0;		/* have to get it from the selfsignature */
  max_expiredate = 0;
  algorithm = iobuf_get_noeof (inp);
  pktlen--;
  if (list_mode)
    es_fprintf (listfp, ":%s key packet:\n"
                "\tversion %d, algo %d, created %lu, expires %lu\n",
                pkttype == PKT_PUBLIC_KEY ? "public" :
                pkttype == PKT_SECRET_KEY ? "secret" :
                pkttype == PKT_PUBLIC_SUBKEY ? "public sub" :
                pkttype == PKT_SECRET_SUBKEY ? "secret sub" : "??",
                version, algorithm, timestamp, expiredate);

  pk->timestamp = timestamp;
  pk->expiredate = expiredate;
  pk->max_expiredate = max_expiredate;
  pk->hdrbytes = hdrlen;
  pk->version = version;
  pk->flags.primary = (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY);
  pk->pubkey_algo = algorithm;

  nskey = pubkey_get_nskey (algorithm);
  npkey = pubkey_get_npkey (algorithm);
  if (!npkey)
    {
      if (list_mode)
	es_fprintf (listfp, "\tunknown algorithm %d\n", algorithm);
      unknown_pubkey_warning (algorithm);
    }

  if (!npkey)
    {
      /* Unknown algorithm - put data into an opaque MPI.  */
      pk->pkey[0] = gcry_mpi_set_opaque (NULL,
                                         read_rest (inp, pktlen), pktlen * 8);
      pktlen = 0;
      goto leave;
    }
  else
    {
      for (i = 0; i < npkey; i++)
        {
          if (    (algorithm == PUBKEY_ALGO_ECDSA && (i == 0))
               || (algorithm == PUBKEY_ALGO_EDDSA && (i == 0))
               || (algorithm == PUBKEY_ALGO_ECDH  && (i == 0 || i == 2)))
            {
              /* Read the OID (i==1) or the KDF params (i==2).  */
              size_t n;
	      err = read_size_body (inp, pktlen, &n, pk->pkey+i);
              pktlen -= n;
            }
          else
            {
              unsigned int n = pktlen;
              pk->pkey[i] = mpi_read (inp, &n, 0);
              pktlen -= n;
              if (!pk->pkey[i])
                err = gpg_error (GPG_ERR_INV_PACKET);
            }
          if (err)
            goto leave;
          if (list_mode)
            {
              es_fprintf (listfp, "\tpkey[%d]: ", i);
              mpi_print (listfp, pk->pkey[i], mpi_print_mode);
              if ((algorithm == PUBKEY_ALGO_ECDSA
                   || algorithm == PUBKEY_ALGO_EDDSA
                   || algorithm == PUBKEY_ALGO_ECDH) && i==0)
                {
                  char *curve = openpgp_oid_to_str (pk->pkey[0]);
                  es_fprintf (listfp, " %s (%s)",
                              openpgp_oid_to_curve (curve), curve);
                  xfree (curve);
                }
              es_putc ('\n', listfp);
            }
        }
    }
  if (list_mode)
    keyid_from_pk (pk, keyid);

  if (pkttype == PKT_SECRET_KEY || pkttype == PKT_SECRET_SUBKEY)
    {
      struct seckey_info *ski;
      byte temp[16];
      size_t snlen = 0;

      pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
      if (!pk->seckey_info)
        {
          err = gpg_error_from_syserror ();
          goto leave;
        }

      ski->algo = iobuf_get_noeof (inp);
      pktlen--;
      if (ski->algo)
	{
	  ski->is_protected = 1;
	  ski->s2k.count = 0;
	  if (ski->algo == 254 || ski->algo == 255)
	    {
	      if (pktlen < 3)
		{
		  err = gpg_error (GPG_ERR_INV_PACKET);
		  goto leave;
		}
	      ski->sha1chk = (ski->algo == 254);
	      ski->algo = iobuf_get_noeof (inp);
	      pktlen--;
	      /* Note that a ski->algo > 110 is illegal, but I'm not
	         erroring on it here as otherwise there would be no
	         way to delete such a key.  */
	      ski->s2k.mode = iobuf_get_noeof (inp);
	      pktlen--;
	      ski->s2k.hash_algo = iobuf_get_noeof (inp);
	      pktlen--;
	      /* Check for the special GNU extension.  */
	      if (ski->s2k.mode == 101)
		{
		  for (i = 0; i < 4 && pktlen; i++, pktlen--)
		    temp[i] = iobuf_get_noeof (inp);
		  if (i < 4 || memcmp (temp, "GNU", 3))
		    {
		      if (list_mode)
			es_fprintf (listfp, "\tunknown S2K %d\n",
                                    ski->s2k.mode);
		      err = gpg_error (GPG_ERR_INV_PACKET);
		      goto leave;
		    }
		  /* Here we know that it is a GNU extension.  What
		   * follows is the GNU protection mode: All values
		   * have special meanings and they are mapped to MODE
		   * with a base of 1000.  */
		  ski->s2k.mode = 1000 + temp[3];
		}

              /* Read the salt.  */
	      switch (ski->s2k.mode)
		{
		case 1:
		case 3:
		  for (i = 0; i < 8 && pktlen; i++, pktlen--)
		    temp[i] = iobuf_get_noeof (inp);
		  memcpy (ski->s2k.salt, temp, 8);
		  break;
		}

              /* Check the mode.  */
	      switch (ski->s2k.mode)
		{
		case 0:
		  if (list_mode)
		    es_fprintf (listfp, "\tsimple S2K");
		  break;
		case 1:
		  if (list_mode)
		    es_fprintf (listfp, "\tsalted S2K");
		  break;
		case 3:
		  if (list_mode)
		    es_fprintf (listfp, "\titer+salt S2K");
		  break;
		case 1001:
		  if (list_mode)
		    es_fprintf (listfp, "\tgnu-dummy S2K");
		  break;
		case 1002:
		  if (list_mode)
		    es_fprintf (listfp, "\tgnu-divert-to-card S2K");
		  break;
		default:
		  if (list_mode)
		    es_fprintf (listfp, "\tunknown %sS2K %d\n",
                                ski->s2k.mode < 1000 ? "" : "GNU ",
                                ski->s2k.mode);
		  err = gpg_error (GPG_ERR_INV_PACKET);
		  goto leave;
		}

              /* Print some info.  */
	      if (list_mode)
		{
		  es_fprintf (listfp, ", algo: %d,%s hash: %d",
                              ski->algo,
                              ski->sha1chk ? " SHA1 protection,"
                              : " simple checksum,", ski->s2k.hash_algo);
		  if (ski->s2k.mode == 1 || ski->s2k.mode == 3)
		    {
		      es_fprintf (listfp, ", salt: ");
                      es_write_hexstring (listfp, ski->s2k.salt, 8, 0, NULL);
		    }
		  es_putc ('\n', listfp);
		}

              /* Read remaining protection parameters.  */
	      if (ski->s2k.mode == 3)
		{
		  if (pktlen < 1)
		    {
		      err = gpg_error (GPG_ERR_INV_PACKET);
		      goto leave;
		    }
		  ski->s2k.count = iobuf_get (inp);
		  pktlen--;
		  if (list_mode)
		    es_fprintf (listfp, "\tprotect count: %lu (%lu)\n",
                                (ulong)S2K_DECODE_COUNT ((ulong)ski->s2k.count),
                                (ulong) ski->s2k.count);
		}
	      else if (ski->s2k.mode == 1002)
		{
		  /* Read the serial number. */
		  if (pktlen < 1)
		    {
		      err = gpg_error (GPG_ERR_INV_PACKET);
		      goto leave;
		    }
		  snlen = iobuf_get (inp);
		  pktlen--;
		  if (pktlen < snlen || snlen == (size_t)(-1))
		    {
		      err = gpg_error (GPG_ERR_INV_PACKET);
		      goto leave;
		    }
		}
	    }
	  else /* Old version; no S2K, so we set mode to 0, hash MD5.  */
	    {
              /* Note that a ski->algo > 110 is illegal, but I'm not
                 erroring on it here as otherwise there would be no
                 way to delete such a key.  */
	      ski->s2k.mode = 0;
	      ski->s2k.hash_algo = DIGEST_ALGO_MD5;
	      if (list_mode)
		es_fprintf (listfp, "\tprotect algo: %d  (hash algo: %d)\n",
                            ski->algo, ski->s2k.hash_algo);
	    }

	  /* It is really ugly that we don't know the size
	   * of the IV here in cases we are not aware of the algorithm.
	   * so a
	   *   ski->ivlen = cipher_get_blocksize (ski->algo);
	   * won't work.  The only solution I see is to hardwire it.
	   * NOTE: if you change the ivlen above 16, don't forget to
	   * enlarge temp.  */
	  ski->ivlen = openpgp_cipher_blocklen (ski->algo);
	  assert (ski->ivlen <= sizeof (temp));

	  if (ski->s2k.mode == 1001)
	    ski->ivlen = 0;
	  else if (ski->s2k.mode == 1002)
	    ski->ivlen = snlen < 16 ? snlen : 16;

	  if (pktlen < ski->ivlen)
	    {
              err = gpg_error (GPG_ERR_INV_PACKET);
	      goto leave;
	    }
	  for (i = 0; i < ski->ivlen && pktlen; i++, pktlen--)
	    temp[i] = iobuf_get_noeof (inp);
	  if (list_mode)
	    {
	      es_fprintf (listfp,
                          ski->s2k.mode == 1002 ? "\tserial-number: "
                          : "\tprotect IV: ");
	      for (i = 0; i < ski->ivlen; i++)
		es_fprintf (listfp, " %02x", temp[i]);
	      es_putc ('\n', listfp);
	    }
	  memcpy (ski->iv, temp, ski->ivlen);
	}

      /* It does not make sense to read it into secure memory.
       * If the user is so careless, not to protect his secret key,
       * we can assume, that he operates an open system :=(.
       * So we put the key into secure memory when we unprotect it. */
      if (ski->s2k.mode == 1001 || ski->s2k.mode == 1002)
	{
	  /* Better set some dummy stuff here.  */
	  pk->pkey[npkey] = gcry_mpi_set_opaque (NULL,
						 xstrdup ("dummydata"),
						 10 * 8);
	  pktlen = 0;
	}
      else if (ski->is_protected)
	{
	  /* Ugly: The length is encrypted too, so we read all stuff
	   * up to the end of the packet into the first SKEY
	   * element.  */
	  pk->pkey[npkey] = gcry_mpi_set_opaque (NULL,
						 read_rest (inp, pktlen),
						 pktlen * 8);
          /* Mark that MPI as protected - we need this information for
             importing a key.  The OPAQUE flag can't be used because
             we also store public EdDSA values in opaque MPIs.  */
          if (pk->pkey[npkey])
            gcry_mpi_set_flag (pk->pkey[npkey], GCRYMPI_FLAG_USER1);
	  pktlen = 0;
	  if (list_mode)
            es_fprintf (listfp, "\tskey[%d]: [v4 protected]\n", npkey);
	}
      else
	{
          /* Not encrypted.  */
	  for (i = npkey; i < nskey; i++)
	    {
              unsigned int n = pktlen;
              pk->pkey[i] = mpi_read (inp, &n, 0);
              pktlen -= n;
              if (list_mode)
                {
                  es_fprintf (listfp, "\tskey[%d]: ", i);
                  mpi_print (listfp, pk->pkey[i], mpi_print_mode);
                  es_putc ('\n', listfp);
                }

	      if (!pk->pkey[i])
		err = gpg_error (GPG_ERR_INV_PACKET);
	    }
	  if (err)
	    goto leave;

	  ski->csum = read_16 (inp);
	  pktlen -= 2;
	  if (list_mode)
            es_fprintf (listfp, "\tchecksum: %04hx\n", ski->csum);
	}
    }

  if (list_mode)
    es_fprintf (listfp, "\tkeyid: %08lX%08lX\n",
                (ulong) keyid[0], (ulong) keyid[1]);

 leave:
  iobuf_skip_rest (inp, pktlen, 0);
  return err;
}