 /*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 *  lws_genaes provides an abstraction api for AES in lws that works the
 *  same whether you are using openssl or mbedtls hash functions underneath.
 */
#include "private-lib-core.h"
#if !defined(LWS_HAVE_MBEDTLS_V4)
#if defined(LWS_WITH_JOSE)
#include "private-lib-jose.h"
#endif

static int operation_map[] = { MBEDTLS_AES_ENCRYPT, MBEDTLS_AES_DECRYPT };

static unsigned int
_write_pkcs7_pad(uint8_t *p, int len)
{
	unsigned int n = 0, padlen = LWS_AES_CBC_BLOCKLEN * ((unsigned int)len /
					LWS_AES_CBC_BLOCKLEN + 1) - (unsigned int)len;

	p += len;

	while (n++ < padlen)
		*p++ = (uint8_t)padlen;

	return padlen;
}

int
lws_genaes_create(struct lws_genaes_ctx *ctx, enum enum_aes_operation op,
		  enum enum_aes_modes mode, struct lws_gencrypto_keyelem *el,
		  enum enum_aes_padding padding, void *engine)
{
	int n = 0;

	ctx->mode = mode;
	ctx->k = el;
	ctx->op = (enum enum_aes_operation)operation_map[op];
	ctx->underway = 0;
	ctx->padding = padding == LWS_GAESP_WITH_PADDING;

	switch (ctx->mode) {
	case LWS_GAESM_XTS:
#if defined(MBEDTLS_CIPHER_MODE_XTS)
		mbedtls_aes_xts_init(&ctx->u.ctx_xts);
		break;
#else
		return -1;
#endif
	case LWS_GAESM_GCM:
		mbedtls_gcm_init(&ctx->u.ctx_gcm);
		n = mbedtls_gcm_setkey(&ctx->u.ctx_gcm, MBEDTLS_CIPHER_ID_AES,
				       ctx->k->buf, ctx->k->len * 8);
		if (n) {
			lwsl_notice("%s: mbedtls_gcm_setkey: -0x%x\n",
				    __func__, -n);
			return n;
		}
		return n;
	default:
		mbedtls_aes_init(&ctx->u.ctx);
		break;
	}

	switch (op) {
	case LWS_GAESO_ENC:
		if (ctx->mode == LWS_GAESM_XTS)
#if defined(MBEDTLS_CIPHER_MODE_XTS)
			n = mbedtls_aes_xts_setkey_enc(&ctx->u.ctx_xts,
						       ctx->k->buf,
						       ctx->k->len * 8);
#else
			return -1;
#endif
		else
			n = mbedtls_aes_setkey_enc(&ctx->u.ctx, ctx->k->buf,
						   ctx->k->len * 8);
		break;
	case LWS_GAESO_DEC:
		switch (ctx->mode) {
		case LWS_GAESM_XTS:
#if defined(MBEDTLS_CIPHER_MODE_XTS)
			n = mbedtls_aes_xts_setkey_dec(&ctx->u.ctx_xts,
						       ctx->k->buf,
						       ctx->k->len * 8);
			break;
#else
			return -1;
#endif

		case LWS_GAESM_CFB128:
		case LWS_GAESM_CFB8:
		case LWS_GAESM_CTR:
		case LWS_GAESM_OFB:
			n = mbedtls_aes_setkey_enc(&ctx->u.ctx, ctx->k->buf,
						   ctx->k->len * 8);
			break;
		default:
			n = mbedtls_aes_setkey_dec(&ctx->u.ctx, ctx->k->buf,
						   ctx->k->len * 8);
			break;
		}
		break;
	}

	if (n)
		lwsl_notice("%s: setting key: -0x%x\n", __func__, -n);

	return n;
}

int
lws_genaes_destroy(struct lws_genaes_ctx *ctx, unsigned char *tag, size_t tlen)
{
#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000
	size_t last_len = 0;
	uint8_t last[16];
#endif
	int n;

	if (ctx->mode == LWS_GAESM_GCM) {
#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000
		n = mbedtls_gcm_finish(&ctx->u.ctx_gcm, last, sizeof(last),
					&last_len, tag, tlen);
#else
		n = mbedtls_gcm_finish(&ctx->u.ctx_gcm, tag, tlen);
#endif

		if (n)
			lwsl_notice("%s: mbedtls_gcm_finish: -0x%x\n",
				    __func__, -n);
		if (tag && ctx->op == MBEDTLS_AES_DECRYPT && !n) {
			if (lws_timingsafe_bcmp(ctx->tag, tag, (unsigned int)ctx->taglen)) {
				lwsl_err("%s: lws_genaes_crypt tag "
					 "mismatch (bad first)\n",
						__func__);
				lwsl_hexdump_notice(tag, tlen);
				lwsl_hexdump_notice(ctx->tag, (unsigned int)ctx->taglen);
				n = -1;
			}
		}
		mbedtls_gcm_free(&ctx->u.ctx_gcm);
		return n;
	}
	if (ctx->mode == LWS_GAESM_XTS)
#if defined(MBEDTLS_CIPHER_MODE_XTS)
		mbedtls_aes_xts_free(&ctx->u.ctx_xts);
#else
		return -1;
#endif
	else
		mbedtls_aes_free(&ctx->u.ctx);

	return 0;
}

