/* =========================================================================
 * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_dh.c $
 * $Revision: #3 $
 * $Date: 2010/09/28 $
 * $Change: 1596182 $
 *
 * Synopsys Portability Library Software and documentation
 * (hereinafter, "Software") is an Unsupported proprietary work of
 * Synopsys, Inc. unless otherwise expressly agreed to in writing
 * between Synopsys and you.
 *
 * The Software IS NOT an item of Licensed Software or Licensed Product
 * under any End User Software License Agreement or Agreement for
 * Licensed Product with Synopsys or any supplement thereto. You are
 * permitted to use and redistribute this Software in source and binary
 * forms, with or without modification, provided that redistributions
 * of source code must retain this notice. You may not view, use,
 * disclose, copy or distribute this file or any information contained
 * herein except pursuant to this license grant from Synopsys. If you
 * do not agree with this notice, including the disclaimer below, then
 * you are not authorized to use the Software.
 *
 * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS"
 * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL
 * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 * ========================================================================= */
#ifdef DWC_CRYPTOLIB

#ifndef CONFIG_MACH_IPMATE

#include "dwc_dh.h"
#include "dwc_modpow.h"

#ifdef DEBUG
/* This function prints out a buffer in the format described in the Association
 * Model specification. */
static void dh_dump(char *str, void *_num, int len)
{
	uint8_t *num = _num;
	int i;
	DWC_PRINTF("%s\n", str);
	for (i = 0; i < len; i ++) {
		DWC_PRINTF("%02x", num[i]);
		if (((i + 1) % 2) == 0) DWC_PRINTF(" ");
		if (((i + 1) % 26) == 0) DWC_PRINTF("\n");
	}

	DWC_PRINTF("\n");
}
#else
#define dh_dump(_x...) do {; } while(0)
#endif

/* Constant g value */
static __u32 dh_g[] = {
	0x02000000,
};

/* Constant p value */
static __u32 dh_p[] = {
	0xFFFFFFFF, 0xFFFFFFFF, 0xA2DA0FC9, 0x34C26821, 0x8B62C6C4, 0xD11CDC80, 0x084E0229, 0x74CC678A,
	0xA6BE0B02, 0x229B133B, 0x79084A51, 0xDD04348E, 0xB31995EF, 0x1B433ACD, 0x6D0A2B30, 0x37145FF2,
	0x6D35E14F, 0x45C2516D, 0x76B585E4, 0xC67E5E62, 0xE9424CF4, 0x6BED37A6, 0xB65CFF0B, 0xEDB706F4,
	0xFB6B38EE, 0xA59F895A, 0x11249FAE, 0xE61F4B7C, 0x51662849, 0x3D5BE4EC, 0xB87C00C2, 0x05BF63A1,
	0x3648DA98, 0x9AD3551C, 0xA83F1669, 0x5FCF24FD, 0x235D6583, 0x96ADA3DC, 0x56F3621C, 0xBB528520,
	0x0729D59E, 0x6D969670, 0x4E350C67, 0x0498BC4A, 0x086C74F1, 0x7C2118CA, 0x465E9032, 0x3BCE362E,
	0x2C779EE3, 0x03860E18, 0xA283279B, 0x8FA207EC, 0xF05DC5B5, 0xC9524C6F, 0xF6CB2BDE, 0x18175895,
	0x7C499539, 0xE56A95EA, 0x1826D215, 0x1005FA98, 0x5A8E7215, 0x2DC4AA8A, 0x0D1733AD, 0x337A5004,
	0xAB2155A8, 0x64BA1CDF, 0x0485FBEC, 0x0AEFDB58, 0x5771EA8A, 0x7D0C065D, 0x850F97B3, 0xC7E4E1A6,
	0x8CAEF5AB, 0xD73309DB, 0xE0948C1E, 0x9D61254A, 0x26D2E3CE, 0x6BEED21A, 0x06FA2FF1, 0x64088AD9,
	0x730276D8, 0x646AC83E, 0x182B1F52, 0x0C207B17, 0x5717E1BB, 0x6C5D617A, 0xC0880977, 0xE246D9BA,
	0xA04FE208, 0x31ABE574, 0xFC5BDB43, 0x8E10FDE0, 0x20D1824B, 0xCAD23AA9, 0xFFFFFFFF, 0xFFFFFFFF,
};

