| // Copyright 2020 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! Validate or convert TEE app configuration files. |
| |
| #![deny(unsafe_op_in_unsafe_fn)] |
| |
| use std::collections::BTreeSet as Set; |
| use std::env::args; |
| use std::fs::File; |
| use std::io::{stdout, Read, Stdout, Write}; |
| use std::path::{Path, PathBuf}; |
| |
| use anyhow::{bail, Context, Result}; |
| use getopts::Options; |
| use libsirenia::cli::{HelpOption, VerbosityOption}; |
| use sirenia::app_info::{entries_from_path, AppManifestEntry, ExecutableInfo}; |
| use sirenia::compute_sha256; |
| |
| const CHECK_ONLY: &str = "c"; |
| const OUTPUT_JSON: &str = "j"; |
| const OUTPUT_FILE: &str = "o"; |
| const TARGET_ROOT: &str = "R"; |
| |
| fn get_usage() -> String { |
| "tee_app_info_lint: <Options> [Input-path ..]".to_string() |
| } |
| |
| fn validate_entries(entries: &[AppManifestEntry]) -> Result<()> { |
| let mut app_ids = Set::<&str>::new(); |
| for entry in entries { |
| if !app_ids.insert(&entry.app_name) { |
| bail!("Duplicate entry for id: '{}'", &entry.app_name); |
| } |
| } |
| Ok(()) |
| } |
| |
| fn convert_entries<A: AsRef<Path>>(target_root: A, entries: &mut [AppManifestEntry]) -> Result<()> { |
| for entry in entries { |
| let exec_info = &mut entry.exec_info; |
| let exec_path = if let ExecutableInfo::Path(suffix) = exec_info { |
| target_root.as_ref().join(suffix.trim_start_matches('/')) |
| } else { |
| continue; |
| }; |
| |
| let mut exec_data = Vec::<u8>::new(); |
| File::open(&exec_path) |
| .with_context(|| format!("Failed to open entry executable path: {:?}", &exec_path))? |
| .read_to_end(&mut exec_data) |
| .with_context(|| format!("Failed to read entry executable path: {:?}", &exec_path))?; |
| *exec_info = ExecutableInfo::Digest( |
| compute_sha256(&exec_data).context("Failed to compute SHA256 digest.")?, |
| ); |
| } |
| Ok(()) |
| } |
| |
| fn main() -> Result<()> { |
| stderrlog::new() |
| .module(module_path!()) |
| .init() |
| .context("Failed to initialize log")?; |
| |
| let mut options = Options::default(); |
| let verbose_opt = VerbosityOption::default(&mut options); |
| |
| options.optflag( |
| CHECK_ONLY, |
| "check", |
| "Parse the configurations without printing the result.", |
| ); |
| options.optflag(OUTPUT_JSON, "json", "Write the output as JSON."); |
| options.optopt( |
| OUTPUT_FILE, |
| "output", |
| "Output file name.", |
| "config.flex.bin", |
| ); |
| options.optopt( |
| TARGET_ROOT, |
| "root", |
| "Path to root file system from which to convert binary paths to hashes.", |
| "/build/amd64-generic", |
| ); |
| |
| let help_opt = HelpOption::new(&mut options); |
| |
| let args: Vec<String> = args().collect(); |
| let matches = help_opt.parse_and_check_self(&options, &args, get_usage); |
| let verbosity = verbose_opt.from_matches(&matches); |
| log::set_max_level( |
| match verbosity { |
| 0 => log::Level::Error, |
| 1 => log::Level::Warn, |
| 2 => log::Level::Info, |
| 3 => log::Level::Debug, |
| _ => log::Level::Trace, |
| } |
| .to_level_filter(), |
| ); |
| |
| let mut entries = Vec::<AppManifestEntry>::new(); |
| |
| if matches.free.len() <= 1 { |
| options.usage(&get_usage()); |
| bail!("Missing input path"); |
| } |
| for path_name in &matches.free[1..] { |
| let path = Path::new(path_name); |
| entries.append(&mut entries_from_path(path).context("Failed to get entries from path.")?); |
| } |
| |
| validate_entries(&entries)?; |
| |
| if let Some(target_root) = matches |
| .opt_get::<PathBuf>(TARGET_ROOT) |
| .context("Failed to get validation root option")? |
| { |
| convert_entries(target_root, &mut entries)?; |
| } |
| |
| if matches.opt_present(CHECK_ONLY) { |
| return Ok(()); |
| } |
| |
| let mut file: File; |
| let mut stdout_instance: Stdout; |
| let writer: &mut dyn Write = if let Some(output_filename) = matches |
| .opt_get::<PathBuf>(OUTPUT_FILE) |
| .context("Failed to get output file option")? |
| { |
| file = File::create(output_filename).context("Failed to open file")?; |
| &mut file |
| } else { |
| stdout_instance = stdout(); |
| &mut stdout_instance |
| }; |
| |
| if matches.opt_present(OUTPUT_JSON) { |
| serde_json::to_writer_pretty(writer, &entries).context("Failed to serialize") |
| } else { |
| let serialized: Vec<u8> = flexbuffers::to_vec(&entries).context("Failed to serialize")?; |
| writer |
| .write_all(&serialized) |
| .context("Failed to write output") |
| } |
| } |