Zentaro Kavanagh | 04eb2b0 | 2018-10-25 18:21:22 -0700 | [diff] [blame] | 1 | // Copyright 2018 The Chromium OS Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "libtpmcrypto/tpm1_impl.h" |
| 6 | |
Amin Hassani | 3ee8c80 | 2018-10-24 17:01:45 -0700 | [diff] [blame] | 7 | #include <memory> |
| 8 | #include <string> |
| 9 | |
Qijiang Fan | 713061e | 2021-03-08 15:45:12 +0900 | [diff] [blame] | 10 | #include <base/check.h> |
Zentaro Kavanagh | 04eb2b0 | 2018-10-25 18:21:22 -0700 | [diff] [blame] | 11 | #include <base/logging.h> |
| 12 | #include <base/threading/platform_thread.h> |
| 13 | #include <base/time/time.h> |
| 14 | #include <brillo/secure_blob.h> |
| 15 | #include <trousers/scoped_tss_type.h> |
| 16 | |
| 17 | using base::PlatformThread; |
| 18 | using base::TimeDelta; |
| 19 | using brillo::Blob; |
| 20 | using brillo::SecureBlob; |
| 21 | using trousers::ScopedTssContext; |
| 22 | using trousers::ScopedTssKey; |
| 23 | using trousers::ScopedTssMemory; |
Amin Hassani | 3ee8c80 | 2018-10-24 17:01:45 -0700 | [diff] [blame] | 24 | using trousers::ScopedTssNvStore; |
Zentaro Kavanagh | 04eb2b0 | 2018-10-25 18:21:22 -0700 | [diff] [blame] | 25 | using trousers::ScopedTssPcrs; |
| 26 | |
| 27 | namespace tpmcrypto { |
| 28 | |
| 29 | #define TPM_LOG(severity, result) \ |
| 30 | LOG(severity) << "TPM error 0x" << std::hex << result << " (" \ |
| 31 | << Trspi_Error_String(result) << "): " |
| 32 | |
| 33 | constexpr unsigned char kDefaultSrkAuth[] = {}; |
| 34 | constexpr unsigned int kTpmConnectRetries = 10; |
| 35 | constexpr unsigned int kTpmConnectIntervalMs = 100; |
| 36 | |
| 37 | Tpm1Impl::Tpm1Impl() = default; |
| 38 | Tpm1Impl::~Tpm1Impl() = default; |
| 39 | |
Amin Hassani | 3ee8c80 | 2018-10-24 17:01:45 -0700 | [diff] [blame] | 40 | std::unique_ptr<Tpm> CreateTpmInstance() { |
| 41 | return std::make_unique<Tpm1Impl>(); |
| 42 | } |
| 43 | |
Sarthak Kukreti | 95f75a0 | 2019-01-15 18:34:17 -0800 | [diff] [blame] | 44 | bool Tpm1Impl::SealToPCR0(const SecureBlob& value, SecureBlob* sealed_value) { |
Zentaro Kavanagh | 04eb2b0 | 2018-10-25 18:21:22 -0700 | [diff] [blame] | 45 | CHECK(sealed_value); |
| 46 | ScopedTssContext context_handle; |
| 47 | TSS_HTPM tpm_handle; |
| 48 | if (!ConnectContextAsUser(context_handle.ptr(), &tpm_handle)) { |
| 49 | LOG(ERROR) << "SealToPCR0: Failed to connect to the TPM."; |
| 50 | return false; |
| 51 | } |
| 52 | // Load the Storage Root Key. |
| 53 | TSS_RESULT result; |
| 54 | ScopedTssKey srk_handle(context_handle); |
| 55 | if (!LoadSrk(context_handle, srk_handle.ptr(), &result)) { |
| 56 | TPM_LOG(ERROR, result) << "SealToPCR0: Failed to load SRK."; |
| 57 | return false; |
| 58 | } |
| 59 | |
| 60 | // Check the SRK public key |
| 61 | unsigned int size_n = 0; |
| 62 | ScopedTssMemory public_srk(context_handle); |
| 63 | if (TPM_ERROR( |
| 64 | result = Tspi_Key_GetPubKey(srk_handle, &size_n, public_srk.ptr()))) { |
| 65 | TPM_LOG(ERROR, result) << "SealToPCR0: Unable to get the SRK public key"; |
| 66 | return false; |
| 67 | } |
| 68 | |
| 69 | // Create a PCRS object which holds the value of PCR0. |
| 70 | ScopedTssPcrs pcrs_handle(context_handle); |
| 71 | if (TPM_ERROR(result = Tspi_Context_CreateObject( |
| 72 | context_handle, TSS_OBJECT_TYPE_PCRS, TSS_PCRS_STRUCT_INFO, |
| 73 | pcrs_handle.ptr()))) { |
| 74 | TPM_LOG(ERROR, result) |
| 75 | << "SealToPCR0: Error calling Tspi_Context_CreateObject"; |
| 76 | return false; |
| 77 | } |
| 78 | |
| 79 | // Create a ENCDATA object to receive the sealed data. |
| 80 | UINT32 pcr_len = 0; |
| 81 | ScopedTssMemory pcr_value(context_handle); |
| 82 | Tspi_TPM_PcrRead(tpm_handle, 0, &pcr_len, pcr_value.ptr()); |
| 83 | Tspi_PcrComposite_SetPcrValue(pcrs_handle, 0, pcr_len, pcr_value.value()); |
| 84 | |
| 85 | ScopedTssKey enc_handle(context_handle); |
| 86 | if (TPM_ERROR(result = Tspi_Context_CreateObject( |
| 87 | context_handle, TSS_OBJECT_TYPE_ENCDATA, TSS_ENCDATA_SEAL, |
| 88 | enc_handle.ptr()))) { |
| 89 | TPM_LOG(ERROR, result) |
| 90 | << "SealToPCR0: Error calling Tspi_Context_CreateObject"; |
| 91 | return false; |
| 92 | } |
| 93 | |
| 94 | // Seal the given value with the SRK. |
| 95 | if (TPM_ERROR(result = Tspi_Data_Seal(enc_handle, srk_handle, value.size(), |
| 96 | const_cast<BYTE*>(value.data()), |
| 97 | pcrs_handle))) { |
| 98 | TPM_LOG(ERROR, result) << "SealToPCR0: Error calling Tspi_Data_Seal"; |
| 99 | return false; |
| 100 | } |
| 101 | |
| 102 | // Extract the sealed value. |
| 103 | ScopedTssMemory enc_data(context_handle); |
| 104 | UINT32 enc_data_length = 0; |
| 105 | if (TPM_ERROR(result = |
| 106 | Tspi_GetAttribData(enc_handle, TSS_TSPATTRIB_ENCDATA_BLOB, |
| 107 | TSS_TSPATTRIB_ENCDATABLOB_BLOB, |
| 108 | &enc_data_length, enc_data.ptr()))) { |
| 109 | TPM_LOG(ERROR, result) << "SealToPCR0: Error calling Tspi_GetAttribData"; |
| 110 | return false; |
| 111 | } |
| 112 | sealed_value->assign(&enc_data.value()[0], |
| 113 | &enc_data.value()[enc_data_length]); |
| 114 | return true; |
| 115 | } |
| 116 | |
Sarthak Kukreti | 95f75a0 | 2019-01-15 18:34:17 -0800 | [diff] [blame] | 117 | bool Tpm1Impl::Unseal(const SecureBlob& sealed_value, SecureBlob* value) { |
Zentaro Kavanagh | 04eb2b0 | 2018-10-25 18:21:22 -0700 | [diff] [blame] | 118 | CHECK(value); |
| 119 | ScopedTssContext context_handle; |
| 120 | TSS_HTPM tpm_handle; |
| 121 | if (!ConnectContextAsUser(context_handle.ptr(), &tpm_handle)) { |
| 122 | LOG(ERROR) << "Unseal: Failed to connect to the TPM."; |
| 123 | return false; |
| 124 | } |
| 125 | // Load the Storage Root Key. |
| 126 | TSS_RESULT result; |
| 127 | ScopedTssKey srk_handle(context_handle); |
| 128 | if (!LoadSrk(context_handle, srk_handle.ptr(), &result)) { |
| 129 | TPM_LOG(ERROR, result) << "Unseal: Failed to load SRK."; |
| 130 | return false; |
| 131 | } |
| 132 | |
| 133 | // Create an ENCDATA object with the sealed value. |
| 134 | ScopedTssKey enc_handle(context_handle); |
| 135 | if (TPM_ERROR(result = Tspi_Context_CreateObject( |
| 136 | context_handle, TSS_OBJECT_TYPE_ENCDATA, TSS_ENCDATA_SEAL, |
| 137 | enc_handle.ptr()))) { |
| 138 | TPM_LOG(ERROR, result) << "Unseal: Error calling Tspi_Context_CreateObject"; |
| 139 | return false; |
| 140 | } |
| 141 | |
| 142 | if (TPM_ERROR(result = Tspi_SetAttribData( |
| 143 | enc_handle, TSS_TSPATTRIB_ENCDATA_BLOB, |
| 144 | TSS_TSPATTRIB_ENCDATABLOB_BLOB, sealed_value.size(), |
| 145 | const_cast<BYTE*>(sealed_value.data())))) { |
| 146 | TPM_LOG(ERROR, result) << "Unseal: Error calling Tspi_SetAttribData"; |
| 147 | return false; |
| 148 | } |
| 149 | |
| 150 | // Unseal using the SRK. |
| 151 | ScopedTssMemory dec_data(context_handle); |
| 152 | UINT32 dec_data_length = 0; |
| 153 | if (TPM_ERROR(result = Tspi_Data_Unseal(enc_handle, srk_handle, |
| 154 | &dec_data_length, dec_data.ptr()))) { |
| 155 | TPM_LOG(ERROR, result) << "Unseal: Error calling Tspi_Data_Unseal"; |
| 156 | return false; |
| 157 | } |
| 158 | value->assign(&dec_data.value()[0], &dec_data.value()[dec_data_length]); |
Tom Hughes | bd48f33 | 2021-01-07 10:18:05 -0800 | [diff] [blame] | 159 | brillo::SecureClearBytes(dec_data.value(), dec_data_length); |
Zentaro Kavanagh | 04eb2b0 | 2018-10-25 18:21:22 -0700 | [diff] [blame] | 160 | return true; |
| 161 | } |
| 162 | |
| 163 | TSS_HCONTEXT Tpm1Impl::ConnectContext() { |
| 164 | TSS_RESULT result; |
| 165 | TSS_HCONTEXT context_handle = 0; |
| 166 | if (!OpenAndConnectTpm(&context_handle, &result)) { |
| 167 | return 0; |
| 168 | } |
| 169 | return context_handle; |
| 170 | } |
| 171 | |
| 172 | bool Tpm1Impl::OpenAndConnectTpm(TSS_HCONTEXT* context_handle, |
| 173 | TSS_RESULT* result) { |
| 174 | TSS_RESULT local_result; |
| 175 | ScopedTssContext local_context_handle; |
| 176 | if (TPM_ERROR(local_result = |
| 177 | Tspi_Context_Create(local_context_handle.ptr()))) { |
| 178 | TPM_LOG(ERROR, local_result) << "Error calling Tspi_Context_Create"; |
| 179 | if (result) |
| 180 | *result = local_result; |
| 181 | return false; |
| 182 | } |
| 183 | |
| 184 | for (unsigned int i = 0; i < kTpmConnectRetries; i++) { |
| 185 | LOG(INFO) << "Attempting to connect to TPM"; |
| 186 | if (TPM_ERROR(local_result = |
| 187 | Tspi_Context_Connect(local_context_handle, NULL))) { |
| 188 | // If there was a communications failure, try sleeping a bit here--it may |
| 189 | // be that tcsd is still starting |
| 190 | if (ERROR_CODE(local_result) == TSS_E_COMM_FAILURE) { |
| 191 | LOG(INFO) << "Sleeping to wait for TPM"; |
| 192 | PlatformThread::Sleep( |
| 193 | TimeDelta::FromMilliseconds(kTpmConnectIntervalMs)); |
| 194 | } else { |
| 195 | TPM_LOG(ERROR, local_result) << "Error calling Tspi_Context_Connect"; |
| 196 | if (result) |
| 197 | *result = local_result; |
| 198 | return false; |
| 199 | } |
| 200 | } else { |
| 201 | break; |
| 202 | } |
| 203 | } |
| 204 | if (TPM_ERROR(local_result)) { |
| 205 | TPM_LOG(ERROR, local_result) << "Error calling Tspi_Context_Connect"; |
| 206 | if (result) |
| 207 | *result = local_result; |
| 208 | return false; |
| 209 | } |
| 210 | |
| 211 | *context_handle = local_context_handle.release(); |
| 212 | if (result) |
| 213 | *result = local_result; |
| 214 | return (*context_handle != 0); |
| 215 | } |
| 216 | |
| 217 | bool Tpm1Impl::GetTpm(TSS_HCONTEXT context_handle, TSS_HTPM* tpm_handle) { |
| 218 | TSS_RESULT result; |
| 219 | TSS_HTPM local_tpm_handle; |
| 220 | if (TPM_ERROR(result = Tspi_Context_GetTpmObject(context_handle, |
| 221 | &local_tpm_handle))) { |
| 222 | TPM_LOG(ERROR, result) << "Error calling Tspi_Context_GetTpmObject"; |
| 223 | return false; |
| 224 | } |
| 225 | *tpm_handle = local_tpm_handle; |
| 226 | return true; |
| 227 | } |
| 228 | |
| 229 | bool Tpm1Impl::ConnectContextAsUser(TSS_HCONTEXT* context, TSS_HTPM* tpm) { |
| 230 | *context = 0; |
| 231 | *tpm = 0; |
| 232 | if ((*context = ConnectContext()) == 0) { |
| 233 | LOG(ERROR) << "ConnectContextAsUser: Could not open the TPM"; |
| 234 | return false; |
| 235 | } |
| 236 | if (!GetTpm(*context, tpm)) { |
| 237 | LOG(ERROR) << "ConnectContextAsUser: failed to get a TPM object"; |
| 238 | Tspi_Context_Close(*context); |
| 239 | *context = 0; |
| 240 | *tpm = 0; |
| 241 | return false; |
| 242 | } |
| 243 | return true; |
| 244 | } |
| 245 | |
| 246 | bool Tpm1Impl::LoadSrk(TSS_HCONTEXT context_handle, |
| 247 | TSS_HKEY* srk_handle, |
| 248 | TSS_RESULT* result) const { |
| 249 | *result = TSS_SUCCESS; |
| 250 | |
| 251 | // Load the Storage Root Key |
| 252 | TSS_UUID SRK_UUID = TSS_UUID_SRK; |
| 253 | ScopedTssKey local_srk_handle(context_handle); |
| 254 | if (TPM_ERROR(*result = Tspi_Context_LoadKeyByUUID( |
| 255 | context_handle, TSS_PS_TYPE_SYSTEM, SRK_UUID, |
| 256 | local_srk_handle.ptr()))) { |
| 257 | return false; |
| 258 | } |
| 259 | |
| 260 | // Check if the SRK wants a password |
| 261 | UINT32 srk_authusage; |
| 262 | if (TPM_ERROR(*result = Tspi_GetAttribUint32( |
| 263 | local_srk_handle, TSS_TSPATTRIB_KEY_INFO, |
| 264 | TSS_TSPATTRIB_KEYINFO_AUTHUSAGE, &srk_authusage))) { |
| 265 | return false; |
| 266 | } |
| 267 | |
| 268 | // Give it the password if needed |
| 269 | if (srk_authusage) { |
| 270 | TSS_HPOLICY srk_usage_policy; |
| 271 | if (TPM_ERROR(*result = Tspi_GetPolicyObject( |
| 272 | local_srk_handle, TSS_POLICY_USAGE, &srk_usage_policy))) { |
| 273 | return false; |
| 274 | } |
| 275 | |
| 276 | *result = Tspi_Policy_SetSecret(srk_usage_policy, TSS_SECRET_MODE_PLAIN, |
| 277 | sizeof(kDefaultSrkAuth), |
| 278 | const_cast<BYTE*>(kDefaultSrkAuth)); |
| 279 | if (TPM_ERROR(*result)) { |
| 280 | return false; |
| 281 | } |
| 282 | } |
| 283 | |
| 284 | *srk_handle = local_srk_handle.release(); |
| 285 | return true; |
| 286 | } |
| 287 | |
Amin Hassani | 3ee8c80 | 2018-10-24 17:01:45 -0700 | [diff] [blame] | 288 | bool Tpm1Impl::GetNVAttributes(uint32_t index, uint32_t* attributes) { |
| 289 | CHECK(attributes); |
| 290 | ScopedTssContext context_handle; |
| 291 | TSS_HTPM tpm_handle; |
| 292 | if (!ConnectContextAsUser(context_handle.ptr(), &tpm_handle)) { |
| 293 | LOG(ERROR) << "GetNVAttributes: Failed to connect to the TPM."; |
| 294 | return false; |
| 295 | } |
| 296 | |
| 297 | TSS_RESULT result; |
| 298 | UINT32 nv_index_data_length = sizeof(TPM_NV_DATA_PUBLIC); |
| 299 | ScopedTssMemory nv_index_data(context_handle); |
| 300 | if (TPM_ERROR(result = Tspi_TPM_GetCapability( |
| 301 | tpm_handle, TSS_TPMCAP_NV_INDEX, sizeof(index), |
| 302 | reinterpret_cast<BYTE*>(&index), &nv_index_data_length, |
| 303 | nv_index_data.ptr()))) { |
| 304 | TPM_LOG(ERROR, result) << "Error calling Tspi_TPM_GetCapability"; |
| 305 | return false; |
| 306 | } |
| 307 | if (nv_index_data_length == 0) { |
| 308 | LOG(ERROR) << "The NV index public data length is not valid"; |
| 309 | return false; |
| 310 | } |
| 311 | |
| 312 | TPM_NV_DATA_PUBLIC nv_data_public; |
| 313 | UINT64 offset = 0; |
| 314 | if (TPM_ERROR(result = Trspi_UnloadBlob_NV_DATA_PUBLIC( |
| 315 | &offset, *nv_index_data.ptr(), &nv_data_public))) { |
| 316 | TPM_LOG(ERROR, result) << "Error unloading NV public data."; |
| 317 | return false; |
| 318 | } |
| 319 | |
| 320 | *attributes = nv_data_public.permission.attributes; |
| 321 | return true; |
| 322 | } |
| 323 | |
| 324 | bool Tpm1Impl::NVReadNoAuth(uint32_t index, |
| 325 | uint32_t offset, |
| 326 | size_t size, |
| 327 | std::string* data) { |
| 328 | CHECK(data); |
| 329 | ScopedTssContext context_handle; |
| 330 | TSS_HTPM tpm_handle; |
| 331 | if (!ConnectContextAsUser(context_handle.ptr(), &tpm_handle)) { |
| 332 | LOG(ERROR) << "NVReadNoAuth: Failed to connect to the TPM."; |
| 333 | return false; |
| 334 | } |
| 335 | |
| 336 | // Create an NVRAM store object handle. |
| 337 | TSS_RESULT result; |
| 338 | ScopedTssNvStore nv_handle(context_handle); |
| 339 | result = Tspi_Context_CreateObject(context_handle, TSS_OBJECT_TYPE_NV, 0, |
| 340 | nv_handle.ptr()); |
| 341 | if (TPM_ERROR(result)) { |
| 342 | TPM_LOG(ERROR, result) << "Could not acquire an NVRAM object handle"; |
| 343 | return false; |
| 344 | } |
| 345 | result = Tspi_SetAttribUint32(nv_handle, TSS_TSPATTRIB_NV_INDEX, 0, index); |
| 346 | if (TPM_ERROR(result)) { |
| 347 | TPM_LOG(ERROR, result) << "Could not set index on NVRAM object: " << index; |
| 348 | return false; |
| 349 | } |
| 350 | |
| 351 | SecureBlob blob(size); |
| 352 | // Read from NVRAM in conservatively small chunks. This is a limitation of the |
| 353 | // TPM that is left for the application layer to deal with. The maximum size |
| 354 | // that is supported here can vary between vendors / models, so we'll be |
| 355 | // conservative. FWIW, the Infineon chips seem to handle up to 1024. |
| 356 | const UINT32 kMaxDataSize = 128; |
| 357 | UINT32 offset_l = 0; |
| 358 | while (offset_l < size) { |
| 359 | UINT32 chunk_size = size - offset_l; |
| 360 | if (chunk_size > kMaxDataSize) |
| 361 | chunk_size = kMaxDataSize; |
| 362 | ScopedTssMemory space_data(context_handle); |
| 363 | if ((result = Tspi_NV_ReadValue(nv_handle, offset_l + offset, &chunk_size, |
| 364 | space_data.ptr()))) { |
| 365 | TPM_LOG(ERROR, result) << "Could not read from NVRAM space: " << index; |
| 366 | return false; |
| 367 | } |
| 368 | if (!space_data.value()) { |
| 369 | LOG(ERROR) << "No data read from NVRAM space: " << index; |
| 370 | return false; |
| 371 | } |
| 372 | CHECK(offset_l + chunk_size <= blob.size()); |
| 373 | unsigned char* buffer = blob.data() + offset_l; |
| 374 | memcpy(buffer, space_data.value(), chunk_size); |
| 375 | offset_l += chunk_size; |
| 376 | } |
| 377 | *data = std::string(blob.begin(), blob.end()); |
| 378 | return true; |
| 379 | } |
| 380 | |
Zentaro Kavanagh | 04eb2b0 | 2018-10-25 18:21:22 -0700 | [diff] [blame] | 381 | } // namespace tpmcrypto |