/*	Benjamin DELPY `gentilkiwi`
	https://blog.gentilkiwi.com
	benjamin@gentilkiwi.com
	Licence : https://creativecommons.org/licenses/by/4.0/
*/
#include "kull_m_crypto_ngc.h"

BOOL kull_m_crypto_ngc_keyvalue_derived_software(PBYTE pbLabel, DWORD cbLabel, PBYTE pbContext, DWORD cbContext, LPCBYTE Key, DWORD cbKey, PBYTE DerivedKey, DWORD cbDerivedKey)
{
	BOOL status = FALSE;
	NTSTATUS bStatus;
	BCRYPT_ALG_HANDLE hAlgSP800108;
	BCRYPT_KEY_HANDLE hKeySP800108;
	DWORD ObjectLengthSP800108, cbResult;
	PUCHAR pbKeyObjectSP800108;
	BCryptBuffer buffer[] = {
		{cbLabel, KDF_LABEL, pbLabel},
		{cbContext, KDF_CONTEXT, pbContext},
		{sizeof(BCRYPT_SHA256_ALGORITHM), KDF_HASH_ALGORITHM, BCRYPT_SHA256_ALGORITHM},
	};
	BCryptBufferDesc bufferDesc = {BCRYPTBUFFER_VERSION, ARRAYSIZE(buffer), buffer};

	__try
	{
		bStatus = BCryptOpenAlgorithmProvider(&hAlgSP800108, BCRYPT_SP800108_CTR_HMAC_ALGORITHM, NULL, 0);
		if(BCRYPT_SUCCESS(bStatus))
		{
			bStatus = BCryptGetProperty(hAlgSP800108, BCRYPT_OBJECT_LENGTH, (PUCHAR) &ObjectLengthSP800108, sizeof(ObjectLengthSP800108), &cbResult, 0);
			if(BCRYPT_SUCCESS(bStatus))
			{
				if(pbKeyObjectSP800108 = (PUCHAR) LocalAlloc(LPTR, ObjectLengthSP800108))
				{
					bStatus = BCryptGenerateSymmetricKey(hAlgSP800108, &hKeySP800108, pbKeyObjectSP800108, ObjectLengthSP800108, (PUCHAR) Key, cbKey, 0);
					if(BCRYPT_SUCCESS(bStatus))
					{
						bStatus = BCryptKeyDerivation(hKeySP800108, &bufferDesc, DerivedKey, cbDerivedKey, &cbResult, 0);
						if(BCRYPT_SUCCESS(bStatus))
						{
							status = (cbResult == cbDerivedKey);
							if(!status)
								PRINT_ERROR(L"Bad cbResult (%u vs %u)\n", cbResult, cbDerivedKey);
						}
						else PRINT_ERROR(L"BCryptKeyDerivation: 0x%08x\n", bStatus);
						BCryptDestroyKey(hKeySP800108);
					}
					else PRINT_ERROR(L"BCryptGenerateSymmetricKey: 0x%08x\n", bStatus);
					LocalFree(pbKeyObjectSP800108);
				}
			}
			else PRINT_ERROR(L"BCryptGetProperty: 0x%08x\n", bStatus);
			BCryptCloseAlgorithmProvider(hAlgSP800108, 0);
		}
		else PRINT_ERROR(L"BCryptOpenAlgorithmProvider: 0x%08x\n", bStatus);
	}
	__except(GetExceptionCode() == ERROR_DLL_NOT_FOUND)
	{
		PRINT_ERROR(L"No CNG?\n");
	}
	return status;
}

