#include <Windows.h>
#include <Userenv.h>
#include <stdio.h>
#include <stdint.h>

#include "md5.h"
#include "aes.h"
#include "sha256.h"
#include "EnvironmentalKeying.h"

#include <vld.h>

static unsigned char encryptionKey[16];
static unsigned char iv[16];
static unsigned char zeros[16];

//! Object id file stream
typedef struct
{
	/** The identifier that uniquely identifies the file or directory
	*  within the volume on which it resides. This is the portion of
	*  the object id that is indexed.
	*/
	GUID ObjectId;
	union
	{
		struct
		{
			/** The identifier of the volume on which the object resided
			* when the object identifier was created, or zero if the
			* volume had no object identifier at that time. After copy
			* operations, move operations, or other file operations,
			* this may not be the same as the object identifier of the
			* volume on which the object presently resides.
			*/
			GUID BirthVolumeId;
			/** The object identifier of the object at the time it was created.
			* After copy operations, move operations, or other file operations,
			* this may not be the same as the ObjectId member at present.
			*/
			GUID BirthObjectId;
			/**
			* Reserved; should be zero.
			*/
			GUID DomainId;
		};
		/** This portion of the object id is not indexed,
		* it's just some metadata for the user's benefit.
		*/
		UCHAR ExtendedInfo[48];
	};
} OBJECTID_ATTRIBUTE, *POBJECTID_ATTRIBUTE;


LPSTR GetUserHomeDir();
BOOL GetObjectId(IN LPCSTR sFileName, OUT POBJECTID_ATTRIBUTE pObjId);

#ifdef _DEBUG
void print_hash(unsigned char hash[], unsigned int length)
{
	for (unsigned int idx = 0; idx < length; idx++)
		printf("%02x", hash[idx]);
	printf("\n");
}
#endif

#ifdef _EXE_STANDALONE

