/*
 * 본 프로그램에 대한 저작권을 포함한 지적재산권은 삼성SDS(주)에 있으며,
 * 삼성SDS(주)가 명시적으로 허용하지 않은 사용, 복사, 변경, 제3자에의 공개,
 * 배포는 엄격히 금지되며, 삼성SDS(주)의 지적재산권 침해에 해당됩니다.
 * 
 * Copyright (c) 2016 Samsung SDS Co., Ltd. All Rights Reserved. Confidential.
 * 
 * All information including the intellectual and technical concepts contained
 * herein is, and remains the property of Samsung SDS Co. Ltd. Unauthorized use,
 * dissemination, or reproduction of this material is strictly forbidden unless
 * prior written permission is obtained from Samsung SDS Co. Ltd. 
 */

/**
 * @file aes128-swbc-layer1-cbc.c
 * @brief This file provides the cryptographic API for Samsung WBC algorithm
 * configured with AES-128 as the base permutation algorithm, SWBC as the 
 * base cipher in mode of operation, 1 layer of WBC tables, and cbc mode of operation
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "aes128-swbc-layer1-cbc.h"
#include "swbc-aes.h"
#include "swbc-error.h"
#include "swbc-utils.h"

/**
 * Loads SWBC table resource
 *
 * @param ctx           SWBC context
 * @param swbc_e_table  SWBC table resource for encryption
 * @param e_tag         tag for SWBC encryption table resource. Tag is used to check whether the table resource is an appropriate one or not
 * @param swbc_d_table  SWBC table resource for decryption
 * @param d_tag         tag for SWBC decryption table resource. Tag is used to check whether the table resource is an appropriate one or not
 *
 * @return SWBC_SUCCESS on success, other values on failure
 */
int SWBCEXPORT aes128_swbc_layer1_cbc_load(aes128_swbc_layer1_cbc_ctx_t *ctx, const unsigned char **swbc_e_table, const unsigned char *e_tag, const unsigned char **swbc_d_table, const unsigned char *d_tag)
{
  int result = SWBC_ERROR_RESOURCE_UNLOADED;

	if(!ctx) return SWBC_ERROR_PARAM_CTX_NULL;
	memory_set(ctx, 0, sizeof(aes128_swbc_layer1_cbc_ctx_t));
	  
  if(swbc_e_table != 0 && e_tag != 0){
    if (SWBC_SUCCESS == table_check(swbc_e_table, e_tag)) {
      ctx->swbc_e_table = swbc_e_table;
      result = SWBC_SUCCESS;
    }
  }

  if(swbc_d_table != 0 && d_tag != 0){
    if (SWBC_SUCCESS == table_check(swbc_d_table, d_tag)) {
      ctx->swbc_d_table = swbc_d_table;
      result = SWBC_SUCCESS;
    }
  }

  return result;
}

/**
 * Initializes SWBC encryption context with specified constant
 *
 * @param ctx           SWBC context
 * @param mc            SWBC initialization constant
 * @param mode          SWBC initialization mode (1:encrypt, 0:decrypt)
 *
 * @return SWBC_SUCCESS on success, other values on failure
 */
int SWBCEXPORT aes128_swbc_layer1_cbc_init(aes128_swbc_layer1_cbc_ctx_t *ctx, const unsigned char *mc, SWBC_CIPHER_MODE mode)
{
  int result = 0;
  const unsigned char tmp_mc[16]={0};

	if(!ctx) return SWBC_ERROR_PARAM_CTX_NULL;
	if(!mc) mc = tmp_mc;

	if(mode == ENCRYPT_MODE && ctx->swbc_e_table) {
		result = SWBC_AES_set_key_encrypt(ctx->rc, mc);
		if(result < 10) return SWBC_FAILURE;
	} 
  else if(mode == DECRYPT_MODE && ctx->swbc_d_table){
		result = SWBC_AES_set_key_decrypt(ctx->rc, mc);
		if(result < 10) return SWBC_FAILURE;
	}
  else{
    return SWBC_ERROR_CIPHER_MODE_INVALID;
  }
  ctx->cipher_mode = mode;

	return SWBC_SUCCESS;
}

/**
 * Encrypts data of arbitrary given length using SWBC encryption algorithm.
 * It uses cbc mode of operation.
 *
 * @param ctx         SWBC context
 * @param iv          initialization vector for encryption
 * @param in          plaintext
 * @param out         storage for ciphertext(should be at least as large as plaintext length + block size)
 * @param inl         plaintext length
 * @param outl        return reference(encrypted data length)
 *
 * @return SWBC_SUCCESS on success, other values on failure
 */
