/* $OpenBSD: gcm128.c,v 1.55 2026/01/17 14:30:37 jsing Exp $ */
/* ====================================================================
 * Copyright (c) 2010 The OpenSSL Project.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the OpenSSL Project
 *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
 *
 * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    openssl-core@openssl.org.
 *
 * 5. Products derived from this software may not be called "OpenSSL"
 *    nor may "OpenSSL" appear in their names without prior written
 *    permission of the OpenSSL Project.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the OpenSSL Project
 *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
 *
 * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
 * ITS CONTRIBUTORS 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.
 * ====================================================================
 */

#include <string.h>

#include <openssl/crypto.h>

#include "crypto_arch.h"
#include "crypto_internal.h"
#include "modes_local.h"

void
gcm_init_4bit(u128 Htable[16], uint64_t H[2])
{
	u128 V;
	uint64_t T;
	int i;

	Htable[0].hi = 0;
	Htable[0].lo = 0;
	V.hi = H[0];
	V.lo = H[1];

	for (Htable[8] = V, i = 4; i > 0; i >>= 1) {
		T = U64(0xe100000000000000) & (0 - (V.lo & 1));
		V.lo = (V.hi << 63) | (V.lo >> 1);
		V.hi = (V.hi >> 1 ) ^ T;
		Htable[i] = V;
	}

	for (i = 2; i < 16; i <<= 1) {
		u128 *Hi = Htable + i;
		int   j;
		for (V = *Hi, j = 1; j < i; j++) {
			Hi[j].hi = V.hi ^ Htable[j].hi;
			Hi[j].lo = V.lo ^ Htable[j].lo;
		}
	}
}

#if !defined(HAVE_GCM_GHASH_4BIT) && !defined(HAVE_GCM_GMULT_4BIT)
static const uint16_t rem_4bit[16] = {
	0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
	0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0,
};
#endif

#ifdef HAVE_GCM_GMULT_4BIT
void gcm_gmult_4bit(uint64_t Xi[2], const u128 Htable[16]);

#else
static void
gcm_gmult_4bit(uint64_t Xi[2], const u128 Htable[16])
{
	u128 Z;
	int cnt = 15;
	size_t rem, nlo, nhi;

	nlo = ((const uint8_t *)Xi)[15];
	nhi = nlo >> 4;
	nlo &= 0xf;

	Z.hi = Htable[nlo].hi;
	Z.lo = Htable[nlo].lo;

	while (1) {
		rem = (size_t)Z.lo & 0xf;
		Z.lo = (Z.hi << 60)|(Z.lo >> 4);
		Z.hi = (Z.hi >> 4);
		Z.hi ^= (uint64_t)rem_4bit[rem] << 48;
		Z.hi ^= Htable[nhi].hi;
		Z.lo ^= Htable[nhi].lo;

		if (--cnt < 0)
			break;

		nlo = ((const uint8_t *)Xi)[cnt];
		nhi = nlo >> 4;
		nlo &= 0xf;

		rem = (size_t)Z.lo & 0xf;
		Z.lo = (Z.hi << 60)|(Z.lo >> 4);
		Z.hi = (Z.hi >> 4);
		Z.hi ^= (uint64_t)rem_4bit[rem] << 48;
		Z.hi ^= Htable[nlo].hi;
		Z.lo ^= Htable[nlo].lo;
	}

	Xi[0] = htobe64(Z.hi);
	Xi[1] = htobe64(Z.lo);
}
#endif

#ifdef HAVE_GCM_GHASH_4BIT
void gcm_ghash_4bit(uint64_t Xi[2], const u128 Htable[16], const uint8_t *inp,
    size_t len);

#else
static void
gcm_ghash_4bit(uint64_t Xi[2], const u128 Htable[16],
    const uint8_t *inp, size_t len)
{
	u128 Z;
	int cnt;
	size_t rem, nlo, nhi;

	do {
		cnt = 15;
		nlo = ((const uint8_t *)Xi)[15];
		nlo ^= inp[15];
		nhi = nlo >> 4;
		nlo &= 0xf;

		Z.hi = Htable[nlo].hi;
		Z.lo = Htable[nlo].lo;

		while (1) {
			rem = (size_t)Z.lo & 0xf;
			Z.lo = (Z.hi << 60)|(Z.lo >> 4);
			Z.hi = (Z.hi >> 4);
			Z.hi ^= (uint64_t)rem_4bit[rem] << 48;
			Z.hi ^= Htable[nhi].hi;
			Z.lo ^= Htable[nhi].lo;

			if (--cnt < 0)
				break;

			nlo = ((const uint8_t *)Xi)[cnt];
			nlo ^= inp[cnt];
			nhi = nlo >> 4;
			nlo &= 0xf;

			rem = (size_t)Z.lo & 0xf;
			Z.lo = (Z.hi << 60)|(Z.lo >> 4);
			Z.hi = (Z.hi >> 4);
			Z.hi ^= (uint64_t)rem_4bit[rem] << 48;
			Z.hi ^= Htable[nlo].hi;
			Z.lo ^= Htable[nlo].lo;
		}

		Xi[0] = htobe64(Z.hi);
		Xi[1] = htobe64(Z.lo);
	} while (inp += 16, len -= 16);
}
#endif

