// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. #include #include #include #include "umock_c/umock_c_prod.h" #include "azure_c_shared_utility/gballoc.h" #include "azure_c_shared_utility/sastoken.h" #include "azure_c_shared_utility/azure_base64.h" #include "azure_c_shared_utility/sha.h" #include "azure_c_shared_utility/urlencode.h" #include "azure_c_shared_utility/strings.h" #include "azure_c_shared_utility/xlogging.h" #include "azure_c_shared_utility/crt_abstractions.h" #include "hsm_client_tpm.h" #include "hsm_client_data.h" #include "azure_utpm_c/tpm_comm.h" #include "azure_utpm_c/tpm_codec.h" #include "azure_utpm_c/Marshal_fp.h" // for activation blob unmarshaling #define EPOCH_TIME_T_VALUE 0 #define HMAC_LENGTH 32 #define TPM_DATA_LENGTH 1024 static TPM2B_AUTH NullAuth = { 0 }; static TSS_SESSION NullPwSession; static const UINT32 TPM_20_SRK_HANDLE = HR_PERSISTENT | 0x00000001; static const UINT32 TPM_20_EK_HANDLE = HR_PERSISTENT | 0x00010001; static const UINT32 DPS_ID_KEY_HANDLE = HR_PERSISTENT | 0x00000100; typedef struct HSM_CLIENT_INFO_TAG { TSS_DEVICE tpm_device; TPM2B_PUBLIC ek_pub; TPM2B_PUBLIC srk_pub; TPM2B_PUBLIC id_key_public; TPM2B_PRIVATE id_key_dup_blob; TPM2B_PRIVATE id_key_priv; } HSM_CLIENT_INFO; static const HSM_CLIENT_TPM_INTERFACE tpm_interface = { hsm_client_tpm_create, hsm_client_tpm_destroy, hsm_client_tpm_import_key, hsm_client_tpm_get_endorsement_key, hsm_client_tpm_get_storage_key, hsm_client_tpm_sign_data }; static TPMS_RSA_PARMS RsaStorageParams = { { TPM_ALG_AES, 128, TPM_ALG_CFB }, // TPMT_SYM_DEF_OBJECT symmetric { TPM_ALG_NULL }, // TPMT_RSA_SCHEME scheme 2048, // TPMI_RSA_KEY_BITS keyBits 0 // UINT32 exponent }; static TPM2B_PUBLIC* GetEkTemplate (void) { static TPM2B_PUBLIC EkTemplate = { 0, // size will be computed during marshaling { TPM_ALG_RSA, // TPMI_ALG_PUBLIC type TPM_ALG_SHA256, // TPMI_ALG_HASH nameAlg { 0 }, // TPMA_OBJECT objectAttributes (set below) {32, { 0x83, 0x71, 0x97, 0x67, 0x44, 0x84, 0xb3, 0xf8, 0x1a, 0x90, 0xcc, 0x8d, 0x46, 0xa5, 0xd7, 0x24, 0xfd, 0x52, 0xd7, 0x6e, 0x06, 0x52, 0x0b, 0x64, 0xf2, 0xa1, 0xda, 0x1b, 0x33, 0x14, 0x69, 0xaa } }, // TPM2B_DIGEST authPolicy { 0 }, // TPMU_PUBLIC_PARMS parameters (set below) { 0 } // TPMU_PUBLIC_ID unique } }; EkTemplate.publicArea.objectAttributes = ToTpmaObject( Restricted | Decrypt | FixedTPM | FixedParent | AdminWithPolicy | SensitiveDataOrigin); EkTemplate.publicArea.parameters.rsaDetail = RsaStorageParams; return &EkTemplate; } static TPM2B_PUBLIC* GetSrkTemplate(void) { static TPM2B_PUBLIC SrkTemplate = { 0, // size will be computed during marshaling { TPM_ALG_RSA, // TPMI_ALG_PUBLIC type TPM_ALG_SHA256, // TPMI_ALG_HASH nameAlg { 0 }, // TPMA_OBJECT objectAttributes (set below) { 0 }, // TPM2B_DIGEST authPolicy { 0 }, // TPMU_PUBLIC_PARMS parameters (set before use) { 0 } // TPMU_PUBLIC_ID unique } }; SrkTemplate.publicArea.objectAttributes = ToTpmaObject( Restricted | Decrypt | FixedTPM | FixedParent | NoDA | UserWithAuth | SensitiveDataOrigin); SrkTemplate.publicArea.parameters.rsaDetail = RsaStorageParams; return &SrkTemplate; } #define DPS_UNMARSHAL(Type, pValue) \ { \ TPM_RC rc = Type##_Unmarshal(pValue, &curr_pos, (INT32*)&act_size); \ if (rc != TPM_RC_SUCCESS) \ { \ LogError(#Type"_Unmarshal() for " #pValue " failed"); \ } \ } #define DPS_UNMARSHAL_FLAGGED(Type, pValue) \ { \ TPM_RC rc = Type##_Unmarshal(pValue, &curr_pos, (INT32*)&act_size, TRUE); \ if (rc != TPM_RC_SUCCESS) \ { \ LogError(#Type"_Unmarshal() for " #pValue " failed"); \ } \ } #define DPS_UNMARSHAL_ARRAY(dstPtr, arrSize) \ DPS_UNMARSHAL(UINT32, &(arrSize)); \ printf("act_size %d < actSize %d\r\n", act_size, arrSize); \ if (act_size < arrSize) \ { \ LogError("Unmarshaling " #dstPtr " failed: Need %d bytes, while only %d left", arrSize, act_size); \ result = MU_FAILURE; \ } \ else \ { \ dstPtr = curr_pos - sizeof(UINT16); \ *(UINT16*)dstPtr = (UINT16)arrSize; \ curr_pos += arrSize; \ } static int dps_umarshal_array(unsigned char* dst_ptr, uint32_t dest_size, unsigned char* act_buff, uint32_t act_size) { int result; uint8_t* curr_pos = act_buff; DPS_UNMARSHAL(UINT32, &(dest_size)); if (act_size < dest_size) { LogError("Unmarshaling failed: Need %d bytes, while only %d left", dest_size, act_size); result = __LINE__; } else { dst_ptr = act_buff - sizeof(UINT16); *(UINT16*)dst_ptr = (UINT16)dest_size; act_buff += dest_size; result = 0; } return result; } static int unmarshal_array(uint8_t* dstptr, uint32_t size, uint8_t** curr_pos, uint32_t* curr_size) { int result; TPM_RC tpm_res = UINT32_Unmarshal((uint32_t*)dstptr, curr_pos, (int32_t*)curr_size); if (tpm_res != TPM_RC_SUCCESS) { LogError("Failure: unmarshalling array."); result = MU_FAILURE; } else if (*curr_size < size) { LogError("Failure: unmarshalling array need %d bytes, while only %d left.", size, *curr_size); result = MU_FAILURE; } else { dstptr = *curr_pos - sizeof(UINT16); *(UINT16*)dstptr = (UINT16)size; curr_pos += size; result = 0; } return result; } static int marshal_array_values(const unsigned char* key, size_t key_len, uint8_t** decrypt_blob, uint8_t** decrypt_secret, uint8_t** decrypt_wrap_key, TPM2B_PRIVATE* enc_key_blob) { int result = 0; uint8_t* curr_pos = (uint8_t*)key; uint32_t act_size = (int32_t)key_len; uint32_t decrypt_size = 0; uint32_t decrypt_secret_size = 0; uint32_t decrypt_key_size = 0; TPM2B_PUBLIC id_key_Public = { TPM_ALG_NULL }; UINT16 gratuitousSizeField; // WORKAROUND for the current protocol DPS_UNMARSHAL_ARRAY(*decrypt_blob, decrypt_size); if (result != 0) { LogError("Failure: decrypting blob"); } else { DPS_UNMARSHAL_ARRAY(*decrypt_secret, decrypt_secret_size); if (result != 0) { LogError("Failure: decrypting secret"); } else { DPS_UNMARSHAL_ARRAY(*decrypt_wrap_key, decrypt_key_size); if (result != 0) { LogError("Failure: decrypting wrap secret"); } else { DPS_UNMARSHAL_FLAGGED(TPM2B_PUBLIC, &id_key_Public); if (result != 0) { LogError("Failure: id key public"); } else { DPS_UNMARSHAL(UINT16, &gratuitousSizeField); if (result != 0) { LogError("Failure: gratuitousSizeField"); } else { DPS_UNMARSHAL(TPM2B_PRIVATE, enc_key_blob); if (result != 0) { LogError("Failure: enc key blob"); } } } } } } return result; } static int create_tpm_session(HSM_CLIENT_INFO* sec_info, TSS_SESSION* tpm_session) { int result; TPMA_SESSION sess_attrib = { 1 }; if (TSS_StartAuthSession(&sec_info->tpm_device, TPM_SE_POLICY, TPM_ALG_SHA256, sess_attrib, tpm_session) != TPM_RC_SUCCESS) { LogError("Failure: Starting EK policy session"); result = MU_FAILURE; } else if (TSS_PolicySecret(&sec_info->tpm_device, &NullPwSession, TPM_RH_ENDORSEMENT, tpm_session, NULL, 0) != TPM_RC_SUCCESS) { LogError("Failure: PolicySecret() for EK"); result = MU_FAILURE; } else { result = 0; } return result; } static int insert_key_in_tpm(HSM_CLIENT_INFO* sec_info, const unsigned char* key, size_t key_len) { int result; TSS_SESSION ek_sess = { { TPM_RH_NULL } }; if (create_tpm_session(sec_info, &ek_sess) != 0) { LogError("Failure: Starting EK policy session"); result = MU_FAILURE; } else { TPMT_SYM_DEF_OBJECT Aes128SymDef = { TPM_ALG_AES, 128, TPM_ALG_CFB }; TPM2B_ID_OBJECT enc_key_blob; TPM2B_ENCRYPTED_SECRET tpm_enc_secret; TPM2B_PRIVATE id_key_dup_blob; TPM2B_ENCRYPTED_SECRET encrypt_wrap_key; TPM2B_PUBLIC id_key_Public = { TPM_ALG_NULL }; UINT16 enc_data_size = 0; TPM2B_DIGEST inner_wrap_key = { 0 }; TPM2B_PRIVATE id_key_priv; TPM_HANDLE load_id_key = TPM_ALG_NULL; uint8_t* curr_pos = (uint8_t*)key; uint32_t act_size = (int32_t)key_len; DPS_UNMARSHAL(TPM2B_ID_OBJECT, &enc_key_blob); DPS_UNMARSHAL(TPM2B_ENCRYPTED_SECRET, &tpm_enc_secret); DPS_UNMARSHAL(TPM2B_PRIVATE, &id_key_dup_blob); DPS_UNMARSHAL(TPM2B_ENCRYPTED_SECRET, &encrypt_wrap_key); DPS_UNMARSHAL_FLAGGED(TPM2B_PUBLIC, &id_key_Public); // The given TPM may support larger TPM2B_MAX_BUFFER than this API headers define. // So instead of unmarshaling data in a standalone data structure just reuse the // original activation buffer (after updating byte order of the UINT16 counter) DPS_UNMARSHAL(UINT16, &enc_data_size); if (TPM2_ActivateCredential(&sec_info->tpm_device, &NullPwSession, &ek_sess, TPM_20_SRK_HANDLE, TPM_20_EK_HANDLE, &enc_key_blob, &tpm_enc_secret, &inner_wrap_key) != TPM_RC_SUCCESS) { LogError("Failure: TPM2_ActivateCredential"); result = MU_FAILURE; } else if (TPM2_Import(&sec_info->tpm_device, &NullPwSession, TPM_20_SRK_HANDLE, (TPM2B_DATA*)&inner_wrap_key, &id_key_Public, &id_key_dup_blob, &encrypt_wrap_key, &Aes128SymDef, &id_key_priv) != TPM_RC_SUCCESS) { LogError("Failure: importing dps Id key"); result = MU_FAILURE; } else { TPM2B_SENSITIVE_CREATE sen_create = { 0 }; TPM2B_PUBLIC sym_pub = { 0 }; TPM2B_PRIVATE sym_priv = { 0 }; static TPM2B_PUBLIC symTemplate = { 0, // size will be computed during marshaling { TPM_ALG_SYMCIPHER, // TPMI_ALG_PUBLIC type TPM_ALG_SHA256, // TPMI_ALG_HASH nameAlg { 0 }, // TPMA_OBJECT objectAttributes (set below) { 0 }, // TPM2B_DIGEST authPolicy { 0 }, // TPMU_PUBLIC_PARMS parameters (set below) { 0 } // TPMU_PUBLIC_ID unique } }; symTemplate.publicArea.objectAttributes = ToTpmaObject(Decrypt | FixedTPM | FixedParent | UserWithAuth); symTemplate.publicArea.parameters.symDetail.sym.algorithm = TPM_ALG_AES; symTemplate.publicArea.parameters.symDetail.sym.keyBits.sym = inner_wrap_key.t.size * 8; symTemplate.publicArea.parameters.symDetail.sym.mode.sym = TPM_ALG_CFB; memcpy(sen_create.sensitive.data.t.buffer, inner_wrap_key.t.buffer, inner_wrap_key.t.size); sen_create.sensitive.data.t.size = inner_wrap_key.t.size; if (TSS_Create(&sec_info->tpm_device, &NullPwSession, TPM_20_SRK_HANDLE, &sen_create, &symTemplate, &sym_priv, &sym_pub) != TPM_RC_SUCCESS) { LogError("Failed to inject symmetric key data"); result = MU_FAILURE; } else if (TPM2_Load(&sec_info->tpm_device, &NullPwSession, TPM_20_SRK_HANDLE, &id_key_priv, &id_key_Public, &load_id_key, NULL) != TPM_RC_SUCCESS) { LogError("Failed Load Id key."); result = MU_FAILURE; } else { // Remove old Id key (void)TPM2_EvictControl(&sec_info->tpm_device, &NullPwSession, TPM_RH_OWNER, DPS_ID_KEY_HANDLE, DPS_ID_KEY_HANDLE); if (TPM2_EvictControl(&sec_info->tpm_device, &NullPwSession, TPM_RH_OWNER, load_id_key, DPS_ID_KEY_HANDLE) != TPM_RC_SUCCESS) { LogError("Failed Load Id key."); result = MU_FAILURE; } else if (TPM2_FlushContext(&sec_info->tpm_device, load_id_key) != TPM_RC_SUCCESS) { LogError("Failed Load Id key."); result = MU_FAILURE; } else { result = 0; } } } } return result; } static BUFFER_HANDLE decrypt_data(HSM_CLIENT_INFO* sec_info, const unsigned char* key, size_t key_len) { BUFFER_HANDLE result; TSS_SESSION ek_sess = { { TPM_RH_NULL } }; UINT32 max_tpm_data_size; if (create_tpm_session(sec_info, &ek_sess) != 0) { LogError("Failure: Starting EK policy session"); result = NULL; } else if ((max_tpm_data_size = TSS_GetTpmProperty(&sec_info->tpm_device, TPM_PT_INPUT_BUFFER)) == 0) { LogError("Failure: TSS_GetTpmProperty Input buffer"); result = NULL; } else { TPM2B_ID_OBJECT tpm_blob; TPM2B_ENCRYPTED_SECRET tpm_enc_secret; TPM2B_ENCRYPTED_SECRET tpm_enc_key; TPM2B_PRIVATE id_key_dup_blob; UINT16 enc_data_size = 0; TPM2B_PUBLIC id_key_Public = { TPM_ALG_NULL }; TPM2B_MAX_BUFFER* target_data = NULL; TPM2B_DIGEST inner_wrap_key = { 0 }; uint8_t* curr_pos = (uint8_t*)key; uint32_t act_size = (int32_t)key_len; DPS_UNMARSHAL(TPM2B_ID_OBJECT, &tpm_blob); DPS_UNMARSHAL(TPM2B_ENCRYPTED_SECRET, &tpm_enc_secret); DPS_UNMARSHAL(TPM2B_PRIVATE, &id_key_dup_blob); DPS_UNMARSHAL(TPM2B_ENCRYPTED_SECRET, &tpm_enc_key); DPS_UNMARSHAL_FLAGGED(TPM2B_PUBLIC, &id_key_Public); // The given TPM may support larger TPM2B_MAX_BUFFER than this API headers define. // So instead of unmarshaling data in a standalone data structure just reuse the // original activation buffer (after updating byte order of the UINT16 counter) DPS_UNMARSHAL(UINT16, &enc_data_size); if (enc_data_size > max_tpm_data_size) { LogError("Failure: The encrypted data len (%" PRIu16 ") is too long for tpm", enc_data_size); result = NULL; } else { target_data = (TPM2B_MAX_BUFFER*)(curr_pos - sizeof(UINT16)); target_data->t.size = enc_data_size; // Update local vars in case activation blob contains more data to unmarshal curr_pos += enc_data_size; act_size -= enc_data_size; // decrypts encrypted symmetric key encSecret and returns it as 'tpm_blob'. // Later 'tpm_blob' is used as the inner wrapper key for import of the HMAC key blob. if (TPM2_ActivateCredential(&sec_info->tpm_device, &NullPwSession, &ek_sess, TPM_20_SRK_HANDLE, TPM_20_EK_HANDLE, &tpm_blob, &tpm_enc_secret, &inner_wrap_key) != TPM_RC_SUCCESS) { LogError("Failure: TPM2_ActivateCredential"); result = NULL; } else { result = BUFFER_create(tpm_blob.t.credential, tpm_blob.t.size); if (result == NULL) { LogError("Failure building buffer"); result = NULL; } } } } return result; } static int initialize_tpm_device(HSM_CLIENT_INFO* tpm_info) { int result; if (TSS_CreatePwAuthSession(&NullAuth, &NullPwSession) != TPM_RC_SUCCESS) { LogError("Failure calling TSS_CreatePwAuthSession"); result = MU_FAILURE; } else if (Initialize_TPM_Codec(&tpm_info->tpm_device) != TPM_RC_SUCCESS) { LogError("Failure initializeing TPM Codec"); result = MU_FAILURE; } else if ((TSS_CreatePersistentKey(&tpm_info->tpm_device, TPM_20_EK_HANDLE, &NullPwSession, TPM_RH_ENDORSEMENT, GetEkTemplate(), &tpm_info->ek_pub) ) == 0) { LogError("Failure calling creating persistent key for Endorsement key"); result = MU_FAILURE; } else if (TSS_CreatePersistentKey(&tpm_info->tpm_device, TPM_20_SRK_HANDLE, &NullPwSession, TPM_RH_OWNER, GetSrkTemplate(), &tpm_info->srk_pub) == 0) { LogError("Failure calling creating persistent key for Storage Root key"); result = MU_FAILURE; } else { result = 0; } return result; } HSM_CLIENT_HANDLE hsm_client_tpm_create() { HSM_CLIENT_INFO* result; result = malloc(sizeof(HSM_CLIENT_INFO) ); if (result == NULL) { LogError("Failure: malloc HSM_CLIENT_INFO."); } else { memset(result, 0, sizeof(HSM_CLIENT_INFO)); if (initialize_tpm_device(result) != 0) { LogError("Failure initializing tpm device."); free(result); result = NULL; } } return (HSM_CLIENT_HANDLE)result; } void hsm_client_tpm_destroy(HSM_CLIENT_HANDLE handle) { if (handle != NULL) { HSM_CLIENT_INFO* hsm_client_info = (HSM_CLIENT_INFO*)handle; Deinit_TPM_Codec(&hsm_client_info->tpm_device); free(hsm_client_info); } } int hsm_client_tpm_init(void) { return 0; } void hsm_client_tpm_deinit(void) { } const HSM_CLIENT_TPM_INTERFACE* hsm_client_tpm_interface(void) { return &tpm_interface; } int hsm_client_tpm_import_key(HSM_CLIENT_HANDLE handle, const unsigned char* key, size_t key_len) { int result; if (handle == NULL || key == NULL || key_len == 0) { LogError("Invalid argument specified handle: %p, key: %p, key_len: %lu", handle, key, (unsigned long)key_len); result = MU_FAILURE; } else { if (insert_key_in_tpm((HSM_CLIENT_INFO*)handle, key, key_len)) { LogError("Failure inserting key into tpm"); result = MU_FAILURE; } else { result = 0; } } return result; } int hsm_client_tpm_get_endorsement_key(HSM_CLIENT_HANDLE handle, unsigned char** key, size_t* key_len) { int result; if (handle == NULL || key == NULL || key_len == NULL) { LogError("Invalid handle value specified: handle: %p, result: %p, result_len: %p", handle, key, key_len); result = MU_FAILURE; } else { HSM_CLIENT_INFO* hsm_client_info = (HSM_CLIENT_INFO*)handle; if (hsm_client_info->ek_pub.publicArea.unique.rsa.t.size == 0) { LogError("Endorsement key is invalid"); result = MU_FAILURE; } else { unsigned char data_bytes[TPM_DATA_LENGTH]; unsigned char* data_pos = data_bytes; uint32_t data_length = TPM2B_PUBLIC_Marshal(&hsm_client_info->ek_pub, &data_pos, NULL); if (data_length > TPM_DATA_LENGTH) { LogError("EK data length larger than allocated buffer %" PRIu32, data_length); result = MU_FAILURE; } else if ((*key = (unsigned char*)malloc(data_length)) == NULL) { LogError("Failure creating buffer handle"); result = MU_FAILURE; } else { memcpy(*key, data_bytes, data_length); *key_len = (size_t)data_length; result = 0; } } } return result; } int hsm_client_tpm_get_storage_key(HSM_CLIENT_HANDLE handle, unsigned char** key, size_t* key_len) { int result; if (handle == NULL || key == NULL || key_len == NULL) { LogError("Invalid handle value specified: handle: %p, result: %p, result_len: %p", handle, key, key_len); result = MU_FAILURE; } else { HSM_CLIENT_INFO* hsm_client_info = (HSM_CLIENT_INFO*)handle; if (hsm_client_info->srk_pub.publicArea.unique.rsa.t.size == 0) { LogError("storage root key is invalid"); result = MU_FAILURE; } else { unsigned char data_bytes[TPM_DATA_LENGTH]; unsigned char* data_pos = data_bytes; uint32_t data_length = TPM2B_PUBLIC_Marshal(&hsm_client_info->srk_pub, &data_pos, NULL); if (data_length > TPM_DATA_LENGTH) { LogError("SRK data length larger than allocated buffer %" PRIu32, data_length); result = MU_FAILURE; } else if ((*key = (unsigned char*)malloc(data_length)) == NULL) { LogError("Failure creating buffer handle"); result = MU_FAILURE; } else { memcpy(*key, data_bytes, data_length); *key_len = (size_t)data_length; result = 0; } } } return result; } int hsm_client_tpm_sign_data(HSM_CLIENT_HANDLE handle, const unsigned char* data, size_t data_len, unsigned char** signed_value, size_t* signed_len) { int result; if (handle == NULL || data == NULL || data_len == 0 || signed_value == NULL || signed_len == NULL) { LogError("Invalid handle value specified handle: %p, data: %p, data_len: %lu, signed_value: %p, signed_len: %p", handle, data, (unsigned long)data_len, signed_value, signed_len); result = MU_FAILURE; } else { BYTE data_signature[TPM_DATA_LENGTH]; BYTE* data_copy = (unsigned char*)data; HSM_CLIENT_INFO* hsm_client_info = (HSM_CLIENT_INFO*)handle; uint32_t sign_len = SignData(&hsm_client_info->tpm_device, &NullPwSession, data_copy, (UINT32)data_len, data_signature, sizeof(data_signature) ); if (sign_len == 0) { LogError("Failure signing data from hash"); result = MU_FAILURE; } else { if ((*signed_value = (unsigned char*)malloc(sign_len)) == NULL) { LogError("Failure creating buffer handle"); result = MU_FAILURE; } else { memcpy(*signed_value, data_signature, sign_len); *signed_len = (size_t)sign_len; result = 0; } } } return result; }