diff --git a/src/main.rs b/src/main.rs index d1adaedf..ff6e38a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,6 +32,7 @@ use hyper::service::service_fn; use hyper::{Body, Method, Request, Response, Server, StatusCode}; use serde_json::Map; use std::collections::HashMap; +use std::error::Error; use std::fs::File; use std::io::BufReader; use std::io::Read; @@ -368,8 +369,8 @@ fn get_request_handler( common::RSA_PUBLICKEY_EXPORTABLE.to_string(), p.to_string(), ) { - Some(q) => q, - None => { + Ok(q) => q, + Err(err) => { if let Err(e) = set_response_content( 400, "Failed to crate quote from TPM.", @@ -383,7 +384,7 @@ fn get_request_handler( } return emsg( "TPM error. Failed to create quote from TPM.", - None::, + Some(err.description().to_string()), ); } }; @@ -396,8 +397,8 @@ fn get_request_handler( v.to_string(), p.to_string(), ) { - Some(q) => q, - None => { + Ok(q) => q, + Err(err) => { if let Err(e) = set_response_content( 400, "Failed to create deep quote from TPM.", @@ -411,7 +412,7 @@ fn get_request_handler( } return emsg( "TPM error. Failed to create deep quote from TPM.", - None::, + Some(err.description().to_string()), ); } }; diff --git a/src/secure_mount.rs b/src/secure_mount.rs index 3fe3c3f5..ef8ba3f7 100644 --- a/src/secure_mount.rs +++ b/src/secure_mount.rs @@ -1,10 +1,10 @@ use super::*; use common::emsg; +use std::error::Error; use std::fs; use std::os::unix::fs::PermissionsExt; use std::process::Command; - /* * Input: secure mount directory * Return: Result wrap boolean with error message @@ -128,11 +128,9 @@ fn mount() -> Result> { common::SECURE_SIZE, s, ), - tpm::EXIT_SUCCESS, - true, - false, - String::new(), - ); + None, + ) + .map_err(|e| e.description().to_string())?; Ok(s.to_string()) } diff --git a/src/tpm.rs b/src/tpm.rs index 8cd8b4b5..ea558b5c 100644 --- a/src/tpm.rs +++ b/src/tpm.rs @@ -8,6 +8,8 @@ use flate2::Compression; use openssl::sha; use serde_json::Value; use std::env; +use std::error::Error; +use std::fmt; use std::fs::File; use std::io::prelude::*; use std::io::BufWriter; @@ -24,7 +26,6 @@ const MAX_TRY: usize = 10; const RETRY_SLEEP: Duration = Duration::from_millis(50); const TPM_IO_ERROR: i32 = 5; const RETRY: usize = 4; -pub const EXIT_SUCCESS: i32 = 0; static EMPTYMASK: &'static str = "1"; @@ -34,82 +35,88 @@ Following are function from tpm_initialize.py program *****************************************************************/ /* - * Input: content key in tpmdata - * Return: Result wrap value string or error message + * Input: + * content key in tpmdata + * Return: + * Value string + * KeylimeTpmError * * Getting the tpm data struct and convert it to a json value object to * retrive a particular value by the given key inside the tpm data. */ -fn get_tpm_metadata_content(key: &str) -> Result> { - let tpm_data = match read_tpm_data() { - Ok(data) => data, - Err(e) => return emsg("Failed to read tpmdata.json.", Some(e)), - }; - +fn get_tpm_metadata_content(key: &str) -> Result { + let tpm_data = read_tpm_data()?; let remove: &[_] = &['"', ' ', '/']; - match tpm_data.get(key) { - Some(content) => match content.as_str() { - Some(s) => Ok(s.to_string().trim_matches(remove).to_string()), - None => emsg("Can't convert to string", None::), + tpm_data.get(key).map_or_else( + || { + Err(KeylimeTpmError::new_tpm_rust_error( + format!("Key: {} is missing in tpmdata.json", key).as_str(), + )) }, - None => emsg("Key doesn't exist", None::), - } + |content| { + content.as_str().map_or_else( + || { + Err(KeylimeTpmError::new_tpm_rust_error( + "Failed to convert Value to stirng.", + )) + }, + |s| Ok(s.to_string().trim_matches(remove).to_string()), + ) + }, + ) } /* - * Input: tpm data key - * tpm data value - * Return: Result wrap success or error code -1 + * Input: + * tpm data key + * tpm data value + * Return: + * success + * KeylimeTpmError * * Set the corresponding tpm data key with new value and save the new content - * to tpmdata.json. This version remove global tpmdata variable. Read the file - * before write the content to the file. + * to tpmdata.json. This version remove global tpmdata variable. Read the + * file before write the content to the file. */ fn set_tpm_metadata_content( key: &str, value: &str, -) -> Result<(), Box> { - let mut tpm_data = match read_tpm_data() { - Ok(data) => data, - Err(e) => return emsg("Fail to read tpmdata.json.", Some(e)), - }; - +) -> Result<(), KeylimeTpmError> { + let mut tpm_data = read_tpm_data()?; match tpm_data.get_mut(key) { Some(ptr) => *ptr = json!(value), - None => return emsg("Key doesn't exist", None::), + None => { + return Err(KeylimeTpmError::new_tpm_rust_error( + format!("Key: {} is missing in tpmdata.json", key).as_str(), + )); + } }; - if let Err(e) = write_tpm_data(tpm_data) { - return emsg("Failed to write data to dpmdata.json", Some(e)); - } + write_tpm_data(tpm_data)?; Ok(()) } /* - * Return: Result wrap TPM data or Error Message + * Return: + * TPM data + * KeylimeTpmError * * Read in tpmdata.json file and convert it to a pre-defined struct. Now its * using the sample tpmdata.json in the crate root directory for testing. The * format the same as the original python version. Result is returned to * caller for error handling. */ -fn read_tpm_data() -> Result> { - let file = match File::open("tpmdata.json") { - Ok(f) => f, - Err(e) => return emsg("Failed to open tpmdata.json.", Some(e)), - }; - - let data: Value = match serde_json::from_reader(file) { - Ok(d) => d, - Err(e) => return emsg("Failed to convert tpm data to Json.", Some(e)), - }; - +fn read_tpm_data() -> Result { + let file = File::open("tpmdata.json")?; + let data: Value = serde_json::from_reader(file)?; Ok(data) } /* * Input: tpmdata in Value type - * Return: Result wrap success or io Error + * Return: + * success + * KeylimeTpmError * * Write the tpmdata to tpmdata.json file with result indicating execution * result. Different implementation than the original python version, which @@ -117,33 +124,20 @@ fn read_tpm_data() -> Result> { * could read the data before write instead of using a static type to store * it globally. */ -fn write_tpm_data(data: Value) -> Result<(), Box> { - let mut buffer = match File::create("tpmdata.json") { - Ok(f) => BufWriter::new(f), - Err(e) => return emsg("Failed to open tpmdata.json.", Some(e)), - }; - - let data_string = match serde_json::to_string_pretty(&data) { - Ok(d) => d, - Err(e) => return emsg("Failed to convert tpm data to Json.", Some(e)), - }; - - match buffer.write(data_string.as_bytes()) { - Ok(s) => info!("Wrote {} byte to file.", s), - Err(e) => return emsg("Failed to write to tpmdata.json.", Some(e)), - }; +fn write_tpm_data(data: Value) -> Result<(), KeylimeTpmError> { + let mut buffer = BufWriter::new(File::create("tpmdata.json")?); + let data_string = serde_json::to_string_pretty(&data)?; + buffer.write(data_string.as_bytes())?; // Use flush to ensure all the intermediately buffered contents // reach their destination - if let Err(e) = buffer.flush() { - return emsg("Failed to flush to tpm data file.", Some(e)); - } + buffer.flush()?; Ok(()) } /* - * Input: None - * Return: boolean + * Return: + * true for vtpm/false otherwise * * If tpm is a tpm elumator, return true, other wise return false */ @@ -161,25 +155,16 @@ pub fn is_vtpm() -> bool { } /* - * Return: Result wrap the manufacture information + * Return: + * manufacture information + * KeylimeTpmError * * getting the tpm manufacturer information * is_vtpm helper method */ -fn get_tpm_manufacturer() -> Result> { - let (return_output, _return_code, _file_output) = run( - "getcapability -cap 1a".to_string(), - EXIT_SUCCESS, - true, - false, - String::new(), - ); - let content = match String::from_utf8(return_output) { - Ok(c) => c, - Err(e) => return emsg("Failed to convert output to string.", Some(e)), - }; - - let lines: Vec<&str> = content.split("\n").collect(); +fn get_tpm_manufacturer() -> Result { + let (return_output, _) = run("getcapability -cap 1a".to_string(), None)?; + let lines: Vec<&str> = return_output.split("\n").collect(); let mut manufacturer = String::new(); for line in lines { let line_tmp = String::from(line); @@ -190,7 +175,9 @@ fn get_tpm_manufacturer() -> Result> { } } } - emsg("Vendor information not found.", None::) + Err(KeylimeTpmError::new_tpm_rust_error( + "TPM manufacture information is missing.", + )) } /*************************************************************** @@ -199,11 +186,14 @@ Following are function from tpm_quote.py program *****************************************************************/ /* - * Input: nonce string - * data that needs to be pass to the pcr - * pcrmask + * Input: + * nonce string + * data that needs to be pass to the pcr + * pcrmask * - * Output: quote from tpm pcr + * Output: + * quote from tpm pcr + * KeylimeTpmError * * Getting quote form tpm, same implementation as the original python version. */ @@ -211,55 +201,32 @@ pub fn create_quote( nonce: String, data: String, mut pcrmask: String, -) -> Option { - let temp_file = match NamedTempFile::new() { - Ok(f) => f, - Err(e) => { - error!("Failed to create new temporary file. Error {}.?", e); - return None; - } - }; - +) -> Result { + let temp_file = NamedTempFile::new()?; let quote_path = match temp_file.path().to_str() { - Some(s) => s, - None => return None, - }; - - let key_handle = match get_tpm_metadata_content("aik_handle") { - Ok(c) => c, - Err(e) => { - println!("Failed to get tpm aik_handle with error {}.", e); - return None; - } - }; - - let aik_password = match get_tpm_metadata_content("aik_pw") { - Ok(c) => c, - Err(e) => { - println!("Failed to get tpm aik_pw with error {}.", e); - return None; + None => { + return Err(KeylimeTpmError::new_tpm_rust_error( + "Can't retrieve temp file path.", + )); } + Some(p) => p, }; + let key_handle = get_tpm_metadata_content("aik_handle")?; + let aik_password = get_tpm_metadata_content("aik_pw")?; if pcrmask == "".to_string() { pcrmask = EMPTYMASK.to_string(); } if !(data == "".to_string()) { - let pcrmask_int: i32 = match pcrmask.parse() { - Ok(i) => i, - Err(e) => { - error!("Failed to parse pcrmask to integer. Error {}.", e); - return None; - } - }; + let pcrmask_int: i32 = pcrmask.parse()?; pcrmask = format!("0x{}", (pcrmask_int + (1 << common::TPM_DATA_PCR))); let mut command = format!("pcrreset -ix {}", common::TPM_DATA_PCR); // RUN - run(command, EXIT_SUCCESS, true, false, String::new()); + run(command, None)?; // Use SHA1 to hash the data let mut hasher = sha::Sha1::new(); @@ -272,7 +239,8 @@ pub fn create_quote( hex::encode(data_sha1_hash), ); - run(command, EXIT_SUCCESS, true, false, String::new()); + // RUN + run(command, None)?; } // store quote into the temp file that will be extracted later @@ -281,25 +249,21 @@ pub fn create_quote( key_handle, aik_password, pcrmask, nonce, quote_path, ); - let (_return_output, _exit_code, quote_raw) = - run(command, EXIT_SUCCESS, true, false, quote_path.to_string()); - + let (_, quote_raw) = run(command, Some(quote_path))?; let mut quote_return = String::from("r"); - - if let Some(s) = quote_raw { - quote_return.push_str(&base64_zlib_encode(s)); - Some(quote_return) - } else { - None - } + quote_return.push_str(&base64_zlib_encode(quote_raw)?); + Ok(quote_return) } /* - * Input: nonce string - * data that needs to be pass to the pcr - * pcrmask + * Input: + * nonce string + * data that needs to be pass to the pcr + * pcrmask * - * Output: deep quote from tpm pcr + * Output: + * deep quote string from tpm pcr + * KeylimeTpmError * * Getting deep quote form tpm, same implementation as the original python * version. Same procedures as quote by this is a deep quote. @@ -309,43 +273,19 @@ pub fn create_deep_quote( data: String, mut pcrmask: String, mut vpcrmask: String, -) -> Option { - let temp_file = match NamedTempFile::new() { - Ok(f) => f, - Err(e) => { - error!("Failed to create new temporary file. Error {}.?", e); - return None; - } - }; - +) -> Result { + let temp_file = NamedTempFile::new()?; let quote_path = match temp_file.path().to_str() { - Some(s) => s, - None => return None, - }; - - let key_handle = match get_tpm_metadata_content("aik_handle") { - Ok(c) => c, - Err(e) => { - println!("Failed to get tpm aik_handle with error {}.", e); - return None; - } - }; - - let aik_password = match get_tpm_metadata_content("aik_pw") { - Ok(c) => c, - Err(e) => { - println!("Failed to get tpm aik_pw with error {}.", e); - return None; - } - }; - - let owner_password = match get_tpm_metadata_content("owner_pw") { - Ok(c) => c, - Err(e) => { - println!("Failed to get tpm owner_pw with error {}.", e); - return None; + None => { + return Err(KeylimeTpmError::new_tpm_rust_error( + "Can't retieve temp file path.", + )); } + Some(p) => p, }; + let key_handle = get_tpm_metadata_content("aik_handle")?; + let aik_password = get_tpm_metadata_content("aik_pw")?; + let owner_password = get_tpm_metadata_content("owner_pw")?; if pcrmask == "".to_string() { pcrmask = EMPTYMASK.to_string(); @@ -356,20 +296,13 @@ pub fn create_deep_quote( } if !(data == "".to_string()) { - let vpcrmask_int: i32 = match vpcrmask.parse() { - Ok(i) => i, - Err(e) => { - error!("Failed to parse vpcrmask to integer. Error {}.", e); - return None; - } - }; + let vpcrmask_int: i32 = vpcrmask.parse()?; vpcrmask = format!("0x{}", (vpcrmask_int + (1 << common::TPM_DATA_PCR))); let mut command = format!("pcrreset -ix {}", common::TPM_DATA_PCR); - // RUN - run(command, EXIT_SUCCESS, true, false, String::new()); - + //RUN + run(command, None)?; let mut hasher = sha::Sha1::new(); hasher.update(data.as_bytes()); let data_sha1_hash = hasher.finish(); @@ -380,8 +313,8 @@ pub fn create_deep_quote( hex::encode(data_sha1_hash), ); - // RUN - run(command, EXIT_SUCCESS, true, false, String::new()); + //RUN + run(command, None)?; } // store quote into the temp file that will be extracted later @@ -397,21 +330,17 @@ pub fn create_deep_quote( ); // RUN - let (_, _, quote_raw) = - run(command, EXIT_SUCCESS, true, false, quote_path.to_string()); - + let (_, quote_raw) = run(command, Some(quote_path))?; let mut quote_return = String::from("d"); - if let Some(q) = quote_raw { - quote_return.push_str(&base64_zlib_encode(q)); - Some(quote_return) - } else { - None - } + quote_return.push_str("e_raw); + Ok(quote_return) } /* * Input: string to be encoded - * Output: encoded string output + * Output: + * encoded string output + * KeylimeTpmError * * Use zlib to compression the input and encoded with base64 encoding * method @@ -420,19 +349,11 @@ pub fn create_deep_quote( * decode the hex output and give back the original text message. No able * to test with identical python function output string. */ -fn base64_zlib_encode(data: String) -> String { - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - - match e.write_all(data.as_bytes()) { - Ok(_) => { - let compressed_bytes = e.finish(); - match compressed_bytes { - Ok(e) => base64::encode(&e), - Err(_) => String::from(""), - } - } - Err(_) => String::from("Encode Fail!"), - } +fn base64_zlib_encode(data: String) -> Result { + let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(data.as_bytes())?; + let compressed_bytes = encoder.finish()?; + Ok(base64::encode(&compressed_bytes)) } /* @@ -491,26 +412,25 @@ Following are function from tpm_exec.py program /* * Input: - * cmd: command to be executed - * except_code: return code that needs extra handling - * raise_on_error: raise exception/panic while encounter error option - * lock: lock engage option + * command: command to be executed * output_path: file output location * return: - * tuple contains (standard output, return code, and file output) + * execution return output and file output + * KeylimeTpmError * - * Execute tpm command through shell commands and return the execution - * result in a tuple. Implement as original python version. Haven't - * implemented tpm stubbing and metric. + * Set up execution envrionment to execute tpm command through shell commands + * and return the execution result in a tuple. Based on the latest update of + * python keylime this function implement the functionality of cmd_exec + * script in the python keylime repo. RaiseOnError, return code and lock are + * dropped due to different error handling in Rust. Returned output string are + * preprocessed to before returning for code efficient. */ pub fn run<'a>( command: String, - except_code: i32, - raise_on_error: bool, - _lock: bool, - output_path: String, -) -> (Vec, Option, Option) { - /* stubbing placeholder */ + output_path: Option<&str>, +) -> Result<(String, String), KeylimeTpmError> { + let mut file_output = String::new(); + let mut output: Output; // tokenize input command let words: Vec<&str> = command.split(" ").collect(); @@ -521,36 +441,29 @@ pub fn run<'a>( // setup environment variable let mut env_vars: HashMap = HashMap::new(); for (key, value) in env::vars() { - // println!("{}: {}", key, value); env_vars.insert(key.to_string(), value.to_string()); } - env_vars.insert("TPM_SERVER_PORT".to_string(), "9998".to_string()); env_vars.insert("TPM_SERVER_NAME".to_string(), "localhost".to_string()); match env_vars.get_mut("PATH") { Some(v) => v.push_str(common::TPM_TOOLS_PATH), - None => error!("PATH doesn't exist."), + None => { + return Err(KeylimeTpmError::new_tpm_rust_error( + "PATH envrionment variable dosen't exist.", + )); + } } - let mut t_diff: u64 = 0; - let mut output: Output; - - loop { + // main loop + 'exec: loop { + // Start time stamp let t0 = SystemTime::now(); - // command execution - output = Command::new(&cmd) - .args(args) - .envs(&env_vars) - .output() - .expect("failed to execute process"); + output = Command::new(&cmd).args(args).envs(&env_vars).output()?; // measure execution time - match t0.duration_since(t0) { - Ok(t_delta) => t_diff = t_delta.as_secs(), - Err(_) => {} - } - info!("Time cost: {}", t_diff); + let t_diff = t0.duration_since(t0)?; + info!("Time cost: {}", t_diff.as_secs()); // assume the system is linux println!("number tries: {:?}", number_tries); @@ -559,12 +472,17 @@ pub fn run<'a>( Some(TPM_IO_ERROR) => { number_tries += 1; if number_tries >= MAX_TRY { - error!("TPM appears to be in use by another application. Keylime is incompatible with other TPM TSS applications like trousers/tpm-tools. Please uninstall or disable."); - break; + return Err(KeylimeTpmError::new_tpm_error( + TPM_IO_ERROR, + "TPM appears to be in use by another application. + Keylime is incompatible with other TPM TSS + applications like trousers/tpm-tools. Please + uninstall or disable.", + )); } info!( - "Failed to call TPM {}/{} times, trying again in {} seconds...", + "Failed to call TPM {}/{} times, trying again in {} secs.", number_tries, MAX_TRY, RETRY, @@ -572,40 +490,42 @@ pub fn run<'a>( thread::sleep(RETRY_SLEEP); } - _ => break, + + _ => break 'exec, } } - let return_output = output.stdout; - let return_code = output.status.code(); - - if let (Some(c), true) = (return_code, raise_on_error) { - if c != except_code { - error!( - "Command: {} returned {}, expected {}, output {}", - command, + let return_output = String::from_utf8(output.stdout)?; + match output.status.code() { + None => { + return Err(KeylimeTpmError::new_tpm_rust_error( + "Execution return code is None.", + )); + } + Some(0) => info!("Successfully executed TPM command."), + Some(c) => { + return Err(KeylimeTpmError::new_tpm_error( c, - except_code.to_string(), - String::from_utf8_lossy(&return_output), - ); + format!( + "Command: {} returned {}, output {}", + command, c, return_output, + ) + .as_str(), + )); } } - let mut file_output: String = String::new(); - - match read_file_output_path(output_path) { - Ok(content) => file_output = content, - Err(_) => {} + // Retrive data from output path file + if let Some(p) = output_path { + file_output = read_file_output_path(p.to_string())?; } - /* metric output placeholder */ - - (return_output, return_code, Some(file_output)) + Ok((return_output, file_output)) } /* - * input: file name - * return: the content of the file int Result<> + * Input: file name + * Return: the content of the file int Result<> * * run method helper method * read in the file and return the content of the file into a Result enum @@ -617,6 +537,94 @@ fn read_file_output_path(output_path: String) -> std::io::Result { Ok(contents) } +/* + * Custom Error type for tpm execution error. It contains both error from the + * TPM command execution result or error cause by rust function. Potential + * rust error are map to this error by implemented From<> trait. + */ +#[derive(Debug)] +pub enum KeylimeTpmError { + TpmRustError { details: String }, + TpmError { code: i32, details: String }, +} + +impl KeylimeTpmError { + fn new_tpm_error(err_code: i32, err_msg: &str) -> KeylimeTpmError { + KeylimeTpmError::TpmError { + code: err_code, + details: err_msg.to_string(), + } + } + + fn new_tpm_rust_error(err_msg: &str) -> KeylimeTpmError { + KeylimeTpmError::TpmRustError { + details: err_msg.to_string(), + } + } +} + +impl Error for KeylimeTpmError { + fn description(&self) -> &str { + match &self { + KeylimeTpmError::TpmError { + ref details, + ref code, + } => details, + KeylimeTpmError::TpmRustError { ref details } => details, + } + } +} + +impl fmt::Display for KeylimeTpmError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + KeylimeTpmError::TpmError { + ref code, + ref details, + } => write!( + f, + "Execute TPM command failed with Error Code: [{}] and + Error Message [{}].", + code, details, + ), + KeylimeTpmError::TpmRustError { ref details } => write!( + f, + "Error occur in TPM rust interface with message [{}].", + details, + ), + } + } +} + +impl From for KeylimeTpmError { + fn from(e: std::io::Error) -> KeylimeTpmError { + KeylimeTpmError::new_tpm_rust_error(e.description()) + } +} + +impl From for KeylimeTpmError { + fn from(e: std::time::SystemTimeError) -> KeylimeTpmError { + KeylimeTpmError::new_tpm_rust_error(e.description()) + } +} + +impl From for KeylimeTpmError { + fn from(e: std::string::FromUtf8Error) -> KeylimeTpmError { + KeylimeTpmError::new_tpm_rust_error(e.description()) + } +} + +impl From for KeylimeTpmError { + fn from(e: serde_json::error::Error) -> KeylimeTpmError { + KeylimeTpmError::new_tpm_rust_error(e.description()) + } +} + +impl From for KeylimeTpmError { + fn from(e: std::num::ParseIntError) -> KeylimeTpmError { + KeylimeTpmError::new_tpm_rust_error(e.description()) + } +} /* * These test are for Centos and tpm4720 elmulator install environment. It * test tpm command before execution. @@ -663,8 +671,7 @@ mod tests { match command_exist("getrandom") { true => { let command = "getrandom -size 8 -out foo.out".to_string(); - run(command, EXIT_SUCCESS, true, false, String::new()); - + run(command, None); let p = Path::new("foo.out"); assert_eq!(p.exists(), true); match fs::remove_file("foo.out") {