static inline void
gcm_mul(GCM128_CONTEXT *ctx, uint64_t u[2])
{
	ctx->gmult(u, ctx->Htable);
}

static inline void
gcm_ghash(GCM128_CONTEXT *ctx, const uint8_t *in, size_t len)
{
	ctx->ghash(ctx->Xi.u, ctx->Htable, in, len);
}

#ifdef HAVE_GCM128_INIT
void gcm128_init(GCM128_CONTEXT *ctx);

#else
static void
gcm128_init(GCM128_CONTEXT *ctx)
{
	gcm_init_4bit(ctx->Htable, ctx->H.u);
	ctx->gmult = gcm_gmult_4bit;
	ctx->ghash = gcm_ghash_4bit;
}
#endif

void
CRYPTO_gcm128_init(GCM128_CONTEXT *ctx, void *key, block128_f block)
{
	memset(ctx, 0, sizeof(*ctx));
	ctx->block = block;
	ctx->key = key;

	(*block)(ctx->H.c, ctx->H.c, key);

	/* H is stored in host byte order */
	ctx->H.u[0] = be64toh(ctx->H.u[0]);
	ctx->H.u[1] = be64toh(ctx->H.u[1]);

	gcm128_init(ctx);
}
LCRYPTO_ALIAS(CRYPTO_gcm128_init);

GCM128_CONTEXT *
CRYPTO_gcm128_new(void *key, block128_f block)
{
	GCM128_CONTEXT *ctx;

	if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
		return NULL;

	CRYPTO_gcm128_init(ctx, key, block);

	return ctx;
}
LCRYPTO_ALIAS(CRYPTO_gcm128_new);

void
CRYPTO_gcm128_release(GCM128_CONTEXT *ctx)
{
	freezero(ctx, sizeof(*ctx));
}
LCRYPTO_ALIAS(CRYPTO_gcm128_release);

void
CRYPTO_gcm128_setiv(GCM128_CONTEXT *ctx, const unsigned char *iv, size_t len)
{
	unsigned int ctr;

	ctx->Yi.u[0] = 0;
	ctx->Yi.u[1] = 0;
	ctx->Xi.u[0] = 0;
	ctx->Xi.u[1] = 0;
	ctx->len.u[0] = 0;	/* AAD length */
	ctx->len.u[1] = 0;	/* message length */
	ctx->ares = 0;
	ctx->mres = 0;

	if (len == 12) {
		memcpy(ctx->Yi.c, iv, 12);
		ctx->Yi.c[15] = 1;
		ctr = 1;
	} else {
		size_t i;
		uint64_t len0 = len;

		while (len >= 16) {
			for (i = 0; i < 16; i++)
				ctx->Yi.c[i] ^= iv[i];
			gcm_mul(ctx, ctx->Yi.u);
			iv += 16;
			len -= 16;
		}
		if (len > 0) {
			for (i = 0; i < len; i++)
				ctx->Yi.c[i] ^= iv[i];
			gcm_mul(ctx, ctx->Yi.u);
		}
		len0 <<= 3;
		ctx->Yi.u[1] ^= htobe64(len0);

		gcm_mul(ctx, ctx->Yi.u);

		ctr = be32toh(ctx->Yi.d[3]);
	}

	(*ctx->block)(ctx->Yi.c, ctx->EK0.c, ctx->key);
	ctx->Yi.d[3] = htobe32(++ctr);
}
LCRYPTO_ALIAS(CRYPTO_gcm128_setiv);

