上一篇最后讲到,dispatchCommand是通过调用runCommand来执行具体的CMD操作,这一篇会接着说明。在进行说明前,需要先了解FBE的一些内容,为什么需要这些内容呢?因为在接下来的分析会涉及到CE、DE的存储位置,所以需要先了解。
全盘加密和文件级加密的区别
借助文件级加密,Android 7.0 中引入了一项称为直接启动的新功能。该功能处于启用状态时,已加密设备在启动后将直接进入锁定屏幕。之前,在使用全盘加密 (FDE) 的已加密设备上,用户在访问任何数据之前都需要先提供凭据,从而导致手机无法执行除最基本操作之外的所有其他操作。例如,闹钟无法运行,无障碍服务不可用,手机无法接电话,而只能进行基本的紧急拨号操作。
文件级加密概述
引入文件级加密 (FBE) 和新 API 后,便可以将应用设为加密感知型应用,这样一来,它们将能够在受限环境中运行。这些应用将可以在用户提供凭据之前运行,同时系统仍能保护私密用户信息。
在启用了 FBE 的设备上,每位用户均有两个可供应用使用的存储位置:
- 凭据加密 (CE) 存储空间:这是默认存储位置,只有在用户解锁设备后才可用。
- 设备加密 (DE) 存储空间:在直接启动模式期间以及用户解锁设备后均可用。
这种区分能够使工作资料更加安全,因为这样一来,加密不再只基于启动时密码,从而能够同时保护多位用户。好了,这里就先了解这么多,如果需要更详细的分析大家可以看Android文档的描述或者找其他博客看看。
帅气分割线
根据虚函数和继承关系,最后调用的runCommand是CryptCommandListener::CryptfsCmd::runCommand,另外大家回想下,之前在client端传进来command是CMD=”PID cryptfs enablefilecrypto”,记住这个下面要对这个CMD进行解析了。
CryptCommandListener::CryptfsCmd::runCommand
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 |
int CryptCommandListener::CryptfsCmd::runCommand(SocketClient *cli, int argc, char **argv) { // 验证执行操作的进行的用户的权限 if ((cli->getUid() != 0) && (cli->getUid() != AID_SYSTEM)) { cli->sendMsg(ResponseCode::CommandNoPermission, "No permission to run cryptfs commands", false); return 0; } if (argc < 2) { cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing subcommand", false); return 0; } int rc = 0; std::string subcommand(argv[1]); // 根据subcommand进行选择,这里是"enablefilecrypto",下面直接分析这个CMD if (subcommand == "checkpw") { if (!check_argc(cli, subcommand, argc, 3, "<passwd>")) return 0; dumpArgs(argc, argv, 2); .................. } else if (subcommand == "enablefilecrypto") { if (!check_argc(cli, subcommand, argc, 2, "")) return 0; dumpArgs(argc, argv, -1); // easy,什么都不做,调用e4crypt_initialize_global_de来完成 rc = e4crypt_initialize_global_de(); } else if (subcommand == "changepw") { ................. |
runCommand –> e4crypt_initialize_global_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 41 |
bool e4crypt_initialize_global_de() { LOG(INFO) << "e4crypt_initialize_global_de"; if (s_global_de_initialized) { LOG(INFO) << "Already initialized"; return true; } /* * 最后经过函数调用和赋值后 * contents_mode = "ice" * filenames_mode = "aes-256-cts" * mode_filename = /data/unencrypted/mode */ 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; // 调用retrieveAndInstallKey创建秘钥 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; } |
e4crypt_initialize_global_de –> retrieveAndInstallKey
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 |
/* * create_if_absent = true * key_path = /data/unencrypted/key * tmp_path = /data/unencrypted/tmp */ bool retrieveAndInstallKey(bool create_if_absent, const std::string& key_path, const std::string& tmp_path, std::string* key_ref) { KeyBuffer key; // 路径已存在 if (pathExists(key_path)) { LOG(DEBUG) << "Key exists, using: " << key_path; if (!retrieveKey(key_path, kEmptyAuthentication, &key)) return false; } else { if (!create_if_absent) { LOG(ERROR) << "No key found in " << key_path; return false; } LOG(INFO) << "Creating new key in " << key_path; // 创建DE秘钥 if (!randomKey(&key)) return false; // 保存DE秘钥 if (!storeKeyAtomically(key_path, tmp_path, kEmptyAuthentication, key)) return false; } // 存储秘钥到秘钥库中 if (!installKey(key, key_ref)) { LOG(ERROR) << "Failed to install key in " << key_path; return false; } return true; } |
这样,DE key就创建完成了。至于installKey、retrieveKey之类的函数具体是怎么样产生key的留做以后学习分析。有兴趣的童鞋也可以自行看,这里就先不扩展讲解了。接下来看看CE key是如何生成的。
回到post-fs-data阶段
在post-fs-data阶段的最后部分,有一个init_user0的动作
1 2 3 4 5 6 7 8 9 |
on post-fs-data ....... mkdir /data/cache/backup 0700 system system init_user0 # Set SELinux security contexts on upgrade or policy update. restorecon --recursive --skip-ce /data ....... |
这里init_user0对应的函数是do_init_user0
do_init_user0
1 2 3 4 5 |
static int do_init_user0(const std::vector<std::string>& args) { std::vector<std::string> exec_args = {"exec", "/system/bin/vdc", "--wait", "cryptfs", "init_user0"}; return do_exec(exec_args); } |
看这个代码和上一篇博客中do_installKey的代码是类似的,最后也是通过vdc,传送init_user0进行最后的操作。调用启动过程这里就简略了,直接分析CryptCommandListener的runCommand是如何处理init_user0的。
CryptCommandListener::runCommand
1 2 3 4 5 6 7 8 9 10 11 12 13 |
int CryptCommandListener::CryptfsCmd::runCommand(SocketClient *cli, int argc, char **argv) { ................. rc = cryptfs_isConvertibleToFBE(); } else if (subcommand == "init_user0") { if (!check_argc(cli, subcommand, argc, 2, "")) return 0; // sendGenericOkFailOnBool会根据e4crypt_init_user0调用返回值给client端发送对应的信息 return sendGenericOkFailOnBool(cli, e4crypt_init_user0()); } else if (subcommand == "create_user_key") { ................ } |
runCommand –> e4crypt_init_user0
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 |
bool e4crypt_init_user0() { LOG(DEBUG) << "e4crypt_init_user0"; if (e4crypt_is_native()) { // 准备存储key的文件夹,user_key_dir = "/data/misc/vold/user_keys" 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))) { // 创建user0使用的key if (!create_and_install_user_keys(0, false)) return false; } // 加载所有用户使用的DE key if (!load_all_de_keys()) return false; } // 准备user的DE存储空间 if (!e4crypt_prepare_user_storage(nullptr, 0, 0, FLAG_STORAGE_DE)) { LOG(ERROR) << "Failed to prepare user 0 storage"; return false; } // 不是用FBE就解锁 if (!e4crypt_is_native() && !e4crypt_is_emulated()) { e4crypt_unlock_user_key(0, 0, "!", "!"); } return true; } |
e4crypt_init_user0 –> create_and_install_user_keys
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 |
// NB this assumes that there is only one thread listening for crypt commands, because // it creates keys in a fixed location. static bool create_and_install_user_keys(userid_t user_id, bool create_ephemeral) { KeyBuffer de_key, ce_key; // 产生CE和DE key if (!android::vold::randomKey(&de_key)) return false; if (!android::vold::randomKey(&ce_key)) return false; // create_ephemeral为false if (create_ephemeral) { // If the key should be created as ephemeral, don't store it. s_ephemeral_users.insert(user_id); } else { // 获取CE key保存的路径 auto const directory_path = get_ce_key_directory_path(user_id); if (!prepare_dir(directory_path, 0700, AID_ROOT, AID_ROOT)) return false; auto const paths = get_ce_key_paths(directory_path); std::string ce_key_path; // 获取CE key保存路径 if (!get_ce_key_new_path(directory_path, paths, &ce_key_path)) return false; // 保存CE key if (!android::vold::storeKeyAtomically(ce_key_path, user_key_temp, kEmptyAuthentication, ce_key)) return false; fixate_user_ce_key(directory_path, ce_key_path, paths); // 保存DE key if (!android::vold::storeKeyAtomically(get_de_key_path(user_id), user_key_temp, kEmptyAuthentication, de_key)) return false; } std::string de_raw_ref; // 存储DE key到秘钥库 if (!android::vold::installKey(de_key, &de_raw_ref)) return false; s_de_key_raw_refs[user_id] = de_raw_ref; std::string ce_raw_ref; // 存储CE key到秘钥库 if (!android::vold::installKey(ce_key, &ce_raw_ref)) return false; s_ce_keys[user_id] = ce_key; s_ce_key_raw_refs[user_id] = ce_raw_ref; LOG(DEBUG) << "Created keys for user " << user_id; return true; } |
到这里,用户的DE和CE key都已经有了。问题是,在installKey的时候也已经产生过DE key了,这里为什么还需要再生成一次DE key呢?这里留个疑问,具体到时候还得看看,不过看回installKey部分的代码,猜测installkey生成的DE key应该是全局DE key,init_user0的DE key是用户DE key,所以是两组不同的key。
key已经有了,接下来就是使用key对文件进行加解密了。判断哪些文件需要进行加解密实在init.rc中通过mkdir的时候记性的,init.rc中mkdir动作,是通过do_mkdir函数进行的。
do_mkdir
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
static int do_mkdir(const std::vector<std::string>& args) { mode_t mode = 0755; int ret; ............... // 如果使用FBE if (e4crypt_is_native()) { // 设置文件目录加密规则 if (e4crypt_set_directory_policy(args[1].c_str())) { // 设置失败需要进入recovery模式 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; } |
do_mkdir函数前面的代码基本就是判断参数,根据参数创建对应的文件夹,而对应加密规则的设置则是在最后面部分。我们只分析最后面部分即可。
do_mkdir –> e4crypt_set_directory_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 |
int e4crypt_set_directory_policy(const char* dir) { // 判断是否在/data/路径 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; } if (strchr(dir + 6, '/')) { return 0; } // 不需要处理的文件夹集合,但是底下的文件夹还是会被加密 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; } } // 对不在directories_to_exclude的文件夹进行处理 return set_system_de_policy_on(dir); } |
e4crypt_set_directory_policy —> set_system_de_policy_on
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 |
static int set_system_de_policy_on(char const* dir) { // 秘钥引用,ref_filename=/data/unencrypted/ref std::string ref_filename = std::string("/data") + e4crypt_key_ref; std::string policy; // 读取秘钥应用内容到policy if (!android::base::ReadFileToString(ref_filename, &policy)) { LOG(ERROR) << "Unable to read system policy to set on " << dir; return -1; } // 获取加密模式 auto type_filename = std::string("/data") + e4crypt_key_mode; std::string modestring; if (!android::base::ReadFileToString(type_filename, &modestring)) { LOG(ERROR) << "Cannot read mode"; } std::vector<std::string> modes = android::base::Split(modestring, ":"); if (modes.size() < 1 || modes.size() > 2) { LOG(ERROR) << "Invalid encryption mode string: " << modestring; return -1; } LOG(INFO) << "Setting policy on " << dir; // 设置加密 int result = e4crypt_policy_ensure(dir, policy.c_str(), policy.length(), modes[0].c_str(), modes.size() >= 2 ? modes[1].c_str() : "aes-256-cts"); if (result) { LOG(ERROR) << android::base::StringPrintf( "Setting %02x%02x%02x%02x policy on %s failed!", policy[0], policy[1], policy[2], policy[3], dir); return -1; } return 0; } |
set_system_de_policy_on —> e4crypt_policy_ensure
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_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; } |
contents_encryption_mode和filenames_mode在installKey的时候就有设置过了,类似于contents_mode = “ice”,filenames_mode = “aes-256-cts”。
e4crypt_policy_ensure —> e4crypt_policy_set
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 |
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; } // 设置ext4加密规则 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; } |
到这里设置就做完了。FBE大概就了解这么多,但是感觉还是不够深入,后续有机会再多做些分析吧。
看博主对这块研究比较深。
我这边有个疑问,系统默认只支持data分区的FDE功能,如果想添加一个自己增加分区,进行FDE支持,系统是否可以支持,有什么可以借鉴或者注意的地方?
既然data分区支持FDE加密,那按照启动时的data分区FDE加载流程修改一下,应该是可以支持其他分区进行FDE加密的