Jimmy Chen

A Programmer

(原创)Android FBE加密源码分析(二)

上一篇最后讲到,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

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

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

/*
 * 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的动作

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

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

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

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

// 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

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

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

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

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

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大概就了解这么多,但是感觉还是不够深入,后续有机会再多做些分析吧。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d 博主赞过: