/*
 * Digest Authentication Module
 * Nonce related functions
 *
 * Copyright (C) 2001-2003 FhG Fokus
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * 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
 *
 */

#include <time.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include "../../core/compiler_opt.h"
#include "../../core/crypto/md5.h"
#include "../../core/dprint.h"
#include "../../core/ut.h"
#include "../../core/parser/msg_parser.h"
#include "../../core/parser/parse_from.h"
#include "../../core/ip_addr.h"
#include "nonce.h"
#include "../../core/globals.h"
#include <assert.h>
#ifdef USE_NC
#include "nc.h"
#endif
#ifdef USE_OT_NONCE
#include "ot_nonce.h"
#endif


int auth_checks_reg = 0;
int auth_checks_ood = 0;
int auth_checks_ind = 0;

/* maximum time drift accepted for the nonce creation time
 * (e.g. nonce generated by another proxy in the same cluster with the
 * clock slightly in the future)
 */
unsigned int nonce_auth_max_drift = 3; /* in s */

/** Select extra check configuration based on request type.
 * This function determines which configuration variable for
 * extra authentication checks is to be used based on the
 * type of the request. It returns the value of auth_checks_reg
 * for REGISTER requests, the value auth_checks_ind for requests
 * that contain valid To tag and the value of auth_checks_ood
 * otherwise.
 */
int get_auth_checks(struct sip_msg *msg)
{
	str tag;

	if(msg == NULL)
		return 0;

	if(msg->REQ_METHOD == METHOD_REGISTER) {
		return auth_checks_reg;
	}

	if(!msg->to && parse_headers(msg, HDR_TO_F, 0) == -1) {
		DBG("auth: Error while parsing To header field\n");
		return auth_checks_ood;
	}
	if(msg->to) {
		tag = get_to(msg)->tag_value;
		if(tag.s && tag.len > 0)
			return auth_checks_ind;
	}
	return auth_checks_ood;
}


/* takes a pre-filled bin_nonce union (see BIN_NONCE_PREPARE), fills the
 * MD5s and returns the length of the binary nonce (cannot return error).
 * See calc_nonce below for more details.*/
inline static int calc_bin_nonce_md5(union bin_nonce *b_nonce, int cfg,
		str *secret1, str *secret2, struct sip_msg *msg)
{
	MD5_CTX ctx;

	str *s;
	int len;

	MD5Init(&ctx);

	U_MD5Update(&ctx, &b_nonce->raw[0], 4 + 4);
	if(cfg && msg) {
		/* auth extra checks => 2 md5s */
		len = 4 + 4 + 16 + 16;
#if defined USE_NC || defined USE_OT_NONCE
		if(b_nonce->n.nid_pf & (NF_VALID_NC_ID | NF_VALID_OT_ID)) {
			/* if extra auth checks enabled, nid & pf are after the 2nd md5 */
			U_MD5Update(&ctx, (unsigned char *)&b_nonce->n.nid_i,
					nonce_nid_extra_size);
			len += nonce_nid_extra_size;
		}
#endif /* USE_NC || USE_OT_NONCE */
		MD5Update(&ctx, secret1->s, secret1->len);
		MD5Final(&b_nonce->n.md5_1[0], &ctx);
		/* second MD5(auth_extra_checks) */
		MD5Init(&ctx);
		if(cfg & AUTH_CHECK_FULL_URI) {
			s = GET_RURI(msg);
			MD5Update(&ctx, s->s, s->len);
		}
		if((cfg & AUTH_CHECK_CALLID)
				&& !(parse_headers(msg, HDR_CALLID_F, 0) < 0
						|| msg->callid == 0)) {
			MD5Update(&ctx, msg->callid->body.s, msg->callid->body.len);
		}
		if((cfg & AUTH_CHECK_FROMTAG) && !(parse_from_header(msg) < 0)) {
			MD5Update(&ctx, get_from(msg)->tag_value.s,
					get_from(msg)->tag_value.len);
		}
		if(cfg & AUTH_CHECK_SRC_IP) {
			U_MD5Update(&ctx, msg->rcv.src_ip.u.addr, msg->rcv.src_ip.len);
		}
		MD5Update(&ctx, secret2->s, secret2->len);
		MD5Final(&b_nonce->n.md5_2[0], &ctx);
	} else {
		/* no extra checks => only one md5 */
		len = 4 + 4 + 16;
#if defined USE_NC || USE_OT_NONCE
		if(b_nonce->n_small.nid_pf & (NF_VALID_NC_ID | NF_VALID_OT_ID)) {
			/* if extra auth checks are not enabled, nid & pf are after the
			 *  1st md5 */
			U_MD5Update(&ctx, (unsigned char *)&b_nonce->n_small.nid_i,
					nonce_nid_extra_size);
			len += nonce_nid_extra_size;
		}
#endif /* USE_NC  || USE_OT_NONCE*/
		MD5Update(&ctx, secret1->s, secret1->len);
		MD5Final(&b_nonce->n.md5_1[0], &ctx);
	}

	return len;
}


