From a high level, I'm trying to implement a ECDH key agreement scheme that results in the final ephemeral key being stored on the TPM. It seems that the bcrypt
/ncrypt
interfaces have almost everything that I need. I can perform the ECDH secret agreement and then derive a key from the shared secret. As far as I can tell, by using the MS_PLATFORM_CRYPTO_PROVIDER
key storage provider, the secret agreement is done inside the TPM. However, all of the key derivation functions that I can find will only output the newly derived key into a plaintext buffer in RAM. This seems to defeat the purpose of performing the ECDH on the TPM (the ECHD itself is using ephemeral keys).
Here's a snippet of the exchange that works, albeit with the output of NCryptDeriveKey
getting stored in RAM. Essentially, what I'm looking for is a way to store the output NCryptDeriveKey
in the TPM so I can use it for encryption later without leaking it out. I assumed this was a normal use case, but I can't seem to find a way to do it. This example uses NCryptDeriveKey
, but based on this, I'll eventually want to use BCryptxxx
for everything since I don't need it to be persistent.
//NOTE: Some error checking lines have been removed for clarity
class EcdhPartner
{
public:
EcdhPartner(NCRYPT_PROV_HANDLE hProvider, string name) :
m_hProvider(hProvider),
m_keyname("ECDHTestKey1" + name),
m_name(name)
{}
cout << "New EcdhPartner '" + m_name + "' keyname=" + m_keyname + "\n";
}
void GenerateKeypair()
{
SECURITY_STATUS secStatus;
secStatus = NCryptCreatePersistedKey(m_hProvider, &m_hKey, BCRYPT_ECDH_P256_ALGORITHM, s2lpcwstr(m_keyname), 0, NCRYPT_OVERWRITE_KEY_FLAG);
secStatus = NCryptFinalizeKey(m_hKey, 0);
// export the public key
DWORD publicBlobLength;
// get the length first
secStatus = NCryptExportKey(m_hKey, NULL, BCRYPT_ECCPUBLIC_BLOB, NULL, NULL, NULL, &publicBlobLength, 0);
// now get the blob
secStatus = NCryptExportKey(m_hKey, NULL, BCRYPT_ECCPUBLIC_BLOB, NULL, m_publicKeyBlob, 72, &publicBlobLength, 0);
}
void DoKeyAgreement(EcdhPartner& partner)
{
SECURITY_STATUS secStatus;
NCRYPT_KEY_HANDLE hPartnerPublicKey;
// open partner public key
secStatus = NCryptImportKey(m_hProvider, NULL, BCRYPT_ECCPUBLIC_BLOB, NULL, &hPartnerPublicKey, partner.m_publicKeyBlob, 72, 0);
CheckSecStatus(secStatus, m_name, "NCryptImportKey");
secStatus = NCryptSecretAgreement(m_hKey, hPartnerPublicKey, &m_hSharedSecret, 0);
// Build KDF parameter list
const DWORD BufferLength = 3;
BCryptBuffer BufferArray[BufferLength] = { 0 };
//specify hash algorithm
BufferArray[0].BufferType = KDF_HASH_ALGORITHM;
BufferArray[0].cbBuffer = (DWORD)((wcslen(BCRYPT_SHA256_ALGORITHM) + 1) * sizeof(WCHAR));
BufferArray[0].pvBuffer = (PVOID)BCRYPT_SHA256_ALGORITHM;
NCryptBufferDesc parameterList = { 0 };
parameterList.cBuffers = 1;
parameterList.pBuffers = BufferArray;
parameterList.ulVersion = BCRYPTBUFFER_VERSION;
DWORD derivedKeyLength;
// I NEED THE OUTPUT OF THIS TO REMAIN ON THE TPM!!!
secStatus = NCryptDeriveKey(m_hSharedSecret, BCRYPT_KDF_HMAC, ¶meterList, m_derivedKey, sizeof(m_derivedKey), &derivedKeyLength, KDF_USE_SECRET_AS_HMAC_KEY_FLAG);
}
const string m_keyname;
const string m_name;
NCRYPT_PROV_HANDLE m_hProvider;
NCRYPT_KEY_HANDLE m_hKey;
BYTE m_publicKeyBlob[72]; //64 + 8-byte header
NCRYPT_SECRET_HANDLE m_hSharedSecret;
BYTE m_derivedKey[32];
};
int main()
{
LPCWSTR providerName = MS_PLATFORM_CRYPTO_PROVIDER;
NCRYPT_PROV_HANDLE hProvider;
SECURITY_STATUS secStatus;
secStatus = NCryptOpenStorageProvider(&hProvider, providerName, 0);
EcdhPartner alice(hProvider, "Alice");
EcdhPartner bob(hProvider, "Bob");
alice.GenerateKeypair();
bob.GenerateKeypair();
alice.DoKeyAgreement(bob);
bob.DoKeyAgreement(alice);
}