static void dh_swap_bytes(void *_in, void *_out, uint32_t len)
{
	uint8_t *in = _in;
	uint8_t *out = _out;
	int i;
	for (i=0; i<len; i++) {
		out[i] = in[len-1-i];
	}
}

/* Computes the modular exponentiation (num^exp % mod).  num, exp, and mod are
 * big endian numbers of size len, in bytes.  Each len value must be a multiple
 * of 4. */
int dwc_dh_modpow(void *mem_ctx, void *num, uint32_t num_len,
		  void *exp, uint32_t exp_len,
		  void *mod, uint32_t mod_len,
		  void *out)
{
	/* modpow() takes little endian numbers.  AM uses big-endian.  This
	 * function swaps bytes of numbers before passing onto modpow. */

	int retval = 0;
	uint32_t *result;

	uint32_t *bignum_num = dwc_alloc(mem_ctx, num_len + 4);
	uint32_t *bignum_exp = dwc_alloc(mem_ctx, exp_len + 4);
	uint32_t *bignum_mod = dwc_alloc(mem_ctx, mod_len + 4);

	dh_swap_bytes(num, &bignum_num[1], num_len);
	bignum_num[0] = num_len / 4;

	dh_swap_bytes(exp, &bignum_exp[1], exp_len);
	bignum_exp[0] = exp_len / 4;

	dh_swap_bytes(mod, &bignum_mod[1], mod_len);
	bignum_mod[0] = mod_len / 4;

	result = dwc_modpow(mem_ctx, bignum_num, bignum_exp, bignum_mod);
	if (!result) {
		retval = -1;
		goto dh_modpow_nomem;
	}

	dh_swap_bytes(&result[1], out, result[0] * 4);
	dwc_free(mem_ctx, result);

 dh_modpow_nomem:
	dwc_free(mem_ctx, bignum_num);
	dwc_free(mem_ctx, bignum_exp);
	dwc_free(mem_ctx, bignum_mod);
	return retval;
}


int dwc_dh_pk(void *mem_ctx, uint8_t nd, uint8_t *exp, uint8_t *pk, uint8_t *hash)
{
	int retval;
	uint8_t m3[385];

#ifndef DH_TEST_VECTORS
	DWC_RANDOM_BYTES(exp, 32);
#endif

	/* Compute the pkd */
	if ((retval = dwc_dh_modpow(mem_ctx, dh_g, 4,
				    exp, 32,
				    dh_p, 384, pk))) {
		return retval;
	}

	m3[384] = nd;
	DWC_MEMCPY(&m3[0], pk, 384);
	DWC_SHA256(m3, 385, hash);

	dh_dump("PK", pk, 384);
	dh_dump("SHA-256(M3)", hash, 32);
	return 0;
}

