Sign artifacts after compilation
If compilation succeeds, pass over all the artifacts we generated and
produce an OdsignInfo file with all the names and digests and a
signature of it using the CompOS key.
Various refactorings along the way to make it eaiser:
- Extract the odrefresh ExitCode enum for reuse
- Extracted a Signer, distinct from an ArtifactSigner
- Defined OdrefreshContext to stop the linter complaining about a
function with >7 arguments.
Also added more debugging to help fix my various silly mistakes.
Bug: 161471326
Test: composd_cmd forced-odrefresh -> signature file produced
Change-Id: Ia6a6abd9e28158f4767e7752727635087f9bb566
diff --git a/compos/src/artifact_signer.rs b/compos/src/artifact_signer.rs
index 54b8d7a..ce32d6b 100644
--- a/compos/src/artifact_signer.rs
+++ b/compos/src/artifact_signer.rs
@@ -19,7 +19,7 @@
#![allow(dead_code)] // Will be used soon
-use crate::compos_key_service::CompOsKeyService;
+use crate::compos_key_service::Signer;
use crate::fsverity;
use anyhow::{anyhow, Context, Result};
use odsign_proto::odsign_info::OdsignInfo;
@@ -27,31 +27,25 @@
use std::fs::File;
use std::io::Write;
use std::os::unix::io::AsRawFd;
-use std::path::{Path, PathBuf};
+use std::path::Path;
const TARGET_DIRECTORY: &str = "/data/misc/apexdata/com.android.art/dalvik-cache";
const SIGNATURE_EXTENSION: &str = ".signature";
/// Accumulates and then signs information about generated artifacts.
pub struct ArtifactSigner<'a> {
- key_blob: Vec<u8>,
- key_service: &'a CompOsKeyService,
- base_directory: PathBuf,
+ base_directory: &'a Path,
file_digests: Vec<(String, String)>, // (File name, digest in hex)
}
impl<'a> ArtifactSigner<'a> {
/// base_directory specifies the directory under which the artifacts are currently located;
/// they will eventually be moved under TARGET_DIRECTORY once they are verified and activated.
- pub fn new(
- key_blob: Vec<u8>,
- key_service: &'a CompOsKeyService,
- base_directory: PathBuf,
- ) -> Self {
- ArtifactSigner { key_blob, key_service, base_directory, file_digests: Vec::new() }
+ pub fn new(base_directory: &'a Path) -> Self {
+ Self { base_directory, file_digests: Vec::new() }
}
- pub fn add_artifact(&mut self, path: PathBuf) -> Result<()> {
+ pub fn add_artifact(&mut self, path: &Path) -> Result<()> {
// The path we store is where the file will be when it is verified, not where it is now.
let suffix = path
.strip_prefix(&self.base_directory)
@@ -59,7 +53,7 @@
let target_path = Path::new(TARGET_DIRECTORY).join(suffix);
let target_path = target_path.to_str().ok_or_else(|| anyhow!("Invalid path"))?;
- let file = File::open(&path)?;
+ let file = File::open(path).with_context(|| format!("Opening {}", path.display()))?;
let digest = fsverity::measure(file.as_raw_fd())?;
let digest = to_hex_string(&digest);
@@ -69,12 +63,12 @@
/// Consume this ArtifactSigner and write details of all its artifacts to the given path,
/// with accompanying sigature file.
- pub fn write_info_and_signature(self, info_path: &Path) -> Result<()> {
+ pub fn write_info_and_signature(self, signer: Signer, info_path: &Path) -> Result<()> {
let mut info = OdsignInfo::new();
info.mut_file_hashes().extend(self.file_digests.into_iter());
let bytes = info.write_to_bytes()?;
- let signature = self.key_service.sign(&self.key_blob, &bytes)?;
+ let signature = signer.sign(&bytes)?;
let mut file = File::create(info_path)?;
file.write_all(&bytes)?;
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index f8a66c2..cf6f30a 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -18,10 +18,12 @@
use log::{debug, error, info};
use minijail::{self, Minijail};
use std::env;
-use std::fs::File;
+use std::fs::{read_dir, File};
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::{Path, PathBuf};
+use crate::artifact_signer::ArtifactSigner;
+use crate::compos_key_service::Signer;
use crate::fsverity;
use authfs_aidl_interface::aidl::com::android::virt::fs::{
AuthFsConfig::{
@@ -34,6 +36,7 @@
};
use authfs_aidl_interface::binder::{ParcelFileDescriptor, Strong};
use compos_aidl_interface::aidl::com::android::compos::FdAnnotation::FdAnnotation;
+use compos_common::odrefresh::ExitCode;
const FD_SERVER_PORT: i32 = 3264; // TODO: support dynamic port
@@ -58,28 +61,51 @@
image: ParcelFileDescriptor,
}
-pub fn odrefresh(
- odrefresh_path: &Path,
- target_dir_name: &str,
+pub struct OdrefreshContext<'a> {
system_dir_fd: i32,
output_dir_fd: i32,
staging_dir_fd: i32,
- zygote_arch: &str,
+ target_dir_name: &'a str,
+ zygote_arch: &'a str,
+}
+
+impl<'a> OdrefreshContext<'a> {
+ pub fn new(
+ system_dir_fd: i32,
+ output_dir_fd: i32,
+ staging_dir_fd: i32,
+ target_dir_name: &'a str,
+ zygote_arch: &'a str,
+ ) -> Result<Self> {
+ if system_dir_fd < 0 || output_dir_fd < 0 || staging_dir_fd < 0 {
+ bail!("The remote FDs are expected to be non-negative");
+ }
+ if zygote_arch != "zygote64" && zygote_arch != "zygote64_32" {
+ bail!("Invalid zygote arch");
+ }
+ Ok(Self { system_dir_fd, output_dir_fd, staging_dir_fd, target_dir_name, zygote_arch })
+ }
+}
+
+pub fn odrefresh(
+ odrefresh_path: &Path,
+ context: OdrefreshContext,
authfs_service: Strong<dyn IAuthFsService>,
-) -> Result<i8> {
+ signer: Signer,
+) -> Result<ExitCode> {
// Mount authfs (via authfs_service). The authfs instance unmounts once the `authfs` variable
// is out of scope.
let authfs_config = AuthFsConfig {
port: FD_SERVER_PORT,
inputDirFdAnnotations: vec![InputDirFdAnnotation {
- fd: system_dir_fd,
+ fd: context.system_dir_fd,
// TODO(206869687): Replace /dev/null with the real path when possible.
manifestPath: "/dev/null".to_string(),
prefix: "/system".to_string(),
}],
outputDirFdAnnotations: vec![
- OutputDirFdAnnotation { fd: output_dir_fd },
- OutputDirFdAnnotation { fd: staging_dir_fd },
+ OutputDirFdAnnotation { fd: context.output_dir_fd },
+ OutputDirFdAnnotation { fd: context.staging_dir_fd },
],
..Default::default()
};
@@ -87,21 +113,21 @@
let mountpoint = PathBuf::from(authfs.getMountPoint()?);
let mut android_root = mountpoint.clone();
- android_root.push(system_dir_fd.to_string());
+ android_root.push(context.system_dir_fd.to_string());
android_root.push("system");
env::set_var("ANDROID_ROOT", &android_root);
+ debug!("ANDROID_ROOT={:?}", &android_root);
- let mut art_apex_data = mountpoint.clone();
- art_apex_data.push(output_dir_fd.to_string());
+ let art_apex_data = mountpoint.join(context.output_dir_fd.to_string());
env::set_var("ART_APEX_DATA", &art_apex_data);
+ debug!("ART_APEX_DATA={:?}", &art_apex_data);
- let mut staging_dir = mountpoint;
- staging_dir.push(staging_dir_fd.to_string());
+ let staging_dir = mountpoint.join(context.staging_dir_fd.to_string());
let args = vec![
"odrefresh".to_string(),
- format!("--zygote-arch={}", zygote_arch),
- format!("--dalvik-cache={}", target_dir_name),
+ format!("--zygote-arch={}", context.zygote_arch),
+ format!("--dalvik-cache={}", context.target_dir_name),
"--no-refresh".to_string(),
format!("--staging-dir={}", staging_dir.display()),
"--force-compile".to_string(),
@@ -109,17 +135,47 @@
debug!("Running odrefresh with args: {:?}", &args);
let jail = spawn_jailed_task(odrefresh_path, &args, Vec::new() /* fd_mapping */)
.context("Spawn odrefresh")?;
- match jail.wait() {
- // TODO(161471326): On success, sign all files in the output directory.
- Ok(()) => Ok(0i8),
- Err(minijail::Error::ReturnCode(exit_code)) => {
- info!("odrefresh exited with exit code {}", exit_code);
- Ok(exit_code as i8)
- }
+ let exit_code = match jail.wait() {
+ Ok(_) => Result::<u8>::Ok(0),
+ Err(minijail::Error::ReturnCode(exit_code)) => Ok(exit_code),
Err(e) => {
bail!("Unexpected minijail error: {}", e)
}
+ }?;
+
+ let exit_code = ExitCode::from_i32(exit_code.into())
+ .ok_or_else(|| anyhow!("Unexpected odrefresh exit code: {}", exit_code))?;
+ info!("odrefresh exited with {:?}", exit_code);
+
+ if exit_code == ExitCode::CompilationSuccess {
+ // authfs only shows us the files we created, so it's ok to just sign everything under
+ // the target directory.
+ let target_dir = art_apex_data.join(context.target_dir_name);
+ let mut artifact_signer = ArtifactSigner::new(&target_dir);
+ add_artifacts(&target_dir, &mut artifact_signer)?;
+
+ artifact_signer.write_info_and_signature(signer, &target_dir.join("compos.info"))?;
}
+
+ Ok(exit_code)
+}
+
+fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
+ for entry in
+ read_dir(&target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
+ {
+ let entry = entry?;
+ let file_type = entry.file_type()?;
+ if file_type.is_dir() {
+ add_artifacts(&entry.path(), artifact_signer)?;
+ } else if file_type.is_file() {
+ artifact_signer.add_artifact(&entry.path())?;
+ } else {
+ // authfs shouldn't create anything else, but just in case
+ bail!("Unexpected file type in artifacts: {:?}", entry);
+ }
+ }
+ Ok(())
}
/// Runs the compiler with given flags with file descriptors described in `fd_annotation` retrieved
diff --git a/compos/src/compos_key_service.rs b/compos/src/compos_key_service.rs
index f6caac9..086a162 100644
--- a/compos/src/compos_key_service.rs
+++ b/compos/src/compos_key_service.rs
@@ -101,7 +101,7 @@
let mut data = [0u8; 32];
self.random.fill(&mut data).context("No random data")?;
- let signature = self.sign(key_blob, &data)?;
+ let signature = self.new_signer(key_blob).sign(&data)?;
let public_key =
signature::UnparsedPublicKey::new(&signature::RSA_PKCS1_2048_8192_SHA256, public_key);
@@ -110,8 +110,19 @@
Ok(())
}
- pub fn sign(&self, key_blob: &[u8], data: &[u8]) -> Result<Vec<u8>> {
- let key_descriptor = KeyDescriptor { blob: Some(key_blob.to_vec()), ..BLOB_KEY_DESCRIPTOR };
+ pub fn new_signer(&self, key_blob: &[u8]) -> Signer {
+ Signer { key_blob: key_blob.to_vec(), security_level: self.security_level.clone() }
+ }
+}
+
+pub struct Signer {
+ key_blob: Vec<u8>,
+ security_level: Strong<dyn IKeystoreSecurityLevel>,
+}
+
+impl Signer {
+ pub fn sign(self, data: &[u8]) -> Result<Vec<u8>> {
+ let key_descriptor = KeyDescriptor { blob: Some(self.key_blob), ..BLOB_KEY_DESCRIPTOR };
let operation_parameters = [PURPOSE_SIGN, ALGORITHM, PADDING, DIGEST];
let forced = false;
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 4540cd8..aa4b9bd 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -26,8 +26,8 @@
use std::path::PathBuf;
use std::sync::RwLock;
-use crate::compilation::{compile_cmd, odrefresh, CompilerOutput};
-use crate::compos_key_service::CompOsKeyService;
+use crate::compilation::{compile_cmd, odrefresh, CompilerOutput, OdrefreshContext};
+use crate::compos_key_service::{CompOsKeyService, Signer};
use crate::fsverity;
use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::IAuthFsService;
use compos_aidl_interface::aidl::com::android::compos::{
@@ -39,10 +39,10 @@
use compos_aidl_interface::binder::{
BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Strong,
};
+use compos_common::odrefresh::ODREFRESH_PATH;
const AUTHFS_SERVICE_NAME: &str = "authfs_service";
const DEX2OAT_PATH: &str = "/apex/com.android.art/bin/dex2oat64";
-const ODREFRESH_PATH: &str = "/apex/com.android.art/bin/odrefresh";
/// Constructs a binder object that implements ICompOsService.
pub fn new_binder() -> Result<Strong<dyn ICompOsService>> {
@@ -65,14 +65,21 @@
impl CompOsService {
fn generate_raw_fsverity_signature(
&self,
- key_blob: &[u8],
fsverity_digest: &fsverity::Sha256Digest,
- ) -> Vec<u8> {
+ ) -> BinderResult<Vec<u8>> {
let formatted_digest = fsverity::to_formatted_digest(fsverity_digest);
- self.key_service.sign(key_blob, &formatted_digest[..]).unwrap_or_else(|e| {
- warn!("Failed to sign the fsverity digest, returning empty signature. Error: {}", e);
- Vec::new()
- })
+ self.new_signer()?
+ .sign(&formatted_digest[..])
+ .map_err(|e| new_binder_exception(ExceptionCode::SERVICE_SPECIFIC, e.to_string()))
+ }
+
+ fn new_signer(&self) -> BinderResult<Signer> {
+ let key = &*self.key_blob.read().unwrap();
+ if key.is_empty() {
+ Err(new_binder_exception(ExceptionCode::ILLEGAL_STATE, "Key is not initialized"))
+ } else {
+ Ok(self.key_service.new_signer(key))
+ }
}
}
@@ -110,36 +117,27 @@
target_dir_name: &str,
zygote_arch: &str,
) -> BinderResult<i8> {
- if system_dir_fd < 0 || output_dir_fd < 0 || staging_dir_fd < 0 {
- return Err(new_binder_exception(
- ExceptionCode::ILLEGAL_ARGUMENT,
- "The remote FDs are expected to be non-negative",
- ));
- }
- if zygote_arch != "zygote64" && zygote_arch != "zygote64_32" {
- return Err(new_binder_exception(
- ExceptionCode::ILLEGAL_ARGUMENT,
- "Invalid zygote arch",
- ));
- }
-
- let authfs_service = get_authfs_service()?;
- odrefresh(
- &self.odrefresh_path,
- target_dir_name,
+ let context = OdrefreshContext::new(
system_dir_fd,
output_dir_fd,
staging_dir_fd,
+ target_dir_name,
zygote_arch,
- authfs_service,
)
- .map_err(|e| {
- warn!("odrefresh failed: {}", e);
- new_binder_exception(
- ExceptionCode::SERVICE_SPECIFIC,
- format!("odrefresh failed: {}", e),
- )
- })
+ .map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT, e.to_string()))?;
+
+ let authfs_service = get_authfs_service()?;
+ let exit_code =
+ odrefresh(&self.odrefresh_path, context, authfs_service, self.new_signer()?).map_err(
+ |e| {
+ warn!("odrefresh failed: {:?}", e);
+ new_binder_exception(
+ ExceptionCode::SERVICE_SPECIFIC,
+ format!("odrefresh failed: {}", e),
+ )
+ },
+ )?;
+ Ok(exit_code as i8)
}
fn compile_cmd(
@@ -157,23 +155,15 @@
})?;
match output {
CompilerOutput::Digests { oat, vdex, image } => {
- let key = &*self.key_blob.read().unwrap();
- if key.is_empty() {
- Err(new_binder_exception(
- ExceptionCode::ILLEGAL_STATE,
- "Key is not initialized",
- ))
- } else {
- let oat_signature = self.generate_raw_fsverity_signature(key, &oat);
- let vdex_signature = self.generate_raw_fsverity_signature(key, &vdex);
- let image_signature = self.generate_raw_fsverity_signature(key, &image);
- Ok(CompilationResult {
- exitCode: 0,
- oatSignature: oat_signature,
- vdexSignature: vdex_signature,
- imageSignature: image_signature,
- })
- }
+ let oat_signature = self.generate_raw_fsverity_signature(&oat)?;
+ let vdex_signature = self.generate_raw_fsverity_signature(&vdex)?;
+ let image_signature = self.generate_raw_fsverity_signature(&image)?;
+ Ok(CompilationResult {
+ exitCode: 0,
+ oatSignature: oat_signature,
+ vdexSignature: vdex_signature,
+ imageSignature: image_signature,
+ })
}
CompilerOutput::ExitCode(exit_code) => {
Ok(CompilationResult { exitCode: exit_code, ..Default::default() })
@@ -201,14 +191,9 @@
}
fn sign(&self, data: &[u8]) -> BinderResult<Vec<u8>> {
- let key = &*self.key_blob.read().unwrap();
- if key.is_empty() {
- Err(new_binder_exception(ExceptionCode::ILLEGAL_STATE, "Key is not initialized"))
- } else {
- self.key_service
- .sign(key, data)
- .map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))
- }
+ self.new_signer()?
+ .sign(data)
+ .map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))
}
}