#if defined(LWS_HAVE_mbedtls_internal_aes_encrypt)
static int
lws_genaes_rfc3394_wrap(int wrap, int cek_bits, const uint8_t *kek,
			int kek_bits, const uint8_t *in, uint8_t *out)
{
	int n, m, ret = -1, c64 = cek_bits / 64;
	mbedtls_aes_context ctx;
	uint8_t a[8], b[16];

	/*
	 * notice the KEK key used to perform the wrapping or unwrapping is
	 * always the size of the AES key used, eg, A128KW == 128 bits.  The
	 * key being wrapped or unwrapped may be larger and is set by the
	 * 'bits' parameter.
	 *
	 * If it's larger than the KEK key size bits, we iterate over it
	 */

	mbedtls_aes_init(&ctx);

	if (wrap) {
		/*
		 * The inputs to the key wrapping process are the KEK and the
		 * plaintext to be wrapped.  The plaintext consists of n 64-bit
		 * blocks, containing the key data being wrapped.
		 *
		 * Inputs:      Plaintext, n 64-bit values {P1, P2, ..., Pn},
		 *		and Key, K (the KEK).
		 * Outputs:     Ciphertext, (n+1) 64-bit values
		 *		{C0, C1, ..., Cn}.
		 *
		 * The default initial value (IV) is defined to be the
		 * hexadecimal constant:
		 *
		 * A[0] = IV = A6A6A6A6A6A6A6A6
		 */
		memset(out, 0xa6, 8);
		memcpy(out + 8, in, 8 * (unsigned int)c64);
		n = mbedtls_aes_setkey_enc(&ctx, kek, (unsigned int)kek_bits);
	} else {
		/*
		 * 2.2.2 Key Unwrap
		 *
		 * The inputs to the unwrap process are the KEK and (n+1)
		 * 64-bit blocks of ciphertext consisting of previously
		 * wrapped key.  It returns n blocks of plaintext consisting
		 * of the n 64-bit blocks of the decrypted key data.
		 *
		 * Inputs:  Ciphertext, (n+1) 64-bit values {C0, C1, ..., Cn},
		 * and Key, K (the KEK).
		 *
		 * Outputs: Plaintext, n 64-bit values {P1, P2, ..., Pn}.
		 */
		memcpy(a, in, 8);
		memcpy(out, in + 8, 8 * (unsigned int)c64);
		n = mbedtls_aes_setkey_dec(&ctx, kek, (unsigned int)kek_bits);
	}

	if (n < 0) {
		lwsl_err("%s: setkey failed\n", __func__);
		goto bail;
	}

	if (wrap) {
		for (n = 0; n <= 5; n++) {
			uint8_t *r = out + 8;
			for (m = 1; m <= c64; m++) {
				memcpy(b, out, 8);
				memcpy(b + 8, r, 8);
				if (mbedtls_internal_aes_encrypt(&ctx, b, b))
					goto bail;

				memcpy(out, b, 8);
				out[7] ^= (uint8_t)(c64 * n + m);
				memcpy(r, b + 8, 8);
				r += 8;
			}
		}
		ret = 0;
	} else {
		/*
		 *
		 */
		for (n = 5; n >= 0; n--) {
			uint8_t *r = out + (c64 - 1) * 8;
			for (m = c64; m >= 1; m--) {
				memcpy(b, a, 8);
				b[7] ^= (uint8_t)(c64 * n + m);
				memcpy(b + 8, r, 8);
				if (mbedtls_internal_aes_decrypt(&ctx, b, b))
					goto bail;

				memcpy(a, b, 8);
				memcpy(r, b + 8, 8);
				r -= 8;
			}
		}

		ret = 0;
		for (n = 0; n < 8; n++)
			if (a[n] != 0xa6)
				ret = -1;
	}

bail:
	if (ret)
		lwsl_notice("%s: failed\n", __func__);
	mbedtls_aes_free(&ctx);

	return ret;
}
#endif

