#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <md5.h>
#include <bn.h>
#include <rsa.h>
#include <idea.h>

/* remove memory leaks on error returns */
/*------------------------------------------------------------------------*/
/* pgp key stuff */

/*------------------------------------*/
/* secret key decryption data */
static unsigned char iv[8];
static IDEA_KEY_SCHEDULE iks;
static int ivcnt;

/*------------------------------------*/
/* pgp format to SSLeay bignum */
BIGNUM *pgp2BN(unsigned char **buf, int decrypt)
{
  int bytes;
  BIGNUM *N;

  bytes = (((*buf)[0] * 256 + (*buf)[1]) + 7) / 8;  /* num bytes in mpi */
  *buf += 2;                    /* bypass bytecount */
  if (decrypt)
    idea_cfb64_encrypt(*buf, *buf, bytes, &iks, iv, &ivcnt, IDEA_DECRYPT);
  N = BN_bin2bn(*buf, bytes, NULL);  /* allocates space and convert */
  *buf += bytes;                /* bypass this number */
  return N;
}

/*------------------------------------*/
static FILE *kring = NULL;
FILE *setkeyring(char *file)
{
  if (kring)
    fclose(kring);
  kring = fopen(file, "rb");
  return kring;
}
void setkeyring_fp(FILE * newkr)
{
  kring = newkr;
}

/*------------------------------------*/
/* find key on ring to RSA structure */

/* uses keyid to locate key returned in RSA */
/* if hashpass is non NULL (use "" for no passphrase), gets secret key */
/* if keyid is zero and hashpass is nonNULL returns first secret key on ring */
/* returns 0 if it found the key, something else if an error occurs */
/* will return public keys (only N and E) from secret key rings */
/* Experimental: if match is specified - will return rsakey and/or keyid on */
/* the first key containing the (CASE SENSITIVE) match string in the userid */
/* returns: 0=ok, -1=badpass -2=disabled, -3=no keyring, -4=not found */

int getkey2(RSA ** rsakey, char *match, unsigned char *hashpass,
            unsigned long long *keyid)
{
  unsigned char buf[2048], *bp, t;
  unsigned long i, j, ll;
  int usepass;
  unsigned long long keyfromn;
  BIGNUM *temp;
  BN_CTX *ctx;
  RSA *rsatmp, **testkey;

  testkey = rsakey ? rsakey : &rsatmp;
  /* better yet, lookup "0x%8lX" keyid on a keyserver */
  if (!kring && (bp = getenv("PGPPATH"))) {  /* if no kring, try PGPPATH */
    strncpy(buf, bp, 2000);
    strcat(buf, hashpass ? "/secring.pgp" : "/pubring.pgp");
    kring = fopen(buf, "rb");
  }
  if (!kring && (bp = getenv("HOME"))) {  /* no, try default from HOME */
    strncpy(buf, bp, 2000);
    strcat(buf, hashpass ? "/.pgp/secring.pgp" : "/.pgp/pubring.pgp");
    kring = fopen(buf, "rb");
  }
  if (!kring)
    return -3;                  /* no keyring */
  for (;;) {
    t = fgetc(kring);
    j = (t & 0x7c) >> 2;
    if (j == 5 || j == 6 || j == 2) {
      ll = 1 << (t & 3);        /* length of length */
      fread(&buf[1], ll++, 1, kring);  /* decode length of rest */
      for (j = 1, i = 0; j < ll; j++)
        i = (i << 8) + buf[j];
    } else if (j == 12 || j == 13 || j == 14)
      i = fgetc(kring);
    else
      break;
    fread(buf, i, 1, kring);    /* read the rest of whatever it is */
    if (i < 2000)
      buf[i] = 0;               /* for UIDs and comments */
    bp = buf + 8;
    t = (t & 0x7c) >> 2;
    if (t == 2)                 /* sig */
      continue;
    else if (t == 13) {
      if (!match)
        continue;
      if (strstr(buf, match)) { /* upcase? */
        strcpy(match, buf);
        if (!rsakey)
          RSA_free(rsatmp);
        return 0;
      }
    } else if (t == 12 || t == 14) {  /* trust, comment */
      continue;
    } else if (t == 5 || t == 6) {  /* secret or public key */
      if ((t == 6) && hashpass)
        continue;
      if (buf[0] > 3 || buf[7] != 1)  /* new PGP or not RSA */
        continue;
      /* else tstamp in buf[1..4], validays in [5..6] */
      /* public key section, N and E */
      *testkey = RSA_new();
      (*testkey)->n = pgp2BN(&bp, 0);
      i = 64;
      keyfromn = 0;
      while (i)
        keyfromn = (keyfromn << 1) + BN_is_bit_set((*testkey)->n, --i);
      if (keyid) {
#ifdef TESTMODE
        if (!match && (*keyid != 0) &&  /* get any key if id is zero */
#else
        /* get first secret key if id is zero and not name matching */
        if (!match && !(hashpass && *keyid == 0) &&
#endif
            (*keyid & 0xffffffff) != (keyfromn & 0xffffffff)) {
          BN_free((*testkey)->n);
          continue;
        }
        *keyid = keyfromn;      /* save 64 bit version */
        if (fgetc(kring) == 0xb0 && fgetc(kring) == 1 && fgetc(kring) & 32) {
          BN_free((*testkey)->n);
          return -2;            /* match on disabled key */
        }
        if (!match)
          fseek(kring, 0, SEEK_SET);
      }
      (*testkey)->e = pgp2BN(&bp, 0);
      if (!hashpass) {
        if (match)
          continue;
        else
          return 0;
      }
      /* secret stuff starts here */
      usepass = *bp++;
      if (usepass == 1) {
        memcpy(iv, bp, 8);      /* encryption vector */
        bp += 8;
        idea_set_encrypt_key(hashpass, &iks);
        ivcnt = 0;
      }
      /* decrypt next 4 pgp numbers if needed */
      (*testkey)->d = pgp2BN(&bp, usepass);
      (*testkey)->q = pgp2BN(&bp, usepass);
      (*testkey)->p = pgp2BN(&bp, usepass);
      /* verify key before continuing */
      temp = BN_new();
      BN_mul(temp, (*testkey)->q, (*testkey)->p);
      if (BN_cmp(temp, (*testkey)->n) != 0)
        return -1;              /* error in passphrase */
      (*testkey)->iqmp = pgp2BN(&bp, usepass);
      /* fill in the missing pieces */
      BN_sub(temp, (*testkey)->q, BN_value_one());
      ctx = BN_CTX_new();
      BN_mod(temp, (*testkey)->d, temp, ctx);
      (*testkey)->dmq1 = temp;
      temp = BN_new();
      BN_sub(temp, (*testkey)->p, BN_value_one());
      BN_mod(temp, (*testkey)->d, temp, ctx);
      (*testkey)->dmp1 = temp;
      BN_CTX_free(ctx);
      if (!match)
        return 0;
    }
    /* 1-pke 8-cmprs 9-cke 11-raw */
    else
      break;                    /* chunk that shouldn't be here */
  }
  return -4;                    /* not found */
}
