blob: 06b15f73eddc4fe9c55726aac1a9c65b2366f24f [file] [log] [blame]
Alan Stokes1125e012023-10-13 12:31:10 +01001// Copyright 2023 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::instance::{ApkData, MicrodroidData, RootHash};
16use crate::payload::get_apex_data_from_payload;
17use crate::{is_strict_boot, is_verified_boot, write_apex_payload_data, MicrodroidError};
18use anyhow::{anyhow, ensure, Context, Result};
19use apkmanifest::get_manifest_info;
20use apkverify::{get_public_key_der, verify, V4Signature};
21use glob::glob;
22use itertools::sorted;
23use log::{info, warn};
24use microdroid_metadata::Metadata;
25use rand::Fill;
26use rustutils::system_properties;
27use std::path::Path;
28use std::process::{Child, Command};
29use std::str;
30use std::time::SystemTime;
31
32pub const DM_MOUNTED_APK_PATH: &str = "/dev/block/mapper/microdroid-apk";
33
34const MAIN_APK_PATH: &str = "/dev/block/by-name/microdroid-apk";
35const MAIN_APK_IDSIG_PATH: &str = "/dev/block/by-name/microdroid-apk-idsig";
36const MAIN_APK_DEVICE_NAME: &str = "microdroid-apk";
37const EXTRA_APK_PATH_PATTERN: &str = "/dev/block/by-name/extra-apk-*";
38const EXTRA_IDSIG_PATH_PATTERN: &str = "/dev/block/by-name/extra-idsig-*";
39
40const APKDMVERITY_BIN: &str = "/system/bin/apkdmverity";
41
42/// Verify payload before executing it. For APK payload, Full verification (which is slow) is done
43/// when the root_hash values from the idsig file and the instance disk are different. This function
44/// returns the verified root hash (for APK payload) and pubkeys (for APEX payloads) that can be
45/// saved to the instance disk.
46pub fn verify_payload(
47 metadata: &Metadata,
48 saved_data: Option<&MicrodroidData>,
49) -> Result<MicrodroidData> {
50 let start_time = SystemTime::now();
51
52 // Verify main APK
53 let root_hash_from_idsig = get_apk_root_hash_from_idsig(MAIN_APK_IDSIG_PATH)?;
54 let root_hash_trustful =
55 saved_data.map(|d| d.apk_data.root_hash_eq(root_hash_from_idsig.as_ref())).unwrap_or(false);
56
57 // If root_hash can be trusted, pass it to apkdmverity so that it uses the passed root_hash
58 // instead of the value read from the idsig file.
59 let main_apk_argument = {
60 ApkDmverityArgument {
61 apk: MAIN_APK_PATH,
62 idsig: MAIN_APK_IDSIG_PATH,
63 name: MAIN_APK_DEVICE_NAME,
64 saved_root_hash: if root_hash_trustful {
65 Some(root_hash_from_idsig.as_ref())
66 } else {
67 None
68 },
69 }
70 };
71 let mut apkdmverity_arguments = vec![main_apk_argument];
72
73 // Verify extra APKs
74 // For now, we can't read the payload config, so glob APKs and idsigs.
75 // Later, we'll see if it matches with the payload config.
76
77 // sort globbed paths to match apks (extra-apk-{idx}) and idsigs (extra-idsig-{idx})
78 // e.g. "extra-apk-0" corresponds to "extra-idsig-0"
79 let extra_apks =
80 sorted(glob(EXTRA_APK_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
81 let extra_idsigs =
82 sorted(glob(EXTRA_IDSIG_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
83 ensure!(
84 extra_apks.len() == extra_idsigs.len(),
85 "Extra apks/idsigs mismatch: {} apks but {} idsigs",
86 extra_apks.len(),
87 extra_idsigs.len()
88 );
89
90 let extra_root_hashes_from_idsig: Vec<_> = extra_idsigs
91 .iter()
92 .map(|idsig| {
93 get_apk_root_hash_from_idsig(idsig).expect("Can't find root hash from extra idsig")
94 })
95 .collect();
96
97 let extra_root_hashes_trustful: Vec<_> = if let Some(data) = saved_data {
98 extra_root_hashes_from_idsig
99 .iter()
100 .enumerate()
101 .map(|(i, root_hash)| data.extra_apk_root_hash_eq(i, root_hash))
102 .collect()
103 } else {
104 vec![false; extra_root_hashes_from_idsig.len()]
105 };
106 let extra_apk_names: Vec<_> =
107 (0..extra_apks.len()).map(|i| format!("extra-apk-{}", i)).collect();
108
109 for (i, extra_apk) in extra_apks.iter().enumerate() {
110 apkdmverity_arguments.push({
111 ApkDmverityArgument {
112 apk: extra_apk.to_str().unwrap(),
113 idsig: extra_idsigs[i].to_str().unwrap(),
114 name: &extra_apk_names[i],
115 saved_root_hash: if extra_root_hashes_trustful[i] {
116 Some(&extra_root_hashes_from_idsig[i])
117 } else {
118 None
119 },
120 }
121 });
122 }
123
124 // Start apkdmverity and wait for the dm-verify block
125 let mut apkdmverity_child = run_apkdmverity(&apkdmverity_arguments)?;
126
127 // While waiting for apkdmverity to mount APK, gathers public keys and root digests from
128 // APEX payload.
129 let apex_data_from_payload = get_apex_data_from_payload(metadata)?;
130
131 // Writing /apex/vm-payload-metadata is to verify that the payload isn't changed.
132 // Skip writing it if the debug policy ignoring identity is on
133 if is_verified_boot() {
134 write_apex_payload_data(saved_data, &apex_data_from_payload)?;
135 }
136
137 // Start apexd to activate APEXes
138 system_properties::write("ctl.start", "apexd-vm")?;
139
140 // TODO(inseob): add timeout
141 apkdmverity_child.wait()?;
142
143 // Do the full verification if the root_hash is un-trustful. This requires the full scanning of
144 // the APK file and therefore can be very slow if the APK is large. Note that this step is
145 // taken only when the root_hash is un-trustful which can be either when this is the first boot
146 // of the VM or APK was updated in the host.
147 // TODO(jooyung): consider multithreading to make this faster
148 let main_apk_pubkey = get_public_key_from_apk(DM_MOUNTED_APK_PATH, root_hash_trustful)?;
149 let extra_apks_data = extra_root_hashes_from_idsig
150 .into_iter()
151 .enumerate()
152 .map(|(i, extra_root_hash)| {
153 let mount_path = format!("/dev/block/mapper/{}", &extra_apk_names[i]);
154 let apk_pubkey = get_public_key_from_apk(&mount_path, extra_root_hashes_trustful[i])?;
155 Ok(ApkData { root_hash: extra_root_hash, pubkey: apk_pubkey })
156 })
157 .collect::<Result<Vec<_>>>()?;
158
159 info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
160
161 // Use the salt from a verified instance, or generate a salt for a new instance.
162 let salt = if let Some(saved_data) = saved_data {
163 saved_data.salt.clone()
164 } else if is_strict_boot() {
165 // No need to add more entropy as a previous stage must have used a new, random salt.
166 vec![0u8; 64]
167 } else {
168 let mut salt = vec![0u8; 64];
169 salt.as_mut_slice().try_fill(&mut rand::thread_rng())?;
170 salt
171 };
172
173 // At this point, we can ensure that the root_hash from the idsig file is trusted, either by
174 // fully verifying the APK or by comparing it with the saved root_hash.
175 Ok(MicrodroidData {
176 salt,
177 apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: main_apk_pubkey },
178 extra_apks_data,
179 apex_data: apex_data_from_payload,
180 })
181}
182
183fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<RootHash>> {
184 Ok(V4Signature::from_idsig_path(idsig_path)?.hashing_info.raw_root_hash)
185}
186
187fn get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>> {
188 let current_sdk = get_current_sdk()?;
189
190 let public_key_der = if !root_hash_trustful {
191 verify(apk, current_sdk).context(MicrodroidError::PayloadVerificationFailed(format!(
192 "failed to verify {}",
193 apk
194 )))?
195 } else {
196 get_public_key_der(apk, current_sdk)?
197 };
198
199 match get_manifest_info(apk) {
200 Ok(manifest_info) => {
201 // TODO (b/299591171): Do something with this info
202 info!("Manifest info is {manifest_info:?}")
203 }
204 Err(e) => warn!("Failed to read manifest info from APK: {e:?}"),
205 };
206
207 Ok(public_key_der)
208}
209
210fn get_current_sdk() -> Result<u32> {
211 let current_sdk = system_properties::read("ro.build.version.sdk")?;
212 let current_sdk = current_sdk.ok_or_else(|| anyhow!("SDK version missing"))?;
213 current_sdk.parse().context("Malformed SDK version")
214}
215
216struct ApkDmverityArgument<'a> {
217 apk: &'a str,
218 idsig: &'a str,
219 name: &'a str,
220 saved_root_hash: Option<&'a RootHash>,
221}
222
223fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
224 let mut cmd = Command::new(APKDMVERITY_BIN);
225
226 for argument in args {
227 cmd.arg("--apk").arg(argument.apk).arg(argument.idsig).arg(argument.name);
228 if let Some(root_hash) = argument.saved_root_hash {
229 cmd.arg(&hex::encode(root_hash));
230 } else {
231 cmd.arg("none");
232 }
233 }
234
235 cmd.spawn().context("Spawn apkdmverity")
236}