blob: 23dbed139af59ddd98a9e656e8fb91e9aa45eff7 [file] [log] [blame]
// 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")
}
}