/*
 * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
 *
 * 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.
 */

#include "t_inner.h"

/*
 * Implementation Notes
 * ====================
 *
 * Since CTR and GHASH implementations can handle only full blocks, a
 * 16-byte buffer (buf[]) is maintained in the context:
 *
 *  - When processing AAD, buf[] contains the 0-15 unprocessed bytes.
 *
 *  - When doing CTR encryption / decryption, buf[] contains the AES output
 *    for the last partial block, to be used with the next few bytes of
 *    data, as well as the already encrypted bytes. For instance, if the
 *    processed data length so far is 21 bytes, then buf[0..4] contains
 *    the five last encrypted bytes, and buf[5..15] contains the next 11
 *    AES output bytes to be XORed with the next 11 bytes of input.
 *
 *    The recorded AES output bytes are used to complete the block when
 *    the corresponding bytes are obtained. Note that buf[] always
 *    contains the _encrypted_ bytes, whether we apply encryption or
 *    decryption: these bytes are used as input to GHASH when the block
 *    is complete.
 *
 * In both cases, the low bits of the data length counters (count_aad,
 * count_ctr) are used to work out the current situation.
 */

/* see bearssl_aead.h */
void
br_gcm_init(br_gcm_context *ctx, const br_block_ctr_class **bctx, br_ghash gh)
{
	unsigned char iv[12];

	ctx->vtable = &br_gcm_vtable;
	ctx->bctx = bctx;
	ctx->gh = gh;

	/*
	 * The GHASH key h[] is the raw encryption of the all-zero
	 * block. Since we only have a CTR implementation, we use it
	 * with an all-zero IV and a zero counter, to CTR-encrypt an
	 * all-zero block.
	 */
	memset(ctx->h, 0, sizeof ctx->h);
	memset(iv, 0, sizeof iv);
	(*bctx)->run(bctx, iv, 0, ctx->h, sizeof ctx->h);
}

/* see bearssl_aead.h */
void
br_gcm_reset(br_gcm_context *ctx, const void *iv, size_t len)
{
	/*
	 * If the provided nonce is 12 bytes, then this is the initial
	 * IV for CTR mode; it will be used with a counter that starts
	 * at 2 (value 1 is for encrypting the GHASH output into the tag).
	 *
	 * If the provided nonce has any other length, then it is hashed
	 * (with GHASH) into a 16-byte value that will be the IV for CTR
	 * (both 12-byte IV and 32-bit counter).
	 */
	if (len == 12) {
		memcpy(ctx->j0_1, iv, 12);
		ctx->j0_2 = 1;
	} else {
		unsigned char ty[16], tmp[16];

		memset(ty, 0, sizeof ty);
		ctx->gh(ty, ctx->h, iv, len);
		memset(tmp, 0, 8);
		br_enc64be(tmp + 8, (uint64_t)len << 3);
		ctx->gh(ty, ctx->h, tmp, 16);
		memcpy(ctx->j0_1, ty, 12);
		ctx->j0_2 = br_dec32be(ty + 12);
	}
	ctx->jc = ctx->j0_2 + 1;
	memset(ctx->y, 0, sizeof ctx->y);
	ctx->count_aad = 0;
	ctx->count_ctr = 0;
}

/* see bearssl_aead.h */
void
br_gcm_aad_inject(br_gcm_context *ctx, const void *data, size_t len)
{
	size_t ptr, dlen;

	ptr = (size_t)ctx->count_aad & (size_t)15;
	if (ptr != 0) {
		/*
		 * If there is a partial block, then we first try to
		 * complete it.
		 */
		size_t clen;

		clen = 16 - ptr;
		if (len < clen) {
			memcpy(ctx->buf + ptr, data, len);
			ctx->count_aad += (uint64_t)len;
			return;
		}
		memcpy(ctx->buf + ptr, data, clen);
		ctx->gh(ctx->y, ctx->h, ctx->buf, 16);
		data = (const unsigned char *)data + clen;
		len -= clen;
		ctx->count_aad += (uint64_t)clen;
	}

	/*
	 * Now AAD is aligned on a 16-byte block (with regards to GHASH).
	 * We process all complete blocks, and save the last partial
	 * block.
	 */
	dlen = len & ~(size_t)15;
	ctx->gh(ctx->y, ctx->h, data, dlen);
	memcpy(ctx->buf, (const unsigned char *)data + dlen, len - dlen);
	ctx->count_aad += (uint64_t)len;
}

/* see bearssl_aead.h */
void
br_gcm_flip(br_gcm_context *ctx)
{
	/*
	 * We complete the GHASH computation if there is a partial block.
	 * The GHASH implementation automatically applies padding with
	 * zeros.
	 */
	size_t ptr;

	ptr = (size_t)ctx->count_aad & (size_t)15;
	if (ptr != 0) {
		ctx->gh(ctx->y, ctx->h, ctx->buf, ptr);
	}
}