/** Calculates the nonce string for RFC2617 digest authentication.
 * This function creates the nonce string as it will be sent to the
 * user agent in digest challenge. The format of the nonce string
 * depends on the value of three module parameters, auth_checks_register,
 * auth_checks_no_dlg and auth_checks_in_dlg. These module parameters
 * control the amount of information from the SIP request that will be
 * stored in the nonce string for verification purposes.
 *
 * If all three parameters contain zero then the nonce string consists
 * of time in seconds since 1.1.1970 and a secret phrase:
 * <expire_time> <valid_since> MD5(<expire_time>, <valid_since>, secret)
 * If any of the parameters is not zero (some optional checks are enabled
 * then the nonce string will also contain MD5 hash of selected parts
 * of the SIP request:
 * <expire_time> <valid_since> MD5(<expire_time>, <valid_since>, secret1) MD5(<extra_checks>, secret2)
 * @param nonce  Pointer to a buffer of *nonce_len. It must have enough
 *               space to hold the nonce. MAX_NONCE_LEN should be always
 *               safe.
 * @param nonce_len A value/result parameter. Initially it contains the
 *                  nonce buffer length. If the length is too small, it
 *                  will be set to the needed length and the function will
 *                  return error immediately. After a successful call it will
 *                  contain the size of nonce written into the buffer,
 *                  without the terminating 0.
 * @param cfg This is the value of one of the three module parameters that
 *            control which optional checks are enabled/disabled and which
 *            parts of the message will be included in the nonce string.
 * @param since Time when nonce was created, i.e. nonce is valid since <valid_since> up to <expires>
 * @param expires Time in seconds after which the nonce will be considered
 *                stale.
 * @param n_id    Nounce count and/or one-time nonce index value
 *                (32 bit counter)
 * @param pf      First 2 bits are flags, the rest is the index pool number
 *                used if nonce counts or one-time nonces are enabled.
 *                The possible flags values are: NF_VALID_NC_ID which means
 *                the nonce-count support is enabled and NF_VALID_OT_ID
 *                which means the one-time nonces support is enabled.
 *                The pool number can be obtained by and-ing with
 *                NF_POOL_NO_MASK
 * @param secret1 A secret used for the nonce expires integrity check:
 *                MD5(<expire_time>, <valid_since>, secret1).
 * @param secret2 A secret used for integrity check of the message parts
 *                selected by auth_extra_checks (if any):
 *                MD5(<msg_parts(auth_extra_checks)>, secret2).
 * @param msg     The message for which the nonce is computed. If
 *                auth_extra_checks is set, the MD5 of some fields of the
 *                message will be included in the generated nonce.
 * @return 0 on success and -1 on error
 */
int calc_nonce(char *nonce, int *nonce_len, int cfg, unsigned int since,
		unsigned int expires,
#if defined USE_NC || defined USE_OT_NONCE
		unsigned int n_id, unsigned char pf,
#endif /* USE_NC || USE_OT_NONCE */
		str *secret1, str *secret2, struct sip_msg *msg)
{
	union bin_nonce b_nonce;
	int len;
	if(unlikely(*nonce_len < MAX_NONCE_LEN)) {
		len = get_nonce_len(cfg, pf & NF_VALID_NC_ID);
		if(unlikely(*nonce_len < len)) {
			*nonce_len = len;
			return -1;
		}
	}

	BIN_NONCE_PREPARE(&b_nonce, expires, since, n_id, pf, cfg, msg);
	len = calc_bin_nonce_md5(&b_nonce, cfg, secret1, secret2, msg);
	*nonce_len = base64_enc(
			&b_nonce.raw[0], len, (unsigned char *)nonce, *nonce_len);
	assert(*nonce_len >= 0);
	return 0;
}


/** Returns the expire time of the nonce string.
 * This function returns the absolute expire time
 * extracted from the nonce string in the parameter.
 * @param bn is a valid pointer to a union bin_nonce (decoded nonce)
 * @return Absolute time when the nonce string expires.
 */

#define get_bin_nonce_expire(bn) ((time_t)ntohl((bn)->n.expire))

