/*
 * Packet Encryption Layer for Tiny SHell,
 * by Christophe Devine <devine@cr0.net>;
 * this program is licensed under the GPL.
 */

#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#include "aes.h"
#include "pel.h"
#include "sha1.h"

/* 全局数据 */

int pel_errno;

struct pel_context {
	/* AES-CBC-128 变量 */

	struct aes_context SK; /* Rijndael 会话密钥 */
	unsigned char LCT[16]; /* 最后一个密文块 */

	/* HMAC-SHA1 变量 */

	unsigned char k_ipad[64]; /* 内部填充 */
	unsigned char k_opad[64]; /* 外部填充 */
	unsigned long int p_cntr; /* 数据包计数器 */
};

struct pel_context send_ctx; /* 用于加密传出数据 */
struct pel_context recv_ctx; /* 用于解密传入数据 */

unsigned char challenge[16] = /* 版本特定 */

	"\x58\x90\xAE\x86\xF1\xB9\x1C\xF6"
	"\x29\x83\x95\x71\x1D\xDE\x58\x0D";

unsigned char buffer[BUFSIZE + 16 + 20];

/* 函数声明 */

void pel_setup_context(struct pel_context *pel_ctx, char *key,
			   unsigned char IV[20]);

int pel_send_all(int s, void *buf, size_t len, int flags);
int pel_recv_all(int s, void *buf, size_t len, int flags);

/* 会话初始化 - 客户端 */

int pel_client_init(int server, char *key)
{
	int ret, len, pid;
	struct timeval tv;
	struct sha1_context sha1_ctx;
	unsigned char IV1[20], IV2[20];

	/* 生成两个初始化向量 */

	pid = getpid();

	if (gettimeofday(&tv, NULL) < 0) {
		pel_errno = PEL_SYSTEM_ERROR;

		return (PEL_FAILURE);
	}

	sha1_starts(&sha1_ctx);
	sha1_update(&sha1_ctx, (uint8 *)&tv, sizeof(tv));
	sha1_update(&sha1_ctx, (uint8 *)&pid, sizeof(pid));
	sha1_finish(&sha1_ctx, &buffer[0]);

	memcpy(IV1, &buffer[0], 20);

	pid++;

	if (gettimeofday(&tv, NULL) < 0) {
		pel_errno = PEL_SYSTEM_ERROR;

		return (PEL_FAILURE);
	}

	sha1_starts(&sha1_ctx);
	sha1_update(&sha1_ctx, (uint8 *)&tv, sizeof(tv));
	sha1_update(&sha1_ctx, (uint8 *)&pid, sizeof(pid));
	sha1_finish(&sha1_ctx, &buffer[20]);

	memcpy(IV2, &buffer[20], 20);

	/* 将它们传递给服务器 */

	ret = pel_send_all(server, buffer, 40, 0);

	if (ret != PEL_SUCCESS)
		return (PEL_FAILURE);

	/* 设置会话密钥 */

	pel_setup_context(&send_ctx, key, IV1);
	pel_setup_context(&recv_ctx, key, IV2);

	/* 握手 - 加密并发送客户端的挑战 */

	ret = pel_send_msg(server, challenge, 16);

	if (ret != PEL_SUCCESS)
		return (PEL_FAILURE);

	/* 握手 - 解密并验证服务器的挑战 */

	ret = pel_recv_msg(server, buffer, &len);

	if (ret != PEL_SUCCESS)
		return (PEL_FAILURE);

	if (len != 16 || memcmp(buffer, challenge, 16) != 0) {
		pel_errno = PEL_WRONG_CHALLENGE;

		return (PEL_FAILURE);
	}

	pel_errno = PEL_UNDEFINED_ERROR;

	return (PEL_SUCCESS);
}

/* 会话初始化 - 服务器 */

