// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//#ifndef PLATFORM_UNIX
#define UMDF_USING_NTSTATUS
#include "windows.h"
#ifndef _NTSTATUS_
#include "ntstatus.h"
#endif // !_NTSTATUS_
#include "bcrypt.h"
#include "wincrypt.h"
#include "..\BcryptError.h"
//#endif // !PLATFORM_UNIX
#include "..\ECDiffieHellman.h"
#include "BcryptECDiffieHellman.h"
#include "..\LibraryLogger.h"
#include "..\ReturnCodes.h"
#include <vector>

using namespace SecretsLogger;

// ASN.1 constants. The unknown ASN.1 bytes are referenced in other
// implementations of ECDiffieHellman, but the definitions could not
// be found in the Windows API documentation.
#define EC_UNCOMPRESSED_BLOB 0x04
#define EC_UNKNOWN_ASN_BYTE  0x06
#define EC_UNKNOWN_ASN_BYTE2 0x07

#define EC_PUBLIC_NUM_COMPONENTS  2
#define EC_PRIVATE_NUM_COMPONENTS 3

BcryptECDiffieHellman::BcryptECDiffieHellman()
{
#ifndef PLATFORM_UNIX
    NTSTATUS status = STATUS_SUCCESS;
    this->hSharedSecret = nullptr;
    this->hEccKeyHandle = nullptr;
    this->hEcHandle = nullptr;
    status = BCryptOpenAlgorithmProvider(&(this->hEcHandle), BCRYPT_ECDH_P256_ALGORITHM, NULL, 0);
    if (status != STATUS_SUCCESS) {
        // LibraryError, Bcrypt subclass, providerError
		throw BcryptError(status, "BCryptOpenAlgorithmProvider failed.\n",
            ErrorCode::LibraryError_Bcrypt_providerError);
	}
#endif // !PLATFORM_UNIX
}

BcryptECDiffieHellman::~BcryptECDiffieHellman()
{
#ifndef PLATFORM_UNIX
    if (this->hSharedSecret != nullptr) {
		BCryptDestroySecret(this->hSharedSecret);
	}
    if (this->hEccKeyHandle != nullptr) {
        BCryptDestroyKey(this->hEccKeyHandle);
    }
    if (this->hEcHandle != nullptr) {
        BCryptCloseAlgorithmProvider(this->hEcHandle, 0);
    }
#endif // !PLATFORM_UNIX
}

void BcryptECDiffieHellman::GenerateKeyPair() {
#ifndef PLATFORM_UNIX
	NTSTATUS status;
	status = BCryptGenerateKeyPair(
        this->hEcHandle,
        &(this->hEccKeyHandle),
        256,
        0);
    if (status != STATUS_SUCCESS) {
		// CryptographyError, ECC subclass, keyError
		throw BcryptError(status, "BCryptGenerateKeyPair failed.\n",
            ErrorCode::CryptographyError_ECC_keyError);
	}
    status = BCryptFinalizeKeyPair(this->hEccKeyHandle, 0);
    if (status != STATUS_SUCCESS) {
        // CryptographyError, ECC subclass, keyError
        throw BcryptError(status, "BCryptFinalizeKeyPair failed.\n",
            ErrorCode::CryptographyError_ECC_keyError);
    }
#endif // !PLATFORM_UNIX
}

