/* Copyright 2000 Enhanced Software Technologies Inc.
 *   All Rights Reserved
 *
 * Released for public use under a BSD-style license. See file LICENSE
 * for details. 
 *
 *   Encrypt a file in CFB-128 mode using AES candidate "twofish".
 *
 * Cleaned up a bit in a desperate bid to keep busy in aftermath of
 * EST's demise.
*/


#include "config.h"

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#include "rijndael.h"
#include "bin2hex.h"
#include "dstring.h"
#include "dorandom.h"

static char *progname;

keyInstance key;
cipherInstance cipher;

static int cfb128_idx=-1;

static char *cfb_blk;
static char cfb_crypt[16];


static void usage(void) {
  fprintf(stderr,"%s: Usage: %s -k {keyfile} [-s 128|192|256]\n",progname,progname);
  exit(1);
}



/* write data, aborting if we have a write error. */

static void ewrite(int outfile,void *buf, size_t count) {
  int result;

  result=write(outfile,buf,count);
  if (result!=(int) count) {
    fprintf(stderr,"%s:error:could not write data.\n",progname);
    perror(progname);
    usage();
  }
}

static void cryptblock(char *src, int srclen) {
  int i,ch;
 
  /* Now to encrypt each individual character in cfb mode: */
  for (i=0; i<srclen; i++) {
    if ((cfb128_idx < 0) || (cfb128_idx > 15)) {

      blockEncrypt(&cipher,&key,cfb_blk,128,cfb_crypt);
      /*encrypt(cfb_blk,cfb_crypt); */

      cfb128_idx=0;
    }
    /* XOR the data with a byte from our encrypted buffer. */ 
    ch=src[i] ^ cfb_crypt[cfb128_idx];
    /* do output feedback: put crypted byte into next block to be crypted */
    cfb_blk[cfb128_idx++]=ch; 
    src[i]=(char) ch;
  }
  
  return;
}


static void cryptfile(int infile,int outfile) {
  char *salt;
  int result;

#define CRYPTBUF_SIZE 8192  
  char buf[CRYPTBUF_SIZE];
  int bufsize;  /* how many chars are currently in that buffer. */

  result=urand_seed(NULL);
  if (result) {
    fprintf(stderr,"%s:error:Could not seed PRNG\n",progname);
    perror(progname);
    usage();
  }

  salt=urand_get(16);


  if (!salt) {
    fprintf(stderr,"%s:error:could not get salt from PRNG\n",progname);
    perror(progname);
    usage();
  }
  
  /* set the feedback buffer to be the salt. */
  cfb_blk=salt;

  /* we have salt... now write it to outfile.... */

  ewrite(outfile,salt,16);


  /* Now initialize the cipher: */
  if (cipherInit(&cipher,MODE_ECB,NULL) != TRUE) {
    fprintf(stderr,"%s:Could not initialize cipher.\n",progname);
    usage();
  }

  while (bufsize=read(infile,buf,CRYPTBUF_SIZE), bufsize != 0) {
    if (bufsize<0) { /* some sort of I/O error... */
      fprintf(stderr,"%s:Error on read.\n",progname);
      perror(progname);
      exit(1);
    }
    cryptblock(buf,bufsize);
    ewrite(outfile,buf,bufsize); /* checks for error... */
  }

  /* okay, we've done everything... */
  
  return;
}
  


/* Routine to set the key once we've read it. */
static void aes_set_key(char *inkey, int keysize) {
  int keylen,rqskeysize,rtncode;
  char *hexkey;
  rqskeysize = keysize / 4;

  hexkey=d_trim(inkey); 

  keylen=strlen(hexkey);

  /* if it's a long key, chop it! */
  if ( keylen > rqskeysize ) {
    keylen = rqskeysize;  /* chop! */
    hexkey[rqskeysize]=0; 
  }    
    
  if ( keylen==rqskeysize) {
    keysize=keylen>>1;
        
    if ((rtncode = makeKey(&key,DIR_ENCRYPT,keylen*4,hexkey)) !=TRUE) {
      fprintf(stderr,"%s: reported from makeKey: Key format error:%i\n",progname,rtncode);
      usage();
    }

    /* I suppose I should just suck it up. */
    return;
  }
    
  fprintf(stderr,"%s:Key size error:%d[%s]\n",progname,keylen,hexkey);
  usage();
}

/* Routine to read a 128-bit key from stdin, as a 32-byte hex
 * null-terminated string:
 */
static void read_key_from_stdin(int infile, int keysize) {
  unsigned char keybuf[65];
  int result, hexkeysize;

  hexkeysize = keysize / 4;
  result=read(infile,keybuf, hexkeysize+1);
  if (result<hexkeysize+1) {
    fprintf(stderr,"%s: Error: Key not found on stdin.\n",progname);
    usage();
  }

  keybuf[hexkeysize]=0; /* make sure it is null terminated */
  aes_set_key(keybuf, keysize);
  return;
}

  
/* Routine to read a key from keyfile. */
static void read_key(char *filename, int keysize) {
  char inbuf[1024];
  FILE *infile;
  char *result;

  infile=fopen(filename,"r");
  if (!infile) {
    fprintf(stderr,"%s:Error: Key file '%s' not found.\n",progname,filename);
    usage();
  }

  while (result=fgets(inbuf,1023,infile), result != NULL) {
    if ((strncmp(result,"kk=",3)==0) || strncmp(result,"kk:",3)==0) {
      aes_set_key(result+3, keysize);
      return;
    }
  }

  fprintf(stderr,"%s: Error: Key not found in file '%s'.\n",progname,filename);
  usage();
}
  


int main(int argc, char **argv) {

  char * infile;
  int keysize, op;
  keysize = 128; /* default to 128 bit keys */
  progname = argv[0];
  infile = NULL;
  while (1) {
    op = getopt(argc, argv, "k:s:");
    if (op == -1) break;
    switch (op) {
    case 'k': /* keyfile */
      if ((infile = (char *)malloc(strlen(optarg))) == NULL) {
	fprintf(stderr, "malloc failed.\n");
	exit(1);
      }
      strcpy(infile, optarg);
      break;
    case 's':
      keysize = atoi(optarg);
      if ((keysize != 128) && (keysize != 192) & (keysize != 256)) {
	fprintf(stderr, "keysize must be one of 128, 192, 256.\n");
	exit(1);
      }
      break;
    default:
      usage();
      exit(1);
    }
  }
  
  if (infile == NULL) {usage(); exit(1);}
  if (!strcmp(infile, "-")) {
    read_key_from_stdin(fileno(stdin), keysize);
  } else {
    read_key(infile, keysize); /* read the keyfile, hopefully! */
  }
  
  cryptfile(fileno(stdin),fileno(stdout));
  
  
  exit(0);

}
