/*
 * @(#) auth.c  RCS: $Revision: 1.4 $ $Date: 95/03/02 12:53:56 $
 *
 * This file contains all the routines needed to interface to routines to 
 * perform digital signatures, and stream data encryption.  Currently it 
 * uses the des.  If another method is used, this module should be the only
 * one changed.
 */
#define _POSIX_SOURCE
#include <sys/types.h>	/* pid_t */
#include <unistd.h>	/* write getpid */
#include <stdio.h>
#include <string.h>	/* memset memcmp */
#include <ctype.h>	/* isspace */
#include <stdlib.h>	/* exit free (after ctype.h for BSDI BSD/386) */
#include <time.h>
#include "auth.h"
#include "txfr.h"
#include "log.h"
#include "deslogin.h"

extern int verbose;

typedef struct {
   char     iv[DES_BLOCKSIZE];
   unsigned ivlen;
   keyType  key;
   int 	    mode;
} cipherState;

cipherKey mkCipherKey(key, mode)
   keyType key;
   int mode;
{
   cipherState *cs =(cipherState *)malloc(sizeof (cipherState));

   if (cs != (cipherState *) 0) {
      memset(cs->iv, '\0', DES_BLOCKSIZE);
      cs->ivlen = DES_BLOCKSIZE;
      cs->key   = key;
      cs->mode  = mode;
   }
   return (cipherKey) cs;
}

void destroyCipherKey(c)
   cipherKey *c;
{
   register cipherState *cs = * (cipherState **) c;

   if (cs != (cipherState *) 0) {
      memset(cs->iv, '\0', DES_BLOCKSIZE);
      cs->ivlen = 0;
      cs->key   = (keyType) 0;
      cs->mode  = 0;
      free(cs);
      *c = (cipherKey) 0;
   }
}

void cipherData(dst, src, size, c)
   char *dst, *src;
   unsigned size;
   cipherKey c;
{
   register cipherState *cs = (cipherState *) c;

   if (cs->key != (keyType) 0) {
      cs->ivlen = desCFB(dst, src, size, cs->iv, cs->ivlen, cs->key, cs->mode);
   } else {
      memcpy(dst, src, size);
   }
}

/*
 * Convert a string of bytes to their hex representation
 */
char *hexData(dst, src, size)
   register char *dst, *src;
   register unsigned size;
{
   register char *chp = dst;
   if (size != 0) do {
      sprintf(chp, "%.2x", * (unsigned char *) src++);
      chp += 2;
   } while (--size != 0);
   return dst;
}

/*
 * Create a key from a keyString
 *
 * Mode: 0 = encrypt, 1 = decrypt
 */
keyType mkKey(keyString) 		
   char *keyString;
{
   char 	keybits[DES_BLOCKSIZE];
   keyType key = (keyType) 0;

   desKey(keybits, keyString, ' ');		/* hash string into 64-bits */
   desMakeKey(&key, keybits, DES_BLOCKSIZE, 0);	/* always encrypt for CFB */
   memset(keybits, '\0', DES_BLOCKSIZE);
   return key;
}

/*
 * Perform one DES encryption(decryption if mode nonzero) (ECB mode)
 *
 * Dst and src must be exactly DES_BLOCKSIZE bytes long.
 */
void cipherKeyString(dst, src, keyString, mode)
   char *dst, *src, *keyString;
   int mode;			/* 0 = encipher, 1 = decipher */
{
   char 	keybits[DES_BLOCKSIZE];
   keyType key = (keyType) 0;

   desKey(keybits, keyString, ' ');		/* hash string into 64-bits */

   desMakeKey(&key, keybits, DES_BLOCKSIZE, mode);
   memset(keybits, '\0', DES_BLOCKSIZE);

   des(dst, src, key);
   desDestroyKey(&key);
}

/*
 * Generate a unique session key from the challenge.  The idea here is to allow
 * the program on both the client and server side to destroy the user's 
 * private key from memory ASAP.  If a user on the local machine tries to
 * scan the process memory, they can only compromise the session key and not
 * the user's long-term login key.
 */
keyType sessionKey(challenge, keyString, mode)
   char *challenge, *keyString;
   int mode;
{
   char    keybits[DES_BLOCKSIZE];
   keyType res = (keyType) 0;

   cipherKeyString(keybits, challenge, keyString, 0);		/* encrypt */

   desMakeKey(&res, keybits, DES_BLOCKSIZE, mode);
   memset(keybits, '\0', DES_BLOCKSIZE);
   return res;
}

/*
* Issue and validate a crypto challenge
 *
 * Returns: session key if success, 0 if failure
 */
