/*
 * sm.c: Unit tests for Secure Messaging
 *
 * Copyright (C) 2021 Red Hat, Inc.
 *
 * Author: Jakub Jelen <jjelen@redhat.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "torture.h"
#include "libopensc/log.c"
#include "sm/sm-common.h"

/* Setup context */
static int setup_sc_context(void **state)
{
	sc_context_t *ctx = NULL;
	int rv;

	rv = sc_establish_context(&ctx, "sm");
	assert_non_null(ctx);
	assert_int_equal(rv, SC_SUCCESS);

	*state = ctx;

	return 0;
}

/* Cleanup context */
static int teardown_sc_context(void **state)
{
	sc_context_t *ctx = *state;
	int rv;

	rv = sc_release_context(ctx);
	assert_int_equal(rv, SC_SUCCESS);

	return 0;
}

static void torture_sm_incr_ssc(void **state)
{
	unsigned char in[] = {0x00, 0x00};

	(void)state;

	/* just make sure it does not crash */
	sm_incr_ssc(NULL, 0);

	/* zero-length input should not underflow the buffer */
	sm_incr_ssc(in, 0);

	/* shortest possible input */
	in[0] = 0x42;
	sm_incr_ssc(in, 1);
	assert_int_equal(in[0], 0x43);

	/* overflow to the second byte */
	in[0] = 0x00;
	in[1] = 0xff;
	sm_incr_ssc(in, 2);
	assert_int_equal(in[0], 0x01);
	assert_int_equal(in[1], 0x00);

	/* overflow */
	in[0] = 0xff;
	in[1] = 0xff;
	sm_incr_ssc(in, 2);
	assert_int_equal(in[0], 0x00);
	assert_int_equal(in[1], 0x00);
}

static void torture_sm_crypt_des_cbc3(void **state)
{
	sc_context_t *ctx = *state;
	/* Test vector from
	 * https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-20.pdf
	 * 5.2.1.1 The Variable Plaintext Known Answer Test -TCBC Mode
	 */
	unsigned char key[] = {
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY1 */
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY2 */};
	unsigned char plain[] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	unsigned char ciphertext[] = {0x95, 0xF8, 0xA5, 0xE5, 0xDD, 0x31, 0xD9, 0x00};
	unsigned char *out = NULL; /* allocates */
	size_t out_len = 0;
	int rv;

	rv = sm_encrypt_des_cbc3(ctx, key, plain, sizeof(plain), &out, &out_len, 1);
	assert_int_equal(rv, SC_SUCCESS);
	assert_int_equal(out_len, sizeof(ciphertext));
	assert_memory_equal(out, ciphertext, sizeof(ciphertext));
	free(out);
	out = NULL;
	out_len = 0;

	rv = sm_decrypt_des_cbc3(ctx, key, ciphertext, sizeof(ciphertext), &out, &out_len);
	assert_int_equal(rv, SC_SUCCESS);
	assert_memory_equal(out, plain, sizeof(plain));
	free(out);
}

static void torture_sm_crypt_des_cbc3_multiblock(void **state)
{
	/* not a test vector -- generated by openssl 1.1.1 */
	sc_context_t *ctx = *state;
	unsigned char key[] = {
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY1 */
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY2 */};
	unsigned char plain[] = {
		0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00};
	unsigned char ciphertext[] = {
		0x95, 0xF8, 0xA5, 0xE5, 0xDD, 0x31, 0xD9, 0x00,
		0xAF, 0xA0, 0x77, 0x1d, 0x35, 0xE1, 0xCC, 0x26};
	unsigned char *out = NULL; /* allocates */
	size_t out_len = 0;
	int rv;

	rv = sm_encrypt_des_cbc3(ctx, key, plain, sizeof(plain), &out, &out_len, 1);
	assert_int_equal(rv, SC_SUCCESS);
	assert_int_equal(out_len, sizeof(ciphertext));
	assert_memory_equal(out, ciphertext, sizeof(ciphertext));
	free(out);
	out = NULL;
	out_len = 0;

	rv = sm_decrypt_des_cbc3(ctx, key, ciphertext, sizeof(ciphertext), &out, &out_len);
	assert_int_equal(rv, SC_SUCCESS);
	assert_memory_equal(out, plain, sizeof(plain));
	free(out);
}

