From b69cbc7a0e66fc8ba6dd408a1889b236267e72b7 Mon Sep 17 00:00:00 2001 From: TommyLike Date: Mon, 7 Aug 2023 17:30:00 +0800 Subject: [PATCH] Support EDDSA in rpm package --- Cargo.toml | 1 + proto/signatrust.proto | 14 ++ scripts/initialize-user-and-keys.sh | 13 +- src/application/datakey.rs | 5 + src/client/cmd/add.rs | 33 +++- src/client/file_handler/efi.rs | 4 +- src/client/file_handler/generic.rs | 3 +- src/client/file_handler/kernel_module.rs | 8 +- src/client/file_handler/rpm.rs | 84 ++++++++--- src/client/file_handler/traits.rs | 2 + src/client/worker/assembler.rs | 11 +- src/client/worker/key_fetcher.rs | 63 ++++++++ src/client/worker/mod.rs | 1 + src/client/worker/splitter.rs | 7 +- src/control_admin_entrypoint.rs | 8 +- src/domain/datakey/mod.rs | 1 + src/domain/datakey/plugins/mod.rs | 16 ++ src/domain/datakey/plugins/openpgp.rs | 142 ++++++++++++++++++ src/domain/datakey/plugins/x509.rs | 121 +++++++++++++++ src/infra/sign_plugin/openpgp.rs | 96 +++--------- src/infra/sign_plugin/x509.rs | 88 +++-------- src/presentation/handler/data/sign_handler.rs | 34 ++++- 22 files changed, 572 insertions(+), 183 deletions(-) create mode 100644 src/client/worker/key_fetcher.rs create mode 100644 src/domain/datakey/plugins/mod.rs create mode 100644 src/domain/datakey/plugins/openpgp.rs create mode 100644 src/domain/datakey/plugins/x509.rs diff --git a/Cargo.toml b/Cargo.toml index 1e920fc..2efd5be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ efi_signer = "0.2.4" regex = "1" csrf= "0.4.1" data-encoding= "2.4.0" +enum-iterator= "1.4.1" [build-dependencies] tonic-build = "0.8.4" diff --git a/proto/signatrust.proto b/proto/signatrust.proto index f60eece..56cc7c1 100644 --- a/proto/signatrust.proto +++ b/proto/signatrust.proto @@ -5,9 +5,23 @@ package signatrust; // The Signatrust service definition. service Signatrust { + //Obtain key information + rpc GetKeyInfo(GetKeyInfoRequest) returns (GetKeyInfoResponse) {}; + //Sign package rpc SignStream(stream SignStreamRequest) returns (SignStreamResponse) {}; } +message GetKeyInfoRequest { + string key_type = 1; + string key_id = 2; + optional string token = 3; +} + +message GetKeyInfoResponse { + map attributes = 1; + string error = 2; +} + message SignStreamRequest { bytes data = 1; string key_type = 2; diff --git a/scripts/initialize-user-and-keys.sh b/scripts/initialize-user-and-keys.sh index e8bfbec..e65349b 100755 --- a/scripts/initialize-user-and-keys.sh +++ b/scripts/initialize-user-and-keys.sh @@ -38,9 +38,14 @@ function create_default_x509_ee { --param-x509-common-name Infra --param-x509-organization Huawei --param-x509-locality ShenZhen --param-x509-province-name GuangDong --param-x509-country-name CN --param-x509-organizational-unit Infra --digest-algorithm sha2_256 --param-x509-parent-name default-x509ica --visibility public } -function create_default_openpgp_keys { +function create_default_openpgp_rsa { echo "start to create default openpgp keys identified with default-pgp" - RUST_LOG=info ./target/debug/control-admin --config ./config/server.toml generate-keys --name default-pgp --description "used for test purpose only" --key-type pgp --email tommylikehu@gmail.com --param-key-type rsa --param-key-size 2048 --param-pgp-email infra@openeuler.org --param-pgp-passphrase husheng1234 --digest-algorithm sha2_256 --visibility public + RUST_LOG=info ./target/debug/control-admin --config ./config/server.toml generate-keys --name default-pgp-rsa --description "used for test purpose only" --key-type pgp --email tommylikehu@gmail.com --param-key-type rsa --param-key-size 2048 --param-pgp-email infra@openeuler.org --param-pgp-passphrase husheng1234 --digest-algorithm sha2_256 --visibility public +} + +function create_default_openpgp_eddsa { + echo "start to create default openpgp keys identified with default-pgp" + RUST_LOG=info ./target/debug/control-admin --config ./config/server.toml generate-keys --name default-pgp-eddsa --description "used for test purpose only" --key-type pgp --email tommylikehu@gmail.com --param-key-type eddsa --param-pgp-email infra@openeuler.org --param-pgp-passphrase husheng1234 --digest-algorithm sha2_256 --visibility public } @@ -58,5 +63,7 @@ create_default_x509_ica create_default_x509_ee -create_default_openpgp_keys +create_default_openpgp_rsa + +create_default_openpgp_eddsa diff --git a/src/application/datakey.rs b/src/application/datakey.rs index 1798027..123d543 100644 --- a/src/application/datakey.rs +++ b/src/application/datakey.rs @@ -49,6 +49,7 @@ pub trait KeyService: Send + Sync{ async fn disable(&self, user: Option, id_or_name: String) -> Result<()>; //used for data server async fn sign(&self, key_type: String, key_name: String, options: &HashMap, data: Vec) ->Result>; + async fn get_by_type_and_name(&self, key_type: String, key_name: String) ->Result; //method below used for maintenance fn start_cache_cleanup_loop(&self, cancel_token: CancellationToken) -> Result<()>; @@ -283,6 +284,10 @@ where self.sign_service.read().await.sign(&key, data, options.clone()).await } + async fn get_by_type_and_name(&self, key_type: String, key_name: String) -> Result { + self.container.get_data_key(key_type, key_name).await + } + fn start_cache_cleanup_loop(&self, cancel_token: CancellationToken) -> Result<()> { let container = self.container.clone(); let mut interval = time::interval(Duration::seconds(120).to_std()?); diff --git a/src/client/cmd/add.rs b/src/client/cmd/add.rs index a5bb68a..103b331 100644 --- a/src/client/cmd/add.rs +++ b/src/client/cmd/add.rs @@ -37,6 +37,7 @@ use crate::client::worker::signer::RemoteSigner; use crate::client::worker::splitter::Splitter; use crate::client::worker::traits::SignHandler; use std::sync::atomic::{AtomicI32, Ordering}; +use crate::client::worker::key_fetcher::KeyFetcher; use crate::util::error::Error::CommandProcessFailed; use crate::util::key::file_exists; @@ -163,7 +164,7 @@ impl SignCommand for CommandAddHandler { } let mut token = None; if let Ok(t) = config.read()?.get_string("token") { - if t != "" { + if !t.is_empty() { token = Some(t); } } @@ -199,7 +200,6 @@ impl SignCommand for CommandAddHandler { // vector sign_chn assemble_chn collect_chn // fetcher-----------splitter * N----------remote signer * N---------------assembler * N--------------collector * N fn handle(&self) -> Result { - let files = self.collect_file_candidates()?; let succeed_files = Arc::new(AtomicI32::new(0)); let failed_files = Arc::new(AtomicI32::new(0)); let runtime = runtime::Builder::new_multi_thread() @@ -211,7 +211,6 @@ impl SignCommand for CommandAddHandler { let (sign_s, sign_r) = bounded::(self.max_concurrency); let (assemble_s, assemble_r) = bounded::(self.max_concurrency); let (collect_s, collect_r) = bounded::(self.max_concurrency); - info!("starting to sign {} files", files.len()); let lb_config = self.config.read()?.get_table("server")?; let errored = runtime.block_on(async { let channel_provider = ChannelFactory::new(&lb_config).await; @@ -222,6 +221,29 @@ impl SignCommand for CommandAddHandler { if let Err(err) = channel { return Some(err) } + //fetch datakey attributes + info!("starting to fetch datakey [{}] {} attribute",self.key_type, self.key_name); + let mut key_fetcher = KeyFetcher::new(channel.clone().unwrap(), self.token.clone()); + let key_attributes; + match key_fetcher.get_key_attributes(&self.key_name, &self.key_type.to_string()).await { + Ok(attributes) => { + key_attributes = attributes + } + Err(err) => { + return Some(err) + } + } + //collect file candidates + let files; + match self.collect_file_candidates() { + Ok(f) => { + files = f + } + Err(err) => { + return Some(err) + } + } + info!("starting to sign {} files", files.len()); let mut signer = RemoteSigner::new(channel.unwrap(), self.buffer_size, self.token.clone()); //split file let send_handlers = files.into_iter().map(|file|{ @@ -238,12 +260,13 @@ impl SignCommand for CommandAddHandler { }).collect::>(); //do file split let task_sign_s = sign_s.clone(); + let s_key_attributes = key_attributes.clone(); let split_handler = tokio::spawn(async move { loop { let sign_identity = split_r.recv().await; match sign_identity { Ok(identity) => { - let mut splitter = Splitter::new(); + let mut splitter = Splitter::new(s_key_attributes.clone()); splitter.handle(identity, task_sign_s.clone()).await; }, Err(_) => { @@ -277,7 +300,7 @@ impl SignCommand for CommandAddHandler { let sign_identity = assemble_r.recv().await; match sign_identity { Ok(identity) => { - let mut assembler = Assembler::new( working_dir.clone()); + let mut assembler = Assembler::new( working_dir.clone(), key_attributes.clone()); assembler.handle(identity, task_collect_s.clone()).await; }, Err(_) => { diff --git a/src/client/file_handler/efi.rs b/src/client/file_handler/efi.rs index 36475f3..167cbdc 100644 --- a/src/client/file_handler/efi.rs +++ b/src/client/file_handler/efi.rs @@ -50,6 +50,7 @@ impl FileHandler for EfiFileHandler { &self, path: &PathBuf, _sign_options: &mut HashMap, + _key_attributes: &HashMap ) -> Result>> { let buf = read(path)?; let pe = EfiImage::parse(&buf)?; @@ -67,6 +68,7 @@ impl FileHandler for EfiFileHandler { data: Vec>, temp_dir: &PathBuf, _sign_options: &HashMap, + _key_attributes: &HashMap ) -> Result<(String, String)> { let temp_file = temp_dir.join(Uuid::new_v4().to_string()); let buf = read(path)?; @@ -162,7 +164,7 @@ mod test { let temp_dir = env::temp_dir(); let result = handler - .assemble_data(&path, vec![signature_buf], &temp_dir, &options) + .assemble_data(&path, vec![signature_buf], &temp_dir, &options, &HashMap::new()) .await; assert!(result.is_ok()); let (temp_file, file_name) = result.expect("efi sign should work"); diff --git a/src/client/file_handler/generic.rs b/src/client/file_handler/generic.rs index d44bf32..bafb962 100644 --- a/src/client/file_handler/generic.rs +++ b/src/client/file_handler/generic.rs @@ -66,6 +66,7 @@ impl FileHandler for GenericFileHandler { data: Vec>, temp_dir: &PathBuf, _sign_options: &HashMap, + _key_attributes: &HashMap ) -> Result<(String, String)> { let temp_file = temp_dir.join(Uuid::new_v4().to_string()); //convert bytes into string @@ -120,7 +121,7 @@ mod test { 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; + let result = handler.assemble_data(&path, data, &temp_dir, &options, &HashMap::new()).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); diff --git a/src/client/file_handler/kernel_module.rs b/src/client/file_handler/kernel_module.rs index 3c29b27..21a2092 100644 --- a/src/client/file_handler/kernel_module.rs +++ b/src/client/file_handler/kernel_module.rs @@ -177,6 +177,7 @@ impl FileHandler for KernelModuleFileHandler { &self, path: &PathBuf, sign_options: &mut HashMap, + _key_attributes: &HashMap ) -> Result>> { Ok(vec![self.get_raw_content(path, sign_options)?]) } @@ -188,6 +189,7 @@ impl FileHandler for KernelModuleFileHandler { data: Vec>, temp_dir: &PathBuf, sign_options: &HashMap, + _key_attributes: &HashMap ) -> Result<(String, String)> { let temp_file = temp_dir.join(Uuid::new_v4().to_string()); //convert bytes into string @@ -332,7 +334,7 @@ mod test { 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; + let result = handler.assemble_data(&path, data, &temp_dir, &options, &HashMap::new()).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); @@ -350,7 +352,7 @@ mod test { 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; + let result = handler.assemble_data(&path, data, &temp_dir, &options, &HashMap::new()).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); @@ -365,7 +367,7 @@ mod test { 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"); + let raw_content = file_handler.split_data(&path, &mut sign_options, &HashMap::new()).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/file_handler/rpm.rs b/src/client/file_handler/rpm.rs index b6a4bac..22473c9 100644 --- a/src/client/file_handler/rpm.rs +++ b/src/client/file_handler/rpm.rs @@ -20,11 +20,14 @@ use super::traits::FileHandler; use async_trait::async_trait; use crate::util::error::Result; use std::fs::File; +use std::str::FromStr; +#[allow(unused_imports)] use std::io::{BufReader, Read}; use rpm::{Header, IndexSignatureTag, Package, Digests}; use uuid::Uuid; -use crate::util::options; +use crate::domain::datakey::plugins::openpgp::OpenPGPKeyType; +use crate::util::{options}; use crate::util::sign::KeyType; use crate::util::error::Error; @@ -40,6 +43,24 @@ impl RpmFileHandler { } } + //defaults to RSA + fn get_key_type(&self, key_attributes: &HashMap) -> OpenPGPKeyType { + match key_attributes.get(options::KEY_TYPE) { + Some(value) => { + match OpenPGPKeyType::from_str(value) { + Ok(key_type) => { + key_type + } + Err(_) => { + OpenPGPKeyType::RSA + } + } + } + None => { + OpenPGPKeyType::RSA + } + } + } } //todo: figure our why is much slower when async read & write with tokio is enabled. @@ -60,20 +81,29 @@ impl FileHandler for RpmFileHandler { Ok(()) } - //rpm has two sections need to be signed + //rpm has two sections need to be signed for rsa //1. header //2. header and content - async fn split_data(&self, path: &PathBuf, _sign_options: &mut HashMap) -> Result>> { + // while there is only section to be signed for dsa + //1. header + async fn split_data( + &self, + path: &PathBuf, + _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)?; - 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::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]) + } else { + Ok(vec![header_bytes]) + } } async fn assemble_data( &self, @@ -81,6 +111,7 @@ impl FileHandler for RpmFileHandler { data: Vec>, temp_dir: &PathBuf, _sign_options: &HashMap, + key_attributes: &HashMap ) -> Result<(String, String)> { let temp_rpm = temp_dir.join(Uuid::new_v4().to_string()); let file = File::open(path)?; @@ -93,15 +124,28 @@ impl FileHandler for RpmFileHandler { header_and_content_digest, } = Package::create_sig_header_digests(header_bytes.as_slice(), &package.content.as_slice())?; - //Only RSA Signature is supported currently. - let builder = 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(), - ); + 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(), + ) + } + OpenPGPKeyType::EDDSA => { + Header::::builder().add_digest( + &header_digest_sha1, + &header_digest_sha256, + &header_and_content_digest, + ).add_eddsa_signature( + data[0].as_slice(), + ) + } + }; package.metadata.signature = builder.build(header_bytes.as_slice().len() + package.content.len()); //save data into temp file let mut output = File::create(temp_rpm.clone())?; @@ -172,7 +216,7 @@ mod test { 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).await.expect("get raw content 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(), 4325); assert_eq!(raw_content[1].len(), 67757); @@ -183,7 +227,7 @@ mod test { let mut sign_options = HashMap::new(); let file_handler = RpmFileHandler::new(); let path = generate_invalid_rpm().expect("generate invalid rpm failed"); - let _raw_content = file_handler.split_data(&path, &mut sign_options).await.expect_err("split invalid rpm file would failed"); + let _raw_content = file_handler.split_data(&path, &mut sign_options, &HashMap::new()).await.expect_err("split invalid rpm file would failed"); } #[tokio::test] @@ -192,7 +236,7 @@ mod test { let file_handler = RpmFileHandler::new(); let path = generate_signed_rpm().expect("generate signed rpm failed"); let fake_signature = vec![vec![1,2,3,4], vec![1,2,3,4]]; - let _raw_content = file_handler.assemble_data(&path, fake_signature, &env::temp_dir(), &mut sign_options).await.expect("assemble data failed"); + let _raw_content = file_handler.assemble_data(&path, fake_signature, &env::temp_dir(), &mut sign_options, &HashMap::new()).await.expect("assemble data failed"); } } diff --git a/src/client/file_handler/traits.rs b/src/client/file_handler/traits.rs index 1767002..cb50995 100644 --- a/src/client/file_handler/traits.rs +++ b/src/client/file_handler/traits.rs @@ -27,6 +27,7 @@ pub trait FileHandler: Send + Sync { &self, path: &PathBuf, _sign_options: &mut HashMap, + _key_attributes: &HashMap ) -> Result>> { let content = fs::read(path).await?; Ok(vec![content]) @@ -38,5 +39,6 @@ pub trait FileHandler: Send + Sync { data: Vec>, temp_dir: &PathBuf, sign_options: &HashMap, + key_attributes: &HashMap ) -> Result<(String, String)>; } diff --git a/src/client/worker/assembler.rs b/src/client/worker/assembler.rs index d72a05b..d2c39d3 100644 --- a/src/client/worker/assembler.rs +++ b/src/client/worker/assembler.rs @@ -14,6 +14,7 @@ * */ +use std::collections::HashMap; use crate::client::{sign_identity::SignIdentity}; @@ -28,15 +29,17 @@ use crate::util::error::Error; use std::fs; pub struct Assembler { - temp_dir: PathBuf + temp_dir: PathBuf, + key_attributes: HashMap } impl Assembler { - pub fn new(temp_dir: String) -> Self { + pub fn new(temp_dir: String, key_attributes: HashMap) -> Self { Self { - temp_dir: PathBuf::from(temp_dir) + temp_dir: PathBuf::from(temp_dir), + key_attributes } } @@ -48,7 +51,7 @@ impl SignHandler for Assembler { async fn process(&mut self, handler: Box, item: SignIdentity) -> SignIdentity { let signatures: Vec> = (*item.signature).borrow().clone(); let sign_options = item.sign_options.borrow().clone(); - match handler.assemble_data(&item.file_path, signatures, &self.temp_dir, &sign_options).await { + match handler.assemble_data(&item.file_path, signatures, &self.temp_dir, &sign_options, &self.key_attributes).await { Ok(content) => { debug!("successfully assemble file {}", item.file_path.as_path().display()); let temp_file = Path::new(&content.0); diff --git a/src/client/worker/key_fetcher.rs b/src/client/worker/key_fetcher.rs new file mode 100644 index 0000000..31e9376 --- /dev/null +++ b/src/client/worker/key_fetcher.rs @@ -0,0 +1,63 @@ +/* + * + * * // Copyright (c) 2023 Huawei Technologies Co.,Ltd. All rights reserved. + * * // + * * // signatrust is licensed under Mulan PSL v2. + * * // You can use this software according to the terms and conditions of the Mulan + * * // PSL v2. + * * // You may obtain a copy of Mulan PSL v2 at: + * * // http://license.coscl.org.cn/MulanPSL2 + * * // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY + * * // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO + * * // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * * // See the Mulan PSL v2 for more details. + * + */ + +use std::collections::HashMap; +use crate::util::error::{Result, Error}; + +pub mod signatrust { + tonic::include_proto!("signatrust"); +} + +use tonic::transport::Channel; +use self::signatrust::{ + signatrust_client::SignatrustClient, GetKeyInfoRequest +}; + +pub struct KeyFetcher { + client: SignatrustClient, + token: Option, +} + +impl KeyFetcher { + + pub fn new(channel: Channel, token: Option) -> Self { + Self { + client: SignatrustClient::new(channel), + token + } + } + + pub async fn get_key_attributes(&mut self, key_name: &str, key_type: &str) -> Result> { + let key = GetKeyInfoRequest{ + key_type: key_type.to_string(), + key_id: key_name.to_string(), + token: self.token.clone(), + }; + match self.client.get_key_info(key).await { + Ok(result) => { + let data = result.into_inner(); + if data.error.is_empty() { + Ok(data.attributes) + } else { + Err(Error::RemoteSignError(format!("{:?}", data.error))) + } + } + Err(err) => { + Err(Error::RemoteSignError(format!("{:?}", err))) + } + } + } +} diff --git a/src/client/worker/mod.rs b/src/client/worker/mod.rs index ca02893..437e916 100644 --- a/src/client/worker/mod.rs +++ b/src/client/worker/mod.rs @@ -2,3 +2,4 @@ pub mod assembler; pub mod splitter; pub mod signer; pub mod traits; +pub mod key_fetcher; diff --git a/src/client/worker/splitter.rs b/src/client/worker/splitter.rs index bbec269..0f74f92 100644 --- a/src/client/worker/splitter.rs +++ b/src/client/worker/splitter.rs @@ -20,16 +20,19 @@ use crate::client::{sign_identity::SignIdentity}; use crate::client::worker::traits::SignHandler; use crate::client::file_handler::traits::FileHandler; use async_trait::async_trait; +use std::collections::HashMap; use crate::util::error; pub struct Splitter { + key_attributes: HashMap } impl Splitter { - pub fn new() -> Self { + pub fn new(key_attributes: HashMap) -> Self { Self { + key_attributes } } } @@ -38,7 +41,7 @@ impl Splitter { impl SignHandler for Splitter { async fn process(&mut self, handler: Box, item: SignIdentity) -> SignIdentity { let mut sign_options = item.sign_options.borrow().clone(); - match handler.split_data(&item.file_path, &mut sign_options).await { + match handler.split_data(&item.file_path, &mut sign_options, &self.key_attributes).await { Ok(content) => { *item.raw_content.borrow_mut() = content; *item.sign_options.borrow_mut() = sign_options; diff --git a/src/control_admin_entrypoint.rs b/src/control_admin_entrypoint.rs index 6f1699b..43264dc 100644 --- a/src/control_admin_entrypoint.rs +++ b/src/control_admin_entrypoint.rs @@ -84,8 +84,8 @@ pub struct CommandGenerateKeys { #[arg(help = "specify the type of internal key used for keys generation, ie, rsa")] param_key_type: String, #[arg(long)] - #[arg(help = "specify the type of internal key used for keys generation, ie, 1024")] - param_key_size: String, + #[arg(help = "specify the type of internal key used for keys generation, ie, 2048")] + param_key_size: Option, #[arg(long)] #[arg(help = "specify the type of digest algorithm used for signing, ie, sha1")] digest_algorithm: String, @@ -132,7 +132,9 @@ pub struct CommandGenerateKeys { fn generate_keys_parameters(command: &CommandGenerateKeys) -> HashMap { let mut attributes = HashMap::new(); attributes.insert("key_type".to_string(), command.param_key_type.clone()); - attributes.insert("key_length".to_string(), command.param_key_size.clone()); + if command.param_key_size.is_some() { + attributes.insert("key_length".to_string(), command.param_key_size.clone().unwrap()); + } attributes.insert("digest_algorithm".to_string(), command.digest_algorithm.clone()); let key_type = EntityKeyTpe::from_str(&command.key_type).unwrap(); if key_type == EntityKeyTpe::OpenPGP { diff --git a/src/domain/datakey/mod.rs b/src/domain/datakey/mod.rs index 6111620..51eb69a 100644 --- a/src/domain/datakey/mod.rs +++ b/src/domain/datakey/mod.rs @@ -1,3 +1,4 @@ pub mod entity; pub mod repository; pub mod traits; +pub mod plugins; diff --git a/src/domain/datakey/plugins/mod.rs b/src/domain/datakey/plugins/mod.rs new file mode 100644 index 0000000..3cdf0b0 --- /dev/null +++ b/src/domain/datakey/plugins/mod.rs @@ -0,0 +1,16 @@ +/* + * // Copyright (c) 2023 Huawei Technologies Co.,Ltd. All rights reserved. + * // + * // signatrust is licensed under Mulan PSL v2. + * // You can use this software according to the terms and conditions of the Mulan + * // PSL v2. + * // You may obtain a copy of Mulan PSL v2 at: + * // http://license.coscl.org.cn/MulanPSL2 + * // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY + * // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO + * // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * // See the Mulan PSL v2 for more details. + */ +pub mod openpgp; +pub mod x509; + diff --git a/src/domain/datakey/plugins/openpgp.rs b/src/domain/datakey/plugins/openpgp.rs new file mode 100644 index 0000000..795bd06 --- /dev/null +++ b/src/domain/datakey/plugins/openpgp.rs @@ -0,0 +1,142 @@ +/* + * // Copyright (c) 2023 Huawei Technologies Co.,Ltd. All rights reserved. + * // + * // signatrust is licensed under Mulan PSL v2. + * // You can use this software according to the terms and conditions of the Mulan + * // PSL v2. + * // You may obtain a copy of Mulan PSL v2 at: + * // http://license.coscl.org.cn/MulanPSL2 + * // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY + * // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO + * // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * // See the Mulan PSL v2 for more details. + */ + +use std::str::FromStr; +use crate::util::error::{Error, Result}; +use std::fmt::{Display, Formatter}; +use std::fmt; +use pgp::composed::{KeyType}; +use pgp::crypto::{hash::HashAlgorithm}; +use enum_iterator::{Sequence}; +use serde::Deserialize; + +pub const PGP_VALID_KEY_SIZE: [&str; 3] = ["2048", "3072", "4096"]; + +#[derive(Debug, Clone, PartialEq, Sequence, Deserialize)] +pub enum OpenPGPKeyType { + #[serde(rename = "rsa")] + RSA, + #[serde(rename = "eddsa")] + EDDSA, +} + +impl FromStr for OpenPGPKeyType { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "rsa" => Ok(OpenPGPKeyType::RSA), + "eddsa" => Ok(OpenPGPKeyType::EDDSA), + _ => Err(Error::UnsupportedTypeError(format!("unsupported openpgp key state {}", s))), + } + } +} + +impl Display for OpenPGPKeyType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + OpenPGPKeyType::RSA => write!(f, "rsa"), + OpenPGPKeyType::EDDSA => write!(f, "eddsa"), + } + } +} + +impl OpenPGPKeyType { + //key length defaults to 2048 + pub fn get_real_key_type(&self, key_length: Option) -> KeyType { + match self { + OpenPGPKeyType::RSA => { + if key_length.is_none() { + KeyType::Rsa(2048) + } else { + KeyType::Rsa(key_length.unwrap().parse().unwrap()) + } + }, + OpenPGPKeyType::EDDSA => KeyType::EdDSA + } + } +} + +#[derive(Debug, Clone, PartialEq, Sequence, Deserialize)] +pub enum OpenPGPDigestAlgorithm { + #[serde(rename = "none")] + None, + #[serde(rename = "md5")] + MD5, + #[serde(rename = "sha1")] + SHA1, + #[serde(rename = "sha2_224")] + SHA2_224, + #[serde(rename = "sha2_256")] + SHA2_256, + #[serde(rename = "sha2_384")] + SHA2_384, + #[serde(rename = "sha2_512")] + SHA2_512, + #[serde(rename = "sha3_256")] + SHA3_256, + #[serde(rename = "sha3_512")] + SHA3_512, +} + +impl Display for OpenPGPDigestAlgorithm { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + OpenPGPDigestAlgorithm::None => write!(f, "none"), + OpenPGPDigestAlgorithm::MD5 => write!(f, "md5"), + OpenPGPDigestAlgorithm::SHA1 => write!(f, "sha1"), + OpenPGPDigestAlgorithm::SHA2_224 => write!(f, "sha2_224"), + OpenPGPDigestAlgorithm::SHA2_256 => write!(f, "sha2_256"), + OpenPGPDigestAlgorithm::SHA2_384 => write!(f, "sha2_384"), + OpenPGPDigestAlgorithm::SHA2_512 => write!(f, "sha2_512"), + OpenPGPDigestAlgorithm::SHA3_256 => write!(f, "sha3_256"), + OpenPGPDigestAlgorithm::SHA3_512 => write!(f, "sha3_512"), + } + } +} + +impl FromStr for OpenPGPDigestAlgorithm { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "none" => Ok(OpenPGPDigestAlgorithm::None), + "md5" => Ok(OpenPGPDigestAlgorithm::MD5), + "sha1" => Ok(OpenPGPDigestAlgorithm::SHA1), + "sha2_224" => Ok(OpenPGPDigestAlgorithm::SHA2_224), + "sha2_256" => Ok(OpenPGPDigestAlgorithm::SHA2_256), + "sha2_384" => Ok(OpenPGPDigestAlgorithm::SHA2_384), + "sha2_512" => Ok(OpenPGPDigestAlgorithm::SHA2_512), + "sha3_256" => Ok(OpenPGPDigestAlgorithm::SHA3_256), + "sha3_512" => Ok(OpenPGPDigestAlgorithm::SHA3_512), + _ => Err(Error::UnsupportedTypeError(format!("unsupported openpgp digest algorithm {}", s))), + } + } +} + +impl OpenPGPDigestAlgorithm { + pub fn get_real_algorithm(&self) -> HashAlgorithm { + match self { + OpenPGPDigestAlgorithm::None => HashAlgorithm::None, + OpenPGPDigestAlgorithm::MD5 => HashAlgorithm::MD5, + OpenPGPDigestAlgorithm::SHA1=> HashAlgorithm::SHA1, + OpenPGPDigestAlgorithm::SHA2_224 => HashAlgorithm::SHA2_224, + OpenPGPDigestAlgorithm::SHA2_256 => HashAlgorithm::SHA2_256, + OpenPGPDigestAlgorithm::SHA2_384 => HashAlgorithm::SHA2_384, + OpenPGPDigestAlgorithm::SHA2_512 => HashAlgorithm::SHA2_512, + OpenPGPDigestAlgorithm::SHA3_256 => HashAlgorithm::SHA3_256, + OpenPGPDigestAlgorithm::SHA3_512 => HashAlgorithm::SHA3_512, + } + } +} diff --git a/src/domain/datakey/plugins/x509.rs b/src/domain/datakey/plugins/x509.rs new file mode 100644 index 0000000..befa8f8 --- /dev/null +++ b/src/domain/datakey/plugins/x509.rs @@ -0,0 +1,121 @@ +/* + * // Copyright (c) 2023 Huawei Technologies Co.,Ltd. All rights reserved. + * // + * // signatrust is licensed under Mulan PSL v2. + * // You can use this software according to the terms and conditions of the Mulan + * // PSL v2. + * // You may obtain a copy of Mulan PSL v2 at: + * // http://license.coscl.org.cn/MulanPSL2 + * // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY + * // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO + * // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * // See the Mulan PSL v2 for more details. + */ +use std::str::FromStr; +use crate::util::error::{Error, Result}; +use std::fmt::{Display, Formatter}; +use std::fmt; +use openssl::pkey::{PKey, Private}; +use openssl::rsa::Rsa; +use openssl::dsa::Dsa; +use openssl::hash::MessageDigest; +use enum_iterator::{Sequence}; +use serde::Deserialize; + +pub const X509_VALID_KEY_SIZE: [&str; 3] = ["2048", "3072", "4096"]; + +#[derive(Debug, Clone, PartialEq, Sequence, Deserialize)] +pub enum X509KeyType { + #[serde(rename = "rsa")] + RSA, + #[serde(rename = "dsa")] + DSA, +} + +impl FromStr for X509KeyType { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "rsa" => Ok(X509KeyType::RSA), + "dsa" => Ok(X509KeyType::DSA), + _ => Err(Error::UnsupportedTypeError(format!("unsupported x509 key type {}", s))), + } + } +} + +impl Display for X509KeyType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + X509KeyType::RSA => write!(f, "rsa"), + X509KeyType::DSA => write!(f, "dsa"), + } + } +} + +impl X509KeyType { + pub fn get_real_key_type(&self, key_length: u32) -> Result> { + match self { + X509KeyType::RSA => Ok(PKey::from_rsa(Rsa::generate(key_length)?)?), + X509KeyType::DSA => Ok(PKey::from_dsa(Dsa::generate(key_length)?)?), + } + } +} + +#[derive(Debug, Clone, PartialEq, Sequence, Deserialize)] +pub enum X509DigestAlgorithm { + #[serde(rename = "md5")] + MD5, + #[serde(rename = "sha1")] + SHA1, + #[serde(rename = "sha2_224")] + SHA2_224, + #[serde(rename = "sha2_256")] + SHA2_256, + #[serde(rename = "sha2_384")] + SHA2_384, + #[serde(rename = "sha2_512")] + SHA2_512, +} + +impl Display for X509DigestAlgorithm { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + X509DigestAlgorithm::MD5 => write!(f, "md5"), + X509DigestAlgorithm::SHA1 => write!(f, "sha1"), + X509DigestAlgorithm::SHA2_224 => write!(f, "sha2_224"), + X509DigestAlgorithm::SHA2_256 => write!(f, "sha2_256"), + X509DigestAlgorithm::SHA2_384 => write!(f, "sha2_384"), + X509DigestAlgorithm::SHA2_512 => write!(f, "sha2_512"), + } + } +} + +impl FromStr for X509DigestAlgorithm { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "md5" => Ok(X509DigestAlgorithm::MD5), + "sha1" => Ok(X509DigestAlgorithm::SHA1), + "sha2_224" => Ok(X509DigestAlgorithm::SHA2_224), + "sha2_256" => Ok(X509DigestAlgorithm::SHA2_256), + "sha2_384" => Ok(X509DigestAlgorithm::SHA2_384), + "sha2_512" => Ok(X509DigestAlgorithm::SHA2_512), + _ => Err(Error::UnsupportedTypeError(format!("unsupported x509 digest algorithm {}", s))), + } + } +} + +impl X509DigestAlgorithm { + pub fn get_real_algorithm(&self) -> MessageDigest { + match self { + X509DigestAlgorithm::MD5 => MessageDigest::md5(), + X509DigestAlgorithm::SHA1 => MessageDigest::sha1(), + X509DigestAlgorithm::SHA2_224 => MessageDigest::sha224(), + X509DigestAlgorithm::SHA2_256 => MessageDigest::sha256(), + X509DigestAlgorithm::SHA2_384 => MessageDigest::sha384(), + X509DigestAlgorithm::SHA2_512 => MessageDigest::sha512() + } + } +} diff --git a/src/infra/sign_plugin/openpgp.rs b/src/infra/sign_plugin/openpgp.rs index dc10145..5d35128 100644 --- a/src/infra/sign_plugin/openpgp.rs +++ b/src/infra/sign_plugin/openpgp.rs @@ -20,7 +20,7 @@ use crate::util::error::{Error, Result}; use crate::util::options; use chrono::{DateTime, Utc}; use pgp::composed::signed_key::{SignedSecretKey, SignedPublicKey}; -use pgp::composed::{key::SecretKeyParamsBuilder, KeyType}; +use pgp::composed::{key::SecretKeyParamsBuilder}; use pgp::crypto::{hash::HashAlgorithm, sym::SymmetricKeyAlgorithm}; use pgp::packet::SignatureConfig; use pgp::packet::*; @@ -33,25 +33,23 @@ use smallvec::*; use std::collections::HashMap; use std::io::{Cursor}; use std::str::from_utf8; +use std::str::FromStr; use validator::{Validate, ValidationError}; use pgp::composed::StandaloneSignature; use crate::domain::datakey::entity::{DataKey, DataKeyContent, SecDataKey, KeyType as EntityKeyType, RevokedKey}; +use crate::domain::datakey::plugins::openpgp::{OpenPGPDigestAlgorithm, OpenPGPKeyType, PGP_VALID_KEY_SIZE}; use crate::util::key::encode_u8_to_hex_string; use super::util::{validate_utc_time_not_expire, validate_utc_time, attributes_validate}; +#[allow(unused_imports)] +use enum_iterator::{all}; -// 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"]; #[derive(Debug, Validate, Deserialize)] pub struct PgpKeyImportParameter { - #[validate(custom( function = "validate_key_type", message="invalid openpgp attribute 'key_type'"))] - key_type: String, + key_type: OpenPGPKeyType, #[validate(custom(function = "validate_key_size", message="invalid openpgp attribute 'key_length'"))] - key_length: String, - #[validate(custom(function= "validate_digest_algorithm_type", message="invalid digest algorithm"))] - digest_algorithm: String, + key_length: Option, + digest_algorithm: OpenPGPDigestAlgorithm, #[validate(custom(function = "validate_utc_time", message="invalid openpgp attribute 'create_at'"))] create_at: String, #[validate(custom(function= "validate_utc_time_not_expire", message="invalid openpgp attribute 'expire_at'"))] @@ -67,12 +65,10 @@ pub struct PgpKeyGenerationParameter { // email format validation is disabled due to copr has the case of group prefixed email: `@group#project@copr.com` #[validate(length(min = 2, max = 100, message="invalid openpgp attribute 'email'"))] email: String, - #[validate(custom( function = "validate_key_type", message="invalid openpgp attribute 'key_type'"))] - key_type: String, + key_type: OpenPGPKeyType, #[validate(custom(function = "validate_key_size", message="invalid openpgp attribute 'key_length'"))] - key_length: String, - #[validate(custom(function= "validate_digest_algorithm_type", message="invalid digest algorithm"))] - digest_algorithm: String, + key_length: Option, + digest_algorithm: OpenPGPDigestAlgorithm, #[validate(custom(function = "validate_utc_time", message="invalid openpgp attribute 'create_at'"))] create_at: String, #[validate(custom(function= "validate_utc_time_not_expire", message="invalid openpgp attribute 'expire_at'"))] @@ -81,54 +77,13 @@ pub struct PgpKeyGenerationParameter { } impl PgpKeyGenerationParameter { - pub fn get_key(&self) -> Result { - return match self.key_type.as_str() { - "rsa" => Ok(KeyType::Rsa(self.key_length.parse::()?)), - "eddsa" => Ok(KeyType::EdDSA), - _ => Err(Error::ParameterError( - "invalid key type for openpgp".to_string(), - )), - }; - } - pub fn get_user_id(&self) -> String { format!("{} <{}>", self.name, self.email) } } -pub fn get_digest_algorithm(hash_digest: &str) -> Result { - match hash_digest { - "none" => Ok(HashAlgorithm::None), - "md5" => Ok(HashAlgorithm::MD5), - "sha1" => Ok(HashAlgorithm::SHA1), - "sha2_256" => Ok(HashAlgorithm::SHA2_256), - "sha2_384" => Ok(HashAlgorithm::SHA2_384), - "sha2_512" => Ok(HashAlgorithm::SHA2_512), - "sha2_224" => Ok(HashAlgorithm::SHA2_224), - "sha3_256" => Ok(HashAlgorithm::SHA3_256), - "sha3_512" => Ok(HashAlgorithm::SHA3_512), - _ => Err(Error::ParameterError( - "invalid digest algorithm for openpgp".to_string(), - )), - } -} - -fn validate_key_type(key_type: &str) -> std::result::Result<(), ValidationError> { - if !VALID_KEY_TYPE.contains(&key_type) { - return Err(ValidationError::new("invalid key type, possible values are rsa/ecdh/eddsa")); - } - Ok(()) -} - -fn validate_digest_algorithm_type(key_type: &str) -> std::result::Result<(), ValidationError> { - if !VALID_DIGEST_ALGORITHM.contains(&key_type) { - return Err(ValidationError::new("invalid hash algorithm, possible values are none/md5/sha1/sha1/sha2_256/sha2_384/sha2_512/sha2_224/sha3_256/sha3_512")); - } - Ok(()) -} - fn validate_key_size(key_size: &str) -> std::result::Result<(), ValidationError> { - if !VALID_KEY_SIZE.contains(&key_size) { + if !PGP_VALID_KEY_SIZE.contains(&key_size) { return Err(ValidationError::new("invalid key size, possible values are 2048/3072/4096")); } Ok(()) @@ -181,10 +136,6 @@ impl SignPlugins for OpenPGPPlugin { fn validate_and_update(key: &mut DataKey) -> Result<()> where Self: Sized { let _ = attributes_validate::(&key.attributes)?; - //validate the digest - if let Some(digest_str) = key.attributes.get("digest_algorithm") { - let _ = get_digest_algorithm(digest_str)?; - } //validate keys let private = from_utf8(&key.private_key).map_err(|e| Error::KeyParseError(e.to_string()))?; let (secret_key, _) = @@ -219,12 +170,12 @@ impl SignPlugins for OpenPGPPlugin { let expire :DateTime = parameter.expire_at.parse()?; let duration: core::time::Duration = (expire - Utc::now()).to_std()?; key_params - .key_type(parameter.get_key()?) + .key_type(parameter.key_type.get_real_key_type(parameter.key_length.clone())) .can_create_certificates(false) .can_sign(true) .primary_user_id(parameter.get_user_id()) .preferred_symmetric_algorithms(smallvec![SymmetricKeyAlgorithm::AES256,]) - .preferred_hash_algorithms(smallvec![get_digest_algorithm(parameter.digest_algorithm.as_str())?]) + .preferred_hash_algorithms(smallvec![parameter.digest_algorithm.get_real_algorithm()]) .preferred_compression_algorithms(smallvec![CompressionAlgorithm::ZLIB,]) .created_at(create_at) .expiration(Some(duration)); @@ -253,7 +204,7 @@ impl SignPlugins for OpenPGPPlugin { fn sign(&self, content: Vec, options: HashMap) -> Result> { let mut digest = HashAlgorithm::SHA2_256; if let Some(digest_str) = options.get("digest_algorithm") { - digest = get_digest_algorithm(digest_str)? + digest = OpenPGPDigestAlgorithm::from_str(digest_str)?.get_real_algorithm(); } let passwd_fn = || return match options.get("passphrase") { None => { @@ -372,7 +323,7 @@ mod test { attributes_validate::(¶meter).expect_err("invalid key type"); parameter.insert("key_type".to_string(), "".to_string()); attributes_validate::(¶meter).expect_err("invalid empty key type"); - for key_type in VALID_KEY_TYPE { + for key_type in all::().collect::>() { parameter.insert("key_type".to_string(), key_type.to_string()); attributes_validate::(¶meter).expect("valid key type"); } @@ -385,7 +336,7 @@ mod test { attributes_validate::(¶meter).expect_err("invalid key length"); parameter.insert("key_length".to_string(), "".to_string()); attributes_validate::(¶meter).expect_err("invalid empty key length"); - for key_length in VALID_KEY_SIZE { + for key_length in PGP_VALID_KEY_SIZE { parameter.insert("key_length".to_string(), key_length.to_string()); attributes_validate::(¶meter).expect("valid key length"); } @@ -398,7 +349,7 @@ mod test { attributes_validate::(¶meter).expect_err("invalid digest algorithm"); parameter.insert("digest_algorithm".to_string(),"".to_string()); attributes_validate::(¶meter).expect_err("invalid empty digest algorithm"); - for key_length in VALID_DIGEST_ALGORITHM { + for key_length in all::().collect::>() { parameter.insert("digest_algorithm".to_string(),key_length.to_string()); attributes_validate::(¶meter).expect("valid digest algorithm"); } @@ -443,15 +394,16 @@ mod test { async fn test_generate_key_with_possible_digest_hash() { let mut parameter = get_default_parameter(); let dummy_engine = get_encryption_engine(); + let algorithms = all::().collect::>(); //choose 3 random digest algorithm for _ in [1,2,3] { - let num = rand::thread_rng().gen_range(0..VALID_DIGEST_ALGORITHM.len()); - parameter.insert("digest_algorithm".to_string(), VALID_DIGEST_ALGORITHM[num].to_string()); + let num = rand::thread_rng().gen_range(0..algorithms.len()); + parameter.insert("digest_algorithm".to_string(), algorithms[num].to_string()); let sec_datakey = SecDataKey::load( &get_default_datakey( None, Some(parameter.clone())), &dummy_engine).await.expect("load sec datakey successfully"); let plugin = OpenPGPPlugin::new(sec_datakey).expect("create openpgp plugin successfully"); - plugin.generate_keys(&KeyType::OpenPGP, &HashMap::new()).expect(format!("generate key with digest {} successfully", VALID_DIGEST_ALGORITHM[num]).as_str()); + plugin.generate_keys(&KeyType::OpenPGP, &HashMap::new()).expect(format!("generate key with digest {} successfully", algorithms[num]).as_str()); } } @@ -460,7 +412,7 @@ mod test { async fn test_generate_key_with_possible_length() { let mut parameter = get_default_parameter(); let dummy_engine = get_encryption_engine(); - for key_size in VALID_KEY_SIZE{ + for key_size in PGP_VALID_KEY_SIZE { parameter.insert("key_size".to_string(), key_size.to_string()); let sec_datakey = SecDataKey::load( &get_default_datakey( @@ -474,7 +426,7 @@ mod test { async fn test_generate_key_with_possible_key_type() { let mut parameter = get_default_parameter(); let dummy_engine = get_encryption_engine(); - for key_type in VALID_KEY_TYPE{ + for key_type in all::().collect::>(){ parameter.insert("key_type".to_string(), key_type.to_string()); let sec_datakey = SecDataKey::load( &get_default_datakey( diff --git a/src/infra/sign_plugin/x509.rs b/src/infra/sign_plugin/x509.rs index 8fb897b..2cd290e 100644 --- a/src/infra/sign_plugin/x509.rs +++ b/src/infra/sign_plugin/x509.rs @@ -22,12 +22,10 @@ use chrono::{DateTime, Utc}; use openssl::asn1::{Asn1Integer, Asn1Time}; use openssl::bn::{BigNum, MsbOption}; use openssl::cms::{CmsContentInfo, CMSOptions}; -use openssl::dsa::Dsa; use openssl::hash::MessageDigest; use openssl::nid::Nid; use openssl::pkcs7::{Pkcs7, Pkcs7Flags}; -use openssl::pkey::{PKey, Private}; -use openssl::rsa::Rsa; +use openssl::pkey::{PKey}; use openssl::stack::{Stack}; use openssl::x509; use openssl::x509::extension::{AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectKeyIdentifier}; @@ -41,14 +39,13 @@ use validator::{Validate, ValidationError}; use crate::util::options; use crate::util::sign::SignType; use crate::domain::datakey::entity::{DataKey, DataKeyContent, INFRA_CONFIG_DOMAIN_NAME, KeyType, RevokedKey, SecDataKey, SecParentDateKey}; +use crate::domain::datakey::plugins::x509::{X509KeyType, X509_VALID_KEY_SIZE, X509DigestAlgorithm}; use crate::util::error::{Error, Result}; use crate::domain::sign_plugin::SignPlugins; use crate::util::key::{decode_hex_string_to_u8, 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", "dsa"]; -const VALID_KEY_SIZE: [&str; 3] = ["2048", "3072", "4096"]; -const VALID_DIGEST_ALGORITHM: [&str; 6] = ["md5", "sha1", "sha2_256","sha2_384","sha2_512","sha2_224"]; +#[allow(unused_imports)] +use enum_iterator::{all}; #[derive(Debug, Validate, Deserialize)] pub struct X509KeyGenerationParameter { @@ -64,12 +61,10 @@ pub struct X509KeyGenerationParameter { province_name: String, #[validate(length(min = 2, max = 2, message="invalid x509 subject 'CountryName'"))] country_name: String, - #[validate(custom(function = "validate_x509_key_type", message="invalid x509 attribute 'key_type'"))] - key_type: String, + key_type: X509KeyType, #[validate(custom(function = "validate_x509_key_size", message="invalid x509 attribute 'key_length'"))] key_length: String, - #[validate(custom(function= "validate_x509_digest_algorithm_type", message="invalid digest algorithm"))] - digest_algorithm: String, + digest_algorithm: X509DigestAlgorithm, #[validate(custom(function = "validate_utc_time", message="invalid x509 attribute 'create_at'"))] create_at: String, #[validate(custom(function= "validate_utc_time_not_expire", message="invalid x509 attribute 'expire_at'"))] @@ -78,11 +73,10 @@ pub struct X509KeyGenerationParameter { #[derive(Debug, Validate, Deserialize)] pub struct X509KeyImportParameter { - key_type: String, + key_type: X509KeyType, #[validate(custom(function = "validate_x509_key_size", message="invalid x509 attribute 'key_length'"))] key_length: String, - #[validate(custom(function= "validate_x509_digest_algorithm_type", message="invalid digest algorithm"))] - digest_algorithm: String, + digest_algorithm: X509DigestAlgorithm, #[validate(custom(function = "validate_utc_time", message="invalid x509 attribute 'create_at'"))] create_at: String, #[validate(custom(function= "validate_utc_time_not_expire", message="invalid x509 attribute 'expire_at'"))] @@ -92,30 +86,6 @@ pub struct X509KeyImportParameter { impl X509KeyGenerationParameter { - pub fn get_key(&self) -> Result> { - return match self.key_type.as_str() { - "rsa" => Ok(PKey::from_rsa(Rsa::generate(self.key_length.parse()?)?)?), - "dsa" => Ok(PKey::from_dsa(Dsa::generate(self.key_length.parse()?)?)?), - _ => Err(Error::ParameterError( - "invalid key type for x509".to_string(), - )), - }; - } - - pub fn get_digest_algorithm(&self) -> Result { - return match self.digest_algorithm.as_str() { - "md5" => Ok(MessageDigest::md5()), - "sha1" => Ok(MessageDigest::sha1()), - "sha2_256" => Ok(MessageDigest::sha224()), - "sha2_384" => Ok(MessageDigest::sha256()), - "sha2_512" => Ok(MessageDigest::sha384()), - "sha2_224" => Ok(MessageDigest::sha512()), - _ => Err(Error::ParameterError( - "invalid digest algorithm for x509".to_string(), - )), - }; - } - pub fn get_subject_name(&self) -> Result { let mut x509_name = x509::X509NameBuilder::new()?; x509_name.append_entry_by_text("CN", &self.common_name)?; @@ -128,15 +98,8 @@ impl X509KeyGenerationParameter { } } -fn validate_x509_key_type(key_type: &str) -> std::result::Result<(), ValidationError> { - if !VALID_KEY_TYPE.contains(&key_type) { - return Err(ValidationError::new("invalid key type, possible values are rsa/dsa")); - } - Ok(()) -} - fn validate_x509_key_size(key_size: &str) -> std::result::Result<(), ValidationError> { - if !VALID_KEY_SIZE.contains(&key_size) { + if !X509_VALID_KEY_SIZE.contains(&key_size) { return Err(ValidationError::new("invalid key size, possible values are 2048/3072/4096")); } Ok(()) @@ -148,13 +111,6 @@ fn days_in_duration(time: &str) -> Result { Ok((end - start).num_days()) } -fn validate_x509_digest_algorithm_type(key_type: &str) -> std::result::Result<(), ValidationError> { - if !VALID_DIGEST_ALGORITHM.contains(&key_type) { - return Err(ValidationError::new("invalid hash algorithm, possible values are none/md5/sha1/sha1/sha2_256/sha2_384/sha2_512/sha2_224")); - } - Ok(()) -} - pub struct X509Plugin { name: String, private_key: SecVec, @@ -191,7 +147,7 @@ impl X509Plugin { fn generate_x509ca_keys(&self, _infra_config: &HashMap) -> Result { let parameter = attributes_validate::(&self.attributes)?; //generate self signed certificate - let keys = parameter.get_key()?; + let keys = parameter.key_type.get_real_key_type(parameter.key_length.parse()?)?; let mut generator = x509::X509Builder::new()?; let serial_number = X509Plugin::generate_serial_number()?; generator.set_subject_name(parameter.get_subject_name()?.as_ref())?; @@ -209,7 +165,7 @@ impl X509Plugin { generator.append_extension(X509Extension::new_nid(None, None, Nid::NETSCAPE_COMMENT, "Signatrust Root CA")?)?; generator.append_extension(X509Extension::new_nid(None, None, Nid::NETSCAPE_CERT_TYPE, "objCA")?)?; - generator.sign(keys.as_ref(), parameter.get_digest_algorithm()?)?; + generator.sign(keys.as_ref(), parameter.digest_algorithm.get_real_algorithm())?; let cert = generator.build(); Ok(DataKeyContent{ private_key: keys.private_key_to_pem_pkcs8()?, @@ -240,7 +196,7 @@ impl X509Plugin { let ca_key = PKey::private_key_from_pem(self.parent_key.clone().unwrap().private_key.unsecure())?; let ca_cert = x509::X509::from_pem(self.parent_key.clone().unwrap().certificate.unsecure())?; //generate self signed certificate - let keys = parameter.get_key()?; + let keys = parameter.key_type.get_real_key_type(parameter.key_length.parse()?)?; let mut generator = x509::X509Builder::new()?; let serial_number = X509Plugin::generate_serial_number()?; generator.set_subject_name(parameter.get_subject_name()?.as_ref())?; @@ -258,7 +214,7 @@ impl X509Plugin { generator.append_extension(X509Extension::new_nid(None, None, Nid::CRL_DISTRIBUTION_POINTS, &self.generate_crl_endpoint(&self.parent_key.clone().unwrap().name, infra_config)?)?)?; generator.append_extension(X509Extension::new_nid(None, None, Nid::NETSCAPE_COMMENT, "Signatrust Intermediate CA")?)?; generator.append_extension(X509Extension::new_nid(None, None, Nid::NETSCAPE_CERT_TYPE, "objCA")?)?; - generator.sign(ca_key.as_ref(), parameter.get_digest_algorithm()?)?; + generator.sign(ca_key.as_ref(), parameter.digest_algorithm.get_real_algorithm())?; let cert = generator.build(); //use parent private key to sign the certificate Ok(DataKeyContent{ @@ -291,7 +247,7 @@ impl X509Plugin { let ca_key = PKey::private_key_from_pem(self.parent_key.clone().unwrap().private_key.unsecure())?; let ca_cert = x509::X509::from_pem(self.parent_key.clone().unwrap().certificate.unsecure())?; //generate self signed certificate - let keys = parameter.get_key()?; + let keys = parameter.key_type.get_real_key_type(parameter.key_length.parse()?)?; let mut generator = x509::X509Builder::new()?; let serial_number = X509Plugin::generate_serial_number()?; generator.set_subject_name(parameter.get_subject_name()?.as_ref())?; @@ -309,7 +265,7 @@ impl X509Plugin { generator.append_extension(X509Extension::new_nid(None, None, Nid::CRL_DISTRIBUTION_POINTS, &self.generate_crl_endpoint(&self.parent_key.clone().unwrap().name, infra_config)?)?)?; generator.append_extension(X509Extension::new_nid(None, None, Nid::NETSCAPE_COMMENT, "Signatrust Sign Certificate")?)?; generator.append_extension(X509Extension::new_nid(None, None, Nid::NETSCAPE_CERT_TYPE, "objsign")?)?; - generator.sign(ca_key.as_ref(), parameter.get_digest_algorithm()?)?; + generator.sign(ca_key.as_ref(), parameter.digest_algorithm.get_real_algorithm())?; let cert = generator.build(); //use parent private key to sign the certificate Ok(DataKeyContent{ @@ -442,7 +398,7 @@ impl SignPlugins for X509Plugin { unsafe {X509_CRL_add0_revoked(crl, revoked)}; } } - unsafe {X509_CRL_sign(crl, private_key.as_ptr(), parameter.get_digest_algorithm()?.as_ptr())}; + unsafe {X509_CRL_sign(crl, private_key.as_ptr(), parameter.digest_algorithm.get_real_algorithm().as_ptr())}; let content = unsafe {X509Crl::from_ptr(crl)}; Ok(content.to_pem()?) } @@ -584,7 +540,7 @@ mod test { attributes_validate::(¶meter).expect_err("invalid key type"); parameter.insert("key_type".to_string(), "".to_string()); attributes_validate::(¶meter).expect_err("invalid empty key type"); - for key_type in VALID_KEY_TYPE { + for key_type in all::().collect::>() { parameter.insert("key_type".to_string(), key_type.to_string()); attributes_validate::(¶meter).expect("valid key type"); } @@ -597,7 +553,7 @@ mod test { attributes_validate::(¶meter).expect_err("invalid key length"); parameter.insert("key_length".to_string(), "".to_string()); attributes_validate::(¶meter).expect_err("invalid empty key length"); - for key_length in VALID_KEY_SIZE { + for key_length in X509_VALID_KEY_SIZE { parameter.insert("key_length".to_string(), key_length.to_string()); attributes_validate::(¶meter).expect("valid key length"); } @@ -610,7 +566,7 @@ mod test { attributes_validate::(¶meter).expect_err("invalid digest algorithm"); parameter.insert("digest_algorithm".to_string(),"".to_string()); attributes_validate::(¶meter).expect_err("invalid empty digest algorithm"); - for key_length in VALID_DIGEST_ALGORITHM { + for key_length in all::().collect::>() { parameter.insert("digest_algorithm".to_string(),key_length.to_string()); attributes_validate::(¶meter).expect("valid digest algorithm"); } @@ -646,7 +602,7 @@ mod test { //choose 4 random digest algorithm let dummy_engine = get_encryption_engine(); let infra_config = get_infra_config(); - for hash in VALID_DIGEST_ALGORITHM { + for hash in all::().collect::>() { parameter.insert("digest_algorithm".to_string(), hash.to_string()); let sec_datakey = SecDataKey::load( &get_default_datakey( @@ -661,7 +617,7 @@ mod test { let mut parameter = get_default_parameter(); let dummy_engine = get_encryption_engine(); let infra_config = get_infra_config(); - for key_size in VALID_KEY_SIZE{ + for key_size in X509_VALID_KEY_SIZE { parameter.insert("key_size".to_string(), key_size.to_string()); let sec_datakey = SecDataKey::load( &get_default_datakey( @@ -676,7 +632,7 @@ mod test { let mut parameter = get_default_parameter(); let dummy_engine = get_encryption_engine(); let infra_config = get_infra_config(); - for key_type in VALID_KEY_TYPE{ + for key_type in all::().collect::>() { parameter.insert("key_type".to_string(), key_type.to_string()); let sec_datakey = SecDataKey::load( &get_default_datakey( diff --git a/src/presentation/handler/data/sign_handler.rs b/src/presentation/handler/data/sign_handler.rs index bb8deeb..f870327 100644 --- a/src/presentation/handler/data/sign_handler.rs +++ b/src/presentation/handler/data/sign_handler.rs @@ -23,7 +23,7 @@ use tokio_stream::StreamExt; use signatrust::{ signatrust_server::Signatrust, signatrust_server::SignatrustServer, SignStreamRequest, - SignStreamResponse, + SignStreamResponse, GetKeyInfoRequest, GetKeyInfoResponse }; use tonic::{Request, Response, Status, Streaming}; use crate::application::datakey::KeyService; @@ -51,7 +51,7 @@ where user_service } } - async fn validate_private_key_token(&self, token: Option, name: &str) -> SignatrustResult<()> { + async fn validate_key_token_matched(&self, token: Option, name: &str) -> SignatrustResult<()> { let names: Vec<_> = name.split(':').collect(); if names.len() <= 1 { return Ok(()) @@ -69,6 +69,34 @@ where K: KeyService + 'static, U: UserService + 'static, { + async fn get_key_info( + &self, + request: Request, + ) -> Result, Status> + { + let request = request.into_inner(); + //perform token validation on private keys + if let Err(err) = self.validate_key_token_matched(request.token, &request.key_id).await { + return Ok(Response::new(GetKeyInfoResponse { + attributes: HashMap::new(), + error: err.to_string(), + })) + } + return match self.key_service.get_by_type_and_name(request.key_type, request.key_id).await { + Ok(datakey) => { + Ok(Response::new(GetKeyInfoResponse { + attributes: datakey.attributes, + error: "".to_string(), + })) + } + Err(err) => { + Ok(Response::new(GetKeyInfoResponse { + attributes: HashMap::new(), + error: err.to_string(), + })) + } + } + } async fn sign_stream( &self, request: Request>, @@ -88,7 +116,7 @@ where token = inner_result.token; } //perform token validation on private keys - if let Err(err) = self.validate_private_key_token(token, &key_name).await { + if let Err(err) = self.validate_key_token_matched(token, &key_name).await { return Ok(Response::new(SignStreamResponse { signature: vec![], error: err.to_string(), -- Gitee