#include <time.h>
#include "libopgp.h"
#include <rsa.h>
#include <dh.h>
#include <dsa.h>

#define BSZ 8
/*-------------------------*/
static int write_BN(u8_t ** sp, BIGNUM * bnptr)
{
  int i;

  i = BN_num_bits(bnptr);
  *(*sp)++ = i >> 8, *(*sp)++ = i;
  i = BN_bn2bin(bnptr, *sp);
  *sp += i;
  return i + 2;
}

/*------------------------------------------------------------------------*/
int PGP_wrkey(u8_t * buf, void *key, int prim, int alg, u8_t * passph,
              char *userid)
{
  u8_t *pp, *cp, *icp, keyctb[] =
  {0xb9, 0x9d, 0x99, 0x95};
  u32_t i, len, v3flg, sec;

  sec = (passph != NULL);
  v3flg = alg >> 8;
  alg &= 0xff;
  pp = buf;
  *pp++ = keyctb[prim * 2 + sec];
  cp = pp;
  pp += 2;
  *pp++ = v3flg ? 3 : 4;
  i = time(NULL);               /* timestamp */
  *pp++ = i >> 24, *pp++ = i >> 16, *pp++ = i >> 8, *pp++ = i;
  len = 6;
  if (v3flg)
    *pp++ = 0, *pp++ = 0, len += 2;
  *pp++ = alg;
  switch (alg) {
  case 1:
    len += write_BN(&pp, ((RSA *) key)->n);
    len += write_BN(&pp, ((RSA *) key)->e);
    break;
  case 17:
    len += write_BN(&pp, ((DSA *) key)->p);
    len += write_BN(&pp, ((DSA *) key)->q);
    len += write_BN(&pp, ((DSA *) key)->g);
    len += write_BN(&pp, ((DSA *) key)->pub_key);
    break;
  case 16:
  case 20:
    len += write_BN(&pp, ((DH *) key)->p);
    len += write_BN(&pp, ((DH *) key)->g);
    len += write_BN(&pp, ((DH *) key)->pub_key);
    break;
  }
  if (sec) {
    void *cfbc;
    if (v3flg)
      pp[0] = pp[1] = 1, pp[2] = 0, pp[3] = 1;
    else
      pp[0] = 0xff, pp[1] = 0;  /* auto */
    i = PGP_wrs2k(pp, passph, &cfbc);
    pp += i, len += i;
    cp = pp;                    /* mark start of crypt/checksum */
    switch (alg) {
    case 1:
      len += write_BN(&pp, ((RSA *) key)->d);
      len += write_BN(&pp, ((RSA *) key)->q);
      len += write_BN(&pp, ((RSA *) key)->p);
      len += write_BN(&pp, ((RSA *) key)->iqmp);
      RSA_free(((RSA *) key));
      break;
    case 17:
      len += write_BN(&pp, ((DSA *) key)->priv_key);
      DSA_free(((DSA *) key));
      break;
    case 16:
      len += write_BN(&pp, ((DH *) key)->priv_key);
      DH_free((DH *) key);
      break;
    }
    /* compute checksum */
    icp = cp, i = 0;
    while (icp != pp)
      i += *icp++;
    *pp++ = i >> 8, *pp++ = i, len += 2;
    if (cfbc) {                 /* passphrase encrypt secret portions of key */
      if (v3flg)
        while (cp != pp - 2) {
          i = *cp++ * 256, i += *cp++, i = (i + 7) / 8;
          PGP_cblk(cp, i, cfbc), PGP_cres(&cp[i - BSZ], cfbc);
          cp += i;
      } else
        PGP_cblk(cp, pp - cp, cfbc);
      free(cfbc);
    }
  }
  buf[1] = len >> 8, buf[2] = len;
  if (!sec)
    *pp++ = 0xb0, *pp++ = 0x01, *pp++ = 0x87;
  if (prim && userid) {
    *pp++ = 0xb4, *pp++ = strlen(userid);
    memcpy(pp, userid, strlen(userid));
    pp += strlen(userid);
    if (!sec && userid)
      *pp++ = 0xb0, *pp++ = 0x01, *pp++ = 0x03;
  }
  return (pp - buf);
}