static void torture_sm_crypt_des_cbc3_force_pad(void **state)
{
	/* not a test vector -- generated by openssl 1.1.1 */
	sc_context_t *ctx = *state;
	unsigned char key[] = {
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY1 */
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY2 */};
	unsigned char plain[] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	unsigned char ciphertext[] = {
		0x95, 0xF8, 0xA5, 0xE5, 0xDD, 0x31, 0xD9, 0x00,
		0xC6, 0xD3, 0xE1, 0x4F, 0xFB, 0xDE, 0xDF, 0xF9};
	unsigned char *out = NULL; /* allocates */
	size_t out_len = 0;
	int rv;

	rv = sm_encrypt_des_cbc3(ctx, key, plain, sizeof(plain), &out, &out_len, 0);
	assert_int_equal(rv, SC_SUCCESS);
	assert_int_equal(out_len, sizeof(ciphertext));
	assert_memory_equal(out, ciphertext, sizeof(ciphertext));
	free(out);
	out = NULL;
	out_len = 0;

	rv = sm_decrypt_des_cbc3(ctx, key, ciphertext, sizeof(ciphertext), &out, &out_len);
	assert_int_equal(rv, SC_SUCCESS);
	assert_memory_equal(out, plain, sizeof(plain));
	free(out);
}

static void torture_sm_encrypt_des_ecb3(void **state)
{
	sc_context_t *ctx = *state;
	/* Test vector from
	 * https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-20.pdf
	 * 5.2.1.1 The Variable Plaintext Known Answer Test -TCBC Mode
	 */
	unsigned char key[] = {
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY1 */
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY2 */};
	unsigned char plain[] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	unsigned char ciphertext[] = {0x95, 0xF8, 0xA5, 0xE5, 0xDD, 0x31, 0xD9, 0x00};
	unsigned char *out = NULL; /* allocates */
	int out_len = 0;
	int rv;

	rv = sm_encrypt_des_ecb3(ctx, key, plain, sizeof(plain), &out, &out_len);
	assert_int_equal(rv, SC_SUCCESS);
	assert_int_equal(out_len, sizeof(ciphertext));
	assert_memory_equal(out, ciphertext, sizeof(ciphertext));
	free(out);
	out = NULL;
	out_len = 0;

	rv = sm_encrypt_des_ecb3(ctx, key, ciphertext, sizeof(ciphertext), &out, &out_len);
	assert_int_equal(rv, SC_SUCCESS);
	assert_memory_equal(out, plain, sizeof(plain));
	free(out);
}