/* see bearssl_aead.h */
void
br_gcm_run(br_gcm_context *ctx, int encrypt, void *data, size_t len)
{
	unsigned char *buf;
	size_t ptr, dlen;

	buf = data;
	ptr = (size_t)ctx->count_ctr & (size_t)15;
	if (ptr != 0) {
		/*
		 * If we have a partial block, then we try to complete it.
		 */
		size_t u, clen;

		clen = 16 - ptr;
		if (len < clen) {
			clen = len;
		}
		for (u = 0; u < clen; u ++) {
			unsigned x, y;

			x = buf[u];
			y = x ^ ctx->buf[ptr + u];
			ctx->buf[ptr + u] = encrypt ? y : x;
			buf[u] = y;
		}
		ctx->count_ctr += (uint64_t)clen;
		buf += clen;
		len -= clen;
		if (ptr + clen < 16) {
			return;
		}
		ctx->gh(ctx->y, ctx->h, ctx->buf, 16);
	}

	/*
	 * Process full blocks.
	 */
	dlen = len & ~(size_t)15;
	if (!encrypt) {
		ctx->gh(ctx->y, ctx->h, buf, dlen);
	}
	ctx->jc = (*ctx->bctx)->run(ctx->bctx, ctx->j0_1, ctx->jc, buf, dlen);
	if (encrypt) {
		ctx->gh(ctx->y, ctx->h, buf, dlen);
	}
	buf += dlen;
	len -= dlen;
	ctx->count_ctr += (uint64_t)dlen;

	if (len > 0) {
		/*
		 * There is a partial block.
		 */
		size_t u;

		memset(ctx->buf, 0, sizeof ctx->buf);
		ctx->jc = (*ctx->bctx)->run(ctx->bctx, ctx->j0_1,
			ctx->jc, ctx->buf, 16);
		for (u = 0; u < len; u ++) {
			unsigned x, y;

			x = buf[u];
			y = x ^ ctx->buf[u];
			ctx->buf[u] = encrypt ? y : x;
			buf[u] = y;
		}
		ctx->count_ctr += (uint64_t)len;
	}
}

/* see bearssl_aead.h */
void
br_gcm_get_tag(br_gcm_context *ctx, void *tag)
{
	size_t ptr;
	unsigned char tmp[16];

	ptr = (size_t)ctx->count_ctr & (size_t)15;
	if (ptr > 0) {
		/*
		 * There is a partial block: encrypted/decrypted data has
		 * been produced, but the encrypted bytes must still be
		 * processed by GHASH.
		 */
		ctx->gh(ctx->y, ctx->h, ctx->buf, ptr);
	}

	/*
	 * Final block for GHASH: the AAD and plaintext lengths (in bits).
	 */
	br_enc64be(tmp, ctx->count_aad << 3);
	br_enc64be(tmp + 8, ctx->count_ctr << 3);
	ctx->gh(ctx->y, ctx->h, tmp, 16);

	/*
	 * Tag is the GHASH output XORed with the encryption of the
	 * nonce with the initial counter value.
	 */
	memcpy(tag, ctx->y, 16);
	(*ctx->bctx)->run(ctx->bctx, ctx->j0_1, ctx->j0_2, tag, 16);
}

/* see bearssl_aead.h */
void
br_gcm_get_tag_trunc(br_gcm_context *ctx, void *tag, size_t len)
{
	unsigned char tmp[16];

	br_gcm_get_tag(ctx, tmp);
	memcpy(tag, tmp, len);
}

/* see bearssl_aead.h */
uint32_t
br_gcm_check_tag_trunc(br_gcm_context *ctx, const void *tag, size_t len)
{
	unsigned char tmp[16];
	size_t u;
	int x;

	br_gcm_get_tag(ctx, tmp);
	x = 0;
	for (u = 0; u < len; u ++) {
		x |= tmp[u] ^ ((const unsigned char *)tag)[u];
	}
	return EQ0(x);
}

/* see bearssl_aead.h */
uint32_t
br_gcm_check_tag(br_gcm_context *ctx, const void *tag)
{
	return br_gcm_check_tag_trunc(ctx, tag, 16);
}

/* see bearssl_aead.h */
const br_aead_class br_gcm_vtable PROGMEM = {
	16,
	(void (*)(const br_aead_class **, const void *, size_t))
		&br_gcm_reset,
	(void (*)(const br_aead_class **, const void *, size_t))
		&br_gcm_aad_inject,
	(void (*)(const br_aead_class **))
		&br_gcm_flip,
	(void (*)(const br_aead_class **, int, void *, size_t))
		&br_gcm_run,
	(void (*)(const br_aead_class **, void *))
		&br_gcm_get_tag,
	(uint32_t (*)(const br_aead_class **, const void *))
		&br_gcm_check_tag,
	(void (*)(const br_aead_class **, void *, size_t))
		&br_gcm_get_tag_trunc,
	(uint32_t (*)(const br_aead_class **, const void *, size_t))
		&br_gcm_check_tag_trunc
};
