/* 
   Copyright (c) 1995-1998 by Cisco systems, Inc.

   Permission to use, copy, modify, and distribute this software for
   any purpose and without fee is hereby granted, provided that this
   copyright and permission notice appear on all copies of the
   software and supporting documentation, the name of Cisco Systems,
   Inc. not be used in advertising or publicity pertaining to
   distribution of the program without specific prior permission, and
   notice be given in supporting documentation that modification,
   copying and distribution is by permission of Cisco Systems, Inc.

   Cisco Systems, Inc. makes no representations about the suitability
   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
   FITNESS FOR A PARTICULAR PURPOSE.
*/

#include "tac_plus.h"
#include "md5.h"

/*
 * create_md5_hash(): create an md5 hash of the "session_id", "the user's
 * key", "the version number", the "sequence number", and an optional
 * 16 bytes of data (a previously calculated hash). If not present, this
 * should be NULL pointer.
 *
 * Write resulting hash into the array pointed to by "hash".
 *
 * The caller must allocate sufficient space for the resulting hash
 * (which is 16 bytes long). The resulting hash can safely be used as
 * input to another call to create_md5_hash, as its contents are copied
 * before the new hash is generated.
 *
 *
 */

void
create_md5_hash(session_id, key, version, seq_no, prev_hash, hash)
int session_id;
char *key;
u_char version;
u_char seq_no;
u_char *prev_hash;
u_char *hash;
{
    u_char *md_stream, *mdp;
    int md_len;
    MD5_CTX mdcontext;

    md_len = sizeof(session_id) + strlen(key) + sizeof(version) +
	sizeof(seq_no);

    if (prev_hash) {
	md_len += MD5_LEN;
    }
    mdp = md_stream = (u_char *) tac_malloc(md_len);
    bcopy(&session_id, mdp, sizeof(session_id));
    mdp += sizeof(session_id);

    bcopy(key, mdp, strlen(key));
    mdp += strlen(key);

    bcopy(&version, mdp, sizeof(version));
    mdp += sizeof(version);

    bcopy(&seq_no, mdp, sizeof(seq_no));
    mdp += sizeof(seq_no);

    if (prev_hash) {
	bcopy(prev_hash, mdp, MD5_LEN);
	mdp += MD5_LEN;
    }
    MD5Init(&mdcontext);
    MD5Update(&mdcontext, md_stream, md_len);
    MD5Final(hash, &mdcontext);
    free(md_stream);
    return;
}

/*
 * Overwrite input data with en/decrypted version by generating an MD5 hash and
 * xor'ing data with it.
 *
 * When more than 16 bytes of hash is needed, the MD5 hash is performed
 * again with the same values as before, but with the previous hash value
 * appended to the MD5 input stream.
 *
 * Return 0 on success, -1 on failure.
 */

md5_xor(hdr, data, key)
HDR *hdr;
u_char *data;
char *key;
{
    int i, j;
    u_char hash[MD5_LEN];	/* the md5 hash */
    u_char last_hash[MD5_LEN];	/* the last hash we generated */
    u_char *prev_hashp = (u_char *) NULL;	/* pointer to last created
						 * hash */
    int data_len;
    int session_id;
    u_char version;
    u_char seq_no;

    data_len = ntohl(hdr->datalength);
    session_id = hdr->session_id; /* always in network order for hashing */
    version = hdr->version;
    seq_no = hdr->seq_no;

    if (!key)
	return (0);

    for (i = 0; i < data_len; i += 16) {

	create_md5_hash(session_id, key, version, seq_no, prev_hashp, hash);

	if (debug & DEBUG_MD5_HASH_FLAG) {
	    int k;

	    report(LOG_DEBUG, 
		   "hash: session_id=%u, key=%s, version=%d, seq_no=%d",
		   session_id, key, version, seq_no);
	    if (prev_hashp) {
		report(LOG_DEBUG, "prev_hash:");
		for (k = 0; k < MD5_LEN; k++)
		    report(LOG_DEBUG, "0x%x", prev_hashp[k]);
	    } else {
		report(LOG_DEBUG, "no prev. hash");
	    }

	    report(LOG_DEBUG, "hash: ");
	    for (k = 0; k < MD5_LEN; k++)
		report(LOG_DEBUG, "0x%x", hash[k]);
	}			/* debug */
	bcopy(hash, last_hash, MD5_LEN);
	prev_hashp = last_hash;

	for (j = 0; j < 16; j++) {

	    if ((i + j) >= data_len) {
		hdr->encryption = (hdr->encryption == TAC_PLUS_CLEAR) 
		    ? TAC_PLUS_ENCRYPTED : TAC_PLUS_CLEAR;
		return (0);
	    }
	    if (debug & DEBUG_XOR_FLAG) {
		report(LOG_DEBUG,
		   "data[%d] = 0x%x, xor'ed with hash[%d] = 0x%x -> 0x%x\n",
		       i + j,
		       data[i + j],
		       j,
		       hash[j],
		       data[i + j] ^ hash[j]);
	    }			/* debug */
	    data[i + j] ^= hash[j];
	}
    }
    hdr->encryption = (hdr->encryption == TAC_PLUS_CLEAR) 
	? TAC_PLUS_ENCRYPTED : TAC_PLUS_CLEAR;
    return (0);
}