keyType challenge(fd, keyString, timeout)
   int fd;
   char *keyString;
   unsigned timeout;
{
   char plainText[DES_BLOCKSIZE], cipherText[DES_BLOCKSIZE];
   char buf[DES_BLOCKSIZE], buf2[DES_BLOCKSIZE*2+1];
   int wcount, rcount, res;
   keyType result = (keyType) 0;

   /*
    * The challenge message must be unpredictable and ideally, never reused
    * to prevent playback, or anticipating the next challenge response
    *
    * To make it unpredictable, take the current time + current pid , and 
    * encipher it once with the user's key.  This is then used as the 
    * plaintext of the challenge.  
    */
   memset(cipherText, '\0', DES_BLOCKSIZE);
   time((time_t *) &cipherText[0]);
   * (unsigned *) &cipherText[4] = (unsigned) getpid();
   cipherKeyString(plainText, cipherText, keyString, 0);

   cipherKeyString(cipherText, plainText, keyString, 0);	/* encrypt */

   if (debug) {
      log("%s: challenge(%s)\n", 
	  progName, hexData(buf2, cipherText, DES_BLOCKSIZE));
   }

   wcount = write(fd, cipherText, DES_BLOCKSIZE);

   if (wcount != DES_BLOCKSIZE) {
      log("%s: challenge--write failed; %s\n", progName, ERRMSG);
      exit(1);
   }

   cipherKeyString(buf, cipherText, keyString, 1);	/* decrypt */

   rcount = getBlock(fd, buf, DES_BLOCKSIZE, timeout);	/* decrypt */
   if (debug) {
      if (rcount > 0) {
	 log("%s: response(%s)\n", progName, hexData(buf2, buf, DES_BLOCKSIZE));
      }
   }

   /*
    * The remote user deciphers the challenge and it'd better be the same
    * as the one we originally enciphered to be a valid signature
    */
   if (rcount == DES_BLOCKSIZE) {
      res = memcmp(buf, plainText, DES_BLOCKSIZE);
      if (res == 0) {
	 result = sessionKey(cipherText, keyString, 0);
      }
   }
   return result;
}

/*
 * Wait for and respond to a crypto challenge return the session key.
 */
keyType signature(fd, keyString, timeout)
   int fd;
   char *keyString;
   unsigned timeout;
{
   keyType res = (keyType) 0;

   char plainText[DES_BLOCKSIZE], cipherText[DES_BLOCKSIZE];
   register int wcount = -1, rcount;

   rcount = getBlock(fd, cipherText, DES_BLOCKSIZE, timeout);
   if (rcount == DES_BLOCKSIZE) {
      cipherKeyString(plainText, cipherText, keyString, 1);	/* decrypt */
      wcount = write(fd, plainText, rcount);
      res = sessionKey(cipherText, keyString, 0);
   }

   return res;
}

/*
 * Find the corresponding passphrase for the given user name.
 *
 * The user file is a sequence of lines with username passphrase pairs
 * Lines beginning with '#' are comments, empty lines are permitted.
 *
 * Returns: -2 if key incorrect, -1 if open failed, 0 no such user, 1 success
 */
int getUserPhrase(fname, phrase, size, uname, key)
   char *fname;
   register char *phrase;
   register unsigned size;
   char *uname;
   keyType key;
{
   register int i;
   char cipherChar, ch;
   register int res = -2, state = 0;
   register char *chp = 0;
   FILE *f = fopen(fname, "r");
   cipherKey ck = (cipherKey) 0;

   ck = mkCipherKey(key, 1);		/* decrypt */

   if (f == NULL) return -1;
   do {
      i = getc(f);
      if (i == EOF)  {
	 res = 0; 
	 break;
      }
      cipherChar = (char) i;
      cipherData(&ch, &cipherChar, 1, ck);
      if (ch & 0x80) break;		/* non-printable character: stop! */
      switch (state) {
      case 0:				
	 if (ch == '#') { 
	    state = 1;
	    break;
	 } 
	 chp = uname;
	 state = 2;			/* fall through */
      case 2:				/* user field leading whitespace */
	 if (ch == '\n') state = 0;
	 if (ch == '#') {
	    state = 1;
	    break;
	 }
	 if (isspace(ch)) break;	/* newline is also whitespace */
	 state = 3;			/* fall through */
      case 3:				/* user field */
	 if (!isspace(ch)) {
	    if (*chp++ != ch) state = 1;
	 } else {
	    if (*chp == '\0') {		/* match? */
	       if (ch == '\n') state = 6; else state = 4;
	       if (verbose) log("founduser\n");
	    } else {
	       state = 0;
	    }
	 }
	 break;
      case 1:				/* skip remainder of line */
	 if (ch == '\n') state = 0;
	 break;
      case 4:				/* passphrase leading whitespace */
	 if (ch == '\n') state = 6;
	 if (isspace(ch)) break;
	 state = 5;			/* fall through */
	 chp = phrase;
      case 5:				/* passphrase */
	 if (ch == '\n') {
	    if (size != 0) *chp = '\0';
	    state = 6;
	    res   = 1;
	 }  else {
	    if (size != 0) {
	       *chp++ = ch;
	       --size;
	    }
	 }
	 break;
      }
   } while (state != 6);

   destroyCipherKey(&ck);

   fclose(f);
   return res;
}