BOOL kull_m_crypto_ngc_keyvalue_derived_hardware(PBYTE pbLabel, DWORD cbLabel, PBYTE pbContext, DWORD cbContext, LPCWSTR TransportKeyName, LPCBYTE Key, DWORD cbKey, PBYTE DerivedKey, DWORD cbDerivedKey)
{
	BOOL status = FALSE;
	SECURITY_STATUS nStatus;
	NCRYPT_PROV_HANDLE hProvider;
	NCRYPT_KEY_HANDLE hImportKey, hKey;
	DWORD cbResult;
	NCryptBuffer buffer[] = {
		{cbLabel, KDF_LABEL, pbLabel},
		{cbContext, KDF_CONTEXT, pbContext},
		{sizeof(NCRYPT_SHA256_ALGORITHM), KDF_HASH_ALGORITHM, NCRYPT_SHA256_ALGORITHM},
	};
	NCryptBufferDesc bufferDesc = {NCRYPTBUFFER_VERSION, ARRAYSIZE(buffer), buffer};
	PNCRYPTKEYDERIVATION NCryptKeyDerivation; // tofix

	__try
	{
		nStatus = NCryptOpenStorageProvider(&hProvider, MS_PLATFORM_CRYPTO_PROVIDER, 0);
		if(nStatus == ERROR_SUCCESS)
		{
			nStatus = NCryptOpenKey(hProvider, &hImportKey, TransportKeyName, 0, 0);
			if(nStatus == ERROR_SUCCESS)
			{
				nStatus = NCryptImportKey(hProvider, hImportKey, NCRYPT_OPAQUETRANSPORT_BLOB, NULL, &hKey, (PBYTE) Key, cbKey, 0);
				if(nStatus == ERROR_SUCCESS)
				{
					NCryptKeyDerivation = (PNCRYPTKEYDERIVATION) GetProcAddress(GetModuleHandle(L"ncrypt.dll"), "NCryptKeyDerivation"); // tofix
					if(NCryptKeyDerivation)
					{
						nStatus = NCryptKeyDerivation(hKey, &bufferDesc, DerivedKey, cbDerivedKey, &cbResult, 0);
						if(nStatus == ERROR_SUCCESS)
						{
							status = (cbResult == cbDerivedKey);
							if(!status)
								PRINT_ERROR(L"Bad cbResult (%u vs %u)\n", cbResult, cbDerivedKey);
						}
						else PRINT_ERROR(L"NCryptKeyDerivation: 0x%08x\n", nStatus);
					}
					else PRINT_ERROR(L"No NCryptKeyDerivation?\n", nStatus);
					NCryptFreeObject(hKey);
				}
				else PRINT_ERROR(L"NCryptImportKey: 0x%08x\n", nStatus);
				NCryptFreeObject(hImportKey);
			}
			else PRINT_ERROR(L"NCryptOpenKey: 0x%08x\n", nStatus);
			NCryptFreeObject(hProvider);
		}
		else PRINT_ERROR(L"NCryptOpenStorageProvider: 0x%08x\n", nStatus);
	}
	__except(GetExceptionCode() == ERROR_DLL_NOT_FOUND)
	{
		PRINT_ERROR(L"No CNG?\n");
	}
	return status;
}

BOOL kull_m_crypto_ngc_signature_derived(LPCBYTE pcbKey, DWORD cbKey, LPCBYTE pcbData, DWORD cbData, LPBYTE pbHash, DWORD cbHash)
{
	BOOL status = FALSE;
	NTSTATUS ntStatus;
	BCRYPT_ALG_HANDLE hAlgorithm;
	BCRYPT_HASH_HANDLE hHash;
	DWORD ObjectLength, cbResult;
	PUCHAR pbHashObject;

	__try
	{
		ntStatus = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_SHA256_ALGORITHM, MS_PRIMITIVE_PROVIDER, BCRYPT_ALG_HANDLE_HMAC_FLAG);
		if(BCRYPT_SUCCESS(ntStatus))
		{
			ntStatus = BCryptGetProperty(hAlgorithm, BCRYPT_OBJECT_LENGTH, (PUCHAR) &ObjectLength, sizeof(ObjectLength), &cbResult, 0);
			if(BCRYPT_SUCCESS(ntStatus))
			{
				if(pbHashObject = (PUCHAR) LocalAlloc(LPTR, ObjectLength))
				{
					ntStatus = BCryptCreateHash(hAlgorithm, &hHash, pbHashObject, ObjectLength, (PUCHAR) pcbKey, cbKey, 0);
					if(BCRYPT_SUCCESS(ntStatus))
					{
						BCryptHashData(hHash, (PUCHAR) pcbData, cbData, 0);
						ntStatus = BCryptFinishHash(hHash, pbHash, cbHash, 0);
						status = BCRYPT_SUCCESS(ntStatus);
						if(!status)
							PRINT_ERROR(L"BCryptFinishHash: 0x%08x\n", ntStatus);
						BCryptDestroyHash(hHash);
					}
					else PRINT_ERROR(L"BCryptCreateHash: 0x%08x\n", ntStatus);
					LocalFree(pbHashObject);
				}
			}
			else PRINT_ERROR(L"BCryptGetProperty: 0x%08x\n", ntStatus);
			BCryptCloseAlgorithmProvider(hAlgorithm, 0);
		}
		else PRINT_ERROR(L"BCryptOpenAlgorithmProvider: 0x%08x\n", ntStatus);
	}
	__except(GetExceptionCode() == ERROR_DLL_NOT_FOUND)
	{
		PRINT_ERROR(L"No CNG?\n");
	}
	return status;
}

