Android FBE

Posted by Chen An on 2018-07-12

Android FBE

1. FBE 简介

  • 名称: FBE, File-Based Encryption,基于文件的加密
  • 凭据加密 (CE) 存储空间:这是默认存储位置,只有在用户解锁设备后才可用。设备加密 (DE) 存储空间:在直接启动模式期间以及用户解锁设备后均可用。
  • 开启 FBE 方式,在相关的 fstab 文件中添加相关的代码:
1
/dev/block/bootdevice/by-name/userdata                  /data              ext4    noatime,nosuid,nodev,barrier=1,noauto_da_alloc,discard wait,check,resize,**fileencryption=aes-256-xts**,quota
  • 一些概念性内容这里不再赘述,如有需要自行阅读 Goole FBE

2. FBE 流程分析

2.1 开机过程中,加密前的准备

  • init.rc 中加入相关的代码,用于根据 fstab 文件中进行相关的挂载操作
1
2
3
4
on fs
wait /dev/block/bootdevice
write /proc/sys/vm/swappiness 100
mount_all fstab.qcom
  • 看完 init.rc 中,当然是查看 init 进程中如何解析 init.rc ,代码在 system/core/init/builtins.cpp 中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const {
...
{"mount_all", {1, kMax, do_mount_all}},
...
}
static int do_mount_all(const std::vector<std::string>& args) {
...
/*mount_fstab 会 fork 出一个子进程调用 fs_mgr_read_fstab 以及 fs_mgr_mount_all 函数,前一个函数用于读取 fstab 文件,后者用于 mount,之后重点分析 fs_mgr_mount_all函数*/
int ret = mount_fstab(fstabfile, mount_mode);
...
if (queue_event) {
/* queue_fs_event will queue event based on mount_fstab return code
* and return processed return code*/
ret = queue_fs_event(ret);
}
}
  • 现在来看一下 fs_mgr_mount_all 函数,代码路径在system/core/fs_mgr/fs_mgr.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int fs_mgr_mount_all(struct fstab *fstab, int mount_mode){
int encryptable = FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE;
...
for (i = 0; i < fstab->num_entries; i++) {
......
int last_idx_inspected;
int top_idx = i;

mret = mount_with_alternatives(fstab, i, &last_idx_inspected, &attempted_idx);
i = last_idx_inspected;
mount_errno = errno;

/* Deal with encryptability. */
if (!mret) {
int status = handle_encryptable(&fstab->recs[attempted_idx]);

if (status == FS_MGR_MNTALL_FAIL) {
/* Fatal error - no point continuing */
return status;
}

if (status != FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) {
if (encryptable != FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) {
// Log and continue
LERROR << "Only one encryptable/encrypted partition supported";
}
//结果赋值给 encryptable
encryptable = status;
}

/* Success! Go get the next one */
continue;
}
}
......
/*此处返回给父进程,即 queue_fs_event 接收返回值进行之后的处理*/
if (error_count) {
return FS_MGR_MNTALL_FAIL;
} else {
return encryptable;
}
- [ ] }
  • 先来看一下 queue_fs_event 函数,它会根据 mount_fstab 的返回值结果进行不同的操作,而 mount_fstab 会返回 FS_MGR_MNTALL_DEV_FILE_ENCRYPTED 给 queue_fs_event,然后调用 e4crypt_install_keyring 函数用于安装 e4crypt keyring,这个用于存放文件加密的 key,之后设置相关的属性,然后触发 nonencrypted 这个 trigger 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