int
CRYPTO_gcm128_aad(GCM128_CONTEXT *ctx, const unsigned char *aad, size_t len)
{
	unsigned int n;
	uint64_t alen;
	size_t i;

	if (ctx->len.u[1] != 0)
		return -2;

	alen = ctx->len.u[0] + len;
	if (alen > (U64(1) << 61) || (sizeof(len) == 8 && alen < len))
		return -1;
	ctx->len.u[0] = alen;

	if ((n = ctx->ares) > 0) {
		while (n > 0 && len > 0) {
			ctx->Xi.c[n] ^= *(aad++);
			n = (n + 1) % 16;
			len--;
		}
		if (n > 0) {
			ctx->ares = n;
			return 0;
		}
		gcm_mul(ctx, ctx->Xi.u);
	}

	if ((i = (len & (size_t)-16)) > 0) {
		gcm_ghash(ctx, aad, i);
		aad += i;
		len -= i;
	}
	if (len > 0) {
		n = (unsigned int)len;
		for (i = 0; i < len; i++)
			ctx->Xi.c[i] ^= aad[i];
	}
	ctx->ares = n;

	return 0;
}
LCRYPTO_ALIAS(CRYPTO_gcm128_aad);

int
CRYPTO_gcm128_encrypt(GCM128_CONTEXT *ctx, const unsigned char *in,
    unsigned char *out, size_t len)
{
	unsigned int n, ctr;
	uint64_t mlen;
	size_t i;

	mlen = ctx->len.u[1] + len;
	if (mlen > ((U64(1) << 36) - 32) || (sizeof(len) == 8 && mlen < len))
		return -1;
	ctx->len.u[1] = mlen;

	if (ctx->ares > 0) {
		/* First call to encrypt finalizes GHASH(AAD) */
		gcm_mul(ctx, ctx->Xi.u);
		ctx->ares = 0;
	}

	ctr = be32toh(ctx->Yi.d[3]);

	n = ctx->mres;

	for (i = 0; i < len; i++) {
		if (n == 0) {
			ctx->block(ctx->Yi.c, ctx->EKi.c, ctx->key);
			ctx->Yi.d[3] = htobe32(++ctr);
		}
		ctx->Xi.c[n] ^= out[i] = in[i] ^ ctx->EKi.c[n];
		n = (n + 1) % 16;
		if (n == 0)
			gcm_mul(ctx, ctx->Xi.u);
	}

	ctx->mres = n;

	return 0;
}
LCRYPTO_ALIAS(CRYPTO_gcm128_encrypt);

int
CRYPTO_gcm128_decrypt(GCM128_CONTEXT *ctx, const unsigned char *in,
    unsigned char *out, size_t len)
{
	unsigned int n, ctr;
	uint64_t mlen;
	uint8_t c;
	size_t i;

	mlen = ctx->len.u[1] + len;
	if (mlen > ((U64(1) << 36) - 32) || (sizeof(len) == 8 && mlen < len))
		return -1;
	ctx->len.u[1] = mlen;

	if (ctx->ares) {
		/* First call to decrypt finalizes GHASH(AAD) */
		gcm_mul(ctx, ctx->Xi.u);
		ctx->ares = 0;
	}

	ctr = be32toh(ctx->Yi.d[3]);

	n = ctx->mres;

	for (i = 0; i < len; i++) {
		if (n == 0) {
			ctx->block(ctx->Yi.c, ctx->EKi.c, ctx->key);
			ctx->Yi.d[3] = htobe32(++ctr);
		}
		c = in[i];
		out[i] = c ^ ctx->EKi.c[n];
		ctx->Xi.c[n] ^= c;
		n = (n + 1) % 16;
		if (n == 0)
			gcm_mul(ctx, ctx->Xi.u);
	}

	ctx->mres = n;

	return 0;
}
LCRYPTO_ALIAS(CRYPTO_gcm128_decrypt);

