blob: c6150b25f37abf4a1e23e63c1aa0f8c6363c333f [file] [log] [blame]
David Brazdilafc9a9e2023-01-12 16:08:10 +00001// 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
David Brazdil33a31022023-01-12 16:55:16 +000017use crate::atom::{forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom};
David Drysdale79af2662024-02-19 14:50:31 +000018use crate::maintenance;
Alice Wange64dd182024-01-17 15:57:55 +000019use crate::remote_provisioning;
Alan Stokesac667072024-02-19 16:26:00 +000020use crate::rkpvm::{generate_ecdsa_p256_key_pair, request_attestation};
21use crate::{get_calling_pid, get_calling_uid, REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME};
David Brazdilafc9a9e2023-01-12 16:08:10 +000022use android_os_permissions_aidl::aidl::android::os::IPermissionController;
Alan Stokesac667072024-02-19 16:26:00 +000023use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon;
David Drysdale79af2662024-02-19 14:50:31 +000024use android_system_virtualizationmaintenance::aidl::android::system::virtualizationmaintenance;
Alan Stokesac667072024-02-19 16:26:00 +000025use android_system_virtualizationservice::aidl::android::system::virtualizationservice;
26use android_system_virtualizationservice_internal as android_vs_internal;
27use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice;
28use android_vs_internal::aidl::android::system::virtualizationservice_internal;
Alice Wangd1b11a02023-04-18 12:30:20 +000029use anyhow::{anyhow, ensure, Context, Result};
Jiyong Parkd7bd2f22023-08-10 20:41:19 +090030use avflog::LogResult;
Alan Stokesac667072024-02-19 16:26:00 +000031use binder::{
32 self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, IntoBinderResult,
33 LazyServiceGuard, ParcelFileDescriptor, Status, Strong,
34};
Jakob Vukalovicd42aa2c2023-11-09 16:04:00 +000035use lazy_static::lazy_static;
David Brazdilafc9a9e2023-01-12 16:08:10 +000036use libc::VMADDR_CID_HOST;
37use log::{error, info, warn};
Alan Stokesac667072024-02-19 16:26:00 +000038use nix::unistd::{chown, Uid};
39use openssl::x509::X509;
Shikha Panwar61a74b52024-02-16 13:17:01 +000040use rand::Fill;
Alice Wangbff017f2023-11-09 14:43:28 +000041use rkpd_client::get_rkpd_attestation_key;
David Drysdalee64de8e2024-02-29 11:54:29 +000042use rustutils::{
43 system_properties,
44 users::{multiuser_get_app_id, multiuser_get_user_id},
45};
Inseob Kimc4a774d2023-08-30 12:48:43 +090046use serde::Deserialize;
Alan Stokesac667072024-02-19 16:26:00 +000047use service_vm_comm::Response;
Inseob Kimc4a774d2023-08-30 12:48:43 +090048use std::collections::{HashMap, HashSet};
David Brazdil2dfefd12023-11-17 14:07:36 +000049use std::fs::{self, create_dir, remove_dir_all, remove_file, set_permissions, File, Permissions};
David Brazdilafc9a9e2023-01-12 16:08:10 +000050use std::io::{Read, Write};
51use std::os::unix::fs::PermissionsExt;
52use std::os::unix::raw::{pid_t, uid_t};
Inseob Kim55438b22023-08-09 20:16:01 +090053use std::path::{Path, PathBuf};
David Brazdilafc9a9e2023-01-12 16:08:10 +000054use std::sync::{Arc, Mutex, Weak};
55use tombstoned_client::{DebuggerdDumpType, TombstonedConnection};
Alan Stokesac667072024-02-19 16:26:00 +000056use virtualizationcommon::Certificate::Certificate;
Alan Stokes30ccacb2024-02-20 14:59:02 +000057use virtualizationmaintenance::{
58 IVirtualizationMaintenance::IVirtualizationMaintenance,
59 IVirtualizationReconciliationCallback::IVirtualizationReconciliationCallback,
60};
Alan Stokesac667072024-02-19 16:26:00 +000061use virtualizationservice::{
62 AssignableDevice::AssignableDevice, VirtualMachineDebugInfo::VirtualMachineDebugInfo,
63};
64use 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},
David Drysdale79af2662024-02-19 14:50:31 +000072 IVirtualizationServiceInternal::IVirtualizationServiceInternal,
Alan Stokesac667072024-02-19 16:26:00 +000073};
74use virtualmachineservice::IVirtualMachineService::VM_TOMBSTONES_SERVICE_PORT;
David Brazdilafc9a9e2023-01-12 16:08:10 +000075use vsock::{VsockListener, VsockStream};
David Brazdilafc9a9e2023-01-12 16:08:10 +000076
77/// The unique ID of a VM used (together with a port number) for vsock communication.
78pub type Cid = u32;
79
David Brazdilafc9a9e2023-01-12 16:08:10 +000080/// Directory in which to write disk image files used while running VMs.
81pub const TEMPORARY_DIRECTORY: &str = "/data/misc/virtualizationservice";
82
83/// The first CID to assign to a guest VM managed by the VirtualizationService. CIDs lower than this
84/// are reserved for the host or other usage.
85const GUEST_CID_MIN: Cid = 2048;
86const GUEST_CID_MAX: Cid = 65535;
87
88const SYSPROP_LAST_CID: &str = "virtualizationservice.state.last_cid";
89
90const CHUNK_RECV_MAX_LEN: usize = 1024;
91
Alice Wange64dd182024-01-17 15:57:55 +000092/// The fake certificate is used for testing only when a client VM requests attestation in test
93/// mode, it is a single certificate extracted on an unregistered device for testing.
94/// Here is the snapshot of the certificate:
95///
96/// ```
97/// Certificate:
98/// Data:
99/// Version: 3 (0x2)
100/// Serial Number:
101/// 59:ae:50:98:95:e1:34:25:f1:21:93:c0:4c:e5:24:66
102/// Signature Algorithm: ecdsa-with-SHA256
103/// Issuer: CN = Droid Unregistered Device CA, O = Google Test LLC
104/// Validity
105/// Not Before: Feb 5 14:39:39 2024 GMT
106/// Not After : Feb 14 14:39:39 2024 GMT
107/// Subject: CN = 59ae509895e13425f12193c04ce52466, O = TEE
108/// Subject Public Key Info:
109/// Public Key Algorithm: id-ecPublicKey
110/// Public-Key: (256 bit)
111/// pub:
112/// 04:30:32:cd:95:12:b0:71:8b:b7:14:44:26:58:d5:
113/// 82:8c:25:55:2c:6d:ef:98:e3:4f:88:d0:74:82:09:
114/// 3e:8d:6c:f0:f2:18:d5:83:0e:0d:f2:ce:c5:15:38:
115/// e5:6a:e6:4d:4d:95:15:b7:24:e7:cb:4b:63:42:21:
116/// bc:36:c6:0a:d8
117/// ASN1 OID: prime256v1
118/// NIST CURVE: P-256
119/// X509v3 extensions:
120/// ...
121/// ```
122const FAKE_CERTIFICATE_FOR_TESTING: &[u8] = &[
123 0x30, 0x82, 0x01, 0xee, 0x30, 0x82, 0x01, 0x94, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x59,
124 0xae, 0x50, 0x98, 0x95, 0xe1, 0x34, 0x25, 0xf1, 0x21, 0x93, 0xc0, 0x4c, 0xe5, 0x24, 0x66, 0x30,
125 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x41, 0x31, 0x25, 0x30,
126 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c, 0x44, 0x72, 0x6f, 0x69, 0x64, 0x20, 0x55, 0x6e,
127 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x20, 0x44, 0x65, 0x76, 0x69, 0x63,
128 0x65, 0x20, 0x43, 0x41, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47,
129 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x4c, 0x4c, 0x43, 0x30, 0x1e,
130 0x17, 0x0d, 0x32, 0x34, 0x30, 0x32, 0x30, 0x35, 0x31, 0x34, 0x33, 0x39, 0x33, 0x39, 0x5a, 0x17,
131 0x0d, 0x32, 0x34, 0x30, 0x32, 0x31, 0x34, 0x31, 0x34, 0x33, 0x39, 0x33, 0x39, 0x5a, 0x30, 0x39,
132 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x35, 0x39, 0x61, 0x65, 0x35,
133 0x30, 0x39, 0x38, 0x39, 0x35, 0x65, 0x31, 0x33, 0x34, 0x32, 0x35, 0x66, 0x31, 0x32, 0x31, 0x39,
134 0x33, 0x63, 0x30, 0x34, 0x63, 0x65, 0x35, 0x32, 0x34, 0x36, 0x36, 0x31, 0x0c, 0x30, 0x0a, 0x06,
135 0x03, 0x55, 0x04, 0x0a, 0x13, 0x03, 0x54, 0x45, 0x45, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a,
136 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07,
137 0x03, 0x42, 0x00, 0x04, 0x30, 0x32, 0xcd, 0x95, 0x12, 0xb0, 0x71, 0x8b, 0xb7, 0x14, 0x44, 0x26,
138 0x58, 0xd5, 0x82, 0x8c, 0x25, 0x55, 0x2c, 0x6d, 0xef, 0x98, 0xe3, 0x4f, 0x88, 0xd0, 0x74, 0x82,
139 0x09, 0x3e, 0x8d, 0x6c, 0xf0, 0xf2, 0x18, 0xd5, 0x83, 0x0e, 0x0d, 0xf2, 0xce, 0xc5, 0x15, 0x38,
140 0xe5, 0x6a, 0xe6, 0x4d, 0x4d, 0x95, 0x15, 0xb7, 0x24, 0xe7, 0xcb, 0x4b, 0x63, 0x42, 0x21, 0xbc,
141 0x36, 0xc6, 0x0a, 0xd8, 0xa3, 0x76, 0x30, 0x74, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
142 0x16, 0x04, 0x14, 0x39, 0x81, 0x41, 0x0a, 0xb9, 0xf3, 0xf4, 0x5b, 0x75, 0x97, 0x4a, 0x46, 0xd6,
143 0x30, 0x9e, 0x1d, 0x7a, 0x3b, 0xec, 0xa8, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
144 0x30, 0x16, 0x80, 0x14, 0x82, 0xbd, 0x00, 0xde, 0xcb, 0xc5, 0xe7, 0x72, 0x87, 0x3d, 0x1c, 0x0a,
145 0x1e, 0x78, 0x4f, 0xf5, 0xd3, 0xc1, 0x3e, 0xb8, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
146 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
147 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x02, 0x04, 0x30, 0x11, 0x06, 0x0a, 0x2b, 0x06, 0x01,
148 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x1e, 0x04, 0x03, 0xa1, 0x01, 0x08, 0x30, 0x0a, 0x06, 0x08,
149 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x21, 0x00,
150 0xae, 0xd8, 0x40, 0x9e, 0x37, 0x3e, 0x5c, 0x9c, 0xe2, 0x93, 0x3d, 0x8c, 0xf7, 0x05, 0x10, 0xe7,
151 0xd1, 0x2b, 0x87, 0x8a, 0xee, 0xd6, 0x1e, 0x6c, 0x3b, 0xd2, 0x91, 0x3e, 0xa5, 0xdf, 0x91, 0x20,
152 0x02, 0x20, 0x7f, 0x0f, 0x29, 0x54, 0x60, 0x80, 0x07, 0x50, 0x5f, 0x56, 0x6b, 0x9f, 0xe0, 0x94,
153 0xb4, 0x3f, 0x3b, 0x0f, 0x61, 0xa0, 0x33, 0x40, 0xe6, 0x1a, 0x42, 0xda, 0x4b, 0xa4, 0xfd, 0x92,
154 0xb9, 0x0f,
155];
156
Jakob Vukalovicd42aa2c2023-11-09 16:04:00 +0000157lazy_static! {
Alice Wange64dd182024-01-17 15:57:55 +0000158 static ref FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING: Mutex<Option<Vec<u8>>> = Mutex::new(None);
Jakob Vukalovicd42aa2c2023-11-09 16:04:00 +0000159 static ref VFIO_SERVICE: Strong<dyn IVfioHandler> =
160 wait_for_interface(<BpVfioHandler as IVfioHandler>::get_descriptor())
161 .expect("Could not connect to VfioHandler");
162}
163
David Brazdilafc9a9e2023-01-12 16:08:10 +0000164fn is_valid_guest_cid(cid: Cid) -> bool {
165 (GUEST_CID_MIN..=GUEST_CID_MAX).contains(&cid)
166}
167
168/// Singleton service for allocating globally-unique VM resources, such as the CID, and running
169/// singleton servers, like tombstone receiver.
David Drysdale79af2662024-02-19 14:50:31 +0000170#[derive(Clone)]
David Brazdilafc9a9e2023-01-12 16:08:10 +0000171pub struct VirtualizationServiceInternal {
172 state: Arc<Mutex<GlobalState>>,
173}
174
175impl VirtualizationServiceInternal {
David Drysdale79af2662024-02-19 14:50:31 +0000176 pub fn init() -> VirtualizationServiceInternal {
177 let service =
178 VirtualizationServiceInternal { state: Arc::new(Mutex::new(GlobalState::new())) };
David Brazdilafc9a9e2023-01-12 16:08:10 +0000179
180 std::thread::spawn(|| {
181 if let Err(e) = handle_stream_connection_tombstoned() {
182 warn!("Error receiving tombstone from guest or writing them. Error: {:?}", e);
183 }
184 });
185
David Drysdale79af2662024-02-19 14:50:31 +0000186 service
David Brazdilafc9a9e2023-01-12 16:08:10 +0000187 }
188}
189
190impl Interface for VirtualizationServiceInternal {}
191
192impl IVirtualizationServiceInternal for VirtualizationServiceInternal {
193 fn removeMemlockRlimit(&self) -> binder::Result<()> {
194 let pid = get_calling_pid();
195 let lim = libc::rlimit { rlim_cur: libc::RLIM_INFINITY, rlim_max: libc::RLIM_INFINITY };
196
Andrew Walbranb58d1b42023-07-07 13:54:49 +0100197 // SAFETY: borrowing the new limit struct only
David Brazdilafc9a9e2023-01-12 16:08:10 +0000198 let ret = unsafe { libc::prlimit(pid, libc::RLIMIT_MEMLOCK, &lim, std::ptr::null_mut()) };
199
200 match ret {
201 0 => Ok(()),
Jiyong Park2227eaa2023-08-04 11:59:18 +0900202 -1 => Err(std::io::Error::last_os_error().into()),
203 n => Err(anyhow!("Unexpected return value from prlimit(): {n}")),
David Brazdilafc9a9e2023-01-12 16:08:10 +0000204 }
Jiyong Park2227eaa2023-08-04 11:59:18 +0900205 .or_binder_exception(ExceptionCode::ILLEGAL_STATE)
David Brazdilafc9a9e2023-01-12 16:08:10 +0000206 }
207
208 fn allocateGlobalVmContext(
209 &self,
210 requester_debug_pid: i32,
211 ) -> binder::Result<Strong<dyn IGlobalVmContext>> {
212 check_manage_access()?;
213
214 let requester_uid = get_calling_uid();
215 let requester_debug_pid = requester_debug_pid as pid_t;
216 let state = &mut *self.state.lock().unwrap();
Jiyong Park2227eaa2023-08-04 11:59:18 +0900217 state
218 .allocate_vm_context(requester_uid, requester_debug_pid)
219 .or_binder_exception(ExceptionCode::ILLEGAL_STATE)
David Brazdilafc9a9e2023-01-12 16:08:10 +0000220 }
221
222 fn atomVmBooted(&self, atom: &AtomVmBooted) -> Result<(), Status> {
223 forward_vm_booted_atom(atom);
224 Ok(())
225 }
226
227 fn atomVmCreationRequested(&self, atom: &AtomVmCreationRequested) -> Result<(), Status> {
228 forward_vm_creation_atom(atom);
229 Ok(())
230 }
231
232 fn atomVmExited(&self, atom: &AtomVmExited) -> Result<(), Status> {
233 forward_vm_exited_atom(atom);
234 Ok(())
235 }
236
237 fn debugListVms(&self) -> binder::Result<Vec<VirtualMachineDebugInfo>> {
238 check_debug_access()?;
239
240 let state = &mut *self.state.lock().unwrap();
241 let cids = state
242 .held_contexts
243 .iter()
244 .filter_map(|(_, inst)| Weak::upgrade(inst))
245 .map(|vm| VirtualMachineDebugInfo {
246 cid: vm.cid as i32,
247 temporaryDirectory: vm.get_temp_dir().to_string_lossy().to_string(),
248 requesterUid: vm.requester_uid as i32,
Charisee96113f32023-01-26 09:00:42 +0000249 requesterPid: vm.requester_debug_pid,
David Brazdilafc9a9e2023-01-12 16:08:10 +0000250 })
251 .collect();
252 Ok(cids)
253 }
Alice Wangc2fec932023-02-23 16:24:02 +0000254
Alice Wange64dd182024-01-17 15:57:55 +0000255 fn enableTestAttestation(&self) -> binder::Result<()> {
256 check_manage_access()?;
257 check_use_custom_virtual_machine()?;
258 if !cfg!(remote_attestation) {
259 return Err(Status::new_exception_str(
260 ExceptionCode::UNSUPPORTED_OPERATION,
261 Some(
262 "enableTestAttestation is not supported with the remote_attestation \
263 feature disabled",
264 ),
265 ))
266 .with_log();
267 }
268 let res = generate_ecdsa_p256_key_pair()
269 .context("Failed to generate ECDSA P-256 key pair for testing")
270 .with_log()
271 .or_service_specific_exception(-1)?;
Alice Wang5daec072024-03-15 15:31:17 +0000272 // Wait until the service VM shuts down, so that the Service VM will be restarted when
273 // the key generated in the current session will be used for attestation.
274 // This ensures that different Service VM sessions have the same KEK for the key blob.
275 service_vm_manager::wait_until_service_vm_shuts_down()
276 .context("Failed to wait until the service VM shuts down")
277 .with_log()
278 .or_service_specific_exception(-1)?;
Alice Wange64dd182024-01-17 15:57:55 +0000279 match res {
280 Response::GenerateEcdsaP256KeyPair(key_pair) => {
281 FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING
282 .lock()
283 .unwrap()
284 .replace(key_pair.key_blob.to_vec());
285 Ok(())
286 }
287 _ => Err(remote_provisioning::to_service_specific_error(res)),
288 }
289 .with_log()
290 }
291
Alice Wangbff017f2023-11-09 14:43:28 +0000292 fn requestAttestation(
293 &self,
294 csr: &[u8],
295 requester_uid: i32,
Alice Wange64dd182024-01-17 15:57:55 +0000296 test_mode: bool,
Alice Wangbff017f2023-11-09 14:43:28 +0000297 ) -> binder::Result<Vec<Certificate>> {
Alice Wangc2fec932023-02-23 16:24:02 +0000298 check_manage_access()?;
Alice Wang4c6c5582023-11-23 15:07:18 +0000299 if !cfg!(remote_attestation) {
300 return Err(Status::new_exception_str(
Alice Wange9ac2db2023-09-08 15:13:13 +0000301 ExceptionCode::UNSUPPORTED_OPERATION,
302 Some(
Alice Wanga410b642023-10-18 09:05:15 +0000303 "requestAttestation is not supported with the remote_attestation feature \
304 disabled",
Alice Wange9ac2db2023-09-08 15:13:13 +0000305 ),
306 ))
Alice Wang4c6c5582023-11-23 15:07:18 +0000307 .with_log();
Alice Wange9ac2db2023-09-08 15:13:13 +0000308 }
Alice Wang0dcab552024-03-20 14:42:30 +0000309 if !remotely_provisioned_component_service_exists()? {
310 return Err(Status::new_exception_str(
311 ExceptionCode::UNSUPPORTED_OPERATION,
312 Some("AVF remotely provisioned component service is not declared"),
313 ))
314 .with_log();
315 }
Alice Wang4c6c5582023-11-23 15:07:18 +0000316 info!("Received csr. Requestting attestation...");
Alice Wange64dd182024-01-17 15:57:55 +0000317 let (key_blob, certificate_chain) = if test_mode {
318 check_use_custom_virtual_machine()?;
319 info!("Using the fake key blob for testing...");
320 (
321 FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING
322 .lock()
323 .unwrap()
324 .clone()
325 .ok_or_else(|| anyhow!("No key blob for testing"))
326 .with_log()
327 .or_service_specific_exception(-1)?,
328 FAKE_CERTIFICATE_FOR_TESTING.to_vec(),
329 )
330 } else {
331 info!("Retrieving the remotely provisioned keys from RKPD...");
332 let attestation_key = get_rkpd_attestation_key(
333 REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
334 requester_uid as u32,
335 )
336 .context("Failed to retrieve the remotely provisioned keys")
337 .with_log()
338 .or_service_specific_exception(-1)?;
339 (attestation_key.keyBlob, attestation_key.encodedCertChain)
340 };
341 let mut certificate_chain = split_x509_certificate_chain(&certificate_chain)
Alice Wang4c6c5582023-11-23 15:07:18 +0000342 .context("Failed to split the remotely provisioned certificate chain")
343 .with_log()
344 .or_service_specific_exception(-1)?;
345 if certificate_chain.is_empty() {
346 return Err(Status::new_service_specific_error_str(
347 -1,
348 Some("The certificate chain should contain at least 1 certificate"),
349 ))
350 .with_log();
351 }
Alice Wang20b8ebc2023-11-17 09:54:47 +0000352 let certificate = request_attestation(
353 csr.to_vec(),
Alice Wange64dd182024-01-17 15:57:55 +0000354 key_blob,
Alice Wang20b8ebc2023-11-17 09:54:47 +0000355 certificate_chain[0].encodedCertificate.clone(),
356 )
357 .context("Failed to request attestation")
358 .with_log()
359 .or_service_specific_exception(-1)?;
Alice Wang4c6c5582023-11-23 15:07:18 +0000360 certificate_chain.insert(0, Certificate { encodedCertificate: certificate });
361
362 Ok(certificate_chain)
Alice Wangc2fec932023-02-23 16:24:02 +0000363 }
Inseob Kim53d0b212023-07-20 16:58:37 +0900364
Alice Wang0362f7f2024-03-21 08:16:26 +0000365 fn isRemoteAttestationSupported(&self) -> binder::Result<bool> {
366 remotely_provisioned_component_service_exists()
367 }
368
Inseob Kim53d0b212023-07-20 16:58:37 +0900369 fn getAssignableDevices(&self) -> binder::Result<Vec<AssignableDevice>> {
370 check_use_custom_virtual_machine()?;
371
Inseob Kim7307a892023-09-14 13:37:58 +0900372 Ok(get_assignable_devices()?
373 .device
374 .into_iter()
Jaewan Kim0c99c612024-03-23 00:44:14 +0900375 .map(|x| AssignableDevice { node: x.sysfs_path, dtbo_label: x.dtbo_label })
Inseob Kim7307a892023-09-14 13:37:58 +0900376 .collect::<Vec<_>>())
Inseob Kim53d0b212023-07-20 16:58:37 +0900377 }
Inseob Kim1ca0f652023-07-20 17:18:12 +0900378
Jakob Vukalovicd42aa2c2023-11-09 16:04:00 +0000379 fn bindDevicesToVfioDriver(
380 &self,
381 devices: &[String],
382 ) -> binder::Result<Vec<Strong<dyn IBoundDevice>>> {
Inseob Kim1ca0f652023-07-20 17:18:12 +0900383 check_use_custom_virtual_machine()?;
384
Jakob Vukalovicd42aa2c2023-11-09 16:04:00 +0000385 let devices = get_assignable_devices()?
Inseob Kim7307a892023-09-14 13:37:58 +0900386 .device
387 .into_iter()
388 .filter_map(|x| {
389 if devices.contains(&x.sysfs_path) {
Jakob Vukalovicd42aa2c2023-11-09 16:04:00 +0000390 Some(VfioDev { sysfsPath: x.sysfs_path, dtboLabel: x.dtbo_label })
Inseob Kim7307a892023-09-14 13:37:58 +0900391 } else {
Jakob Vukalovicd42aa2c2023-11-09 16:04:00 +0000392 warn!("device {} is not assignable", x.sysfs_path);
Inseob Kim7307a892023-09-14 13:37:58 +0900393 None
394 }
395 })
Jakob Vukalovicd42aa2c2023-11-09 16:04:00 +0000396 .collect::<Vec<VfioDev>>();
397
398 VFIO_SERVICE.bindDevicesToVfioDriver(devices.as_slice())
Inseob Kim1ca0f652023-07-20 17:18:12 +0900399 }
David Brazdil2dfefd12023-11-17 14:07:36 +0000400
401 fn getDtboFile(&self) -> binder::Result<ParcelFileDescriptor> {
402 check_use_custom_virtual_machine()?;
403
404 let state = &mut *self.state.lock().unwrap();
405 let file = state.get_dtbo_file().or_service_specific_exception(-1)?;
406 Ok(ParcelFileDescriptor::new(file))
407 }
Shikha Panwar61a74b52024-02-16 13:17:01 +0000408
Shikha Panwar61a74b52024-02-16 13:17:01 +0000409 fn allocateInstanceId(&self) -> binder::Result<[u8; 64]> {
410 let mut id = [0u8; 64];
411 id.try_fill(&mut rand::thread_rng())
412 .context("Failed to allocate instance_id")
413 .or_service_specific_exception(-1)?;
414 let uid = get_calling_uid();
415 info!("Allocated a VM's instance_id: {:?}, for uid: {:?}", hex::encode(id), uid);
David Drysdalee64de8e2024-02-29 11:54:29 +0000416 let state = &mut *self.state.lock().unwrap();
417 if let Some(sk_state) = &mut state.sk_state {
418 let user_id = multiuser_get_user_id(uid);
419 let app_id = multiuser_get_app_id(uid);
David Drysdale1138fa02024-03-19 13:06:23 +0000420 info!("Recording possible existence of state for (user_id={user_id}, app_id={app_id})");
David Drysdalee64de8e2024-02-29 11:54:29 +0000421 if let Err(e) = sk_state.add_id(&id, user_id, app_id) {
422 error!("Failed to record the instance_id: {e:?}");
423 }
424 }
425
Shikha Panwar61a74b52024-02-16 13:17:01 +0000426 Ok(id)
427 }
David Drysdale79af2662024-02-19 14:50:31 +0000428
429 fn removeVmInstance(&self, instance_id: &[u8; 64]) -> binder::Result<()> {
430 let state = &mut *self.state.lock().unwrap();
431 if let Some(sk_state) = &mut state.sk_state {
432 info!("removeVmInstance(): delete secret");
433 sk_state.delete_ids(&[*instance_id]);
434 } else {
435 info!("ignoring removeVmInstance() as no ISecretkeeper");
436 }
437 Ok(())
438 }
David Drysdale3aa62b32024-03-25 12:31:48 +0000439
440 fn claimVmInstance(&self, instance_id: &[u8; 64]) -> binder::Result<()> {
441 let state = &mut *self.state.lock().unwrap();
442 if let Some(sk_state) = &mut state.sk_state {
443 let uid = get_calling_uid();
444 info!(
445 "Claiming a VM's instance_id: {:?}, for uid: {:?}",
446 hex::encode(instance_id),
447 uid
448 );
449
450 let user_id = multiuser_get_user_id(uid);
451 let app_id = multiuser_get_app_id(uid);
452 info!("Recording possible new owner of state for (user_id={user_id}, app_id={app_id})");
453 if let Err(e) = sk_state.add_id(instance_id, user_id, app_id) {
454 error!("Failed to update the instance_id owner: {e:?}");
455 }
456 } else {
457 info!("ignoring claimVmInstance() as no ISecretkeeper");
458 }
459 Ok(())
460 }
David Drysdale79af2662024-02-19 14:50:31 +0000461}
462
463impl IVirtualizationMaintenance for VirtualizationServiceInternal {
464 fn appRemoved(&self, user_id: i32, app_id: i32) -> binder::Result<()> {
465 let state = &mut *self.state.lock().unwrap();
466 if let Some(sk_state) = &mut state.sk_state {
467 info!("packageRemoved(user_id={user_id}, app_id={app_id})");
468 sk_state.delete_ids_for_app(user_id, app_id).or_service_specific_exception(-1)?;
469 } else {
470 info!("ignoring packageRemoved(user_id={user_id}, app_id={app_id})");
471 }
472 Ok(())
473 }
474
475 fn userRemoved(&self, user_id: i32) -> binder::Result<()> {
476 let state = &mut *self.state.lock().unwrap();
477 if let Some(sk_state) = &mut state.sk_state {
478 info!("userRemoved({user_id})");
479 sk_state.delete_ids_for_user(user_id).or_service_specific_exception(-1)?;
480 } else {
481 info!("ignoring userRemoved(user_id={user_id})");
482 }
483 Ok(())
484 }
Alan Stokes30ccacb2024-02-20 14:59:02 +0000485
486 fn performReconciliation(
487 &self,
David Drysdale1138fa02024-03-19 13:06:23 +0000488 callback: &Strong<dyn IVirtualizationReconciliationCallback>,
Alan Stokes30ccacb2024-02-20 14:59:02 +0000489 ) -> binder::Result<()> {
David Drysdale1138fa02024-03-19 13:06:23 +0000490 let state = &mut *self.state.lock().unwrap();
491 if let Some(sk_state) = &mut state.sk_state {
492 info!("performReconciliation()");
493 sk_state.reconcile(callback).or_service_specific_exception(-1)?;
494 } else {
495 info!("ignoring performReconciliation()");
496 }
497 Ok(())
Alan Stokes30ccacb2024-02-20 14:59:02 +0000498 }
Inseob Kim1ca0f652023-07-20 17:18:12 +0900499}
500
Inseob Kimc4a774d2023-08-30 12:48:43 +0900501#[derive(Debug, Deserialize)]
502struct Device {
Jaewan Kim35e818d2023-10-18 05:36:38 +0000503 dtbo_label: String,
Inseob Kimc4a774d2023-08-30 12:48:43 +0900504 sysfs_path: String,
505}
506
Inseob Kim7307a892023-09-14 13:37:58 +0900507#[derive(Debug, Default, Deserialize)]
Inseob Kimc4a774d2023-08-30 12:48:43 +0900508struct Devices {
509 device: Vec<Device>,
510}
511
Inseob Kim7307a892023-09-14 13:37:58 +0900512fn get_assignable_devices() -> binder::Result<Devices> {
513 let xml_path = Path::new("/vendor/etc/avf/assignable_devices.xml");
514 if !xml_path.exists() {
515 return Ok(Devices { ..Default::default() });
516 }
517
518 let xml = fs::read(xml_path)
519 .context("Failed to read assignable_devices.xml")
520 .with_log()
521 .or_service_specific_exception(-1)?;
522
523 let xml = String::from_utf8(xml)
524 .context("assignable_devices.xml is not a valid UTF-8 file")
525 .with_log()
526 .or_service_specific_exception(-1)?;
527
528 let mut devices: Devices = serde_xml_rs::from_str(&xml)
529 .context("can't parse assignable_devices.xml")
530 .with_log()
531 .or_service_specific_exception(-1)?;
532
533 let mut device_set = HashSet::new();
534 devices.device.retain(move |device| {
535 if device_set.contains(&device.sysfs_path) {
536 warn!("duplicated assignable device {device:?}; ignoring...");
537 return false;
538 }
539
540 if !Path::new(&device.sysfs_path).exists() {
541 warn!("assignable device {device:?} doesn't exist; ignoring...");
542 return false;
543 }
544
545 device_set.insert(device.sysfs_path.clone());
546 true
547 });
548 Ok(devices)
549}
550
Alice Wang4c6c5582023-11-23 15:07:18 +0000551fn split_x509_certificate_chain(mut cert_chain: &[u8]) -> Result<Vec<Certificate>> {
552 let mut out = Vec::new();
553 while !cert_chain.is_empty() {
Alice Wangfc5a44a2023-12-21 12:22:40 +0000554 let cert = X509::from_der(cert_chain)?;
555 let end = cert.to_der()?.len();
Alice Wang4c6c5582023-11-23 15:07:18 +0000556 out.push(Certificate { encodedCertificate: cert_chain[..end].to_vec() });
Alice Wangfc5a44a2023-12-21 12:22:40 +0000557 cert_chain = &cert_chain[end..];
Alice Wang4c6c5582023-11-23 15:07:18 +0000558 }
559 Ok(out)
560}
561
David Brazdilafc9a9e2023-01-12 16:08:10 +0000562#[derive(Debug, Default)]
563struct GlobalVmInstance {
564 /// The unique CID assigned to the VM for vsock communication.
565 cid: Cid,
566 /// UID of the client who requested this VM instance.
567 requester_uid: uid_t,
568 /// PID of the client who requested this VM instance.
569 requester_debug_pid: pid_t,
570}
571
572impl GlobalVmInstance {
573 fn get_temp_dir(&self) -> PathBuf {
574 let cid = self.cid;
575 format!("{TEMPORARY_DIRECTORY}/{cid}").into()
576 }
577}
578
579/// The mutable state of the VirtualizationServiceInternal. There should only be one instance
580/// of this struct.
David Brazdilafc9a9e2023-01-12 16:08:10 +0000581struct GlobalState {
582 /// VM contexts currently allocated to running VMs. A CID is never recycled as long
583 /// as there is a strong reference held by a GlobalVmContext.
584 held_contexts: HashMap<Cid, Weak<GlobalVmInstance>>,
David Brazdil2dfefd12023-11-17 14:07:36 +0000585
586 /// Cached read-only FD of VM DTBO file. Also serves as a lock for creating the file.
587 dtbo_file: Mutex<Option<File>>,
David Drysdale79af2662024-02-19 14:50:31 +0000588
589 /// State relating to secrets held by (optional) Secretkeeper instance on behalf of VMs.
590 sk_state: Option<maintenance::State>,
David Brazdilafc9a9e2023-01-12 16:08:10 +0000591}
592
593impl GlobalState {
David Drysdale79af2662024-02-19 14:50:31 +0000594 fn new() -> Self {
595 Self {
596 held_contexts: HashMap::new(),
597 dtbo_file: Mutex::new(None),
598 sk_state: maintenance::State::new(),
599 }
600 }
601
David Brazdilafc9a9e2023-01-12 16:08:10 +0000602 /// Get the next available CID, or an error if we have run out. The last CID used is stored in
603 /// a system property so that restart of virtualizationservice doesn't reuse CID while the host
604 /// Android is up.
605 fn get_next_available_cid(&mut self) -> Result<Cid> {
606 // Start trying to find a CID from the last used CID + 1. This ensures
607 // that we do not eagerly recycle CIDs. It makes debugging easier but
608 // also means that retrying to allocate a CID, eg. because it is
609 // erroneously occupied by a process, will not recycle the same CID.
610 let last_cid_prop =
611 system_properties::read(SYSPROP_LAST_CID)?.and_then(|val| match val.parse::<Cid>() {
612 Ok(num) => {
613 if is_valid_guest_cid(num) {
614 Some(num)
615 } else {
616 error!("Invalid value '{}' of property '{}'", num, SYSPROP_LAST_CID);
617 None
618 }
619 }
620 Err(_) => {
621 error!("Invalid value '{}' of property '{}'", val, SYSPROP_LAST_CID);
622 None
623 }
624 });
625
626 let first_cid = if let Some(last_cid) = last_cid_prop {
627 if last_cid == GUEST_CID_MAX {
628 GUEST_CID_MIN
629 } else {
630 last_cid + 1
631 }
632 } else {
633 GUEST_CID_MIN
634 };
635
636 let cid = self
637 .find_available_cid(first_cid..=GUEST_CID_MAX)
638 .or_else(|| self.find_available_cid(GUEST_CID_MIN..first_cid))
639 .ok_or_else(|| anyhow!("Could not find an available CID."))?;
640
641 system_properties::write(SYSPROP_LAST_CID, &format!("{}", cid))?;
642 Ok(cid)
643 }
644
645 fn find_available_cid<I>(&self, mut range: I) -> Option<Cid>
646 where
647 I: Iterator<Item = Cid>,
648 {
649 range.find(|cid| !self.held_contexts.contains_key(cid))
650 }
651
652 fn allocate_vm_context(
653 &mut self,
654 requester_uid: uid_t,
655 requester_debug_pid: pid_t,
656 ) -> Result<Strong<dyn IGlobalVmContext>> {
657 // Garbage collect unused VM contexts.
658 self.held_contexts.retain(|_, instance| instance.strong_count() > 0);
659
660 let cid = self.get_next_available_cid()?;
661 let instance = Arc::new(GlobalVmInstance { cid, requester_uid, requester_debug_pid });
David Brazdil2dfefd12023-11-17 14:07:36 +0000662 create_temporary_directory(&instance.get_temp_dir(), Some(requester_uid))?;
David Brazdilafc9a9e2023-01-12 16:08:10 +0000663
664 self.held_contexts.insert(cid, Arc::downgrade(&instance));
665 let binder = GlobalVmContext { instance, ..Default::default() };
666 Ok(BnGlobalVmContext::new_binder(binder, BinderFeatures::default()))
667 }
David Brazdil2dfefd12023-11-17 14:07:36 +0000668
669 fn get_dtbo_file(&mut self) -> Result<File> {
670 let mut file = self.dtbo_file.lock().unwrap();
671
672 let fd = if let Some(ref_fd) = &*file {
673 ref_fd.try_clone()?
674 } else {
675 let path = get_or_create_common_dir()?.join("vm.dtbo");
676 if path.exists() {
677 // All temporary files are deleted when the service is started.
678 // If the file exists but the FD is not cached, the file is
679 // likely corrupted.
680 remove_file(&path).context("Failed to clone cached VM DTBO file descriptor")?;
681 }
682
683 // Open a write-only file descriptor for vfio_handler.
684 let write_fd = File::create(&path).context("Failed to create VM DTBO file")?;
Jakob Vukalovicd42aa2c2023-11-09 16:04:00 +0000685 VFIO_SERVICE.writeVmDtbo(&ParcelFileDescriptor::new(write_fd))?;
David Brazdil2dfefd12023-11-17 14:07:36 +0000686
687 // Open read-only. This FD will be cached and returned to clients.
688 let read_fd = File::open(&path).context("Failed to open VM DTBO file")?;
689 let read_fd_clone =
690 read_fd.try_clone().context("Failed to clone VM DTBO file descriptor")?;
691 *file = Some(read_fd);
692 read_fd_clone
693 };
694
695 Ok(fd)
696 }
David Brazdilafc9a9e2023-01-12 16:08:10 +0000697}
698
David Brazdil2dfefd12023-11-17 14:07:36 +0000699fn create_temporary_directory(path: &PathBuf, requester_uid: Option<uid_t>) -> Result<()> {
700 // Directory may exist if previous attempt to create it had failed.
701 // Delete it before trying again.
David Brazdilafc9a9e2023-01-12 16:08:10 +0000702 if path.as_path().exists() {
703 remove_temporary_dir(path).unwrap_or_else(|e| {
704 warn!("Could not delete temporary directory {:?}: {}", path, e);
705 });
706 }
David Brazdil2dfefd12023-11-17 14:07:36 +0000707 // Create directory.
708 create_dir(path).with_context(|| format!("Could not create temporary directory {:?}", path))?;
709 // If provided, change ownership to client's UID but system's GID, and permissions 0700.
David Brazdilafc9a9e2023-01-12 16:08:10 +0000710 // If the chown() fails, this will leave behind an empty directory that will get removed
711 // at the next attempt, or if virtualizationservice is restarted.
David Brazdil2dfefd12023-11-17 14:07:36 +0000712 if let Some(uid) = requester_uid {
713 chown(path, Some(Uid::from_raw(uid)), None).with_context(|| {
714 format!("Could not set ownership of temporary directory {:?}", path)
715 })?;
716 }
David Brazdilafc9a9e2023-01-12 16:08:10 +0000717 Ok(())
718}
719
720/// Removes a directory owned by a different user by first changing its owner back
721/// to VirtualizationService.
722pub fn remove_temporary_dir(path: &PathBuf) -> Result<()> {
Alice Wangd1b11a02023-04-18 12:30:20 +0000723 ensure!(path.as_path().is_dir(), "Path {:?} is not a directory", path);
David Brazdilafc9a9e2023-01-12 16:08:10 +0000724 chown(path, Some(Uid::current()), None)?;
725 set_permissions(path, Permissions::from_mode(0o700))?;
Alice Wangd1b11a02023-04-18 12:30:20 +0000726 remove_dir_all(path)?;
David Brazdilafc9a9e2023-01-12 16:08:10 +0000727 Ok(())
728}
729
David Brazdil2dfefd12023-11-17 14:07:36 +0000730fn get_or_create_common_dir() -> Result<PathBuf> {
731 let path = Path::new(TEMPORARY_DIRECTORY).join("common");
732 if !path.exists() {
733 create_temporary_directory(&path, None)?;
734 }
735 Ok(path)
736}
737
David Brazdilafc9a9e2023-01-12 16:08:10 +0000738/// Implementation of the AIDL `IGlobalVmContext` interface.
739#[derive(Debug, Default)]
740struct GlobalVmContext {
741 /// Strong reference to the context's instance data structure.
742 instance: Arc<GlobalVmInstance>,
743 /// Keeps our service process running as long as this VM context exists.
744 #[allow(dead_code)]
745 lazy_service_guard: LazyServiceGuard,
746}
747
748impl Interface for GlobalVmContext {}
749
750impl IGlobalVmContext for GlobalVmContext {
751 fn getCid(&self) -> binder::Result<i32> {
752 Ok(self.instance.cid as i32)
753 }
754
755 fn getTemporaryDirectory(&self) -> binder::Result<String> {
756 Ok(self.instance.get_temp_dir().to_string_lossy().to_string())
757 }
758}
759
760fn handle_stream_connection_tombstoned() -> Result<()> {
761 // Should not listen for tombstones on a guest VM's port.
762 assert!(!is_valid_guest_cid(VM_TOMBSTONES_SERVICE_PORT as Cid));
763 let listener =
764 VsockListener::bind_with_cid_port(VMADDR_CID_HOST, VM_TOMBSTONES_SERVICE_PORT as Cid)?;
765 for incoming_stream in listener.incoming() {
766 let mut incoming_stream = match incoming_stream {
767 Err(e) => {
768 warn!("invalid incoming connection: {:?}", e);
769 continue;
770 }
771 Ok(s) => s,
772 };
773 std::thread::spawn(move || {
774 if let Err(e) = handle_tombstone(&mut incoming_stream) {
775 error!("Failed to write tombstone- {:?}", e);
776 }
777 });
778 }
779 Ok(())
780}
781
782fn handle_tombstone(stream: &mut VsockStream) -> Result<()> {
783 if let Ok(addr) = stream.peer_addr() {
784 info!("Vsock Stream connected to cid={} for tombstones", addr.cid());
785 }
786 let tb_connection =
787 TombstonedConnection::connect(std::process::id() as i32, DebuggerdDumpType::Tombstone)
788 .context("Failed to connect to tombstoned")?;
789 let mut text_output = tb_connection
790 .text_output
791 .as_ref()
792 .ok_or_else(|| anyhow!("Could not get file to write the tombstones on"))?;
793 let mut num_bytes_read = 0;
794 loop {
795 let mut chunk_recv = [0; CHUNK_RECV_MAX_LEN];
796 let n = stream
797 .read(&mut chunk_recv)
798 .context("Failed to read tombstone data from Vsock stream")?;
799 if n == 0 {
800 break;
801 }
802 num_bytes_read += n;
803 text_output.write_all(&chunk_recv[0..n]).context("Failed to write guests tombstones")?;
804 }
805 info!("Received {} bytes from guest & wrote to tombstone file", num_bytes_read);
806 tb_connection.notify_completion()?;
807 Ok(())
808}
809
Alice Wang0dcab552024-03-20 14:42:30 +0000810fn remotely_provisioned_component_service_exists() -> binder::Result<bool> {
811 Ok(binder::is_declared(REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME)?)
812}
813
David Brazdilafc9a9e2023-01-12 16:08:10 +0000814/// Checks whether the caller has a specific permission
815fn check_permission(perm: &str) -> binder::Result<()> {
816 let calling_pid = get_calling_pid();
817 let calling_uid = get_calling_uid();
818 // Root can do anything
819 if calling_uid == 0 {
820 return Ok(());
821 }
822 let perm_svc: Strong<dyn IPermissionController::IPermissionController> =
823 binder::get_interface("permission")?;
824 if perm_svc.checkPermission(perm, calling_pid, calling_uid as i32)? {
825 Ok(())
826 } else {
Jiyong Park2227eaa2023-08-04 11:59:18 +0900827 Err(anyhow!("does not have the {} permission", perm))
828 .or_binder_exception(ExceptionCode::SECURITY)
David Brazdilafc9a9e2023-01-12 16:08:10 +0000829 }
830}
831
832/// Check whether the caller of the current Binder method is allowed to call debug methods.
833fn check_debug_access() -> binder::Result<()> {
834 check_permission("android.permission.DEBUG_VIRTUAL_MACHINE")
835}
836
837/// Check whether the caller of the current Binder method is allowed to manage VMs
838fn check_manage_access() -> binder::Result<()> {
839 check_permission("android.permission.MANAGE_VIRTUAL_MACHINE")
840}
Inseob Kim53d0b212023-07-20 16:58:37 +0900841
842/// Check whether the caller of the current Binder method is allowed to use custom VMs
843fn check_use_custom_virtual_machine() -> binder::Result<()> {
844 check_permission("android.permission.USE_CUSTOM_VIRTUAL_MACHINE")
845}
Alice Wang4c6c5582023-11-23 15:07:18 +0000846
847#[cfg(test)]
848mod tests {
849 use super::*;
Alice Wang4c6c5582023-11-23 15:07:18 +0000850
851 const TEST_RKP_CERT_CHAIN_PATH: &str = "testdata/rkp_cert_chain.der";
852
853 #[test]
854 fn splitting_x509_certificate_chain_succeeds() -> Result<()> {
855 let bytes = fs::read(TEST_RKP_CERT_CHAIN_PATH)?;
856 let cert_chain = split_x509_certificate_chain(&bytes)?;
857
858 assert_eq!(4, cert_chain.len());
859 for cert in cert_chain {
Alice Wangfc5a44a2023-12-21 12:22:40 +0000860 let x509_cert = X509::from_der(&cert.encodedCertificate)?;
861 assert_eq!(x509_cert.to_der()?.len(), cert.encodedCertificate.len());
Alice Wang4c6c5582023-11-23 15:07:18 +0000862 }
863 Ok(())
864 }
865}