1 //
2 // Copyright (C) 2020-2023 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 #include "host/commands/secure_env/storage/tpm_storage.h"
17 
18 #include <android-base/logging.h>
19 #include <tss2/tss2_rc.h>
20 
21 #include "host/commands/secure_env/json_serializable.h"
22 #include "host/commands/secure_env/tpm_random_source.h"
23 
24 namespace cuttlefish {
25 namespace secure_env {
26 
27 static constexpr size_t MAX_HANDLE_ATTEMPTS = 1;
28 
29 static constexpr char kEntries[] = "entries";
30 static constexpr char kKey[] = "key";
31 static constexpr char kHandle[] = "handle";
32 
TpmStorage(TpmResourceManager & resource_manager,const std::string & index_file)33 TpmStorage::TpmStorage(TpmResourceManager& resource_manager, const std::string& index_file)
34     : resource_manager_(resource_manager), index_file_(index_file) {
35   index_ = ReadProtectedJsonFromFile(resource_manager_, index_file);
36   if (!index_.isMember(kEntries)
37       || index_[kEntries].type() != Json::arrayValue) {
38     if (index_.empty()) {
39       LOG(DEBUG) << "Initializing secure index file";
40     } else {
41       LOG(WARNING) << "Index file missing entries, likely corrupted.";
42     }
43     index_[kEntries] = Json::Value(Json::arrayValue);
44   } else {
45     LOG(DEBUG) << "Restoring index from file";
46   }
47 }
48 
Exists() const49 bool TpmStorage::Exists() const {
50   return ReadProtectedJsonFromFile(resource_manager_, index_file_).isMember(kEntries);
51 }
52 
HasKey(const std::string & key) const53 Result<bool> TpmStorage::HasKey(const std::string& key) const {
54   return CF_EXPECT(GetHandle(key)).has_value();
55 }
56 
Read(const std::string & key) const57 Result<ManagedStorageData> TpmStorage::Read(const std::string& key) const {
58   auto handle_optional = CF_EXPECT(GetHandle(key));
59   auto handle = CF_EXPECT(handle_optional.value());
60   auto close_tr = [this](ESYS_TR* handle) {
61     Esys_TR_Close(*resource_manager_.Esys(), handle);
62     delete handle;
63   };
64   std::unique_ptr<ESYS_TR, decltype(close_tr)> nv_handle(new ESYS_TR, close_tr);
65   auto rc = Esys_TR_FromTPMPublic(
66       /* esysContext */ *resource_manager_.Esys(),
67       /* tpm_handle */ handle,
68       /* optionalSession1 */ ESYS_TR_NONE,
69       /* optionalSession2 */ ESYS_TR_NONE,
70       /* optionalSession3 */ ESYS_TR_NONE,
71       /* object */ nv_handle.get());
72   CF_EXPECTF(rc == TPM2_RC_SUCCESS, "Esys_TR_FromTPMPublic failed: {}: {}",
73              rc, Tss2_RC_Decode(rc));
74 
75   TPM2B_AUTH auth = { .size = 0, .buffer = {} };
76   Esys_TR_SetAuth(*resource_manager_.Esys(), *nv_handle, &auth);
77 
78   TPM2B_NV_PUBLIC* public_area;
79   rc = Esys_NV_ReadPublic(
80       /* esysContext */ *resource_manager_.Esys(),
81       /* nvIndex */ *nv_handle,
82       /* shandle1 */ ESYS_TR_NONE,
83       /* shandle2 */ ESYS_TR_NONE,
84       /* shandle3 */ ESYS_TR_NONE,
85       /* nvPublic */ &public_area,
86       /* nvName */ nullptr);
87   CF_EXPECTF(rc == TPM2_RC_SUCCESS && public_area != nullptr,
88              "Esys_NV_ReadPublic failed: {}: {}", rc, Tss2_RC_Decode(rc));
89 
90   std::unique_ptr<TPM2B_NV_PUBLIC, decltype(Esys_Free)*> public_deleter(public_area, Esys_Free);
91   TPM2B_MAX_NV_BUFFER* buffer = nullptr;
92   rc = Esys_NV_Read(
93       /* esysContext */ *resource_manager_.Esys(),
94       /* authHandle */ *nv_handle,
95       /* nvIndex */ *nv_handle,
96       /* shandle1 */ ESYS_TR_PASSWORD,
97       /* shandle2 */ ESYS_TR_NONE,
98       /* shandle3 */ ESYS_TR_NONE,
99       /* size */ public_area->nvPublic.dataSize,
100       /* offset */ 0,
101       /* data */ &buffer);
102   CF_EXPECTF(rc == TSS2_RC_SUCCESS && buffer != nullptr,
103              "Esys_NV_Read failed with return code {} ({})", rc, Tss2_RC_Decode(rc));
104 
105   auto data = CF_EXPECT(CreateStorageData(buffer->size));
106   std::memcpy(data->payload, buffer->buffer, buffer->size);
107 
108   return data;
109 }
110 
Write(const std::string & key,const StorageData & data)111 Result<void> TpmStorage::Write(const std::string& key, const StorageData& data) {
112   if (!CF_EXPECT(HasKey(key))) {
113     CF_EXPECT(Allocate(key, data.size));
114   }
115   auto handle_optional = CF_EXPECT(GetHandle(key));
116   auto handle = CF_EXPECT(handle_optional.value());
117   ESYS_TR nv_handle;
118   auto rc = Esys_TR_FromTPMPublic(
119       /* esysContext */ *resource_manager_.Esys(),
120       /* tpm_handle */ handle,
121       /* optionalSession1 */ ESYS_TR_NONE,
122       /* optionalSession2 */ ESYS_TR_NONE,
123       /* optionalSession3 */ ESYS_TR_NONE,
124       /* object */ &nv_handle);
125   CF_EXPECTF(rc == TPM2_RC_SUCCESS, "Esys_TR_FromTPMPublic failed: {}: {}",
126              rc, Tss2_RC_Decode(rc));
127 
128   TPM2B_AUTH auth = { .size = 0, .buffer = {} };
129   Esys_TR_SetAuth(*resource_manager_.Esys(), nv_handle, &auth);
130 
131   TPM2B_MAX_NV_BUFFER buffer;
132   buffer.size = data.size;
133   std::memcpy(buffer.buffer, data.payload, data.size);
134 
135   rc = Esys_NV_Write(
136       /* esysContext */ *resource_manager_.Esys(),
137       /* authHandle */ nv_handle,
138       /* nvIndex */ nv_handle,
139       /* shandle1 */ ESYS_TR_PASSWORD,
140       /* shandle2 */ ESYS_TR_NONE,
141       /* shandle3 */ ESYS_TR_NONE,
142       /* data */ &buffer,
143       /* offset */ 0);
144   Esys_TR_Close(*resource_manager_.Esys(), &nv_handle);
145   CF_EXPECTF(rc == TSS2_RC_SUCCESS, "Esys_NV_Write failed with return code {} ({})",
146              rc, Tss2_RC_Decode(rc));
147 
148   return {};
149 }
150 
GenerateRandomHandle()151 TPM2_HANDLE TpmStorage::GenerateRandomHandle() {
152   TpmRandomSource random_source{resource_manager_};
153   TPM2_HANDLE handle = 0;
154   random_source.GenerateRandom(reinterpret_cast<uint8_t*>(&handle), sizeof(handle));
155   if (handle == 0) {
156     LOG(WARNING) << "TPM randomness failed. Falling back to software RNG.";
157     handle = rand();
158   }
159   handle = handle % (TPM2_NV_INDEX_LAST + 1 - TPM2_NV_INDEX_FIRST);
160   handle += TPM2_NV_INDEX_FIRST;
161   return handle;
162 }
163 
GetHandle(const std::string & key) const164 Result<std::optional<TPM2_HANDLE>> TpmStorage::GetHandle(const std::string& key) const {
165   for (const auto& entry : index_[kEntries]) {
166     CF_EXPECT(entry.isMember(kKey), "Index was corrupted");
167     if (entry[kKey] != key) {
168       continue;
169     }
170     CF_EXPECT(entry.isMember(kHandle), "Index was corrupted");
171     return entry[kHandle].asUInt();
172   }
173   return std::nullopt;
174 }
175 
Allocate(const std::string & key,uint16_t size)176 Result<void> TpmStorage::Allocate(const std::string& key, uint16_t size) {
177   auto has_key = CF_EXPECT(HasKey(key));
178   CF_EXPECT(!has_key, "Key: " << key << " is already defined");
179 
180   TPM2_HANDLE handle;
181   for (int i = 0; i < MAX_HANDLE_ATTEMPTS; i++) {
182     handle = GenerateRandomHandle();
183     TPM2B_NV_PUBLIC public_info = {
184       .size = 0,
185       .nvPublic = {
186         .nvIndex = handle,
187         .nameAlg = TPM2_ALG_SHA1,
188         .attributes = TPMA_NV_AUTHWRITE | TPMA_NV_AUTHREAD,
189         .authPolicy = { .size = 0, .buffer = {} },
190         .dataSize = size,
191       }
192     };
193     TPM2B_AUTH auth = { .size = 0, .buffer = {} };
194     Esys_TR_SetAuth(*resource_manager_.Esys(), ESYS_TR_RH_OWNER, &auth);
195     ESYS_TR nv_handle;
196     auto rc = Esys_NV_DefineSpace(
197         /* esysContext */ *resource_manager_.Esys(),
198         /* authHandle */ ESYS_TR_RH_OWNER,
199         /* shandle1 */ ESYS_TR_PASSWORD,
200         /* shandle2 */ ESYS_TR_NONE,
201         /* shandle3 */ ESYS_TR_NONE,
202         /* auth */ &auth,
203         /* publicInfo */ &public_info,
204         /* nvHandle */ &nv_handle);
205     if (rc == TPM2_RC_NV_DEFINED) {
206       LOG(VERBOSE) << "Esys_NV_DefineSpace failed with TPM2_RC_NV_DEFINED";
207       continue;
208     } else if (rc == TPM2_RC_SUCCESS) {
209       Esys_TR_Close(*resource_manager_.Esys(), &nv_handle);
210       break;
211     } else {
212       LOG(DEBUG) << "Esys_NV_DefineSpace failed with " << rc << ": "
213                  << Tss2_RC_Decode(rc);
214     }
215   }
216   Json::Value entry(Json::objectValue);
217   entry[kKey] = key;
218   entry[kHandle] = handle;
219   index_[kEntries].append(entry);
220 
221   CF_EXPECT(WriteProtectedJsonToFile(resource_manager_, index_file_, index_),
222             "Failed to save changes to " << index_file_);
223 
224   return {};
225 }
226 
227 }  // namespace secure_env
228 }  // namespace cuttlefish
229