#include <stdio.h>
#include <string.h>

#include "libpgp5.h"

/*--------------------------------------------------*/
/* do DSA verify function */

int checkdsa(BIGNUM * r, BIGNUM * u1, DSA * dsakey,
             unsigned char *hash, unsigned int hashlen)
{
  BN_CTX *ctx = BN_CTX_new();
  BIGNUM *t1 = BN_new(), *t2 = BN_new(), *u2 = NULL;
  int ret;

  u2 = BN_mod_inverse(u1, dsakey->q, ctx);
  BN_bin2bn(hash, hashlen, u1);
  BN_mod_mul(u1, u1, u2, dsakey->q, ctx);
  BN_mod_mul(u2, r, u2, dsakey->q, ctx);
  BN_mod_exp(t1, dsakey->g, u1, dsakey->p, ctx);
  BN_mod_exp(t2, dsakey->pub_key, u2, dsakey->p, ctx);
  BN_mod_mul(u1, t1, t2, dsakey->p, ctx);
  BN_mod(u1, u1, dsakey->q, ctx);
  DSA_free(dsakey);

  ret = BN_ucmp(u1, r);
  BN_CTX_free(ctx), BN_clear_free(u2), BN_clear_free(t1);
  BN_clear_free(t2), BN_clear_free(u1), BN_clear_free(r);
  return ret;
}

#ifndef NOELGSIG
/*--------------------------------------------------*/
/* do ElG verify function */

int checkelg(BIGNUM * a, BIGNUM * b, DH * dhkey,
             unsigned char *hash, unsigned int hashlen)
{
  BN_CTX *ctx = BN_CTX_new();
  BIGNUM *t = BN_new(), *u = BN_new();
  unsigned char hbuf[1024];
  unsigned int k;

  BN_sub(t, dhkey->p, BN_value_one());
  BN_gcd(u, a, t, ctx);
  BN_set_word(t, 2);
  if (BN_ucmp(u, t) > 0) {
    BN_print_fp(stderr, u);
    fprintf(stderr, " = gcd(p-1,r)\n");
    fprintf(stderr, "WARNING: Weak El Gamal Signature\n");
    exit(-1);
  }
  memset(hbuf, 0xff, 1024);
  hbuf[0] = 0;
  hbuf[1] = 1;
  k = BN_num_bytes(dhkey->p);
  hbuf[k - hashlen - 1] = 0;
  memcpy(&hbuf[k - hashlen], hash, hashlen);
  BN_bin2bn(hbuf, k, u);

  BN_mod_exp(t, dhkey->g, u, dhkey->p, ctx);

  BN_mod_exp(u, a, b, dhkey->p, ctx);
  BN_mod_exp(b, dhkey->pub_key, a, dhkey->p, ctx);
  BN_mod_mul(a, b, u, dhkey->p, ctx);
  DH_free(dhkey);

  k = BN_ucmp(t, a);
  BN_CTX_free(ctx), BN_clear_free(u), BN_clear_free(t);
  BN_clear_free(a), BN_clear_free(b);
  return k;
}
#endif

#ifdef RSAKEY
/*--------------------------------------------------*/
/* do RSA verify function */

int checkrsa(BIGNUM * a, RSA * key, unsigned char *hash, unsigned int hashlen)
{
  unsigned char dbuf[1024];
  int j, ll;

  j = BN_bn2bin(a, dbuf);
  ll = BN_num_bytes(key->n);
  while (j < ll)
    memmove(&dbuf[1], dbuf, j++), dbuf[0] = 0;
  j = RSA_public_decrypt(ll, dbuf, dbuf, key, RSA_PKCS1_PADDING);
  RSA_free(key);
  BN_clear_free(a);
  return (j != hashlen || memcmp(dbuf, hash, j));
}
#endif