int pel_server_init(int client, char *key)
{
	int ret, len;
	unsigned char IV1[20], IV2[20];

	/* 从客户端获取 IVs */

	ret = pel_recv_all(client, buffer, 40, 0);

	if (ret != PEL_SUCCESS)
		return (PEL_FAILURE);

	memcpy(IV2, &buffer[0], 20);
	memcpy(IV1, &buffer[20], 20);

	/* 设置会话密钥 */

	pel_setup_context(&send_ctx, key, IV1);
	pel_setup_context(&recv_ctx, key, IV2);

	/* 握手 - 解密并验证客户端的挑战 */

	ret = pel_recv_msg(client, buffer, &len);

	if (ret != PEL_SUCCESS)
		return (PEL_FAILURE);

	if (len != 16 || memcmp(buffer, challenge, 16) != 0) {
		pel_errno = PEL_WRONG_CHALLENGE;

		return (PEL_FAILURE);
	}

	/* 握手 - 加密并发送服务器的挑战 */

	ret = pel_send_msg(client, challenge, 16);

	if (ret != PEL_SUCCESS)
		return (PEL_FAILURE);

	pel_errno = PEL_UNDEFINED_ERROR;

	return (PEL_SUCCESS);
}

/* 该例程计算 AES 和 HMAC 会话密钥 */

void pel_setup_context(struct pel_context *pel_ctx, char *key,
			   unsigned char IV[20])
{
	int i;
	struct sha1_context sha1_ctx;

	sha1_starts(&sha1_ctx);
	sha1_update(&sha1_ctx, (uint8 *)key, strlen(key));
	sha1_update(&sha1_ctx, IV, 20);
	sha1_finish(&sha1_ctx, buffer);

	aes_set_key(&pel_ctx->SK, buffer, 128);

	memcpy(pel_ctx->LCT, IV, 16);

	memset(pel_ctx->k_ipad, 0x36, 64);
	memset(pel_ctx->k_opad, 0x5C, 64);

	for (i = 0; i < 20; i++) {
		pel_ctx->k_ipad[i] ^= buffer[i];
		pel_ctx->k_opad[i] ^= buffer[i];
	}

	pel_ctx->p_cntr = 0;
}

/* 加密并传输消息 */

int pel_send_msg(int sockfd, unsigned char *msg, int length)
{
	unsigned char digest[20];
	struct sha1_context sha1_ctx;
	int i, j, ret, blk_len;

	/* 验证消息长度 */

	if (length <= 0 || length > BUFSIZE) {
		pel_errno = PEL_BAD_MSG_LENGTH;

		return (PEL_FAILURE);
	}

	/* 将消息长度写入缓冲区开始位置 */

	buffer[0] = (length >> 8) & 0xFF;
	buffer[1] = (length)&0xFF;

	/* 追加消息内容 */

	memcpy(buffer + 2, msg, length);

	/* 向上取整到 AES 块长度 (16 字节) */

	blk_len = 2 + length;

	if ((blk_len & 0x0F) != 0) {
		blk_len += 16 - (blk_len & 0x0F);
	}

	/* 使用 AES-CBC-128 加密缓冲区 */

	for (i = 0; i < blk_len; i += 16) {
		for (j = 0; j < 16; j++) {
			buffer[i + j] ^= send_ctx.LCT[j];
		}

		aes_encrypt(&send_ctx.SK, &buffer[i]);

		memcpy(send_ctx.LCT, &buffer[i], 16);
	}

	/* 计算密文的 HMAC-SHA1 */

	buffer[blk_len] = (send_ctx.p_cntr << 24) & 0xFF;
	buffer[blk_len + 1] = (send_ctx.p_cntr << 16) & 0xFF;
	buffer[blk_len + 2] = (send_ctx.p_cntr << 8) & 0xFF;
	buffer[blk_len + 3] = (send_ctx.p_cntr) & 0xFF;

	sha1_starts(&sha1_ctx);
	sha1_update(&sha1_ctx, send_ctx.k_ipad, 64);
	sha1_update(&sha1_ctx, buffer, blk_len + 4);
	sha1_finish(&sha1_ctx, digest);

	sha1_starts(&sha1_ctx);
	sha1_update(&sha1_ctx, send_ctx.k_opad, 64);
	sha1_update(&sha1_ctx, digest, 20);
	sha1_finish(&sha1_ctx, &buffer[blk_len]);

	/* 增加数据包计数器 */

	send_ctx.p_cntr++;

	/* 传输密文和消息认证码 */

	ret = pel_send_all(sockfd, buffer, blk_len + 20, 0);

	if (ret != PEL_SUCCESS)
		return (PEL_FAILURE);

	pel_errno = PEL_UNDEFINED_ERROR;

	return (PEL_SUCCESS);
}

/* 接收并解密消息 */