BOOL kull_m_crypto_ngc_signature_pop(PBYTE pbKey, DWORD cbKey, PBYTE pbLabel, DWORD cbLabel, PBYTE pbContext, DWORD cbContext, PBYTE pbData, DWORD cbData, PBYTE *ppbOutput, PDWORD pcbOutput)
{
	BOOL status = FALSE;
	HMODULE hModule;
	PNGCSIGNWITHSYMMETRICPOPKEY NgcSignWithSymmetricPopKey;
	NTSTATUS ntStatus;

	*ppbOutput = NULL;
	*pcbOutput = 0;

	hModule = LoadLibrary(L"cryptngc.dll");
	if(hModule)
	{
		NgcSignWithSymmetricPopKey = (PNGCSIGNWITHSYMMETRICPOPKEY) GetProcAddress(hModule, "NgcSignWithSymmetricPopKey");
		if(NgcSignWithSymmetricPopKey)
		{
			ntStatus = NgcSignWithSymmetricPopKey(pbKey, cbKey, pbLabel, cbLabel, pbContext, cbContext, pbData, cbData, ppbOutput, pcbOutput);
			status = ntStatus == STATUS_SUCCESS;
			if(!status)
				PRINT_ERROR(L"NgcSignWithSymmetricPopKey: 0x%08x\n", ntStatus);
		}
		else PRINT_ERROR(L"No NgcSignWithSymmetricPopKey?\n");
		FreeLibrary(hModule);
	}
	else PRINT_ERROR_AUTO(L"LoadLibrary");
	return status;
}

PBYTE kull_m_crypto_ngc_pin_BinaryPinToPinProperty(LPCBYTE pbBinary, DWORD cbBinary, DWORD *pcbResult)
{
	PWSTR data = NULL;
	DWORD i, cbBuffer = cbBinary * 2 + 1;

	if(data = (PWSTR) LocalAlloc(LPTR, cbBuffer * sizeof(wchar_t)))
	{
		for(i = 0; i < cbBinary; i++)
			swprintf_s(data + i * 2, cbBuffer - i * 2, L"%02.2X", pbBinary[i]);
		if(pcbResult)
			*pcbResult = cbBuffer * sizeof(wchar_t);
	}
	return (PBYTE) data;
}

SECURITY_STATUS kull_m_crypto_ngc_hardware_unseal(NCRYPT_PROV_HANDLE hProv, LPCBYTE pbPin, DWORD cbPin, LPCBYTE pbInput, DWORD cbInput, PBYTE *ppOutput, DWORD *pcbOutput)
{
	SECURITY_STATUS status;
	NCRYPT_KEY_HANDLE hSealKey;
	UNK_PIN uPin = {cbPin, 0x46, (PWSTR) pbPin};
	UNK_PADDING uPadding = {0, 1, &uPin};
	DWORD cbResult;

	status = NCryptOpenKey(hProv, &hSealKey, TPM_RSA_SRK_SEAL_KEY, 0, NCRYPT_SILENT_FLAG);
	if(status == ERROR_SUCCESS)
	{
		status = NCryptDecrypt(hSealKey, (PBYTE) pbInput, cbInput, &uPadding, NULL, 0, &cbResult, NCRYPT_SEALING_FLAG);
		if(status == ERROR_SUCCESS)
		{
			if(*ppOutput = (PBYTE) LocalAlloc(LPTR, cbResult))
			{
				status = NCryptDecrypt(hSealKey, (PBYTE) pbInput, cbInput, &uPadding, *ppOutput, cbResult, pcbOutput, NCRYPT_SEALING_FLAG);
				if(status != ERROR_SUCCESS)
				{
					PRINT_ERROR(L"NCryptDecrypt(data): 0x%08x\n", status);
					*ppOutput = (PBYTE) LocalFree(*ppOutput);
					*pcbOutput = 0;
				}
			}
			else status = NTE_NO_MEMORY;
		}
		else PRINT_ERROR(L"NCryptDecrypt(init): 0x%08x\n", status);
		NCryptFreeObject(hSealKey);
	}
	else PRINT_ERROR(L"NCryptOpenKey(seal): 0x%08x\n", status);
	return status;
}