/** Returns the valid_since time of the nonce string.
 * This function returns the absolute time
 * extracted from the nonce string in the parameter.
 * @param bn is a valid pointer to a union bin_nonce (decoded nonce)
 * @return Absolute time when the nonce string was created.
 */
#define get_bin_nonce_since(bn) ((time_t)ntohl((bn)->n.since))


/** Checks if nonce is stale.
 * This function checks if a nonce given to it in the parameter is stale.
 * A nonce is stale if the expire time stored in the nonce is in the past.
 * @param b_nonce a pointer to a union bin_nonce to be checked.
 * @return 1 the nonce is stale, 0 the nonce is not stale.
 */
#define is_bin_nonce_stale(b_nonce, t) (get_bin_nonce_expire(b_nonce) < (t))


/** Utility to convert 8 hex digit string to int */
static inline int l8hex2int(char *_s, unsigned int *_r)
{
	unsigned int i, res = 0;

	for(i = 0; i < 8; i++) {
		res *= 16;
		if((_s[i] >= '0') && (_s[i] <= '9')) {
			res += _s[i] - '0';
		} else if((_s[i] >= 'a') && (_s[i] <= 'f')) {
			res += _s[i] - 'a' + 10;
		} else if((_s[i] >= 'A') && (_s[i] <= 'F')) {
			res += _s[i] - 'A' + 10;
		} else
			return -1;
	}

	*_r = res;
	return 0;
}


/** Check whether the nonce returned by UA is valid.
 * This function checks whether the nonce string returned by UA
 * in digest response is valid. The function checks if the nonce
 * string hasn't expired, it verifies the secret stored in the nonce
 * string with the secret configured on the server. If any of the
 * optional extra integrity checks are enabled then it also verifies
 * whether the corresponding parts in the new SIP requests are same.
 * @param nonce A nonce string to be verified.
 * @param secret1 A secret used for the nonce expires integrity check:
 *                MD5(<expire_time>,, secret1).
 * @param secret2 A secret used for integrity check of the message parts
 *                selected by auth_extra_checks (if any):
 *                MD5(<msg_parts(auth_extra_checks)>, secret2).
 * @param msg The message which contains the nonce being verified.
 * @return 0 - success (the nonce was not tampered with and if
 *             auth_extra_checks are enabled - the selected message fields
 *             have not changes from the time the nonce was generated)
 *         -1 - invalid nonce
 *          1 - nonce length too small
 *          2 - no match
 *          3 - nonce expires ok, but the auth_extra checks failed
 *          4 - stale
 *          5 - invalid nc value (not an unsigned int)
 *          6 - nonce reused
 */
int check_nonce(auth_body_t *auth, str *secret1, str *secret2,
		struct sip_msg *msg, int update_nonce)
{
	str *nonce;
	time_t since;
	int b_nonce2_len, b_nonce_len, cfg;
	union bin_nonce b_nonce;
	union bin_nonce b_nonce2;
	time_t t;
#if defined USE_NC || defined USE_OT_NONCE
	unsigned int n_id;
	unsigned char pf;
#endif /* USE_NC || USE_OT_NONCE */
#ifdef USE_NC
	unsigned int nc;
#endif

	cfg = get_auth_checks(msg);
	nonce = &auth->digest.nonce;

	if(unlikely(nonce->s == 0)) {
		return -1; /* Invalid nonce */
	}

	if(unlikely(nonce->len < MIN_NONCE_LEN)) {
		return 1; /* length musth be >= then minimum length */
	}

#if defined USE_NC || defined USE_OT_NONCE
	/* clear all possible nonce flags positions prior to decoding,
	 * to make sure they can be used even if the nonce is shorter */
	b_nonce.n.nid_pf = 0;
	b_nonce.n_small.nid_pf = 0;
#endif /* USE_NC || USE_OT_NONCE */

	/* decode nonce */
	b_nonce_len = base64_dec((unsigned char *)nonce->s, nonce->len,
			&b_nonce.raw[0], sizeof(b_nonce));
	if(unlikely(b_nonce_len < MIN_BIN_NONCE_LEN)) {
		DBG("auth: check_nonce: base64_dec failed\n");
		return -1; /* error decoding the nonce (invalid nonce since we checked
					* the len of the base64 enc. nonce above)*/
	}