int
lws_genaes_crypt(struct lws_genaes_ctx *ctx, const uint8_t *in, size_t len,
		 uint8_t *out, uint8_t *iv_or_nonce_ctr_or_data_unit_16,
		 uint8_t *stream_block_16, size_t *nc_or_iv_off, int taglen)
{
	uint8_t iv[LWS_JWE_AES_IV_BYTES], sb[16];
	int n = 0;

	switch (ctx->mode) {
	case LWS_GAESM_KW:
#if defined(LWS_HAVE_mbedtls_internal_aes_encrypt)
		/* a key of length ctx->k->len is wrapped by a 128-bit KEK */
		n = lws_genaes_rfc3394_wrap(ctx->op == MBEDTLS_AES_ENCRYPT,
				(ctx->op == MBEDTLS_AES_ENCRYPT ? (int)len * 8 :
						((int)len - 8) * 8), ctx->k->buf,
						(int)ctx->k->len * 8,
				in, out);
		break;
#else
		lwsl_err("%s: your mbedtls is too old\n", __func__);
		return -1;
#endif
	case LWS_GAESM_CBC:
		memcpy(iv, iv_or_nonce_ctr_or_data_unit_16, 16);

		/*
		 * If encrypting, we do the PKCS#7 padding.
		 * During decryption, the caller will need to unpad.
		 */
		if (ctx->padding && ctx->op == MBEDTLS_AES_ENCRYPT) {
			/*
			 * Since we don't want to burden the caller with
			 * the over-allocation at the end of the input,
			 * we have to allocate a temp with space for it
			 */
			uint8_t *padin = (uint8_t *)lws_malloc(
				lws_gencrypto_padded_length(LWS_AES_CBC_BLOCKLEN, len),
								__func__);

			if (!padin)
				return -1;

			memcpy(padin, in, len);
			len += _write_pkcs7_pad((uint8_t *)padin, (int)len);
			n = mbedtls_aes_crypt_cbc(&ctx->u.ctx, (int)ctx->op, len, iv,
						  padin, out);
			lws_free(padin);
		} else
			n = mbedtls_aes_crypt_cbc(&ctx->u.ctx, (int)ctx->op, len, iv,
                                      in, out);

		break;

	case LWS_GAESM_CFB128:
		memcpy(iv, iv_or_nonce_ctr_or_data_unit_16, 16);
		n = mbedtls_aes_crypt_cfb128(&ctx->u.ctx, (int)ctx->op, len,
					     nc_or_iv_off, iv, in, out);
		break;

	case LWS_GAESM_CFB8:
		memcpy(iv, iv_or_nonce_ctr_or_data_unit_16, 16);
		n = mbedtls_aes_crypt_cfb8(&ctx->u.ctx, (int)ctx->op, len, iv,
					   in, out);
		break;

	case LWS_GAESM_CTR:
		memcpy(iv, iv_or_nonce_ctr_or_data_unit_16, 16);
		memcpy(sb, stream_block_16, 16);
		n = mbedtls_aes_crypt_ctr(&ctx->u.ctx, len, nc_or_iv_off,
					  iv, sb, in, out);
		memcpy(iv_or_nonce_ctr_or_data_unit_16, iv, 16);
		memcpy(stream_block_16, sb, 16);
		break;

	case LWS_GAESM_ECB:
		n = mbedtls_aes_crypt_ecb(&ctx->u.ctx, (int)ctx->op, in, out);
		break;

	case LWS_GAESM_OFB:
#if defined(MBEDTLS_CIPHER_MODE_OFB)
		memcpy(iv, iv_or_nonce_ctr_or_data_unit_16, 16);
		n = mbedtls_aes_crypt_ofb(&ctx->u.ctx, len, nc_or_iv_off, iv,
					  in, out);
		break;
#else
		return -1;
#endif

	case LWS_GAESM_XTS:
#if defined(MBEDTLS_CIPHER_MODE_XTS)
		memcpy(iv, iv_or_nonce_ctr_or_data_unit_16, 16);
		n = mbedtls_aes_crypt_xts(&ctx->u.ctx_xts, (int)ctx->op, len, iv,
					  in, out);
		break;
#else
		return -1;
#endif
	case LWS_GAESM_GCM:
		if (!ctx->underway) {
			ctx->underway = 1;

			memcpy(ctx->tag, stream_block_16, (unsigned int)taglen);
			ctx->taglen = taglen;

			/*
			 * iv:                   iv_or_nonce_ctr_or_data_unit_16
			 * iv_len:               *nc_or_iv_off
			 * stream_block_16:      pointer to tag
			 * additional data:      in
			 * additional data len:  len
			 */

#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000
			n = mbedtls_gcm_starts(&ctx->u.ctx_gcm, (int)ctx->op,
					       iv_or_nonce_ctr_or_data_unit_16,
					       *nc_or_iv_off);
			if (!n)
				n = mbedtls_gcm_update_ad(&ctx->u.ctx_gcm,
							  in, len);
#else
			n = mbedtls_gcm_starts(&ctx->u.ctx_gcm, (int)ctx->op,
					       iv_or_nonce_ctr_or_data_unit_16,
					       *nc_or_iv_off, in, len);
#endif
			if (n) {
				lwsl_notice("%s: mbedtls_gcm_starts: -0x%x\n",
					    __func__, -n);

				return -1;
			}
			break;
		}

#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000
		{
			size_t al;

			n = mbedtls_gcm_update(&ctx->u.ctx_gcm, in, len, out, len, &al);
		}
#else
		n = mbedtls_gcm_update(&ctx->u.ctx_gcm, len, in, out);
#endif
		if (n) {
			lwsl_notice("%s: mbedtls_gcm_update: -0x%x\n",
				    __func__, -n);

			return -1;
		}
		break;
	}

	if (n) {
		lwsl_notice("%s: failed: -0x%x, len %d\n", __func__, -n, (int)len);

		return -1;
	}

	return 0;
}
#else /* LWS_HAVE_MBEDTLS_V4 */