/*--------------------------------------------------*/
int sigchk5x(unsigned char *dbuf, int sigalg, char *hash, int halg,
             unsigned long long keyid)
{
  int k;
  unsigned char *bp;

  BIGNUM *r, *u1 = NULL;
  DSA *dsakey;
  DH *dhkey;

#ifdef RSAKEY
  RSA *rsakey;

#endif

  if (sigalg != 0x11 && sigalg != 0x10 && sigalg != 1 && sigalg != 3)
    return -3;
  if (sigalg == 0x11)
    dsakey = DSA_new(),
      k = getkey5(NULL, &dsakey, NULL, &keyid);
#ifndef NOELGSIG
  else if (sigalg == 0x10)
    dhkey = DH_new(),
      k = getkey5(&dhkey, NULL, NULL, &keyid);
#endif
#ifdef RSAKEY
  else if (sigalg == 1 || sigalg == 3)
    rsakey = RSA_new(),
      k = getkey2(&rsakey, NULL, NULL, &keyid);
#endif
  else
    return -3;
  if (k)
    return -5;

  bp = dbuf;

  r = pgp2BN5(&bp, 0);
  if (sigalg != 1 && sigalg != 3)
    u1 = pgp2BN5(&bp, 0);
  if (sigalg == 0x11)
    k = checkdsa(r, u1, dsakey, hash, hashlen[halg]);
  else {
    /* prefix the DER stuff */
    memmove(&hash[hashDERlen[halg]], hash, hashlen[halg]);
    memcpy(hash, hashDER[halg], hashDERlen[halg]);
#ifndef NOELGSIG
    if (sigalg == 0x10)
      k = checkelg(r, u1, dhkey, hash, hashlen[halg] + hashDERlen[halg]);
#endif
#ifdef RSAKEY
    if (sigalg == 1 || sigalg == 3)
      k = checkrsa(r, rsakey, hash, hashlen[halg] + hashDERlen[halg]);
#endif
  }
  return k;
}

/*--------------------------------------------------*/
int sigchk5(FILE * chkfile, FILE * sigfile)
{
  unsigned char dbuf[4096], tstp[4], mdck[2], hash[100];
  unsigned char hashctx[1024], xmat[1024];
  unsigned long long keyid = 0LL;
  unsigned int scf, i, j, halg = 0, xmatlen = 0, sigalg = 0x11;
  int k;

  j = fgetc(sigfile);
  if ((j & 0xfc) != 0x88) {
    ungetc(j, sigfile);
    return -1;
  }
  k = 1 << (j & 3);             /* length of length */
  fread(dbuf, 1, k, sigfile);
  for (j = 0, i = 0; j < k; j++)
    i = (i << 8) + dbuf[j];

  j = fgetc(sigfile);
  if (j == 3) {
    if (fgetc(sigfile) != 5)    /* extramd length - fixed on old ver */
      return -3;
    scf = fgetc(sigfile);
    xmat[0] = scf;
    fread(tstp, 1, 4, sigfile);
    memcpy(&xmat[1], tstp, 4);
    fread(dbuf, 1, 8, sigfile); /* keyid */
    for (j = 0; j < 8; j++)
      keyid = (keyid << 8) + dbuf[j];
    sigalg = fgetc(sigfile);
    xmatlen = 5;
    halg = fgetc(sigfile);      /* hash algorithm */
    i -= 19;
  } else if (j == 4) {
    fread(xmat, 1, 5, sigfile);
    scf = xmat[0];
    sigalg = xmat[1];
    halg = xmat[2];
    j = xmat[3] * 256 + xmat[4];
    fread(&xmat[5], 1, j, sigfile);
    /* { chunklen / subpacket type 2-sigtime,
       9-keyexptime, 11-prefconv / sp data } */
    xmatlen = 5 + j;

    fread(dbuf, 1, 2, sigfile);
    j = dbuf[0] * 256 + dbuf[1];
    fread(dbuf, 1, j, sigfile);
    /* dbuf[0] = chunk len = 9 */
    /* dbuf[1] & 0x7f = sp type = 0x10 - keyid */
    for (j = 2; j < 10; j++)
      keyid = (keyid << 8) + dbuf[j];
    fread(mdck, 1, 2, sigfile);
    return -4;
  }
  /* ADD: precheck ha is valid */
  /* this is above the key check to commonize */
  hashinit(halg, hashctx);
  while (!feof(chkfile))        /* do message digest */
    if ((k = fread(dbuf, 1, 4096, chkfile)))
      hashupdate(halg, hashctx, dbuf, k);
  hashupdate(halg, hashctx, xmat, xmatlen);
  hashfinal(halg, hash, hashctx);

  fread(mdck, 1, 2, sigfile);   /* quick first two bytes of hash check */
  if (mdck[0] != hash[0] || mdck[1] != hash[1])
    return -1;
  fread(dbuf, 1, i, sigfile);

  return sigchk5x(dbuf, sigalg, hash, halg, keyid);
}
