1 // Copyright 2021, 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
15 //! Implementation of the AIDL interface of the VirtualizationService.
16
17 use crate::atom::{forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom};
18 use crate::maintenance;
19 use crate::remote_provisioning;
20 use crate::rkpvm::{generate_ecdsa_p256_key_pair, request_attestation};
21 use crate::{get_calling_pid, get_calling_uid, REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME};
22 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
23 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon;
24 use android_system_virtualizationmaintenance::aidl::android::system::virtualizationmaintenance;
25 use android_system_virtualizationservice::aidl::android::system::virtualizationservice;
26 use android_system_virtualizationservice_internal as android_vs_internal;
27 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice;
28 use android_system_vmtethering::aidl::android::system::vmtethering;
29 use android_vs_internal::aidl::android::system::virtualizationservice_internal;
30 use anyhow::{anyhow, ensure, Context, Result};
31 use avflog::LogResult;
32 use binder::{
33 self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, IntoBinderResult,
34 LazyServiceGuard, ParcelFileDescriptor, Status, Strong,
35 };
36 use libc::{VMADDR_CID_HOST, VMADDR_CID_HYPERVISOR, VMADDR_CID_LOCAL};
37 use log::{error, info, warn};
38 use nix::unistd::{chown, Uid};
39 use openssl::x509::X509;
40 use rand::Fill;
41 use rkpd_client::get_rkpd_attestation_key;
42 use rustutils::{
43 system_properties,
44 users::{multiuser_get_app_id, multiuser_get_user_id},
45 };
46 use serde::Deserialize;
47 use service_vm_comm::Response;
48 use std::collections::{HashMap, HashSet};
49 use std::fs::{self, create_dir, remove_dir_all, remove_file, set_permissions, File, Permissions};
50 use std::io::{Read, Write};
51 use std::os::unix::fs::PermissionsExt;
52 use std::os::unix::raw::{pid_t, uid_t};
53 use std::path::{Path, PathBuf};
54 use std::sync::{Arc, Condvar, LazyLock, Mutex, Weak};
55 use tombstoned_client::{DebuggerdDumpType, TombstonedConnection};
56 use virtualizationcommon::Certificate::Certificate;
57 use virtualizationmaintenance::{
58 IVirtualizationMaintenance::IVirtualizationMaintenance,
59 IVirtualizationReconciliationCallback::IVirtualizationReconciliationCallback,
60 };
61 use virtualizationservice::{
62 AssignableDevice::AssignableDevice, VirtualMachineDebugInfo::VirtualMachineDebugInfo,
63 };
64 use virtualizationservice_internal::{
65 AtomVmBooted::AtomVmBooted,
66 AtomVmCreationRequested::AtomVmCreationRequested,
67 AtomVmExited::AtomVmExited,
68 IBoundDevice::IBoundDevice,
69 IGlobalVmContext::{BnGlobalVmContext, IGlobalVmContext},
70 IVfioHandler::VfioDev::VfioDev,
71 IVfioHandler::{BpVfioHandler, IVfioHandler},
72 IVirtualizationServiceInternal::IVirtualizationServiceInternal,
73 IVmnic::{BpVmnic, IVmnic},
74 };
75 use virtualmachineservice::IVirtualMachineService::VM_TOMBSTONES_SERVICE_PORT;
76 use vmtethering::IVmTethering::{BpVmTethering, IVmTethering};
77 use vsock::{VsockListener, VsockStream};
78
79 /// The unique ID of a VM used (together with a port number) for vsock communication.
80 pub type Cid = u32;
81
82 /// Directory in which to write disk image files used while running VMs.
83 pub const TEMPORARY_DIRECTORY: &str = "/data/misc/virtualizationservice";
84
85 /// The first CID to assign to a guest VM managed by the VirtualizationService. CIDs lower than this
86 /// are reserved for the host or other usage.
87 const GUEST_CID_MIN: Cid = 2048;
88 const GUEST_CID_MAX: Cid = 65535;
89
90 const SYSPROP_LAST_CID: &str = "virtualizationservice.state.last_cid";
91
92 const CHUNK_RECV_MAX_LEN: usize = 1024;
93
94 /// The fake certificate is used for testing only when a client VM requests attestation in test
95 /// mode, it is a single certificate extracted on an unregistered device for testing.
96 /// Here is the snapshot of the certificate:
97 ///
98 /// ```
99 /// Certificate:
100 /// Data:
101 /// Version: 3 (0x2)
102 /// Serial Number:
103 /// 59:ae:50:98:95:e1:34:25:f1:21:93:c0:4c:e5:24:66
104 /// Signature Algorithm: ecdsa-with-SHA256
105 /// Issuer: CN = Droid Unregistered Device CA, O = Google Test LLC
106 /// Validity
107 /// Not Before: Feb 5 14:39:39 2024 GMT
108 /// Not After : Feb 14 14:39:39 2024 GMT
109 /// Subject: CN = 59ae509895e13425f12193c04ce52466, O = TEE
110 /// Subject Public Key Info:
111 /// Public Key Algorithm: id-ecPublicKey
112 /// Public-Key: (256 bit)
113 /// pub:
114 /// 04:30:32:cd:95:12:b0:71:8b:b7:14:44:26:58:d5:
115 /// 82:8c:25:55:2c:6d:ef:98:e3:4f:88:d0:74:82:09:
116 /// 3e:8d:6c:f0:f2:18:d5:83:0e:0d:f2:ce:c5:15:38:
117 /// e5:6a:e6:4d:4d:95:15:b7:24:e7:cb:4b:63:42:21:
118 /// bc:36:c6:0a:d8
119 /// ASN1 OID: prime256v1
120 /// NIST CURVE: P-256
121 /// X509v3 extensions:
122 /// ...
123 /// ```
124 const FAKE_CERTIFICATE_FOR_TESTING: &[u8] = &[
125 0x30, 0x82, 0x01, 0xee, 0x30, 0x82, 0x01, 0x94, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x59,
126 0xae, 0x50, 0x98, 0x95, 0xe1, 0x34, 0x25, 0xf1, 0x21, 0x93, 0xc0, 0x4c, 0xe5, 0x24, 0x66, 0x30,
127 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x41, 0x31, 0x25, 0x30,
128 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c, 0x44, 0x72, 0x6f, 0x69, 0x64, 0x20, 0x55, 0x6e,
129 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x20, 0x44, 0x65, 0x76, 0x69, 0x63,
130 0x65, 0x20, 0x43, 0x41, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47,
131 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x4c, 0x4c, 0x43, 0x30, 0x1e,
132 0x17, 0x0d, 0x32, 0x34, 0x30, 0x32, 0x30, 0x35, 0x31, 0x34, 0x33, 0x39, 0x33, 0x39, 0x5a, 0x17,
133 0x0d, 0x32, 0x34, 0x30, 0x32, 0x31, 0x34, 0x31, 0x34, 0x33, 0x39, 0x33, 0x39, 0x5a, 0x30, 0x39,
134 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x35, 0x39, 0x61, 0x65, 0x35,
135 0x30, 0x39, 0x38, 0x39, 0x35, 0x65, 0x31, 0x33, 0x34, 0x32, 0x35, 0x66, 0x31, 0x32, 0x31, 0x39,
136 0x33, 0x63, 0x30, 0x34, 0x63, 0x65, 0x35, 0x32, 0x34, 0x36, 0x36, 0x31, 0x0c, 0x30, 0x0a, 0x06,
137 0x03, 0x55, 0x04, 0x0a, 0x13, 0x03, 0x54, 0x45, 0x45, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a,
138 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07,
139 0x03, 0x42, 0x00, 0x04, 0x30, 0x32, 0xcd, 0x95, 0x12, 0xb0, 0x71, 0x8b, 0xb7, 0x14, 0x44, 0x26,
140 0x58, 0xd5, 0x82, 0x8c, 0x25, 0x55, 0x2c, 0x6d, 0xef, 0x98, 0xe3, 0x4f, 0x88, 0xd0, 0x74, 0x82,
141 0x09, 0x3e, 0x8d, 0x6c, 0xf0, 0xf2, 0x18, 0xd5, 0x83, 0x0e, 0x0d, 0xf2, 0xce, 0xc5, 0x15, 0x38,
142 0xe5, 0x6a, 0xe6, 0x4d, 0x4d, 0x95, 0x15, 0xb7, 0x24, 0xe7, 0xcb, 0x4b, 0x63, 0x42, 0x21, 0xbc,
143 0x36, 0xc6, 0x0a, 0xd8, 0xa3, 0x76, 0x30, 0x74, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
144 0x16, 0x04, 0x14, 0x39, 0x81, 0x41, 0x0a, 0xb9, 0xf3, 0xf4, 0x5b, 0x75, 0x97, 0x4a, 0x46, 0xd6,
145 0x30, 0x9e, 0x1d, 0x7a, 0x3b, 0xec, 0xa8, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
146 0x30, 0x16, 0x80, 0x14, 0x82, 0xbd, 0x00, 0xde, 0xcb, 0xc5, 0xe7, 0x72, 0x87, 0x3d, 0x1c, 0x0a,
147 0x1e, 0x78, 0x4f, 0xf5, 0xd3, 0xc1, 0x3e, 0xb8, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
148 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
149 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x02, 0x04, 0x30, 0x11, 0x06, 0x0a, 0x2b, 0x06, 0x01,
150 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x1e, 0x04, 0x03, 0xa1, 0x01, 0x08, 0x30, 0x0a, 0x06, 0x08,
151 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x21, 0x00,
152 0xae, 0xd8, 0x40, 0x9e, 0x37, 0x3e, 0x5c, 0x9c, 0xe2, 0x93, 0x3d, 0x8c, 0xf7, 0x05, 0x10, 0xe7,
153 0xd1, 0x2b, 0x87, 0x8a, 0xee, 0xd6, 0x1e, 0x6c, 0x3b, 0xd2, 0x91, 0x3e, 0xa5, 0xdf, 0x91, 0x20,
154 0x02, 0x20, 0x7f, 0x0f, 0x29, 0x54, 0x60, 0x80, 0x07, 0x50, 0x5f, 0x56, 0x6b, 0x9f, 0xe0, 0x94,
155 0xb4, 0x3f, 0x3b, 0x0f, 0x61, 0xa0, 0x33, 0x40, 0xe6, 0x1a, 0x42, 0xda, 0x4b, 0xa4, 0xfd, 0x92,
156 0xb9, 0x0f,
157 ];
158
159 static FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING: Mutex<Option<Vec<u8>>> = Mutex::new(None);
160 static VFIO_SERVICE: LazyLock<Strong<dyn IVfioHandler>> = LazyLock::new(|| {
161 wait_for_interface(<BpVfioHandler as IVfioHandler>::get_descriptor())
162 .expect("Could not connect to VfioHandler")
163 });
164 static NETWORK_SERVICE: LazyLock<Strong<dyn IVmnic>> = LazyLock::new(|| {
165 wait_for_interface(<BpVmnic as IVmnic>::get_descriptor()).expect("Could not connect to Vmnic")
166 });
167 static TETHERING_SERVICE: LazyLock<Strong<dyn IVmTethering>> = LazyLock::new(|| {
168 wait_for_interface(<BpVmTethering as IVmTethering>::get_descriptor())
169 .expect("Could not connect to VmTethering")
170 });
171
is_valid_guest_cid(cid: Cid) -> bool172 fn is_valid_guest_cid(cid: Cid) -> bool {
173 (GUEST_CID_MIN..=GUEST_CID_MAX).contains(&cid)
174 }
175
176 /// Singleton service for allocating globally-unique VM resources, such as the CID, and running
177 /// singleton servers, like tombstone receiver.
178 #[derive(Clone)]
179 pub struct VirtualizationServiceInternal {
180 state: Arc<Mutex<GlobalState>>,
181 display_service_set: Arc<Condvar>,
182 }
183
184 impl VirtualizationServiceInternal {
init() -> VirtualizationServiceInternal185 pub fn init() -> VirtualizationServiceInternal {
186 let service = VirtualizationServiceInternal {
187 state: Arc::new(Mutex::new(GlobalState::new())),
188 display_service_set: Arc::new(Condvar::new()),
189 };
190
191 std::thread::spawn(|| {
192 if let Err(e) = handle_stream_connection_tombstoned() {
193 warn!("Error receiving tombstone from guest or writing them. Error: {:?}", e);
194 }
195 });
196
197 service
198 }
199
200 // Attempt to update the sk_state maintenance database. Errors are ignored - calling app
201 // can not really do much to fix the errors & letting AVF VMs run irrespective of such internal
202 // error is acceptable.
try_updating_sk_state(&self, id: &[u8; 64])203 fn try_updating_sk_state(&self, id: &[u8; 64]) {
204 let state = &mut *self.state.lock().unwrap();
205 if let Some(sk_state) = &mut state.sk_state {
206 let uid = get_calling_uid();
207 let user_id = multiuser_get_user_id(uid);
208 let app_id = multiuser_get_app_id(uid);
209 info!(
210 "Recording possible new owner of Secretkeeper entry={:?}:
211 (user_id={user_id}, app_id={app_id},)",
212 hex::encode(id)
213 );
214 if let Err(e) = sk_state.add_id(id, user_id, app_id) {
215 error!("Failed to update the Secretkeeper entry owner: {e:?}");
216 }
217 } else {
218 info!("ignoring update of Secretkeeper entry as no ISecretkeeper");
219 }
220 }
221 }
222
223 impl Interface for VirtualizationServiceInternal {}
224
225 impl IVirtualizationServiceInternal for VirtualizationServiceInternal {
setDisplayService( &self, ibinder: &binder::SpIBinder, ) -> std::result::Result<(), binder::Status>226 fn setDisplayService(
227 &self,
228 ibinder: &binder::SpIBinder,
229 ) -> std::result::Result<(), binder::Status> {
230 check_manage_access()?;
231 check_use_custom_virtual_machine()?;
232 let state = &mut *self.state.lock().unwrap();
233 state.display_service = Some(ibinder.clone());
234 self.display_service_set.notify_all();
235 Ok(())
236 }
237
clearDisplayService(&self) -> std::result::Result<(), binder::Status>238 fn clearDisplayService(&self) -> std::result::Result<(), binder::Status> {
239 check_manage_access()?;
240 check_use_custom_virtual_machine()?;
241 let state = &mut *self.state.lock().unwrap();
242 state.display_service = None;
243 self.display_service_set.notify_all();
244 Ok(())
245 }
246
waitDisplayService(&self) -> std::result::Result<binder::SpIBinder, binder::Status>247 fn waitDisplayService(&self) -> std::result::Result<binder::SpIBinder, binder::Status> {
248 check_manage_access()?;
249 check_use_custom_virtual_machine()?;
250 let state = self
251 .display_service_set
252 .wait_while(self.state.lock().unwrap(), |state| state.display_service.is_none())
253 .unwrap();
254 Ok((state.display_service)
255 .as_ref()
256 .cloned()
257 .expect("Display service cannot be None in this context"))
258 }
removeMemlockRlimit(&self) -> binder::Result<()>259 fn removeMemlockRlimit(&self) -> binder::Result<()> {
260 let pid = get_calling_pid();
261 let lim = libc::rlimit { rlim_cur: libc::RLIM_INFINITY, rlim_max: libc::RLIM_INFINITY };
262
263 // SAFETY: borrowing the new limit struct only
264 let ret = unsafe { libc::prlimit(pid, libc::RLIMIT_MEMLOCK, &lim, std::ptr::null_mut()) };
265
266 match ret {
267 0 => Ok(()),
268 -1 => Err(std::io::Error::last_os_error().into()),
269 n => Err(anyhow!("Unexpected return value from prlimit(): {n}")),
270 }
271 .or_binder_exception(ExceptionCode::ILLEGAL_STATE)
272 }
273
allocateGlobalVmContext( &self, name: &str, requester_debug_pid: i32, ) -> binder::Result<Strong<dyn IGlobalVmContext>>274 fn allocateGlobalVmContext(
275 &self,
276 name: &str,
277 requester_debug_pid: i32,
278 ) -> binder::Result<Strong<dyn IGlobalVmContext>> {
279 check_manage_access()?;
280
281 let requester_uid = get_calling_uid();
282 let requester_debug_pid = requester_debug_pid as pid_t;
283 let state = &mut *self.state.lock().unwrap();
284 state
285 .allocate_vm_context(name, requester_uid, requester_debug_pid)
286 .or_binder_exception(ExceptionCode::ILLEGAL_STATE)
287 }
288
atomVmBooted(&self, atom: &AtomVmBooted) -> Result<(), Status>289 fn atomVmBooted(&self, atom: &AtomVmBooted) -> Result<(), Status> {
290 forward_vm_booted_atom(atom);
291 Ok(())
292 }
293
atomVmCreationRequested(&self, atom: &AtomVmCreationRequested) -> Result<(), Status>294 fn atomVmCreationRequested(&self, atom: &AtomVmCreationRequested) -> Result<(), Status> {
295 forward_vm_creation_atom(atom);
296 Ok(())
297 }
298
atomVmExited(&self, atom: &AtomVmExited) -> Result<(), Status>299 fn atomVmExited(&self, atom: &AtomVmExited) -> Result<(), Status> {
300 forward_vm_exited_atom(atom);
301 Ok(())
302 }
303
debugListVms(&self) -> binder::Result<Vec<VirtualMachineDebugInfo>>304 fn debugListVms(&self) -> binder::Result<Vec<VirtualMachineDebugInfo>> {
305 check_debug_access()?;
306
307 let state = &mut *self.state.lock().unwrap();
308 let cids = state
309 .held_contexts
310 .iter()
311 .filter_map(|(_, inst)| Weak::upgrade(inst))
312 .map(|vm| {
313 let vm = vm.lock().unwrap();
314 VirtualMachineDebugInfo {
315 name: vm.name.clone(),
316 cid: vm.cid as i32,
317 temporaryDirectory: vm.get_temp_dir().to_string_lossy().to_string(),
318 requesterUid: vm.requester_uid as i32,
319 requesterPid: vm.requester_debug_pid,
320 hostConsoleName: vm.host_console_name.clone(),
321 }
322 })
323 .collect();
324 Ok(cids)
325 }
326
enableTestAttestation(&self) -> binder::Result<()>327 fn enableTestAttestation(&self) -> binder::Result<()> {
328 check_manage_access()?;
329 check_use_custom_virtual_machine()?;
330 if !cfg!(remote_attestation) {
331 return Err(Status::new_exception_str(
332 ExceptionCode::UNSUPPORTED_OPERATION,
333 Some(
334 "enableTestAttestation is not supported with the remote_attestation \
335 feature disabled",
336 ),
337 ))
338 .with_log();
339 }
340 let res = generate_ecdsa_p256_key_pair()
341 .context("Failed to generate ECDSA P-256 key pair for testing")
342 .with_log()
343 .or_service_specific_exception(-1)?;
344 // Wait until the service VM shuts down, so that the Service VM will be restarted when
345 // the key generated in the current session will be used for attestation.
346 // This ensures that different Service VM sessions have the same KEK for the key blob.
347 service_vm_manager::wait_until_service_vm_shuts_down()
348 .context("Failed to wait until the service VM shuts down")
349 .with_log()
350 .or_service_specific_exception(-1)?;
351 match res {
352 Response::GenerateEcdsaP256KeyPair(key_pair) => {
353 FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING
354 .lock()
355 .unwrap()
356 .replace(key_pair.key_blob.to_vec());
357 Ok(())
358 }
359 _ => Err(remote_provisioning::to_service_specific_error(res)),
360 }
361 .with_log()
362 }
363
requestAttestation( &self, csr: &[u8], requester_uid: i32, test_mode: bool, ) -> binder::Result<Vec<Certificate>>364 fn requestAttestation(
365 &self,
366 csr: &[u8],
367 requester_uid: i32,
368 test_mode: bool,
369 ) -> binder::Result<Vec<Certificate>> {
370 check_manage_access()?;
371 if !cfg!(remote_attestation) {
372 return Err(Status::new_exception_str(
373 ExceptionCode::UNSUPPORTED_OPERATION,
374 Some(
375 "requestAttestation is not supported with the remote_attestation feature \
376 disabled",
377 ),
378 ))
379 .with_log();
380 }
381 if !is_remote_provisioning_hal_declared()? {
382 return Err(Status::new_exception_str(
383 ExceptionCode::UNSUPPORTED_OPERATION,
384 Some("AVF remotely provisioned component service is not declared"),
385 ))
386 .with_log();
387 }
388 remote_provisioning::check_remote_attestation_is_supported()?;
389 info!("Received csr. Requestting attestation...");
390 let (key_blob, certificate_chain) = if test_mode {
391 check_use_custom_virtual_machine()?;
392 info!("Using the fake key blob for testing...");
393 (
394 FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING
395 .lock()
396 .unwrap()
397 .clone()
398 .ok_or_else(|| anyhow!("No key blob for testing"))
399 .with_log()
400 .or_service_specific_exception(-1)?,
401 FAKE_CERTIFICATE_FOR_TESTING.to_vec(),
402 )
403 } else {
404 info!("Retrieving the remotely provisioned keys from RKPD...");
405 let attestation_key = get_rkpd_attestation_key(
406 REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
407 requester_uid as u32,
408 )
409 .context("Failed to retrieve the remotely provisioned keys")
410 .with_log()
411 .or_service_specific_exception(-1)?;
412 (attestation_key.keyBlob, attestation_key.encodedCertChain)
413 };
414 let mut certificate_chain = split_x509_certificate_chain(&certificate_chain)
415 .context("Failed to split the remotely provisioned certificate chain")
416 .with_log()
417 .or_service_specific_exception(-1)?;
418 if certificate_chain.is_empty() {
419 return Err(Status::new_service_specific_error_str(
420 -1,
421 Some("The certificate chain should contain at least 1 certificate"),
422 ))
423 .with_log();
424 }
425 let certificate = request_attestation(
426 csr.to_vec(),
427 key_blob,
428 certificate_chain[0].encodedCertificate.clone(),
429 )
430 .context("Failed to request attestation")
431 .with_log()
432 .or_service_specific_exception(-1)?;
433 certificate_chain.insert(0, Certificate { encodedCertificate: certificate });
434
435 Ok(certificate_chain)
436 }
437
isRemoteAttestationSupported(&self) -> binder::Result<bool>438 fn isRemoteAttestationSupported(&self) -> binder::Result<bool> {
439 if is_remote_provisioning_hal_declared()? {
440 Ok(remote_provisioning::is_remote_attestation_supported())
441 } else {
442 warn!("AVF IRemotelyProvisionedComponent HAL is not declared");
443 Ok(false)
444 }
445 }
446
getAssignableDevices(&self) -> binder::Result<Vec<AssignableDevice>>447 fn getAssignableDevices(&self) -> binder::Result<Vec<AssignableDevice>> {
448 check_use_custom_virtual_machine()?;
449
450 Ok(get_assignable_devices()?
451 .device
452 .into_iter()
453 .map(|x| AssignableDevice { node: x.sysfs_path, dtbo_label: x.dtbo_label })
454 .collect::<Vec<_>>())
455 }
456
bindDevicesToVfioDriver( &self, devices: &[String], ) -> binder::Result<Vec<Strong<dyn IBoundDevice>>>457 fn bindDevicesToVfioDriver(
458 &self,
459 devices: &[String],
460 ) -> binder::Result<Vec<Strong<dyn IBoundDevice>>> {
461 check_use_custom_virtual_machine()?;
462
463 let devices = get_assignable_devices()?
464 .device
465 .into_iter()
466 .filter_map(|x| {
467 if devices.contains(&x.sysfs_path) {
468 Some(VfioDev { sysfsPath: x.sysfs_path, dtboLabel: x.dtbo_label })
469 } else {
470 warn!("device {} is not assignable", x.sysfs_path);
471 None
472 }
473 })
474 .collect::<Vec<VfioDev>>();
475
476 VFIO_SERVICE.bindDevicesToVfioDriver(devices.as_slice())
477 }
478
getDtboFile(&self) -> binder::Result<ParcelFileDescriptor>479 fn getDtboFile(&self) -> binder::Result<ParcelFileDescriptor> {
480 check_use_custom_virtual_machine()?;
481
482 let state = &mut *self.state.lock().unwrap();
483 let file = state.get_dtbo_file().or_service_specific_exception(-1)?;
484 Ok(ParcelFileDescriptor::new(file))
485 }
486
allocateInstanceId(&self) -> binder::Result<[u8; 64]>487 fn allocateInstanceId(&self) -> binder::Result<[u8; 64]> {
488 let mut id = [0u8; 64];
489 id.try_fill(&mut rand::thread_rng())
490 .context("Failed to allocate instance_id")
491 .or_service_specific_exception(-1)?;
492 // Randomly allocated IDs always start with all 7s to avoid colliding with statically
493 // assigned IDs.
494 id[..4].fill(0x77);
495 let uid = get_calling_uid();
496 info!("Allocated a VM's instance_id: {:?}..., for uid: {:?}", &hex::encode(id)[..8], uid);
497 self.try_updating_sk_state(&id);
498 Ok(id)
499 }
500
removeVmInstance(&self, instance_id: &[u8; 64]) -> binder::Result<()>501 fn removeVmInstance(&self, instance_id: &[u8; 64]) -> binder::Result<()> {
502 let state = &mut *self.state.lock().unwrap();
503 if let Some(sk_state) = &mut state.sk_state {
504 let uid = get_calling_uid();
505 info!(
506 "Removing a VM's instance_id: {:?}, for uid: {:?}",
507 hex::encode(instance_id),
508 uid
509 );
510
511 let user_id = multiuser_get_user_id(uid);
512 let app_id = multiuser_get_app_id(uid);
513 sk_state.delete_id(instance_id, user_id, app_id);
514 } else {
515 info!("ignoring removeVmInstance() as no ISecretkeeper");
516 }
517 Ok(())
518 }
519
claimVmInstance(&self, instance_id: &[u8; 64]) -> binder::Result<()>520 fn claimVmInstance(&self, instance_id: &[u8; 64]) -> binder::Result<()> {
521 info!("Claiming a VM's instance_id: {:?}", hex::encode(instance_id));
522 self.try_updating_sk_state(instance_id);
523 Ok(())
524 }
525
createTapInterface(&self, _iface_name_suffix: &str) -> binder::Result<ParcelFileDescriptor>526 fn createTapInterface(&self, _iface_name_suffix: &str) -> binder::Result<ParcelFileDescriptor> {
527 check_internet_permission()?;
528 check_use_custom_virtual_machine()?;
529 if !cfg!(network) {
530 return Err(Status::new_exception_str(
531 ExceptionCode::UNSUPPORTED_OPERATION,
532 Some("createTapInterface is not supported with the network feature disabled"),
533 ))
534 .with_log();
535 }
536 // TODO(340377643): Use iface_name_suffix after introducing bridge interface, not fixed
537 // value.
538 let tap_fd = NETWORK_SERVICE.createTapInterface("fixed")?;
539
540 // TODO(340377643): Due to lack of implementation of creating bridge interface, tethering is
541 // enabled for TAP interface instead of bridge interface. After introducing creation of
542 // bridge interface in AVF, we should modify it.
543 TETHERING_SERVICE.enableVmTethering()?;
544
545 Ok(tap_fd)
546 }
547
deleteTapInterface(&self, tap_fd: &ParcelFileDescriptor) -> binder::Result<()>548 fn deleteTapInterface(&self, tap_fd: &ParcelFileDescriptor) -> binder::Result<()> {
549 check_internet_permission()?;
550 check_use_custom_virtual_machine()?;
551 if !cfg!(network) {
552 return Err(Status::new_exception_str(
553 ExceptionCode::UNSUPPORTED_OPERATION,
554 Some("deleteTapInterface is not supported with the network feature disabled"),
555 ))
556 .with_log();
557 }
558
559 // TODO(340377643): Disabling tethering should be for bridge interface, not TAP interface.
560 TETHERING_SERVICE.disableVmTethering()?;
561
562 NETWORK_SERVICE.deleteTapInterface(tap_fd)
563 }
564
claimSecretkeeperEntry(&self, id: &[u8; 64]) -> binder::Result<()>565 fn claimSecretkeeperEntry(&self, id: &[u8; 64]) -> binder::Result<()> {
566 info!("Claiming Secretkeeper entry: {:?}", hex::encode(id));
567 self.try_updating_sk_state(id);
568 Ok(())
569 }
570 }
571
572 impl IVirtualizationMaintenance for VirtualizationServiceInternal {
appRemoved(&self, user_id: i32, app_id: i32) -> binder::Result<()>573 fn appRemoved(&self, user_id: i32, app_id: i32) -> binder::Result<()> {
574 let state = &mut *self.state.lock().unwrap();
575 if let Some(sk_state) = &mut state.sk_state {
576 info!("packageRemoved(user_id={user_id}, app_id={app_id})");
577 sk_state.delete_ids_for_app(user_id, app_id).or_service_specific_exception(-1)?;
578 } else {
579 info!("ignoring packageRemoved(user_id={user_id}, app_id={app_id})");
580 }
581 Ok(())
582 }
583
userRemoved(&self, user_id: i32) -> binder::Result<()>584 fn userRemoved(&self, user_id: i32) -> binder::Result<()> {
585 let state = &mut *self.state.lock().unwrap();
586 if let Some(sk_state) = &mut state.sk_state {
587 info!("userRemoved({user_id})");
588 sk_state.delete_ids_for_user(user_id).or_service_specific_exception(-1)?;
589 } else {
590 info!("ignoring userRemoved(user_id={user_id})");
591 }
592 Ok(())
593 }
594
performReconciliation( &self, callback: &Strong<dyn IVirtualizationReconciliationCallback>, ) -> binder::Result<()>595 fn performReconciliation(
596 &self,
597 callback: &Strong<dyn IVirtualizationReconciliationCallback>,
598 ) -> binder::Result<()> {
599 let state = &mut *self.state.lock().unwrap();
600 if let Some(sk_state) = &mut state.sk_state {
601 info!("performReconciliation()");
602 sk_state.reconcile(callback).or_service_specific_exception(-1)?;
603 } else {
604 info!("ignoring performReconciliation()");
605 }
606 Ok(())
607 }
608 }
609
610 #[derive(Debug, Deserialize)]
611 struct Device {
612 dtbo_label: String,
613 sysfs_path: String,
614 }
615
616 #[derive(Debug, Default, Deserialize)]
617 struct Devices {
618 device: Vec<Device>,
619 }
620
get_assignable_devices() -> binder::Result<Devices>621 fn get_assignable_devices() -> binder::Result<Devices> {
622 let xml_path = Path::new("/vendor/etc/avf/assignable_devices.xml");
623 if !xml_path.exists() {
624 return Ok(Devices { ..Default::default() });
625 }
626
627 let xml = fs::read(xml_path)
628 .context("Failed to read assignable_devices.xml")
629 .with_log()
630 .or_service_specific_exception(-1)?;
631
632 let xml = String::from_utf8(xml)
633 .context("assignable_devices.xml is not a valid UTF-8 file")
634 .with_log()
635 .or_service_specific_exception(-1)?;
636
637 let mut devices: Devices = serde_xml_rs::from_str(&xml)
638 .context("can't parse assignable_devices.xml")
639 .with_log()
640 .or_service_specific_exception(-1)?;
641
642 let mut device_set = HashSet::new();
643 devices.device.retain(move |device| {
644 if device_set.contains(&device.sysfs_path) {
645 warn!("duplicated assignable device {device:?}; ignoring...");
646 return false;
647 }
648
649 if !Path::new(&device.sysfs_path).exists() {
650 warn!("assignable device {device:?} doesn't exist; ignoring...");
651 return false;
652 }
653
654 device_set.insert(device.sysfs_path.clone());
655 true
656 });
657 Ok(devices)
658 }
659
split_x509_certificate_chain(mut cert_chain: &[u8]) -> Result<Vec<Certificate>>660 fn split_x509_certificate_chain(mut cert_chain: &[u8]) -> Result<Vec<Certificate>> {
661 let mut out = Vec::new();
662 while !cert_chain.is_empty() {
663 let cert = X509::from_der(cert_chain)?;
664 let end = cert.to_der()?.len();
665 out.push(Certificate { encodedCertificate: cert_chain[..end].to_vec() });
666 cert_chain = &cert_chain[end..];
667 }
668 Ok(out)
669 }
670
671 #[derive(Debug, Default)]
672 struct GlobalVmInstance {
673 /// Name of the VM
674 name: String,
675 /// The unique CID assigned to the VM for vsock communication.
676 cid: Cid,
677 /// UID of the client who requested this VM instance.
678 requester_uid: uid_t,
679 /// PID of the client who requested this VM instance.
680 requester_debug_pid: pid_t,
681 /// Name of the host console.
682 host_console_name: Option<String>,
683 }
684
685 impl GlobalVmInstance {
get_temp_dir(&self) -> PathBuf686 fn get_temp_dir(&self) -> PathBuf {
687 let cid = self.cid;
688 format!("{TEMPORARY_DIRECTORY}/{cid}").into()
689 }
690 }
691
692 /// The mutable state of the VirtualizationServiceInternal. There should only be one instance
693 /// of this struct.
694 struct GlobalState {
695 /// VM contexts currently allocated to running VMs. A CID is never recycled as long
696 /// as there is a strong reference held by a GlobalVmContext.
697 held_contexts: HashMap<Cid, Weak<Mutex<GlobalVmInstance>>>,
698
699 /// Cached read-only FD of VM DTBO file. Also serves as a lock for creating the file.
700 dtbo_file: Mutex<Option<File>>,
701
702 /// State relating to secrets held by (optional) Secretkeeper instance on behalf of VMs.
703 sk_state: Option<maintenance::State>,
704
705 display_service: Option<binder::SpIBinder>,
706 }
707
708 impl GlobalState {
new() -> Self709 fn new() -> Self {
710 Self {
711 held_contexts: HashMap::new(),
712 dtbo_file: Mutex::new(None),
713 sk_state: maintenance::State::new(),
714 display_service: None,
715 }
716 }
717
718 /// Get the next available CID, or an error if we have run out. The last CID used is stored in
719 /// a system property so that restart of virtualizationservice doesn't reuse CID while the host
720 /// Android is up.
get_next_available_cid(&mut self) -> Result<Cid>721 fn get_next_available_cid(&mut self) -> Result<Cid> {
722 // Start trying to find a CID from the last used CID + 1. This ensures
723 // that we do not eagerly recycle CIDs. It makes debugging easier but
724 // also means that retrying to allocate a CID, eg. because it is
725 // erroneously occupied by a process, will not recycle the same CID.
726 let last_cid_prop =
727 system_properties::read(SYSPROP_LAST_CID)?.and_then(|val| match val.parse::<Cid>() {
728 Ok(num) => {
729 if is_valid_guest_cid(num) {
730 Some(num)
731 } else {
732 error!("Invalid value '{}' of property '{}'", num, SYSPROP_LAST_CID);
733 None
734 }
735 }
736 Err(_) => {
737 error!("Invalid value '{}' of property '{}'", val, SYSPROP_LAST_CID);
738 None
739 }
740 });
741
742 let first_cid = if let Some(last_cid) = last_cid_prop {
743 if last_cid == GUEST_CID_MAX {
744 GUEST_CID_MIN
745 } else {
746 last_cid + 1
747 }
748 } else {
749 GUEST_CID_MIN
750 };
751
752 let cid = self
753 .find_available_cid(first_cid..=GUEST_CID_MAX)
754 .or_else(|| self.find_available_cid(GUEST_CID_MIN..first_cid))
755 .ok_or_else(|| anyhow!("Could not find an available CID."))?;
756
757 system_properties::write(SYSPROP_LAST_CID, &format!("{}", cid))?;
758 Ok(cid)
759 }
760
find_available_cid<I>(&self, mut range: I) -> Option<Cid> where I: Iterator<Item = Cid>,761 fn find_available_cid<I>(&self, mut range: I) -> Option<Cid>
762 where
763 I: Iterator<Item = Cid>,
764 {
765 range.find(|cid| !self.held_contexts.contains_key(cid))
766 }
767
allocate_vm_context( &mut self, name: &str, requester_uid: uid_t, requester_debug_pid: pid_t, ) -> Result<Strong<dyn IGlobalVmContext>>768 fn allocate_vm_context(
769 &mut self,
770 name: &str,
771 requester_uid: uid_t,
772 requester_debug_pid: pid_t,
773 ) -> Result<Strong<dyn IGlobalVmContext>> {
774 // Garbage collect unused VM contexts.
775 self.held_contexts.retain(|_, instance| instance.strong_count() > 0);
776
777 let cid = self.get_next_available_cid()?;
778 let instance = Arc::new(Mutex::new(GlobalVmInstance {
779 name: name.to_owned(),
780 cid,
781 requester_uid,
782 requester_debug_pid,
783 ..Default::default()
784 }));
785 create_temporary_directory(&instance.lock().unwrap().get_temp_dir(), Some(requester_uid))?;
786
787 self.held_contexts.insert(cid, Arc::downgrade(&instance));
788 let binder = GlobalVmContext { instance, ..Default::default() };
789 Ok(BnGlobalVmContext::new_binder(binder, BinderFeatures::default()))
790 }
791
get_dtbo_file(&mut self) -> Result<File>792 fn get_dtbo_file(&mut self) -> Result<File> {
793 let mut file = self.dtbo_file.lock().unwrap();
794
795 let fd = if let Some(ref_fd) = &*file {
796 ref_fd.try_clone()?
797 } else {
798 let path = get_or_create_common_dir()?.join("vm.dtbo");
799 if path.exists() {
800 // All temporary files are deleted when the service is started.
801 // If the file exists but the FD is not cached, the file is
802 // likely corrupted.
803 remove_file(&path).context("Failed to clone cached VM DTBO file descriptor")?;
804 }
805
806 // Open a write-only file descriptor for vfio_handler.
807 let write_fd = File::create(&path).context("Failed to create VM DTBO file")?;
808 VFIO_SERVICE.writeVmDtbo(&ParcelFileDescriptor::new(write_fd))?;
809
810 // Open read-only. This FD will be cached and returned to clients.
811 let read_fd = File::open(&path).context("Failed to open VM DTBO file")?;
812 let read_fd_clone =
813 read_fd.try_clone().context("Failed to clone VM DTBO file descriptor")?;
814 *file = Some(read_fd);
815 read_fd_clone
816 };
817
818 Ok(fd)
819 }
820 }
821
create_temporary_directory(path: &PathBuf, requester_uid: Option<uid_t>) -> Result<()>822 fn create_temporary_directory(path: &PathBuf, requester_uid: Option<uid_t>) -> Result<()> {
823 // Directory may exist if previous attempt to create it had failed.
824 // Delete it before trying again.
825 if path.as_path().exists() {
826 remove_temporary_dir(path).unwrap_or_else(|e| {
827 warn!("Could not delete temporary directory {:?}: {}", path, e);
828 });
829 }
830 // Create directory.
831 create_dir(path).with_context(|| format!("Could not create temporary directory {:?}", path))?;
832 // If provided, change ownership to client's UID but system's GID, and permissions 0700.
833 // If the chown() fails, this will leave behind an empty directory that will get removed
834 // at the next attempt, or if virtualizationservice is restarted.
835 if let Some(uid) = requester_uid {
836 chown(path, Some(Uid::from_raw(uid)), None).with_context(|| {
837 format!("Could not set ownership of temporary directory {:?}", path)
838 })?;
839 }
840 Ok(())
841 }
842
843 /// Removes a directory owned by a different user by first changing its owner back
844 /// to VirtualizationService.
remove_temporary_dir(path: &PathBuf) -> Result<()>845 pub fn remove_temporary_dir(path: &PathBuf) -> Result<()> {
846 ensure!(path.as_path().is_dir(), "Path {:?} is not a directory", path);
847 chown(path, Some(Uid::current()), None)?;
848 set_permissions(path, Permissions::from_mode(0o700))?;
849 remove_dir_all(path)?;
850 Ok(())
851 }
852
get_or_create_common_dir() -> Result<PathBuf>853 fn get_or_create_common_dir() -> Result<PathBuf> {
854 let path = Path::new(TEMPORARY_DIRECTORY).join("common");
855 if !path.exists() {
856 create_temporary_directory(&path, None)?;
857 }
858 Ok(path)
859 }
860
861 /// Implementation of the AIDL `IGlobalVmContext` interface.
862 #[derive(Debug, Default)]
863 struct GlobalVmContext {
864 /// Strong reference to the context's instance data structure.
865 instance: Arc<Mutex<GlobalVmInstance>>,
866 /// Keeps our service process running as long as this VM context exists.
867 #[allow(dead_code)]
868 lazy_service_guard: LazyServiceGuard,
869 }
870
871 impl Interface for GlobalVmContext {}
872
873 impl IGlobalVmContext for GlobalVmContext {
getCid(&self) -> binder::Result<i32>874 fn getCid(&self) -> binder::Result<i32> {
875 Ok(self.instance.lock().unwrap().cid as i32)
876 }
877
getTemporaryDirectory(&self) -> binder::Result<String>878 fn getTemporaryDirectory(&self) -> binder::Result<String> {
879 Ok(self.instance.lock().unwrap().get_temp_dir().to_string_lossy().to_string())
880 }
881
setHostConsoleName(&self, pathname: &str) -> binder::Result<()>882 fn setHostConsoleName(&self, pathname: &str) -> binder::Result<()> {
883 self.instance.lock().unwrap().host_console_name = Some(pathname.to_string());
884 Ok(())
885 }
886 }
887
handle_stream_connection_tombstoned() -> Result<()>888 fn handle_stream_connection_tombstoned() -> Result<()> {
889 // Should not listen for tombstones on a guest VM's port.
890 assert!(!is_valid_guest_cid(VM_TOMBSTONES_SERVICE_PORT as Cid));
891 let listener =
892 VsockListener::bind_with_cid_port(VMADDR_CID_HOST, VM_TOMBSTONES_SERVICE_PORT as Cid)?;
893 for incoming_stream in listener.incoming() {
894 let mut incoming_stream = match incoming_stream {
895 Err(e) => {
896 warn!("invalid incoming connection: {e:?}");
897 continue;
898 }
899 Ok(s) => s,
900 };
901 if let Ok(addr) = incoming_stream.peer_addr() {
902 let cid = addr.cid();
903 match cid {
904 VMADDR_CID_LOCAL | VMADDR_CID_HOST | VMADDR_CID_HYPERVISOR => {
905 warn!("Rejecting non-guest tombstone vsock connection from cid={cid}");
906 continue;
907 }
908 _ => info!("Vsock Stream connected to cid={cid} for tombstones"),
909 }
910 }
911 std::thread::spawn(move || {
912 if let Err(e) = handle_tombstone(&mut incoming_stream) {
913 error!("Failed to write tombstone- {:?}", e);
914 }
915 });
916 }
917 Ok(())
918 }
919
handle_tombstone(stream: &mut VsockStream) -> Result<()>920 fn handle_tombstone(stream: &mut VsockStream) -> Result<()> {
921 let tb_connection =
922 TombstonedConnection::connect(std::process::id() as i32, DebuggerdDumpType::Tombstone)
923 .context("Failed to connect to tombstoned")?;
924 let mut text_output = tb_connection
925 .text_output
926 .as_ref()
927 .ok_or_else(|| anyhow!("Could not get file to write the tombstones on"))?;
928 let mut num_bytes_read = 0;
929 loop {
930 let mut chunk_recv = [0; CHUNK_RECV_MAX_LEN];
931 let n = stream
932 .read(&mut chunk_recv)
933 .context("Failed to read tombstone data from Vsock stream")?;
934 if n == 0 {
935 break;
936 }
937 num_bytes_read += n;
938 text_output.write_all(&chunk_recv[0..n]).context("Failed to write guests tombstones")?;
939 }
940 info!("Received {} bytes from guest & wrote to tombstone file", num_bytes_read);
941 tb_connection.notify_completion()?;
942 Ok(())
943 }
944
945 /// Returns true if the AVF remotely provisioned component service is declared in the
946 /// VINTF manifest.
is_remote_provisioning_hal_declared() -> binder::Result<bool>947 pub(crate) fn is_remote_provisioning_hal_declared() -> binder::Result<bool> {
948 Ok(binder::is_declared(REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME)?)
949 }
950
951 /// Checks whether the caller has a specific permission
check_permission(perm: &str) -> binder::Result<()>952 fn check_permission(perm: &str) -> binder::Result<()> {
953 let calling_pid = get_calling_pid();
954 let calling_uid = get_calling_uid();
955 // Root can do anything
956 if calling_uid == 0 {
957 return Ok(());
958 }
959 let perm_svc: Strong<dyn IPermissionController::IPermissionController> =
960 binder::wait_for_interface("permission")?;
961 if perm_svc.checkPermission(perm, calling_pid, calling_uid as i32)? {
962 Ok(())
963 } else {
964 Err(anyhow!("does not have the {} permission", perm))
965 .or_binder_exception(ExceptionCode::SECURITY)
966 }
967 }
968
969 /// Check whether the caller of the current Binder method is allowed to call debug methods.
check_debug_access() -> binder::Result<()>970 fn check_debug_access() -> binder::Result<()> {
971 check_permission("android.permission.DEBUG_VIRTUAL_MACHINE")
972 }
973
974 /// Check whether the caller of the current Binder method is allowed to manage VMs
check_manage_access() -> binder::Result<()>975 fn check_manage_access() -> binder::Result<()> {
976 check_permission("android.permission.MANAGE_VIRTUAL_MACHINE")
977 }
978
979 /// Check whether the caller of the current Binder method is allowed to use custom VMs
check_use_custom_virtual_machine() -> binder::Result<()>980 fn check_use_custom_virtual_machine() -> binder::Result<()> {
981 check_permission("android.permission.USE_CUSTOM_VIRTUAL_MACHINE")
982 }
983
984 /// Check whether the caller of the current Binder method is allowed to create socket and
985 /// establish connection between the VM and the Internet.
check_internet_permission() -> binder::Result<()>986 fn check_internet_permission() -> binder::Result<()> {
987 check_permission("android.permission.INTERNET")
988 }
989
990 #[cfg(test)]
991 mod tests {
992 use super::*;
993
994 const TEST_RKP_CERT_CHAIN_PATH: &str = "testdata/rkp_cert_chain.der";
995
996 #[test]
splitting_x509_certificate_chain_succeeds() -> Result<()>997 fn splitting_x509_certificate_chain_succeeds() -> Result<()> {
998 let bytes = fs::read(TEST_RKP_CERT_CHAIN_PATH)?;
999 let cert_chain = split_x509_certificate_chain(&bytes)?;
1000
1001 assert_eq!(4, cert_chain.len());
1002 for cert in cert_chain {
1003 let x509_cert = X509::from_der(&cert.encodedCertificate)?;
1004 assert_eq!(x509_cert.to_der()?.len(), cert.encodedCertificate.len());
1005 }
1006 Ok(())
1007 }
1008 }
1009