/* Code to generate a key pair for a particular username
 *  Probably could be better and more random, but
 *  good enough for our purposes.
 */

#include "incremental.h"

#include <stdio.h>
#include <sys/param.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/time.h>

#include <global.h>
#include <rsaref.h>
#include <nn.h>

static struct termios save_termios;
static int ttysavefd = -1;
static enum {RESET, CBREAK} ttystate = RESET;

void get_home(char *), tty_atexit(void),
  InitFile(char *, int *, int *), 
  OutputKeys(R_RSA_PUBLIC_KEY, int, R_RSA_PRIVATE_KEY, int),
  InitRandomStruct (R_RANDOM_STRUCT *randomStruct),
  PrintError (char *, int);
int tty_cbreak(int), tty_reset(int);

int main(int argc, char *argv[]){
  int keySize, fpub, fpriv, status;
  R_RANDOM_STRUCT randomStruct;
  R_RSA_PUBLIC_KEY key_pub;
  R_RSA_PRIVATE_KEY key_priv;
  R_RSA_PROTO_KEY protoKey;
  char buffer[10];

  atexit(tty_atexit);
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);

  if (argc != 2){
    fprintf(stderr, "usage %s {username}\n", argv[0]);
    exit(1);
  }
  InitRandomStruct(&randomStruct);
  InitFile(argv[1], &fpub, &fpriv);

  /* initiate the proto structure */
  keySize = 0;
  while((keySize <512) || (keySize > 1024)) {
    printf("Please select a keysize (512 - 1024) : ");
    fflush(NULL);
    fgets(buffer, 9, stdin);
    sscanf(buffer, "%d", &keySize);
  }
  protoKey.bits = (unsigned int)keySize;
  protoKey.useFermat4 =1;
  printf("Generating keys. This will take a while. Please wait...\n");
  if (status = R_GeneratePEMKeys
      (&key_pub, &key_priv, &protoKey, &randomStruct)) {
    PrintError ("Generating keys", status);
    exit(1);
  }
  printf("       done.  ... Saving keys\n");
  OutputKeys(key_pub, fpub, key_priv, fpriv);
  R_RandomFinal(&randomStruct);
  R_memset((POINTER)&key_priv, 0, sizeof(key_priv));
  return(0);
  printf("Thanks for waiting. \n");
  close (fpub);
  close (fpriv);
}


void InitFile(char *name, int *fpub, int *fpriv) {
  char keypath[MAXPATHLEN];  /* yeah yeah yeah.. constant length */
  char *pos;  /* where extension goes */

  get_home(keypath);
  strcat(keypath, PKE_PATH);
  /* Test whether there exists a directory for keys */
  if (access(keypath, F_OK)) { /* failed to find file */
    if (mkdir(keypath, S_IRWXU)) {
      perror("Failed to create directory:");
      exit(1);
    }
  }
  strcat(keypath, name);
  pos = (&keypath[strlen(keypath)]);
  
  strcpy (pos, ".pub");
  if ((*fpub = open(keypath, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR)) <0) {
    perror("failed to create keyfile:");
    exit(1);
  }
  strcpy (pos, ".prv");
  if ((*fpriv = open(keypath, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR)) <0) {
    perror("failed to create keyfile:");
    exit(1);
  }
  pos[0] = '\0';
  printf("\n  Thanks\nWill store in %s.pub and .prv\n", keypath);
}

void OutputKeys(R_RSA_PUBLIC_KEY key_pub, int fpub,
		R_RSA_PRIVATE_KEY key_priv, int fpriv) {

  /* Public Key first */
  write(fpub, &key_pub.bits, sizeof(key_pub.bits));
  write(fpub, &key_pub.modulus, sizeof(key_pub.modulus));
  write(fpub, &key_pub.exponent, sizeof(key_pub.exponent));

  /* Private key now */
  write(fpriv, &key_priv.bits, sizeof(key_priv.bits));
  write(fpriv, &key_priv.modulus, sizeof(key_priv.modulus));
  write(fpriv, &key_priv.publicExponent, 
	sizeof(key_priv.publicExponent));
  write(fpriv, &key_priv.exponent, sizeof(key_priv.exponent));
  write(fpriv, &key_priv.prime[0], sizeof(key_priv.prime[0]));
  write(fpriv, &key_priv.prime[1], sizeof(key_priv.prime[1]));
  write(fpriv, &key_priv.primeExponent,
	sizeof(key_priv.primeExponent));
  write(fpriv, &key_priv.coefficient, sizeof(key_priv.coefficient));

  /* Done writing everything */
}

