1 /*
2 * Copyright (c) 2022-2023 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 "openssl_crypto.h"
28 #include "storage_service_log.h"
29 #include "string_ex.h"
30 #include "utils/file_utils.h"
31 #include "utils/string_utils.h"
32
33 namespace {
34 const std::string PATH_LATEST_BACKUP = "/latest_bak";
35 const std::string PATH_KEY_VERSION = "/version_";
36 const std::string PATH_KEY_TEMP = "/temp";
37
38 #ifndef F2FS_IOCTL_MAGIC
39 #define F2FS_IOCTL_MAGIC 0xf5
40 #endif
41
42 #ifndef F2FS_IOC_SEC_TRIM_FILE
43 struct F2fsSectrimRange {
44 uint64_t start;
45 uint64_t len;
46 uint64_t flags;
47 };
48 using F2fsSectrim = F2fsSectrimRange;
49 #define F2FS_IOC_SEC_TRIM_FILE _IOW(F2FS_IOCTL_MAGIC, 20, F2fsSectrim)
50 #define F2FS_TRIM_FILE_DISCARD 0x1
51 #define F2FS_TRIM_FILE_ZEROOUT 0x2
52 #endif
53 #ifndef F2FS_IOC_SET_PIN_FILE
54 #define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, set)
55 #define F2FS_IOC_GET_PIN_FILE _IOR(F2FS_IOCTL_MAGIC, 14, set)
56 #endif
57 }
58
59 namespace OHOS {
60 namespace StorageDaemon {
BaseKey(const std::string & dir,uint8_t keyLen)61 BaseKey::BaseKey(const std::string &dir, uint8_t keyLen) : dir_(dir), keyLen_(keyLen)
62 {
63 }
64
InitKey()65 bool BaseKey::InitKey()
66 {
67 LOGD("enter");
68 if (keyInfo_.version == FSCRYPT_INVALID || keyInfo_.version > KeyCtrlGetFscryptVersion(MNT_DATA.c_str())) {
69 LOGE("invalid version %{public}u", keyInfo_.version);
70 return false;
71 }
72 if (!keyInfo_.key.IsEmpty()) {
73 LOGE("key is not empty");
74 return false;
75 }
76 if (!GenerateKeyBlob(keyInfo_.key, keyLen_)) {
77 LOGE("GenerateKeyBlob raw key failed");
78 return false;
79 }
80 return true;
81 }
82
GenerateKeyBlob(KeyBlob & blob,const uint32_t size)83 bool BaseKey::GenerateKeyBlob(KeyBlob &blob, const uint32_t size)
84 {
85 blob = HuksMaster::GenerateRandomKey(size);
86 return !blob.IsEmpty();
87 }
88
SaveKeyBlob(const KeyBlob & blob,const std::string & path)89 bool BaseKey::SaveKeyBlob(const KeyBlob &blob, const std::string &path)
90 {
91 if (blob.IsEmpty()) {
92 return false;
93 }
94 LOGD("enter %{public}s, size=%{public}d", path.c_str(), blob.size);
95 return WriteFileSync(path.c_str(), blob.data.get(), blob.size);
96 }
97
GenerateAndSaveKeyBlob(KeyBlob & blob,const std::string & path,const uint32_t size)98 bool BaseKey::GenerateAndSaveKeyBlob(KeyBlob &blob, const std::string &path, const uint32_t size)
99 {
100 if (!GenerateKeyBlob(blob, size)) {
101 return false;
102 }
103 return SaveKeyBlob(blob, path);
104 }
105
LoadKeyBlob(KeyBlob & blob,const std::string & path,const uint32_t size=0)106 bool BaseKey::LoadKeyBlob(KeyBlob &blob, const std::string &path, const uint32_t size = 0)
107 {
108 LOGD("enter %{public}s, size=%{public}d", path.c_str(), size);
109 std::ifstream file(path, std::ios::binary);
110 if (file.fail()) {
111 LOGE("open %{public}s failed, errno %{public}d", path.c_str(), errno);
112 return false;
113 }
114
115 file.seekg(0, std::ios::end);
116 uint32_t length = static_cast<uint32_t>(file.tellg());
117 // zero size means use the file length.
118 if ((size != 0) && (length != size)) {
119 LOGE("file:%{public}s size error, real len %{public}d not expected %{public}d", path.c_str(), length, size);
120 return false;
121 }
122 if (!blob.Alloc(length)) {
123 return false;
124 }
125
126 file.seekg(0, std::ios::beg);
127 if (file.read(reinterpret_cast<char *>(blob.data.get()), length).fail()) {
128 LOGE("read %{public}s failed, errno %{public}d", path.c_str(), errno);
129 return false;
130 }
131 return true;
132 }
133
GetCandidateVersion() const134 int BaseKey::GetCandidateVersion() const
135 {
136 auto prefix = PATH_KEY_VERSION.substr(1); // skip the first slash
137 std::vector<std::string> files;
138 GetSubDirs(dir_, files);
139 int candidate = -1;
140 for (const auto &it: files) {
141 if (it.rfind(prefix) == 0) {
142 std::string str = it.substr(prefix.length());
143 int ver;
144 if (IsNumericStr(str) && StrToInt(str, ver) && ver >= candidate) {
145 candidate = ver;
146 }
147 }
148 }
149 LOGD("candidate key version is %{public}d", candidate);
150 return candidate;
151 }
152
153 // Get last version_xx dir to load key files.
GetCandidateDir() const154 std::string BaseKey::GetCandidateDir() const
155 {
156 auto candidate = GetCandidateVersion();
157 // candidate is -1 means no version_xx dir.
158 if (candidate == -1) {
159 return "";
160 }
161
162 return dir_ + PATH_KEY_VERSION + std::to_string(candidate);
163 }
164
165 // Get next available version_xx dir to save key files.
GetNextCandidateDir() const166 std::string BaseKey::GetNextCandidateDir() const
167 {
168 auto candidate = GetCandidateVersion();
169 return dir_ + PATH_KEY_VERSION + std::to_string(candidate + 1);
170 }
171
172 #ifdef USER_CRYPTO_MIGRATE_KEY
StoreKey(const UserAuth & auth,bool needGenerateShield)173 bool BaseKey::StoreKey(const UserAuth &auth, bool needGenerateShield)
174 #else
175 bool BaseKey::StoreKey(const UserAuth &auth)
176 #endif
177 {
178 LOGD("enter");
179 auto pathTemp = dir_ + PATH_KEY_TEMP;
180 #ifdef USER_CRYPTO_MIGRATE_KEY
181 if (DoStoreKey(auth, needGenerateShield)) {
182 #else
183 if (DoStoreKey(auth)) {
184 #endif
185 // rename keypath/temp/ to keypath/version_xx/
186 auto candidate = GetNextCandidateDir();
187 LOGD("rename %{public}s to %{public}s", pathTemp.c_str(), candidate.c_str());
188 if (rename(pathTemp.c_str(), candidate.c_str()) == 0) {
189 SyncKeyDir();
190 return true;
191 }
192 LOGE("rename fail return %{public}d, cleanup the temp dir", errno);
193 } else {
194 LOGE("DoStoreKey fail, cleanup the temp dir");
195 }
196 OHOS::ForceRemoveDirectory(pathTemp);
197 SyncKeyDir();
198 return false;
199 }
200
201 // All key files are saved under keypath/temp/ in this function.
202 #ifdef USER_CRYPTO_MIGRATE_KEY
203 bool BaseKey::DoStoreKey(const UserAuth &auth, bool needGenerateShield)
204 #else
205 bool BaseKey::DoStoreKey(const UserAuth &auth)
206 #endif
207 {
208 auto pathTemp = dir_ + PATH_KEY_TEMP;
209 MkDirRecurse(pathTemp, S_IRWXU);
210 const std::string NEED_UPDATE_PATH = pathTemp + SUFFIX_NEED_UPDATE;
211 if (!CheckAndUpdateVersion()) {
212 return false;
213 }
214 if ((auth.secret.IsEmpty()) && (!LoadAndSaveShield(auth, pathTemp, needGenerateShield))) {
215 return false;
216 }
217 if (!GenerateAndSaveKeyBlob(keyContext_.secDiscard, pathTemp + PATH_SECDISC, CRYPTO_KEY_SECDISC_SIZE)) {
218 LOGE("GenerateAndSaveKeyBlob sec_discard failed");
219 return false;
220 }
221 if (!Encrypt(auth)) {
222 return false;
223 }
224 if (!SaveKeyBlob(keyContext_.encrypted, pathTemp + PATH_ENCRYPTED)) {
225 return false;
226 }
227 if (!SaveStringToFile(NEED_UPDATE_PATH, KeyEncryptTypeToString(keyEncryptType_))) {
228 LOGE("make file fali");
229 return false;
230 }
231 keyContext_.encrypted.Clear();
232 LOGD("finish");
233 return true;
234 }
235
236 bool BaseKey::CheckAndUpdateVersion()
237 {
238 auto pathVersion = dir_ + PATH_FSCRYPT_VER;
239 std::string version;
240 if (OHOS::LoadStringFromFile(pathVersion, version)) {
241 if (version != std::to_string(keyInfo_.version)) {
242 LOGE("version already exist %{public}s, not expected %{public}d", version.c_str(), keyInfo_.version);
243 return false;
244 }
245 } else if (SaveStringToFileSync(pathVersion, std::to_string(keyInfo_.version)) == false) {
246 LOGE("save version failed, errno:%{public}d", errno);
247 return false;
248 }
249 ChMod(pathVersion, S_IREAD | S_IWRITE);
250 return true;
251 }
252
253 bool BaseKey::LoadAndSaveShield(const UserAuth &auth, const std::string &pathTemp, bool needGenerateShield)
254 {
255 #ifdef USER_CRYPTO_MIGRATE_KEY
256 if (needGenerateShield) {
257 if (!HuksMaster::GetInstance().GenerateKey(auth, keyContext_.shield)) {
258 LOGE("GenerateKey of shield failed");
259 return false;
260 }
261 } else {
262 if (!LoadKeyBlob(keyContext_.shield, dir_ + PATH_LATEST + PATH_SHIELD)) {
263 keyContext_.encrypted.Clear();
264 return false;
265 }
266 }
267 #else
268 if (!HuksMaster::GetInstance().GenerateKey(auth, keyContext_.shield)) {
269 LOGE("GenerateKey of shield failed");
270 return false;
271 }
272 #endif
273 if (!SaveKeyBlob(keyContext_.shield, pathTemp + PATH_SHIELD)) {
274 return false;
275 }
276 return true;
277 }
278
279 // update the latest and do cleanups.
280 bool BaseKey::UpdateKey(const std::string &keypath)
281 {
282 LOGD("enter");
283 auto candidate = keypath.empty() ? GetCandidateDir() : keypath;
284 if (candidate.empty()) {
285 LOGE("no candidate dir");
286 return false;
287 }
288
289 // backup the latest
290 std::string pathLatest = dir_ + PATH_LATEST;
291 std::string pathLatestBak = dir_ + PATH_LATEST_BACKUP;
292 bool hasLatest = IsDir(dir_ + PATH_LATEST);
293 if (hasLatest) {
294 OHOS::ForceRemoveDirectory(pathLatestBak);
295 if (rename(pathLatest.c_str(),
296 pathLatestBak.c_str()) != 0) {
297 LOGE("backup the latest fail errno:%{public}d", errno);
298 }
299 LOGD("backup the latest success");
300 }
301
302 // rename {candidate} to latest
303 OHOS::ForceRemoveDirectory(dir_ + PATH_LATEST);
304 if (rename(candidate.c_str(), pathLatest.c_str()) != 0) {
305 LOGE("rename candidate to latest fail return %{public}d", errno);
306 if (hasLatest) {
307 // revert from the backup
308 if (rename(pathLatestBak.c_str(),
309 pathLatest.c_str()) != 0) {
310 LOGE("restore the latest_backup fail errno:%{public}d", errno);
311 } else {
312 LOGI("restore the latest_backup success");
313 }
314 }
315 SyncKeyDir();
316 return false;
317 }
318 LOGD("rename candidate %{public}s to latest success", candidate.c_str());
319
320 // cleanup backup and other versions
321 std::vector<std::string> files;
322 GetSubDirs(dir_, files);
323 for (const auto &it: files) {
324 if (it != PATH_LATEST.substr(1)) {
325 OHOS::ForceRemoveDirectory(dir_ + "/" + it);
326 }
327 }
328
329 SyncKeyDir();
330 return true;
331 }
332
333 bool BaseKey::Encrypt(const UserAuth &auth)
334 {
335 LOGD("enter");
336 bool ret;
337 if (!auth.secret.IsEmpty()) {
338 LOGI("Enhanced encrypt start");
339 ret = OpensslCrypto::AESEncrypt(auth.secret, keyInfo_.key, keyContext_);
340 keyEncryptType_ = KeyEncryptType::KEY_CRYPT_OPENSSL;
341 } else {
342 LOGI("Huks encrypt start");
343 ret = HuksMaster::GetInstance().EncryptKey(keyContext_, auth, keyInfo_);
344 keyEncryptType_ = KeyEncryptType::KEY_CRYPT_HUKS;
345 }
346 keyContext_.shield.Clear();
347 keyContext_.secDiscard.Clear();
348 keyContext_.nonce.Clear();
349 keyContext_.aad.Clear();
350 LOGD("finish");
351 return ret;
352 }
353
354 bool BaseKey::RestoreKey(const UserAuth &auth)
355 {
356 LOGD("enter");
357 auto candidate = GetCandidateDir();
358 if (candidate.empty()) {
359 // no candidate dir, just restore from the latest
360 return DoRestoreKey(auth, dir_ + PATH_LATEST);
361 }
362
363 if (DoRestoreKey(auth, candidate)) {
364 // update the latest with the candidate
365 UpdateKey();
366 return true;
367 }
368
369 LOGE("DoRestoreKey with %{public}s failed", candidate.c_str());
370 // try to restore from other versions
371 std::vector<std::string> files;
372 GetSubDirs(dir_, files);
373 std::sort(files.begin(), files.end(), [&](const std::string &a, const std::string &b) {
374 if (a.length() != b.length() ||
375 a.length() < PATH_KEY_VERSION.length() ||
376 b.length() < PATH_KEY_VERSION.length()) {
377 return a.length() > b.length();
378 }
379 // make sure a.length() >= PATH_KEY_VERSION.length() && b.length() >= PATH_KEY_VERSION.length()
380 return std::stoi(a.substr(PATH_KEY_VERSION.size() - 1)) > std::stoi(b.substr(PATH_KEY_VERSION.size() - 1));
381 });
382 for (const auto &it: files) {
383 if (it != candidate) {
384 if (DoRestoreKey(auth, dir_ + "/" + it)) {
385 UpdateKey(it);
386 return true;
387 }
388 }
389 }
390 return false;
391 }
392
393 bool BaseKey::DoRestoreKey(const UserAuth &auth, const std::string &path)
394 {
395 LOGD("enter, path = %{public}s", path.c_str());
396 const std::string NEED_UPDATE_PATH = dir_ + PATH_LATEST + SUFFIX_NEED_UPDATE;
397 if (!auth.secret.IsEmpty() && FileExists(NEED_UPDATE_PATH)) {
398 keyEncryptType_ = KeyEncryptType::KEY_CRYPT_OPENSSL;
399 LOGI("set keyEncryptType_ as KEY_CRYPT_OPENSSL success");
400 } else if (auth.secret.IsEmpty() || (!auth.secret.IsEmpty() && !FileExists(NEED_UPDATE_PATH))) {
401 keyEncryptType_ = KeyEncryptType::KEY_CRYPT_HUKS;
402 LOGI("set keyEncryptType_ as KEY_CRYPT_HUKS success");
403 }
404 auto ver = KeyCtrlLoadVersion(dir_.c_str());
405 if (ver == FSCRYPT_INVALID || ver != keyInfo_.version) {
406 LOGE("RestoreKey fail. bad version loaded %{public}u not expected %{public}u", ver, keyInfo_.version);
407 return false;
408 }
409 if (!LoadKeyBlob(keyContext_.encrypted, path + PATH_ENCRYPTED)) {
410 return false;
411 }
412 if (keyEncryptType_ == KeyEncryptType::KEY_CRYPT_HUKS) {
413 if (!LoadKeyBlob(keyContext_.shield, path + PATH_SHIELD)) {
414 keyContext_.encrypted.Clear();
415 return false;
416 }
417 }
418 if (!LoadKeyBlob(keyContext_.secDiscard, path + PATH_SECDISC, CRYPTO_KEY_SECDISC_SIZE)) {
419 keyContext_.encrypted.Clear();
420 keyContext_.shield.Clear();
421 return false;
422 }
423 return Decrypt(auth);
424 }
425
426 bool BaseKey::Decrypt(const UserAuth &auth)
427 {
428 bool ret = false;
429 switch (keyEncryptType_) {
430 case KeyEncryptType::KEY_CRYPT_OPENSSL:
431 LOGI("Enhanced decrypt key start");
432 ret = OpensslCrypto::AESDecrypt(auth.secret, keyContext_, keyInfo_.key);
433 break;
434 case KeyEncryptType::KEY_CRYPT_HUKS:
435 LOGI("Huks decrypt key start");
436 ret = HuksMaster::GetInstance().DecryptKey(keyContext_, auth, keyInfo_);
437 break;
438 }
439 keyContext_.encrypted.Clear();
440 keyContext_.shield.Clear();
441 keyContext_.secDiscard.Clear();
442 keyContext_.nonce.Clear();
443 keyContext_.aad.Clear();
444 return ret;
445 }
446
447 bool BaseKey::ClearKey(const std::string &mnt)
448 {
449 LOGD("enter, dir_ = %{public}s", dir_.c_str());
450 InactiveKey(USER_DESTROY, mnt);
451 keyInfo_.key.Clear();
452 WipingActionDir(dir_);
453 return OHOS::ForceRemoveDirectory(dir_);
454 // use F2FS_IOC_SEC_TRIM_FILE
455 }
456
457 void BaseKey::WipingActionDir(std::string &path)
458 {
459 std::vector<std::string> fileList;
460 LOGI("WipingActionDir path.c_str() is %{public}s", path.c_str());
461 OpenSubFile(path.c_str(), fileList);
462 for (const auto &it: fileList) {
463 int fd = open(it.c_str(), O_WRONLY | O_CLOEXEC);
464 if (fd < 0) {
465 LOGE("open %{public}s failed, errno %{public}u", it.c_str(), errno);
466 return;
467 }
468 uint32_t set = 1;
469 int ret = ioctl(fd, F2FS_IOC_SET_PIN_FILE, &set);
470 if (ret != 0) {
471 LOGE("F2FS_IOC_SET_PIN_FILE ioctl is %{public}u, errno = %{public}u", ret, errno);
472 }
473 struct F2fsSectrimRange trimRange;
474 trimRange.start = 0;
475 trimRange.len = -1;
476 trimRange.flags = F2FS_TRIM_FILE_DISCARD | F2FS_TRIM_FILE_ZEROOUT;
477 ret = ioctl(fd, F2FS_IOC_SEC_TRIM_FILE, &trimRange);
478 if (ret != 0 && errno == EOPNOTSUPP) {
479 trimRange.flags = F2FS_TRIM_FILE_ZEROOUT;
480 ret = ioctl(fd, F2FS_IOC_SEC_TRIM_FILE, &trimRange);
481 if (ret != 0) {
482 LOGE("F2FS_IOC_SEC_TRIM_FILE ioctl is %{public}u, errno = %{public}u", ret, errno);
483 }
484 }
485 set = 0;
486 ret = ioctl(fd, F2FS_IOC_SET_PIN_FILE, &set);
487 if (ret != 0) {
488 LOGE("F2FS_IOC_SET_PIN_FILE ioctl is %{public}u", ret);
489 }
490 LOGI("WipingActionDir success");
491 close(fd);
492 }
493 }
494
495 void BaseKey::SyncKeyDir() const
496 {
497 int fd = open(dir_.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC);
498 if (fd < 0) {
499 LOGE("open %{private}s failed, errno %{public}d", dir_.c_str(), errno);
500 sync();
501 return;
502 }
503 if (syncfs(fd) != 0) {
504 LOGE("syncfs %{private}s failed, errno %{public}d", dir_.c_str(), errno);
505 sync();
506 }
507 (void)close(fd);
508 }
509
510 bool BaseKey::UpgradeKeys()
511 {
512 std::vector<std::string> versions;
513 GetSubDirs(dir_, versions);
514
515 for (const auto &it : versions) {
516 std::string shieldPath = dir_ + "/" + it + PATH_SHIELD;
517 LOGI("Upgrade of %{public}s", shieldPath.c_str());
518 LoadKeyBlob(keyContext_.shield, shieldPath);
519 if (HuksMaster::GetInstance().UpgradeKey(keyContext_)) {
520 LOGI("success upgrade of %{public}s", shieldPath.c_str());
521 SaveKeyBlob(keyContext_.shield, shieldPath);
522 SyncKeyDir();
523 }
524 }
525 return true;
526 }
527
528 std::string BaseKey::KeyEncryptTypeToString(KeyEncryptType keyEncryptType_) const
529 {
530 switch (keyEncryptType_) {
531 case KeyEncryptType::KEY_CRYPT_OPENSSL:
532 return "KEY_CRYPT_OPENSSL";
533 case KeyEncryptType::KEY_CRYPT_HUKS:
534 return "KEY_CRYPT_HUKS";
535 }
536 }
537 } // namespace StorageDaemon
538 } // namespace OHOS
539