int tool(int argc, char** argv)
{
	if (argc != 5)
	{
		printf("[*] EnvironmentalKeying (ntfs object id) usage: %s [d|e] folder_for_object_id file_to_encrypt_or_decrypt output_file\n", argv[0]);
		printf("e.g.: %s e c:\\Users\\test\\ pic_code output_file\n", argv[0]);
		exit(1);
	}

	/* mode */
	UCHAR* mode = argv[1];
	if (strcmp(mode, "e") && strcmp(mode, "d"))
	{
		printf("[E] wrong usage mode %s\n", mode);
		exit(1);
	}

	/* folder for key */
	UCHAR* folder = argv[2];
	UCHAR* expectedValue = EnvironmentalKeyingGetExpectedValue(folder);

	printf("[*] exptected value for folder %s: ", folder);
	print_hash(expectedValue, 32);

	/* target file */
	UCHAR* targetFile = argv[3];
	HANDLE hFile = CreateFile(targetFile, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	DWORD dwFileSize = GetFileSize(hFile, NULL);
	LPVOID lpLoaderBuffer = (LPVOID)VirtualAlloc(NULL, dwFileSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	DWORD dwBytesRead = 0;
	ReadFile(hFile, lpLoaderBuffer, dwFileSize, &dwBytesRead, NULL);
	CloseHandle(hFile);

	/* output */
	UCHAR* outputFile = argv[4];

	// setup
	EnvironmentalKeyingSetup(folder, expectedValue);

	// encrypt
	if (!strcmp(mode, "e"))
	{
		printf("[*] encrypting file %s\n", targetFile);

		LPVOID lpOutput = (LPVOID)VirtualAlloc(NULL, dwBytesRead, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
		EnvironmentalKeyingEncrypt(lpLoaderBuffer, dwBytesRead, lpOutput);


		printf("[*] writing output to %s\n", outputFile);

		HANDLE hOutputFile = CreateFile(outputFile, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		DWORD dwBytesWritten = 0;
		WriteFile(hOutputFile, lpOutput, dwBytesRead, &dwBytesWritten, NULL);
		CloseHandle(hOutputFile);
	}

	// decrypt
	else if (!strcmp(mode, "d"))
	{
		printf("[*] decrypting file %s\n", targetFile);

		LPVOID lpOutput = (LPVOID)VirtualAlloc(NULL, dwBytesRead, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
		EnvironmentalKeyingDecrypt(lpLoaderBuffer, dwBytesRead, lpOutput);

		printf("[*] writing output to %s\n", outputFile);

		HANDLE hOutputFile = CreateFile(outputFile, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		DWORD dwBytesWritten = 0;
		WriteFile(hOutputFile, lpOutput, dwBytesRead, &dwBytesWritten, NULL);
		CloseHandle(hOutputFile);
	}


	/* cleanup */
	free(expectedValue);

}

int main(int argc, char** argv)
{

	//POBJECTID_ATTRIBUTE pObjId = (POBJECTID_ATTRIBUTE)malloc(sizeof(OBJECTID_ATTRIBUTE));
	//if (!GetObjectId(GetUserHomeDir(), pObjId))
	//	return 1;
	//if (pObjId != NULL)
	//	printf("[*] folder %s objectd_id %x-%x-%x-%x%x-%x%x%x%x%x%x\n", GetUserHomeDir(), pObjId->ObjectId.Data1, pObjId->ObjectId.Data2, pObjId->ObjectId.Data3, 
	//	pObjId->ObjectId.Data4[0], pObjId->ObjectId.Data4[1], pObjId->ObjectId.Data4[2], pObjId->ObjectId.Data4[3], pObjId->ObjectId.Data4[4], pObjId->ObjectId.Data4[5], pObjId->ObjectId.Data4[6], pObjId->ObjectId.Data4[7] );
	//else
	//	printf("[E] something happened \n");

	///* GUID:
	//	unsigned long  Data1;
	//	unsigned short Data2;
	//	unsigned short Data3;
	//	unsigned char  Data4[8];
	//*/
	//uint8_t aes_key[16];
	//memcpy(aes_key, &pObjId->ObjectId.Data1, 4);
	//memcpy(&aes_key[4], &pObjId->ObjectId.Data2, 2);
	//memcpy(&aes_key[6], &pObjId->ObjectId.Data3, 2);
	//memcpy(&aes_key[8], &pObjId->ObjectId.Data4, 8);
	//
	//uint8_t iv[16];
	//memset(iv, 0, 16);

	//char *test = "I will be encrypted and decrypted\n";
	//uint32_t size = (uint32_t) strlen(test)+1;
	//char *output = (char*)malloc(size);
	//char *output2 = (char*)malloc(size);

	//printf("[*] plain text: %s size %d\n", test, size);

	//AES128_CBC_encrypt_buffer(output, test, size, aes_key, iv);
	//printf("[*] encrypted: %s\n", output);

	//AES128_CBC_decrypt_buffer(output2, output, size, aes_key, iv);
	//printf("[*] decrypted: %s\n", output2);
		
	/*unsigned char expectedValue[32] = {
			0x4F, 0x94, 0x07, 0xAD, 0x10, 0xE4, 0xC7, 0x18, 0x2A, 0xA1, 0x73, 0x05, 0x39, 0x8F, 0x55, 0x6E,
			0xCC, 0x36, 0x21, 0x3E, 0x03, 0x13, 0x8D, 0xB7, 0x56, 0x2D, 0x24, 0x14, 0x08, 0x14, 0xEC, 0xC3
	};
	
	unsigned char ucHomeObjectId[16] = { 0xC5, 0x10, 0x3B, 0xE1, 0x9D, 0xA9, 0x11, 0xE3, 0xA5, 0xCC, 0xBD, 0xA1, 0x72, 0x58, 0x35, 0x06 };*/
	
	//unsigned char ucHomeObjectIdSha256[32] = {
	//	0xF2, 0xFF, 0x22, 0x12, 0x5F, 0x9E, 0x3E, 0xBE, 0xEC, 0xF5, 0x8B, 0x42, 0xCE, 0xFB, 0x9F, 0x2D,
	//	0xBD, 0xA1, 0x7F, 0xCA, 0xE8, 0x93, 0x28, 0x35, 0xBF, 0xA1, 0x30, 0xC3, 0x4E, 0x06, 0xD5, 0x66
	//};

	//unsigned char ucHomeObjectIdSha256Sha256[32] = {
	//	0x4F, 0x94, 0x07, 0xAD, 0x10, 0xE4, 0xC7, 0x18, 0x2A, 0xA1, 0x73, 0x05, 0x39, 0x8F, 0x55, 0x6E,
	//	0xCC, 0x36, 0x21, 0x3E, 0x03, 0x13, 0x8D, 0xB7, 0x56, 0x2D, 0x24, 0x14, 0x08, 0x14, 0xEC, 0xC3
	//};

	// expected encryption key: ae69fc6bc67892ee692ccea2ec7a380c

	//LPSTR szHomeDir = GetUserHomeDir();

	//unsigned char *calcdExpectedValue = NULL;
	//calcdExpectedValue = EnvironmentalKeyingGetExpectedValue(szHomeDir);

	//print_hash(calcdExpectedValue, 32);
	//print_hash(expectedValue, 32);

	//unsigned char hash1[32];
	//SHA256_CTX ctx1;
	//
	//sha256_init(&ctx1);
	//sha256_update(&ctx1, ucHomeObjectId, 16);
	//sha256_final(&ctx1, hash1);
	////print_hash(hash1, 32);

	//sha256_init(&ctx1);
	//sha256_update(&ctx1, hash1, 32);
	//sha256_final(&ctx1, hash1);
	////print_hash(hash1, 32);

	//EnvironmentalKeyingSetup(szHomeDir, expectedValue);

	//char *cleartext = "I am very clear and very text\n";
	//
	//LPVOID lpCipherText = (LPVOID) malloc(128);
	//LPVOID lpClearText = (LPVOID)malloc(128);

	//EnvironmentalKeyingEncrypt(cleartext, strlen(cleartext) + 1, lpCipherText);
	//EnvironmentalKeyingDecrypt(lpCipherText, strlen(cleartext) + 1, lpClearText);

	//printf("[*] original cleartext: %s %d\n", cleartext, strlen(cleartext));
	//printf("[*] cypher: %s %d\n", lpCipherText, strlen(cleartext));
	//printf("[*] clear: %s %d\n", lpClearText, strlen(cleartext));

	//
	//free(lpCipherText);
	//free(lpClearText);
	//free(szHomeDir);
	//free(calcdExpectedValue);

	tool(argc, argv);
	
}
#endif

uint32_t swap_uint32(uint32_t val)
{
	val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF);
	return (val << 16) | (val >> 16);
}

uint16_t swap_uint16(uint16_t val)
{
	return (val << 8) | (val >> 8);
}

LPSTR GetUserHomeDir()
{
	LPSTR szHomeDirBuf = (LPSTR)malloc(MAX_PATH * sizeof(CHAR));

	HANDLE hToken = 0;
	OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);

	DWORD BufSize = MAX_PATH;
	GetUserProfileDirectory(hToken, szHomeDirBuf, &BufSize);

	CloseHandle(hToken);
	return szHomeDirBuf;
}

static HANDLE OpenFileForRead(IN LPCSTR sFileName, IN BOOL bBackup)
{
	return CreateFile(
		sFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
		(bBackup)
		? FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS
		: FILE_FLAG_OPEN_REPARSE_POINT, 0);
}

// TODO: what to do with non NTFS filesystems
BOOL GetObjectId(IN LPCSTR sFileName, OUT POBJECTID_ATTRIBUTE pObjId)
{
	if (NULL == pObjId)
	{
		return FALSE;
	}

	HANDLE hNew = OpenFileForRead(sFileName, (GetFileAttributes(sFileName) & FILE_ATTRIBUTE_DIRECTORY));

	DWORD dwRet;

	if (hNew == INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}

	if (!DeviceIoControl(hNew, FSCTL_GET_OBJECT_ID, NULL, 0, pObjId, sizeof(OBJECTID_ATTRIBUTE), &dwRet, NULL))
	{
		CloseHandle(hNew);
		return FALSE;
	}

	CloseHandle(hNew);
	return TRUE;
}

BOOL EnvironmentalKeyingDecrypt(IN LPVOID lpBufferToDecrypt, IN size_t length, OUT LPVOID lpDecryptedBuffer)
{
	/* check EnvironmentalKeyingSetup has been called */
	if (memcmp(zeros, encryptionKey, 16) == 0 || memcmp(zeros, iv, 16) == 0)
		return FALSE;

	AES128_CBC_decrypt_buffer(lpDecryptedBuffer, lpBufferToDecrypt, length, encryptionKey, iv);

	return TRUE;
}

BOOL EnvironmentalKeyingEncrypt(IN LPVOID lpBufferToEncrypt, IN size_t length, OUT LPVOID lpEncrypt)
{
	// N.B cleartext is not preserved after decryption
		
	/* check EnvironmentalKeyingSetup has been called */
	if (memcmp(zeros, encryptionKey, 16) == 0 || memcmp(zeros, iv, 16) == 0)
		return FALSE;

	AES128_CBC_encrypt_buffer(lpEncrypt, lpBufferToEncrypt, length, encryptionKey, iv);

	return TRUE;
}


BOOL EnvironmentalKeyingSetup(IN LPCSTR szFolderPath, IN unsigned char *expectedValue)
{
	/*
		expectedValue is such that hash(hash(getobjectid(szFolderPath))) == expectedValue,
		if the match is successful hash(getobjectid(szFolderPath)) is used to derive encryption key
	*/


	/* GUID:
	unsigned long  Data1;
	unsigned short Data2;
	unsigned short Data3;
	unsigned char  Data4[8];
	*/

	POBJECTID_ATTRIBUTE pObjId = (POBJECTID_ATTRIBUTE)malloc(sizeof(OBJECTID_ATTRIBUTE));

	if (pObjId == NULL)
		return FALSE;

	if (!GetObjectId(szFolderPath, pObjId))
		return FALSE;

	unsigned char objId[16];
	uint32_t first = swap_uint32(pObjId->ObjectId.Data1);
	uint16_t second = swap_uint16(pObjId->ObjectId.Data2);
	uint16_t third = swap_uint16(pObjId->ObjectId.Data3);

	memcpy(objId, &first, 4);
	memcpy(&objId[4], &second, 2);
	memcpy(&objId[6], &third, 2);
	memcpy(&objId[8], &pObjId->ObjectId.Data4, 8);

	free(pObjId);

	/*	get hash(hash(getobjectid(szFolderPath))) */
	unsigned char hash[32];

	/*	decryptionKeyMaterial, which is hash(getobjectid(szFolderPath)),
		is then used to feed md5 and get the 128 bit aes key,
		while hash(hash(getobjectid(szFolderPath))) is kept as IV
	*/
	unsigned char decryptionKeyMaterial[32];

	SHA256_CTX ctx;

	sha256_init(&ctx);
	sha256_update(&ctx, objId, 16);
	sha256_final(&ctx, decryptionKeyMaterial);

	sha256_init(&ctx);
	sha256_update(&ctx, decryptionKeyMaterial, 32);
	sha256_final(&ctx, hash);
		
	/*print_hash(objId, 16);
	print_hash(hash, 32);
	print_hash(expectedValue, 32);*/

	/* compare against the expected value */
	if (memcmp(hash, expectedValue, 32) == 0)
	{
		/*	derive final 128 bit aes key 
			iv is the expected value itself
		*/
		unsigned char key[16];

		MD5_CTX ctx_md5;
		MD5_Init(&ctx_md5);
		MD5_Update(&ctx_md5, decryptionKeyMaterial, 32);
		MD5_Final(key, &ctx_md5);
		
		memcpy_s(iv, 16, hash, 16);
		memcpy_s(encryptionKey, 16, key, 16);

		return TRUE;
	}
	else
		return FALSE;
}

__declspec(dllexport) unsigned char * EnvironmentalKeyingGetExpectedValue(IN LPCSTR szFolderPath)
{
	/* fetch object id, calc hash(hash(objectid)) */
	POBJECTID_ATTRIBUTE pObjId = (POBJECTID_ATTRIBUTE)malloc(sizeof(OBJECTID_ATTRIBUTE));

	if (pObjId == NULL)
		return FALSE;

	if (!GetObjectId(szFolderPath, pObjId))
		return FALSE;

	unsigned char *expectedValue = (unsigned char*)malloc(32);

	if (expectedValue == NULL)
		return NULL;

	unsigned char objId[16];
	uint32_t first = swap_uint32(pObjId->ObjectId.Data1);
	uint16_t second = swap_uint16(pObjId->ObjectId.Data2);
	uint16_t third = swap_uint16(pObjId->ObjectId.Data3);

	memcpy(objId, &first, 4);
	memcpy(&objId[4], &second, 2);
	memcpy(&objId[6], &third, 2);
	memcpy(&objId[8], &pObjId->ObjectId.Data4, 8);

	free(pObjId);

	unsigned char decryptionKeyMaterial[32];

	SHA256_CTX ctx;

	sha256_init(&ctx);
	sha256_update(&ctx, objId, 16);
	sha256_final(&ctx, decryptionKeyMaterial);

	sha256_init(&ctx);
	sha256_update(&ctx, decryptionKeyMaterial, 32);
	sha256_final(&ctx, expectedValue);

	
	return expectedValue;
}