/*! \file auth_xor.c
 * GSM/GPRS/3G authentication core infrastructure */
/*
 * (C) 2018 by Harald Welte <laforge@gnumonks.org>
 * (C) 2017 by sysmocom s.f.m.c. GmbH
 *
 * All Rights Reserved
 *
 * Author: Daniel Willmann <dwillmann@sysmocom.de>
 *
 * All Rights Reserved
 *
 * This program 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.
 *
 * This program 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.
 *
 */

#include <string.h>
#include <stdint.h>
#include <errno.h>

#include <osmocom/core/bit64gen.h>
#include <osmocom/crypt/auth.h>

/*! \addtogroup auth
 *  @{
 */

static void xor(uint8_t *out, const uint8_t *a, const uint8_t *b, size_t len)
{
	size_t i;

	for (i = 0; i < len; i++)
		out[i] = a[i] ^ b[i];
}

/* 3GPP TS 34.108, section 8.1.2.1 */
static int xor_gen_vec(struct osmo_auth_vector *vec,
		       struct osmo_sub_auth_data2 *aud,
		       const uint8_t *_rand)
{
	uint8_t xdout[16], cdout[8];
	uint8_t ak[6], xmac[8];
	int i;

	OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_XOR_3G);

	/* Step 1: xdout = (ki or k) ^ rand */
	if (aud->type == OSMO_AUTH_TYPE_GSM)
		xor(xdout, aud->u.gsm.ki, _rand, sizeof(xdout));
	else if (aud->type == OSMO_AUTH_TYPE_UMTS) {
		if (aud->u.umts.k_len != 16)
			return -EINVAL;
		xor(xdout, aud->u.umts.k, _rand, sizeof(xdout));
	} else
		return -ENOTSUP;

	/**
	 * Step 2: res = xdout
	 *
	 * Suggested length for res is 128 bits, i.e. 16 bytes,
	 * but also can be in range: 30 < n < 128 bits.
	 */
	memcpy(vec->res, xdout, sizeof(xdout));
	vec->res_len = sizeof(xdout);

	/* ck = xdout[1-15,0] */
	memcpy(vec->ck, xdout + 1, sizeof(xdout) - 1);
	vec->ck[15] = xdout[0];

	/* ik = xdout[2-15,0-1] */
	memcpy(vec->ik, xdout + 2, sizeof(xdout) - 2);
	memcpy(vec->ik + sizeof(xdout) - 2, xdout, 2);

	/* ak = xdout[3-8] */
	memcpy(ak, xdout + 3, sizeof(ak));

	/**
	 * 3GPP TS 33.102, clause 6.8.1.2, b
	 * sres = c2(res) = res[0-3] ^ res[4-7] ^ res[8-11] ^ res[12-15]
	 */
	for (i = 0; i < 4; i++) {
		vec->sres[i]  = vec->res[i] ^ vec->res[i + 4];
		vec->sres[i] ^= vec->res[i + 8] ^ vec->res[i + 12];
	}

	/**
	 * 3GPP TS 33.102, clause 6.8.1.2, c
	 * kc = c3(ck, ik) = ck[0-7] ^ ck[8-15] ^ ik[0-7] ^ ik[8-15]
	 * FIXME: do we really have CK/IK for GSM?
	 */
	osmo_auth_c3(vec->kc, vec->ck, vec->ik);

	/* The further part is UMTS specific */
	if (aud->type != OSMO_AUTH_TYPE_UMTS) {
		vec->auth_types = OSMO_AUTH_TYPE_GSM;
		return 0;
	}

	/**
	 * Step 3: cdout = sqn[0-5] || amf[0-1]
	 * NOTE (for USIM): sqn[0-5] = autn[0-5] ^ ak[0-5]
	 */
	osmo_store64be_ext(aud->u.umts.sqn, cdout, 6);
	memcpy(cdout + 6, aud->u.umts.amf, 2);

	/* Step 4: xmac = xdout[0-8] ^ cdout[0-8] */
	xor(xmac, xdout, cdout, sizeof(xmac));

	/**
	 * Step 5: autn = sqn ^ ak || amf || mac
	 * NOTE: cdout still contains SQN from step 3
	 */
	xor(vec->autn, cdout, ak, sizeof(ak));
	memcpy(vec->autn + 6, aud->u.umts.amf, 2);
	memcpy(vec->autn + 8, xmac, sizeof(xmac));

	vec->auth_types = OSMO_AUTH_TYPE_UMTS | OSMO_AUTH_TYPE_GSM;

	return 0;
}

/* 3GPP TS 34.108, section 8.1.2.2 */
static int xor_gen_vec_auts(struct osmo_auth_vector *vec,
			    struct osmo_sub_auth_data2 *aud,
			    const uint8_t *auts,
			    const uint8_t *rand_auts,
			    const uint8_t *_rand)
{
	uint8_t xdout[16], cdout[8];
	uint8_t ak[6], xmac[8];
	uint8_t sqnms[6];

	OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_XOR_3G);

	/* Step 1: xdout = (ki or k) ^ rand */
	if (aud->type == OSMO_AUTH_TYPE_GSM)
		xor(xdout, aud->u.gsm.ki, _rand, sizeof(xdout));
	else if (aud->type == OSMO_AUTH_TYPE_UMTS) {
		if (aud->u.umts.k_len != 16)
			return -EINVAL;
		xor(xdout, aud->u.umts.k, _rand, sizeof(xdout));
	} else
		return -ENOTSUP;

	/* Step 2: ak = xdout[2-8] */
	memcpy(ak, xdout + 3, 6);

	/* sqnms = auts[0-5] ^ ak[0-5] */
	xor(sqnms, auts, ak, sizeof(ak));

	/* cdout = sqnms || amf* (dummy) */
	memcpy(cdout, sqnms, 6);
	memset(cdout + 6, 0x00, 2);

	/* xmac = xdout[0-7] ^ cdout[0-7] */
	xor(xmac, xdout, cdout, 8);

	/* Compare the last 64 bits of received AUTS with the locally-generated MAC-S */
	if (memcmp(auts + 6, xmac, 8))
		return -1;

	/* Update the "largest used SQN" from the USIM,
	 * milenage_gen_vec() will increment it. */
	aud->u.umts.sqn_ms = osmo_load64be_ext(sqnms, 6) >> 16;
	aud->u.umts.sqn = aud->u.umts.sqn_ms;

	return xor_gen_vec(vec, aud, _rand);
}

static struct osmo_auth_impl xor_alg = {
	.algo = OSMO_AUTH_ALG_XOR_3G,
	.name = "XOR-3G (libosmogsm built-in)",
	.priority = 1000,
	.gen_vec = &xor_gen_vec,
	.gen_vec_auts = &xor_gen_vec_auts,
};

static __attribute__((constructor)) void on_dso_load_xor(void)
{
	osmo_auth_register(&xor_alg);
}

/*! @} */
