#include <stdlib.h>
#include <malloc.h>

#include "libpgp5.h"

/*------------------------------------*/
/* pgp format to SSLeay bignum */
BIGNUM *pgp2BN5(unsigned char **buf)
{
  int len;
  BIGNUM *N;

  len = (((*buf)[0] * 256 + (*buf)[1]) + 7) / 8;  /* num bytes in mpi */
  *buf += 2;                    /* bypass bytecount */
  N = BN_bin2bn(*buf, len, NULL);  /* allocates space and convert */
  *buf += len;                  /* bypass this number */
  return N;
}

/*------------------------------------*/
/* hash for new keyid - SHA hardcoded */
static unsigned char newkeyid[20];
/* these two are used by lookup5 */
unsigned long long nkidul;
unsigned char xbp[3];

void donewkeyid(unsigned char *buf, int len)
{
  int i;
  void *hctx;

  hctx = hashinit(2);
  xbp[0] = 0x99;
  xbp[1] = len >> 8;
  xbp[2] = len & 0xff;

  hashupdate(hctx, xbp, 3);
  hashupdate(hctx, buf, len);
  hashfinal(newkeyid, hctx);

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

/*------------------------------------*/
/* unlock the secret key */
static int cksumkey(unsigned char *buf, unsigned char **bp,
                    unsigned char *pp, int gaz)
{
  int itmp, icnt;
  unsigned char *cp;
  void *cfbc;

  itmp = getcfbkey(bp, pp, &cfbc);

  if (!itmp)
    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)
    docfb(cp, icnt, cfbc);
  else
    while (icnt > 2) {
      itmp = *cp++ * 256, itmp += *cp++, itmp = (itmp + 7) / 8;
      docfb(cp, itmp, 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 *kring = NULL;

FILE *setkeyring5(char *file)
{
  if (kring)
    fclose(kring);
  kring = fopen(file, "rb");
  return kring;
}

void setkeyring5_fp(FILE * newkr)
{
  kring = newkr;
}

int getkey5(DH ** dhkey, DSA ** dsakey, unsigned char *passph,
            unsigned long long *keyid)
{
  unsigned char *buf = NULL, *bp, t;
  unsigned long k;
  int itmp, get, gaz;
  DSA *dsatmp = DSA_new();
  DH *dhtmp = DH_new();
#ifndef NO_RSA
  RSA *rsatmp = RSA_new();
  RSA **rsakey = &rsatmp;
  BIGNUM *temp;
  BN_CTX *ctx;

  if (dhkey == (DH **) dsakey) {
    get = 4,
      rsakey = (RSA **) dsakey;
  } else
#endif
  {
    /* if only one type of key wanted, and *keyid is zero,
       get first secret key of type and return keyid */
    get = 3;
    if (!dsakey)
      get &= 1, dsakey = &dsatmp;
    if (!dhkey)
      get &= 2, dhkey = &dhtmp;
    if (!get)
      return -1;
  }
  if (*keyid)
    get = 0;

  buf = malloc(2000);
  /* set keyring if not already set */
  if (!kring && (bp = getenv("PGPPATH"))) {  /* if no kring, try PGPPATH */
    strncpy(buf, bp, 2000);
    strcat(buf, passph ? "/secring.skr" : "/pubring.pkr");
    kring = fopen(buf, "rb");
  }
  if (!kring && (bp = getenv("HOME"))) {  /* no, try default from HOME */
    strncpy(buf, bp, 2000);
    strcat(buf, passph ? "/.pgp/secring.skr" : "/.pgp/pubring.pkr");
    kring = fopen(buf, "rb");
  }
  free(buf);
  buf = NULL;

  if (!kring)
    return -3;                  /* no keyring */
  if (kring)
    fseek(kring, 0, SEEK_SET);

  nkidul = *keyid + 1;          /* insure it is not equal to start */
  while (!feof(kring)) {
    if (nkidul == *keyid) {
      DSA_free(dsatmp);
      DH_free(dhtmp);
#ifndef NO_RSA
      RSA_free(rsatmp);
#endif
      free(buf);
      return 0;
    }
    if (0 > (itmp = fgetc(kring)))
      break;
    t = itmp;
    if (buf)
      free(buf);
    buf = NULL;
    buf = gazinta(kring, 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 */
      if ((t == 6 || t == 14) && passph)
        continue;
      itmp = *bp++;             /* vers */
      /* timestamp */
      k = *bp++ << 24, k += *bp++ << 16, k += *bp++ << 8, k += *bp++;
      if (itmp != 4)
        bp += 2;                /* V3 valid days */
      switch (*bp++) {
#ifndef NO_RSA
      case 1:
      case 2:
      case 3:
        if (dhkey != (DH **) dsakey)
          continue;
        (*rsakey)->n = pgp2BN5(&bp), (*rsakey)->e = pgp2BN5(&bp);
        donewkeyid(buf, bp - buf);
        if (*keyid != nkidul)   /* dont recompute if v4 RSA ID match */
          for (itmp = 63, nkidul = 0; itmp >= 0; itmp--)
            nkidul = (nkidul << 1) + BN_is_bit_set((*rsakey)->n, itmp);
        if (get == 4 && dhkey == (DH **) dsakey)
          *keyid = nkidul;
        if (*keyid != nkidul)   /* wrong key */
          continue;
        if ((t != 5 && t != 7) || cksumkey(buf, &bp, passph, gaz))
          continue;
        (*rsakey)->d = pgp2BN5(&bp);
        (*rsakey)->q = pgp2BN5(&bp), (*rsakey)->p = pgp2BN5(&bp);
        BN_mul(temp = BN_new(), (*rsakey)->q, (*rsakey)->p);  /* n=pq? */
        if (BN_cmp(temp, (*rsakey)->n) != 0) {
          BN_free(temp);
          continue;
        }
        (*rsakey)->iqmp = pgp2BN5(&bp);
        /* fill in the missing pieces */
        BN_sub(temp, (*rsakey)->q, BN_value_one());
        ctx = BN_CTX_new();
        BN_mod(temp, (*rsakey)->d, temp, ctx);
        (*rsakey)->dmq1 = temp;
        temp = BN_new();
        BN_sub(temp, (*rsakey)->p, BN_value_one());
        BN_mod(temp, (*rsakey)->d, temp, ctx);
        (*rsakey)->dmp1 = temp;
        BN_CTX_free(ctx);
        continue;
#endif
      case 0x11:
        if (dhkey == (DH **) dsakey)
          continue;
        (*dsakey)->p = pgp2BN5(&bp), (*dsakey)->q = pgp2BN5(&bp);
        (*dsakey)->g = pgp2BN5(&bp), (*dsakey)->pub_key = pgp2BN5(&bp);
        donewkeyid(buf, bp - buf);
        if (get == 2 && dhkey != (DH **) dsakey)
          *keyid = nkidul;
        if (*keyid != nkidul)   /* wrong key */
          continue;
        if (t != 5 || cksumkey(buf, &bp, passph, gaz))  /* secret key? */
          continue;
        (*dsakey)->priv_key = pgp2BN5(&bp);
        continue;
      case 0x10:
        if (dhkey == (DH **) dsakey)
          continue;
        (*dhkey)->p = pgp2BN5(&bp), (*dhkey)->g = pgp2BN5(&bp);
        (*dhkey)->pub_key = pgp2BN5(&bp);
        donewkeyid(buf, bp - buf);
        if (get == 1 && dhkey != (DH **) dsakey)
          *keyid = nkidul;
        if (*keyid != nkidul)   /* wrong key */
          continue;
        if (t != 7 || cksumkey(buf, &bp, passph, gaz))  /* secret key? */
          continue;
        (*dhkey)->priv_key = pgp2BN5(&bp);
        continue;
      default:
        continue;
      }
      free(buf);
      return -7;
    }
    /* 1-pke 8-cmprs 9-cke 11-raw */
    else
      break;                    /* chunk that shouldn't be here */
  }
  DSA_free(dsatmp);
  DH_free(dhtmp);
#ifndef NO_RSA
  RSA_free(rsatmp);
#endif
  free(buf);
  return -4;                    /* not found */
}
