/*
 * tnmSnmpAuth.c --
 *
 *	This file contains some utilities to compute and maintain
 *	authentication keys and parameters associated with engineIDs.
 *
 * Copyright (c) 1994-1996 Technical University of Braunschweig.
 * Copyright (c) 1996-1997 University of Twente.
 * Copyright (c) 1997-1998 Technical University of Braunschweig.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * @(#) $Id: tnmSnmpAuth.c 1070 1998-03-30 17:30:04Z schoenfr $
 */

#include "tnmSnmp.h"
#include "tnmMib.h"
#include "tnmMD5.h"

#ifdef TNM_SNMPv3

/*
 * The table of known SNMP security levels.
 */

TnmTable tnmSnmpSecurityLevelTable[] =
{
    { TNM_SNMP_AUTH_NONE | TNM_SNMP_PRIV_NONE,	"noAuth/noPriv" },
    { TNM_SNMP_AUTH_MD5  | TNM_SNMP_PRIV_NONE,	"md5/noPriv" },
    { TNM_SNMP_AUTH_MD5  | TNM_SNMP_PRIV_DES,	"md5/des" },
    { TNM_SNMP_AUTH_SHA  | TNM_SNMP_PRIV_NONE,	"sha/noPriv" },
    { TNM_SNMP_AUTH_SHA  | TNM_SNMP_PRIV_DES,	"sha/des" },
    { 0, NULL }
};

/*
 * The following structures and procedures are used to keep a list of
 * keys that were computed with the SNMPv3 password to key algorithm.
 * This cache is needed so that identical sessions don't suffer from
 * repeated slow computations of authentication keys.
 */

typedef struct KeyCache {
    Tcl_Obj *password;
    Tcl_Obj *engineID;
    Tcl_Obj *key;
    int algorithm;
    struct KeyCache *nextPtr;
} KeyCache;

/*
 * Forward declarations for procedures defined later in this file:
 */

static void
MD5PassWord2Key	_ANSI_ARGS_((u_char *pwBytes, int pwLength,
			     u_char *engineBytes, int engineLength,
			     u_char *key));
#if 0
static void
SHAPassWord2Key	_ANSI_ARGS_((u_char *pwBytes, int pwLength,
			     u_char *engineBytes, int engineLength,
			     u_char *key));
static void
ComputeKey	_ANSI_ARGS_((Tcl_Obj **objPtrPtr, Tcl_Obj *password,
			     Tcl_Obj *engineID, int algorithm));
#endif

