#define NOMINMAX #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "TpmAtt.h" #pragma comment(lib, "ncrypt.lib") #pragma comment(lib, "bcrypt.lib") #pragma comment(lib, "crypt32.lib") #pragma comment(lib, "TpmAtt.lib") enum class HardwareKeyType { ECDSA_P256, RSA_2048_PSS }; class UnifiedCryptoHelper { private: NCRYPT_PROV_HANDLE hProvider_ = NULL; NCRYPT_KEY_HANDLE hHardwareKey_ = NULL; NCRYPT_KEY_HANDLE hAIKKey_ = NULL; EC_KEY *accelEcdhKey_ = nullptr; std::vector sharedSecret_; HardwareKeyType hwKeyType_; std::string keyTypeString_; std::wstring aikName_; public: UnifiedCryptoHelper(HardwareKeyType keyType = HardwareKeyType::ECDSA_P256) : hwKeyType_(keyType) { // Initialize OpenSSL ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); // Initialize NCrypt for hardware key SECURITY_STATUS status; // Open the default key storage provider status = NCryptOpenStorageProvider(&hProvider_, MS_PLATFORM_KEY_STORAGE_PROVIDER, 0); if (FAILED(status)) { throw std::runtime_error("Failed to open NCrypt storage provider"); } // Generate hardware key based on type if (hwKeyType_ == HardwareKeyType::RSA_2048_PSS) { generateRSAHardwareKey(); keyTypeString_ = "rsa-2048-pss"; } else { generateECDSAHardwareKey(); keyTypeString_ = "ecdsa-p256"; } // Generate ECDH P-256 key using OpenSSL for acceleration accelEcdhKey_ = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); if (!accelEcdhKey_ || !EC_KEY_generate_key(accelEcdhKey_)) { throw std::runtime_error("Failed to generate ECDH acceleration key"); } std::cout << "Generated " << keyTypeString_ << " hardware key (NCrypt) and ECDH P-256 acceleration key (OpenSSL)" << std::endl; } ~UnifiedCryptoHelper() { if (hAIKKey_) { NCryptDeleteKey(hAIKKey_, 0); NCryptFreeObject(hAIKKey_); } if (hHardwareKey_) { NCryptDeleteKey(hHardwareKey_, 0); NCryptFreeObject(hHardwareKey_); } if (hProvider_) { NCryptFreeObject(hProvider_); } if (accelEcdhKey_) { EC_KEY_free(accelEcdhKey_); } } private: std::wstring getHardwareKeyProperty(std::wstring name, std::string type) { SECURITY_STATUS status; DWORD cbResult = 0; status = NCryptGetProperty(hHardwareKey_, name.data(), NULL, cbResult, &cbResult, 0); if (FAILED(status)) { return L"N/A"; } std::vector w; w.resize(cbResult); status = NCryptGetProperty(hHardwareKey_, name.data(), (PBYTE) w.data(), cbResult, &cbResult, 0); if (FAILED(status)) { throw std::runtime_error("Failed to get property value"); } if (type == "string") { return (WCHAR *) w.data(); } if (type == "bool") { return w[0] == 0 ? L"false" : L"true"; } if (type == "int") { return std::to_wstring(*(DWORD *) w.data()); } if (type == "binary") { std::wstring result = L"0x"; for (BYTE byte: w) { result += std::format(L"{:02X}", byte); } return result; } } void printHardwareKeyProperties() { std::pair all_properties[] = { {NCRYPT_ALGORITHM_PROPERTY, "string"}, {NCRYPT_LENGTH_PROPERTY, "string"}, {NCRYPT_BLOCK_LENGTH_PROPERTY, "string"}, {NCRYPT_ECC_CURVE_NAME_PROPERTY, "string"}, {NCRYPT_PCP_PLATFORM_TYPE_PROPERTY, "string"}, {NCRYPT_PCP_KEYATTESTATION_PROPERTY, "string"}, {NCRYPT_PCP_EKPUB_PROPERTY, "string"}, {NCRYPT_PCP_EKCERT_PROPERTY, "string"}, {NCRYPT_PCP_EKNVCERT_PROPERTY, "string"}, {NCRYPT_PCP_PCRTABLE_PROPERTY, "string"}, {NCRYPT_PCP_SESSIONID_PROPERTY, "string"}, {NCRYPT_PCP_EXPORT_ALLOWED_PROPERTY, "bool"}, {NCRYPT_PCP_TPM_VERSION_PROPERTY, "string"}, {NCRYPT_PCP_TPM_FW_VERSION_PROPERTY, "string"}, {NCRYPT_PCP_TPM_MANUFACTURER_ID_PROPERTY, "string"}, {NCRYPT_PCP_TPM2BNAME_PROPERTY, "binary"}, {NCRYPT_PCP_PLATFORMHANDLE_PROPERTY, "binary"}, {NCRYPT_PCP_PROVIDERHANDLE_PROPERTY, "binary"}, }; for (const auto &[name, type]: all_properties) { std::wstring value = getHardwareKeyProperty(name, type); std::wcout << L"Property " << name << L": " << value << std::endl; } } void printAIKProperties() { std::wcout << L"\n--- AIK Properties ---" << std::endl; std::pair aik_properties[] = { {NCRYPT_ALGORITHM_PROPERTY, "string"}, {NCRYPT_LENGTH_PROPERTY, "string"}, {NCRYPT_KEY_USAGE_PROPERTY, "int"}, {NCRYPT_PCP_PLATFORM_TYPE_PROPERTY, "string"}, {NCRYPT_PCP_TPM_VERSION_PROPERTY, "string"}, }; for (const auto &[name, type]: aik_properties) { std::wstring value = getAIKProperty(name, type); std::wcout << L"AIK " << name << L": " << value << std::endl; } std::wcout << L"--- End AIK Properties ---\n" << std::endl; } std::wstring getAIKProperty(std::wstring name, std::string type) { SECURITY_STATUS status; DWORD cbResult = 0; status = NCryptGetProperty(hAIKKey_, name.data(), NULL, cbResult, &cbResult, 0); if (FAILED(status)) { return L"N/A"; } std::vector w; w.resize(cbResult); status = NCryptGetProperty(hAIKKey_, name.data(), (PBYTE) w.data(), cbResult, &cbResult, 0); if (FAILED(status)) { return L"Error"; } if (type == "string") { return (WCHAR *) w.data(); } if (type == "bool") { return w[0] == 0 ? L"false" : L"true"; } if (type == "int") { return std::to_wstring(*(DWORD *) w.data()); } if (type == "binary") { std::wstring result = L"0x"; for (BYTE byte: w) { result += std::format(L"{:02X}", byte); } return result; } return L"Unknown"; } std::string getRealPCRValues() { std::cout << "Retrieving PCR values from TPM..." << std::endl; HRESULT hr = S_OK; NCRYPT_PROV_HANDLE hProv = NULL; BYTE pcrTable[TPM_AVAILABLE_PLATFORM_PCRS * MAX_DIGEST_SIZE] = {0}; DWORD cbPcrTable = sizeof(pcrTable); DWORD digestSize = SHA1_DIGEST_SIZE; try { // Open the platform crypto provider hr = HRESULT_FROM_WIN32(NCryptOpenStorageProvider( &hProv, MS_PLATFORM_CRYPTO_PROVIDER, 0)); if (FAILED(hr)) { throw std::runtime_error("Failed to open platform crypto provider: " + std::to_string(hr)); } // Get PCR table from TPM hr = HRESULT_FROM_WIN32(NCryptGetProperty(hProv, NCRYPT_PCP_PCRTABLE_PROPERTY, pcrTable, sizeof(pcrTable), &cbPcrTable, 0)); if (FAILED(hr)) { throw std::runtime_error("Failed to get PCR table: " + std::to_string(hr)); } // Determine digest size based on returned data if ((cbPcrTable / TPM_AVAILABLE_PLATFORM_PCRS) == SHA256_DIGEST_SIZE) { digestSize = SHA256_DIGEST_SIZE; std::cout << "Using SHA256 PCRs (" << digestSize << " bytes per PCR)" << std::endl; } else { std::cout << "Using SHA1 PCRs (" << digestSize << " bytes per PCR)" << std::endl; } std::cout << "Retrieved " << cbPcrTable << " bytes of PCR data for " << TPM_AVAILABLE_PLATFORM_PCRS << " PCRs" << std::endl; // Debug: Print first few PCRs std::cout << "Sample PCR values:" << std::endl; for (UINT32 n = 0; n < std::min(4U, (UINT32)TPM_AVAILABLE_PLATFORM_PCRS); n++) { std::cout << " PCR[" << n << "]: "; for (UINT32 m = 0; m < std::min(8Ul, digestSize); m++) { std::cout << std::format("{:02x}", pcrTable[n * digestSize + m]); } std::cout << "..." << std::endl; } // Convert to base64 std::vector pcrData(pcrTable, pcrTable + cbPcrTable); std::string result = base64Encode(pcrData); // Cleanup if (hProv) { NCryptFreeObject(hProv); } return result; } catch (...) { // Cleanup on error if (hProv) { NCryptFreeObject(hProv); } throw; } } void generateECDSAHardwareKey() { std::wstring hwKeyName = L"HwSignTestECDSA_" + std::to_wstring(std::time(nullptr)); SECURITY_STATUS status = NCryptCreatePersistedKey( hProvider_, &hHardwareKey_, BCRYPT_ECDSA_P256_ALGORITHM, hwKeyName.c_str(), 0, NCRYPT_OVERWRITE_KEY_FLAG ); if (FAILED(status)) { throw std::runtime_error("Failed to create ECDSA hardware key"); } // Finalize the key status = NCryptFinalizeKey(hHardwareKey_, 0); if (FAILED(status)) { NCryptFreeObject(hHardwareKey_); throw std::runtime_error("Failed to finalize ECDSA hardware key"); } printHardwareKeyProperties(); } void generateRSAHardwareKey() { std::wstring hwKeyName = L"HwSignTestRSA_" + std::to_wstring(std::time(nullptr)); SECURITY_STATUS status = NCryptCreatePersistedKey( hProvider_, &hHardwareKey_, BCRYPT_RSA_ALGORITHM, hwKeyName.c_str(), 0, NCRYPT_OVERWRITE_KEY_FLAG ); if (FAILED(status)) { throw std::runtime_error("Failed to create RSA hardware key"); } // Set key length to 2048 bits DWORD keyLength = 2048; status = NCryptSetProperty( hHardwareKey_, NCRYPT_LENGTH_PROPERTY, (PBYTE) &keyLength, sizeof(keyLength), 0 ); if (FAILED(status)) { NCryptFreeObject(hHardwareKey_); throw std::runtime_error("Failed to set RSA key length"); } // Finalize the key status = NCryptFinalizeKey(hHardwareKey_, 0); if (FAILED(status)) { NCryptFreeObject(hHardwareKey_); throw std::runtime_error("Failed to finalize RSA hardware key"); } } public: struct AttestationData { std::string attestation; std::string aikPublicKey; std::string pcrValues; std::string keyType; }; std::string base64Encode(const std::vector &data) { BIO *bio = BIO_new(BIO_s_mem()); BIO *b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); bio = BIO_push(b64, bio); BIO_write(bio, data.data(), data.size()); BIO_flush(bio); BUF_MEM *bufferPtr; BIO_get_mem_ptr(bio, &bufferPtr); std::string result(bufferPtr->data, bufferPtr->length); BIO_free_all(bio); return result; } std::vector base64Decode(const std::string &input) { BIO *bio = BIO_new_mem_buf(input.data(), input.length()); BIO *b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); bio = BIO_push(b64, bio); std::vector result(input.length()); int decodedLength = BIO_read(bio, result.data(), input.length()); BIO_free_all(bio); result.resize(decodedLength); return result; } std::string exportHardwarePublicKey() { DWORD cbResult = 0; SECURITY_STATUS status; // First, export the key status = NCryptExportKey( hHardwareKey_, NULL, BCRYPT_PUBLIC_KEY_BLOB, NULL, NULL, 0, &cbResult, 0 ); if (FAILED(status)) { throw std::runtime_error("Failed to get public key size"); } std::vector keyBlob(cbResult); status = NCryptExportKey( hHardwareKey_, NULL, BCRYPT_PUBLIC_KEY_BLOB, NULL, keyBlob.data(), cbResult, &cbResult, 0 ); if (FAILED(status)) { throw std::runtime_error("Failed to export public key"); } // Convert to standard format based on key type if (hwKeyType_ == HardwareKeyType::ECDSA_P256) { return convertECDSAKeyToPKIX(keyBlob); } else { return convertRSAKeyToPKIX(keyBlob); } } private: std::string convertECDSAKeyToPKIX(std::vector &keyBlob) { // BCrypt ECC public key blob structure BCRYPT_ECCKEY_BLOB *eccBlob = (BCRYPT_ECCKEY_BLOB *) keyBlob.data(); // Extract X and Y coordinates BYTE *x = keyBlob.data() + sizeof(BCRYPT_ECCKEY_BLOB); BYTE *y = x + eccBlob->cbKey; // Create OpenSSL EC_KEY EC_KEY *ecKey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); if (!ecKey) { throw std::runtime_error("Failed to create EC_KEY"); } // Create EC_POINT from coordinates const EC_GROUP *group = EC_KEY_get0_group(ecKey); EC_POINT *point = EC_POINT_new(group); BIGNUM *bn_x = BN_bin2bn(x, eccBlob->cbKey, NULL); BIGNUM *bn_y = BN_bin2bn(y, eccBlob->cbKey, NULL); if (!EC_POINT_set_affine_coordinates_GFp(group, point, bn_x, bn_y, NULL)) { BN_free(bn_x); BN_free(bn_y); EC_POINT_free(point); EC_KEY_free(ecKey); throw std::runtime_error("Failed to set EC point coordinates"); } EC_KEY_set_public_key(ecKey, point); // Convert to EVP_PKEY EVP_PKEY *pkey = EVP_PKEY_new(); EVP_PKEY_set1_EC_KEY(pkey, ecKey); // Export to PKIX format BIO *bio = BIO_new(BIO_s_mem()); i2d_PUBKEY_bio(bio, pkey); BUF_MEM *bufferPtr; BIO_get_mem_ptr(bio, &bufferPtr); std::vector pkixKey(bufferPtr->data, bufferPtr->data + bufferPtr->length); // Cleanup BN_free(bn_x); BN_free(bn_y); EC_POINT_free(point); EC_KEY_free(ecKey); EVP_PKEY_free(pkey); BIO_free(bio); return base64Encode(pkixKey); } std::string convertRSAKeyToPKIX(std::vector &keyBlob) { // BCrypt RSA public key blob structure BCRYPT_RSAKEY_BLOB *rsaBlob = (BCRYPT_RSAKEY_BLOB *) keyBlob.data(); // Extract modulus and exponent BYTE *exponent = keyBlob.data() + sizeof(BCRYPT_RSAKEY_BLOB); BYTE *modulus = exponent + rsaBlob->cbPublicExp; // Create OpenSSL RSA key RSA *rsaKey = RSA_new(); BIGNUM *n = BN_bin2bn(modulus, rsaBlob->cbModulus, NULL); BIGNUM *e = BN_bin2bn(exponent, rsaBlob->cbPublicExp, NULL); if (!RSA_set0_key(rsaKey, n, e, NULL)) { BN_free(n); BN_free(e); RSA_free(rsaKey); throw std::runtime_error("Failed to set RSA key components"); } // Convert to EVP_PKEY EVP_PKEY *pkey = EVP_PKEY_new(); EVP_PKEY_set1_RSA(pkey, rsaKey); // Export to PKIX format BIO *bio = BIO_new(BIO_s_mem()); i2d_PUBKEY_bio(bio, pkey); BUF_MEM *bufferPtr; BIO_get_mem_ptr(bio, &bufferPtr); std::vector pkixKey(bufferPtr->data, bufferPtr->data + bufferPtr->length); // Cleanup RSA_free(rsaKey); EVP_PKEY_free(pkey); BIO_free(bio); return base64Encode(pkixKey); } public: std::string exportAccelPublicKeyPKIX() { // Create EVP_PKEY from EC_KEY EVP_PKEY *pkey = EVP_PKEY_new(); if (!pkey || !EVP_PKEY_set1_EC_KEY(pkey, accelEcdhKey_)) { throw std::runtime_error("Failed to create EVP_PKEY for accel key"); } // Export to PKIX format BIO *bio = BIO_new(BIO_s_mem()); if (!i2d_PUBKEY_bio(bio, pkey)) { BIO_free(bio); EVP_PKEY_free(pkey); throw std::runtime_error("Failed to export acceleration public key"); } BUF_MEM *bufferPtr; BIO_get_mem_ptr(bio, &bufferPtr); std::vector keyData(bufferPtr->data, bufferPtr->data + bufferPtr->length); BIO_free(bio); EVP_PKEY_free(pkey); return base64Encode(keyData); } std::string signDataWithHardwareKey(const std::string &data) { // Hash the data with SHA-256 using OpenSSL unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256(reinterpret_cast(data.c_str()), data.length(), hash); DWORD cbSignature = 0; SECURITY_STATUS status; if (hwKeyType_ == HardwareKeyType::RSA_2048_PSS) { // Sign with RSA-PSS using NCrypt BCRYPT_PSS_PADDING_INFO paddingInfo = {0}; paddingInfo.pszAlgId = BCRYPT_SHA256_ALGORITHM; paddingInfo.cbSalt = 32; status = NCryptSignHash( hHardwareKey_, &paddingInfo, hash, SHA256_DIGEST_LENGTH, NULL, 0, &cbSignature, BCRYPT_PAD_PSS ); if (FAILED(status)) { throw std::runtime_error("Failed to get RSA signature size"); } std::vector signature(cbSignature); status = NCryptSignHash( hHardwareKey_, &paddingInfo, hash, SHA256_DIGEST_LENGTH, signature.data(), cbSignature, &cbSignature, BCRYPT_PAD_PSS ); if (FAILED(status)) { throw std::runtime_error("Failed to sign data with RSA hardware key"); } return base64Encode(signature); } else { // Sign with ECDSA using NCrypt status = NCryptSignHash( hHardwareKey_, NULL, hash, SHA256_DIGEST_LENGTH, NULL, 0, &cbSignature, 0 ); if (FAILED(status)) { throw std::runtime_error("Failed to get ECDSA signature size"); } std::vector signature(cbSignature); status = NCryptSignHash( hHardwareKey_, NULL, hash, SHA256_DIGEST_LENGTH, signature.data(), cbSignature, &cbSignature, 0 ); if (FAILED(status)) { throw std::runtime_error("Failed to sign data with ECDSA hardware key"); } return base64Encode(signature); } } std::string signDataWithAccelKey(const std::string &data) { if (sharedSecret_.empty()) { // No shared secret yet, use ECDSA signing with OpenSSL unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256(reinterpret_cast(data.c_str()), data.length(), hash); unsigned char signature[256]; unsigned int sigLen; if (!ECDSA_sign(0, hash, SHA256_DIGEST_LENGTH, signature, &sigLen, accelEcdhKey_)) { throw std::runtime_error("Failed to sign data with acceleration key"); } std::vector sigVec(signature, signature + sigLen); return base64Encode(sigVec); } else { // Use HMAC-SHA256 with shared secret return computeHMAC(data, sharedSecret_); } } std::string computeHMAC(const std::string &data, const std::vector &key) { unsigned char result[EVP_MAX_MD_SIZE]; unsigned int result_len; HMAC(EVP_sha256(), key.data(), key.size(), reinterpret_cast(data.c_str()), data.length(), result, &result_len); std::vector hmacVec(result, result + result_len); return base64Encode(hmacVec); } void setSharedSecret(const std::string &serverPubKeyBase64) { try { std::cout << "Setting up ECDH shared secret..." << std::endl; // Decode server's public key from base64 std::vector serverPubKeyBytes = base64Decode(serverPubKeyBase64); std::cout << "Decoded server public key, length: " << serverPubKeyBytes.size() << " bytes" << std::endl; // Create BIO from server's public key bytes BIO *bio = BIO_new_mem_buf(serverPubKeyBytes.data(), static_cast(serverPubKeyBytes.size())); if (!bio) { throw std::runtime_error("Failed to create BIO from server public key"); } // Try to parse as PKIX format first EVP_PKEY *serverPubKey = d2i_PUBKEY_bio(bio, nullptr); BIO_free(bio); if (!serverPubKey) { // If PKIX parsing failed, try raw uncompressed point format if (serverPubKeyBytes.size() == 65 && serverPubKeyBytes[0] == 0x04) { std::cout << "Trying raw uncompressed point format..." << std::endl; serverPubKey = createEVPKeyFromRawPoint(serverPubKeyBytes); } else { throw std::runtime_error("Failed to parse server public key in any known format"); } } if (!serverPubKey) { throw std::runtime_error("Failed to create server EVP_PKEY"); } // Convert our ECDH key to EVP_PKEY format EVP_PKEY *clientPrivKey = EVP_PKEY_new(); if (!clientPrivKey || !EVP_PKEY_set1_EC_KEY(clientPrivKey, accelEcdhKey_)) { EVP_PKEY_free(serverPubKey); if (clientPrivKey) EVP_PKEY_free(clientPrivKey); throw std::runtime_error("Failed to convert client ECDH key to EVP_PKEY"); } // Perform ECDH key derivation sharedSecret_ = performECDHKeyDerivation(clientPrivKey, serverPubKey); // Cleanup EVP_PKEY_free(serverPubKey); EVP_PKEY_free(clientPrivKey); std::cout << "✓ ECDH shared secret established successfully, length: " << sharedSecret_.size() << " bytes" << std::endl; } catch (const std::exception &e) { sharedSecret_.clear(); throw std::runtime_error(std::string("ECDH key exchange failed: ") + e.what()); } } std::string getHardwareKeyType() const { return keyTypeString_; } std::string getAccelKeyType() const { return "ecdh-p256"; } AttestationData getHardwareKeyAttestation(const std::string &nonce = "") { std::cout << "\n=== Generating Hardware Key Attestation using TpmAtt ===" << std::endl; AttestationData result; SECURITY_STATUS status; HRESULT hr; try { // Step 1: Create an AIK (Attestation Identity Key) aikName_ = L"HwSignAIK_" + std::to_wstring(std::time(nullptr)); status = NCryptCreatePersistedKey( hProvider_, &hAIKKey_, BCRYPT_RSA_ALGORITHM, aikName_.c_str(), 0, NCRYPT_OVERWRITE_KEY_FLAG ); if (FAILED(status)) { throw std::runtime_error("Failed to create AIK: " + std::to_string(status)); } // Mark as AIK for signing and attestation DWORD keyUsage = NCRYPT_ALLOW_SIGNING_FLAG; status = NCryptSetProperty( hAIKKey_, NCRYPT_KEY_USAGE_PROPERTY, (PBYTE) &keyUsage, sizeof(keyUsage), 0 ); if (FAILED(status)) { NCryptDeleteKey(hAIKKey_, 0); throw std::runtime_error("Failed to set AIK usage: " + std::to_string(status)); } DWORD dwKeyUsage = NCRYPT_PCP_IDENTITY_KEY; if (FAILED(hr = HRESULT_FROM_WIN32(NCryptSetProperty( hAIKKey_, NCRYPT_PCP_KEY_USAGE_POLICY_PROPERTY, (PBYTE)&dwKeyUsage, sizeof(dwKeyUsage), 0)))) { throw std::runtime_error("Failed to set AIK usage: " + std::to_string(status)); } // Finalize the AIK status = NCryptFinalizeKey(hAIKKey_, 0); if (FAILED(status)) { NCryptDeleteKey(hAIKKey_, 0); throw std::runtime_error("Failed to finalize AIK: " + std::to_string(status)); } std::cout << "✓ Created RSA-2048 AIK: " << std::string(aikName_.begin(), aikName_.end()) << std::endl; // Print AIK properties for debugging try { printAIKProperties(); } catch (...) { std::cout << "Warning: Could not print AIK properties" << std::endl; } // Step 2: Export AIK public key DWORD cbAIKPub = 0; status = NCryptExportKey( hAIKKey_, NULL, BCRYPT_PUBLIC_KEY_BLOB, NULL, NULL, 0, &cbAIKPub, 0 ); if (FAILED(status)) { throw std::runtime_error("Failed to get AIK public key size"); } std::vector aikPubBlob(cbAIKPub); status = NCryptExportKey( hAIKKey_, NULL, BCRYPT_PUBLIC_KEY_BLOB, NULL, aikPubBlob.data(), cbAIKPub, &cbAIKPub, 0 ); if (FAILED(status)) { throw std::runtime_error("Failed to export AIK public key"); } // Convert to PKIX format result.aikPublicKey = convertRSAKeyToPKIX(aikPubBlob); std::cout << "✓ Exported AIK public key" << std::endl; // Step 3: Use TpmAttGenerateKeyAttestation to generate real TPM attestation std::vector nonceBytes; PBYTE pbNonce = nullptr; UINT32 cbNonce = 0; if (!nonce.empty()) { nonceBytes = base64Decode(nonce); pbNonce = nonceBytes.data(); cbNonce = static_cast(nonceBytes.size()); std::cout << "Using nonce of size: " << cbNonce << " bytes" << std::endl; } // Verify both keys are on the same provider std::cout << "Verifying keys are on the same provider..." << std::endl; std::cout << "Hardware key handle: " << std::hex << hHardwareKey_ << std::dec << std::endl; std::cout << "AIK handle: " << std::hex << hAIKKey_ << std::dec << std::endl; std::cout << "Provider handle: " << std::hex << hProvider_ << std::dec << std::endl; // First call to get required buffer size UINT32 cbAttestation = 0; std::cout << "Calling TpmAttGenerateKeyAttestation to get buffer size..." << std::endl; hr = TpmAttGenerateKeyAttestation( hAIKKey_, // AIK handle hHardwareKey_, // Hardware key to attest pbNonce, // Optional nonce cbNonce, // Nonce size nullptr, // Output buffer (null to get size) 0, // Buffer size &cbAttestation // Required size ); std::cout << "TpmAttGenerateKeyAttestation result: 0x" << std::hex << hr << std::dec << std::endl; if (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) && FAILED(hr)) { std::string errorMsg = "Failed to get attestation buffer size: 0x" + std::to_string(hr) + " (" + std::to_string(hr) + ")"; // Check for specific TPM error codes if ((hr & 0x0000FFFF) == 0x0000001D) { // TPM_RC_SCHEME errorMsg += " - TPM_RC_SCHEME: The signing scheme is not supported by the TPM"; } else if ((hr & 0x0000FFFF) == 0x00000143) { // TPM_RC_KEY errorMsg += " - TPM_RC_KEY: Key handle references a key that is not suitable for the operation"; } else if ((hr & 0x0000FFFF) == 0x00000184) { // TPM_RC_ATTRIBUTES errorMsg += " - TPM_RC_ATTRIBUTES: Key attributes are not compatible with the operation"; } throw std::runtime_error(errorMsg); } std::cout << "Required attestation buffer size: " << cbAttestation << " bytes" << std::endl; // Second call to generate actual attestation std::vector attestationBlob(cbAttestation); hr = TpmAttGenerateKeyAttestation( hAIKKey_, // AIK handle hHardwareKey_, // Hardware key to attest pbNonce, // Optional nonce cbNonce, // Nonce size attestationBlob.data(), // Output buffer cbAttestation, // Buffer size &cbAttestation // Actual size used ); if (FAILED(hr)) { throw std::runtime_error("Failed to generate key attestation: " + std::to_string(hr)); } // Resize to actual used size attestationBlob.resize(cbAttestation); result.attestation = base64Encode(attestationBlob); result.keyType = keyTypeString_; // Get real PCR values from TPM try { result.pcrValues = getRealPCRValues(); std::cout << "✓ Retrieved real PCR values from TPM" << std::endl; } catch (const std::exception &e) { std::cout << "Warning: Could not get real PCR values: " << e.what() << std::endl; // Fallback to empty PCR values if platform access fails result.pcrValues = ""; } std::cout << "✓ Generated TPM key attestation using TmpAtt library" << std::endl; std::cout << " Attestation size: " << cbAttestation << " bytes" << std::endl; std::cout << " Key type: " << result.keyType << std::endl; return result; } catch (const std::exception &e) { if (hAIKKey_) { NCryptDeleteKey(hAIKKey_, 0); NCryptFreeObject(hAIKKey_); hAIKKey_ = NULL; } throw; } } private: EVP_PKEY *createEVPKeyFromRawPoint(const std::vector &rawPoint) { if (rawPoint.size() != 65 || rawPoint[0] != 0x04) { return nullptr; } // Create EC_KEY for P-256 curve EC_KEY *ecKey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); if (!ecKey) { return nullptr; } // Create point from raw coordinates const EC_GROUP *group = EC_KEY_get0_group(ecKey); EC_POINT *point = EC_POINT_new(group); if (!point) { EC_KEY_free(ecKey); return nullptr; } // Set point from uncompressed format if (!EC_POINT_oct2point(group, point, rawPoint.data(), rawPoint.size(), nullptr)) { EC_POINT_free(point); EC_KEY_free(ecKey); return nullptr; } // Set the public key point if (!EC_KEY_set_public_key(ecKey, point)) { EC_POINT_free(point); EC_KEY_free(ecKey); return nullptr; } // Convert to EVP_PKEY EVP_PKEY *pkey = EVP_PKEY_new(); if (!pkey || !EVP_PKEY_set1_EC_KEY(pkey, ecKey)) { if (pkey) EVP_PKEY_free(pkey); EC_POINT_free(point); EC_KEY_free(ecKey); return nullptr; } // Cleanup intermediate objects EC_POINT_free(point); EC_KEY_free(ecKey); return pkey; } std::vector performECDHKeyDerivation(EVP_PKEY *clientPrivKey, EVP_PKEY *serverPubKey) { // Create derivation context EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(clientPrivKey, nullptr); if (!ctx) { throw std::runtime_error("Failed to create ECDH context"); } // Initialize derivation if (EVP_PKEY_derive_init(ctx) <= 0) { EVP_PKEY_CTX_free(ctx); throw std::runtime_error("Failed to initialize ECDH derivation"); } // Set peer key if (EVP_PKEY_derive_set_peer(ctx, serverPubKey) <= 0) { EVP_PKEY_CTX_free(ctx); throw std::runtime_error("Failed to set ECDH peer key"); } // Get shared secret length size_t secretLen = 0; if (EVP_PKEY_derive(ctx, nullptr, &secretLen) <= 0) { EVP_PKEY_CTX_free(ctx); throw std::runtime_error("Failed to get ECDH secret length"); } // Derive shared secret std::vector secret(secretLen); if (EVP_PKEY_derive(ctx, secret.data(), &secretLen) <= 0) { EVP_PKEY_CTX_free(ctx); throw std::runtime_error("Failed to derive ECDH shared secret"); } // Cleanup and resize to actual length EVP_PKEY_CTX_free(ctx); secret.resize(secretLen); std::cout << "ECDH derivation completed, secret length: " << secretLen << " bytes" << std::endl; return secret; } }; class TestClient { private: std::string baseUrl_ = "http://localhost:28280"; std::string authToken_; std::string accelKeyId_; std::unique_ptr crypto_; HardwareKeyType keyType_; public: TestClient(HardwareKeyType keyType = HardwareKeyType::ECDSA_P256) : keyType_(keyType) { crypto_ = std::make_unique(keyType); std::cout << "Initialized test client with base URL: " << baseUrl_ << std::endl; } bool testRegister(const std::string &username, const std::string &password) { std::cout << "\n=== Testing Registration ===" << std::endl; std::cout << "Username: " << username << std::endl; std::cout << "Password: " << password << std::endl; try { nlohmann::json payload = { {"username", username}, {"password", password} }; auto response = cpr::Post( cpr::Url{baseUrl_ + "/register"}, cpr::Body{payload.dump()}, cpr::Header{{"Content-Type", "application/json"}} ); std::cout << "Response status: " << response.status_code << std::endl; std::cout << "Response body: " << response.text << std::endl; if (response.status_code == 201) { std::cout << "✓ Registration successful!" << std::endl; return true; } else { std::cout << "✗ Registration failed!" << std::endl; return false; } } catch (const std::exception &e) { std::cout << "✗ Registration error: " << e.what() << std::endl; return false; } } bool testLogin(const std::string &username, const std::string &password) { std::cout << "\n=== Testing Login ===" << std::endl; std::cout << "Username: " << username << std::endl; std::cout << "Password: " << password << std::endl; try { nlohmann::json payload = { {"username", username}, {"password", password} }; std::string hwPubKey = crypto_->exportHardwarePublicKey(); std::string hwPubType = crypto_->getHardwareKeyType(); std::cout << "Hardware public key (first 50 chars): " << hwPubKey.substr(0, 50) << "..." << std::endl; std::cout << "Hardware key type: " << hwPubType << std::endl; auto response = cpr::Post( cpr::Url{baseUrl_ + "/login"}, cpr::Body{payload.dump()}, cpr::Header{ {"Content-Type", "application/json"}, {"x-rpc-sec-bound-token-hw-pub", hwPubKey}, {"x-rpc-sec-bound-token-hw-pub-type", hwPubType} } ); std::cout << "Response status: " << response.status_code << std::endl; std::cout << "Response body: " << response.text << std::endl; if (response.status_code == 200) { auto respJson = nlohmann::json::parse(response.text); if (respJson.contains("token")) { authToken_ = respJson["token"]; std::cout << "✓ Login successful! Token: " << authToken_.substr(0, 20) << "..." << std::endl; return true; } } std::cout << "✗ Login failed!" << std::endl; return false; } catch (const std::exception &e) { std::cout << "✗ Login error: " << e.what() << std::endl; return false; } } bool testAuthenticated(bool generateAttestation = false) { std::cout << "\n=== Testing Authenticated Request" << (generateAttestation ? " with Attestation" : "") << " ===" << std::endl; if (authToken_.empty()) { std::cout << "✗ No auth token available!" << std::endl; return false; } try { // Generate timestamp and random hex using OpenSSL std::string timestamp = std::to_string(std::time(nullptr)); // Generate 32 bytes of random data using OpenSSL unsigned char randomBytes[32]; if (!RAND_bytes(randomBytes, 32)) { throw std::runtime_error("Failed to generate random bytes"); } // Convert to hex string std::stringstream hexStream; hexStream << std::hex << std::setfill('0'); for (int i = 0; i < 32; i++) { hexStream << std::setw(2) << static_cast(randomBytes[i]); } std::string randomHex = hexStream.str(); // Format request data std::string requestData = timestamp + "-" + randomHex; std::cout << "Request data: " << timestamp << "-" << randomHex.substr(0, 16) << "..." << std::endl; cpr::Header requestHeaders; requestHeaders["Authorization"] = "Bearer " + authToken_; requestHeaders["x-rpc-sec-bound-token-data"] = requestData; if (accelKeyId_.empty()) { // First authenticated request - register ECDH acceleration key std::cout << "Registering new ECDH acceleration key..." << std::endl; std::string accelPub = crypto_->exportAccelPublicKeyPKIX(); std::string accelPubType = crypto_->getAccelKeyType(); std::string accelPubSig = crypto_->signDataWithHardwareKey(accelPub); std::string dataSig = crypto_->signDataWithAccelKey(requestData); requestHeaders["x-rpc-sec-bound-token-accel-pub"] = accelPub; requestHeaders["x-rpc-sec-bound-token-accel-pub-type"] = accelPubType; requestHeaders["x-rpc-sec-bound-token-accel-pub-sig"] = accelPubSig; requestHeaders["x-rpc-sec-bound-token-data-sig"] = dataSig; std::cout << "Acceleration public key (first 50 chars): " << accelPub.substr(0, 50) << "..." << std::endl; std::cout << "Acceleration key type: " << accelPubType << std::endl; std::cout << "Accel pub signature (first 20 chars): " << accelPubSig.substr(0, 20) << "..." << std::endl; std::cout << "Data signature (first 20 chars): " << dataSig.substr(0, 20) << "..." << std::endl; } else { // Subsequent requests - use HMAC with shared secret std::cout << "Using existing acceleration key ID with HMAC: " << accelKeyId_ << std::endl; std::string dataSig = crypto_->signDataWithAccelKey(requestData); requestHeaders["x-rpc-sec-bound-token-accel-pub-id"] = accelKeyId_; requestHeaders["x-rpc-sec-bound-token-data-sig"] = dataSig; std::cout << "HMAC signature (first 20 chars): " << dataSig.substr(0, 20) << "..." << std::endl; } auto response = cpr::Get( cpr::Url{baseUrl_ + "/authenticated"}, requestHeaders ); std::cout << "Response status: " << response.status_code << std::endl; std::cout << "Response body: " << response.text << std::endl; // Check for acceleration key ID in response headers auto it = response.header.find("x-rpc-sec-bound-token-accel-pub-id"); if (it != response.header.end()) { accelKeyId_ = it->second; std::cout << "Received acceleration key ID: " << accelKeyId_ << std::endl; } // Check for server's ECDH public key in response headers auto serverPubIt = response.header.find("x-rpc-sec-bound-token-accel-pub"); if (serverPubIt != response.header.end()) { std::cout << "Received server ECDH public key (first 30 chars): " << serverPubIt->second.substr(0, 30) << "..." << std::endl; // Establish shared secret for future HMAC operations crypto_->setSharedSecret(serverPubIt->second); std::cout << "Shared secret established for HMAC authentication" << std::endl; } if (response.status_code == 200) { std::cout << "✓ Authenticated request successful!" << std::endl; return true; } else { std::cout << "✗ Authenticated request failed!" << std::endl; return false; } } catch (const std::exception &e) { std::cout << "✗ Authenticated request error: " << e.what() << std::endl; return false; } } bool testAttestation() { std::cout << "\n=== Testing Hardware Key Attestation ===" << std::endl; if (authToken_.empty()) { std::cout << "✗ No auth token available!" << std::endl; return false; } try { // First, get a challenge nonce from the server auto challengeResponse = cpr::Get( cpr::Url{baseUrl_ + "/challenge"}, cpr::Header{{"Authorization", "Bearer " + authToken_}} ); if (challengeResponse.status_code != 200) { std::cout << "✗ Failed to get challenge: " << challengeResponse.status_code << std::endl; return false; } auto challengeJson = nlohmann::json::parse(challengeResponse.text); std::string nonce = challengeJson["nonce"]; std::cout << "Received challenge nonce: " << nonce.substr(0, 20) << "..." << std::endl; // Generate attestation for the hardware key auto attestationData = crypto_->getHardwareKeyAttestation(nonce); // Send attestation to server nlohmann::json attestPayload = { {"attestation", attestationData.attestation}, {"aik_public_key", attestationData.aikPublicKey}, {"pcr_values", attestationData.pcrValues}, {"key_type", attestationData.keyType}, {"nonce", nonce} }; auto attestResponse = cpr::Post( cpr::Url{baseUrl_ + "/attest"}, cpr::Body{attestPayload.dump()}, cpr::Header{ {"Content-Type", "application/json"}, {"Authorization", "Bearer " + authToken_} } ); std::cout << "Attestation response status: " << attestResponse.status_code << std::endl; std::cout << "Attestation response body: " << attestResponse.text << std::endl; if (attestResponse.status_code == 200) { std::cout << "✓ Hardware key attestation successful!" << std::endl; return true; } else { std::cout << "✗ Hardware key attestation failed!" << std::endl; return false; } } catch (const std::exception &e) { std::cout << "✗ Attestation error: " << e.what() << std::endl; return false; } } void runFullTest() { std::string keyTypeStr = (keyType_ == HardwareKeyType::RSA_2048_PSS) ? "RSA-2048-PSS" : "ECDSA-P256"; std::cout << "==========================================" << std::endl; std::cout << "Hardware-Bound Authentication Test" << std::endl; std::cout << keyTypeStr << " Hardware Key + ECDH-P256 Accel Key" << std::endl; std::cout << "Using NCrypt (Hardware) + OpenSSL (ECDH)" << std::endl; std::cout << "==========================================" << std::endl; std::string username = "testuser_" + keyTypeStr + "_" + std::to_string(std::time(nullptr)); std::string password = "testpass123"; // Test 1: Register bool registerSuccess = testRegister(username, password); // Test 2: Login with hardware key bool loginSuccess = false; if (registerSuccess) { loginSuccess = testLogin(username, password); } // Test 3: Authenticated request (first time - register ECDH accel key) bool authSuccess1 = false; if (loginSuccess) { authSuccess1 = testAuthenticated(); } // Test 4: Authenticated request (second time - use existing ECDH key) bool authSuccess2 = false; if (authSuccess1) { std::cout << "\n=== Testing Second Authenticated Request ===" << std::endl; authSuccess2 = testAuthenticated(); } // Test 5: Third authenticated request to verify ECDH key persistence bool authSuccess3 = false; if (authSuccess2) { std::cout << "\n=== Testing Third Authenticated Request ===" << std::endl; authSuccess3 = testAuthenticated(); } // Test 6: Hardware key attestation bool attestSuccess = false; if (authSuccess3) { attestSuccess = testAttestation(); } // Summary std::cout << "\n==========================================" << std::endl; std::cout << "Test Results Summary:" << std::endl; std::cout << "==========================================" << std::endl; std::cout << "Registration: " << (registerSuccess ? "✓ PASS" : "✗ FAIL") << std::endl; std::cout << "Login (" << keyTypeStr << " HW): " << (loginSuccess ? "✓ PASS" : "✗ FAIL") << std::endl; std::cout << "Auth (new ECDH key): " << (authSuccess1 ? "✓ PASS" : "✗ FAIL") << std::endl; std::cout << "Auth (existing ECDH): " << (authSuccess2 ? "✓ PASS" : "✗ FAIL") << std::endl; std::cout << "Auth (ECDH persistent): " << (authSuccess3 ? "✓ PASS" : "✗ FAIL") << std::endl; std::cout << "HW Key Attestation: " << (attestSuccess ? "✓ PASS" : "✗ FAIL") << std::endl; bool allPassed = registerSuccess && loginSuccess && authSuccess1 && authSuccess2 && authSuccess3 && attestSuccess; std::cout << "\nOverall Result: " << (allPassed ? "✓ ALL TESTS PASSED" : "✗ SOME TESTS FAILED") << std::endl; if (allPassed) { std::cout << "\n🎉 Congratulations! All hardware-bound authentication tests passed!" << std::endl; std::cout << "✓ " << keyTypeStr << " hardware key authentication works (NCrypt)" << std::endl; std::cout << "✓ ECDH-P256 acceleration key exchange works (OpenSSL)" << std::endl; std::cout << "✓ Key persistence and reuse works" << std::endl; std::cout << "✓ Hardware key attestation with AIK works" << std::endl; std::cout << "✓ Hybrid NCrypt/OpenSSL integration successful" << std::endl; } std::cout << "==========================================" << std::endl; } }; int main(int argc, char *argv[]) { try { std::cout << "Starting hardware-bound authentication test..." << std::endl; // Default to testing both key types bool testECDSA = true; bool testRSA = true; // Parse command line arguments if (argc > 1) { std::string arg = argv[1]; if (arg == "ecdsa") { testRSA = false; } else if (arg == "rsa") { testECDSA = false; } else if (arg == "both") { // Test both (default) } else { std::cout << "Usage: " << argv[0] << " [ecdsa|rsa|both]" << std::endl; return 1; } } // Run tests based on selection if (testECDSA) { std::cout << "\nTesting with ECDSA-P256 hardware key..." << std::endl; TestClient clientECDSA(HardwareKeyType::ECDSA_P256); clientECDSA.runFullTest(); } if (testRSA) { if (testECDSA) { std::cout << "\n\n"; } std::cout << "Testing with RSA-2048-PSS hardware key..." << std::endl; TestClient clientRSA(HardwareKeyType::RSA_2048_PSS); clientRSA.runFullTest(); } std::cout << "\nAll tests completed." << std::endl; return 0; } catch (const std::exception &e) { std::cerr << "Fatal error: " << e.what() << std::endl; return 1; } }