void BcryptECDiffieHellman::ImportPkcs8PrivateKey(std::vector<unsigned char> const&derPrivateKey)
{
#ifndef PLATFORM_UNIX
    PCRYPT_PRIVATE_KEY_INFO privateKeyInfo = nullptr;
    DWORD publicKeyInfoLen = 0;
    CRYPT_ECC_PRIVATE_KEY_INFO* eccPrivateInfo = nullptr;
    DWORD eccKeyInfoLen = 0;
    ULONG cbPubKey = 0;
    BCRYPT_ECCKEY_BLOB* pekb = nullptr;
    NTSTATUS status = STATUS_SUCCESS;
    if (!CryptDecodeObjectEx(
            X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
            PKCS_PRIVATE_KEY_INFO,
            derPrivateKey.data(), derPrivateKey.size(),
            CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
            NULL,
            &privateKeyInfo, &publicKeyInfoLen))
    {
        // ParsingError, ASN subclass, x509PrivKeyError
        throw WinCryptError("CryptDecodeObjectEx 1 failed.", GetLastError(),
            ErrorCode::ParsingError_Asn1_x509PrivKeyError);
    }

    if (!CryptDecodeObjectEx(
            X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
            X509_ECC_PRIVATE_KEY,
            privateKeyInfo->PrivateKey.pbData, privateKeyInfo->PrivateKey.cbData,
            CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
            NULL,
            &eccPrivateInfo, &eccKeyInfoLen))
    {
        // ParsingError, ASN subclass, x509PrivKeyError
        throw WinCryptError("CryptDecodeObjectEx 2 failed.", GetLastError(),
            ErrorCode::ParsingError_Asn1_x509PrivKeyError);
    }

    if (NULL != eccPrivateInfo->PublicKey.pbData &&
        eccPrivateInfo->PublicKey.cbData > 0)
    {
        if (
            EC_UNCOMPRESSED_BLOB != eccPrivateInfo->PublicKey.pbData[0] &&
            EC_UNKNOWN_ASN_BYTE  != eccPrivateInfo->PublicKey.pbData[0] &&
            EC_UNKNOWN_ASN_BYTE2 != eccPrivateInfo->PublicKey.pbData[0]
            )
        {
            // ParsingError, ASN subclass, x509PrivKeyError
            throw WinCryptError("Error with public key info", 0,
                ErrorCode::ParsingError_Asn1_x509PrivKeyError);
        }
        cbPubKey = (eccPrivateInfo->PublicKey.cbData - 1) / EC_PUBLIC_NUM_COMPONENTS;
    }
    else
    {
        cbPubKey = 0;
    }

    ULONG cbKey = eccPrivateInfo->PrivateKey.cbData;

    if (cbKey < eccPrivateInfo->PrivateKey.cbData ||
        cbKey < cbPubKey)
    {
        LIBSECRETS_LOG(
			LogLevel::Info,
			"EC Diffie Hellman Import",
			"Error with private key info");
    }
    LIBSECRETS_LOG(
        LogLevel::Debug,
        "EC Diffie Hellman Import",
        "Passed private validation");

    ULONG cbKeyBlob = sizeof(BCRYPT_ECCKEY_BLOB) + EC_PRIVATE_NUM_COMPONENTS * cbKey;
    pekb = (BCRYPT_ECCKEY_BLOB*)malloc(cbKeyBlob);
    if (pekb == nullptr) {
        // GenericError, Memory subclass, mallocError
        throw std::exception("ECDH - Malloc Fail");
    }
    ZeroMemory(pekb, cbKeyBlob);

    pekb->dwMagic = BCRYPT_ECDH_PRIVATE_P256_MAGIC;
    pekb->cbKey = cbKey;

    BYTE* pb = NULL;
    if (cbPubKey > 0)
    {
        if (cbKey == cbPubKey)
        {
            CopyMemory(pekb + 1, eccPrivateInfo->PublicKey.pbData + 1, EC_PUBLIC_NUM_COMPONENTS * cbKey);
        }
        else
        {
            ULONG off = 0;
            pb = (BYTE*)(pekb + 1);
            off = cbKey - cbPubKey;
            CopyMemory(pb + off, eccPrivateInfo->PublicKey.pbData + 1, cbPubKey);
            CopyMemory(pb + cbKey + off, eccPrivateInfo->PublicKey.pbData + 1 + cbPubKey, cbPubKey);
        }
    }

    pb = (BYTE*)(pekb + 1) + EC_PUBLIC_NUM_COMPONENTS * cbKey;
    CopyMemory(pb + (cbKey - eccPrivateInfo->PrivateKey.cbData),
        eccPrivateInfo->PrivateKey.pbData,
        eccPrivateInfo->PrivateKey.cbData);

    if (privateKeyInfo != nullptr) {
        LocalFree(privateKeyInfo);
    }
    
    if (eccPrivateInfo != nullptr) {
        LocalFree(eccPrivateInfo);
    }

    if (cbKeyBlob != sizeof(BCRYPT_ECCKEY_BLOB) + pekb->cbKey * EC_PRIVATE_NUM_COMPONENTS) {
        // ParsingError, ASN subclass, x509PrivKeyError
        throw WinCryptError("Invalid key blob", 0,
            ErrorCode::ParsingError_Asn1_x509PrivKeyError);
    }
    status = BCryptImportKeyPair(this->hEcHandle, NULL, BCRYPT_ECCPRIVATE_BLOB, &(this->hEccKeyHandle), (PUCHAR)(pekb), cbKeyBlob, 0);
    if (status != STATUS_SUCCESS) {
		// LibraryError, Bcrypt subclass, keyError
        throw BcryptError(status, "BCryptImportKeyPair 1 failed.\n",
            ErrorCode::LibraryError_Bcrypt_keyError);
    }
    
#endif // !PLATFORM_UNIX
}