static int queue_fs_event(int code) {
int ret = code;
if (code == FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION) {
ActionManager::GetInstance().QueueEventTrigger("encrypt");
} else if (code == FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED) {
property_set("ro.crypto.state", "encrypted");
property_set("ro.crypto.type", "block");
ActionManager::GetInstance().QueueEventTrigger("defaultcrypto");
} else if (code == FS_MGR_MNTALL_DEV_NOT_ENCRYPTED) {
property_set("ro.crypto.state", "unencrypted");
ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
} else if (code == FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) {
property_set("ro.crypto.state", "unsupported");
ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
} else if (code == FS_MGR_MNTALL_DEV_NEEDS_RECOVERY) {
/* Setup a wipe via recovery, and reboot into recovery */
PLOG(ERROR) << "fs_mgr_mount_all suggested recovery, so wiping data via recovery.";
const std::vector<std::string> options = {"--wipe_data", "--reason=fs_mgr_mount_all" };
reboot_into_recovery(options);
return 0;
/* If reboot worked, there is no return. */
} else if (code == FS_MGR_MNTALL_DEV_FILE_ENCRYPTED) {
if (e4crypt_install_keyring()) {
return -1;
}
property_set("ro.crypto.state", "encrypted");
property_set("ro.crypto.type", "file");

// Although encrypted, we have device key, so we do not need to
// do anything different from the nonencrypted case.
ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
} else if (code == FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED) {
if (e4crypt_install_keyring()) {
return -1;
}
property_set("ro.crypto.state", "encrypted");
property_set("ro.crypto.type", "file");

// defaultcrypto detects file/block encryption. init flow is same for each.
ActionManager::GetInstance().QueueEventTrigger("defaultcrypto");
} else if (code == FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION) {
if (e4crypt_install_keyring()) {
return -1;
}
property_set("ro.crypto.type", "file");

// encrypt detects file/block encryption. init flow is same for each.
ActionManager::GetInstance().QueueEventTrigger("encrypt");
} else if (code > 0) {
PLOG(ERROR) << "fs_mgr_mount_all returned unexpected error " << code;
}
/* else ... < 0: error */

return ret;
}
  • 之前如果有了解过全盘加密的同学应该会很熟悉,全盘加密会返回 FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION ,然后触发一个 trigger ,最后在 init.rc 中执行 vdc 进程,最后启动 vold 进行全盘加密操作.那现在我们来看一下 FBE 中的 nonencrypted 这个 trigger 中做了什么操作,没看到有触发 vold 的操作啊,那文件加密是什么时候做的呢?不着急,我们继续往下看.
1
2
3
on nonencrypted
class_start main
class_start late_start
  • 我们知道,正常系统起来后,init 的执行顺序为 early-init,init,late-init.既然我们一下子无法知道 FBE 中 vold 是在什么时候执行的,那就只能一步步跟 init.rc,看下能否发现一些端倪,功夫不负有心人,终于在 init.rc 中发现了一点可能和 FBE 相关的东西. installkey /data 这个看着有点像.
1
2
3
4
5
6
7
8
9
10
on post-fs-data
# We chown/chmod /data again so because mount is run as root + defaults
chown system system /data
chmod 0771 /data
# We restorecon /data in case the userdata partition has been reset.
restorecon /data

# Make sure we have the device encryption key.
start vold
installkey /data
  • 类似 mount 的处理流程, installkey /data 最后会调用system/core/init/builtins.cpp 中的 do_installkey 函数,do_installkey 首先判断是否为文件加密方式,如果是文件加密方式,则会执行 vdc 命令,到这里终于开始进入加密流程过程了.
1
2
3
4
5
6
7
8
9
10
11
12
13
static int do_installkey(const std::vector<std::string>& args) {
if (!is_file_crypto()) {
return 0;
}
auto unencrypted_dir = args[1] + e4crypt_unencrypted_folder;
if (do_installkeys_ensure_dir_exists(unencrypted_dir.c_str())) {
PLOG(ERROR) << "Failed to create " << unencrypted_dir;
return -1;
}
std::vector<std::string> exec_args = {"exec", "/system/bin/vdc", "--wait", "cryptfs",
"enablefilecrypto"};
return do_exec(exec_args);
}

2.2 FBE 加密处理流程

  • 先看一下 vdc 代码中的处理流程,在 system/vold/vdc.cpp, 通过 local socket 实现了 vdc 通知 vold 进行之后的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
........
while ((sock = socket_local_client(sockname,
ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_STREAM)) < 0) {
if (!wait_for_socket) {
PLOG(ERROR) << "Error connecting to " << sockname;
exit(4);
} else {
usleep(10000);
}
}