#if defined(LWS_WITH_JOSE)
#include "private-lib-jose.h"
#endif

#include <psa/crypto.h>

static unsigned int
_write_pkcs7_pad(uint8_t *p, int len)
{
	unsigned int n = 0, padlen = LWS_AES_CBC_BLOCKLEN * ((unsigned int)len /
					LWS_AES_CBC_BLOCKLEN + 1) - (unsigned int)len;

	p += len;

	while (n++ < padlen)
		*p++ = (uint8_t)padlen;

	return padlen;
}

int
lws_genaes_create(struct lws_genaes_ctx *ctx, enum enum_aes_operation op,
		  enum enum_aes_modes mode, struct lws_gencrypto_keyelem *el,
		  enum enum_aes_padding padding, void *engine)
{
	psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
	psa_algorithm_t alg;
	psa_key_usage_t usage;

	ctx->mode = mode;
	ctx->k = el;
	ctx->op = op; /* We use the enum_aes_operation directly */
	ctx->underway = 0;
	ctx->padding = padding == LWS_GAESP_WITH_PADDING;
	ctx->cipher_ctx = psa_cipher_operation_init();
	ctx->aead_ctx = psa_aead_operation_init();
	ctx->key_id = 0;

	usage = (op == LWS_GAESO_ENC) ? PSA_KEY_USAGE_ENCRYPT : PSA_KEY_USAGE_DECRYPT;

	switch (ctx->mode) {
	case LWS_GAESM_CBC:
		alg = PSA_ALG_CBC_NO_PADDING;
		break;
	case LWS_GAESM_CFB128:
		alg = PSA_ALG_CFB;
		break;
	case LWS_GAESM_CFB8:
		lwsl_err("%s: AES-CFB8 not supported in V4\n", __func__);
		return -1;
	case LWS_GAESM_CTR:
		alg = PSA_ALG_CTR;
		break;
	case LWS_GAESM_ECB:
		alg = PSA_ALG_ECB_NO_PADDING;
		break;
	case LWS_GAESM_OFB:
		alg = PSA_ALG_OFB;
		break;
	case LWS_GAESM_XTS:
		lwsl_err("%s: AES-XTS not supported in V4\n", __func__);
		return -1;
	case LWS_GAESM_GCM:
		alg = PSA_ALG_GCM;
		break;
	case LWS_GAESM_KW:
		/* Not supported directly via cipher, but we can implement the wrapper */
		lwsl_err("%s: AES-KW not yet supported in V4\n", __func__);
		return -1;
	default:
		return -1;
	}

	ctx->alg = alg;

	psa_set_key_type(&attr, PSA_KEY_TYPE_AES);
	psa_set_key_bits(&attr, el->len * 8);
	psa_set_key_usage_flags(&attr, usage);
	psa_set_key_algorithm(&attr, alg);

	if (psa_import_key(&attr, el->buf, el->len, &ctx->key_id) != PSA_SUCCESS) {
		lwsl_err("%s: psa_import_key failed\n", __func__);
		return -1;
	}

	return 0;
}