	since = get_bin_nonce_since(&b_nonce);
	if(unlikely(since < up_since)) {
		/* if valid_since time is time pointing before ser was started
		 * then we consider nonce as stalled.
		 * It may be the nonce generated by previous ser instance having
		 * different length (for example because of different auth.
		 * checks)..  Therefore we force credentials to be rebuilt by UAC
		 * without prompting for password */
		/* if current time is less than start time, reset the start time
		 * (e.g., after start, the system clock was set in the past) */
		t = time(0);
		if(t < up_since)
			up_since = t;
		if(since < t)
			return 4;
	}
	t = time(0);
	if(unlikely((since > t) && ((since - t) > nonce_auth_max_drift))) {
		/* the nonce comes from the future, either because of an external
		 * time adjustment, or because it was generated by another host
		 * which has the time slightly unsynchronized */
		return 4; /* consider it stale */
	}
	b_nonce2 = b_nonce; /*pre-fill it with the values from the received nonce*/
	b_nonce2.n.expire = b_nonce.n.expire;
	b_nonce2.n.since = b_nonce.n.since;
#if defined USE_NC || defined USE_OT_NONCE
	if(cfg) {
		b_nonce2.n.nid_i = b_nonce.n.nid_i;
		b_nonce2.n.nid_pf = b_nonce.n.nid_pf;
		pf = b_nonce.n.nid_pf;
		n_id = ntohl(b_nonce.n.nid_i);
	} else {
		b_nonce2.n_small.nid_i = b_nonce.n_small.nid_i;
		b_nonce2.n_small.nid_pf = b_nonce.n_small.nid_pf;
		pf = b_nonce.n_small.nid_pf;
		n_id = ntohl(b_nonce.n_small.nid_i);
	}
#ifdef UE_NC
	if(unlikely(nc_enabled && !(pf & NF_VALID_NC_ID)))
		/* nounce count enabled, but nonce is not marked as nonce count ready
		 * or is too short => either an old nonce (should
		 * be caught by the ser start time  check) or truncated nonce  */
		return 4; /* return stale for now */
}
#endif /* USE_NC */
#ifdef USE_OT_NONCE
if(unlikely(otn_enabled && !(pf & NF_VALID_OT_ID))) {
	/* same as above for one-time-nonce */
	return 4; /* return stale for now */
}
#endif /* USE_OT_NONCE */
/* don't check if we got the expected length, if the length is smaller
 * then expected then  the md5 check below will fail (since the nid
 * members of the bin_nonce struct will be 0); if the length is bigger
 * and it was not caught by the base64_dec above, and the md5 matches,
 * we ignore the extra stuff */
#endif /* USE_NC || USE_OT_NONCE */
b_nonce2_len = calc_bin_nonce_md5(&b_nonce2, cfg, secret1, secret2, msg);
if(!memcmp(&b_nonce.n.md5_1[0], &b_nonce2.n.md5_1[0], 16)) {
#ifdef USE_NC
	/* if nounce-count checks enabled & auth. headers has nc */
	if(nc_enabled && (pf & NF_VALID_NC_ID) && auth->digest.nc.s
			&& auth->digest.nc.len) {
		if((auth->digest.nc.len != 8)
				|| l8hex2int(auth->digest.nc.s, &nc) != 0) {
			LM_ERR("bad nc value %.*s\n", auth->digest.nc.len,
					auth->digest.nc.s);
			return 5; /* invalid nc */
		}
		switch(nc_check_val(n_id, pf & NF_POOL_NO_MASK, nc, update_nonce)) {
			case NC_OK:
				/* don't perform extra checks or one-time nonce checks
				 * anymore, if we have nc */
				goto check_stale;
			case NC_ID_OVERFLOW: /* id too old => stale */
			case NC_TOO_BIG:	 /* nc overlfow => force re-auth => stale */
			case NC_REPLAY:		 /* nc seen before => re-auth => stale */
			case NC_INV_POOL:	 /* pool-no too big, maybe ser restart?*/
				return 4;		 /* stale */
		}
	}
#endif /* USE_NC */
#ifdef USE_OT_NONCE
	if(otn_enabled && (pf & NF_VALID_OT_ID)) {
		switch(otn_check_id(n_id, pf & NF_POOL_NO_MASK)) {
			case OTN_OK:
				/* continue in case auth extra checks are enabled */
				break;
			case OTN_ID_OVERFLOW:
			case OTN_INV_POOL:
			case OTN_REPLAY:
				return 6; /* reused */
		}
	}
#endif
	if(cfg) {
		if(unlikely(b_nonce_len != b_nonce2_len))
			return 2; /* someone truncated our nonce? */
		if(memcmp(&b_nonce.n.md5_2[0], &b_nonce2.n.md5_2[0], 16))
			return 3; /* auth_extra_checks failed */
	}
#ifdef USE_NC
check_stale:
#endif /* USE_NC */
	if(unlikely(is_bin_nonce_stale(&b_nonce, t)))
		return 4;
	return 0;
}

return 2;
}