if (!strcmp(argv[1], "monitor")) {
exit(do_monitor(sock, 0));
} else {
exit(do_cmd(sock, argc, argv));
}
........
if (TEMP_FAILURE_RETRY(write(sock, cmd.c_str(), cmd.length() + 1)) < 0) {
PLOG(ERROR) << "Failed to write command";
return errno;
}
  • 代码在 system/vold/CryptCommandListener.cpp,通过之前的命令执行到 e4crypt_initialize_global_de 函数,到这里终于进入了文件加密的核心部分了,该函数会生成 /data/unencrypted/key.unencrypted key用来设置/data下除了directories_to_exclude (system/extras/ext4_utils/ext4_crypt_init_extensions.cpp 中定义)目录的当前所有目录的policy,(更准确的说,用的是 /data/unencrypted/ref ,该文件是是key 的引用(),key 其实是存入到密钥环(keyring)当中去的 ).其实 FBE 下有三种类型的 key 用于整个 Android 系统,那这里只是生成的一把 key,之后的 key 是在哪里生成的呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
bool e4crypt_initialize_global_de() {
LOG(INFO) << "e4crypt_initialize_global_de";

if (s_global_de_initialized) {
LOG(INFO) << "Already initialized";
return true;
}

const char *contents_mode;
const char *filenames_mode;
cryptfs_get_file_encryption_modes(&contents_mode, &filenames_mode);
std::string modestring = std::string(contents_mode) + ":" + filenames_mode;

std::string mode_filename = std::string("/data") + e4crypt_key_mode;
if (!android::base::WriteStringToFile(modestring, mode_filename)) {
PLOG(ERROR) << "Cannot save type";
return false;
}

std::string device_key_ref;
//生成 /data/unencrypted/key这个文件夹 key 是怎样生成的 ,首先会读取 /dev/urandom 节点生成一个随机数,再通过 keymaster key 进行签名操作

//同时会生成 /data/uncrypt/key/version(版本信息) /data/uncrypt/key/encrypt_key(之前通过/dev/urandom 生成的 key 再通过 keymaster key 加密过的 key)
if (!android::vold::retrieveAndInstallKey(true,
device_key_path, device_key_temp, &device_key_ref)) return false;

std::string ref_filename = std::string("/data") + e4crypt_key_ref;
if (!android::base::WriteStringToFile(device_key_ref, ref_filename)) {
PLOG(ERROR) << "Cannot save key reference to:" << ref_filename;
return false;
}
LOG(INFO) << "Wrote system DE key reference to:" << ref_filename;

s_global_de_initialized = true;
return true;
}
  • 生成了 uncryptkey 之后, 分析 init.rc 看看之后做了什么操作,看了大部分都是 mkdir 操作啊.
1
2
3
4
5
6
7
8
9
10
11
# Start bootcharting as soon as possible after the data partition is
# mounted to collect more data.
mkdir /data/bootchart 0755 shell shell
bootchart start

# Avoid predictable entropy pool. Carry over entropy from previous boot.
copy /data/system/entropy.dat /dev/urandom