void get_home(result)
char *result;
{
  struct passwd *pwd;
 
  if (strcpy(result,(char *)getenv("HOME")))
    return;
  if (pwd = getpwuid(getuid())){
    strcpy(result, pwd->pw_dir);
    return;
  }
  else{
    strcpy(result, "/");
    return;
  }
}

void InitRandomStruct (R_RANDOM_STRUCT *randomStruct)
{
  static unsigned char seedByte = 0;
  unsigned int bytesNeeded =1000;
  int i= 40;
  struct timeval tp;
  struct timezone tz;

  tty_cbreak(STDIN_FILENO);

  R_RandomInit (randomStruct);
  
  gettimeofday(&tp, &tz);
  seedByte = (char)tp.tv_usec;
  R_RandomUpdate (randomStruct, &seedByte, 1);

  printf("Please type in random chars until I tell you to quit\n");
  printf("to go   ");
  while (bytesNeeded > 0) {
    if (( read (STDIN_FILENO, &seedByte, 1)) != 1) {
      perror("read");
      tty_reset(STDIN_FILENO);
      exit(1);
    }
    R_RandomUpdate (randomStruct, &seedByte, 1);

    gettimeofday(&tp, &tz);
    seedByte = (char)tp.tv_usec;
    R_RandomUpdate (randomStruct, &seedByte, 1);
    R_GetRandomBytesNeeded (&bytesNeeded, randomStruct);
    if (bytesNeeded > 18)
      printf(".");
    else
      printf("%d", (bytesNeeded/2));
    i -= 1;
    if (i==0){
      printf("\n%5d - ", (bytesNeeded/2));
      i = 40;
    }
  }
  tty_reset(STDIN_FILENO);
}

int tty_cbreak(int fd)
{
  struct termios buf;

  if (tcgetattr(fd, &save_termios) <0)
    return (-1);

  buf= save_termios;

  buf.c_lflag &= ~(ECHO | ICANON);

  buf.c_cc[VMIN] = 1;
  buf.c_cc[VTIME] = 0;

  if (tcsetattr(fd, TCSAFLUSH, &buf) < 0)
    return (-1);
  ttystate = CBREAK;
  ttysavefd = fd;
  return(0);
}

int tty_reset(int fd)
{
  if (ttystate != CBREAK)
    return (0);
  if (tcsetattr(fd, TCSAFLUSH, &save_termios) <0)
    return (-1);
  ttystate = RESET;
  return (0);
}

void tty_atexit(void)
{
  if (ttysavefd >= 0)
    tty_reset(ttysavefd);
}

void PrintError (char *task, int type)
{
  char *typeString, buf[80];

  if (type == 0) {
    puts (task);
    return;
  }
  
  /* Convert the type to a string if it is recognized.
   */
  switch (type) {
  case RE_KEY:
    typeString = "Recovered DES key cannot decrypt encrypted content";
    break;
  case RE_LEN:
    typeString = 
      "Encrypted key length or signature length is out of range";
    break;
  case RE_MODULUS_LEN:
    typeString = "Modulus length is out of range";
    break;
  case RE_PRIVATE_KEY:
    typeString = "Private key cannot encrypt.";
    break;
  case RE_PUBLIC_KEY:
    typeString = "Public key cannot encrypt.";
    break;
  case RE_SIGNATURE:
    typeString = "Signature is incorrect";
    break;
    
  default:
    sprintf (buf, "Code 0x%04x", type);
    typeString = buf;
  }

  printf ("ERROR: %s while %s\n", typeString, task);  
  fflush (stdout);
}

