From 06fe20992fc221f8948bbca5f3c373caae1396dd Mon Sep 17 00:00:00 2001 From: TommyLike Date: Thu, 1 Jun 2023 09:10:44 +0800 Subject: [PATCH] Fix file type issue --- Cargo.toml | 1 + src/client/cmd/add.rs | 15 +- src/client/file_handler/checksum.rs | 64 +++++++-- src/client/file_handler/kernel_module.rs | 167 ++++++++++++++++++++++- src/client_entrypoint.rs | 9 +- src/infra/sign_plugin/openpgp.rs | 8 +- src/infra/sign_plugin/x509.rs | 5 +- src/util/error.rs | 4 +- 8 files changed, 249 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2a5b6bd..c22074e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ futures = "0.3.26" utoipa = { version = "3", features = ["actix_extras"] } utoipa-swagger-ui = { version ="3.1.3", features = ["actix-web"]} efi_signer = "0.2.0" +regex = "1" [build-dependencies] tonic-build = "0.8.4" diff --git a/src/client/cmd/add.rs b/src/client/cmd/add.rs index e34cd90..1bf7968 100644 --- a/src/client/cmd/add.rs +++ b/src/client/cmd/add.rs @@ -17,6 +17,7 @@ use clap::{Args}; use crate::util::error::Result; use config::{Config}; +use regex::Regex; use std::sync::{Arc, atomic::AtomicBool, RwLock}; use super::traits::SignCommand; use std::path::PathBuf; @@ -40,7 +41,8 @@ use std::sync::atomic::{AtomicI32, Ordering}; lazy_static! { pub static ref FILE_EXTENSION: HashMap> = HashMap::from([ (FileType::Rpm, vec!["rpm", "srpm"]), - (FileType::CheckSum, vec!["txt", "sha256sum"]), + //checksum file can be used for any file + (FileType::CheckSum, vec![".*"]), (FileType::KernelModule, vec!["ko"]), (FileType::EfiImage, vec!["efi"]), ]); @@ -133,11 +135,14 @@ impl CommandAddHandler { fn file_candidates(&self, extension: &str) -> Result { let collections = FILE_EXTENSION.get( &self.file_type).ok_or_else(|| - error::Error::FileNotSupportError(format!("{}", self.file_type)))?; - if collections.contains(&extension) { - return Ok(true) + error::Error::FileNotSupportError(extension.to_string(), self.file_type.to_string()))?; + for value in collections { + let re = Regex::new(format!(r"^{}$", value).as_str()).unwrap(); + if re.is_match(extension) { + return Ok(true) + } } - Ok(false) + Err(error::Error::FileNotSupportError(extension.to_string(), self.file_type.to_string())) } } diff --git a/src/client/file_handler/checksum.rs b/src/client/file_handler/checksum.rs index fdfe5cd..4d1333f 100644 --- a/src/client/file_handler/checksum.rs +++ b/src/client/file_handler/checksum.rs @@ -15,7 +15,7 @@ */ use super::traits::FileHandler; -use crate::util::sign::{SignType, KeyType}; +use crate::util::sign::{KeyType}; use crate::util::error::Result; use async_trait::async_trait; use std::path::PathBuf; @@ -49,14 +49,10 @@ impl FileHandler for CheckSumFileHandler { } if let Some(key_type) = sign_options.get(options::KEY_TYPE) { - if let Some(sign_type) = sign_options.get(options::SIGN_TYPE) { - if sign_type != SignType::Cms.to_string().as_str() - && key_type == KeyType::X509.to_string().as_str() - { - return Err(Error::InvalidArgumentError( - "checksum file only support x509 key with cms sign type".to_string(), - )); - } + if key_type != KeyType::Pgp.to_string().as_str() { + return Err(Error::InvalidArgumentError( + "checksum file only support pgp key type".to_string(), + )); } } Ok(()) @@ -80,3 +76,53 @@ impl FileHandler for CheckSumFileHandler { )) } } + +#[cfg(test)] +mod test { + use super::*; + use std::env; + + #[test] + fn test_validate_options() { + let mut options = HashMap::new(); + options.insert(options::DETACHED.to_string(), "false".to_string()); + let handler = CheckSumFileHandler::new(); + let result = handler.validate_options(&options); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "invalid argument: checksum file only support detached signature" + ); + + options.remove(options::DETACHED); + let result = handler.validate_options(&options); + assert!(result.is_ok()); + + options.insert(options::DETACHED.to_string(), "true".to_string()); + options.insert(options::KEY_TYPE.to_string(), KeyType::Pgp.to_string()); + let result = handler.validate_options(&options); + assert!(result.is_ok()); + + options.insert(options::KEY_TYPE.to_string(), KeyType::X509.to_string()); + let result = handler.validate_options(&options); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "invalid argument: checksum file only support pgp key type" + ); + } + + #[tokio::test] + async fn test_assemble_data() { + let handler = CheckSumFileHandler::new(); + let options = HashMap::new(); + let path = PathBuf::from("./test_data/test.txt"); + let data = vec![vec![1, 2, 3]]; + let temp_dir = env::temp_dir(); + let result = handler.assemble_data(&path, data, &temp_dir, &options).await; + assert!(result.is_ok()); + let (temp_file, file_name) = result.expect("invoke assemble data should work"); + assert_eq!(temp_file.starts_with(temp_dir.to_str().unwrap()), true); + assert_eq!(file_name, "./test_data/test.txt.asc"); + } +} diff --git a/src/client/file_handler/kernel_module.rs b/src/client/file_handler/kernel_module.rs index 1a52f65..809b851 100644 --- a/src/client/file_handler/kernel_module.rs +++ b/src/client/file_handler/kernel_module.rs @@ -102,7 +102,7 @@ impl KernelModuleFileHandler { pub fn get_raw_content(&self, path: &PathBuf, sign_options: &mut HashMap) -> Result> { let raw_content = fs::read(path)?; let mut file = fs::File::open(path)?; - if file.metadata()?.len() <= MAGIC_NUMBER_SIZE as u64 { + if file.metadata()?.len() <= SIGNATURE_SIZE as u64 { return Ok(raw_content); } //identify magic string and end of the file @@ -207,3 +207,168 @@ impl FileHandler for KernelModuleFileHandler { )); } } + +#[cfg(test)] +mod test { + use super::*; + use std::env; + use rand::Rng; + + fn generate_signed_kernel_module(length: usize, incorrect_length: bool) -> Result<(String, Vec)> { + let mut rng = rand::thread_rng(); + let temp_file = env::temp_dir().join(Uuid::new_v4().to_string()); + let mut file = fs::File::create(temp_file.clone())?; + let raw_content: Vec = (0..length).map(|_| rng.gen_range(0..=255)).collect(); + file.write_all(&raw_content)?; + //append fake signature + let signature = vec![1,2,3,4,5,6]; + file.write_all(&signature)?; + let mut size = signature.len(); + if incorrect_length { + size = size + length + 2; + } + //append signature metadata + let signature = ModuleSignature::new(size as c_uint); + file.write_all(&bincode::encode_to_vec( + signature, + config::standard() + .with_fixed_int_encoding() + .with_big_endian())?)?; + file.write_all(MAGIC_NUMBER.as_bytes())?; + Ok((temp_file.display().to_string(), raw_content)) + } + + fn generate_unsigned_kernel_module(length: usize) -> Result<(String, Vec)> { + let mut rng = rand::thread_rng(); + let temp_file = env::temp_dir().join(Uuid::new_v4().to_string()); + let mut file = fs::File::create(temp_file.clone())?; + let raw_content: Vec = (0..length).map(|_| rng.gen_range(0..=255)).collect(); + file.write_all(&raw_content)?; + Ok((temp_file.display().to_string(), raw_content)) + } + + #[test] + fn test_get_raw_content_with_small_unsigned_content() { + let mut sign_options = HashMap::new(); + let file_handler = KernelModuleFileHandler::new(); + let (name, original_content) = generate_unsigned_kernel_module(SIGNATURE_SIZE-1).expect("generate unsigned kernel module failed"); + let path = PathBuf::from(name); + let raw_content = file_handler.get_raw_content(&path, &mut sign_options).expect("get raw content failed"); + assert_eq!(raw_content.len(), SIGNATURE_SIZE-1); + assert_eq!(original_content, raw_content); + } + + #[test] + fn test_get_raw_content_with_large_unsigned_content() { + let mut sign_options = HashMap::new(); + let file_handler = KernelModuleFileHandler::new(); + let (name, original_content) = generate_unsigned_kernel_module(SIGNATURE_SIZE+100).expect("generate unsigned kernel module failed"); + let path = PathBuf::from(name); + let raw_content = file_handler.get_raw_content(&path, &mut sign_options).expect("get raw content failed"); + assert_eq!(raw_content.len(), SIGNATURE_SIZE+100); + assert_eq!(original_content, raw_content); + } + + #[test] + fn test_get_raw_content_with_signed_content() { + let mut sign_options = HashMap::new(); + let file_handler = KernelModuleFileHandler::new(); + let (name, original_content) = generate_signed_kernel_module(100,false).expect("generate signed kernel module failed"); + let path = PathBuf::from(name); + let raw_content = file_handler.get_raw_content(&path, &mut sign_options).expect("get raw content failed"); + assert_eq!(raw_content.len(), 100); + assert_eq!(original_content, raw_content); + } + + #[test] + fn test_get_raw_content_with_invalid_signed_content() { + let mut sign_options = HashMap::new(); + let file_handler = KernelModuleFileHandler::new(); + let (name, _) = generate_signed_kernel_module(100,true).expect("generate signed kernel module failed"); + let path = PathBuf::from(name); + let result = file_handler.get_raw_content(&path, &mut sign_options); + assert_eq!( + result.unwrap_err().to_string(), + "failed to split file: invalid kernel module signature size found" + ); + } + + #[test] + fn test_validate_options() { + let mut options = HashMap::new(); + options.insert(options::KEY_TYPE.to_string(), KeyType::Pgp.to_string()); + let handler = KernelModuleFileHandler::new(); + let result = handler.validate_options(&options); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "invalid argument: kernel module file only support x509 signature" + ); + + options.insert(options::KEY_TYPE.to_string(), KeyType::X509.to_string()); + options.insert(options::SIGN_TYPE.to_string(), SignType::Authenticode.to_string()); + let result = handler.validate_options(&options); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "invalid argument: kernel module file only support cms or pkcs7 sign type" + ); + + + options.insert(options::SIGN_TYPE.to_string(), SignType::Cms.to_string()); + let result = handler.validate_options(&options); + assert!(result.is_ok()); + + options.insert(options::SIGN_TYPE.to_string(), SignType::PKCS7.to_string()); + let result = handler.validate_options(&options); + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_assemble_data_with_detached_true() { + let handler = KernelModuleFileHandler::new(); + let mut options = HashMap::new(); + options.insert(options::DETACHED.to_string(), "true".to_string()); + let path = PathBuf::from("./test_data/test.ko"); + let data = vec![vec![1, 2, 3]]; + let temp_dir = env::temp_dir(); + let result = handler.assemble_data(&path, data, &temp_dir, &options).await; + assert!(result.is_ok()); + let (temp_file, file_name) = result.expect("invoke assemble data should work"); + assert_eq!(temp_file.starts_with(temp_dir.to_str().unwrap()), true); + assert_eq!(file_name, "./test_data/test.ko.p7s"); + let result = fs::read(temp_file).expect("read temp file failed"); + assert_eq!(result, vec![1, 2, 3]); + } + + #[tokio::test] + async fn test_assemble_data_with_detached_false() { + let handler = KernelModuleFileHandler::new(); + let mut options = HashMap::new(); + options.insert(options::DETACHED.to_string(), "false".to_string()); + let (name, raw_content) = generate_signed_kernel_module(100,false).expect("generate signed kernel module failed"); + let path = PathBuf::from(name.clone()); + let data = vec![vec![1, 2, 3]]; + let temp_dir = env::temp_dir(); + let result = handler.assemble_data(&path, data, &temp_dir, &options).await; + assert!(result.is_ok()); + let (temp_file, file_name) = result.expect("invoke assemble data should work"); + assert_eq!(temp_file.starts_with(temp_dir.to_str().unwrap()), true); + assert_eq!(file_name, name); + let result = handler.get_raw_content(&PathBuf::from(temp_file), &mut options).expect("get raw content failed"); + assert_eq!(result, raw_content); + } + + #[tokio::test] + async fn test_split_content() { + let mut sign_options = HashMap::new(); + let file_handler = KernelModuleFileHandler::new(); + let (name, original_content) = generate_unsigned_kernel_module(SIGNATURE_SIZE-1).expect("generate unsigned kernel module failed"); + let path = PathBuf::from(name); + let raw_content = file_handler.split_data(&path, &mut sign_options).await.expect("get raw content failed"); + assert_eq!(raw_content[0].len(), SIGNATURE_SIZE-1); + assert_eq!(original_content, raw_content[0]); + } + +} + diff --git a/src/client_entrypoint.rs b/src/client_entrypoint.rs index 61caab5..395dee2 100644 --- a/src/client_entrypoint.rs +++ b/src/client_entrypoint.rs @@ -74,8 +74,13 @@ fn main() -> Result<()> { }; //handler and quit if let Some(handler) = command { - handler.validate().expect("failed to validate command option"); - if !handler.handle().expect("failed to perform command") { + if let Err(err) = handler.validate() { + error!("failed to validate command: {}", err); + return Err(err); + } + + if let Err(err) = handler.handle() { + error!("failed to handle command: {}", err); return Err(Error::PartialSuccessError) } } diff --git a/src/infra/sign_plugin/openpgp.rs b/src/infra/sign_plugin/openpgp.rs index eee3bc4..0fa57ce 100644 --- a/src/infra/sign_plugin/openpgp.rs +++ b/src/infra/sign_plugin/openpgp.rs @@ -35,11 +35,12 @@ use std::io::{Cursor}; use std::str::from_utf8; use validator::{Validate, ValidationError}; use pgp::composed::StandaloneSignature; -use crate::domain::datakey::entity::{DataKey, DataKeyContent, SecDataKey, KeyType as DataKeyType}; +use crate::domain::datakey::entity::{DataKey, DataKeyContent, SecDataKey}; use crate::util::key::encode_u8_to_hex_string; use super::util::{validate_utc_time_not_expire, validate_utc_time, attributes_validate}; -const VALID_KEY_TYPE: [&str; 2] = ["rsa", "eddsa"]; +// NOTE: `eddsa` will be supported only when it's supported in rpm library, check https://github.com/rpm-rs/rpm/pull/146 +const VALID_KEY_TYPE: [&str; 1] = ["rsa"]; const VALID_KEY_SIZE: [&str; 3] = ["2048", "3072", "4096"]; const VALID_DIGEST_ALGORITHM: [&str; 10] = ["none", "md5", "sha1", "sha1", "sha2_256", "sha2_384","sha2_512","sha2_224","sha3_256", "sha3_512"]; @@ -293,6 +294,7 @@ mod test { use rand::Rng; use secstr::SecVec; use crate::domain::datakey::entity::{KeyState, Visibility}; + use crate::domain::datakey::entity::{KeyType}; use crate::util::options::DETACHED; fn get_default_parameter() -> HashMap { @@ -317,7 +319,7 @@ mod test { description: "fake description".to_string(), user: 1, attributes: get_default_parameter(), - key_type: DataKeyType::OpenPGP, + key_type: KeyType::OpenPGP, fingerprint: "".to_string(), private_key: vec![], public_key: vec![], diff --git a/src/infra/sign_plugin/x509.rs b/src/infra/sign_plugin/x509.rs index eaa0689..6fb9d81 100644 --- a/src/infra/sign_plugin/x509.rs +++ b/src/infra/sign_plugin/x509.rs @@ -34,7 +34,7 @@ use serde::Deserialize; use validator::{Validate, ValidationError}; use crate::util::options; use crate::util::sign::SignType; -use crate::domain::datakey::entity::{DataKey, DataKeyContent, SecDataKey, KeyType as DataKeyType}; +use crate::domain::datakey::entity::{DataKey, DataKeyContent, SecDataKey}; use crate::util::error::{Error, Result}; use crate::domain::sign_plugin::SignPlugins; use crate::util::key::encode_u8_to_hex_string; @@ -264,6 +264,7 @@ mod test { use chrono::{Duration, Utc}; use secstr::SecVec; use crate::domain::datakey::entity::{KeyState, Visibility}; + use crate::domain::datakey::entity::{KeyType}; use crate::util::options::{DETACHED, SIGN_TYPE}; fn get_default_parameter() -> HashMap { @@ -292,7 +293,7 @@ mod test { description: "fake description".to_string(), user: 1, attributes: get_default_parameter(), - key_type: DataKeyType::X509, + key_type: KeyType::X509, fingerprint: "".to_string(), private_key: vec![], public_key: vec![], diff --git a/src/util/error.rs b/src/util/error.rs index 09e861d..083ff98 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -96,8 +96,8 @@ pub enum Error { UnprivilegedError, //client error - #[error("file type not supported {0}")] - FileNotSupportError(String), + #[error("file extension {0} not supported for file {1}")] + FileNotSupportError(String, String), #[error("not any valid file found")] NoFileCandidateError, #[error("failed to split file: {0}")] -- Gitee