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

/*--------------------------------------------------*/
int sigmak5(void *privkey, FILE * chkfile, FILE * sigfile,
            unsigned long long keyid, unsigned int stype,
            unsigned int halg, unsigned int salg)
{
  unsigned char dbuf[4096], extramd[256], hash[100], mdck[2];
  unsigned long i, j, k;
  unsigned char hctx[1024];
  BN_CTX *ctx = BN_CTX_new();
  BIGNUM *r = BN_new();
  BIGNUM *t1 = BN_new(), *t2 = BN_new();
  BIGNUM *u1 = BN_new(), *u2 = BN_new();

  /* ADD: check halg is valid */
  hashinit(halg, hctx);
  while (!feof(chkfile))        /* do message digest */
    if ((k = fread(dbuf, 1, 4096, chkfile)))
      hashupdate(halg, hctx, dbuf, k);

  extramd[0] = stype;           /* binary-generic */
  k = time(NULL);               /* timestamp */
  extramd[1] = k >> 24;
  extramd[2] = k >> 16;
  extramd[3] = k >> 8;
  extramd[4] = k;
  hashupdate(halg, hctx, extramd, 5);
  hashfinal(halg, hash, hctx);
  memcpy(mdck, hash, 2);

  if (salg == 0x11) {
    DSA *dsakey = (DSA *) privkey;

    BN_bin2bn(hash, hashlen[halg], t2);

    BN_rand(u2, 160, 1, 0);     /* random */
    /* Compute r = (g^u2 mod p) mod q */
    BN_mod_exp(u1, dsakey->g, u2, dsakey->p, ctx);
    BN_mod(r, u1, dsakey->q, ctx);

    /* Compute  u1 = inv(u2) (t2 + xr) mod q */
    BN_mul(u1, dsakey->priv_key, r);
    BN_add(u1, u1, t2);
    t1 = BN_mod_inverse(u2, dsakey->q, ctx);
    BN_mod_mul(u1, u1, t1, dsakey->q, ctx);
    DSA_free(dsakey);
#ifndef NOELGSIG
  } else if (salg == 0x10) {
    DH *dhkey = (DH *) privkey;

    /* prefix the DER stuff */
    memmove(&hash[hashDERlen[halg]], hash, hashlen[halg]);
    memcpy(hash, hashDER[halg], hashDERlen[halg]);

    memset(dbuf, 0xff, 1024);
    dbuf[0] = 0;
    dbuf[1] = 1;
    k = BN_num_bytes(dhkey->p);
    j = hashlen[halg] + hashDERlen[halg];
    dbuf[k - j - 1] = 0;
    memcpy(&dbuf[k - j], hash, j);
    BN_bin2bn(dbuf, k, t2);

    BN_sub(t1, dhkey->p, BN_value_one());
    j = BN_num_bits(dhkey->p) - 1;
    do {                        /* random u2 */
      BN_rand(u2, j, 1, 1);
      BN_gcd(u1, u2, t1, ctx);
    } while (BN_ucmp(u1, BN_value_one()));
    BN_mod_exp(r, dhkey->g, u2, dhkey->p, ctx);
    BN_mod_mul(u1, dhkey->priv_key, r, t1, ctx);
    DH_free(dhkey);
    if (BN_cmp(t2, u1) < 0)
      BN_add(t2, t2, t1);
    BN_sub(t2, t2, u1);
    BN_mod_mul(u1, t2, BN_mod_inverse(u2, t1, ctx), t1, ctx);
#endif
#ifdef RSAKEY
  } else if (salg == 1 || salg == 3) {
    RSA *rsakey = (RSA *) privkey;
    /* prefix the DER stuff */
    memmove(&hash[hashDERlen[halg]], hash, hashlen[halg]);
    memcpy(hash, hashDER[halg], hashDERlen[halg]);

    j = RSA_private_encrypt(hashDERlen[halg] + hashlen[halg], hash,
                            dbuf, rsakey, RSA_PKCS1_PADDING);
    RSA_free(rsakey);
    while (dbuf[0] == 0)
      memmove(dbuf, &dbuf[1], j--);
    BN_bin2bn(dbuf, j, r);
    BN_set_word(u1, 0);
#endif
  } else
    return -1;

/* output r, u1 as PGP mpi numbers */
  j = BN_num_bits(r);
  i = BN_num_bits(u1);
  k = 23 + (j + 7) / 8 + (i + 7) / 8;
  if (salg == 1 || salg == 3)
    k -= 2;
#ifdef NEWFORMAT
  fputc(0xc2, sigfile);
  fputc(k, sigfile);
#else
  fputc(0x89, sigfile);
  fputc(k >> 8, sigfile);       /* packet length */
  fputc(k, sigfile);
#endif
  fputc(3, sigfile);            /* version */
  fputc(5, sigfile);            /* number of bytes in hash append */
  fwrite(extramd, 1, 5, sigfile);  /* bin/text and timestamp */
  for (k = 0; k < 8; k++)       /* keyid */
    fputc(0xff & (keyid >> (56 - 8 * k)), sigfile);
  fputc(salg, sigfile);         /* sig alg */
  fputc(halg, sigfile);         /* hash alg */
  fputc(mdck[0], sigfile);      /* hash quick check */
  fputc(mdck[1], sigfile);
  fputc(j >> 8, sigfile);
  fputc(j, sigfile);
  BN_bn2bin(r, dbuf);
  fwrite(dbuf, 1, (j + 7) / 8, sigfile);
  if (salg != 1) {
    fputc(i >> 8, sigfile);
    fputc(i, sigfile);
    BN_bn2bin(u1, dbuf);
    fwrite(dbuf, 1, (i + 7) / 8, sigfile);
  }
  BN_CTX_free(ctx), BN_clear_free(r);
  BN_clear_free(u1), BN_clear_free(u2);
  BN_clear_free(t1), BN_clear_free(t2);

  return 0;
}