static void torture_sm_encrypt_des_ecb3_multiblock(void **state)
{
	sc_context_t *ctx = *state;
	/* not a test vector -- generated by openssl 1.1.1 */
	unsigned char key[] = {
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY1 */
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY2 */};
	unsigned char plain[] = {
		0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	unsigned char ciphertext[] = {
		0x95, 0xF8, 0xA5, 0xE5, 0xDD, 0x31, 0xD9, 0x00,
		0x4B, 0xD3, 0x88, 0xFF, 0x6C, 0xD8, 0x1D, 0x4F};
	unsigned char *out = NULL; /* allocates */
	int out_len = 0;
	int rv;

	rv = sm_encrypt_des_ecb3(ctx, key, plain, sizeof(plain), &out, &out_len);
	assert_int_equal(rv, SC_SUCCESS);
	assert_int_equal(out_len, sizeof(ciphertext));
	assert_memory_equal(out, ciphertext, sizeof(ciphertext));
	free(out);
	out = NULL;
	out_len = 0;

	rv = sm_encrypt_des_ecb3(ctx, key, ciphertext, sizeof(ciphertext), &out, &out_len);
	assert_int_equal(rv, SC_SUCCESS);
	assert_memory_equal(out, plain, sizeof(plain));
	free(out);
}

static void torture_DES_cbc_cksum_3des(void **state)
{
	sc_context_t *ctx = *state;
	/* not a test vector -- generated by openssl 1.1.1 */
	unsigned char key[] = {
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY1 */
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY2 */};
	unsigned char iv[] = {
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	unsigned char plain[] = {
		0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	unsigned char checksum_ref[] = {
		0x95, 0xF8, 0xA5, 0xE5, 0xDD, 0x31, 0xD9, 0x00};
	unsigned long sum_ref = 0xdd31d900UL;
	unsigned char checksum[8];
	unsigned long sum;

	sum = DES_cbc_cksum_3des(ctx, plain, &checksum, sizeof(plain), key, &iv);
	assert_int_equal(sum, sum_ref);
	assert_memory_equal(checksum, checksum_ref, sizeof(checksum_ref));

	/* The checksum argument is not required */
	sum = DES_cbc_cksum_3des(ctx, plain, NULL, sizeof(plain), key, &iv);
	assert_int_equal(sum, sum_ref);
}

static void torture_DES_cbc_cksum_3des_multiblock(void **state)
{
	sc_context_t *ctx = *state;
	/* not a test vector -- generated by openssl 1.1.1 */
	unsigned char key[] = {
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY1 */
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY2 */};
	unsigned char iv[] = {
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	/* I think this function assumes/requires full blocks */
	unsigned char plain[] = {
		0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	unsigned char checksum_ref[] = {
		0xC6, 0x3F, 0x6E, 0x72, 0xC7, 0xCF, 0x4E, 0x07};
	unsigned long sum_ref = 0xc7cf4e07UL;
	unsigned char checksum[8];
	unsigned long sum;

	sum = DES_cbc_cksum_3des(ctx, plain, &checksum, sizeof(plain), key, &iv);
	assert_memory_equal(checksum, checksum_ref, sizeof(checksum_ref));
	assert_int_equal(sum, sum_ref);

	/* The checksum argument is not required */
	sum = DES_cbc_cksum_3des(ctx, plain, NULL, sizeof(plain), key, &iv);
	assert_int_equal(sum, sum_ref);
}

static void torture_DES_cbc_cksum_3des_emv96(void **state)
{
	sc_context_t *ctx = *state;
	/* not a test vector -- generated by openssl 1.1.1 */
	unsigned char key[] = {
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY1 */
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY2 */};
	unsigned char iv[] = {
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	unsigned char plain[] = {
		0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	unsigned char checksum_ref[] = {
		0x00, 0x00, 0x00, 0x00, 0xDD, 0x31, 0xD9, 0x00};
	unsigned long sum_ref = 0xdd31d900UL;
	unsigned char checksum[8];
	unsigned long sum;

	sum = DES_cbc_cksum_3des_emv96(ctx, plain, &checksum, sizeof(plain), key, &iv);
	assert_int_equal(sum, sum_ref);
	assert_memory_equal(checksum, checksum_ref, sizeof(checksum_ref));

	/* The checksum argument is not required */
	sum = DES_cbc_cksum_3des_emv96(ctx, plain, NULL, sizeof(plain), key, &iv);
	assert_int_equal(sum, sum_ref);
}

static void torture_DES_cbc_cksum_3des_emv96_multiblock(void **state)
{
	sc_context_t *ctx = *state;
	/* not a test vector -- generated by openssl 1.1.1 */
	unsigned char key[] = {
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY1 */
		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* KEY2 */};
	unsigned char iv[] = {
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	/* I think this function assumes/requires full blocks */
	unsigned char plain[] = {
		0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	unsigned char checksum_ref[] = {
		0x95, 0xf8, 0xA5, 0xe5, 0xC7, 0xCF, 0x4E, 0x07};
	unsigned long sum_ref = 0xc7cf4e07UL;
	unsigned char checksum[8] = {0};
	unsigned long sum;

	sum = DES_cbc_cksum_3des_emv96(ctx, plain, &checksum, sizeof(plain), key, &iv);
	assert_memory_equal(checksum, checksum_ref, sizeof(checksum_ref));
	assert_int_equal(sum, sum_ref);

	/* The checksum argument is not required */
	sum = DES_cbc_cksum_3des_emv96(ctx, plain, NULL, sizeof(plain), key, &iv);
	assert_int_equal(sum, sum_ref);
}

int main(void)
{
	int rc;
	struct CMUnitTest tests[] = {
		/* sm_incr_ssc */
		cmocka_unit_test(torture_sm_incr_ssc),
		/* sm_encrypt_des_cbc3 and sm_decrypt_des_cbc3 */
		cmocka_unit_test_setup_teardown(torture_sm_crypt_des_cbc3,
			setup_sc_context, teardown_sc_context),
		cmocka_unit_test_setup_teardown(torture_sm_crypt_des_cbc3_multiblock,
			setup_sc_context, teardown_sc_context),
		cmocka_unit_test_setup_teardown(torture_sm_crypt_des_cbc3_force_pad,
			setup_sc_context, teardown_sc_context),
		/* sm_encrypt_des_ecb3 */
		cmocka_unit_test_setup_teardown(torture_sm_encrypt_des_ecb3,
			setup_sc_context, teardown_sc_context),
		cmocka_unit_test_setup_teardown(torture_sm_encrypt_des_ecb3_multiblock,
			setup_sc_context, teardown_sc_context),
		/* DES_cbc_cksum_3des */
		cmocka_unit_test_setup_teardown(torture_DES_cbc_cksum_3des,
			setup_sc_context, teardown_sc_context),
		cmocka_unit_test_setup_teardown(torture_DES_cbc_cksum_3des_multiblock,
			setup_sc_context, teardown_sc_context),
		/* DES_cbc_cksum_3des_emv96 */
		cmocka_unit_test_setup_teardown(torture_DES_cbc_cksum_3des_emv96,
			setup_sc_context, teardown_sc_context),
		cmocka_unit_test_setup_teardown(torture_DES_cbc_cksum_3des_emv96_multiblock,
			setup_sc_context, teardown_sc_context),
	};

	rc = cmocka_run_group_tests(tests, NULL, NULL);
	return rc;
}
