blob: 5352250f042dc2450a825b979accc6966f0162aa [file] [log] [blame]
Alan Stokes6b2d0a82021-09-29 11:30:39 +01001/*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! Responsible for validating and starting an existing instance of the CompOS VM, or creating and
18//! starting a new instance if necessary.
19
20use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
21 IVirtualizationService::IVirtualizationService, PartitionType::PartitionType,
22};
23use anyhow::{bail, Context, Result};
24use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
25use compos_aidl_interface::binder::{ParcelFileDescriptor, Strong};
26use compos_common::compos_client::VmInstance;
27use compos_common::{
28 COMPOS_DATA_ROOT, INSTANCE_IMAGE_FILE, PRIVATE_KEY_BLOB_FILE, PUBLIC_KEY_FILE,
29};
30use log::{info, warn};
31use std::fs;
32use std::path::{Path, PathBuf};
33
34pub struct CompOsInstance {
35 #[allow(dead_code)] // Keeps VirtualizationService & the VM alive
36 vm_instance: VmInstance,
37 service: Strong<dyn ICompOsService>,
38}
39
40impl CompOsInstance {
41 pub fn get_service(&self) -> Strong<dyn ICompOsService> {
42 self.service.clone()
43 }
44}
45
46pub struct InstanceStarter {
47 instance_name: String,
48 instance_root: PathBuf,
49 instance_image: PathBuf,
50 key_blob: PathBuf,
51 public_key: PathBuf,
52}
53
54impl InstanceStarter {
55 pub fn new(instance_name: &str) -> Self {
56 let instance_root = Path::new(COMPOS_DATA_ROOT).join(instance_name);
57 let instant_root_path = instance_root.as_path();
58 let instance_image = instant_root_path.join(INSTANCE_IMAGE_FILE);
59 let key_blob = instant_root_path.join(PRIVATE_KEY_BLOB_FILE);
60 let public_key = instant_root_path.join(PUBLIC_KEY_FILE);
61 Self {
62 instance_name: instance_name.to_owned(),
63 instance_root,
64 instance_image,
65 key_blob,
66 public_key,
67 }
68 }
69
70 pub fn create_or_start_instance(
71 &self,
72 service: &dyn IVirtualizationService,
73 ) -> Result<CompOsInstance> {
74 let compos_instance = self.start_existing_instance();
75 match compos_instance {
76 Ok(_) => return compos_instance,
77 Err(e) => warn!("Failed to start {}: {}", self.instance_name, e),
78 }
79
80 self.start_new_instance(service)
81 }
82
83 fn start_existing_instance(&self) -> Result<CompOsInstance> {
84 // No point even trying if the files we need aren't there.
85 self.check_files_exist()?;
86
87 let key_blob = fs::read(&self.key_blob).context("Reading private key blob")?;
88 let public_key = fs::read(&self.public_key).context("Reading public key")?;
89
90 let vm_instance = VmInstance::start(&self.instance_image).context("Starting VM")?;
91 let service = vm_instance.get_service().context("Connecting to CompOS")?;
92
93 if !service.verifySigningKey(&key_blob, &public_key).context("Verifying key pair")? {
94 bail!("Key pair invalid");
95 }
96
97 // If we get this far then the instance image is valid in the current context (e.g. the
98 // current set of APEXes) and the key blob can be successfully decrypted by the VM. So the
99 // files have not been tampered with and we're good to go.
100
101 service.initializeSigningKey(&key_blob).context("Loading signing key")?;
102
103 Ok(CompOsInstance { vm_instance, service })
104 }
105
106 fn start_new_instance(
107 &self,
108 virtualization_service: &dyn IVirtualizationService,
109 ) -> Result<CompOsInstance> {
110 info!("Creating {} CompOs instance", self.instance_name);
111
112 // Ignore failure here - the directory may already exist.
113 let _ = fs::create_dir(&self.instance_root);
114
115 self.create_instance_image(virtualization_service)?;
116
117 let vm_instance = VmInstance::start(&self.instance_image).context("Starting VM")?;
118 let service = vm_instance.get_service().context("Connecting to CompOS")?;
119
120 let key_data = service.generateSigningKey().context("Generating signing key")?;
121 fs::write(&self.key_blob, &key_data.keyBlob).context("Writing key blob")?;
122 // TODO: Extract public key from cert
123 fs::write(&self.public_key, &key_data.certificate).context("Writing public key")?;
124
125 // We don't need to verify the key, since we just generated it and have it in memory.
126
127 service.initializeSigningKey(&key_data.keyBlob).context("Loading signing key")?;
128
129 Ok(CompOsInstance { vm_instance, service })
130 }
131
132 fn create_instance_image(
133 &self,
134 virtualization_service: &dyn IVirtualizationService,
135 ) -> Result<()> {
136 let instance_image = fs::OpenOptions::new()
137 .create(true)
138 .read(true)
139 .write(true)
140 .open(&self.instance_image)
141 .context("Creating instance image file")?;
142 let instance_image = ParcelFileDescriptor::new(instance_image);
143 // TODO: Where does this number come from?
144 let size = 10 * 1024 * 1024;
145 virtualization_service
146 .initializeWritablePartition(&instance_image, size, PartitionType::ANDROID_VM_INSTANCE)
147 .context("Writing instance image file")?;
148 Ok(())
149 }
150
151 fn check_files_exist(&self) -> Result<()> {
152 if !self.instance_root.is_dir() {
153 bail!("Directory {} not found", self.instance_root.display())
154 };
155 Self::check_file_exists(&self.instance_image)?;
156 Self::check_file_exists(&self.key_blob)?;
157 Self::check_file_exists(&self.public_key)?;
158 Ok(())
159 }
160
161 fn check_file_exists(file: &Path) -> Result<()> {
162 if !file.is_file() {
163 bail!("File {} not found", file.display())
164 };
165 Ok(())
166 }
167}