int pel_recv_msg(int sockfd, unsigned char *msg, int *length)
{
	unsigned char temp[16];
	unsigned char hmac[20];
	unsigned char digest[20];
	struct sha1_context sha1_ctx;
	int i, j, ret, blk_len;

	/* 接收第一个加密块 */

	ret = pel_recv_all(sockfd, buffer, 16, 0);

	if (ret != PEL_SUCCESS)
		return (PEL_FAILURE);

	/* 解密该块并提取消息长度 */

	memcpy(temp, buffer, 16);

	aes_decrypt(&recv_ctx.SK, buffer);

	for (j = 0; j < 16; j++) {
		buffer[j] ^= recv_ctx.LCT[j];
	}

	*length = (((int)buffer[0]) << 8) + (int)buffer[1];

	/* 恢复密文 */

	memcpy(buffer, temp, 16);

	/* 验证消息长度 */

	if (*length <= 0 || *length > BUFSIZE) {
		pel_errno = PEL_BAD_MSG_LENGTH;

		return (PEL_FAILURE);
	}

	/* 向上取整到 AES 块长度 (16 字节) */

	blk_len = 2 + *length;

	if ((blk_len & 0x0F) != 0) {
		blk_len += 16 - (blk_len & 0x0F);
	}

	/* 接收剩余的密文和 mac */

	ret = pel_recv_all(sockfd, &buffer[16], blk_len - 16 + 20, 0);

	if (ret != PEL_SUCCESS)
		return (PEL_FAILURE);

	memcpy(hmac, &buffer[blk_len], 20);

	/* 验证密文完整性 */

	buffer[blk_len] = (recv_ctx.p_cntr << 24) & 0xFF;
	buffer[blk_len + 1] = (recv_ctx.p_cntr << 16) & 0xFF;
	buffer[blk_len + 2] = (recv_ctx.p_cntr << 8) & 0xFF;
	buffer[blk_len + 3] = (recv_ctx.p_cntr) & 0xFF;

	sha1_starts(&sha1_ctx);
	sha1_update(&sha1_ctx, recv_ctx.k_ipad, 64);
	sha1_update(&sha1_ctx, buffer, blk_len + 4);
	sha1_finish(&sha1_ctx, digest);

	sha1_starts(&sha1_ctx);
	sha1_update(&sha1_ctx, recv_ctx.k_opad, 64);
	sha1_update(&sha1_ctx, digest, 20);
	sha1_finish(&sha1_ctx, digest);

	if (memcmp(hmac, digest, 20) != 0) {
		pel_errno = PEL_CORRUPTED_DATA;

		return (PEL_FAILURE);
	}

	/* 增加数据包计数器 */

	recv_ctx.p_cntr++;

	/* 最后，解密并复制消息 */

	for (i = 0; i < blk_len; i += 16) {
		memcpy(temp, &buffer[i], 16);

		aes_decrypt(&recv_ctx.SK, &buffer[i]);

		for (j = 0; j < 16; j++) {
			buffer[i + j] ^= recv_ctx.LCT[j];
		}

		memcpy(recv_ctx.LCT, temp, 16);
	}

	memcpy(msg, &buffer[2], *length);

	pel_errno = PEL_UNDEFINED_ERROR;

	return (PEL_SUCCESS);
}

/* 发送/接收包装器以处理分段的 TCP 数据包 */

int pel_send_all(int s, void *buf, size_t len, int flags)
{
	int n;
	size_t sum = 0;
	char *offset = buf;

	while (sum < len) {
		n = send(s, (void *)offset, len - sum, flags);

		if (n < 0) {
			pel_errno = PEL_SYSTEM_ERROR;

			return (PEL_FAILURE);
		}

		sum += n;

		offset += n;
	}

	pel_errno = PEL_UNDEFINED_ERROR;

	return (PEL_SUCCESS);
}

int pel_recv_all(int s, void *buf, size_t len, int flags)
{
	int n;
	size_t sum = 0;
	char *offset = buf;

	while (sum < len) {
		n = recv(s, (void *)offset, len - sum, flags);

		if (n == 0) {
			pel_errno = PEL_CONN_CLOSED;

			return (PEL_FAILURE);
		}

		if (n < 0) {
			pel_errno = PEL_SYSTEM_ERROR;

			return (PEL_FAILURE);
		}

		sum += n;

		offset += n;
	}

	pel_errno = PEL_UNDEFINED_ERROR;

	return (PEL_SUCCESS);
}
