#if !defined(lint) && !defined(__INSIGHT__)
static char sos__rcsid[] = "skid.c,v 1.1.1.1 1995/06/16 17:42:43 seth Exp";
static char sos__copyright[] = "Copyright (c) 1994, 1995 SOS Corporation";
static char sos__contact[] = "SOS Corporation <sos-info@soscorp.com> +1 800 SOS UNIX";
#endif /* not lint */

/*
 * ++Copyright Released Product++
 *
 * Copyright (c) 1994, 1995 Sources of Supply Corporation ("SOS").
 * All rights reserved.
 *
 * The SOS Released Product License Agreement specifies the terms and
 * conditions for redistribution.  You may find the License Agreement
 * in the file LICENSE.
 *
 * SOS Corporation
 * 461 5th Ave.; 16th floor
 * New York, NY 10017
 *
 * +1 800 SOS UNIX
 * <sos-info@soscorp.com>
 *
 * --Copyright Released Product--
 */

/*
 *			      S K I D 3
 *
 * SKID3 -- secret-key identification protocol developed for RACE's
 * RIPE project It uses a keyed one-way hash function (a MAC) to
 * provide security, and assumes that both Alice and Bob share a
 * secret key, K.
 *
 * SKID - Secret Key IDentification
 * RACE - Research and development in Advanced Communication technologies in Europe
 * RIPE - RACE Integrity Primitives Evaluation
 * 
 * Implemented from a description of SKID3 in:
 *
 * B. Schneier, _Applied_cryptography_, New York: John Wiley & Sons, 1994.
 *
 * SKID3 source document:
 *
 * RACE, _RIPE_Integrity_Primitives:_Final_Report_of_RACE_Integrity_Primitives_Evaluation
 * (R1040), June 1992.
 *
 *
 * Protocol (as it appears in Schneier--we perform very minor reordering in the code):
 *
 * Step		ALICE		BOB
 * 
 * A 	       	Ra	-->
 * 
 *		Alice generates 64 bit random
 *		number (Ra) and sends to Bob
 * 
 * B			<--	Rb H(Ra,Rb,Bob,K)
 *
 *		Bob generates 64 bit random number
 *		(Rb), computes keyed hash. Bob is Bob's
 *		name (we use source IP address).
 *		K is shared secret key.
 *
 * C		Verify
 *
 *		Alice verifies that Bob knows the secret
 *		key (K) by computing the hash herself.
 *
 * D		H(Rb,Alice,K)	-->
 *
 *		Alice computes keyed hash.  Alice is
 *		Alice's name.
 *
 * E				Verify
 *
 *		Bob verifies that Alice knows the secret
 *		key (K) by computing the hash himself.
 *
 *
 * At step C, Alice knows that Bob knows K.
 * At step E, Bob knows that Alice knows K.
 *
 */

#include "sos.h"
#include "md5.h"


#define RAN_LEN		8	/* 64 bits according to RIPE */
#define NAME_SZ		32	/* max len of IP address and port */
#define HASH_LEN	16	/* Length of hash */

struct skid_protocol
{
  caddr_t	Key;		/* Shared secret key */
  char		Alice[NAME_SZ];	/* Alice's name (IP address) */
  char		Bob[NAME_SZ];	/* Bob's name (IP address) */

  unsigned char	Ra[RAN_LEN];	/* Alice's random number */
  unsigned char	Rb[RAN_LEN];	/* Bob's random number */

  unsigned char	Hb[HASH_LEN];	/* Bob's keyed hash */
  unsigned char Ha[HASH_LEN];	/* Alice's keyed hash */
};

static char *skid_name[2] = { "Alice", "Bob" };

int sos_skid_trace = 0;

static void skid_find_my_name(int fd, char *buf);
static void skid_find_his_name(int fd, char *buf);
static void skid_hash(char *hash, char *R1, char *R2, char *Name, char *Key);


/*
 * sos_skid
 *
 * Routine to perform SKID3 authentication.
 *
 * Returns -1 on error
 * Returns 1 on success
 * Returns 0 on failure
 */
