1 /*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "base_key.h"
17
18 #include <fcntl.h>
19 #include <fstream>
20 #include <string>
21 #include <unistd.h>
22
23 #include "directory_ex.h"
24 #include "file_ex.h"
25 #include "huks_master.h"
26 #include "libfscrypt/key_control.h"
27 #include "storage_service_log.h"
28 #include "string_ex.h"
29 #include "utils/file_utils.h"
30 #include "utils/string_utils.h"
31
32 namespace {
33 const std::string PATH_LATEST_BACKUP = "/latest_bak";
34 const std::string PATH_KEY_VERSION = "/version_";
35 const std::string PATH_KEY_TEMP = "/temp";
36 }
37
38 namespace OHOS {
39 namespace StorageDaemon {
BaseKey(const std::string & dir,uint8_t keyLen)40 BaseKey::BaseKey(const std::string &dir, uint8_t keyLen) : dir_(dir), keyLen_(keyLen)
41 {
42 }
43
InitKey()44 bool BaseKey::InitKey()
45 {
46 LOGD("enter");
47 if (keyInfo_.version == FSCRYPT_INVALID || keyInfo_.version > KeyCtrlGetFscryptVersion(MNT_DATA.c_str())) {
48 LOGE("invalid version %{public}u", keyInfo_.version);
49 return false;
50 }
51 if (!keyInfo_.key.IsEmpty()) {
52 LOGE("key is not empty");
53 return false;
54 }
55 if (!GenerateKeyBlob(keyInfo_.key, keyLen_)) {
56 LOGE("GenerateKeyBlob raw key failed");
57 return false;
58 }
59 return true;
60 }
61
GenerateKeyBlob(KeyBlob & blob,const uint32_t size)62 bool BaseKey::GenerateKeyBlob(KeyBlob &blob, const uint32_t size)
63 {
64 blob = HuksMaster::GetInstance().GenerateRandomKey(size);
65 return !blob.IsEmpty();
66 }
67
SaveKeyBlob(const KeyBlob & blob,const std::string & path)68 bool BaseKey::SaveKeyBlob(const KeyBlob &blob, const std::string &path)
69 {
70 if (blob.IsEmpty()) {
71 return false;
72 }
73 LOGD("enter %{public}s, size=%{public}d", path.c_str(), blob.size);
74 return WriteFileSync(path.c_str(), blob.data.get(), blob.size);
75 }
76
GenerateAndSaveKeyBlob(KeyBlob & blob,const std::string & path,const uint32_t size)77 bool BaseKey::GenerateAndSaveKeyBlob(KeyBlob &blob, const std::string &path, const uint32_t size)
78 {
79 if (!GenerateKeyBlob(blob, size)) {
80 return false;
81 }
82 return SaveKeyBlob(blob, path);
83 }
84
LoadKeyBlob(KeyBlob & blob,const std::string & path,const uint32_t size=0)85 bool BaseKey::LoadKeyBlob(KeyBlob &blob, const std::string &path, const uint32_t size = 0)
86 {
87 LOGD("enter %{public}s, size=%{public}d", path.c_str(), size);
88 std::ifstream file(path, std::ios::binary);
89 if (file.fail()) {
90 LOGE("open %{public}s failed, errno %{public}d", path.c_str(), errno);
91 return false;
92 }
93
94 file.seekg(0, std::ios::end);
95 uint32_t length = static_cast<uint32_t>(file.tellg());
96 // zero size means use the file length.
97 if ((size != 0) && (length != size)) {
98 LOGE("file:%{public}s size error, real len %{public}d not expected %{public}d", path.c_str(), length, size);
99 return false;
100 }
101 if (!blob.Alloc(length)) {
102 return false;
103 }
104
105 file.seekg(0, std::ios::beg);
106 if (file.read(reinterpret_cast<char *>(blob.data.get()), length).fail()) {
107 LOGE("read %{public}s failed, errno %{public}d", path.c_str(), errno);
108 return false;
109 }
110 return true;
111 }
112
GetCandidateVersion() const113 int BaseKey::GetCandidateVersion() const
114 {
115 auto prefix = PATH_KEY_VERSION.substr(1); // skip the first slash
116 std::vector<std::string> files;
117 GetSubDirs(dir_, files);
118 int candidate = -1;
119 for (const auto &it: files) {
120 if (it.rfind(prefix) == 0) {
121 std::string str = it.substr(prefix.length());
122 int ver;
123 if (IsNumericStr(str) && StrToInt(str, ver) && ver >= candidate) {
124 candidate = ver;
125 }
126 }
127 }
128 LOGD("candidate key version is %{public}d", candidate);
129 return candidate;
130 }
131
132 // Get last version_xx dir to load key files.
GetCandidateDir() const133 std::string BaseKey::GetCandidateDir() const
134 {
135 auto candidate = GetCandidateVersion();
136 // candidate is -1 means no version_xx dir.
137 if (candidate == -1) {
138 return "";
139 }
140
141 return dir_ + PATH_KEY_VERSION + std::to_string(candidate);
142 }
143
144 // Get next available version_xx dir to save key files.
GetNextCandidateDir() const145 std::string BaseKey::GetNextCandidateDir() const
146 {
147 auto candidate = GetCandidateVersion();
148 return dir_ + PATH_KEY_VERSION + std::to_string(candidate + 1);
149 }
150
StoreKey(const UserAuth & auth)151 bool BaseKey::StoreKey(const UserAuth &auth)
152 {
153 LOGD("enter");
154 auto pathTemp = dir_ + PATH_KEY_TEMP;
155 if (DoStoreKey(auth)) {
156 // rename keypath/temp/ to keypath/version_xx/
157 auto candidate = GetNextCandidateDir();
158 LOGD("rename %{public}s to %{public}s", pathTemp.c_str(), candidate.c_str());
159 if (rename(pathTemp.c_str(), candidate.c_str()) == 0) {
160 SyncKeyDir();
161 return true;
162 }
163 LOGE("rename fail return %{public}d, cleanup the temp dir", errno);
164 } else {
165 LOGE("DoStoreKey fail, cleanup the temp dir");
166 }
167 OHOS::ForceRemoveDirectory(pathTemp);
168 SyncKeyDir();
169 return false;
170 }
171
172 // All key files are saved under keypath/temp/ in this function.
DoStoreKey(const UserAuth & auth)173 bool BaseKey::DoStoreKey(const UserAuth &auth)
174 {
175 auto pathTemp = dir_ + PATH_KEY_TEMP;
176 MkDirRecurse(pathTemp, S_IRWXU);
177
178 auto pathVersion = dir_ + PATH_FSCRYPT_VER;
179 std::string version;
180 if (OHOS::LoadStringFromFile(pathVersion, version)) {
181 if (version != std::to_string(keyInfo_.version)) {
182 LOGE("version already exist %{public}s, not expected %{public}d", version.c_str(), keyInfo_.version);
183 return false;
184 }
185 } else if (SaveStringToFileSync(pathVersion, std::to_string(keyInfo_.version)) == false) {
186 LOGE("save version failed, errno:%{public}d", errno);
187 return false;
188 }
189 ChMod(pathVersion, S_IREAD | S_IWRITE);
190
191 if (!HuksMaster::GetInstance().GenerateKey(auth, keyContext_.shield)) {
192 LOGE("GenerateKey of shield failed");
193 return false;
194 }
195 if (!SaveKeyBlob(keyContext_.shield, pathTemp + PATH_SHIELD)) {
196 return false;
197 }
198 if (!GenerateAndSaveKeyBlob(keyContext_.secDiscard, pathTemp + PATH_SECDISC, CRYPTO_KEY_SECDISC_SIZE)) {
199 LOGE("GenerateAndSaveKeyBlob sec_discard failed");
200 return false;
201 }
202 if (!Encrypt(auth)) {
203 return false;
204 }
205 if (!SaveKeyBlob(keyContext_.encrypted, pathTemp + PATH_ENCRYPTED)) {
206 return false;
207 }
208 keyContext_.encrypted.Clear();
209 LOGD("finish");
210 return true;
211 }
212
213 // update the latest and do cleanups.
UpdateKey(const std::string & keypath)214 bool BaseKey::UpdateKey(const std::string &keypath)
215 {
216 LOGD("enter");
217 auto candidate = keypath.empty() ? GetCandidateDir() : keypath;
218 if (candidate.empty()) {
219 LOGE("no candidate dir");
220 return false;
221 }
222
223 // backup the latest
224 std::string pathLatest = dir_ + PATH_LATEST;
225 std::string pathLatestBak = dir_ + PATH_LATEST_BACKUP;
226 bool hasLatest = IsDir(dir_ + PATH_LATEST);
227 if (hasLatest) {
228 OHOS::ForceRemoveDirectory(pathLatestBak);
229 if (rename(pathLatest.c_str(),
230 pathLatestBak.c_str()) != 0) {
231 LOGE("backup the latest fail errno:%{public}d", errno);
232 }
233 LOGD("backup the latest success");
234 }
235
236 // rename {candidate} to latest
237 OHOS::ForceRemoveDirectory(dir_ + PATH_LATEST);
238 if (rename(candidate.c_str(), pathLatest.c_str()) != 0) {
239 LOGE("rename candidate to latest fail return %{public}d", errno);
240 if (hasLatest) {
241 // revert from the backup
242 if (rename(pathLatestBak.c_str(),
243 pathLatest.c_str()) != 0) {
244 LOGE("restore the latest_backup fail errno:%{public}d", errno);
245 } else {
246 LOGI("restore the latest_backup success");
247 }
248 }
249 SyncKeyDir();
250 return false;
251 }
252 LOGD("rename candidate %{public}s to latest success", candidate.c_str());
253
254 // cleanup backup and other versions
255 std::vector<std::string> files;
256 GetSubDirs(dir_, files);
257 for (const auto &it: files) {
258 if (it != PATH_LATEST.substr(1)) {
259 OHOS::ForceRemoveDirectory(dir_ + "/" + it);
260 }
261 }
262
263 SyncKeyDir();
264 return true;
265 }
266
Encrypt(const UserAuth & auth)267 bool BaseKey::Encrypt(const UserAuth &auth)
268 {
269 LOGD("enter");
270 auto ret = HuksMaster::GetInstance().EncryptKey(keyContext_, auth, keyInfo_);
271 keyContext_.shield.Clear();
272 keyContext_.secDiscard.Clear();
273 keyContext_.nonce.Clear();
274 keyContext_.aad.Clear();
275 LOGD("finish");
276 return ret;
277 }
278
RestoreKey(const UserAuth & auth)279 bool BaseKey::RestoreKey(const UserAuth &auth)
280 {
281 LOGD("enter");
282 auto candidate = GetCandidateDir();
283 if (candidate.empty()) {
284 // no candidate dir, just restore from the latest
285 return DoRestoreKey(auth, dir_ + PATH_LATEST);
286 }
287
288 if (DoRestoreKey(auth, candidate)) {
289 // update the latest with the candidate
290 UpdateKey();
291 return true;
292 }
293
294 LOGE("DoRestoreKey with %{public}s failed", candidate.c_str());
295 // try to restore from other versions
296 std::vector<std::string> files;
297 GetSubDirs(dir_, files);
298 std::sort(files.begin(), files.end(), [&](const std::string &a, const std::string &b) {
299 if (a.length() != b.length() ||
300 a.length() < PATH_KEY_VERSION.length() ||
301 b.length() < PATH_KEY_VERSION.length()) {
302 return a.length() > b.length();
303 }
304 // make sure a.length() >= PATH_KEY_VERSION.length() && b.length() >= PATH_KEY_VERSION.length()
305 return std::stoi(a.substr(PATH_KEY_VERSION.size() - 1)) > std::stoi(b.substr(PATH_KEY_VERSION.size() - 1));
306 });
307 for (const auto &it: files) {
308 if (it != candidate) {
309 if (DoRestoreKey(auth, dir_ + "/" + it)) {
310 UpdateKey(it);
311 return true;
312 }
313 }
314 }
315 return false;
316 }
317
DoRestoreKey(const UserAuth & auth,const std::string & path)318 bool BaseKey::DoRestoreKey(const UserAuth &auth, const std::string &path)
319 {
320 LOGD("enter, path = %{public}s", path.c_str());
321 auto ver = KeyCtrlLoadVersion(dir_.c_str());
322 if (ver == FSCRYPT_INVALID || ver != keyInfo_.version) {
323 LOGE("RestoreKey fail. bad version loaded %{public}u not expected %{public}u", ver, keyInfo_.version);
324 return false;
325 }
326
327 if (!LoadKeyBlob(keyContext_.encrypted, path + PATH_ENCRYPTED)) {
328 return false;
329 }
330 if (!LoadKeyBlob(keyContext_.shield, path + PATH_SHIELD)) {
331 keyContext_.encrypted.Clear();
332 return false;
333 }
334 if (!LoadKeyBlob(keyContext_.secDiscard, path + PATH_SECDISC, CRYPTO_KEY_SECDISC_SIZE)) {
335 keyContext_.encrypted.Clear();
336 keyContext_.shield.Clear();
337 return false;
338 }
339 return Decrypt(auth);
340 }
341
Decrypt(const UserAuth & auth)342 bool BaseKey::Decrypt(const UserAuth &auth)
343 {
344 auto ret = HuksMaster::GetInstance().DecryptKey(keyContext_, auth, keyInfo_);
345 keyContext_.encrypted.Clear();
346 keyContext_.shield.Clear();
347 keyContext_.secDiscard.Clear();
348 keyContext_.nonce.Clear();
349 keyContext_.aad.Clear();
350 return ret;
351 }
352
ClearKey(const std::string & mnt)353 bool BaseKey::ClearKey(const std::string &mnt)
354 {
355 LOGD("enter, dir_ = %{public}s", dir_.c_str());
356 InactiveKey(USER_DESTROY, mnt);
357 keyInfo_.key.Clear();
358
359 return OHOS::ForceRemoveDirectory(dir_);
360 // use F2FS_IOC_SEC_TRIM_FILE
361 }
362
SyncKeyDir() const363 void BaseKey::SyncKeyDir() const
364 {
365 int fd = open(dir_.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC);
366 if (fd < 0) {
367 LOGE("open %{private}s failed, errno %{public}d", dir_.c_str(), errno);
368 sync();
369 return;
370 }
371 if (syncfs(fd) != 0) {
372 LOGE("syncfs %{private}s failed, errno %{public}d", dir_.c_str(), errno);
373 sync();
374 }
375 (void)close(fd);
376 }
377 } // namespace StorageDaemon
378 } // namespace OHOS
379