| // |
| // Copyright (C) 2015 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 "tpm_manager/server/tpm_nvram_impl.h" |
| |
| #include <arpa/inet.h> |
| |
| #include <string> |
| |
| #include <base/logging.h> |
| #include <base/stl_util.h> |
| #include <trousers/scoped_tss_type.h> |
| |
| #include "tpm_manager/common/local_data.pb.h" |
| #include "tpm_manager/server/local_data_store.h" |
| #include "tpm_manager/server/tpm_util.h" |
| |
| namespace { |
| |
| // PCR0 at locality 1 is used to differentiate between developed and normal |
| // mode. Restricting nvram to the PCR0 value in locality 1 prevents nvram from |
| // persisting across mode switch. |
| const unsigned int kTpmBootPCR = 0; |
| const unsigned int kTpmPCRLocality = 1; |
| |
| } // namespace |
| |
| namespace tpm_manager { |
| |
| using trousers::ScopedTssMemory; |
| using trousers::ScopedTssNvStore; |
| using trousers::ScopedTssPcrs; |
| |
| TpmNvramImpl::TpmNvramImpl(LocalDataStore* local_data_store) |
| : local_data_store_(local_data_store) {} |
| |
| bool TpmNvramImpl::DefineNvram(uint32_t index, size_t length) { |
| ScopedTssNvStore nv_handle(tpm_connection_.GetContext()); |
| if (!(InitializeNvramHandle(&nv_handle, index) && |
| SetOwnerPolicy(&nv_handle))) { |
| return false; |
| } |
| TSS_RESULT result; |
| result = |
| Tspi_SetAttribUint32(nv_handle, TSS_TSPATTRIB_NV_DATASIZE, 0, length); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Could not set size on NVRAM object: " << length; |
| return false; |
| } |
| // Restrict to only one write. |
| result = Tspi_SetAttribUint32(nv_handle, TSS_TSPATTRIB_NV_PERMISSIONS, 0, |
| TPM_NV_PER_WRITEDEFINE); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Could not set PER_WRITEDEFINE on NVRAM object"; |
| return false; |
| } |
| // Restrict to writing only with owner authorization. |
| result = Tspi_SetAttribUint32(nv_handle, TSS_TSPATTRIB_NV_PERMISSIONS, 0, |
| TPM_NV_PER_OWNERWRITE); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Could not set PER_OWNERWRITE on NVRAM object"; |
| return false; |
| } |
| ScopedTssPcrs pcr_handle(tpm_connection_.GetContext()); |
| if (!SetCompositePcr0(&pcr_handle)) { |
| return false; |
| } |
| result = Tspi_NV_DefineSpace(nv_handle, |
| pcr_handle /* ReadPCRs restricted to PCR0 */, |
| pcr_handle /* WritePCRs restricted to PCR0 */); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Could not define NVRAM space: " << index; |
| return false; |
| } |
| return true; |
| } |
| |
| bool TpmNvramImpl::DestroyNvram(uint32_t index) { |
| bool defined; |
| if (!IsNvramDefined(index, &defined)) { |
| return false; |
| } |
| if (!defined) { |
| // If the nvram space is not defined, we don't need to destroy it. |
| return true; |
| } |
| ScopedTssNvStore nv_handle(tpm_connection_.GetContext()); |
| if (!(InitializeNvramHandle(&nv_handle, index) && |
| SetOwnerPolicy(&nv_handle))) { |
| return false; |
| } |
| TSS_RESULT result = Tspi_NV_ReleaseSpace(nv_handle); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Could not release NVRAM space: " << index; |
| return false; |
| } |
| return true; |
| } |
| |
| bool TpmNvramImpl::WriteNvram(uint32_t index, const std::string& data) { |
| ScopedTssNvStore nv_handle(tpm_connection_.GetContext()); |
| if (!(InitializeNvramHandle(&nv_handle, index) && |
| SetOwnerPolicy(&nv_handle))) { |
| return false; |
| } |
| TSS_RESULT result = Tspi_NV_WriteValue( |
| nv_handle, 0 /* offset */, data.size(), |
| reinterpret_cast<BYTE*>(const_cast<char*>(data.data()))); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Could not write to NVRAM space: " << index; |
| return false; |
| } |
| return true; |
| } |
| |
| bool TpmNvramImpl::ReadNvram(uint32_t index, std::string* data) { |
| CHECK(data); |
| TSS_RESULT result; |
| ScopedTssNvStore nv_handle(tpm_connection_.GetContext()); |
| if (!InitializeNvramHandle(&nv_handle, index)) { |
| return false; |
| } |
| size_t nvram_size; |
| if (!GetNvramSize(index, &nvram_size)) { |
| return false; |
| } |
| data->resize(nvram_size); |
| // The Tpm1.2 Specification defines the maximum read size of 128 bytes. |
| // Therefore we have to loop through the data returned. |
| const size_t kMaxDataSize = 128; |
| uint32_t offset = 0; |
| while (offset < nvram_size) { |
| uint32_t chunk_size = std::max(nvram_size - offset, kMaxDataSize); |
| ScopedTssMemory space_data(tpm_connection_.GetContext()); |
| if ((result = Tspi_NV_ReadValue(nv_handle, offset, &chunk_size, |
| space_data.ptr()))) { |
| TPM_LOG(ERROR, result) << "Could not read from NVRAM space: " << index; |
| return false; |
| } |
| if (!space_data.value()) { |
| LOG(ERROR) << "No data read from NVRAM space: " << index; |
| return false; |
| } |
| CHECK_LE((offset + chunk_size), data->size()); |
| data->replace(offset, chunk_size, |
| reinterpret_cast<char*>(space_data.value()), chunk_size); |
| offset += chunk_size; |
| } |
| return true; |
| } |
| |
| bool TpmNvramImpl::IsNvramDefined(uint32_t index, bool* defined) { |
| CHECK(defined); |
| uint32_t nv_list_data_length = 0; |
| ScopedTssMemory nv_list_data(tpm_connection_.GetContext()); |
| TSS_RESULT result = |
| Tspi_TPM_GetCapability(tpm_connection_.GetTpm(), TSS_TPMCAP_NV_LIST, 0, |
| NULL, &nv_list_data_length, nv_list_data.ptr()); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Error calling Tspi_TPM_GetCapability"; |
| return false; |
| } |
| // Walk the list and check if the index exists. |
| uint32_t* nv_list = reinterpret_cast<uint32_t*>(nv_list_data.value()); |
| uint32_t nv_list_length = nv_list_data_length / sizeof(uint32_t); |
| index = htonl(index); // TPM data is network byte order. |
| for (uint32_t i = 0; i < nv_list_length; ++i) { |
| if (index == nv_list[i]) { |
| *defined = true; |
| return true; |
| } |
| } |
| *defined = false; |
| return true; |
| } |
| |
| bool TpmNvramImpl::IsNvramLocked(uint32_t index, bool* locked) { |
| CHECK(locked); |
| uint32_t nv_index_data_length = 0; |
| ScopedTssMemory nv_index_data(tpm_connection_.GetContext()); |
| TSS_RESULT result = |
| Tspi_TPM_GetCapability(tpm_connection_.GetTpm(), TSS_TPMCAP_NV_INDEX, |
| sizeof(index), reinterpret_cast<BYTE*>(&index), |
| &nv_index_data_length, nv_index_data.ptr()); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Error calling Tspi_TPM_GetCapability"; |
| return false; |
| } |
| if (nv_index_data_length < (sizeof(uint32_t) + sizeof(TPM_BOOL))) { |
| return false; |
| } |
| // TPM_NV_DATA_PUBLIC->bWriteDefine is the second to last element in the |
| // struct. |
| uint32_t* nv_data_public = |
| reinterpret_cast<uint32_t*>(nv_index_data.value() + nv_index_data_length - |
| (sizeof(uint32_t) + sizeof(TPM_BOOL))); |
| *locked = (*nv_data_public != 0); |
| return true; |
| } |
| |
| bool TpmNvramImpl::GetNvramSize(uint32_t index, size_t* size) { |
| CHECK(size); |
| UINT32 nv_index_data_length = 0; |
| ScopedTssMemory nv_index_data(tpm_connection_.GetContext()); |
| TSS_RESULT result = |
| Tspi_TPM_GetCapability(tpm_connection_.GetTpm(), TSS_TPMCAP_NV_INDEX, |
| sizeof(index), reinterpret_cast<BYTE*>(&index), |
| &nv_index_data_length, nv_index_data.ptr()); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Error calling Tspi_TPM_GetCapability"; |
| return false; |
| } |
| if (nv_index_data_length < sizeof(uint32_t)) { |
| return false; |
| } |
| // TPM_NV_DATA_PUBLIC->dataSize is the last element in the struct. |
| uint32_t* nv_data_public = reinterpret_cast<uint32_t*>( |
| nv_index_data.value() + nv_index_data_length - sizeof(uint32_t)); |
| *size = htonl(*nv_data_public); |
| return true; |
| } |
| |
| bool TpmNvramImpl::InitializeNvramHandle(ScopedTssNvStore* nv_handle, |
| uint32_t index) { |
| TSS_RESULT result = Tspi_Context_CreateObject( |
| tpm_connection_.GetContext(), TSS_OBJECT_TYPE_NV, 0, nv_handle->ptr()); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Could not acquire an NVRAM object handle"; |
| return false; |
| } |
| result = Tspi_SetAttribUint32(nv_handle->value(), TSS_TSPATTRIB_NV_INDEX, 0, |
| index); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Could not set index on NVRAM object: " << index; |
| return false; |
| } |
| return true; |
| } |
| |
| bool TpmNvramImpl::SetOwnerPolicy(ScopedTssNvStore* nv_handle) { |
| trousers::ScopedTssPolicy policy_handle(tpm_connection_.GetContext()); |
| TSS_RESULT result; |
| result = Tspi_Context_CreateObject(tpm_connection_.GetContext(), |
| TSS_OBJECT_TYPE_POLICY, TSS_POLICY_USAGE, |
| policy_handle.ptr()); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Error calling Tspi_Context_CreateObject"; |
| return false; |
| } |
| std::string owner_password; |
| if (!GetOwnerPassword(&owner_password)) { |
| return false; |
| } |
| result = Tspi_Policy_SetSecret( |
| policy_handle, TSS_SECRET_MODE_PLAIN, owner_password.size(), |
| reinterpret_cast<BYTE*>(const_cast<char*>(owner_password.data()))); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Error calling Tspi_Policy_SetSecret"; |
| return false; |
| } |
| result = |
| Tspi_Policy_AssignToObject(policy_handle.value(), nv_handle->value()); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Could not set NVRAM object policy."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool TpmNvramImpl::SetCompositePcr0(ScopedTssPcrs* pcr_handle) { |
| TSS_RESULT result = Tspi_Context_CreateObject( |
| tpm_connection_.GetContext(), TSS_OBJECT_TYPE_PCRS, |
| TSS_PCRS_STRUCT_INFO_SHORT, pcr_handle->ptr()); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Could not acquire PCR object handle"; |
| return false; |
| } |
| uint32_t pcr_len; |
| std::string owner_password; |
| if (!GetOwnerPassword(&owner_password)) { |
| return false; |
| } |
| ScopedTssMemory pcr_value(tpm_connection_.GetContext()); |
| result = Tspi_TPM_PcrRead(tpm_connection_.GetTpmWithAuth(owner_password), |
| kTpmBootPCR, &pcr_len, pcr_value.ptr()); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Could not read PCR0 value"; |
| return false; |
| } |
| result = Tspi_PcrComposite_SetPcrValue(pcr_handle->value(), kTpmBootPCR, |
| pcr_len, pcr_value.value()); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Could not set value for PCR0 in PCR handle"; |
| return false; |
| } |
| result = |
| Tspi_PcrComposite_SetPcrLocality(pcr_handle->value(), kTpmPCRLocality); |
| if (TPM_ERROR(result)) { |
| TPM_LOG(ERROR, result) << "Could not set locality for PCR0 in PCR handle"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool TpmNvramImpl::GetOwnerPassword(std::string* owner_password) { |
| LocalData local_data; |
| if (!local_data_store_->Read(&local_data)) { |
| LOG(ERROR) << "Error reading local data for owner password."; |
| return false; |
| } |
| if (local_data.owner_password().empty()) { |
| LOG(ERROR) << "No owner password present in tpm local_data."; |
| return false; |
| } |
| owner_password->assign(local_data.owner_password()); |
| return true; |
| } |
| |
| } // namespace tpm_manager |