int dwc_dh_derive_keys(void *mem_ctx, uint8_t nd, uint8_t *pkh, uint8_t *pkd,
		       uint8_t *exp, int is_host,
		       char *dd, uint8_t *ck, uint8_t *kdk)
{
	int retval;
	uint8_t mv[784];
	uint8_t sha_result[32];
	uint8_t dhkey[384];
	uint8_t shared_secret[384];
	char *message;
	uint32_t vd;

	uint8_t *pk;

	if (is_host) {
		pk = pkd;
	}
	else {
		pk = pkh;
	}

	if ((retval = dwc_dh_modpow(mem_ctx, pk, 384,
				    exp, 32,
				    dh_p, 384, shared_secret))) {
		return retval;
	}
	dh_dump("Shared Secret", shared_secret, 384);

	DWC_SHA256(shared_secret, 384, dhkey);
	dh_dump("DHKEY", dhkey, 384);

	DWC_MEMCPY(&mv[0], pkd, 384);
	DWC_MEMCPY(&mv[384], pkh, 384);
	DWC_MEMCPY(&mv[768], "displayed digest", 16);
	dh_dump("MV", mv, 784);

	DWC_SHA256(mv, 784, sha_result);
	dh_dump("SHA-256(MV)", sha_result, 32);
	dh_dump("First 32-bits of SHA-256(MV)", sha_result, 4);

	dh_swap_bytes(sha_result, &vd, 4);
#ifdef DEBUG
	DWC_PRINTF("Vd (decimal) = %d\n", vd);
#endif

	switch (nd) {
	case 2:
		vd = vd % 100;
		DWC_SPRINTF(dd, "%02d", vd);
		break;
	case 3:
		vd = vd % 1000;
		DWC_SPRINTF(dd, "%03d", vd);
		break;
	case 4:
		vd = vd % 10000;
		DWC_SPRINTF(dd, "%04d", vd);
		break;
	}
#ifdef DEBUG
	DWC_PRINTF("Display Digits: %s\n", dd);
#endif

	message = "connection key";
	DWC_HMAC_SHA256(message, DWC_STRLEN(message), dhkey, 32, sha_result);
	dh_dump("HMAC(SHA-256, DHKey, connection key)", sha_result, 32);
	DWC_MEMCPY(ck, sha_result, 16);

	message = "key derivation key";
	DWC_HMAC_SHA256(message, DWC_STRLEN(message), dhkey, 32, sha_result);
	dh_dump("HMAC(SHA-256, DHKey, key derivation key)", sha_result, 32);
	DWC_MEMCPY(kdk, sha_result, 32);

	return 0;
}


#ifdef DH_TEST_VECTORS

static __u8 dh_a[] = {
	0x44, 0x00, 0x51, 0xd6,
	0xf0, 0xb5, 0x5e, 0xa9,
	0x67, 0xab, 0x31, 0xc6,
	0x8a, 0x8b, 0x5e, 0x37,
	0xd9, 0x10, 0xda, 0xe0,
	0xe2, 0xd4, 0x59, 0xa4,
	0x86, 0x45, 0x9c, 0xaa,
	0xdf, 0x36, 0x75, 0x16,
};

static __u8 dh_b[] = {
	0x5d, 0xae, 0xc7, 0x86,
	0x79, 0x80, 0xa3, 0x24,
	0x8c, 0xe3, 0x57, 0x8f,
	0xc7, 0x5f, 0x1b, 0x0f,
	0x2d, 0xf8, 0x9d, 0x30,
	0x6f, 0xa4, 0x52, 0xcd,
	0xe0, 0x7a, 0x04, 0x8a,
	0xde, 0xd9, 0x26, 0x56,
};

void dwc_run_dh_test_vectors(void *mem_ctx)
{
	uint8_t pkd[384];
	uint8_t pkh[384];
	uint8_t hashd[32];
	uint8_t hashh[32];
	uint8_t ck[16];
	uint8_t kdk[32];
	char dd[5];

	DWC_PRINTF("\n\n\nDH_TEST_VECTORS\n\n");

	/* compute the PKd and SHA-256(PKd || Nd) */
	DWC_PRINTF("Computing PKd\n");
	dwc_dh_pk(mem_ctx, 2, dh_a, pkd, hashd);

	/* compute the PKd and SHA-256(PKh || Nd) */
	DWC_PRINTF("Computing PKh\n");
	dwc_dh_pk(mem_ctx, 2, dh_b, pkh, hashh);

	/* compute the dhkey */
	dwc_dh_derive_keys(mem_ctx, 2, pkh, pkd, dh_a, 0, dd, ck, kdk);
}
#endif /* DH_TEST_VECTORS */

#endif /* !CONFIG_MACH_IPMATE */

#endif /* DWC_CRYPTOLIB */