# create basic filesystem structure
mkdir /data/misc 01771 system misc
mkdir /data/misc/recovery 0770 system log
  • 也类似 mount_all ,mkdir 最终调用了 init/builtins.cpp 中的 do_mkdir 函数,关注 e4crypt_set_directory_policy 函数实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int do_mkdir(const std::vector<std::string>& args) {
......
//判断是否未文件加密方式,是则执行之后的流程
if (e4crypt_is_native()) {
if (e4crypt_set_directory_policy(args[1].c_str())) {
const std::vector<std::string> options = {
"--prompt_and_wipe_data",
"--reason=set_policy_failed:"s + args[1]};
reboot_into_recovery(options);
return 0;
}
}
return 0;
}
  • 设置相关目录的加密 policy,使用的 policy 便是上面生成的 /data/unencrypted/ref (ref 是 key 经过填充后 再经过 sha512 算法得到的东西). directories_to_exclude 指定的相关目录不会被加密,因为该部分相关的子目录需要加密 (加密 key 使用之后生成的 CE/DE)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int e4crypt_set_directory_policy(const char* dir)
{
if (!dir || strncmp(dir, "/data/", 6)) {
return 0;
}

// Special-case /data/media/obb per b/64566063
if (strcmp(dir, "/data/media/obb") == 0) {
// Try to set policy on this directory, but if it is non-empty this may fail.
set_system_de_policy_on(dir);
return 0;
}

// Only set policy on first level /data directories
// To make this less restrictive, consider using a policy file.
// However this is overkill for as long as the policy is simply
// to apply a global policy to all /data folders created via makedir
if (strchr(dir + 6, '/')) {
return 0;
}

// Special case various directories that must not be encrypted,
// often because their subdirectories must be encrypted.
// This isn't a nice way to do this, see b/26641735
std::vector<std::string> directories_to_exclude = {
"lost+found",
"system_ce", "system_de",
"misc_ce", "misc_de",
"media",
"data", "user", "user_de",
};
std::string prefix = "/data/";
for (auto d: directories_to_exclude) {
if ((prefix + d) == dir) {
LOG(INFO) << "Not setting policy on " << dir;
return 0;
}
}
return set_system_de_policy_on(dir);
}
  • e4crypt_policy_set 先填充了 eep 这个结构体,相关的加密 key 与加密方式都是通过这个结构体进行进一步操作的.最后通过 ioctl 实现相关目录的 key policy ,kernel 部分这里就不去深究了,相关代码位置在 kernel/fs/crypto/ 中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static bool e4crypt_policy_set(const char *directory, const char *policy,
size_t policy_length,
int contents_encryption_mode,
int filenames_encryption_mode) {
if (policy_length != EXT4_KEY_DESCRIPTOR_SIZE) {
LOG(ERROR) << "Policy wrong length: " << policy_length;
return false;
}
int fd = open(directory, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (fd == -1) {
PLOG(ERROR) << "Failed to open directory " << directory;
return false;
}
//填充 eep
ext4_encryption_policy eep;
eep.version = 0;
eep.contents_encryption_mode = contents_encryption_mode;
eep.filenames_encryption_mode = filenames_encryption_mode;
eep.flags = e4crypt_get_policy_flags(filenames_encryption_mode);
memcpy(eep.master_key_descriptor, policy, EXT4_KEY_DESCRIPTOR_SIZE);
//ioctl 实现最后的加密操作
if (ioctl(fd, EXT4_IOC_SET_ENCRYPTION_POLICY, &eep)) {
PLOG(ERROR) << "Failed to set encryption policy for " << directory;
close(fd);
return false;
}
close(fd);

char policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX];
policy_to_hex(policy, policy_hex);
LOG(INFO) << "Policy for " << directory << " set to " << policy_hex;
return true;
}
  • 上面已经分析了 uncryptkey 加密操作. 那么文章开头说的 CE/DE 又是在哪做的操作呢? 继续分析 init.rc,发现在 init.rc 中,在 post-fs-data 中创建了一些必要的文件后,是时候为用户0创建相应的key 了 , init_user0 就是做了这个操作,类似之前的 do_installkey 操作, init_user0 最终会调用了 vold 下的 e4crypt_init_user0 函数,该函数会生成 /data/misc/vold/user_keys 目录下的相关文件.注:DE key 加密相关的存储空间就是在这个阶段实现的 CE 加密相关的存储空间还未生成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
