#include "libopgp.h"
#include <rsa.h>
#include <dh.h>
#include <dsa.h>

/*------------------------------------*/
/* pgp format to SSLeay bignum */
void *PGP_mpiBN(u8_t ** buf)
{
  int len;
  BIGNUM *N;

  len = *(*buf)++ * 256, len += *(*buf)++, len = (len + 7) / 8;
  N = BN_bin2bn(*buf, len, NULL);  /* allocates space and convert */
  *buf += len;                  /* bypass this number */
  return N;
}

/*------------------------------------*/
/* hash for new keyid - SHA hardcoded */
static keyid_t last_keyid;

keyid_t PGP_gkid(u8_t * buf, int len)
{
  int i;
  void *hctx;
  keyid_t keyid = 0;
  u8_t keypfx[3], newkeyid[20]; /* newkid is the new fingerprint */

  hctx = PGP_hini(2);
  keypfx[0] = 0x99, keypfx[1] = len >> 8, keypfx[2] = len & 0xff;
  PGP_hblk(hctx, keypfx, 3);
  PGP_hblk(hctx, buf, len);
  PGP_hfin(newkeyid, hctx);

  for (i = 0; i < 8; i++) {
    keyid <<= 8;
    keyid |= newkeyid[12 + i];
  }
  last_keyid = keyid;
  return keyid;
}

/*------------------------------------*/
/* unlock the secret key */
#define BSZ 8
static int cksumkey(u8_t * buf, u8_t ** bp,
                    u8_t * pp, long int gaz)
{
  long int itmp, icnt;
  u8_t *cp, save[BSZ];
  void *cfbc;

  if (!(itmp = PGP_gts2k(bp, pp, &cfbc)))
    return 0;
  if (itmp < 0)                 /* encrypted, but no passphrase */
    return 1;
  if (itmp != 0 && itmp != 0xff && itmp != 1)
    return -1;
  cp = *bp;

  icnt = gaz + buf - cp;
  if (itmp == 0xff)
    PGP_cblk(cp, icnt, cfbc);
  else
    while (icnt > 2) {
      itmp = *cp++ * 256, itmp += *cp++, itmp = (itmp + 7) / 8;
      memcpy(save, &cp[itmp - BSZ], BSZ);
      PGP_cblk(cp, itmp, cfbc);
      PGP_cres(save, cfbc);
      cp += itmp;
      icnt -= itmp + 2;
    }
  cp = *bp;

  free(cfbc);

  itmp = 0;
  while (cp != buf + gaz - 2)
    itmp += *cp++;
  itmp -= *cp++ << 8, itmp -= *cp++;

  return (itmp & 0xffff);
}

/*-------------------------------------------------------------*/
FILE *PGP_pkr = NULL, *PGP_skr = NULL;

FILE *PGP_kring(char *file, int which)
{
  FILE *f;
  f = fopen(file, "rb");
  if (!(which & 1)) {
    if (PGP_pkr)
      fclose(PGP_pkr);
    PGP_pkr = f;
  }
  if (!(which & 2)) {
    if (PGP_skr)
      fclose(PGP_skr);
    PGP_skr = f;
  }
  return f;
}

