Android FBE

2018/07/12 Security

Android FBE

1. FBE 简介

  • 名称: FBE, File-Based Encryption,基于文件的加密
  • 凭据加密 (CE) 存储空间:这是默认存储位置,只有在用户解锁设备后才可用。设备加密 (DE) 存储空间:在直接启动模式期间以及用户解锁设备后均可用。
  • 开启 FBE 方式,在相关的 fstab 文件中添加相关的代码:
/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 文件中进行相关的挂载操作
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 中:
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
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 。
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 的操作啊,那文件加密是什么时候做的呢?不着急,我们继续往下看.
on nonencrypted
    class_start main
    class_start late_start
  • 我们知道,正常系统起来后,init 的执行顺序为 early-init,init,late-init.既然我们一下子无法知道 FBE 中 vold 是在什么时候执行的,那就只能一步步跟 init.rc,看下能否发现一些端倪,功夫不负有心人,终于在 init.rc 中发现了一点可能和 FBE 相关的东西. installkey /data 这个看着有点像.
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 命令,到这里终于开始进入加密流程过程了.
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 进行之后的操作
    ........
    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 是在哪里生成的呢?
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 操作啊.
    # 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 函数实现
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)
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/ 中
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 加密相关的存储空间还未生成
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
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 了。
    @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 。
// 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
    @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 ,这样子,加密空间就全部准备好了。
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 部分详细的加密操作都未详谈,欢迎各位指正补充。

Search

    Table of Contents