void BcryptECDiffieHellman::ImportSubjectPublicKeyInfo(std::vector<unsigned char> const&derPublicKey) {
#ifndef PLATFORM_UNIX
    DWORD          publicKeyInfoLen = 0;
    HCRYPTPROV     hProv = 0;
    HCRYPTKEY      hKey = 0;
    CERT_PUBLIC_KEY_INFO certPubInfo{};
    ULONG cb = 0;
    NTSTATUS status;
    union {
        PVOID pvStructInfo;
        PCERT_INFO pCertInfo;
        PCERT_PUBLIC_KEY_INFO PublicKeyInfo;
    };

    if (!CryptDecodeObjectEx(
        X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
        X509_PUBLIC_KEY_INFO,
        derPublicKey.data(), derPublicKey.size(),
        CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
        NULL,
        &pvStructInfo, &cb))
    {
		// ParsingError, ASN subclass, x509PubKeyError
        throw WinCryptError("CryptDecodeObjectEx 1 failed.", GetLastError(),
            ErrorCode::ParsingError_Asn1_x509PubKeyError);
    }
    LIBSECRETS_LOG(
        LogLevel::Debug,
        "EC Diffie Hellman Import",
        "Public key size: PublicKeyInfo->PublicKey.cbData %d",
        PublicKeyInfo->PublicKey.cbData);

    CRYPT_ECC_PRIVATE_KEY_INFO* eccPrivateInfo = nullptr;
    DWORD eccKeyInfoLen = 0;
    ULONG cbPubKey = 0;
    if (NULL != PublicKeyInfo->PublicKey.pbData &&
        PublicKeyInfo->PublicKey.cbData > 0
        )
    {
        if (
            EC_UNCOMPRESSED_BLOB != PublicKeyInfo->PublicKey.pbData[0] &&
            EC_UNKNOWN_ASN_BYTE  != PublicKeyInfo->PublicKey.pbData[0] &&
            EC_UNKNOWN_ASN_BYTE2 != PublicKeyInfo->PublicKey.pbData[0]
            )
        {
            LIBSECRETS_LOG(
                LogLevel::Debug,
                "EC Diffie Hellman Import",
                "Error with public key info: PublicKeyInfo->PublicKey.pbData[0] %x",
                PublicKeyInfo->PublicKey.pbData[0]);

        }

        cbPubKey = (PublicKeyInfo->PublicKey.cbData - 1) / EC_PUBLIC_NUM_COMPONENTS;
    }
    else
    {
        cbPubKey = 0;
    }

    LIBSECRETS_LOG(LogLevel::Debug, "EC Diffie Hellman Import", "Passed private validation", nullptr);

    ULONG cbKey = cbPubKey;
    ULONG cbKeyBlob = sizeof(BCRYPT_ECCKEY_BLOB) + EC_PUBLIC_NUM_COMPONENTS * cbKey;
    BCRYPT_ECCKEY_BLOB* pekb = (BCRYPT_ECCKEY_BLOB*)malloc(cbKeyBlob);
    if (pekb == NULL) {
        // GenericError, Memory subclass, mallocError
        throw std::exception("ECDH - Malloc Failed");
    }
    ZeroMemory(pekb, cbKeyBlob);

    pekb->dwMagic = BCRYPT_ECDH_PUBLIC_P256_MAGIC;
    pekb->cbKey = cbKey;
    BYTE* pb = NULL;

    if (cbPubKey > 0)
    {
        if (cbKey == cbPubKey)
        {
            CopyMemory(pekb + 1, PublicKeyInfo->PublicKey.pbData + 1, EC_PUBLIC_NUM_COMPONENTS * cbKey);
        }
        else
        {
            ULONG off = 0;
            pb = (BYTE*)(pekb + 1);
            off = cbKey - cbPubKey;
            CopyMemory(pb + off, PublicKeyInfo->PublicKey.pbData + 1, cbPubKey);
            CopyMemory(pb + cbKey + off, PublicKeyInfo->PublicKey.pbData + 1 + cbPubKey, cbPubKey);
        }
    }
    if (PublicKeyInfo != nullptr) {
        LocalFree(PublicKeyInfo);
    }

    if (cbKeyBlob != sizeof(BCRYPT_ECCKEY_BLOB) + pekb->cbKey * EC_PUBLIC_NUM_COMPONENTS) {
        // ParsingError, ASN subclass, x509PubKeyError
        throw WinCryptError("Invalid key blob", 0,
            ErrorCode::ParsingError_Asn1_x509PubKeyError);
    }

    status = BCryptImportKeyPair(this->hEcHandle, NULL, BCRYPT_ECCPUBLIC_BLOB, &(this->hEccKeyHandle), (PUCHAR)(pekb), cbKeyBlob, 0);

    if (status != STATUS_SUCCESS) {
		// LibraryError, Bcrypt subclass, keyError
        throw BcryptError(status, "BCryptImportKeyPair 1 failed.\n",
            ErrorCode::LibraryError_Bcrypt_keyError);
    }

#endif // !PLATFORM_UNIX
}

std::vector<unsigned char> BcryptECDiffieHellman::ExportPkcs8PrivateKey() const
{
	std::vector<unsigned char> result;
#ifndef PLATFORM_UNIX
	DWORD privateKeyBlobLen = 0;
	NTSTATUS status;
    CRYPT_ECC_PRIVATE_KEY_INFO eccPrivateInfo = { 0 };
    CRYPT_PRIVATE_KEY_INFO* privateKeyInfo = nullptr;
    DWORD privateKeyInfoLen = 0;
    BYTE* pbCurveParameters = nullptr;
    DWORD cbCurveParameters = 0;
    BCRYPT_ECCKEY_BLOB* pekb = nullptr;
    // Get the size of the private key blob
	status = BCryptExportKey(this->hEccKeyHandle, NULL, BCRYPT_ECCPRIVATE_BLOB, NULL, 0, &privateKeyBlobLen, 0);
    if (status != STATUS_SUCCESS) {
		// LibraryError, Bcrypt subclass, keyError
		throw BcryptError(status, "BCryptExportKey - Get Size - failed.\n",
            ErrorCode::LibraryError_Bcrypt_keyError);
	}
    unsigned char* privateKeyBlob = (unsigned char *)malloc(privateKeyBlobLen);
    // Export the private key blob
	status = BCryptExportKey(this->hEccKeyHandle, NULL, BCRYPT_ECCPRIVATE_BLOB, privateKeyBlob, privateKeyBlobLen, &privateKeyBlobLen, 0);
    if (status != STATUS_SUCCESS) {
		// LibraryError, Bcrypt subclass, keyError
		throw BcryptError(status, "BCryptExportKey - Export Key - failed.\n",
            ErrorCode::LibraryError_Bcrypt_keyError);
	}
    pekb = (BCRYPT_ECCKEY_BLOB*)privateKeyBlob;
    // Get the size of the curve parameters
    status = BCryptGetProperty(
        this->hEccKeyHandle,
        BCRYPT_ECC_PARAMETERS,
        NULL,                       // pbOutput
        0,                          // cbOutput
        &cbCurveParameters,
        0                           // dwFlags
    );
    if (status != STATUS_SUCCESS) {
		// LibraryError, Bcrypt subclass, propertyError
        throw BcryptError(status, "BCryptGetProperty Size failed.\n",
            ErrorCode::LibraryError_Bcrypt_propertyError);
    }
    // Get the curve parameters
    pbCurveParameters = (BYTE*)malloc(cbCurveParameters);
    status = BCryptGetProperty(
		this->hEccKeyHandle,
		BCRYPT_ECC_PARAMETERS,
		pbCurveParameters,
		cbCurveParameters,
		&cbCurveParameters,
		0);
    if (status != STATUS_SUCCESS) {
		// LibraryError, Bcrypt subclass, propertyError
        throw BcryptError(status, "BCryptGetProperty Value failed.\n",
            ErrorCode::LibraryError_Bcrypt_propertyError);
    }

    CRYPT_DATA_BLOB CNGECCBlob = { 0 };
	CNGECCBlob.cbData = cbCurveParameters;
    CNGECCBlob.pbData = pbCurveParameters;
    ULONG cbCurveInfo = 0;
    // Get the size of the curve info
    if (!CryptEncodeObject(
        X509_ASN_ENCODING,
        X509_ECC_PARAMETERS,
        &CNGECCBlob,
        NULL,
        &cbCurveInfo))
    {
        // ParsingError, ASN subclass, x509PrivKeyError
        throw WinCryptError("CryptEncodeObject 1 failed.", GetLastError(),
            ErrorCode::ParsingError_Asn1_x509PrivKeyError);
	}

    eccPrivateInfo.dwVersion = CRYPT_ECC_PRIVATE_KEY_INFO_v1;
    eccPrivateInfo.szCurveOid = NULL;
    eccPrivateInfo.PrivateKey.pbData = (BYTE*)(pekb + 1) + EC_PUBLIC_NUM_COMPONENTS * pekb->cbKey;
    eccPrivateInfo.PrivateKey.cbData = pekb->cbKey;
    ULONG cbPublicKey;
    BYTE* pbPublicKey;

    pbPublicKey = (BYTE*)&pekb[1];
    cbPublicKey = pekb->cbKey;

    eccPrivateInfo.PublicKey.cbData = 1 + 2 * cbPublicKey;
    eccPrivateInfo.PublicKey.cUnusedBits = 0;
	eccPrivateInfo.PublicKey.pbData = (BYTE*)malloc(eccPrivateInfo.PublicKey.cbData);
    ZeroMemory(eccPrivateInfo.PublicKey.pbData, eccPrivateInfo.PublicKey.cbData);
    eccPrivateInfo.PublicKey.pbData[0] = EC_UNCOMPRESSED_BLOB;
    CopyMemory(
        eccPrivateInfo.PublicKey.pbData + 1,
        pbPublicKey,
        cbPublicKey);
    CopyMemory(
        eccPrivateInfo.PublicKey.pbData + 1 + cbPublicKey,
        pbPublicKey + pekb->cbKey,
        cbPublicKey);
    CRYPT_BIT_BLOB CryptBitBlob = { 0 };
    CryptBitBlob.cbData = 1;
    CryptBitBlob.pbData = (BYTE*)malloc(1);
    CryptBitBlob.pbData[0] = CERT_KEY_AGREEMENT_KEY_USAGE;
    CryptBitBlob.cUnusedBits = 0;

    ULONG cbKeyUsage = 0;
    ULONG cbEncodedKey = 0;
    // Get the size of the key
    if (!CryptEncodeObjectEx(
        X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
        X509_ECC_PRIVATE_KEY,
        (BYTE*)(&eccPrivateInfo),
        0,
        NULL,
        NULL, &cbEncodedKey))
    {
        // ParsingError, ASN subclass, x509PrivKeyError
        throw WinCryptError("CryptEncodeObjectEx 1 failed.", GetLastError(),
            ErrorCode::ParsingError_Asn1_x509PrivKeyError);
    }

    ULONG cbPriKeyInfo = cbPriKeyInfo = sizeof(*privateKeyInfo) + sizeof(szOID_ECC_PUBLIC_KEY);
    if (cbKeyUsage > 0)
    {
        cbPriKeyInfo +=
            sizeof(CRYPT_ATTRIBUTES) +
            sizeof(CRYPT_ATTRIBUTE) +
            sizeof(CRYPT_ATTR_BLOB) +
            sizeof(szOID_KEY_USAGE);
    }

    cbPriKeyInfo += cbCurveInfo;
    cbPriKeyInfo += cbKeyUsage;
    cbPriKeyInfo += cbEncodedKey;

    BYTE* pb = NULL;
    privateKeyInfo = (PCRYPT_PRIVATE_KEY_INFO)malloc(cbPriKeyInfo);
    if (privateKeyInfo == NULL) {
        // GenericError, Memory subclass, mallocError
        throw std::exception("ECDH Malloc Fail");
    }
    ZeroMemory(privateKeyInfo, cbPriKeyInfo);

    if (cbKeyUsage > 0)
    {
        CRYPT_ATTRIBUTES* pAttrList = (CRYPT_ATTRIBUTES*)(privateKeyInfo + 1);
        CRYPT_ATTRIBUTE* pAttr = (CRYPT_ATTRIBUTE*)(pAttrList + 1);
        CRYPT_ATTR_BLOB* pAttrBlob = (CRYPT_ATTR_BLOB*)(pAttr + 1);

        privateKeyInfo->pAttributes = pAttrList;

        pAttrList->cAttr = 1;
        pAttrList->rgAttr = pAttr;

        pAttr->pszObjId = (PSTR)(pAttrBlob + 1);
        pAttr->cValue = 1;
        pAttr->rgValue = pAttrBlob;

        pAttrBlob->cbData = cbKeyUsage;
        pAttrBlob->pbData =
            (BYTE*)(pAttr->pszObjId + sizeof(szOID_KEY_USAGE));

        CopyMemory(
            pAttr->pszObjId,
            szOID_KEY_USAGE,
            sizeof(szOID_KEY_USAGE));

        if (!CryptEncodeObject(
            X509_ASN_ENCODING,
            X509_BITS,
            &CryptBitBlob,
            pAttrBlob->pbData,
            &pAttrBlob->cbData))
        {
            // ParsingError, ASN subclass, x509PrivKeyError
            throw WinCryptError("CryptEncodeObjectEx 2 failed.", GetLastError(),
                ErrorCode::ParsingError_Asn1_x509PrivKeyError);
        }

        pb = pAttrBlob->pbData + pAttrBlob->cbData;
    }
    else
    {
        pb = (BYTE*)(privateKeyInfo + 1);
    }

    privateKeyInfo->Algorithm.Parameters.cbData = cbCurveInfo;
    privateKeyInfo->Algorithm.Parameters.pbData = pb + cbEncodedKey;
    privateKeyInfo->Algorithm.pszObjId =
        (PSTR)(pb + cbEncodedKey + cbCurveInfo);

    privateKeyInfo->PrivateKey.cbData = cbEncodedKey;
    privateKeyInfo->PrivateKey.pbData = pb;
    // Encode the key
    if (!CryptEncodeObjectEx(
        X509_ASN_ENCODING,
        X509_ECC_PRIVATE_KEY,
        &eccPrivateInfo,
        0,
        NULL,
        privateKeyInfo->PrivateKey.pbData,
        &privateKeyInfo->PrivateKey.cbData))
    {
        // ParsingError, ASN subclass, x509PrivKeyError
        throw WinCryptError("CryptEncodeObjectEx 3 failed.", GetLastError(),
            ErrorCode::ParsingError_Asn1_x509PrivKeyError);
    }
    // Encode the curve parameters
    if (!CryptEncodeObject(
        X509_ASN_ENCODING,
        X509_ECC_PARAMETERS,
        &CNGECCBlob,
        privateKeyInfo->Algorithm.Parameters.pbData,
        &privateKeyInfo->Algorithm.Parameters.cbData))
    {
        // ParsingError, ASN subclass, x509PrivKeyError    
        throw WinCryptError("CryptEncodeObjectEx 4 failed.", GetLastError(),
            ErrorCode::ParsingError_Asn1_x509PrivKeyError);
    }

    CopyMemory(
        privateKeyInfo->Algorithm.pszObjId,
        szOID_ECC_PUBLIC_KEY,
        sizeof(szOID_ECC_PUBLIC_KEY));
    ULONG derPrivateBytesLen = 0;
    unsigned char *derPrivateBytes = nullptr;
    // Encode the private key
    if (!CryptEncodeObjectEx(
        X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
        PKCS_PRIVATE_KEY_INFO,
        (BYTE*)privateKeyInfo,
        CRYPT_ENCODE_ALLOC_FLAG,
        NULL,
        &derPrivateBytes, &derPrivateBytesLen))
    {
        // ParsingError, ASN subclass, x509PrivKeyError
        throw WinCryptError("CryptEncodeObjectEx 5 failed.", GetLastError(),
            ErrorCode::ParsingError_Asn1_x509PrivKeyError);
    }
    
	result = std::vector<unsigned char>(derPrivateBytes, derPrivateBytes + derPrivateBytesLen);
#endif // !PLATFORM_UNIX
    return result;
}