int SWBCEXPORT aes128_swbc_layer1_cbc_encrypt(aes128_swbc_layer1_cbc_ctx_t *ctx, const unsigned char *iv, const unsigned char *in, unsigned char *out, int inl, int *outl)
{
	const unsigned char *ivec = iv;
	unsigned char tempIn[16];
	int i = 0;
	int out_length = 0;
	int len = inl;

	if(!ctx) return SWBC_ERROR_PARAM_CTX_NULL;
	if(!iv) return SWBC_ERROR_PARAM_IV_NULL;
	if(!in) return SWBC_ERROR_PARAM_IN_NULL;
	if(!out) return SWBC_ERROR_PARAM_OUT_NULL;
	if(!outl) return SWBC_ERROR_PARAM_OUT_LENGTH_NULL;
  if(ctx->cipher_mode != ENCRYPT_MODE) return SWBC_ERROR_CIPHER_MODE_INVALID;
	if(len <= 0) return SWBC_ERROR_PARAM_IN_LENGTH_INVALID;

	while (len >= 16)
	{
		for (i = 0; i < 16; i++) {
			tempIn[i] = in[i] ^ ivec[i];
		}
		
		SWBC_AES_encrypt((const unsigned char (*)[256])(ctx->swbc_e_table), ctx->rc, tempIn, out);
		ivec = out;

		len -= 16;
		in += 16;
		out += 16;
		out_length += 16;
	}

	for (i = 0; i < len; i++) {
		tempIn[i] = in[i] ^ ivec[i];
	}

	for (i = len; i < 16; i++) {
		tempIn[i] = ((unsigned char )(16-len)) ^ ivec[i];
	}
	out_length += 16;
	*outl = out_length;

	SWBC_AES_encrypt((const unsigned char (*)[256])(ctx->swbc_e_table), ctx->rc, tempIn, out);

	return SWBC_SUCCESS;
}

/**
 * Decrypts data of arbitrary given length using SWBC decryption algorithm.
 *
 * @param ctx           SWBC context
 * @param iv          initialization vector for decryption
 * @param in            ciphertext
 * @param out           storage for plaintext(should be at least as large as ciphertext length)
 * @param inl           ciphertext length
 * @param outl          return reference(decrypted data length)
 *
 * @return SWBC_SUCCESS on success, other values on failure
 */
int SWBCEXPORT aes128_swbc_layer1_cbc_decrypt(aes128_swbc_layer1_cbc_ctx_t *ctx, const unsigned char *iv, const unsigned char *in, unsigned char *out, int inl, int *outl)
{
	const unsigned char *ivdc = iv;

	unsigned char temp_out[16];  
	int i = 0;
	int out_length = 0;
	int p_idx = 0;
	int len = inl;

	if(!ctx) return SWBC_ERROR_PARAM_CTX_NULL;
	if(!iv) return SWBC_ERROR_PARAM_IV_NULL;
	if(!in) return SWBC_ERROR_PARAM_IN_NULL;
	if(!out) return SWBC_ERROR_PARAM_OUT_NULL;
	if(!outl) return SWBC_ERROR_PARAM_OUT_LENGTH_NULL;
	if(ctx->cipher_mode != DECRYPT_MODE) return SWBC_ERROR_CIPHER_MODE_INVALID;
	if(len <= 0 || (len%16 != 0)) return SWBC_ERROR_PARAM_IN_LENGTH_INVALID;

	while(len > 16) {
		SWBC_AES_decrypt((const unsigned char (*)[256])(ctx->swbc_d_table), ctx->rc, in, temp_out);
		
		for(i=0; i<16; i++) {
			out[i] = ivdc[i] ^ temp_out[i];
		}
		ivdc = in;

		len -= 16;
		in += 16;
		out += 16;
		out_length += 16;
	}

	SWBC_AES_decrypt((const unsigned char (*)[256])(ctx->swbc_d_table), ctx->rc, in, temp_out);

	p_idx = ivdc[15] ^ temp_out[15];
	if(p_idx == 0 || p_idx > 16) return SWBC_FAILURE;
	for(i=1; i<p_idx; i++){
		if((ivdc[15-i] ^ temp_out[15-i]) != p_idx) return SWBC_FAILURE;
	}
	p_idx = 16-p_idx;

	for(i=0; i<p_idx; i++) {
		out[i] = ivdc[i] ^ temp_out[i];
		out_length++;
	}
	*outl = out_length;

	return SWBC_SUCCESS;
}