SECURITY_STATUS kull_m_crypto_ngc_software_decrypt(NCRYPT_PROV_HANDLE hProv, LPCWSTR szKeyName, LPCBYTE pbPin, DWORD cbPin, LPCBYTE pbInput, DWORD cbInput, PBYTE *ppOutput, DWORD *pcbOutput)
{
	SECURITY_STATUS status;
	NCRYPT_KEY_HANDLE hKey;
	DWORD cbResult, dwSalt, dwIterations, dwSmartCardPin;
	BYTE DerivedKey[32] = {0}, *SmartCardPin;

	status = NCryptOpenKey(hProv, &hKey, szKeyName, 0, 0x4000); // ?
	if(status == ERROR_SUCCESS)
	{
		status = NCryptGetProperty(hKey, L"NgcSoftwareKeyPbkdf2Salt", (PBYTE) &dwSalt, sizeof(dwSalt), &cbResult, 0x40000000 | NCRYPT_SILENT_FLAG); // ?
		if(status == ERROR_SUCCESS)
		{
			status = NCryptGetProperty(hKey, L"NgcSoftwareKeyPbkdf2Round", (PBYTE) &dwIterations, sizeof(dwIterations), &cbResult, 0x40000000 | NCRYPT_SILENT_FLAG); // ?
			if(status == ERROR_SUCCESS)
			{
				status = BCryptDeriveKeyPBKDF2(BCRYPT_HMAC_SHA256_ALG_HANDLE, (PUCHAR) pbPin, cbPin - sizeof(wchar_t), (PUCHAR) &dwSalt, sizeof(dwSalt), dwIterations, DerivedKey, sizeof(DerivedKey), 0);
				if(status == ERROR_SUCCESS)
				{
					if(SmartCardPin = kull_m_crypto_ngc_pin_BinaryPinToPinProperty(DerivedKey, sizeof(DerivedKey), &dwSmartCardPin))
					{
						status = NCryptSetProperty(hKey, NCRYPT_PIN_PROPERTY, SmartCardPin, dwSmartCardPin - sizeof(wchar_t), NCRYPT_SILENT_FLAG);
						if(status == ERROR_SUCCESS)
						{
							status = NCryptDecrypt(hKey, (PBYTE) pbInput, cbInput, NULL, NULL, 0, &cbResult, NCRYPT_PAD_PKCS1_FLAG | NCRYPT_SILENT_FLAG);
							if(status == ERROR_SUCCESS)
							{
								if(*ppOutput = (PBYTE) LocalAlloc(LPTR, cbResult))
								{
									status = NCryptDecrypt(hKey, (PBYTE) pbInput, cbInput, NULL, *ppOutput, cbResult, pcbOutput, NCRYPT_PAD_PKCS1_FLAG | NCRYPT_SILENT_FLAG);
									if(status != ERROR_SUCCESS)
									{
										PRINT_ERROR(L"NCryptDecrypt(data): 0x%08x\n", status);
										*ppOutput = (PBYTE) LocalFree(*ppOutput);
										*pcbOutput = 0;
									}
								}
								else status = NTE_NO_MEMORY;
							}
							else PRINT_ERROR(L"NCryptDecrypt(init): 0x%08x\n", status);
						}
						else PRINT_ERROR(L"NCryptSetProperty(NCRYPT_PIN_PROPERTY): 0x%08x\n", status);
						LocalFree(SmartCardPin);
					}
					else status = NTE_NO_MEMORY;
				}
				else PRINT_ERROR(L"BCryptDeriveKeyPBKDF2: 0x%08x\n", status);
			}
			else PRINT_ERROR(L"NCryptGetProperty(NgcSoftwareKeyPbkdf2Round): 0x%08x\n", status);
		}
		else PRINT_ERROR(L"NCryptGetProperty(NgcSoftwareKeyPbkdf2Salt): 0x%08x\n", status);
		NCryptFreeObject(hKey);
	}
	else PRINT_ERROR(L"NCryptOpenKey(0x4000): 0x%08x\n", status);

	return status;
}