int sos_skid_with_ident(int fd, int mode, char *key,
			int (*readf)(int, caddr_t, __SIZE_TYPE__),
			int (*writef)(int, caddr_t, __SIZE_TYPE__))
{
  struct skid_protocol skidInfo;
  unsigned char Remote_Hash[HASH_LEN];
  int retval = -1;

  if (fd < 0 || !readf || !writef || !key ||
      ( mode != SOS_IDENT_ALICE && mode != SOS_IDENT_BOB ))
    {
      if (sos_skid_trace)
	fprintf(stderr,"SKID: Invalid argument\n");
      return(-1);
    }

  if (sos_skid_trace)
    {
      fprintf(stderr,"Welcome, %s, to the SKID3 protocol\n",
	      skid_name[(mode==SOS_IDENT_ALICE)?0:1]);

    }



  /* Initialize structure */
  memset((char *)&skidInfo, 0, sizeof(skidInfo));

  /* Fill out shared secret key */
  skidInfo.Key = key;

  /* Fill out name information */
  if (mode == SOS_IDENT_ALICE)
    {
      skid_find_my_name(fd, skidInfo.Alice);
      skid_find_his_name(fd, skidInfo.Bob);
    }
  else /*BOB*/
    {
      skid_find_my_name(fd, skidInfo.Bob);
      skid_find_his_name(fd, skidInfo.Alice);
    }

  /* Generate Random Numbers */
  if (mode == SOS_IDENT_ALICE)
    {
      sos_get_rand((unsigned char *)&(skidInfo.Ra), RAN_LEN);
    }
  else /* BOB */
    {
      sos_get_rand((unsigned char *)&(skidInfo.Rb), RAN_LEN);
    }

  /* Alice sends Ra to Bob */
  if (mode == SOS_IDENT_ALICE)
    {				/* Alice write Ra */
      if ( sos_xdr_wencode (fd, writef, "cccccccc", 
			    skidInfo.Ra[0],
			    skidInfo.Ra[1],
			    skidInfo.Ra[2],
			    skidInfo.Ra[3],
			    skidInfo.Ra[4],
			    skidInfo.Ra[5],
			    skidInfo.Ra[6],
			    skidInfo.Ra[7] ) <= 0 )
	{
	  if (sos_skid_trace)
	    {
	      perror("write");
	      fprintf(stderr, "SKID: write failed (Ra)\n");
	    }
	  return(-1);
	}
    }
  else /* BOB */
    {				/* Bob reads Ra */
      if ( sos_xdr_wdecode (fd, readf, "cccccccc", 
			    &skidInfo.Ra[0],
			    &skidInfo.Ra[1],
			    &skidInfo.Ra[2],
			    &skidInfo.Ra[3],
			    &skidInfo.Ra[4],
			    &skidInfo.Ra[5],
			    &skidInfo.Ra[6],
			    &skidInfo.Ra[7] ) != 8 )
	{
	  if (sos_skid_trace)
	    {
	      perror("write");
	      fprintf(stderr, "SKID: read failed (Ra)\n");
	    }
	  return(-1);
	}
    }

  if (sos_skid_trace)
    fprintf(stderr,"Ra has been exchanged\n");

  /* Bob sends Rb to Alice */
  if (mode == SOS_IDENT_ALICE)
    {				/* Alice reads Rb */
      if ( sos_xdr_wdecode (fd, readf, "cccccccc", 
			    &skidInfo.Rb[0],
			    &skidInfo.Rb[1],
			    &skidInfo.Rb[2],
			    &skidInfo.Rb[3],
			    &skidInfo.Rb[4],
			    &skidInfo.Rb[5],
			    &skidInfo.Rb[6],
			    &skidInfo.Rb[7] ) != 8 )
	{
	  if (sos_skid_trace)
	    {
	      perror("write");
	      fprintf(stderr, "SKID: read failed (Rb)\n");
	    }
	  return(-1);
	}
    }
  else /* BOB */
    {				/* Bob writes Rb */
      if ( sos_xdr_wencode (fd, writef, "cccccccc", 
			    skidInfo.Rb[0],
			    skidInfo.Rb[1],
			    skidInfo.Rb[2],
			    skidInfo.Rb[3],
			    skidInfo.Rb[4],
			    skidInfo.Rb[5],
			    skidInfo.Rb[6],
			    skidInfo.Rb[7] ) <= 0 )
	{
	  if (sos_skid_trace)
	    {
	      perror("write");
	      fprintf(stderr, "SKID: write failed (Rb)\n");
	    }
	  return(-1);
	}
    }

  if (sos_skid_trace)
    fprintf(stderr,"Rb has been exchanged\n");

  /* Compute Hb - Bob's Keyed hash */
  skid_hash(skidInfo.Hb, skidInfo.Ra, skidInfo.Rb, skidInfo.Bob, skidInfo.Key);

  /* Compute Ha - Alice's Keyed hash */
  skid_hash(skidInfo.Ha, skidInfo.Rb, NULL, skidInfo.Alice, skidInfo.Key);

  if (sos_skid_trace)
    {
      fprintf(stderr,"SKID:\tKey: %s, Alice: %s, Bob: %s\n\tRa: %08x %08x, Rb: %08x %08x\n",
	      skidInfo.Key, skidInfo.Alice, skidInfo.Bob,
	      *((int *)(&(skidInfo.Ra[0]))),
	      *((int *)(&(skidInfo.Ra[4]))),
	      *((int *)(&(skidInfo.Rb[0]))),
	      *((int *)(&(skidInfo.Rb[4]))));
      fprintf(stderr,"\tHa: %08x %08x %08x %08x\n",
	      *((int *)(&(skidInfo.Ha[0]))),
	      *((int *)(&(skidInfo.Ha[4]))),
	      *((int *)(&(skidInfo.Ha[8]))),
	      *((int *)(&(skidInfo.Ha[12]))));
      fprintf(stderr,"\tHb: %08x %08x %08x %08x\n\n",
	      *((int *)(&(skidInfo.Hb[0]))),
	      *((int *)(&(skidInfo.Hb[4]))),
	      *((int *)(&(skidInfo.Hb[8]))),
	      *((int *)(&(skidInfo.Hb[12]))));
    }


  /* Bob sends Hb to Alice */
  if (mode == SOS_IDENT_ALICE)
    {				/* Alice reads Hb */
      if ( sos_xdr_wdecode(fd, readf, "cccccccccccccccc",
			   &Remote_Hash[0],
			   &Remote_Hash[1],
			   &Remote_Hash[2],
			   &Remote_Hash[3],
			   &Remote_Hash[4],
			   &Remote_Hash[5],
			   &Remote_Hash[6],
			   &Remote_Hash[7],
			   &Remote_Hash[8],
			   &Remote_Hash[9],
			   &Remote_Hash[10],
			   &Remote_Hash[11],
			   &Remote_Hash[12],
			   &Remote_Hash[13],
			   &Remote_Hash[14],
			   &Remote_Hash[15] ) != 16 )
	{
	  if (sos_skid_trace)
	    {
	      perror("write");
	      fprintf(stderr, "SKID: read failed (Hb)\n");
	    }
	  return(-1);
	}
    }
  else /* BOB */
    {				/* Bob writes Hb */
      if ( sos_xdr_wencode(fd, writef, "cccccccccccccccc",
			   skidInfo.Hb[0],
			   skidInfo.Hb[1],
			   skidInfo.Hb[2],
			   skidInfo.Hb[3],
			   skidInfo.Hb[4],
			   skidInfo.Hb[5],
			   skidInfo.Hb[6],
			   skidInfo.Hb[7],
			   skidInfo.Hb[8],
			   skidInfo.Hb[9],
			   skidInfo.Hb[10],
			   skidInfo.Hb[11],
			   skidInfo.Hb[12],
			   skidInfo.Hb[13],
			   skidInfo.Hb[14],
			   skidInfo.Hb[15] ) <= 0 )
	{
	  if (sos_skid_trace)
	    {
	      perror("write");
	      fprintf(stderr, "SKID: write failed (Hb)\n");
	    }
	  return(-1);
	}
    }

  /* Alice verifies that Remote hash is equal to Hb */
  if (mode == SOS_IDENT_ALICE)
    {
      if (sos_skid_trace)
	fprintf(stderr,"SKID:\tRemote hash: %08x %08x %08x %08x\n",
		*((int *)(&(Remote_Hash[0]))),
		*((int *)(&(Remote_Hash[4]))),
		*((int *)(&(Remote_Hash[8]))),
		*((int *)(&(Remote_Hash[12]))));

      if (memcmp(Remote_Hash,skidInfo.Hb,HASH_LEN))
	{
	  if (sos_skid_trace)
	    fprintf(stderr,"SKID: Failed--Hb != RemoteHash\n");

	  /* Mess up hash value to fool Ben (Bob's imposter) */
	  sos_get_rand((unsigned char *)&(skidInfo.Ha), HASH_LEN);

	  retval = 0;
	}
      else
	{
	  if (sos_skid_trace)
	    fprintf(stderr,"SKID: SUCCEEDED\n");

	  retval = 1;
	}
    }


  /* Alice sends Ha to Bob */
  if (mode == SOS_IDENT_ALICE)
    {				/* Alice writes Ha */
      if ( sos_xdr_wencode(fd, writef, "cccccccccccccccc",
			   skidInfo.Ha[0],
			   skidInfo.Ha[1],
			   skidInfo.Ha[2],
			   skidInfo.Ha[3],
			   skidInfo.Ha[4],
			   skidInfo.Ha[5],
			   skidInfo.Ha[6],
			   skidInfo.Ha[7],
			   skidInfo.Ha[8],
			   skidInfo.Ha[9],
			   skidInfo.Ha[10],
			   skidInfo.Ha[11],
			   skidInfo.Ha[12],
			   skidInfo.Ha[13],
			   skidInfo.Ha[14],
			   skidInfo.Ha[15] ) <= 0 )
	{
	  if (sos_skid_trace)
	    {
	      perror("write");
	      fprintf(stderr, "SKID: write failed (Ha)\n");
	    }
	  return(-1);
	}
    }
  else /* BOB */
    {				/* Bob reads Ha */
      if ( sos_xdr_wdecode(fd, readf, "cccccccccccccccc",
			   &Remote_Hash[0],
			   &Remote_Hash[1],
			   &Remote_Hash[2],
			   &Remote_Hash[3],
			   &Remote_Hash[4],
			   &Remote_Hash[5],
			   &Remote_Hash[6],
			   &Remote_Hash[7],
			   &Remote_Hash[8],
			   &Remote_Hash[9],
			   &Remote_Hash[10],
			   &Remote_Hash[11],
			   &Remote_Hash[12],
			   &Remote_Hash[13],
			   &Remote_Hash[14],
			   &Remote_Hash[15] ) != 16 )
	{
	  if (sos_skid_trace)
	    {
	      perror("write");
	      fprintf(stderr, "SKID: read failed (Ha)\n");
	    }
	  return(-1);
	}
    }

  /* Bob verifies that Remote hash is equal to Hb */
  if (mode == SOS_IDENT_BOB)
    {
      if (sos_skid_trace)
	fprintf(stderr,"SKID:\tRemote hash: %08x %08x %08x %08x\n",
		*((int *)(&(Remote_Hash[0]))),
		*((int *)(&(Remote_Hash[4]))),
		*((int *)(&(Remote_Hash[8]))),
		*((int *)(&(Remote_Hash[12]))));

      if (memcmp(Remote_Hash,skidInfo.Ha,HASH_LEN))
	{
	  if (sos_skid_trace)
	    fprintf(stderr,"SKID: Failed--Ha != RemoteHash\n");

	  /* Hmm.  We have already proven who we are */
	  retval = 0;
	}
      else
	{
	  if (sos_skid_trace)
	    fprintf(stderr,"SKID: SUCCEEDED\n");

	  retval = 1;
	}
    }

  if (sos_skid_trace)
    fprintf(stderr,"SKID: Returning %d\n\n",retval);

  return(retval);
}


