/*
 * Copyright (C) 2023 - 2026, Stephan Mueller <smueller@chronox.de>
 *
 * License: see LICENSE file in root directory
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
 * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR 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 NOT ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

#include "compare.h"
#include "fips_mode.h"
#include "lc_rng.h"
#include "ret_checkers.h"
#include "lc_x25519.h"
#include "timecop.h"
#include "visibility.h"
#include "x25519_internal.h"
#include "x25519_scalarmult.h"
#include "x25519_scalarmult_c.h"

static void lc_x25519_keypair_selftest(void)
{
	/*
	 * Test vector is
	 * from https://github.com/jedisct1/libsodium.git/test/default/ed25519_convert.exp
	 */
	LC_FIPS_RODATA_SECTION
	static const uint8_t sk[] = { 0x80, FIPS140_MOD(0x52),
				      0x03, 0x03,
				      0x76, 0xd4,
				      0x71, 0x12,
				      0xbe, 0x7f,
				      0x73, 0xed,
				      0x7a, 0x01,
				      0x92, 0x93,
				      0xdd, 0x12,
				      0xad, 0x91,
				      0x0b, 0x65,
				      0x44, 0x55,
				      0x79, 0x8b,
				      0x46, 0x67,
				      0xd7, 0x3d,
				      0xe1, 0x66 };
	LC_FIPS_RODATA_SECTION
	static const uint8_t pk_exp[] = { 0xf1, 0x81, 0x4f, 0x0e, 0x8f, 0xf1,
					  0x04, 0x3d, 0x8a, 0x44, 0xd2, 0x5b,
					  0xab, 0xff, 0x3c, 0xed, 0xca, 0xe6,
					  0xc2, 0x2c, 0x3e, 0xda, 0xa4, 0x8f,
					  0x85, 0x7a, 0xe7, 0x0d, 0xe2, 0xba,
					  0xae, 0x50 };
	uint8_t pk[sizeof(pk_exp)];

	LC_SELFTEST_RUN(LC_ALG_STATUS_X25519_KEYGEN);

	crypto_scalarmult_curve25519_base(pk, sk);
	lc_compare_selftest(LC_ALG_STATUS_X25519_KEYGEN, pk, pk_exp,
			    sizeof(pk_exp),
			    "X25519 base scalar multiplication\n");
}

int lc_x25519_keypair_internal(struct lc_x25519_pk *pk, struct lc_x25519_sk *sk,
			       struct lc_rng_ctx *rng_ctx)
{
	int ret;

	CKNULL(sk, -EINVAL);
	CKNULL(pk, -EINVAL);

	lc_x25519_keypair_selftest();
	LC_SELFTEST_COMPLETED(LC_ALG_STATUS_X25519_KEYGEN);

	lc_rng_check(&rng_ctx);

	CKINT(lc_rng_generate(rng_ctx, NULL, 0, sk->sk,
			      LC_X25519_SECRETKEYBYTES));

	/* Timecop: the random number is the sentitive data */
	poison(sk->sk, LC_X25519_SECRETKEYBYTES);

	CKINT(crypto_scalarmult_curve25519_base(pk->pk, sk->sk));

	/* Timecop: pk and sk are not relevant for side-channels any more. */
	unpoison(sk->sk, LC_X25519_SECRETKEYBYTES);
	unpoison(pk->pk, LC_X25519_PUBLICKEYBYTES);

out:
	return ret;
}

LC_INTERFACE_FUNCTION(int, lc_x25519_keypair, struct lc_x25519_pk *pk,
		      struct lc_x25519_sk *sk, struct lc_rng_ctx *rng_ctx)
{
	if (!non_pqc_algs_enabled())
		return -EOPNOTSUPP;

	return lc_x25519_keypair_internal(pk, sk, rng_ctx);
}

int lc_x25519_pk_from_sk(struct lc_x25519_pk *pk, const struct lc_x25519_sk *sk)
{
	int ret = 0;

	CKNULL(pk, -EINVAL);
	CKNULL(sk, -EINVAL);

	/* Timecop: the random number is the sentitive data */
	poison(sk->sk, LC_X25519_SECRETKEYBYTES);

	CKINT(crypto_scalarmult_curve25519_base(pk->pk, sk->sk));

	/* Timecop: pk and sk are not relevant for side-channels any more. */
	unpoison(sk->sk, LC_X25519_SECRETKEYBYTES);
	unpoison(pk->pk, LC_X25519_PUBLICKEYBYTES);

out:
	return ret;
}

