Files
hw-sign/hw-sign-win/main.cpp

1427 lines
51 KiB
C++

#define NOMINMAX
#include <iostream>
#include <string>
#include <vector>
#include <cpr/cpr.h>
#include <nlohmann/json.hpp>
#include <windows.h>
#include <ncrypt.h>
#include <bcrypt.h>
#include <wincrypt.h>
#include <openssl/ec.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <openssl/rand.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/x509.h>
#include <openssl/hmac.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <ctime>
#include <memory>
#include <sstream>
#include <iomanip>
#include <format>
#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<uint8_t> 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<BYTE> 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<std::wstring, std::string> 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<std::wstring, std::string> 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<BYTE> 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<uint8_t> 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<uint8_t> &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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> &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<uint8_t> 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<uint8_t> &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<uint8_t> 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<uint8_t> 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<const unsigned char *>(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<uint8_t> 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<uint8_t> 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<const unsigned char *>(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<uint8_t> 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<uint8_t> &key) {
unsigned char result[EVP_MAX_MD_SIZE];
unsigned int result_len;
HMAC(EVP_sha256(), key.data(), key.size(),
reinterpret_cast<const unsigned char *>(data.c_str()), data.length(),
result, &result_len);
std::vector<uint8_t> 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<uint8_t> 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<int>(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<uint8_t> 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<uint8_t> nonceBytes;
PBYTE pbNonce = nullptr;
UINT32 cbNonce = 0;
if (!nonce.empty()) {
nonceBytes = base64Decode(nonce);
pbNonce = nonceBytes.data();
cbNonce = static_cast<UINT32>(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<uint8_t> 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<uint8_t> &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<uint8_t> 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<uint8_t> 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<UnifiedCryptoHelper> crypto_;
HardwareKeyType keyType_;
public:
TestClient(HardwareKeyType keyType = HardwareKeyType::ECDSA_P256) : keyType_(keyType) {
crypto_ = std::make_unique<UnifiedCryptoHelper>(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<int>(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;
}
}