bool e4crypt_init_user0() {
LOG(DEBUG) << "e4crypt_init_user0";
if (e4crypt_is_native()) {
if (!prepare_dir(user_key_dir, 0700, AID_ROOT, AID_ROOT)) return false;
if (!prepare_dir(user_key_dir + "/ce", 0700, AID_ROOT, AID_ROOT)) return false;
if (!prepare_dir(user_key_dir + "/de", 0700, AID_ROOT, AID_ROOT)) return false;
if (!android::vold::pathExists(get_de_key_path(0))) {
//第一次系统起来, de/ce key 未创建,所以会走这,创建 DE/CE key 的过程都是在这个函数里面做的,类似 uncrypted key 的生成流程,
// 也会通过调用 randomKey 函数生成随机 key ,storeKeyAtomically 函数生成 keymaster key ,
//最后调用 installKey 将 key 加入到 密钥环(keyring)中
if (!create_and_install_user_keys(0, false)) return false;
}
// TODO: switch to loading only DE_0 here once framework makes
// explicit calls to install DE keys for secondary users
// 加密之后,第一次系统起来直接走这,load key 然后用 keymaster key进行校验
if (!load_all_de_keys()) return false;
}
// We can only safely prepare DE storage here, since CE keys are probably
// entangled with user credentials. The framework will always prepare CE
// storage once CE keys are installed.
//开始准备 DE 存储空间,e4crypt_prepare_user_storage 先是准备了相关的 DE 目录,之后查找相关用户id 的 key ,最后调用 ensure_policy 用户设置相关目录的加密策略
if (!e4crypt_prepare_user_storage(nullptr, 0, 0, FLAG_STORAGE_DE)) {
LOG(ERROR) << "Failed to prepare user 0 storage";
return false;
}

// If this is a non-FBE device that recently left an emulated mode,
// restore user data directories to known-good state.
if (!e4crypt_is_native() && !e4crypt_is_emulated()) {
e4crypt_unlock_user_key(0, 0, "!", "!");
}

return true;
}
  • 接着上面的 ensure_policy 函数,通过 cryptfs_get_file_encryption_modes 函数,根据 fstab 文件获取了 FBE 加密方式(contents_mode,filenames_mode),最后通过 e4crypt_policy_ensure 函数设置相关目录的 policy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
int e4crypt_policy_ensure(const char *directory, const char *policy,
size_t policy_length,
const char *contents_encryption_mode,
const char *filenames_encryption_mode) {
int contents_mode = 0;
int filenames_mode = 0;

if (!strcmp(contents_encryption_mode, "software") ||
!strcmp(contents_encryption_mode, "aes-256-xts")) {
contents_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;
} else if (!strcmp(contents_encryption_mode, "ice")) {
contents_mode = EXT4_ENCRYPTION_MODE_PRIVATE;
} else {
LOG(ERROR) << "Invalid file contents encryption mode: "
<< contents_encryption_mode;
return -1;
}

if (!strcmp(filenames_encryption_mode, "aes-256-cts")) {
filenames_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS;
} else if (!strcmp(filenames_encryption_mode, "aes-256-heh")) {
filenames_mode = EXT4_ENCRYPTION_MODE_AES_256_HEH;
} else {
LOG(ERROR) << "Invalid file names encryption mode: "
<< filenames_encryption_mode;
return -1;
}

bool is_empty;
if (!is_dir_empty(directory, &is_empty)) return -1;
if (is_empty) {
if (!e4crypt_policy_set(directory, policy, policy_length,
contents_mode, filenames_mode)) return -1;
} else {
if (!e4crypt_policy_check(directory, policy, policy_length,
contents_mode, filenames_mode)) return -1;
}
return 0;
}
  • 那么 CE 空间是在什么时候准备好的?其实那部分代码是在 framework 里面做的。framework 相关代码在 frameworks/base/services/core/java/com/android/server/StorageManagerService.java,调用了 unlockUserKey 函数,该部分代码还是很简单的,主要就是通过 mCryptConnector.execute("cryptfs", "unlock_user_key", userId, serialNumber,encodeBytes(token), encodeBytes(secret));通知 vold 需要去生成 key 了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Override