static void lc_x25519_ss_selftest(void)
{
	/*
	 * Test vector is
	 * from https://github.com/jedisct1/libsodium.git/test/default/scalarmult7.c
	 * by taking variable p1 and printing out the out1 variable.
	 */
	LC_FIPS_RODATA_SECTION
	static const uint8_t p1[] = { FIPS140_MOD(0x72),
				      0x20,
				      0xf0,
				      0x09,
				      0x89,
				      0x30,
				      0xa7,
				      0x54,
				      0x74,
				      0x8b,
				      0x7d,
				      0xdc,
				      0xb4,
				      0x3e,
				      0xf7,
				      0x5a,
				      0x0d,
				      0xbf,
				      0x3a,
				      0x0d,
				      0x26,
				      0x38,
				      0x1a,
				      0xf4,
				      0xeb,
				      0xa4,
				      0xa9,
				      0x8e,
				      0xaa,
				      0x9b,
				      0x4e,
				      0xea };
	LC_FIPS_RODATA_SECTION
	static const uint8_t exp[] = { 0x03, 0xad, 0x40, 0x80, 0xc2, 0x91, 0x0b,
				       0x5e, 0x0b, 0xe2, 0x2f, 0x6c, 0x5f, 0x7c,
				       0x7e, 0x08, 0xe6, 0x42, 0x46, 0x2e, 0xf0,
				       0xec, 0x93, 0xa6, 0x54, 0xc5, 0xc3, 0x4d,
				       0xc9, 0x5b, 0x55, 0x6d };
	LC_FIPS_RODATA_SECTION
	static const uint8_t scalar[] = {
		0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	};
	uint8_t out[sizeof(exp)];

	LC_SELFTEST_RUN(LC_ALG_STATUS_X25519_SS);

	crypto_scalarmult_curve25519(out, scalar, p1);
	lc_compare_selftest(LC_ALG_STATUS_X25519_SS, out, exp, sizeof(exp),
			    "X25519 scalar multiplication\n");
}

int lc_x25519_ss_internal(struct lc_x25519_ss *ss,
			  const struct lc_x25519_pk *pk,
			  const struct lc_x25519_sk *sk)
{
	int ret = 0;

	CKNULL(sk, -EINVAL);
	CKNULL(pk, -EINVAL);
	CKNULL(ss, -EINVAL);

	lc_x25519_ss_selftest();
	LC_SELFTEST_COMPLETED(LC_ALG_STATUS_X25519_SS);

	/* Timecop: mark the secret key as sensitive */
	poison(sk->sk, sizeof(sk->sk));

	CKRET(has_small_order(pk->pk), -EKEYREJECTED);

	CKINT(crypto_scalarmult_curve25519(ss->ss, sk->sk, pk->pk));

	/* Timecop: pk and sk are not relevant for side-channels any more. */
	unpoison(sk->sk, LC_X25519_SECRETKEYBYTES);
	unpoison(ss->ss, LC_X25519_SSBYTES);

out:
	return ret;
}

LC_INTERFACE_FUNCTION(int, lc_x25519_ss, struct lc_x25519_ss *ss,
		      const struct lc_x25519_pk *pk,
		      const struct lc_x25519_sk *sk)
{
	if (!non_pqc_algs_enabled())
		return -EOPNOTSUPP;

	return lc_x25519_ss_internal(ss, pk, sk);
}

LC_INTERFACE_FUNCTION(enum lc_alg_status_val, lc_x25519_alg_status,
		      const enum lc_x25519_alg_operation operation)
{
	/* A priori, all algorithms are elegible for FIPS check */
	switch (operation) {
	case lc_alg_operation_x25519_keygen:
		return lc_alg_status(LC_ALG_STATUS_FIPS |
				     LC_ALG_STATUS_X25519_KEYGEN);
	case lc_alg_operation_x25519_ss:
		return lc_alg_status(LC_ALG_STATUS_FIPS |
				     LC_ALG_STATUS_X25519_SS);
	case lc_alg_operation_x25519_unknown:
	default:
		return lc_alg_status_unknown;
	}
	return lc_alg_status_unknown;
}

LC_INTERFACE_FUNCTION(int, lc_x25519_sk_ptr, uint8_t **x25519_key,
		      size_t *x25519_key_len, struct lc_x25519_sk *sk)
{
	if (!sk || !x25519_key || !x25519_key_len)
		return -EINVAL;

	*x25519_key = sk->sk;
	*x25519_key_len = sizeof(sk->sk);
	return 0;
}

LC_INTERFACE_FUNCTION(int, lc_x25519_pk_ptr, uint8_t **x25519_key,
		      size_t *x25519_key_len, struct lc_x25519_pk *pk)
{
	if (!pk || !x25519_key || !x25519_key_len)
		return -EINVAL;

	*x25519_key = pk->pk;
	*x25519_key_len = sizeof(pk->pk);
	return 0;
}

LC_INTERFACE_FUNCTION(int, lc_x25519_ss_ptr, uint8_t **x25519_ss,
		      size_t *x25519_ss_len, struct lc_x25519_ss *ss)
{
	if (!ss || !x25519_ss || !x25519_ss_len)
		return -EINVAL;

	*x25519_ss = ss->ss;
	*x25519_ss_len = sizeof(ss->ss);
	return 0;
}

LC_INTERFACE_FUNCTION(int, lc_x25519_sk_load, struct lc_x25519_sk *sk,
		      const uint8_t *src_key, size_t src_key_len)
{
	if (!sk || !src_key || src_key_len != sizeof(sk->sk))
		return -EINVAL;

	memcpy(sk->sk, src_key, src_key_len);
	return 0;
}

LC_INTERFACE_FUNCTION(int, lc_x25519_pk_load, struct lc_x25519_pk *pk,
		      const uint8_t *src_key, size_t src_key_len)
{
	if (!pk || !src_key || src_key_len != sizeof(pk->pk))
		return -EINVAL;

	memcpy(pk->pk, src_key, src_key_len);
	return 0;
}

LC_INTERFACE_FUNCTION(int, lc_x25519_ss_load, struct lc_x25519_ss *ss,
		      const uint8_t *src_ss, size_t src_ss_len)
{
	if (!ss || !src_ss || src_ss_len != sizeof(ss->ss))
		return -EINVAL;

	memcpy(ss->ss, src_ss, src_ss_len);
	return 0;
}
