#include <stdlib.h>
#include "libpgp5.h"

/*------------------------------------*/
/* pgp format to SSLeay bignum */

BIGNUM *pgp2BN5(unsigned char **buf, unsigned int cryplen)
{
  int len;
  BIGNUM *N;

  if (cryplen)
    docfb(*buf, 2, 0);

  len = (((*buf)[0] * 256 + (*buf)[1]) + 7) / 8;  /* num bytes in mpi */
  *buf += 2;                    /* bypass bytecount */

  if (cryplen) {
    if (len != cryplen - 4)
      return NULL;
    docfb(*buf, len + 2, 0);
  }
  N = BN_bin2bn(*buf, len, NULL);  /* allocates space and convert */
  *buf += len;                  /* bypass this number */

  if (cryplen) {
    unsigned long bncks = 0;

    len += 2;
    while (len)
      bncks += (*buf)[0 - len--];
    bncks -= (*buf)[0] * 256 + (*buf)[1];
    bncks &= 0xffff;
    *buf += 2;
    if (bncks) {
      BN_clear_free(N);
      return NULL;
    }
    /* bncks should be zero if the checksum is good */
  }
  return N;
}

/*------------------------------------*/
/* string to key and initialize conventional encryption */

void getcfbkey(unsigned char **bp, unsigned char *hashpass)
{
  unsigned int i = 0, j, k = 0, ca, ha, sa;
  unsigned char hbuf[256];
  unsigned char hashctx[1024];

  ca = *(*bp)++;                /* crypto type */
  sa = *(*bp)++;                /* salt type */
  ha = *(*bp)++;                /* hash type */

  if (sa & 1 || sa == 4) {
    memcpy(hbuf, *bp, 8);       /* salt */
    memcpy(&hbuf[8], hashpass, strlen(hashpass));
    *bp += 8;
    k = 8 + strlen(hashpass);
    i = k;
  }
  switch (sa) {
  case 4:
    i = *(*bp)++ << 24;         /* postfix - hash size */
    i |= *(*bp)++ << 16;
    i |= *(*bp)++ << 8;
    i |= *(*bp)++;
    break;
  case 3:
    i = *(*bp)++;               /* postfix - hash size */
    j = i >> 4;
    i = (i & 15) + 16;
    i <<= j + 6;
    break;
  case 0:
    memcpy(hbuf, hashpass, strlen(hashpass));
    i = strlen(hashpass);
    k = i;
    break;
  case 1:
    break;
  default:
    exit(-1);
  }

  j = i / k;                    /* loops over whole text */
  i = i % k;                    /* last loop size */

  if (ha < 1 || ha > MAXHASH)
    exit(-1);

  hashinit(ha, hashctx);
  while (j--)
    hashupdate(ha, hashctx, hbuf, k);
  hashupdate(ha, hashctx, hbuf, i);
  hashfinal(ha, hbuf, hashctx);
  memcpy(&hbuf[hashlen[ha]], hbuf, hashlen[ha]);
  memcpy(&hbuf[hashlen[ha] * 2], hbuf, hashlen[ha] * 2);

  cfbinit(hbuf, *bp, ca);
  *bp += 8;
}

/*------------------------------------*/
/* hash for new keyid - SHA hardcoded */
#include <sha.h>

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;
  SHA_CTX SHActx;

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

  SHA1_Update(&SHActx, xbp, 3);
  SHA1_Update(&SHActx, buf, len);
  SHA1_Final(newkeyid, &SHActx);

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

/*-------------------------------------------------------------*/
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 *hashpass,
            unsigned long long *keyid)
{
  unsigned char buf[2048], *bp, t;
  unsigned long i, j, k;
  unsigned int usepass, get;
  DSA *dsatmp = DSA_new();
  DH *dhtmp = DH_new();

  /* 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;

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

  if (kring)
    fseek(kring, 0, SEEK_SET);

  nkidul = *keyid + 1;          /* insure it is not equal to start */
  for (;;) {

    if (nkidul == *keyid) {
      DSA_free(dsatmp);
      DH_free(dhtmp);
      return 0;
    }
    t = fgetc(kring);
    j = (t & 0x7c) >> 2;
    if (j == 5 || j == 6 || j == 2 || j == 14 || j == 7) {
      k = 1 << (t & 3);         /* length of length */
      fread(&buf[1], k++, 1, kring);  /* decode length of rest */
      for (j = 1, i = 0; j < k; j++)
        i = (i << 8) + buf[j];
    } else if (j == 12 || j == 13)
      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 + 6;
    t = (t & 0x7c) >> 2;

    if (t == 2)                 /* sig */
      continue;
    else if (t == 12)           /* validity */
      continue;
    else if (t == 13)           /* uid - should add a match */
      continue;
    else if (t == 5 || t == 6 || t == 7 || t == 14) {  /* sec or pub key */
      if ((t == 6 || t == 14) && hashpass)
        continue;

      if (buf[0] != 4)
        continue;               /* old ring entry */

      k = buf[1] << 24;
      k += buf[2] << 16;
      k += buf[3] << 8;
      k += buf[4];
      if (buf[5] != 0x11 && buf[5] != 0x10)
        continue;               /* buf[5] = 0x11 (DSA) or 0x10 (DH) */
      if (t == 5 || t == 6) {   /* dss */
        (*dsakey)->p = pgp2BN5(&bp, 0);
        (*dsakey)->q = pgp2BN5(&bp, 0);  /* correct length */
        (*dsakey)->g = pgp2BN5(&bp, 0);
        (*dsakey)->pub_key = pgp2BN5(&bp, 0);
        donewkeyid(buf, bp - buf);

        if (get == 2)
          *keyid = nkidul;
        if (t != 5)
          continue;
        /* secret stuff starts here */
        usepass = *bp++;
        if (usepass == 0xff) {
          if (!hashpass)
            continue;
          getcfbkey(&bp, hashpass);
        } else if (usepass != 0)
          return -5;
        (*dsakey)->priv_key = pgp2BN5(&bp, usepass ? i - (bp - buf) : 0);
      } else if (t == 7 || t == 14) {  /* dh priv */
        (*dhkey)->p = pgp2BN5(&bp, 0);
        (*dhkey)->g = pgp2BN5(&bp, 0);
        (*dhkey)->pub_key = pgp2BN5(&bp, 0);
        donewkeyid(buf, bp - buf);

        if (get == 1)
          *keyid = nkidul;
        if (t != 7)
          continue;
        /* secret stuff starts here */
        usepass = *bp++;
        if (usepass == 0xff) {
          if (!hashpass)
            continue;
          getcfbkey(&bp, hashpass);
        } else if (usepass != 0)
          return -5;
        (*dhkey)->priv_key = pgp2BN5(&bp, usepass ? i - (bp - buf) : 0);
      }
      continue;
    }
    /* 1-pke 8-cmprs 9-cke 11-raw */
    else
      break;                    /* chunk that shouldn't be here */
  }
  DSA_free(dsatmp);
  DH_free(dhtmp);
  return -4;                    /* not found */
}
