| // |
| // Copyright (C) 2020 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "encrypted_serializable.h" |
| |
| #include <vector> |
| |
| #include <android-base/logging.h> |
| |
| #include "host/commands/secure_env/tpm_auth.h" |
| #include "host/commands/secure_env/tpm_encrypt_decrypt.h" |
| #include "host/commands/secure_env/tpm_serialize.h" |
| |
| EncryptedSerializable::EncryptedSerializable( |
| TpmResourceManager& resource_manager, |
| std::function<TpmObjectSlot(TpmResourceManager&)> parent_key_fn, |
| Serializable& wrapped) : |
| resource_manager_(resource_manager), |
| parent_key_fn_(parent_key_fn), |
| wrapped_(wrapped) { |
| } |
| |
| static bool CreateKey( |
| TpmResourceManager& resource_manager, // in |
| ESYS_TR parent_key, // in |
| TPM2B_PUBLIC* key_public_out, // out |
| TPM2B_PRIVATE* key_private_out, // out |
| TpmObjectSlot* key_slot_out) { // out |
| TPM2B_AUTH authValue = {}; |
| auto rc = Esys_TR_SetAuth(resource_manager.Esys(), parent_key, &authValue); |
| if (rc != TSS2_RC_SUCCESS) { |
| LOG(ERROR) << "Esys_TR_SetAuth failed with return code " << rc |
| << " (" << Tss2_RC_Decode(rc) << ")"; |
| return false; |
| } |
| |
| TPMT_PUBLIC public_area = { |
| .type = TPM2_ALG_SYMCIPHER, |
| .nameAlg = TPM2_ALG_SHA256, |
| .objectAttributes = (TPMA_OBJECT_USERWITHAUTH | |
| TPMA_OBJECT_DECRYPT | |
| TPMA_OBJECT_SIGN_ENCRYPT | |
| TPMA_OBJECT_FIXEDTPM | |
| TPMA_OBJECT_FIXEDPARENT | |
| TPMA_OBJECT_SENSITIVEDATAORIGIN), |
| .authPolicy.size = 0, |
| .parameters.symDetail.sym = { |
| .algorithm = TPM2_ALG_AES, |
| .keyBits.aes = 128, // The default maximum AES key size in the simulator. |
| .mode.aes = TPM2_ALG_CFB, |
| }, |
| }; |
| |
| TPM2B_TEMPLATE public_template = {}; |
| size_t offset = 0; |
| rc = Tss2_MU_TPMT_PUBLIC_Marshal(&public_area, &public_template.buffer[0], |
| sizeof(public_template.buffer), &offset); |
| if (rc != TSS2_RC_SUCCESS) { |
| LOG(ERROR) << "Tss2_MU_TPMT_PUBLIC_Marshal failed with return code " << rc |
| << " (" << Tss2_RC_Decode(rc) << ")"; |
| return false; |
| } |
| public_template.size = offset; |
| |
| TPM2B_SENSITIVE_CREATE in_sensitive = {}; |
| |
| auto key_slot = resource_manager.ReserveSlot(); |
| if (!key_slot) { |
| LOG(ERROR) << "No slots available"; |
| return false; |
| } |
| ESYS_TR raw_handle; |
| // TODO(b/154956668): Define better ACLs on these keys. |
| TPM2B_PUBLIC* key_public = nullptr; |
| TPM2B_PRIVATE* key_private = nullptr; |
| // TODO(schuffelen): Use Esys_Create when key_slot is NULL |
| rc = Esys_CreateLoaded( |
| /* esysContext */ resource_manager.Esys(), |
| /* primaryHandle */ parent_key, |
| /* shandle1 */ ESYS_TR_PASSWORD, |
| /* shandle2 */ ESYS_TR_NONE, |
| /* shandle3 */ ESYS_TR_NONE, |
| /* inSensitive */ &in_sensitive, |
| /* inPublic */ &public_template, |
| /* objectHandle */ &raw_handle, |
| /* outPrivate */ &key_private, |
| /* outPublic */ &key_public); |
| if (rc != TSS2_RC_SUCCESS) { |
| LOG(ERROR) << "Esys_CreateLoaded failed with return code " << rc |
| << " (" << Tss2_RC_Decode(rc) << ")"; |
| return false; |
| } |
| CHECK(key_public != nullptr) << "key_public was not assigned."; |
| CHECK(key_private != nullptr) << "key_private was not assigned."; |
| *key_public_out = *key_public; |
| *key_private_out = *key_private; |
| key_slot->set(raw_handle); |
| Esys_Free(key_public); |
| Esys_Free(key_private); |
| if (key_slot_out) { |
| rc = Esys_TR_SetAuth(resource_manager.Esys(), raw_handle, &authValue); |
| if (rc != TSS2_RC_SUCCESS) { |
| LOG(ERROR) << "Esys_TR_SetAuth failed with return code " << rc |
| << " (" << Tss2_RC_Decode(rc) << ")"; |
| return false; |
| } |
| } |
| if (key_slot_out) { |
| *key_slot_out = key_slot; |
| } |
| return true; |
| } |
| |
| static TpmObjectSlot LoadKey( |
| TpmResourceManager& resource_manager, |
| ESYS_TR parent_key, |
| const TPM2B_PUBLIC* key_public, |
| const TPM2B_PRIVATE* key_private) { |
| // TODO |
| ESYS_TR raw_handle; |
| auto key_slot = resource_manager.ReserveSlot(); |
| if (!key_slot) { |
| LOG(ERROR) << "No slots available"; |
| return {}; |
| } |
| auto rc = Esys_Load( |
| resource_manager.Esys(), |
| parent_key, |
| ESYS_TR_PASSWORD, |
| ESYS_TR_NONE, |
| ESYS_TR_NONE, |
| key_private, |
| key_public, |
| &raw_handle); |
| if (rc != TSS2_RC_SUCCESS) { |
| LOG(ERROR) << "Esys_Load failed with return code " << rc |
| << " (" << Tss2_RC_Decode(rc) << ")"; |
| return {}; |
| } |
| key_slot->set(raw_handle); |
| return key_slot; |
| } |
| |
| static constexpr uint32_t BLOCK_SIZE = 16; |
| |
| static uint32_t RoundUpToBlockSize(uint32_t num) { |
| return num % BLOCK_SIZE == 0 ? num : num + (BLOCK_SIZE - (num % BLOCK_SIZE)); |
| } |
| |
| size_t EncryptedSerializable::SerializedSize() const { |
| TPM2B_PUBLIC key_public; |
| TPM2B_PRIVATE key_private; |
| auto parent = parent_key_fn_(resource_manager_); |
| if (!CreateKey( |
| resource_manager_, parent->get(), &key_public, &key_private, nullptr)) { |
| LOG(ERROR) << "Unable to create key"; |
| return 0; |
| } |
| // Assumes all created keys will have the same size. |
| SerializeTpmKeyPublic serialize_public(&key_public); |
| SerializeTpmKeyPrivate serialize_private(&key_private); |
| auto encrypted_size = RoundUpToBlockSize(wrapped_.SerializedSize()); |
| return serialize_public.SerializedSize() |
| + serialize_private.SerializedSize() |
| + sizeof(uint32_t) |
| + sizeof(uint32_t) |
| + encrypted_size; |
| } |
| |
| uint8_t* EncryptedSerializable::Serialize( |
| uint8_t* buf, const uint8_t* end) const { |
| TPM2B_PUBLIC key_public; |
| TPM2B_PRIVATE key_private; |
| auto parent = parent_key_fn_(resource_manager_); |
| if (!parent) { |
| LOG(ERROR) << "Unable to load encryption parent key"; |
| return buf; |
| } |
| TpmObjectSlot key_slot; |
| if (!CreateKey( |
| resource_manager_, parent->get(), &key_public, &key_private, &key_slot)) { |
| LOG(ERROR) << "Unable to create key"; |
| return buf; |
| } |
| |
| auto wrapped_size = wrapped_.SerializedSize(); |
| auto encrypted_size = RoundUpToBlockSize(wrapped_size); |
| std::vector<uint8_t> unencrypted(encrypted_size + 1, 0); |
| auto unencrypted_buf = unencrypted.data(); |
| auto unencrypted_buf_end = unencrypted_buf + unencrypted.size(); |
| auto next_buf = wrapped_.Serialize(unencrypted_buf, unencrypted_buf_end); |
| if (next_buf - unencrypted_buf != wrapped_size) { |
| LOG(ERROR) << "Size mismatch on wrapped data"; |
| return buf; |
| } |
| std::vector<uint8_t> encrypted(encrypted_size, 0); |
| if (!TpmEncrypt( |
| resource_manager_.Esys(), |
| key_slot->get(), |
| TpmAuth(ESYS_TR_PASSWORD), |
| unencrypted.data(), |
| encrypted.data(), |
| encrypted_size)) { |
| LOG(ERROR) << "Encryption failed"; |
| return buf; |
| } |
| SerializeTpmKeyPublic serialize_public(&key_public); |
| SerializeTpmKeyPrivate serialize_private(&key_private); |
| |
| buf = serialize_public.Serialize(buf, end); |
| buf = serialize_private.Serialize(buf, end); |
| buf = keymaster::append_uint32_to_buf(buf, end, BLOCK_SIZE); |
| buf = keymaster::append_uint32_to_buf(buf, end, wrapped_size); |
| buf = keymaster::append_to_buf(buf, end, encrypted.data(), encrypted_size); |
| return buf; |
| } |
| |
| bool EncryptedSerializable::Deserialize( |
| const uint8_t** buf_ptr, const uint8_t* end) { |
| auto parent_key = parent_key_fn_(resource_manager_); |
| if (!parent_key) { |
| LOG(ERROR) << "Unable to load encryption parent key"; |
| return false; |
| } |
| TPM2B_PUBLIC key_public; |
| SerializeTpmKeyPublic serialize_public(&key_public); |
| if (!serialize_public.Deserialize(buf_ptr, end)) { |
| LOG(ERROR) << "Unable to deserialize key public part"; |
| return false; |
| } |
| TPM2B_PRIVATE key_private; |
| SerializeTpmKeyPrivate serialize_private(&key_private); |
| if (!serialize_private.Deserialize(buf_ptr, end)) { |
| LOG(ERROR) << "Unable to deserialize key private part"; |
| return false; |
| } |
| auto key_slot = |
| LoadKey(resource_manager_, parent_key->get(), &key_public, &key_private); |
| if (!key_slot) { |
| LOG(ERROR) << "Failed to load key into TPM"; |
| return false; |
| } |
| uint32_t block_size = 0; |
| if (!keymaster::copy_uint32_from_buf(buf_ptr, end, &block_size)) { |
| LOG(ERROR) << "Failed to read block size"; |
| return false; |
| } |
| if (block_size != BLOCK_SIZE) { |
| LOG(ERROR) << "Unexpected block size: was " << block_size |
| << ", expected " << BLOCK_SIZE; |
| return false; |
| } |
| uint32_t wrapped_size = 0; |
| if (!keymaster::copy_uint32_from_buf(buf_ptr, end, &wrapped_size)) { |
| LOG(ERROR) << "Failed to read wrapped size"; |
| return false; |
| } |
| uint32_t encrypted_size = RoundUpToBlockSize(wrapped_size); |
| std::vector<uint8_t> encrypted_data(encrypted_size, 0); |
| if (!keymaster::copy_from_buf( |
| buf_ptr, end, encrypted_data.data(), encrypted_size)) { |
| LOG(ERROR) << "Failed to read encrypted data"; |
| return false; |
| } |
| std::vector<uint8_t> decrypted_data(encrypted_size, 0); |
| if (!TpmDecrypt( |
| resource_manager_.Esys(), |
| key_slot->get(), |
| TpmAuth(ESYS_TR_PASSWORD), |
| encrypted_data.data(), |
| decrypted_data.data(), |
| encrypted_size)) { |
| LOG(ERROR) << "Failed to decrypt encrypted data"; |
| return false; |
| } |
| auto decrypted_buf = decrypted_data.data(); |
| auto decrypted_buf_end = decrypted_data.data() + wrapped_size; |
| if (!wrapped_.Deserialize( |
| const_cast<const uint8_t **>(&decrypted_buf), decrypted_buf_end)) { |
| LOG(ERROR) << "Failed to deserialize wrapped type"; |
| return false; |
| } |
| if (decrypted_buf != decrypted_buf_end) { |
| LOG(ERROR) << "Inner type did not use all data"; |
| return false; |
| } |
| return true; |
| } |