/*
 *----------------------------------------------------------------------
 *
 * MD5PassWord2Key --
 *
 *	This procedure converts a password into a key by using
 *	the `Password to Key Algorithm' as defined in RFC 2274.
 *	The code is a slightly modified version of the source
 *	code in appendix A.2.1 of RFC 2274.
 *
 * Results:
 *	The key is written to the argument key.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
MD5PassWord2Key(pwBytes, pwLength, engineBytes, engineLength, key)
    u_char *pwBytes;
    int pwLength;
    u_char *engineBytes;
    int engineLength;
    u_char *key;
{
    MD5_CTX MD;
    u_char *cp, buffer[64];
    int i, index = 0, count = 0;

    TnmMD5Init(&MD);
    while (count < 1048576) {
	cp = buffer;
	for(i = 0; i < 64; i++) {
	    *cp++ = pwBytes[index++ % pwLength];
	}
	TnmMD5Update(&MD, buffer, 64);
	count += 64;
    }
    TnmMD5Final(key, &MD);

#if 0
    fprintf(stderr, "MD5 PW2K: ");
    for (i = 0; i < 16; i++) {
	fprintf(stderr, "%02x ", key[i]);
    }
    fprintf(stderr, "\n");
#endif

    memcpy(buffer, key, 16);
    memcpy(buffer + 16, engineBytes, engineLength);
    memcpy(buffer + 16 + engineLength, key, 16);
    
    TnmMD5Init(&MD);
    TnmMD5Update(&MD, buffer, 16 + engineLength + 16);
    TnmMD5Final(key, &MD);

#if 0
    fprintf(stderr, "MD5 LK: ");
    for (i = 0; i < 16; i++) {
	fprintf(stderr, "%02x ", key[i]);
    }
    fprintf(stderr, "\n");
#endif
}
#if 0

/*
 *----------------------------------------------------------------------
 *
 * SHAPassWord2Key --
 *
 *	This procedure converts a password into a key by using
 *	the `Password to Key Algorithm' as defined in RFC 2274.
 *	The code is a slightly modified version of the source
 *	code in appendix A.2.2 of RFC 2274.
 *
 * Results:
 *	The key is written to the argument key.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
SHAPassWord2Key(pwBytes, pwLength, engineBytes, engineLength, key)
    u_char *pwBytes;
    int pwLength;
    u_char *engineBytes;
    int engineLength;
    u_char *key;
{
    SHA_CTX SH;
    u_char *cp, buffer[72];
    int i, index = 0, count = 0;

    TnmSHAInit(&MD);
    while (count < 1048576) {
	cp = buffer;
	for(i = 0; i < 64; i++) {
	    *cp++ = pwBytes[index++ % pwLength];
	}
	TnmSHAUpdate(&MD, buffer, 64);
	count += 64;
    }
    TnmSHAFinal(key, &MD);
    
    memcpy(buffer, key, 16);
    memcpy(buffer + 20, engineBytes, engineLength);
    memcpy(buffer + 20 + engineLength, key, 20);
    
    TnmSHAInit(&MD);
    TnmSHAUpdate(&MD, buffer, 20 + engineLength + 20);
    TnmSHAFinal(key, &MD);
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * ComputeKey --
 *
 *	This procedure computes keys by applying the password to
 *	key transformation. A cache of previously computed keys
 *	is maintained in order to save some computations.
 *
 * Results:
 *	A pointer to the key or NULL if not found.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
ComputeKey(objPtrPtr, password, engineID, algorithm)
    Tcl_Obj **objPtrPtr;
    Tcl_Obj *password;
    Tcl_Obj *engineID;
    int algorithm;
{
    char *pwBytes, *engineBytes, *bytes;
    int pwLength, engineLength, length;
    KeyCache *elemPtr;
    static KeyCache *keyList = NULL;

    if (*objPtrPtr) {
	Tcl_DecrRefCount(*objPtrPtr);
	*objPtrPtr = NULL;
    }

    pwBytes = Tcl_GetStringFromObj(password, &pwLength);
    engineBytes = Tcl_GetStringFromObj(engineID, &engineLength);

    /*
     * Check whether the key is already in our cache. We have
     * to check that the password and the engineID  matches as
     * well as the algorithm.
     */

    for (elemPtr = keyList; elemPtr; elemPtr = elemPtr->nextPtr) {
	if (elemPtr->algorithm != algorithm) continue;

	bytes = Tcl_GetStringFromObj(elemPtr->password, &length);
	if (length != pwLength) continue;
	if (memcmp(pwBytes, bytes, length) != 0) continue;
	
	bytes = Tcl_GetStringFromObj(elemPtr->engineID, &length);
	if (length != engineLength) continue;
	if (memcmp(engineBytes, bytes, length) != 0) continue;

	*objPtrPtr = elemPtr->key;
	Tcl_IncrRefCount(*objPtrPtr);
    }

    /*
     * Compute a new localized key as described in the appendix
     * of RFC 2274.
     */

    switch (algorithm) {
    case TNM_SNMP_AUTH_MD5:
	*objPtrPtr = Tcl_NewStringObj(NULL, 0);
	Tcl_IncrRefCount(*objPtrPtr);
	Tcl_SetObjLength(*objPtrPtr, 16);
	MD5PassWord2Key(pwBytes, pwLength, engineBytes, engineLength, 
			Tcl_GetStringFromObj(*objPtrPtr, NULL));
	break;
#if 0
    case TNM_SNMP_AUTH_SHA:
	*objPtrPtr = Tcl_NewStringObj(NULL, 0);
	Tcl_IncrRefCount(*objPtrPtr);
	Tcl_SetObjLength(*objPtrPtr, 20);
	SHAPassWord2Key(pwBytes, pwLength, engineBytes, engineLength, 
			Tcl_GetStringFromObj(*objPtrPtr, NULL));
	break;
#endif
    default:
	panic("unknown algorithm for password to key conversion");
    }

    /*
     * Finally, createw a new cache entry to save the result for the
     * future.
     */

    elemPtr = (KeyCache *) ckalloc(sizeof(KeyCache));
    elemPtr->algorithm = algorithm;
    elemPtr->password = password;
    Tcl_IncrRefCount(elemPtr->password);
    elemPtr->engineID = engineID;
    Tcl_IncrRefCount(elemPtr->engineID);
    elemPtr->key = *objPtrPtr;
    Tcl_IncrRefCount(elemPtr->key);
    elemPtr->nextPtr = keyList;
    keyList = elemPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * TnmSnmpComputeKeys --
 *
 *	This procedure computes new authentication and privacy key
 *	for a given SNMPv3 session.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The authentication and privacy keys for this session are updated.
 *
 *----------------------------------------------------------------------
 */

void
TnmSnmpComputeKeys(session)
    TnmSnmp *session;
{
    int authProto, privProto;

    authProto = (session->readSecurityLevel & TNM_SNMP_AUTH_MASK);
    privProto = (session->readSecurityLevel & TNM_SNMP_PRIV_MASK);
    
    if (authProto != TNM_SNMP_AUTH_NONE) {
	ComputeKey(&session->readAuthKey, session->authPassWord,
		   session->engineID, authProto);
	if (privProto != TNM_SNMP_PRIV_NONE) {
	    ComputeKey(&session->readPrivKey, session->privPassWord,
		       session->engineID, authProto);
	}
    }

    authProto = (session->writeSecurityLevel & TNM_SNMP_AUTH_MASK);
    privProto = (session->writeSecurityLevel & TNM_SNMP_PRIV_MASK);
    
    if (authProto != TNM_SNMP_AUTH_NONE) {
	ComputeKey(&session->writeAuthKey, session->authPassWord,
		   session->engineID, authProto);
	if (privProto != TNM_SNMP_PRIV_NONE) {
	    ComputeKey(&session->writePrivKey, session->privPassWord,
		       session->engineID, authProto);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MD5Digest --
 *
 *	This procedure computes the HMAC message digest of the given
 *	packet. It is based on the MD5 implementation of RFC 1321.
 *	Read RFC 2274 section 6.3 for a more detailed description.
 *
 * Results:
 *	The digest is written into the digest array.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
MD5Digest(packet, length, key, digest)
    u_char *packet;
    int length;
    u_char *key;
    u_char *digest;
{
    MD5_CTX MD;

    TnmMD5Init(&MD);
    TnmMD5Update(&MD, (char *) packet, length);
    if (key) {
	TnmMD5Update(&MD, (char *) key, TNM_MD5_SIZE);
    }
    TnmMD5Final(digest, &MD);
}
#endif