std::vector<unsigned char> BcryptECDiffieHellman::ExportSubjectPublicKeyInfo() const
{
	std::vector<unsigned char> result;
#ifndef PLATFORM_UNIX
    DWORD publicKeyBlobLen = 0;
    NTSTATUS status = STATUS_SUCCESS;
    CRYPT_ECC_PRIVATE_KEY_INFO eccPrivateInfo = { 0 };
    CRYPT_PRIVATE_KEY_INFO privateKeyInfo = { 0 };
    DWORD privateKeyInfoLen = 0;
    BYTE* pbCurveParameters = nullptr;
    DWORD cbCurveParameters = 0;
    BCRYPT_ECCKEY_BLOB* pekb = nullptr;
    CERT_PUBLIC_KEY_INFO publicKeyInfo = { 0 };
    union {
        PVOID pvStructInfo;
        PCERT_INFO pCertInfo;
        PCERT_PUBLIC_KEY_INFO PublicKeyInfo;
    };
    // Get the size of the public key blob
    status = BCryptExportKey(this->hEccKeyHandle, NULL, BCRYPT_ECCPUBLIC_BLOB, NULL, 0, &publicKeyBlobLen, 0);
    if (status != STATUS_SUCCESS) {
        // LibraryError, Bcrypt subclass, keyError
        throw BcryptError(status, "BCryptExportKey - Get Size - failed.\n",
            ErrorCode::LibraryError_Bcrypt_keyError);
    }
    unsigned char* publicKeyBlob = (unsigned char*)malloc(publicKeyBlobLen);
    if (publicKeyBlob == NULL) {
		// GenericError, Memory subclass, mallocError
        throw std::exception("ECDH - Malloc Fail");
    }
    // Export the public key blob
    status = BCryptExportKey(this->hEccKeyHandle, NULL, BCRYPT_ECCPUBLIC_BLOB, publicKeyBlob, publicKeyBlobLen, &publicKeyBlobLen, 0);
    if (status != STATUS_SUCCESS) {
		// LibraryError, Bcrypt subclass, keyError
        throw BcryptError(status, "BCryptExportKey - Export Key - failed.\n",
            ErrorCode::LibraryError_Bcrypt_keyError);
    }

    pekb = (BCRYPT_ECCKEY_BLOB*)publicKeyBlob;

    CRYPT_BIT_BLOB CryptBitBlob = { 0 };
    CryptBitBlob.cbData = 1;
    CryptBitBlob.pbData = (BYTE*)malloc(1);
    CryptBitBlob.pbData[0] = CERT_KEY_AGREEMENT_KEY_USAGE;
    CryptBitBlob.cUnusedBits = 0;
    // Get the size of the curve parameters
    status = BCryptGetProperty(
        this->hEccKeyHandle,
        BCRYPT_ECC_PARAMETERS,
        NULL,                       // pbOutput
        0,                          // cbOutput
        &cbCurveParameters,
        0                           // dwFlags
    );
    if (status != STATUS_SUCCESS) {
		// LibraryError, Bcrypt subclass, propertyError
        throw BcryptError(status, "BCryptGetProperty Size failed.\n",
            ErrorCode::LibraryError_Bcrypt_propertyError);
    }

    pbCurveParameters = (BYTE*)malloc(cbCurveParameters);
    if (pbCurveParameters == nullptr) {
        // GenericError, Memory subclass, mallocError
        throw std::exception("ECDH - Malloc Failed");
    }
    // Get the curve parameters
    status = BCryptGetProperty(
        this->hEccKeyHandle,
        BCRYPT_ECC_PARAMETERS,
        pbCurveParameters,
        cbCurveParameters,
        &cbCurveParameters,
        0);
    if (status != STATUS_SUCCESS) {
		// LibraryError, Bcrypt subclass, propertyError
        throw BcryptError(status, "BCryptGetProperty Value failed.\n",
            ErrorCode::LibraryError_Bcrypt_propertyError);
    }

    CRYPT_DATA_BLOB CNGECCBlob = { 0 };
    CNGECCBlob.cbData = cbCurveParameters;
    CNGECCBlob.pbData = pbCurveParameters;
    ULONG cbCurveInfo = 0;

    if (!CryptEncodeObject(
        X509_ASN_ENCODING,
        X509_ECC_PARAMETERS,
        &CNGECCBlob,
        NULL,
        &cbCurveInfo))
    {
        // ParsingError, ASN subclass, x509PubKeyError
        throw WinCryptError("CryptEncodeObject 1 failed.", GetLastError(),
            ErrorCode::ParsingError_Asn1_x509PubKeyError);
    }

    ULONG cbPupKeyInfo = sizeof(publicKeyInfo) + sizeof(szOID_ECC_PUBLIC_KEY);

    cbPupKeyInfo += cbCurveInfo;

    publicKeyInfo.PublicKey.cbData = pekb->cbKey * EC_PUBLIC_NUM_COMPONENTS + 1;
    publicKeyInfo.PublicKey.cUnusedBits = 0;
    publicKeyInfo.PublicKey.pbData = (BYTE*)malloc(publicKeyInfo.PublicKey.cbData);
    if (publicKeyInfo.PublicKey.pbData == NULL) {
		// GenericError, Memory subclass, mallocError
        throw std::exception("ECDH - Malloc Fail");
    }
    ZeroMemory(publicKeyInfo.PublicKey.pbData, publicKeyInfo.PublicKey.cbData);
    publicKeyInfo.PublicKey.pbData[0] = EC_UNCOMPRESSED_BLOB;
    CopyMemory(
		publicKeyInfo.PublicKey.pbData + 1, pekb + 1, pekb->cbKey * EC_PUBLIC_NUM_COMPONENTS);
    publicKeyInfo.Algorithm.Parameters.cbData = cbCurveInfo;
    publicKeyInfo.Algorithm.Parameters.pbData = (BYTE*)malloc(publicKeyInfo.Algorithm.Parameters.cbData);
    if (publicKeyInfo.Algorithm.Parameters.pbData == NULL) {
        // GenericError, Memory subclass, mallocError
        throw std::exception("ECDH - Malloc Fail");
    }
    publicKeyInfo.Algorithm.pszObjId = (LPSTR)malloc(sizeof(szOID_ECC_PUBLIC_KEY));
    if (publicKeyInfo.Algorithm.pszObjId == NULL) {
        // GenericError, Memory subclass, mallocError
        throw std::exception("ECDH - Malloc Fail");
    }
    ZeroMemory(publicKeyInfo.Algorithm.Parameters.pbData, publicKeyInfo.Algorithm.Parameters.cbData);
    if (!CryptEncodeObject(
        X509_ASN_ENCODING,
        X509_ECC_PARAMETERS,
        &CNGECCBlob,
        publicKeyInfo.Algorithm.Parameters.pbData,
        &publicKeyInfo.Algorithm.Parameters.cbData))
    {
        // ParsingError, ASN subclass, x509PubKeyError
        throw WinCryptError("CryptEncodeObject 2 failed.", GetLastError(),
            ErrorCode::ParsingError_Asn1_x509PubKeyError);
    }
    CopyMemory(
		publicKeyInfo.Algorithm.pszObjId,
		szOID_ECC_PUBLIC_KEY,
		sizeof(szOID_ECC_PUBLIC_KEY));

	ULONG derPublicBytesLen = 0;
	unsigned char* derPublicBytes = nullptr;
    if (!CryptEncodeObjectEx(
        X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
        X509_PUBLIC_KEY_INFO,
        (BYTE*)(&publicKeyInfo),
        CRYPT_ENCODE_ALLOC_FLAG,
        NULL,
        &derPublicBytes, &derPublicBytesLen))
    {
		// ParsingError, ASN subclass, x509PubKeyError
        throw WinCryptError("CryptEncodeObjectEx 1 failed.", GetLastError(),
            ErrorCode::ParsingError_Asn1_x509PubKeyError);
	}
	result = std::vector<unsigned char>(derPublicBytes, derPublicBytes + derPublicBytesLen);
#endif // !PLATFORM_UNIX
	return result;
}

BCRYPT_SECRET_HANDLE BcryptECDiffieHellman::DeriveSecret(ECDiffieHellman &otherParty)
{
#ifndef PLATFORM_UNIX
	BCRYPT_SECRET_HANDLE hSecret;
	NTSTATUS status = BCryptSecretAgreement(this->hEccKeyHandle, otherParty.GetPublicKeyHandle(), &(this->hSharedSecret), 0);
    if (status != STATUS_SUCCESS) {
		// CryptographyError, ECC subclass, keyGenerationError
		throw BcryptError(status, "BCryptSecretAgreement failed.\n",
            ErrorCode::CryptographyError_ECC_keyGenError);
	}
#endif // !PLATFORM_UNIX
    return this->hSharedSecret;
}

BCRYPT_KEY_HANDLE BcryptECDiffieHellman::GetPublicKeyHandle(void) const {
    return this->hEccKeyHandle;
}