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

/* algorithms to password protect secret keys */
/* note: salt type is fixed at 3 */
int sechash = 2;
int secsalt = 3;
int seccrypt = 3;

/*------------------------------------*/
/* for secret keys only - inverse of getcfbkey */
int putcfbkey(FILE * keyout, unsigned char *hashpass)
{
  unsigned int i = 0, j, k, ha, len;
  unsigned char hbuf[256], hctx[1024], ivbuf[8];

  if (!hashpass) {
    fputc(0, keyout);           /* not encrypted */
    return 1;
  }
  fputc(0xff, keyout);
  fputc(seccrypt, keyout);      /* crypto alg */
  fputc(secsalt, keyout);       /* salt type */
  fputc(sechash, keyout);       /* hash type */
  len = 12;                     /* mandatory bytes */

  k = strlen(hashpass);
  if (secsalt && secsalt != 2) {
    RAND_bytes(hbuf, 8);        /* salt */
    fwrite(hbuf, 1, 8, keyout);
    memcpy(&hbuf[8], hashpass, strlen(hashpass));
    k += 8;
    len += 8;
  } else
    memcpy(hbuf, hashpass, strlen(hashpass));

  switch (secsalt) {
  case 4:
    i = 32768;
    fputc(i >> 24, keyout);
    fputc(i >> 16, keyout);
    fputc(i >> 8, keyout);
    fputc(i, keyout);
    len += 4;
    break;
  case 3:
    i = 0x60;                   /* iteration count */
    fputc(i, keyout);
    len++;
    i = (16 + (i & 15)) << ((i >> 4) + 6);
    break;
  case 2:
    exit(-1);
  case 1:
    i = 1;
    break;
  case 0:
    i = 0;
    break;
  }

  j = i / k;
  i = i % k;
  ha = sechash;
  hashinit(ha, hctx);
  while (j--)
    hashupdate(ha, hctx, hbuf, k);
  hashupdate(ha, hctx, hbuf, i);
  hashfinal(ha, hbuf, hctx);

  RAND_bytes(ivbuf, 8);
  fwrite(ivbuf, 1, 8, keyout);

  cfbinit(hbuf, ivbuf, seccrypt);
  return 21;                    /* LEN - currently fixed */
}

/*-------------------------*/
int write_BN(FILE * secout, FILE * pubout,
             BIGNUM * bnptr, unsigned char *hshps)
{
  int i, j, cks;
  unsigned char dbuf[4096];

  i = BN_num_bits(bnptr);
  dbuf[0] = i >> 8;
  dbuf[1] = i;
  BN_bn2bin(bnptr, &dbuf[2]);
  j = 2 + (i + 7) / 8;

  if (pubout)
    fwrite(dbuf, 1, j, pubout);
  else {
    for (cks = 0, i = 0; i < j; i++)
      cks += dbuf[i];
    dbuf[j] = cks >> 8;
    dbuf[j + 1] = cks;
    j += 2;
    if (hshps)
      docfb(dbuf, j, 1);
  }
  if (secout)
    fwrite(dbuf, 1, j, secout);

  return j;
}

/*------------------------------------------------------------------------*/
void keyout5(FILE * secout, FILE * pubout, DH * newkey, DSA * newsig,
             unsigned char *hashpass, char *userid)
{
  unsigned char dbuf[2048];
  unsigned long i, len, ckpt = 1, ckptp = 1;

  if (newsig) {
    memset(dbuf, 0, 9);
    dbuf[0] = 0x95;
    dbuf[3] = 4;
    i = time(NULL);             /* timestamp */
    dbuf[4] = i >> 24;
    dbuf[5] = i >> 16;
    dbuf[6] = i >> 8;
    dbuf[7] = i;
    dbuf[8] = 0x11;
    len = 6;
    if (secout)
      fwrite(dbuf, 1, 9, secout);
    dbuf[0] = 0x99;
    fwrite(dbuf, 1, 9, pubout);

    len += write_BN(secout, pubout, newsig->p, NULL);
    len += write_BN(secout, pubout, newsig->q, NULL);
    len += write_BN(secout, pubout, newsig->g, NULL);
    len += write_BN(secout, pubout, newsig->pub_key, NULL);

    fputc(0xb0, pubout);
    fputc(0x01, pubout);
    fputc(0x87, pubout);
    if (userid) {
      fputc(0xb4, pubout);
      fputc(strlen(userid), pubout);
      fwrite(userid, 1, strlen(userid), pubout);
      fputc(0xb0, pubout);
      fputc(0x01, pubout);
      fputc(0x03, pubout);
    }
    fflush(pubout);
    ckptp = ftell(pubout);

    fseek(pubout, 1, SEEK_SET);
    fputc(len >> 8, pubout);
    fputc(len, pubout);
    fseek(pubout, ckptp++, SEEK_SET);

    if (secout) {
      len += putcfbkey(secout, hashpass);
      len += write_BN(secout, NULL, newsig->priv_key, hashpass);
      DSA_free(newsig);
      if (userid) {
        fputc(0xb4, secout);
        fputc(strlen(userid), secout);
        fwrite(userid, 1, strlen(userid), secout);
      }
      fflush(secout);
      ckpt = ftell(secout);
      fseek(secout, 1, SEEK_SET);
      fputc(len >> 8, secout);
      fputc(len, secout);
      fseek(secout, ckpt++, SEEK_SET);
    } else
      DSA_free(newsig);
  }
  if (newkey) {
    memset(dbuf, 0, 9);
    dbuf[0] = 0x9d;
    dbuf[3] = 4;
    i = time(NULL);             /* timestamp */
    dbuf[4] = i >> 24;
    dbuf[5] = i >> 16;
    dbuf[6] = i >> 8;
    dbuf[7] = i;
    dbuf[8] = 0x10;
    len = 6;
    if (secout)
      fwrite(dbuf, 1, 9, secout);
    dbuf[0] = 0xb9;
    fwrite(dbuf, 1, 9, pubout);

    len += write_BN(secout, pubout, newkey->p, NULL);
    len += write_BN(secout, pubout, newkey->g, NULL);
    len += write_BN(secout, pubout, newkey->pub_key, NULL);

    fputc(0xb0, pubout);
    fputc(0x01, pubout);
    fputc(0x87, pubout);

    fflush(pubout);
    fseek(pubout, ckptp, SEEK_SET);
    fputc(len >> 8, pubout);
    fputc(len, pubout);
    if (secout) {
      len += putcfbkey(secout, hashpass);
      len += write_BN(secout, NULL, newkey->priv_key, hashpass);
      DH_free(newkey);

      fflush(secout);
      fseek(secout, ckpt, SEEK_SET);
      fputc(len >> 8, secout);
      fputc(len, secout);
    } else
      DH_free(newkey);
  }
  fclose(pubout);
  if (secout)
    fclose(secout);
}