public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret) {
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
waitForReady();

if (StorageManager.isFileEncryptedNativeOrEmulated()) {
// When a user has secure lock screen, require secret to actually unlock.
// This check is mostly in place for emulation mode.
if (mLockPatternUtils.isSecure(userId) && ArrayUtils.isEmpty(secret)) {
throw new IllegalStateException("Secret required to unlock secure user " + userId);
}

try {
mCryptConnector.execute("cryptfs", "unlock_user_key", userId, serialNumber,
encodeBytes(token), encodeBytes(secret));
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
}

synchronized (mLock) {
mLocalUnlockedUsers = ArrayUtils.appendInt(mLocalUnlockedUsers, userId);
}
if (userId == UserHandle.USER_SYSTEM) {
String propertyName = "sys.user." + userId + ".ce_available";
Slog.d(TAG, "Setting property: " + propertyName + "=true");
SystemProperties.set(propertyName, "true");
}
}
  • 调用 system/vold/Ext4Crypt.cpp 中的 e4crypt_unlock_user_key 函数进行解密操作,第一次系统起来的时候,因为用户没有设置密码,所以此时的 key 是没有用 auth 进行签名的,s_ce_key_raw_refs 会直接返回,即此时的 key 是已经解密过的 key 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// TODO: rename to 'install' for consistency, and take flags to know which keys to install
bool e4crypt_unlock_user_key(userid_t user_id, int serial, const char* token_hex,
const char* secret_hex) {
LOG(DEBUG) << "e4crypt_unlock_user_key " << user_id << " serial=" << serial
<< " token_present=" << (strcmp(token_hex, "!") != 0);
if (e4crypt_is_native()) {
if (s_ce_key_raw_refs.count(user_id) != 0) {
LOG(WARNING) << "Tried to unlock already-unlocked key for user " << user_id;
return true;
}
std::string token, secret;
if (!parse_hex(token_hex, &token)) return false;
if (!parse_hex(secret_hex, &secret)) return false;
android::vold::KeyAuthentication auth(token, secret);
if (!read_and_install_user_ce_key(user_id, auth)) {
LOG(ERROR) << "Couldn't read key for " << user_id;
return false;
}
} else {
// When in emulation mode, we just use chmod. However, we also
// unlock directories when not in emulation mode, to bring devices
// back into a known-good state.
if (!emulated_unlock(android::vold::BuildDataSystemCePath(user_id), 0771) ||
!emulated_unlock(android::vold::BuildDataMiscCePath(user_id), 01771) ||
!emulated_unlock(android::vold::BuildDataMediaCePath(nullptr, user_id), 0770) ||
!emulated_unlock(android::vold::BuildDataUserCePath(nullptr, user_id), 0771)) {
LOG(ERROR) << "Failed to unlock user " << user_id;
return false;
}
}
return true;
}
  • 之后调用 prepareUserStorage 用于设置 ce 空间相关的 policy
1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
waitForReady();

try {
mCryptConnector.execute("cryptfs", "prepare_user_storage", escapeNull(volumeUuid),
userId, serialNumber, flags);
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
}
  • 类似 DE 空间的加密流程 CE 空间也会调用 e4crypt_prepare_user_storage 进行设置目录的 policy ,这样子,加密空间就全部准备好了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
bool e4crypt_prepare_user_storage(const char* volume_uuid, userid_t user_id, int serial,
int flags) {
LOG(DEBUG) << "e4crypt_prepare_user_storage for volume " << escape_null(volume_uuid)
<< ", user " << user_id << ", serial " << serial << ", flags " << flags;

if (flags & FLAG_STORAGE_DE) {
// DE_sys key
auto system_legacy_path = android::vold::BuildDataSystemLegacyPath(user_id);
auto misc_legacy_path = android::vold::BuildDataMiscLegacyPath(user_id);
auto profiles_de_path = android::vold::BuildDataProfilesDePath(user_id);

// DE_n key
auto system_de_path = android::vold::BuildDataSystemDePath(user_id);
auto misc_de_path = android::vold::BuildDataMiscDePath(user_id);
auto user_de_path = android::vold::BuildDataUserDePath(volume_uuid, user_id);

if (!prepare_dir(system_legacy_path, 0700, AID_SYSTEM, AID_SYSTEM)) return false;
#if MANAGE_MISC_DIRS
if (!prepare_dir(misc_legacy_path, 0750, multiuser_get_uid(user_id, AID_SYSTEM),
multiuser_get_uid(user_id, AID_EVERYBODY))) return false;
#endif
if (!prepare_dir(profiles_de_path, 0771, AID_SYSTEM, AID_SYSTEM)) return false;

if (!prepare_dir(system_de_path, 0770, AID_SYSTEM, AID_SYSTEM)) return false;
if (!prepare_dir(misc_de_path, 01771, AID_SYSTEM, AID_MISC)) return false;
if (!prepare_dir(user_de_path, 0771, AID_SYSTEM, AID_SYSTEM)) return false;

if (e4crypt_is_native()) {
std::string de_raw_ref;
if (!lookup_key_ref(s_de_key_raw_refs, user_id, &de_raw_ref)) return false;
if (!ensure_policy(de_raw_ref, system_de_path)) return false;
if (!ensure_policy(de_raw_ref, misc_de_path)) return false;
if (!ensure_policy(de_raw_ref, user_de_path)) return false;
}
}

if (flags & FLAG_STORAGE_CE) {
// CE_n key
auto system_ce_path = android::vold::BuildDataSystemCePath(user_id);
auto misc_ce_path = android::vold::BuildDataMiscCePath(user_id);
auto media_ce_path = android::vold::BuildDataMediaCePath(volume_uuid, user_id);
auto user_ce_path = android::vold::BuildDataUserCePath(volume_uuid, user_id);

if (!prepare_dir(system_ce_path, 0770, AID_SYSTEM, AID_SYSTEM)) return false;
if (!prepare_dir(misc_ce_path, 01771, AID_SYSTEM, AID_MISC)) return false;
if (!prepare_dir(media_ce_path, 0770, AID_MEDIA_RW, AID_MEDIA_RW)) return false;
if (!prepare_dir(user_ce_path, 0771, AID_SYSTEM, AID_SYSTEM)) return false;

if (e4crypt_is_native()) {
std::string ce_raw_ref;
if (!lookup_key_ref(s_ce_key_raw_refs, user_id, &ce_raw_ref)) return false;
if (!ensure_policy(ce_raw_ref, system_ce_path)) return false;
if (!ensure_policy(ce_raw_ref, misc_ce_path)) return false;
if (!ensure_policy(ce_raw_ref, media_ce_path)) return false;
if (!ensure_policy(ce_raw_ref, user_ce_path)) return false;

// Now that credentials have been installed, we can run restorecon
// over these paths
// NOTE: these paths need to be kept in sync with libselinux
android::vold::RestoreconRecursive(system_ce_path);
android::vold::RestoreconRecursive(misc_ce_path);
}
}

return true;
}

2.3 锁屏密码与 FBE key 的关系

开始前,先说说加密思想。Android FBE 中会通过密码文件生成一个 auth ,用这个 auth 再对 key 进行签名。这样子,用户在没有输入正确密码时,是无法进行解密 CE 空间的。那按照这样子,用户在不输入密码的时候岂不是用不了这些内存位置。其实,根据 auth 对 key 的签名,只针对了 CE 空间的 key 进行签名,所以系统在正常起来时,DE 空间其实已经解密完成了,现在就来看下用户在设置密码后,vold 做了哪些处理吧。

  • 用户设置完成密码后,vold 进程首先会调用 e4crypt_add_user_key_auth 用于生成新的 CE key,一个疑惑,在设置密码完成后,会调用两次该函数,第一次会生成一个未经过 auth 签名的 key,之后会再次生成一个经过 auth 签名的 key,不知这样的意图为何?

  • e4crypt_fixate_newest_user_key_auth 主要是将用户设置密码后生成的 auth 签名过 key 给重命名成之前的 key 名称

  • secdiscard 主要用于删除旧的 key 文件

2.4 FBE 解密

这里说的解密的意思理解成读取可能会比较好理解. Android 的加密思想是如果是未经过授权的读取操作都是无法进行访问的.其实 FBE 解密流程和系统第一次起来时 set policy 的流程几乎是一样的。不同的是,在调用 ensure_policy 时,会对目录检测是否为空,如果是空,则进行 set policy 操作,而如果为非空,则进行 check_policy 操作,所以对 /data 的解密操作都在这个 check_policy ,当校验成功后,kernel 会自动对 /data 进行解密操作。如果要实现对 /data 的解密,最难的倒不是 vold 中解密流程的移植,反倒是 framework 中根据 password 生成的 auth 的提取,因为此时的 key 是使用了 auth 进行签名的 key 了,如何把加密的 key 给解出来倒成了开发的难点。

3. 总结

分析到最后,其实 FBE 的加密思想并不复杂,相对于 Android 之前的全盘加密,整个流程加密思想几乎是相同的。但无疑文件加密是更为人性化的,用户无需在输入密码就可进行一些基础操作。文中可能还有些不足以及 kernel 部分详细的加密操作都未详谈,欢迎各位指正补充。


This is copyright.