AISeal 的 CE 存储:宿主只保管密文,密钥在 VM 里解开
CE 是 Credential Encrypted 的缩写,指用户第一次解锁后才能解密的存储;对应的 DE(Device Encrypted)开机就能解密。
| 情境 | GMS-VSR 要求声明 FEATURE_AISEAL 的设备把 AiSeal VM 跑成受保护 VM(pVM),并且支持 CE 存储。 |
|---|---|
| 冲突 | 用户的锁屏凭据只在宿主 Android 上,而 AISeal 的前提是宿主可能被攻破,不能碰到 VM 里的明文数据。 |
| 问题 | VM 怎样做到"用户解锁之后才能读数据",同时不让宿主拿到解密用的密钥? |
1 总体架构:CE 存储横跨宿主、VM、pKVM 和 TEE 四块
Section titled “1 总体架构:CE 存储横跨宿主、VM、pKVM 和 TEE 四块”宿主提供"用户已解锁"这个信号和一个只有解锁后才能读的位置,VM 负责所有密钥运算,pKVM 保证宿主看不到 VM 内存,TEE 和启动链提供 VM 自己的根密钥。
CE 存储和用户凭据之间的绑定,靠的是宿主自己的 FBE(文件级加密):kek 文件放在宿主的 CE 目录里,宿主用户不解锁,VM 也拿不到这个文件。
2 逻辑视图:宿主只做转发和保管,密钥逻辑全在 VM 里
Section titled “2 逻辑视图:宿主只做转发和保管,密钥逻辑全在 VM 里”宿主侧三个模块只负责转发用户事件、替 VM 读写文件和管理 VM;生成密钥、包裹、解包、给目录加锁都在 VM 里的 microdroid_manager 完成。
| 模块 | 源码位置 |
|---|---|
| AiSealSystemService | frameworks/base/services/aiseal/java/com/android/server/aiseal/AiSealSystemService.java |
| aisealhostservice | frameworks/native/services/aisealhostservice/src/main.rs(AiSealInternalService、CEStoreKEK)、instance_data.rs、config.rs |
| GuestAgent | packages/modules/Virtualization/guest/microdroid_manager/src/main.rs:1446 起 |
| encrypted_folders | guest/microdroid_manager/src/encrypted_folders.rs |
| encrypted_store_kek | guest/microdroid_manager/src/encrypted_store_kek.rs |
| VmSecret | guest/microdroid_manager/src/vm_secret.rs |
| encryptedstore | packages/modules/Virtualization/guest/encryptedstore/src/main.rs |
2.1 密钥层次:三把密钥都由 VM secret 派生,宿主只拿到密文
Section titled “2.1 密钥层次:三把密钥都由 VM secret 派生,宿主只拿到密文”下图展开逻辑视图里的 VmSecret 和 encrypted_store_kek。真正加密 CE 数据的是每个用户一把的用户密钥;它被 KEK 包裹以后才交给宿主保存。
| 密钥 | 代码 |
|---|---|
| VM secret | vm_secret.rs:210 get_vm_secret() |
| 外层盘密钥 | vm_secret.rs:230 derive_encryptedstore_key() |
| 包裹密钥 KEK | vm_secret.rs:235 derive_encryptedstore_key_encryption_key() |
| 用户密钥(首次) | vm_secret.rs:244 derive_random_key() |
| 包裹和解包 | encrypted_store_kek.rs:22 encrypt_kek()、:30 decrypt_kek() |
2.2 存储分层:一次写入在 VM 里加密,宿主只看到密文
Section titled “2.2 存储分层:一次写入在 VM 里加密,宿主只看到密文”下图展开逻辑视图里 VM 的磁盘部分:CE 数据先由 fscrypt 加密,元数据由外层 dm-default-key 加密,落到宿主时已全是密文。
- 外层的参数来自 AppSearch 的 VM 配置
AppSearchAiSealConfig/assets/vm_config.json:"dm_default_key": true、"hugepages": true。 - dm-default-key 跳过已加密文件的规则在内核
include/linux/fscrypt.h的fscrypt_inode_should_skip_dm_default_key(),条件是IS_ENCRYPTED(inode) && S_ISREG(inode->i_mode)。 - 外层的建立和挂载在
encryptedstore/src/main.rs::327enable_dm_default_key()、:351format_ext4()、:455mount()。
3 运行视图:宿主 7 个进程、VM 里 3 个进程协作
Section titled “3 运行视图:宿主 7 个进程、VM 里 3 个进程协作”宿主和 VM 之间只有一条 vsock 上的 RPC binder 会话,用户事件(IGuestAgent)和读写 kek 文件的回调(ICEStoreKEK)都走这条会话;Secretkeeper 的请求经宿主转发,但内容是 VM 和 TA 之间加密的。
aisealhostservice 的启动条件写在 aisealhostservice.rc 里:
service aisealhostservice /system/bin/aisealhostservice user system group system task_profiles MaxPerformance interface aidl aiseal_host disabled
on property:sys.boot_completed=1 && property:service.aiseal.enable=1 enable aisealhostservice运行参数由系统属性控制(config.rs:32-39、device/google/cuttlefish/shared/aiseal/device_vendor.mk):
| 属性 | 默认值 | 作用 |
|---|---|---|
service.aiseal.enable | 无 | 设为 1 才启动服务 |
service.aiseal.protected_vm | true | 是否跑成受保护 VM;Cuttlefish 不支持 pVM,设为 0 |
service.aiseal.memory_bytes | 300 MiB | VM 内存上限 |
service.aiseal.encrypted_storage_bytes | 16 GiB | storage.img 上限,已有磁盘启动时按它调整 |
service.aiseal.tenant_config_package | 无 | 配置包,AOSP 里是 com.android.appsearch.aiseal.config |
service.aiseal.tenant_config_path、aiseal_config_path | 无 | 配置包里的 assets/vm_config.json、assets/aiseal_config.json |
vCPU 数量没有属性可调,写死为 1(main.rs:171 CpuTopology::CpuCount(1))。
4 调用流程:VM 启动、用户解锁,以及停用、删除和重启
Section titled “4 调用流程:VM 启动、用户解锁,以及停用、删除和重启”4.1 VM 启动:外层加密盘挂好,guest agent 注册到宿主
Section titled “4.1 VM 启动:外层加密盘挂好,guest agent 注册到宿主”VM 启动阶段不涉及任何用户:VM 算出自己的 secret,挂好外层加密盘,再把 guest agent 注册给宿主,为后面的用户解锁做好准备。
| 步骤 | 代码 |
|---|---|
| 1 等开机完成 | aisealhostservice main.rs:101 wait_for_boot_completed() |
| 3~4 打开或新建磁盘 | instance_data.rs:60 load_existing()、:87 create() |
| 5~6 建 VM | main.rs:127 VirtualizationService::new()、:157 VirtualMachineAppConfig |
| 11~12 取 Secretkeeper 秘密 | vm_secret.rs:442 get_secretkeeper_service() |
| 13~15 外层盘 | microdroid_manager main.rs:722、:1394 prepare_encryptedstore() |
| 16 注册 guest agent | microdroid_manager main.rs:548 registerGuestAgent() |
| 19 注册 binder 服务 | aisealhostservice main.rs:221、:224 |
4.2 用户解锁:VM 取回用户密钥,给目录加上 fscrypt
Section titled “4.2 用户解锁:VM 取回用户密钥,给目录加上 fscrypt”一次解锁走 17 步:宿主侧只做到第 5 步,之后由 VM 主导,宿主只在 VM 回调时读写一次文件。
| 步骤 | 代码 |
|---|---|
| 1~3 | AiSealSystemService.java:80 onUserUnlocking()、:174 notifyUserUnlockingLocked()、:229 getKekFile() |
| 4~6 | aisealhostservice main.rs:427 onUserUnlocking() |
| 7~14 | microdroid_manager main.rs:1446 userUnlocked()、:1505 set_encrypted_store_per_user_key();宿主侧回调 main.rs:459 impl ICEStoreKEK |
| 15~16 | encrypted_folders.rs:113 set_encryption_key()、:58 fs_ioc_set_encryption_policy() |
加密策略是 fscrypt v2:内容 AES-256-XTS,文件名 AES-256-CTS,FSCRYPT_POLICY_FLAGS_PAD_32。设置策略只在两种情况下成功:目录是空的(第一次),或者目录已经用同一把密钥设过策略(之后每次)。
4.3 停用、删除和重启:宿主只发通知,加锁、删目录、补密钥都在 VM 里做
Section titled “4.3 停用、删除和重启:宿主只发通知,加锁、删目录、补密钥都在 VM 里做”三种情况的入口不同,但宿主侧都只转发一次通知;真正改变数据可读状态的操作都发生在 VM 里。
| 情况 | 代码 |
|---|---|
| 用户停用 | AiSealSystemService.java:87;aisealhostservice main.rs:433;microdroid_manager main.rs:1486 userLocked()、encrypted_folders.rs:137 |
| 用户被删除 | AiSealSystemService.java:218;aisealhostservice main.rs:438;microdroid_manager main.rs:1477 userRemoved() |
| 服务或 VM 重启 | aisealhostservice main.rs:228-239(VM 死亡后 exit(1));AiSealSystemService.java:100 重连、:114 binderDied、:144 补发 |
5 落地要做的:芯片侧提供两个可信来源,系统集成避开三个边界情况
Section titled “5 落地要做的:芯片侧提供两个可信来源,系统集成避开三个边界情况”5.1 芯片侧:保证 VM secret 的两个来源可信
Section titled “5.1 芯片侧:保证 VM secret 的两个来源可信”VM secret 决定所有 CE 密钥的安全性,它的两个来源都由我们的启动链和 TEE 提供。
| 要提供的 | 做不到的后果 |
|---|---|
| 启动链逐级派生 DICE,把 DICE 链和 pvmfw 一起交给 pKVM | sealing CDI 没有可信根,VM secret 可被伪造 |
| TEE 里实现 Secretkeeper TA,数据放在防篡改存储里 | 退回 V1,只用 DICE,失去镜像回滚保护 |
| pKVM 的 Stage-2 和 SMMU 隔离受保护 VM 的内存 | 宿主能直接读到 VM 内存里的明文密钥 |
另外,VM 里的 fscrypt 和 dm-default-key 都在软件里算(挂载选项没有 inlinecrypt),用不上 UFS 的 inline crypto,全部压在 1 个 vCPU 上。AppSearch 读写密集时要在我们的 CPU 核上实测 AES-XTS 的吞吐。
5.2 系统集成:避开三个边界情况
Section titled “5.2 系统集成:避开三个边界情况”前两种是当前代码没有处理的情况,第三种是设计取舍;系统集成和安全测试时都要考虑。
| 情况 | 原因 | 怎么规避 |
|---|---|---|
| 宿主的 kek 文件丢失,这个用户的 AISeal 数据就永远打不开 | VM 会生成新密钥,但目录已用旧密钥设过策略,SET_ENCRYPTION_POLICY 失败;宿主只记一条 Slog.wtf | 备份恢复、清数据、OTA 迁移不能单独删或改 /data/system_ce/<u>/AiSeal/ |
| 用户停用后,还开着的文件仍可读 | REMOVE_ENCRYPTION_KEY 遇到打开的文件只做部分移除,代码没检查 removal_status_flags(encrypted_folders.rs:97) | 安全测试按"所有文件关闭后才算锁上"来验收 |
| 所有用户的密钥用同一把 KEK 包裹 | KEK 由固定 salt 和 identifier 派生,不区分用户 | 用户之间的隔离依赖 VM 内部可信;tenant 只能用平台密钥签名、预装在只读分区(GMS-VSR 7.1-017) |