diff --git a/Cargo.toml b/Cargo.toml index ad7aae3cc0007a24635e26eff87268dbc8c54562..c7809b0e1c821ae1bec463f581e315bb622ede2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ num_cpus = "1.15.0" walkdir = "2.3.2" async-channel = "1.8.0" uuid = { version = "1.3.0", features = ["v4"]} -rpm-infra ="0.12.1" +rpm-infra ="0.12.2" dns-lookup = {version="1.0.8"} sha2 = "0.10.6" bincode = "2.0.0-rc.3" diff --git a/src/client/cmd/add.rs b/src/client/cmd/add.rs index 103b331ce9badd04e57617f8c6f2dead9dc9fb02..2f039164be8d61e2ad263ae95cea717114f8526f 100644 --- a/src/client/cmd/add.rs +++ b/src/client/cmd/add.rs @@ -73,6 +73,9 @@ pub struct CommandAdd { #[arg(value_enum, default_value_t=SignType::Cms)] #[arg(help = "specify the signature type, meaningful when key type is x509, EFI file supports `authenticode` only and KO file supports `cms` and `pkcs7`")] sign_type: SignType, + #[arg(long)] + #[arg(help = "force create rpm v3 signature, default is false. only support when file type is rpm")] + rpm_v3: bool, } @@ -91,6 +94,7 @@ pub struct CommandAddHandler { max_concurrency: usize, sign_type: SignType, token: Option, + rpm_v3: bool, } impl CommandAddHandler { @@ -99,7 +103,8 @@ impl CommandAddHandler { HashMap::from([ (options::DETACHED.to_string(), self.detached.to_string()), (options::KEY_TYPE.to_string(), self.key_type.to_string()), - (options::SIGN_TYPE.to_string(), self.sign_type.to_string())]) + (options::SIGN_TYPE.to_string(), self.sign_type.to_string()), + (options::RPM_V3_SIGNATURE.to_string(), self.rpm_v3.to_string())]) } fn collect_file_candidates(&self) -> Result> { if self.path.is_dir() { @@ -182,6 +187,7 @@ impl SignCommand for CommandAddHandler { max_concurrency: config.read()?.get_string("max_concurrency")?.parse()?, sign_type: command.sign_type, token, + rpm_v3: command.rpm_v3 }) } diff --git a/src/client/file_handler/rpm.rs b/src/client/file_handler/rpm.rs index 383575de7b250cb409578368514688ed874ea164..d2f63322189a6d339c699dbf4c2c23e397d1341e 100644 --- a/src/client/file_handler/rpm.rs +++ b/src/client/file_handler/rpm.rs @@ -23,7 +23,7 @@ use std::fs::File; use std::str::FromStr; #[allow(unused_imports)] use std::io::{BufReader, Read}; -use rpm::{Header, IndexSignatureTag, Package, Digests}; +use rpm::{Header, IndexSignatureTag, Package, Digests, IndexTag}; use uuid::Uuid; use crate::domain::datakey::plugins::openpgp::OpenPGPKeyType; @@ -61,6 +61,18 @@ impl RpmFileHandler { } } } + + fn generate_v3_signature(&self, sign_options: &HashMap, package: &Package) -> bool { + if let Some(v3_format) = sign_options.get(options::RPM_V3_SIGNATURE) { + if v3_format == "true" { + return true; + } + } + if package.metadata.header.entry_is_present(IndexTag::RPMTAG_PAYLOADDIGEST) || package.metadata.header.entry_is_present(IndexTag::RPMTAG_PAYLOADDIGESTALT) { + return false; + } + true + } } //todo: figure our why is much slower when async read & write with tokio is enabled. @@ -81,28 +93,36 @@ impl FileHandler for RpmFileHandler { Ok(()) } - //rpm has two sections need to be signed for rsa + //rpm has two sections need to be signed for rpm v3 signature //1. header //2. header and content - // while there is only section to be signed for dsa + //while there is only section to be signed for rpm v4, also for eddsa key only v4 is supported //1. header async fn split_data( &self, path: &PathBuf, - _sign_options: &mut HashMap, + sign_options: &mut HashMap, key_attributes: &HashMap) -> Result>> { let file = File::open(path)?; let package = Package::parse(&mut BufReader::new(file))?; let mut header_bytes = Vec::::with_capacity(1024); //collect head and head&payload arrays package.metadata.header.write(&mut header_bytes)?; - return if self.get_key_type(key_attributes) == OpenPGPKeyType::Rsa { - let mut header_and_content = Vec::new(); - header_and_content.extend(header_bytes.clone()); - header_and_content.extend(package.content.clone()); - Ok(vec![header_bytes, header_and_content]) + return if self.get_key_type(key_attributes) == OpenPGPKeyType::Eddsa { + if self.generate_v3_signature(sign_options, &package) { + Err(Error::InvalidArgumentError("eddsa key does not support v3 signature".to_string())) + } else { + Ok(vec![header_bytes]) + } } else { - Ok(vec![header_bytes]) + if self.generate_v3_signature(sign_options, &package) { + let mut header_and_content = Vec::new(); + header_and_content.extend(header_bytes.clone()); + header_and_content.extend(package.content.clone()); + Ok(vec![header_bytes, header_and_content]) + } else { + Ok(vec![header_bytes]) + } } } async fn assemble_data( @@ -110,7 +130,7 @@ impl FileHandler for RpmFileHandler { path: & PathBuf, data: Vec>, temp_dir: &PathBuf, - _sign_options: &HashMap, + sign_options: &HashMap, key_attributes: &HashMap ) -> Result<(String, String)> { let temp_rpm = temp_dir.join(Uuid::new_v4().to_string()); @@ -127,14 +147,24 @@ impl FileHandler for RpmFileHandler { let key_type = self.get_key_type(key_attributes); let builder = match key_type { OpenPGPKeyType::Rsa => { - Header::::builder().add_digest( - &header_digest_sha1, - &header_digest_sha256, - &header_and_content_digest, - ).add_rsa_signature( - data[0].as_slice(), - data[1].as_slice(), - ) + if self.generate_v3_signature(sign_options, &package) { + Header::::builder().add_digest( + &header_digest_sha1, + &header_digest_sha256, + &header_and_content_digest, + ).add_rsa_signature_legacy( + data[0].as_slice(), + data[1].as_slice() + ) + } else { + Header::::builder().add_digest( + &header_digest_sha1, + &header_digest_sha256, + &header_and_content_digest, + ).add_rsa_signature( + data[0].as_slice(), + ) + } } OpenPGPKeyType::Eddsa => { Header::::builder().add_digest( @@ -166,6 +196,11 @@ mod test { Ok(PathBuf::from(current_dir.join("test_assets").join("Imath-3.1.4-1.oe2303.x86_64.rpm"))) } + fn get_signed_rpm_without_payload_digest() -> Result { + let current_dir = env::current_dir().expect("get current dir failed"); + Ok(PathBuf::from(current_dir.join("test_assets").join("at-3.1.10-43.el6.x86_64.rpm"))) + } + fn generate_invalid_rpm() -> Result { let temp_file = env::temp_dir().join(Uuid::new_v4().to_string()); let mut file = File::create(temp_file.clone())?; @@ -212,8 +247,19 @@ mod test { } #[tokio::test] - async fn test_split_data_success() { + async fn test_split_rpm_with_payload_digest_success() { + let mut sign_options = HashMap::new(); + let file_handler = RpmFileHandler::new(); + let path = get_signed_rpm().expect("get signed rpm failed"); + let raw_content = file_handler.split_data(&path, &mut sign_options, &HashMap::new()).await.expect("get raw content failed"); + assert_eq!(raw_content.len(), 1); + assert_eq!(raw_content[0].len(), 4325); + } + + #[tokio::test] + async fn test_split_rpm_with_payload_digest_and_v3_force_success() { let mut sign_options = HashMap::new(); + sign_options.insert(options::RPM_V3_SIGNATURE.to_string(), true.to_string()); let file_handler = RpmFileHandler::new(); let path = get_signed_rpm().expect("get signed rpm failed"); let raw_content = file_handler.split_data(&path, &mut sign_options, &HashMap::new()).await.expect("get raw content failed"); @@ -222,6 +268,28 @@ mod test { assert_eq!(raw_content[1].len(), 67757); } + #[tokio::test] + async fn test_split_rpm_with_payload_digest_and_v3_force_eddsa_failed() { + let mut sign_options = HashMap::new(); + sign_options.insert(options::RPM_V3_SIGNATURE.to_string(), true.to_string()); + let mut key_attributes = HashMap::new(); + key_attributes.insert(options::KEY_TYPE.to_string(), OpenPGPKeyType::Eddsa.to_string()); + let file_handler = RpmFileHandler::new(); + let path = get_signed_rpm().expect("get signed rpm failed"); + file_handler.split_data(&path, &mut sign_options, &key_attributes).await.expect_err("eddsa key does not support v3 signature"); + } + + #[tokio::test] + async fn test_split_rpm_without_payload_digest_success() { + let mut sign_options = HashMap::new(); + let file_handler = RpmFileHandler::new(); + let path = get_signed_rpm_without_payload_digest().expect("get signed rpm failed"); + let raw_content = file_handler.split_data(&path, &mut sign_options, &HashMap::new()).await.expect("get raw content failed"); + assert_eq!(raw_content.len(), 2); + assert_eq!(raw_content[0].len(), 21484); + assert_eq!(raw_content[1].len(), 60304); + } + #[tokio::test] async fn test_split_data_failed() { let mut sign_options = HashMap::new(); diff --git a/src/util/options.rs b/src/util/options.rs index 03e2589558976ffe62c9b9b4975e91472fe233d1..5c3bdae1888cc71792ba534ed7d0ec75e99a82f6 100644 --- a/src/util/options.rs +++ b/src/util/options.rs @@ -16,4 +16,5 @@ pub const DETACHED: &str = "detached"; pub const KEY_TYPE: &str = "key_type"; -pub const SIGN_TYPE: &str = "sign_type"; \ No newline at end of file +pub const SIGN_TYPE: &str = "sign_type"; +pub const RPM_V3_SIGNATURE: &str = "rpm_signature_type"; \ No newline at end of file diff --git a/test_assets/at-3.1.10-43.el6.x86_64.rpm b/test_assets/at-3.1.10-43.el6.x86_64.rpm new file mode 100644 index 0000000000000000000000000000000000000000..54780c4538b70530574de213a895509e99195a4f Binary files /dev/null and b/test_assets/at-3.1.10-43.el6.x86_64.rpm differ