/*
 * skid_find_his_name
 *
 * Find out remote user's name via getpeername()
 */
static void skid_find_my_name(int fd, char *buf)
{
  struct sockaddr foo;
  int len = sizeof(struct sockaddr);

  if (getsockname(fd, &foo, &len) < 0)
    {
      if (sos_skid_trace)
	perror("getsockname");
      sprintf(buf,"NON-NETWORKED CONNECTION\n");
    }
  else
    {
      memcpy(buf,foo.sa_data,sizeof(foo.sa_data));
    }
}

/*
 * skid_find_his_name
 *
 * Find out remote user's name via getpeername()
 */
static void skid_find_his_name(int fd, char *buf)
{
  struct sockaddr foo;
  int len = sizeof(struct sockaddr);

  if (getpeername(fd, &foo, &len) < 0)
    {
      if (sos_skid_trace)
	perror("getpeername");
      sprintf(buf,"NON-NETWORKED CONNECTION\n");
    }
  else
    {
      memcpy(buf,foo.sa_data,sizeof(foo.sa_data));
    }
}

/*
 * Create a keyed hash
 */
static void skid_hash(char *hash, char *R1, char *R2, char *Name, char *Key)
{
  MD5_CTX mdContext;

  MD5Init(&mdContext);
  MD5Update(&mdContext, R1, RAN_LEN);
  if (R2)
    MD5Update(&mdContext, R2, RAN_LEN);
  MD5Update(&mdContext, Name, NAME_SZ);
  MD5Update(&mdContext, Key, strlen(Key));
  MD5Final(&mdContext);

  memcpy(hash, mdContext.digest, HASH_LEN);
}


/*
 * Run skid without having to know which peer you'll play.
 * We'll figure that out for you.
 */
int sos_skid(int fd, char *key,
	      int (*readf)(int, caddr_t, __SIZE_TYPE__),
	      int (*writef)(int, caddr_t, __SIZE_TYPE__))

{
  int ident;			/* Which peer will I play */

  if ( (ident = sos_alice_bob(fd, readf, fd, writef)) < 0 )
    {
      return  -1;
    }
  
  if ( sos_skid_trace )
    fprintf(stderr,"I am playing the role of %s\n", 
	    (ident==SOS_IDENT_ALICE)?"Alice":"Bob");
  return (sos_skid_with_ident(fd, ident, key, readf, writef));

}