int
CRYPTO_gcm128_encrypt_ctr32(GCM128_CONTEXT *ctx, const unsigned char *in,
    unsigned char *out, size_t len, ctr128_f stream)
{
	unsigned int n, ctr;
	uint64_t mlen;
	size_t i, j;

	mlen = ctx->len.u[1] + len;
	if (mlen > ((U64(1) << 36) - 32) || (sizeof(len) == 8 && mlen < len))
		return -1;
	ctx->len.u[1] = mlen;

	if (ctx->ares > 0) {
		/* First call to encrypt finalizes GHASH(AAD) */
		gcm_mul(ctx, ctx->Xi.u);
		ctx->ares = 0;
	}

	ctr = be32toh(ctx->Yi.d[3]);

	if ((n = ctx->mres) > 0) {
		while (n > 0 && len > 0) {
			ctx->Xi.c[n] ^= *(out++) = *(in++) ^ ctx->EKi.c[n];
			n = (n + 1) % 16;
			len--;
		}
		if (n > 0) {
			ctx->mres = n;
			return 0;
		}
		gcm_mul(ctx, ctx->Xi.u);
	}
	if ((i = (len & (size_t)-16)) > 0) {
		j = i / 16;
		stream(in, out, j, ctx->key, ctx->Yi.c);
		ctr += (unsigned int)j;
		ctx->Yi.d[3] = htobe32(ctr);
		gcm_ghash(ctx, out, i);
		in += i;
		out += i;
		len -= i;
	}
	if (len > 0) {
		ctx->block(ctx->Yi.c, ctx->EKi.c, ctx->key);
		ctx->Yi.d[3] = htobe32(++ctr);
		while (len-- > 0) {
			ctx->Xi.c[n] ^= out[n] = in[n] ^ ctx->EKi.c[n];
			n++;
		}
	}

	ctx->mres = n;

	return 0;
}
LCRYPTO_ALIAS(CRYPTO_gcm128_encrypt_ctr32);

int
CRYPTO_gcm128_decrypt_ctr32(GCM128_CONTEXT *ctx, const unsigned char *in,
    unsigned char *out, size_t len, ctr128_f stream)
{
	unsigned int n, ctr;
	uint64_t mlen;
	size_t i, j;
	uint8_t c;

	mlen = ctx->len.u[1] + len;
	if (mlen > ((U64(1) << 36) - 32) || (sizeof(len) == 8 && mlen < len))
		return -1;
	ctx->len.u[1] = mlen;

	if (ctx->ares > 0) {
		/* First call to decrypt finalizes GHASH(AAD) */
		gcm_mul(ctx, ctx->Xi.u);
		ctx->ares = 0;
	}

	ctr = be32toh(ctx->Yi.d[3]);

	if ((n = ctx->mres) > 0) {
		while (n > 0 && len > 0) {
			c = *(in++);
			*(out++) = c ^ ctx->EKi.c[n];
			ctx->Xi.c[n] ^= c;
			n = (n + 1) % 16;
			len--;
		}
		if (n > 0) {
			ctx->mres = n;
			return 0;
		}
		gcm_mul(ctx, ctx->Xi.u);
	}
	if ((i = (len & (size_t)-16)) > 0) {
		j = i / 16;
		gcm_ghash(ctx, in, i);
		stream(in, out, j, ctx->key, ctx->Yi.c);
		ctr += (unsigned int)j;
		ctx->Yi.d[3] = htobe32(ctr);
		in += i;
		out += i;
		len -= i;
	}
	if (len > 0) {
		ctx->block(ctx->Yi.c, ctx->EKi.c, ctx->key);
		ctx->Yi.d[3] = htobe32(++ctr);
		while (len-- > 0) {
			c = in[n];
			ctx->Xi.c[n] ^= c;
			out[n] = c ^ ctx->EKi.c[n];
			n++;
		}
	}

	ctx->mres = n;

	return 0;
}
LCRYPTO_ALIAS(CRYPTO_gcm128_decrypt_ctr32);

int
CRYPTO_gcm128_finish(GCM128_CONTEXT *ctx, const unsigned char *tag,
    size_t len)
{
	uint64_t alen, clen;

	alen = ctx->len.u[0] << 3;
	clen = ctx->len.u[1] << 3;

	if (ctx->ares > 0 || ctx->mres > 0)
		gcm_mul(ctx, ctx->Xi.u);

	ctx->Xi.u[0] ^= htobe64(alen);
	ctx->Xi.u[1] ^= htobe64(clen);
	gcm_mul(ctx, ctx->Xi.u);

	ctx->Xi.u[0] ^= ctx->EK0.u[0];
	ctx->Xi.u[1] ^= ctx->EK0.u[1];

	if (tag == NULL || len > sizeof(ctx->Xi))
		return -1;

	return timingsafe_memcmp(ctx->Xi.c, tag, len);
}
LCRYPTO_ALIAS(CRYPTO_gcm128_finish);

void
CRYPTO_gcm128_tag(GCM128_CONTEXT *ctx, unsigned char *tag, size_t len)
{
	CRYPTO_gcm128_finish(ctx, NULL, 0);

	if (len > sizeof(ctx->Xi.c))
		len = sizeof(ctx->Xi.c);

	memcpy(tag, ctx->Xi.c, len);
}
LCRYPTO_ALIAS(CRYPTO_gcm128_tag);
