/*
 * Copyright (C) 2005 Voice Sistem SRL
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Kamailio is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * Kamailio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

/*!
 * \file
 * \brief Kamailio uac :: Authentication
 * \ingroup uac
 * Module: \ref uac
 */


#include "../../core/crypto/md5.h"

#include "auth_alg.h"


static inline void cvt_hex(HASH bin, HASHHEX hex)
{
	unsigned short i;
	unsigned char j;

	for (i = 0; i<HASHLEN; i++)
	{
		j = (bin[i] >> 4) & 0xf;
		if (j <= 9)
		{
			hex[i * 2] = (j + '0');
		} else {
			hex[i * 2] = (j + 'a' - 10);
		}

		j = bin[i] & 0xf;

		if (j <= 9)
		{
			hex[i * 2 + 1] = (j + '0');
		} else {
			hex[i * 2 + 1] = (j + 'a' - 10);
		}
	};

	hex[HASHHEXLEN] = '\0';
}


static inline void cvt_bin(HASHHEX hex, HASH bin)
{
	unsigned short i;
	unsigned char j;
	for (i = 0; i<HASHLEN; i++) {
		if(hex[2*i]>='0' && hex[2*i]<='9')
			j = (hex[2*i]-'0') << 4;
		else if(hex[2*i]>='a'&&hex[2*i]<='f')
			j = (hex[2*i]-'a'+10) << 4;
		else if(hex[2*i]>='A'&&hex[2*i]<='F')
			j = (hex[2*i]-'A'+10) << 4;
		else j = 0;

		if(hex[2*i+1]>='0'&&hex[2*i+1]<='9')
			j += hex[2*i+1]-'0';
		else if(hex[2*i+1]>='a'&&hex[2*i+1]<='f')
			j += hex[2*i+1]-'a'+10;
		else if(hex[2*i+1]>='A'&&hex[2*i+1]<='F')
			j += hex[2*i+1]-'A'+10;

		bin[i] = j;
	}
}

/*
 * calculate H(A1)
 */
void uac_calc_HA1( struct uac_credential *crd,
		struct authenticate_body *auth,
		str* cnonce,
		HASHHEX sess_key)
{
	MD5_CTX Md5Ctx;
	HASH HA1;

	if(crd->aflags & UAC_FLCRED_HA1) {
		memcpy(sess_key, crd->passwd.s, HASHHEXLEN);
		sess_key[HASHHEXLEN] = '\0';
		if ( auth->flags& AUTHENTICATE_MD5SESS ) {
			cvt_bin(sess_key, HA1);
		} else {
			return;
		}
	} else {
		MD5Init(&Md5Ctx);
		MD5Update(&Md5Ctx, crd->user.s, crd->user.len);
		MD5Update(&Md5Ctx, ":", 1);
		MD5Update(&Md5Ctx, crd->realm.s, crd->realm.len);
		MD5Update(&Md5Ctx, ":", 1);
		MD5Update(&Md5Ctx, crd->passwd.s, crd->passwd.len);
		MD5Final(HA1, &Md5Ctx);
	}

	if ( auth->flags& AUTHENTICATE_MD5SESS ) {
		MD5Init(&Md5Ctx);
		MD5Update(&Md5Ctx, HA1, HASHLEN);
		MD5Update(&Md5Ctx, ":", 1);
		MD5Update(&Md5Ctx, auth->nonce.s, auth->nonce.len);
		MD5Update(&Md5Ctx, ":", 1);
		MD5Update(&Md5Ctx, cnonce->s, cnonce->len);
		MD5Final(HA1, &Md5Ctx);
	}

	cvt_hex(HA1, sess_key);
}



/* 
 * calculate H(A2)
 */
void uac_calc_HA2( str *method, str *uri,
		struct authenticate_body *auth,
		HASHHEX hentity,
		HASHHEX HA2Hex )
{
	MD5_CTX Md5Ctx;
	HASH HA2;

	MD5Init(&Md5Ctx);
	MD5Update(&Md5Ctx, method->s, method->len);
	MD5Update(&Md5Ctx, ":", 1);
	MD5Update(&Md5Ctx, uri->s, uri->len);

	if ( auth->flags&QOP_AUTH_INT)
	{
		MD5Update(&Md5Ctx, ":", 1);
		MD5Update(&Md5Ctx, hentity, HASHHEXLEN);
	};

	MD5Final(HA2, &Md5Ctx);
	cvt_hex(HA2, HA2Hex);
}



/* 
 * calculate request-digest/response-digest as per HTTP Digest spec 
 */
void uac_calc_response( HASHHEX ha1, HASHHEX ha2,
		struct authenticate_body *auth,
		str* nc, str* cnonce,
		HASHHEX response)
{
	MD5_CTX Md5Ctx;
	HASH RespHash;
	char *p;

	MD5Init(&Md5Ctx);
	MD5Update(&Md5Ctx, ha1, HASHHEXLEN);
	MD5Update(&Md5Ctx, ":", 1);
	MD5Update(&Md5Ctx, auth->nonce.s, auth->nonce.len);
	MD5Update(&Md5Ctx, ":", 1);

	if ( auth->qop.len)
	{
		MD5Update(&Md5Ctx, nc->s, nc->len);
		MD5Update(&Md5Ctx, ":", 1);
		MD5Update(&Md5Ctx, cnonce->s, cnonce->len);
		MD5Update(&Md5Ctx, ":", 1);
		p = memchr(auth->qop.s, ',', auth->qop.len);
		if(p) {
			MD5Update(&Md5Ctx, auth->qop.s, (size_t)(p - auth->qop.s));
		} else {
			MD5Update(&Md5Ctx, auth->qop.s, auth->qop.len);
		}
		MD5Update(&Md5Ctx, ":", 1);
	};
	MD5Update(&Md5Ctx, ha2, HASHHEXLEN);
	MD5Final(RespHash, &Md5Ctx);
	cvt_hex(RespHash, response);
}