int
lws_genaes_destroy(struct lws_genaes_ctx *ctx, unsigned char *tag, size_t tlen)
{
	int ret = 0;

	if (ctx->mode == LWS_GAESM_GCM) {
		if (ctx->underway) {
			size_t olen;
			if (ctx->op == LWS_GAESO_ENC) {
				if (psa_aead_finish(&ctx->aead_ctx, NULL, 0, &olen, tag, tlen, &olen) != PSA_SUCCESS)
					ret = -1;
			} else {
				if (psa_aead_verify(&ctx->aead_ctx, NULL, 0, &olen, tag, tlen) != PSA_SUCCESS)
					ret = -1;
			}
			psa_aead_abort(&ctx->aead_ctx);
		}
	} else {
		psa_cipher_abort(&ctx->cipher_ctx);
	}

	if (ctx->key_id) {
		psa_destroy_key(ctx->key_id);
		ctx->key_id = 0;
	}

	return ret;
}

int
lws_genaes_crypt(struct lws_genaes_ctx *ctx, const uint8_t *in, size_t len,
		 uint8_t *out, uint8_t *iv_or_nonce_ctr_or_data_unit_16,
		 uint8_t *stream_block_16, size_t *nc_or_iv_off, int taglen)
{
	size_t olen;
	psa_status_t status;

	switch (ctx->mode) {
	case LWS_GAESM_KW:
		lwsl_err("%s: AES-KW not implemented in V4\n", __func__);
		return -1;

	case LWS_GAESM_CBC:
	case LWS_GAESM_CFB128:
	case LWS_GAESM_CFB8:
	case LWS_GAESM_OFB:
	case LWS_GAESM_XTS:
	case LWS_GAESM_ECB:
	case LWS_GAESM_CTR:
		if (ctx->op == LWS_GAESO_ENC)
			status = psa_cipher_encrypt_setup(&ctx->cipher_ctx, ctx->key_id, ctx->alg);
		else
			status = psa_cipher_decrypt_setup(&ctx->cipher_ctx, ctx->key_id, ctx->alg);
		
		if (status != PSA_SUCCESS) return -1;

		if (ctx->mode != LWS_GAESM_ECB) {
			status = psa_cipher_set_iv(&ctx->cipher_ctx, iv_or_nonce_ctr_or_data_unit_16, 16);
			if (status != PSA_SUCCESS) {
				lwsl_err("%s: set_iv failed %d\n", __func__, (int)status);
				return -1;
			}
		}

		if (ctx->padding && ctx->op == LWS_GAESO_ENC && ctx->mode == LWS_GAESM_CBC) {
			uint8_t *padin = (uint8_t *)lws_malloc(
				lws_gencrypto_padded_length(LWS_AES_CBC_BLOCKLEN, len),
								__func__);
			if (!padin) {
				psa_cipher_abort(&ctx->cipher_ctx);
				return -1;
			}
			memcpy(padin, in, len);
			len += _write_pkcs7_pad((uint8_t *)padin, (int)len);
			status = psa_cipher_update(&ctx->cipher_ctx, padin, len, out, len + 16, &olen);
			lws_free(padin);
		} else {
			status = psa_cipher_update(&ctx->cipher_ctx, in, len, out, len + 16, &olen);
		}

		if (status != PSA_SUCCESS) {
			psa_cipher_abort(&ctx->cipher_ctx);
			return -1;
		}

		/* finish */
		status = psa_cipher_finish(&ctx->cipher_ctx, out + olen, 16, &olen);
		if (status != PSA_SUCCESS) return -1;
		break;

	case LWS_GAESM_GCM:
		if (!ctx->underway) {
			ctx->underway = 1;
			ctx->taglen = taglen;

			if (ctx->op == LWS_GAESO_ENC)
				status = psa_aead_encrypt_setup(&ctx->aead_ctx, ctx->key_id, ctx->alg);
			else
				status = psa_aead_decrypt_setup(&ctx->aead_ctx, ctx->key_id, ctx->alg);
			
			if (status != PSA_SUCCESS) return -1;

			status = psa_aead_set_nonce(&ctx->aead_ctx, iv_or_nonce_ctr_or_data_unit_16, *nc_or_iv_off);
			if (status != PSA_SUCCESS) return -1;

			if (len) {
				status = psa_aead_update_ad(&ctx->aead_ctx, in, len);
				if (status != PSA_SUCCESS) return -1;
			}
			break;
		}

		status = psa_aead_update(&ctx->aead_ctx, in, len, out, len + 16, &olen);
		if (status != PSA_SUCCESS) return -1;
		break;
	}

	return 0;
}
#endif