int PGP_gtkey(void **key, u8_t * passph, keyid_t * keyid)
{
  u8_t *buf = NULL, *bp, t;
  u32_t k, gaz;
  keyid_t testid;
  int itmp, alg = 0;
  FILE **ringf;

  ringf = passph ? &PGP_skr : &PGP_pkr;
  if (!*ringf)
    *ringf = fopen(passph ? "secring.skr" : "pubring.pkr", "rb");

  if (!*ringf)
    return -3;                  /* no keyring */
  if (*keyid != NEXT_KEY)       /* look for dupe of last */
    fseek(*ringf, 0, SEEK_SET);
  else
    *keyid = last_keyid;
  last_keyid = *keyid + 1;      /* insure it is not equal to start */
  while (!feof(*ringf)) {
    if (last_keyid == *keyid) {
      fclose(*ringf);
      ringf = 0;
      return alg;
    }
    if (0 > (itmp = fgetc(*ringf)))
      break;
    t = itmp;
    if (buf)
      free(buf);
    buf = PGP_gtpkt(*ringf, t, &gaz);
    if (t < 0xc0)
      t = (t & 0x7c) >> 2;
    else
      t &= 0x1f;
    bp = buf;
    if (t == 2 || t == 12 || t == 13)  /* validity, uid, add match later */
      continue;
    else if (t == 5 || t == 6 || t == 7 || t == 14) {  /* sec or pub key */
      itmp = *bp++;             /* vers */
      /* timestamp */
      k = (u32_t) * bp++ << 24, k += (u32_t) * bp++ << 16,
        k += (u32_t) * bp++ << 8, k += *bp++;
      if (itmp != 4)
        bp += 2;                /* V3 valid days */
      switch (alg = *bp++) {
      case 1:
      case 2:
      case 3:
        {
          RSA *rsatmp = RSA_new();
          BIGNUM *temp;
          BN_CTX *ctx;

          rsatmp->n = PGP_mpiBN(&bp), rsatmp->e = PGP_mpiBN(&bp);
          testid = PGP_gkid(buf, bp - buf);
          if (*keyid != testid) {  /* dont recompute if v4 RSA ID match */
            for (itmp = 63, testid = 0; itmp >= 0; itmp--)
              testid = (testid << 1) + BN_is_bit_set(rsatmp->n, itmp);
            last_keyid = testid;
          }
          if (!*keyid)
            *keyid = testid;
          if (*keyid != testid || !key) {  /* wrong key */
            RSA_free(rsatmp);
            continue;
          }
          *key = rsatmp;
          if ((t != 5 && t != 7) || !passph)
            continue;
          if (cksumkey(buf, &bp, passph, gaz)) {
	    FrmAlert(2000);
	    return -1;
	  }
          rsatmp->d = PGP_mpiBN(&bp);
          rsatmp->q = PGP_mpiBN(&bp), rsatmp->p = PGP_mpiBN(&bp);
          BN_mul(temp = BN_new(), rsatmp->q, rsatmp->p);  /* n=pq? */
          if (BN_cmp(temp, rsatmp->n) != 0) {
            RSA_free(rsatmp);
            BN_free(temp);
            continue;
          }
          rsatmp->iqmp = PGP_mpiBN(&bp);
          /* fill in the missing pieces */
          BN_sub(temp, rsatmp->q, BN_value_one());
          ctx = BN_CTX_new();
          BN_mod(temp, rsatmp->d, temp, ctx);
          rsatmp->dmq1 = temp;
          temp = BN_new();
          BN_sub(temp, rsatmp->p, BN_value_one());
          BN_mod(temp, rsatmp->d, temp, ctx);
          rsatmp->dmp1 = temp;
          BN_CTX_free(ctx);
          continue;
        }
      case 17:
        {
          DSA *dsatmp = DSA_new();

          dsatmp->p = PGP_mpiBN(&bp), dsatmp->q = PGP_mpiBN(&bp);
          dsatmp->g = PGP_mpiBN(&bp), dsatmp->pub_key = PGP_mpiBN(&bp);
          testid = PGP_gkid(buf, bp - buf);

          if (!*keyid)
            *keyid = testid;
          if (*keyid != testid || !key) {  /* wrong key */
            DSA_free(dsatmp);
            continue;
          }
          *key = dsatmp;
          if (t != 5 || !passph)
            continue;
          if (cksumkey(buf, &bp, passph, gaz)) {
	    FrmAlert(2000);
	    return -1;
	  }
          dsatmp->priv_key = PGP_mpiBN(&bp);
          continue;
        }
      case 16:
      case 20:
        {
          DH *dhtmp = DH_new();

          dhtmp->p = PGP_mpiBN(&bp), dhtmp->g = PGP_mpiBN(&bp);
          dhtmp->pub_key = PGP_mpiBN(&bp);
          testid = PGP_gkid(buf, bp - buf);

          if (!*keyid)
            *keyid = testid;
          if (*keyid != testid || !key) {  /* wrong key */
            DH_free(dhtmp);
            continue;
          }
          *key = dhtmp;
          if (t != 7 || !passph)
            continue;
          if (cksumkey(buf, &bp, passph, gaz)) {
	    FrmAlert(2000);
	    return -1;
	  }
          dhtmp->priv_key = PGP_mpiBN(&bp);
          continue;
        }
      default:
        free(buf);
        buf = 0;
        continue;
      }
      free(buf);
      fclose(*ringf);
      ringf = 0;
      return -7;
    }
    /* 1-pke 8-cmprs 9-cke 11-raw */
    else
      continue;                 /* chunk that shouldn't be here */
  }
  free(buf);
  fclose(*ringf);
  ringf = 0;
  return 0;                     /* not found */
}
