From e939f7aaff4a7377c1a1c1625b3c94e23f0a7298 Mon Sep 17 00:00:00 2001 From: unknown <1592085657@qq.com> Date: Thu, 20 Nov 2025 11:15:55 +0800 Subject: [PATCH 1/2] support generate cms by hash --- app/src/pages/listShow/ImportX509.vue | 20 - src/application/datakey.rs | 2 +- src/client/cmd/add.rs | 9 +- src/client/file_handler/kernel_module.rs | 19 +- src/client/file_handler/p7s.rs | 15 + src/client/worker/signer.rs | 21 +- src/domain/datakey/repository.rs | 3 +- .../database/model/datakey/repository.rs | 53 ++- src/infra/sign_plugin/x509.rs | 403 ++++++++++++++++-- src/presentation/server/control_server.rs | 1 - src/util/attributes.rs | 24 +- src/util/sign.rs | 3 + 12 files changed, 511 insertions(+), 62 deletions(-) diff --git a/app/src/pages/listShow/ImportX509.vue b/app/src/pages/listShow/ImportX509.vue index 0bfbc7f..7a516a4 100644 --- a/app/src/pages/listShow/ImportX509.vue +++ b/app/src/pages/listShow/ImportX509.vue @@ -60,26 +60,6 @@ />
- - - - - - - - - - Result { - self.repository.get_all_keys(user_id, query).await + self.repository.get_keys_by_condition(user_id, query).await } async fn get_one(&self, user: Option, id_or_name: String) -> Result { diff --git a/src/client/cmd/add.rs b/src/client/cmd/add.rs index 90e2301..a6ed07a 100644 --- a/src/client/cmd/add.rs +++ b/src/client/cmd/add.rs @@ -303,8 +303,13 @@ impl SignCommand for CommandAddHandler { 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()); + // key_attributes(digest_algo) need to be obtained when generate cms signature + let mut signer = RemoteSigner::new( + channel.unwrap(), + self.buffer_size, + self.token.clone(), + key_attributes.clone(), + ); //split file let send_handlers = files .into_iter() diff --git a/src/client/file_handler/kernel_module.rs b/src/client/file_handler/kernel_module.rs index 081a155..2efc824 100644 --- a/src/client/file_handler/kernel_module.rs +++ b/src/client/file_handler/kernel_module.rs @@ -167,11 +167,11 @@ impl FileHandler for KernelModuleFileHandler { } if let Some(sign_type) = sign_options.get(options::SIGN_TYPE) { - if sign_type != SignType::Cms.to_string().as_str() + if sign_type != SignType::KernelCms.to_string().as_str() && sign_type != SignType::PKCS7.to_string().as_str() { return Err(Error::InvalidArgumentError( - "kernel module file only support cms or pkcs7 sign type".to_string(), + "kernel module file only support kernel-cms or pkcs7 sign type".to_string(), )); } } @@ -265,7 +265,7 @@ mod test { #[test] fn test_get_raw_content_with_small_unsigned_content() { - let mut sign_options = HashMap::new(); + let 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"); @@ -279,7 +279,7 @@ mod test { #[test] fn test_get_raw_content_with_large_unsigned_content() { - let mut sign_options = HashMap::new(); + let 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"); @@ -293,7 +293,7 @@ mod test { #[test] fn test_get_raw_content_with_signed_content() { - let mut sign_options = HashMap::new(); + let 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"); @@ -307,7 +307,7 @@ mod test { #[test] fn test_get_raw_content_with_invalid_signed_content() { - let mut sign_options = HashMap::new(); + let sign_options = HashMap::new(); let file_handler = KernelModuleFileHandler::new(); let (name, _) = generate_signed_kernel_module(100, true).expect("generate signed kernel module failed"); @@ -340,10 +340,13 @@ mod test { assert!(result.is_err()); assert_eq!( result.unwrap_err().to_string(), - "invalid argument: kernel module file only support cms or pkcs7 sign type" + "invalid argument: kernel module file only support kernel-cms or pkcs7 sign type" ); - options.insert(options::SIGN_TYPE.to_string(), SignType::Cms.to_string()); + options.insert( + options::SIGN_TYPE.to_string(), + SignType::KernelCms.to_string(), + ); let result = handler.validate_options(&mut options); assert!(result.is_ok()); diff --git a/src/client/file_handler/p7s.rs b/src/client/file_handler/p7s.rs index 3391bb9..9bddf6c 100644 --- a/src/client/file_handler/p7s.rs +++ b/src/client/file_handler/p7s.rs @@ -15,10 +15,12 @@ */ use super::traits::FileHandler; +use crate::util::attributes::PkeyHashAlgo; use crate::util::error::{Error, Result}; use crate::util::options; use crate::util::sign::{KeyType, SignType}; use async_trait::async_trait; +use openssl::hash::hash; use std::collections::HashMap; use std::path::PathBuf; use tokio::fs; @@ -79,6 +81,19 @@ impl FileHandler for CmsFileHandler { format!("{}.{}", path.as_path().display(), CMS_EXTENSION), )) } + + async fn split_data( + &self, + path: &PathBuf, + _sign_options: &mut HashMap, + key_attributes: &HashMap, + ) -> Result>> { + let content = fs::read(path).await?; + debug!("key_attributes: {:?}", key_attributes); + let digest_algo = PkeyHashAlgo::get_digest_algo_from_attributes(key_attributes); + let digest = hash(digest_algo, &content)?; + Ok(vec![digest.to_vec()]) + } } #[cfg(test)] diff --git a/src/client/worker/signer.rs b/src/client/worker/signer.rs index 65b32a6..f188b96 100644 --- a/src/client/worker/signer.rs +++ b/src/client/worker/signer.rs @@ -18,7 +18,7 @@ use crate::client::file_handler::traits::FileHandler; use crate::client::sign_identity::SignIdentity; use crate::client::worker::traits::SignHandler; use async_trait::async_trait; - +use std::collections::HashMap; pub mod signatrust { tonic::include_proto!("signatrust"); } @@ -33,14 +33,21 @@ pub struct RemoteSigner { client: SignatrustClient, buffer_size: usize, token: Option, + key_attributes: HashMap, } impl RemoteSigner { - pub fn new(channel: Channel, buffer_size: usize, token: Option) -> Self { + pub fn new( + channel: Channel, + buffer_size: usize, + token: Option, + key_attributes: HashMap, + ) -> Self { Self { client: SignatrustClient::new(channel), buffer_size, token, + key_attributes, } } } @@ -54,6 +61,14 @@ impl SignHandler for RemoteSigner { ) -> SignIdentity { let mut signed_content = Vec::new(); let read_data = item.raw_content.borrow().clone(); + let mut sign_options: HashMap = HashMap::new(); + for (k, v) in self.key_attributes.iter() { + sign_options.insert(k.clone(), (*v).to_string()); + } + for (k, v) in item.sign_options.borrow().iter() { + sign_options.insert(k.clone(), (*v).to_string()); + } + for sign_content in read_data.into_iter() { let mut sign_segments: Vec = Vec::new(); let mut buffer = vec![0; self.buffer_size]; @@ -65,7 +80,7 @@ impl SignHandler for RemoteSigner { let content = buffer[0..length].to_vec(); sign_segments.push(SignStreamRequest { data: content, - options: item.sign_options.borrow().clone(), + options: sign_options.clone(), key_type: format!("{}", item.key_type), key_id: item.key_id.clone(), token: self.token.clone(), diff --git a/src/domain/datakey/repository.rs b/src/domain/datakey/repository.rs index a2694b0..30f830c 100644 --- a/src/domain/datakey/repository.rs +++ b/src/domain/datakey/repository.rs @@ -26,11 +26,12 @@ use chrono::Duration; pub trait Repository: Send + Sync { async fn create(&self, data_key: DataKey) -> Result; async fn delete(&self, id: i32) -> Result<()>; - async fn get_all_keys( + async fn get_keys_by_condition( &self, user_id: i32, query: DatakeyPaginationQuery, ) -> Result; + async fn get_all_keys(&self) -> Result; async fn get_by_id_or_name( &self, id: Option, diff --git a/src/infra/database/model/datakey/repository.rs b/src/infra/database/model/datakey/repository.rs index bf735c0..6f3f299 100644 --- a/src/infra/database/model/datakey/repository.rs +++ b/src/infra/database/model/datakey/repository.rs @@ -205,7 +205,7 @@ impl<'a> Repository for DataKeyRepository<'a> { Ok(()) } - async fn get_all_keys( + async fn get_keys_by_condition( &self, user_id: i32, query: DatakeyPaginationQuery, @@ -281,6 +281,57 @@ impl<'a> Repository for DataKeyRepository<'a> { }) } + async fn get_all_keys(&self) -> Result { + let results = datakey_dto::Entity::find() + .select_only() + .columns(datakey_dto::Column::iter().filter(|col| { + !matches!( + col, + datakey_dto::Column::UserEmail + | datakey_dto::Column::RequestDeleteUsers + | datakey_dto::Column::RequestRevokeUsers + | datakey_dto::Column::X509CrlUpdateAt + ) + })) + .exprs([ + Expr::cust("user_table.email as user_email"), + Expr::cust("GROUP_CONCAT(request_delete_table.user_email) as request_delete_users"), + Expr::cust("GROUP_CONCAT(request_revoke_table.user_email) as request_revoke_users"), + ]) + .join_as_rev( + JoinType::InnerJoin, + user_dto::Relation::Datakey.def(), + Alias::new("user_table"), + ) + .join_as_rev( + JoinType::LeftJoin, + self.get_pending_operation_relation(RequestType::Delete) + .into(), + Alias::new("request_delete_table"), + ) + .join_as_rev( + JoinType::LeftJoin, + self.get_pending_operation_relation(RequestType::Revoke) + .into(), + Alias::new("request_revoke_table"), + ) + .group_by(datakey_dto::Column::Id) + .all(self.db_connection) + .await?; + + let mut results_vec = vec![]; + for dto in results { + results_vec.push(DataKey::try_from(dto)?); + } + let total_numbers = results_vec.len(); + Ok(PagedDatakey { + data: results_vec, + meta: PagedMeta { + total_count: total_numbers as u64, + }, + }) + } + async fn get_keys_for_crl_update(&self, duration: Duration) -> Result> { let now = Utc::now(); match datakey_dto::Entity::find() diff --git a/src/infra/sign_plugin/x509.rs b/src/infra/sign_plugin/x509.rs index 4aef025..7f37bed 100644 --- a/src/infra/sign_plugin/x509.rs +++ b/src/infra/sign_plugin/x509.rs @@ -14,7 +14,24 @@ * */ +use super::util::{attributes_validate, validate_utc_time, validate_utc_time_not_expire}; +use crate::domain::datakey::entity::{ + DataKey, DataKeyContent, KeyType, RevokedKey, SecDataKey, SecParentDateKey, + INFRA_CONFIG_DOMAIN_NAME, +}; +use crate::domain::datakey::plugins::x509::{ + X509DigestAlgorithm, X509EEUsage, X509KeyType, X509_SM2_VALID_KEY_SIZE, X509_VALID_KEY_SIZE, +}; +use crate::domain::sign_plugin::SignPlugins; +use crate::util::attributes; +use crate::util::attributes::PkeyHashAlgo; +use crate::util::error::{Error, Result}; +use crate::util::key::{decode_hex_string_to_u8, encode_u8_to_hex_string}; +use crate::util::options; +use crate::util::sign::SignType; use chrono::{DateTime, Utc}; +#[allow(unused_imports)] +use enum_iterator::all; use foreign_types_shared::{ForeignType, ForeignTypeRef}; use openssl::asn1::{Asn1Integer, Asn1Time}; use openssl::bn::{BigNum, MsbOption}; @@ -22,6 +39,7 @@ use openssl::cms::{CMSOptions, CmsContentInfo}; use openssl::hash::MessageDigest; use openssl::nid::Nid; use openssl::pkcs7::{Pkcs7, Pkcs7Flags}; +use openssl::pkey; use openssl::pkey::PKey; use openssl::stack::Stack; use openssl::x509; @@ -30,33 +48,54 @@ use openssl::x509::extension::{ }; use openssl::x509::{X509Crl, X509Extension}; use openssl_sys::{ - X509_CRL_add0_revoked, X509_CRL_new, X509_CRL_set1_lastUpdate, X509_CRL_set1_nextUpdate, - X509_CRL_set_issuer_name, X509_CRL_sign, X509_REVOKED_new, X509_REVOKED_set_revocationDate, - X509_REVOKED_set_serialNumber, + BIO_free_all, BIO_get_mem_data, BIO_new, BIO_new_mem_buf, BIO_s_mem, CMS_ContentInfo, + CMS_ContentInfo_free, CMS_sign, X509_CRL_add0_revoked, X509_CRL_new, X509_CRL_set1_lastUpdate, + X509_CRL_set1_nextUpdate, X509_CRL_set_issuer_name, X509_CRL_sign, X509_REVOKED_new, + X509_REVOKED_set_revocationDate, X509_REVOKED_set_serialNumber, BIO, CMS_BINARY, CMS_DETACHED, CMS_KEY_PARAM, + CMS_NOSMIMECAP, CMS_PARTIAL, EVP_MD, EVP_PKEY, EVP_PKEY_CTX, EVP_PKEY_CTX_set_rsa_padding, X509, X509_CRL, RSA_PKCS1_PSS_PADDING, }; use secstr::SecVec; use serde::Deserialize; use std::collections::HashMap; +use std::ffi::CString; +use std::ffi::{c_char, c_int, c_uchar, c_uint, c_void}; +use std::ptr; +use std::slice; use std::str::FromStr; use std::time::{Duration, SystemTime}; - -use super::util::{attributes_validate, validate_utc_time, validate_utc_time_not_expire}; -use crate::domain::datakey::entity::{ - DataKey, DataKeyContent, KeyType, RevokedKey, SecDataKey, SecParentDateKey, - INFRA_CONFIG_DOMAIN_NAME, -}; -use crate::domain::datakey::plugins::x509::{ - X509DigestAlgorithm, X509EEUsage, X509KeyType, X509_SM2_VALID_KEY_SIZE, X509_VALID_KEY_SIZE, -}; -use crate::domain::sign_plugin::SignPlugins; -use crate::util::attributes; -use crate::util::error::{Error, Result}; -use crate::util::key::{decode_hex_string_to_u8, encode_u8_to_hex_string}; -use crate::util::options; -use crate::util::sign::SignType; -#[allow(unused_imports)] -use enum_iterator::all; use validator::{Validate, ValidationError}; +#[repr(C)] +pub struct CMS_SignerInfo { + // 根据 OpenSSL 头文件添加字段 + pub cert: *mut X509, + pub pkey: *mut EVP_PKEY, + pub md: *const EVP_MD, + pub sig: *mut u8, + pub siglen: i32, +} + +extern "C" { + pub fn CMS_final_digest( + cms: *mut CMS_ContentInfo, + md: *const c_uchar, + mdlen: c_uint, + dcont: *mut BIO, + flags: c_uint, + ) -> c_int; + + pub fn CMS_add1_signer( + cms: *mut CMS_ContentInfo, + cert: *mut X509, + pkey: *mut EVP_PKEY, + md: *const EVP_MD, + flags: u32, + ) -> *mut CMS_SignerInfo; + pub fn CMS_SignerInfo_get0_pkey_ctx(si: *mut CMS_SignerInfo) -> *mut EVP_PKEY_CTX; + pub fn i2d_CMS_bio(out: *mut BIO, cms: *mut CMS_ContentInfo) -> c_int; + pub fn EVP_PKEY_is_a(pkey: *const EVP_PKEY, name: *const c_char) -> c_int; + pub fn CMS_add1_crl(cms: *mut CMS_ContentInfo, + crl: *mut X509_CRL) -> c_int; +} #[derive(Debug, Validate, Deserialize)] #[validate(schema(function = "validate_x509_key_size_for_generation"))] @@ -512,6 +551,139 @@ impl X509Plugin { serial_number: Some(encode_u8_to_hex_string(&serial_number.to_vec())), }) } + // openssl3.0 has adopted a new framework, rust openssl openssl::pkey::Pky::id(EVP_PKEY_get_id()) may not return the traditional NID you expect. + // This requires using the C interface EVP_PKEY_is_a to detect key type + fn detect_key_type(private_key: &PKey) -> Result { + unsafe { + let rsa = CString::new("RSA").unwrap(); + let rsa_pss = CString::new("RSA-PSS").unwrap(); + let dsa = CString::new("DSA").unwrap(); + let sm2 = CString::new("SM2").unwrap(); + if EVP_PKEY_is_a(private_key.as_ptr(), rsa.as_ptr()) == 1 { + Ok(X509KeyType::Rsa.as_str().to_string()) + } else if EVP_PKEY_is_a(private_key.as_ptr(), dsa.as_ptr()) == 1 { + Ok(X509KeyType::Dsa.as_str().to_string()) + } else if EVP_PKEY_is_a(private_key.as_ptr(), sm2.as_ptr()) == 1 { + Ok(X509KeyType::Sm2.as_str().to_string()) + } else if EVP_PKEY_is_a(private_key.as_ptr(), rsa_pss.as_ptr()) == 1 { + Ok(X509KeyType::Rsa.as_str().to_string()) + } else { + Err(Error::InvalidArgumentError( + "key type only support RSA,DSA,SM2".to_string(), + )) + } + } + } +} + +fn cms_sign_with_hash( + cert: &x509::X509Ref, + pkey: &PKey, + digest: &[u8], + attribute: HashMap, +) -> Result> { + unsafe { + let data_bio = BIO_new_mem_buf(digest.as_ptr() as *const c_void, digest.len() as i32); + if data_bio.is_null() { + return Err(Error::InvalidArgumentError( + "BIO_new_mem_buf failed".to_string(), + )); + } + + struct BioGuard(*mut openssl_sys::BIO); + impl Drop for BioGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { BIO_free_all(self.0) }; + } + } + } + let _data_bio_guard = BioGuard(data_bio); + + let flags = CMS_DETACHED | CMS_BINARY | CMS_PARTIAL | CMS_NOSMIMECAP | CMS_KEY_PARAM; + let cms: *mut CMS_ContentInfo = CMS_sign( + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + data_bio, + flags, + ); + if cms.is_null() { + return Err(Error::InvalidArgumentError( + "CMS_sign (partial) failed".to_string(), + )); + } + + struct CmsGuard(*mut CMS_ContentInfo); + impl Drop for CmsGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { CMS_ContentInfo_free(self.0) }; + } + } + } + let _cms_guard = CmsGuard(cms); + + let md = PkeyHashAlgo::get_openssl_c_digest_algo(&attribute); + if md.is_null() { + return Err(Error::InvalidArgumentError( + "EVP_sha256 returned null".to_string(), + )); + } + + let si = CMS_add1_signer(cms, cert.as_ptr(), pkey.as_ptr(), md, flags); + if si.is_null() { + return Err(Error::InvalidArgumentError( + "CMS_add1_signer failed".to_string(), + )); + } + + let pk_ctx = CMS_SignerInfo_get0_pkey_ctx(si); + if pk_ctx.is_null() { + return Err(Error::InvalidArgumentError( + "CMS_SignerInfo_get0_pkey_ctx failed".to_string(), + )); + } + EVP_PKEY_CTX_set_rsa_padding(pk_ctx, RSA_PKCS1_PSS_PADDING); + + let ret = CMS_final_digest( + cms, + digest.as_ptr(), + digest.len() as u32, + ptr::null_mut(), + flags, + ); + if ret != 1 { + return Err(Error::InvalidArgumentError( + "CMS_final_digest failed".to_string(), + )); + } + + let out_bio = BIO_new(BIO_s_mem()); + if out_bio.is_null() { + return Err(Error::InvalidArgumentError( + "BIO_new(BIO_s_mem) failed".to_string(), + )); + } + let _out_bio_guard = BioGuard(out_bio); + + if i2d_CMS_bio(out_bio, cms) != 1 { + return Err(Error::InvalidArgumentError( + "i2d_CMS_bio failed".to_string(), + )); + } + + let mut ptr: *mut c_char = ptr::null_mut(); + let len = BIO_get_mem_data(out_bio, &mut ptr) as usize; + if len == 0 || ptr.is_null() { + return Err(Error::InvalidArgumentError( + "BIO_get_mem_data got empty buffer".to_string(), + )); + } + let data_ptr = ptr as *const u8; + let buf = slice::from_raw_parts(data_ptr, len).to_vec(); + Ok(buf) + } } impl SignPlugins for X509Plugin { @@ -536,11 +708,22 @@ impl SignPlugins for X509Plugin { Self: Sized, { let _ = attributes_validate::(&key.attributes)?; - let _private_key = PKey::private_key_from_pem(&key.private_key)?; + let private_key = PKey::private_key_from_pem(&key.private_key)?; let certificate = x509::X509::from_pem(&key.certificate)?; - if !key.public_key.is_empty() { - let _public_key = PKey::public_key_from_pem(&key.public_key)?; + match X509Plugin::detect_key_type(&private_key) { + Ok(key_type) => { + key.attributes + .insert("key_type".to_string(), key_type.to_string()); + } + Err(_e) => { + return Err(Error::InvalidArgumentError( + "key type only support RSA,DSA,SM2".to_string(), + )); + } } + let bit = private_key.bits(); + key.attributes + .insert("key_length".to_string(), bit.to_string()); let unix_time = Asn1Time::from_unix(0)?.diff(certificate.not_after())?; let expire = SystemTime::UNIX_EPOCH + Duration::from_secs(unix_time.days as u64 * 86400 + unix_time.secs as u64); @@ -644,8 +827,10 @@ impl SignPlugins for X509Plugin { )?; Ok(pkcs7.to_der()?) } - SignType::Cms => { + SignType::KernelCms => { //cms option reference: https://man.openbsd.org/CMS_sign.3 + //the cms signature of the kernel module doesn't have the signedAttrs field + //it can only send all data to data-server for signing let cms_signature = CmsContentInfo::sign( Some(&certificate), Some(&private_key), @@ -659,6 +844,12 @@ impl SignPlugins for X509Plugin { )?; Ok(cms_signature.to_der()?) } + SignType::Cms => { + // common cms signature have the signedAttrs field + // after calculating the hash on the client side, it is sent to the server for signing + cms_sign_with_hash(&certificate, &private_key, &content, options) + } + SignType::RsaHash => { // rust-openssl/openssl/src/pkey_ctx.rs let mut signature = vec![]; @@ -1112,6 +1303,170 @@ mod test { .expect(format!("generate ca key with passphrase successfully").as_str()); } + #[test] + fn test_validate_and_update_with_sm2cert() { + let certificate = "-----BEGIN CERTIFICATE----- +MIIB1DCCAXoCFGfoVD/6iDpHYUbmTA0+LH/b4tfuMAoGCCqBHM9VAYN1MGwxCzAJ +BgNVBAYTAkNOMRAwDgYDVQQIDAdCZWlqaW5nMRAwDgYDVQQHDAdCZWlqaW5nMRIw +EAYDVQQKDAlNeUNvbXBhbnkxDzANBgNVBAsMBlJvb3RDQTEUMBIGA1UEAwwLU00y +IFJvb3QgQ0EwHhcNMjUxMTAzMDgxODEyWhcNMzUxMTAxMDgxODEyWjBsMQswCQYD +VQQGEwJDTjEQMA4GA1UECAwHQmVpamluZzEQMA4GA1UEBwwHQmVpamluZzESMBAG +A1UECgwJTXlDb21wYW55MQ8wDQYDVQQLDAZSb290Q0ExFDASBgNVBAMMC1NNMiBS +b290IENBMFowFAYIKoEcz1UBgi0GCCqBHM9VAYItA0IABNYo1OwvitLruiU3oRAc +uaLSplc2Vrj19z2oPicvx8hn3fQLYlqKrKcFvKOWllL3ByQVcMJ4HmRylmOrk24q +4xYwCgYIKoEcz1UBg3UDSAAwRQIhAMnIl0Em/3b8hhR9Ly/FGlt3q2IN1EHLg64+ +JGLqK0DFAiAULqROgRSmSWpJgMzU8KMoPfDM7CJ5/NCnDqI3oM9uTw== +-----END CERTIFICATE-----"; + let private_key = "-----BEGIN PRIVATE KEY----- +MIGIAgEAMBQGCCqBHM9VAYItBggqgRzPVQGCLQRtMGsCAQEEIDUaoPl+RCqHV/Un +qWcBnNWXVAOM7BMiiPWQFFotA1h0oUQDQgAE1ijU7C+K0uu6JTehEBy5otKmVzZW +uPX3Pag+Jy/HyGfd9AtiWoqspwW8o5aWUvcHJBVwwngeZHKWY6uTbirjFg== +-----END PRIVATE KEY-----"; + let mut datakey = get_default_datakey(None, None, None); + datakey.certificate = certificate.as_bytes().to_vec(); + datakey.private_key = private_key.as_bytes().to_vec(); + let _ = X509Plugin::validate_and_update(&mut datakey); + if let Some(value) = datakey.attributes.get("key_length") { + assert_eq!(value, "256"); + } else { + panic!("Expected key 'key_length' not found in the map."); + } + + if let Some(value) = datakey.attributes.get("key_type") { + assert_eq!(value, "sm2"); + } else { + panic!("Expected key 'key_type' not found in the map."); + } + } + + #[test] + fn test_validate_and_update_with_rsacert() { + let certificate = "-----BEGIN CERTIFICATE----- +MIIDSzCCAjOgAwIBAgIUKTs/prIakrwRyyYbYcfoy6YC6rkwDQYJKoZIhvcNAQEL +BQAwTjELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAmNuMQswCQYDVQQHDAJjbjELMAkG +A1UECgwCY24xCzAJBgNVBAsMAmNuMQswCQYDVQQDDAJDTjAeFw0yNTExMTgxMjAy +MTBaFw0yNjExMTgxMjAyMTBaME4xCzAJBgNVBAYTAkNOMQswCQYDVQQIDAJjbjEL +MAkGA1UEBwwCY24xCzAJBgNVBAoMAmNuMQswCQYDVQQLDAJjbjELMAkGA1UEAwwC +Q04wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf87TFBu9fPQ31mjWA +OcYMQeMmHsI/v1Sng4aBoLhWAWZZ+8AuV37wjBVuYGR4GkZHmIJBmi2YNBkPt/4A +IqX4S8hYAAZUurXcVKub2aQOnfh9OLTIxT/GR56imlcRZDrQ+R/VrpVj3yN3qQHP +M1oyuzWin8osCSDRHfEAWcmKVmvHfq6tYVbnmOSQfiPI8+zxMsaMT07RDogOfxEO +/QkPyj16ih9zpK9N2rqbj7q00JIiTSBZ3P/zlshjiZSGZeVaj3bC4KnzKrdlxS46 +nfJ7tZ+sHxBqSuScO8X/ButvL7HMnB92Ut47C2SnvD/Or+z86G9KDD2pRxqXcfBo +4cYXAgMBAAGjITAfMB0GA1UdDgQWBBSwe9s1aUMAib5BAffASj6l7+5D1DANBgkq +hkiG9w0BAQsFAAOCAQEAPKEWCfyNxRdaemcYEXgp6/AlmFoFaNBLi0ewyKOTuy+N +zQvvgdvFbkdsg9BygNQeZQ/WFpNwrODxMZgcGFLpfxPgq1JrIVrU4CQLn8AgTvkc +2sw/1u4xw4ufyKIYxQdsaOPLXOwedUtY96X0e622oIrr7tmUIse8502WnhllRtHL +aGhroMLSVZrNoAU7KHbC3fnHmQPl9HWx9m37u3DGVtLf8/uXsX74RyV6U9ESHbKw +ptZaepLoHk4fIixgMEVMAlHrZPdtUs7X0B520fi2/BwdChqfewf6thkBK9pzGcss +Z8qL7OaQxFqgD2qi7jjWqkqmYe/yxyIowvu1bWRtbA== +-----END CERTIFICATE-----"; + let private_key = "-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCf87TFBu9fPQ31 +mjWAOcYMQeMmHsI/v1Sng4aBoLhWAWZZ+8AuV37wjBVuYGR4GkZHmIJBmi2YNBkP +t/4AIqX4S8hYAAZUurXcVKub2aQOnfh9OLTIxT/GR56imlcRZDrQ+R/VrpVj3yN3 +qQHPM1oyuzWin8osCSDRHfEAWcmKVmvHfq6tYVbnmOSQfiPI8+zxMsaMT07RDogO +fxEO/QkPyj16ih9zpK9N2rqbj7q00JIiTSBZ3P/zlshjiZSGZeVaj3bC4KnzKrdl +xS46nfJ7tZ+sHxBqSuScO8X/ButvL7HMnB92Ut47C2SnvD/Or+z86G9KDD2pRxqX +cfBo4cYXAgMBAAECggEABZvMCOSXXCWN6cDAg4CDG0bsKhgGA6o306/e9YinLgza +g+k58eYLg2/GCJrEqxlwwW3tk1NOqfmZr11qQKL2YuB1Y/CMSEhLvDAT3GEjSYfs +gKeOX0PbWp6ER3tV9jwne9Bgd2OpxVi7q6R3dcZ9MS4zUUJ9GlIvnmWIX9TGJl2X +NI8tY2SG6J+ow+OOX98IUzLcyVaZR/AJBKUgFl0PjExLfWBDSLJqJJYEamDy1INh +1PLmPjx3Hz+6r5iaVw3OqPJcZzo+9Bj16RhybOlRvrXMAUbD7w2RzfXI096b4De+ +HgyYHFNxj5KpC6qnihkGZVBcbeIjXXPgwexfDipbQQKBgQDNei3y3l0kOXgUgfhp +GMxnmyiHKZmtHzXJJnlNHib7wVPh6YiGDnxVl50MekxSqo53ASv0cpamcDy5TM6N +R5ZeBJW9//MqA0OGbNOCDjYBg2E8jGvyXVPp76ouRxeO5aDordJof4isp6VAPImv +djx3CxxNJKJhzKo5oSJZ9iD+RwKBgQDHR+zsifUDimqg33mrJ92HUMiFxM+haYMV +RySnQZXlDPnVsuZYAdJBUKAMG8ObHy2R2KMnxDdFRRhW6AVzkMkZaQXTUh4M7sx0 +QKWHCXRYWNqoS7qOzxS9O6dW6+kmv/bMCjX6l+z2pIe3bpKuHgOPFVyJuNhz285R +RtbxSrbRsQKBgHLRPA3DfY55YoUrHzEy/z1BsULd1xarIvX0vsF+ANCa9hF92qD2 +RTnaz5IiYLWswpDzIamlwlLc0sHEjoLZpseAjmAuPqWST1A1TXcWE82CqXoZCVTU +G8jT+GeFqD9cRy7dun5UDX5U631alqFqU1094yGkP+ygXdp4FObqJwOPAoGACWMC +7vVknCEV+rPsGDrNfYU5nMtzeEfvC76JJHO7asmcrws5PGYBkGAK2eco5JKoY9lP +fh0I+XNSvS06rIHiZxcCVjzk+3j4GnW9FkpEt7CfxBOlGvr4IB3COR7toYyjRGMq +vb4QRGHlnqdPs3HoewHnlPknAPYWls9+amk5iVECgYEAu90xfsQzVSiQc4FiDC4+ +VU0SuEtd8KmguwckE0RProzJXTs4BhooUB4uwgKA4+IP6cPG9my4vaBL055fHiYb +xpmKp7lj+xPgtXIekR+vzIma2nXiD4Adrs2ITwcjY7dtKLyoLiVJqXQRxWeBoaDS +hlaQghVl9wUq5TwOJgwJDsQ= +-----END PRIVATE KEY-----"; + let mut datakey = get_default_datakey(None, None, None); + datakey.certificate = certificate.as_bytes().to_vec(); + datakey.private_key = private_key.as_bytes().to_vec(); + let _ = X509Plugin::validate_and_update(&mut datakey); + if let Some(value) = datakey.attributes.get("key_length") { + assert_eq!(value, "2048"); + } else { + panic!("Expected key 'key_length' not found in the map."); + } + + if let Some(value) = datakey.attributes.get("key_type") { + assert_eq!(value, "rsa"); + } else { + panic!("Expected key 'key_type' not found in the map."); + } + } + + #[test] + fn test_validate_and_update_with_dsacert() { + let certificate = "-----BEGIN CERTIFICATE----- +MIIEkzCCBEGgAwIBAgIUHw8WUb9beKVreRngMZyZUrP8UqgwCwYJYIZIAWUDBAMC +MEUxCzAJBgNVBAYTAkNOMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJ +bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjUxMTE4MTI1MjUxWhcNMjYxMTE4 +MTI1MjUxWjBFMQswCQYDVQQGEwJDTjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8G +A1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIDRDCCAjYGByqGSM44BAEw +ggIpAoIBAQCrEexqKCpPOzXK9ZyZZeSLCEFoheoAOMTxLDXcTw9h88J7GZ70udGZ +KinBbT/L9yZimzH3FSwyWbR8gjJxzL84q2qv8rLTtXXgjL93uF+1lDcfITgswCKm +Uvm4uMwPCpved7U+Ni5E5E7FJHEY8MVQQdBnQsKhLpIWyi4xTEK1vHVwS3gyL7D9 +HvxaZInZ5x7tOeqKeNsXOS9zXMdkvhyvfDoMtCrq9CwAd/QNkizQ+jyiQP9/Q8xf +crCp4RMHfMTM+c7KkB34w0FYVWg8GOMpWJw55wQ4VpSCxSrc0JC6159vxtLk/CzW +k9IqfA7f7mr/7sR8StKEhioyQ2Z45Z3nAh0AkhAfQfPdW+1ub60zvSsjfOLso36I +fT7TbWvhAQKCAQEAg7xfk8S1E+4NKgxypwtvvt/FVnfbgbkZ0pkQUrD8bbYZamiN +vdMvDauEj16AEwbvUkLdZa5Ht/EcknYimgGG305Ah/hvqUayVd9C22s/7JxOyg/W +Iy1Y6vScDWNO6Svlu9ZU8RmTEXgL9vIekbo1hAnni3zmC9/cDOuMWVdkL0vWqirw +oY8p2QJ3hJXZTD1pgSQaI9VsnnwU+hmCrhRQQUZf6xcDWe6kpgBnCD3kJu3GDr4I +EsA8Q4rGv+Cr4z29Dl7BiSLnJVSVQ9HOSdqvE73CYHJbeaWk0ASNBkimeqzY+5lP +tqGHoZhDw0V6V9xmE6yA+4RA1lCZSn8aLwoEfQOCAQYAAoIBAQCEWY0OioYoUwTx +qOtrDxUypntePUafTAjp3ZsEy54eLGVBWC4Dd9T78Zn98x/9dt/leH+7f40Dv1tA +d2ok5cjMkeMqUEcq9iEC4SkqZfTg6seaOmMaeOSRSfIw2JR0g8RLUCtEvxyfbBNF +2T6aka4PMqSIx4IPKpRENY2/ICYrFAjTHUFyE7d3ER/zbE+r/J8KpBXi0nDMkYcv +1RkZhkDkxoWnVWO6BPDnbe1yL2linCNUhRtmpYHfe9DO16st7MYBdjLS7YEed9Zv +SoBy1UasYaCEoLROzRuUDWXc+mpY73G6B1cAmZd9OU1d+lQ3K5SdIVSrej5YksTE +5QQsoQlUoyEwHzAdBgNVHQ4EFgQUooMZNGLs7K3I1LZBzsX8gbi3fgEwCwYJYIZI +AWUDBAMCAz8AMDwCHGdHB/xu+SQC92KUgXcW2H67qGTg5jsKYMAJBlcCHEbV7+7K +TdRvdJ2ZKZjSsC39ZPCP7KnaP881vaI= +-----END CERTIFICATE-----"; + let private_key = "-----BEGIN PRIVATE KEY----- +MIICXgIBADCCAjYGByqGSM44BAEwggIpAoIBAQCrEexqKCpPOzXK9ZyZZeSLCEFo +heoAOMTxLDXcTw9h88J7GZ70udGZKinBbT/L9yZimzH3FSwyWbR8gjJxzL84q2qv +8rLTtXXgjL93uF+1lDcfITgswCKmUvm4uMwPCpved7U+Ni5E5E7FJHEY8MVQQdBn +QsKhLpIWyi4xTEK1vHVwS3gyL7D9HvxaZInZ5x7tOeqKeNsXOS9zXMdkvhyvfDoM +tCrq9CwAd/QNkizQ+jyiQP9/Q8xfcrCp4RMHfMTM+c7KkB34w0FYVWg8GOMpWJw5 +5wQ4VpSCxSrc0JC6159vxtLk/CzWk9IqfA7f7mr/7sR8StKEhioyQ2Z45Z3nAh0A +khAfQfPdW+1ub60zvSsjfOLso36IfT7TbWvhAQKCAQEAg7xfk8S1E+4NKgxypwtv +vt/FVnfbgbkZ0pkQUrD8bbYZamiNvdMvDauEj16AEwbvUkLdZa5Ht/EcknYimgGG +305Ah/hvqUayVd9C22s/7JxOyg/WIy1Y6vScDWNO6Svlu9ZU8RmTEXgL9vIekbo1 +hAnni3zmC9/cDOuMWVdkL0vWqirwoY8p2QJ3hJXZTD1pgSQaI9VsnnwU+hmCrhRQ +QUZf6xcDWe6kpgBnCD3kJu3GDr4IEsA8Q4rGv+Cr4z29Dl7BiSLnJVSVQ9HOSdqv +E73CYHJbeaWk0ASNBkimeqzY+5lPtqGHoZhDw0V6V9xmE6yA+4RA1lCZSn8aLwoE +fQQfAh0Ajn5YKCFR13WmbVb52M44GN50U+cJ24RFKPaunA== +-----END PRIVATE KEY-----"; + let mut datakey = get_default_datakey(None, None, None); + datakey.certificate = certificate.as_bytes().to_vec(); + datakey.private_key = private_key.as_bytes().to_vec(); + let _ = X509Plugin::validate_and_update(&mut datakey); + if let Some(value) = datakey.attributes.get("key_length") { + assert_eq!(value, "2048"); + } else { + panic!("Expected key 'key_length' not found in the map."); + } + + if let Some(value) = datakey.attributes.get("key_type") { + assert_eq!(value, "dsa"); + } else { + panic!("Expected key 'key_type' not found in the map."); + } + } + #[test] fn test_validate_and_update() { let public_key = "-----BEGIN PUBLIC KEY----- diff --git a/src/presentation/server/control_server.rs b/src/presentation/server/control_server.rs index 6311b62..36cc9bb 100644 --- a/src/presentation/server/control_server.rs +++ b/src/presentation/server/control_server.rs @@ -183,7 +183,6 @@ impl ControlServer { .get_string("control-server.server_port")? .parse()?, )?; - //prepare redis store let store = RedisSessionStore::new(&redis_connection).await?; let limiter = web::Data::new( diff --git a/src/util/attributes.rs b/src/util/attributes.rs index 35c34a4..199ee0b 100644 --- a/src/util/attributes.rs +++ b/src/util/attributes.rs @@ -21,7 +21,9 @@ use openssl::md::Md; use openssl::pkey::PKey; use openssl::pkey_ctx::PkeyCtx; use openssl::rsa::Rsa; - +use openssl_sys::{ + EVP_md5, EVP_sha1, EVP_sha224, EVP_sha256, EVP_sha384, EVP_sha512, EVP_sm3, EVP_MD, +}; pub const DIGEST_ALGO: &str = "digest_algorithm"; #[repr(u8)] @@ -84,6 +86,26 @@ impl PkeyHashAlgo { digest_algo } + pub fn get_openssl_c_digest_algo(attributes: &HashMap) -> *const EVP_MD { + unsafe { + let digest_algo = match attributes + .get(DIGEST_ALGO) + .expect("get algo failed") + .as_str() + { + "md5" => EVP_md5(), + "sha1" => EVP_sha1(), + "sha2_224" => EVP_sha224(), + "sha2_256" => EVP_sha256(), + "sha2_384" => EVP_sha384(), + "sha2_512" => EVP_sha512(), + "sm3" => EVP_sm3(), + _ => EVP_sha256(), + }; + digest_algo + } + } + pub fn to_u8(self) -> u8 { self as u8 } diff --git a/src/util/sign.rs b/src/util/sign.rs index 2ba901f..38070ae 100644 --- a/src/util/sign.rs +++ b/src/util/sign.rs @@ -5,6 +5,7 @@ use std::str::FromStr; #[derive(clap::ValueEnum, Clone, Debug, PartialEq, Eq, Hash)] pub enum SignType { Cms, // signed method for a CMS signed data + KernelCms, // signed method for a Kernel data Authenticode, // signed method for signing EFI image using authenticode spec PKCS7, // signed method for a pkcs7 signed data RsaHash, // signed method for a ima eam using rsa hash @@ -14,6 +15,7 @@ impl Display for SignType { fn fmt(&self, f: &mut Formatter) -> fmtResult { match self { SignType::Cms => write!(f, "cms"), + SignType::KernelCms => write!(f, "kernel-cms"), SignType::Authenticode => write!(f, "authenticode"), SignType::PKCS7 => write!(f, "pkcs7"), SignType::RsaHash => write!(f, "rsahash"), @@ -27,6 +29,7 @@ impl FromStr for SignType { fn from_str(s: &str) -> Result { match s { "cms" => Ok(SignType::Cms), + "kernel-cms" => Ok(SignType::KernelCms), "authenticode" => Ok(SignType::Authenticode), "pkcs7" => Ok(SignType::PKCS7), "rsahash" => Ok(SignType::RsaHash), -- Gitee From f67ed80ecf70bf0a4531bb0dae1ee317da4b65be Mon Sep 17 00:00:00 2001 From: unknown <1592085657@qq.com> Date: Tue, 2 Dec 2025 14:45:50 +0800 Subject: [PATCH 2/2] support timeStamp --- scripts/initialize-user-and-keys.sh | 6 +- src/application/datakey.rs | 33 +- src/client/cmd/add.rs | 24 + src/domain/datakey/repository.rs | 2 +- src/domain/sign_plugin.rs | 2 +- src/domain/sign_service.rs | 2 + .../database/model/datakey/repository.rs | 20 +- src/infra/sign_backend/memory/backend.rs | 18 +- src/infra/sign_plugin/cms.rs | 851 ++++++++++++++++++ src/infra/sign_plugin/mod.rs | 1 + src/infra/sign_plugin/openpgp.rs | 2 +- src/infra/sign_plugin/signers.rs | 5 +- src/infra/sign_plugin/x509.rs | 208 ++--- src/presentation/handler/data/sign_handler.rs | 2 +- src/util/attributes.rs | 7 +- src/util/options.rs | 4 + 16 files changed, 1001 insertions(+), 186 deletions(-) create mode 100644 src/infra/sign_plugin/cms.rs diff --git a/scripts/initialize-user-and-keys.sh b/scripts/initialize-user-and-keys.sh index 5aa974f..5660662 100755 --- a/scripts/initialize-user-and-keys.sh +++ b/scripts/initialize-user-and-keys.sh @@ -40,19 +40,19 @@ function create_default_x509_ee { function create_default_x509_ca_sm2 { echo "start to create default x509_sm2 CA identified with default-x509ca-sm2" - RUST_LOG=info ./target/debug/control-admin --config ./config/server.toml generate-keys --name default-x509ca-sm2 --description "used for test purpose only" --key-type x509ca --email tommylikehu@gmail.com --param-key-type sm2 --param-key-size 2048 \ + RUST_LOG=info ./target/debug/control-admin --config ./config/server.toml generate-keys --name default-x509ca-sm2 --description "used for test purpose only" --key-type x509ca --email tommylikehu@gmail.com --param-key-type sm2 --param-key-size 256 \ --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 CA" --digest-algorithm sm3 --visibility public } function create_default_x509_ia_sm2 { echo "start to create default x509 sm2 ICA identified with default-x509" - RUST_LOG=info ./target/debug/control-admin --config ./config/server.toml generate-keys --name default-x509ica-sm2 --description "used for test purpose only" --key-type x509ica --email tommylikehu@gmail.com --param-key-type sm2 --param-key-size 2048 \ + RUST_LOG=info ./target/debug/control-admin --config ./config/server.toml generate-keys --name default-x509ica-sm2 --description "used for test purpose only" --key-type x509ica --email tommylikehu@gmail.com --param-key-type sm2 --param-key-size 256 \ --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 ICA" --digest-algorithm sm3 --param-x509-parent-name default-x509ca-sm2 --visibility public } function create_default_x509_ee_sm2 { echo "start to create default x509 sm2 EE certificate identified with default-x509" - RUST_LOG=info ./target/debug/control-admin --config ./config/server.toml generate-keys --name default-x509ee-sm2 --description "used for test purpose only" --key-type x509ee --email tommylikehu@gmail.com --param-key-type sm2 --param-key-size 2048 \ + RUST_LOG=info ./target/debug/control-admin --config ./config/server.toml generate-keys --name default-x509ee-sm2 --description "used for test purpose only" --key-type x509ee --email tommylikehu@gmail.com --param-key-type sm2 --param-key-size 256 \ --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 EE" --digest-algorithm sm3 --param-x509-parent-name default-x509ica-sm2 --visibility public } diff --git a/src/application/datakey.rs b/src/application/datakey.rs index 19f9106..52b8ad3 100644 --- a/src/application/datakey.rs +++ b/src/application/datakey.rs @@ -21,6 +21,7 @@ use crate::domain::datakey::entity::{ use crate::domain::datakey::repository::Repository as DatakeyRepository; use crate::domain::sign_service::SignBackend; use crate::util::error::{Error, Result}; +use crate::util::options; use async_trait::async_trait; use tokio::time::{self}; @@ -69,8 +70,8 @@ pub trait KeyService: Send + Sync { options: &HashMap, data: Vec, ) -> Result>; - async fn get_by_type_and_name(&self, key_type: String, key_name: String) -> Result; - + async fn get_by_type_and_name(&self, key_type: Option, key_name: String) -> Result; + async fn get_timestamp_key_by_type_and_name(&self, key_type: Option, key_name: String) -> Result; //method below used for maintenance fn start_key_rotate_loop(&self, cancel_token: CancellationToken) -> Result<()>; @@ -90,6 +91,7 @@ where repository: R, sign_service: Arc>>, container: TimedFixedSizeCache, + timestamp_container: TimedFixedSizeCache, } impl DBKeyService @@ -102,6 +104,7 @@ where repository, sign_service: Arc::new(RwLock::new(sign_service)), container: TimedFixedSizeCache::new(Some(100), None, None, None), + timestamp_container: TimedFixedSizeCache::new(Some(100), None, None, None), } } @@ -538,15 +541,21 @@ where options: &HashMap, data: Vec, ) -> Result> { - let datakey = self.get_by_type_and_name(key_type, key_name).await?; + let mut timekey : Option = None; + if let Some(timestamp_key) = options.get(options::TIMESTAMP_KEY) { + if !timestamp_key.is_empty() { + timekey = Some(self.get_timestamp_key_by_type_and_name(None, timestamp_key.to_string()).await?); + } + } + let datakey = self.get_by_type_and_name(Some(key_type), key_name).await?; self.sign_service .read() .await - .sign(&datakey, data, options.clone()) + .sign(&datakey, timekey, data, options.clone()) .await } - async fn get_by_type_and_name(&self, key_type: String, key_name: String) -> Result { + async fn get_by_type_and_name(&self, key_type: Option, key_name: String) -> Result { if let Some(datakey) = self.container.get_sign_datakey(&key_name).await { return Ok(datakey); } @@ -560,6 +569,20 @@ where Ok(key) } + async fn get_timestamp_key_by_type_and_name(&self, key_type: Option, key_name: String) -> Result { + if let Some(datakey) = self.timestamp_container.get_sign_datakey(&key_name).await { + return Ok(datakey); + } + let key = self + .repository + .get_enabled_key_by_type_and_name_with_parent_key(key_type, key_name.clone()) + .await?; + self.timestamp_container + .update_sign_datakey(&key_name, key.clone()) + .await?; + Ok(key) + } + fn start_key_rotate_loop(&self, cancel_token: CancellationToken) -> Result<()> { let sign_service = self.sign_service.clone(); let mut interval = time::interval(Duration::seconds(60 * 60 * 2).to_std()?); diff --git a/src/client/cmd/add.rs b/src/client/cmd/add.rs index a6ed07a..f911427 100644 --- a/src/client/cmd/add.rs +++ b/src/client/cmd/add.rs @@ -23,6 +23,7 @@ use regex::Regex; use std::collections::HashMap; use std::path::PathBuf; use std::sync::{atomic::AtomicBool, Arc, RwLock}; +use std::fs; use tokio::runtime; use crate::client::file_handler::factory::FileHandlerFactory; @@ -88,6 +89,12 @@ pub struct CommandAdd { help = "force create rpm v3 signature, default is false. only support when file type is rpm" )] rpm_v3: bool, + #[arg(long, default_value = "")] + #[arg(help = "specify the path to the CRL file, which is used in CMS signing. only PEM format is supported")] + crl: String, + #[arg(long, default_value = "")] + #[arg(help = "Specify the timestamp key name, which is used in CMS signing")] + timestamp_key: String, } #[derive(Clone)] @@ -106,6 +113,8 @@ pub struct CommandAddHandler { sign_type: SignType, token: Option, rpm_v3: bool, + timestamp_key: String, + crl: String, sign_options: Option>, } @@ -120,6 +129,8 @@ impl CommandAddHandler { options::RPM_V3_SIGNATURE.to_string(), self.rpm_v3.to_string(), ), + (options::TIMESTAMP_KEY.to_string(), self.timestamp_key.to_string()), + (options::CRL.to_string(), self.crl.to_string()), ]) } else { self.sign_options.clone().unwrap() @@ -223,6 +234,17 @@ impl SignCommand for CommandAddHandler { token = Some(t); } } + + let mut crl: String = String::new(); + if !command.crl.is_empty() { + crl = match fs::read_to_string(command.crl.clone()) { + Ok(crl) => crl, + Err(e) => { + return Err(error::Error::FileFoundError(format!( + "crl file: {} read failed {}", command.crl, e))); + }, + }; + } Ok(CommandAddHandler { worker_threads, buffer_size: config.read()?.get_string("buffer_size")?.parse()?, @@ -238,6 +260,8 @@ impl SignCommand for CommandAddHandler { sign_type: command.sign_type, token, rpm_v3: command.rpm_v3, + timestamp_key: command.timestamp_key, + crl: crl, sign_options: None, }) } diff --git a/src/domain/datakey/repository.rs b/src/domain/datakey/repository.rs index 30f830c..28afabc 100644 --- a/src/domain/datakey/repository.rs +++ b/src/domain/datakey/repository.rs @@ -42,7 +42,7 @@ pub trait Repository: Send + Sync { async fn update_key_data(&self, data_key: DataKey) -> Result<()>; async fn get_enabled_key_by_type_and_name_with_parent_key( &self, - key_type: String, + key_type: Option, name: String, ) -> Result; async fn request_delete_key( diff --git a/src/domain/sign_plugin.rs b/src/domain/sign_plugin.rs index dd2c901..4b907a7 100644 --- a/src/domain/sign_plugin.rs +++ b/src/domain/sign_plugin.rs @@ -20,7 +20,7 @@ use chrono::{DateTime, Utc}; use std::collections::HashMap; pub trait SignPlugins: Send + Sync { - fn new(db: SecDataKey) -> Result + fn new(db: SecDataKey, timestamp_key: Option) -> Result where Self: Sized; fn validate_and_update(key: &mut DataKey) -> Result<()> diff --git a/src/domain/sign_service.rs b/src/domain/sign_service.rs index ab7ed34..b3a59c7 100644 --- a/src/domain/sign_service.rs +++ b/src/domain/sign_service.rs @@ -49,10 +49,12 @@ pub trait SignBackend: Send + Sync { async fn sign( &self, data_key: &DataKey, + timestamp_key: Option, content: Vec, options: HashMap, ) -> Result>; async fn decode_public_keys(&self, data_key: &mut DataKey) -> Result<()>; + async fn decode_private_keys(&self, data_key: &mut DataKey) -> Result<()>; async fn generate_crl_content( &self, data_key: &DataKey, diff --git a/src/infra/database/model/datakey/repository.rs b/src/infra/database/model/datakey/repository.rs index 6f3f299..8f18209 100644 --- a/src/infra/database/model/datakey/repository.rs +++ b/src/infra/database/model/datakey/repository.rs @@ -627,9 +627,17 @@ impl<'a> Repository for DataKeyRepository<'a> { async fn get_enabled_key_by_type_and_name_with_parent_key( &self, - key_type: String, + key_type: Option, name: String, ) -> Result { + + let mut cond = Condition::all() + .add(datakey_dto::Column::Name.eq(name)) + .add(datakey_dto::Column::KeyState.eq(KeyState::Enabled.to_string())); + + if let Some(t) = key_type { + cond = cond.add(datakey_dto::Column::KeyType.eq(t)); + } match datakey_dto::Entity::find() .select_only() .columns(datakey_dto::Column::iter().filter(|col| { @@ -641,14 +649,10 @@ impl<'a> Repository for DataKeyRepository<'a> { | datakey_dto::Column::X509CrlUpdateAt ) })) - .filter( - Condition::all() - .add(datakey_dto::Column::Name.eq(name)) - .add(datakey_dto::Column::KeyType.eq(key_type)) - .add(datakey_dto::Column::KeyState.eq(KeyState::Enabled.to_string())), - ) + .filter(cond) .one(self.db_connection) .await? + { None => Err(Error::NotFoundError), Some(datakey) => { @@ -1503,7 +1507,7 @@ mod tests { assert_eq!( datakey_repository .get_enabled_key_by_type_and_name_with_parent_key( - "openpgp".to_string(), + Some("openpgp".to_string()), "fake_name".to_string() ) .await?, diff --git a/src/infra/sign_backend/memory/backend.rs b/src/infra/sign_backend/memory/backend.rs index 8f914b5..7779dd8 100644 --- a/src/infra/sign_backend/memory/backend.rs +++ b/src/infra/sign_backend/memory/backend.rs @@ -106,7 +106,7 @@ impl SignBackend for MemorySignBackend { async fn generate_keys(&self, data_key: &mut DataKey) -> Result<()> { let sec_key = SecDataKey::load(data_key, &self.engine).await?; - let content = Signers::load_from_data_key(&data_key.key_type, sec_key)? + let content = Signers::load_from_data_key(&data_key.key_type, sec_key, None)? .generate_keys(&data_key.key_type, &self.infra_configs)?; data_key.private_key = self.engine.encode(content.private_key).await?; data_key.public_key = self.engine.encode(content.public_key).await?; @@ -123,11 +123,17 @@ impl SignBackend for MemorySignBackend { async fn sign( &self, data_key: &DataKey, + timestamp_key: Option, content: Vec, options: HashMap, ) -> Result> { + info!("staring!!!"); let sec_key = SecDataKey::load(data_key, &self.engine).await?; - Signers::load_from_data_key(&data_key.key_type, sec_key)?.sign(content, options) + let mut timestamp_sec_key: Option = None; + if let Some(ref key) = timestamp_key { + timestamp_sec_key = Some(SecDataKey::load(&key, &self.engine).await?); + } + Signers::load_from_data_key(&data_key.key_type, sec_key, timestamp_sec_key)?.sign(content, options) } async fn decode_public_keys(&self, data_key: &mut DataKey) -> Result<()> { @@ -136,6 +142,12 @@ impl SignBackend for MemorySignBackend { Ok(()) } + async fn decode_private_keys(&self, data_key: &mut DataKey) -> Result<()> { + data_key.private_key = self.engine.decode(data_key.private_key.clone()).await?; + data_key.certificate = self.engine.decode(data_key.certificate.clone()).await?; + Ok(()) + } + async fn generate_crl_content( &self, data_key: &DataKey, @@ -144,7 +156,7 @@ impl SignBackend for MemorySignBackend { next_update: DateTime, ) -> Result> { let sec_key = SecDataKey::load(data_key, &self.engine).await?; - Signers::load_from_data_key(&data_key.key_type, sec_key)?.generate_crl_content( + Signers::load_from_data_key(&data_key.key_type, sec_key, None)?.generate_crl_content( revoked_keys, last_update, next_update, diff --git a/src/infra/sign_plugin/cms.rs b/src/infra/sign_plugin/cms.rs new file mode 100644 index 0000000..72f55fd --- /dev/null +++ b/src/infra/sign_plugin/cms.rs @@ -0,0 +1,851 @@ +use crate::util::error::{Result, Error}; +use crate::util::attributes; +use crate::util::attributes::PkeyHashAlgo; +use crate::util::options; +use foreign_types_shared::{ForeignType, ForeignTypeRef}; +use openssl_sys::{ + ASN1_INTEGER_free, ASN1_OBJECT_free, BIO_free_all, BIO_get_mem_data, BIO_new, + BIO_new_mem_buf, BIO_s_mem, CMS_ContentInfo, CMS_ContentInfo_free, CMS_sign, EVP_MD_type, + EVP_PKEY_CTX_set_rsa_padding, OBJ_nid2obj, OBJ_txt2obj, X509_ALGOR_free, + ASN1_BOOLEAN, ASN1_GENERALIZEDTIME, ASN1_INTEGER, ASN1_OBJECT, + ASN1_OCTET_STRING, BIO, CMS_BINARY, CMS_DETACHED, CMS_KEY_PARAM, CMS_NOSMIMECAP, CMS_PARTIAL, + EVP_MD, EVP_PKEY, EVP_PKEY_CTX, GENERAL_NAME, RSA_PKCS1_PSS_PADDING, V_ASN1_NULL, + V_ASN1_SEQUENCE, X509, X509_ALGOR, X509_CRL, ERR_get_error, +}; +use openssl::x509; +use openssl::pkey; +use rand::rngs::OsRng; +use rand::Rng; +use std::collections::HashMap; +use std::ffi::CString; +use std::ffi::{c_char, c_int, c_uchar, c_uint, c_void}; +use std::ptr; +use std::slice; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[repr(C)] +pub struct CMS_SignerInfo { + cert: *mut X509, + pkey: *mut EVP_PKEY, + md: *const EVP_MD, + sig: *mut u8, + siglen: i32, + } + +#[repr(C)] +pub struct TS_MSG_IMPRINT { + algo: *mut X509_ALGOR, + hash: *mut ASN1_OCTET_STRING, +} + +#[repr(C)] +pub struct TS_REQ { + version: *mut ASN1_INTEGER, + tst: *mut TS_MSG_IMPRINT, + policy_id: *mut ASN1_OBJECT, + nonce: *mut ASN1_INTEGER, + cert_req: *mut ASN1_BOOLEAN, + extensions: *mut c_void, +} + +#[repr(C)] +pub struct TS_TST_INFO { + version: *mut ASN1_INTEGER, + policy_id: *mut ASN1_OBJECT, + tst: *mut TS_MSG_IMPRINT, + serial: *mut ASN1_INTEGER, + time: *mut ASN1_GENERALIZEDTIME, + accuracy: *mut c_void, + ordering: *mut ASN1_BOOLEAN, + nonce: *mut ASN1_INTEGER, + tsa: *mut GENERAL_NAME, + extensions: *mut c_void, +} + +extern "C" { + pub fn ASN1_INTEGER_new() -> *mut ASN1_INTEGER; + pub fn ASN1_INTEGER_set_uint64(a: *mut ASN1_INTEGER, r: u64) -> i32; + pub fn ASN1_GENERALIZEDTIME_new() -> *mut ASN1_GENERALIZEDTIME; + pub fn ASN1_GENERALIZEDTIME_free(time: *mut ASN1_GENERALIZEDTIME); + pub fn ASN1_GENERALIZEDTIME_set( + s: *mut ASN1_GENERALIZEDTIME, + t: i64, + ) -> *mut ASN1_GENERALIZEDTIME; + pub fn ASN1_STRING_data(octet_str: *mut c_void) -> *const u8; + pub fn ASN1_STRING_length(octet_str: *mut c_void) -> i32; + pub fn CMS_final_digest( + cms: *mut CMS_ContentInfo, + md: *const c_uchar, + mdlen: c_uint, + dcont: *mut BIO, + flags: c_uint, + ) -> c_int; + pub fn CMS_final( + cms: *mut CMS_ContentInfo, + bio: *mut BIO, + dcont: *mut BIO, + flags: c_uint, + ) -> c_int; + pub fn CMS_add1_signer( + cms: *mut CMS_ContentInfo, + cert: *mut X509, + pkey: *mut EVP_PKEY, + md: *const EVP_MD, + flags: u32, + ) -> *mut CMS_SignerInfo; + pub fn CMS_unsigned_add1_attr_by_NID( + si: *mut CMS_SignerInfo, + nid: i32, + attr_type: i32, + bytes: *const c_void, + len: i32, + ) -> i32; + pub fn CMS_get0_SignerInfos(cms: *mut CMS_ContentInfo) -> *mut c_void; + pub fn CMS_add1_crl(cms: *mut CMS_ContentInfo, crl: *mut X509_CRL) -> c_int; + pub fn CMS_SignerInfo_get0_signature(si: *mut c_void) -> *mut c_void; + pub fn CMS_SignerInfo_get0_pkey_ctx(si: *mut CMS_SignerInfo) -> *mut EVP_PKEY_CTX; + pub fn CMS_set1_eContentType(cms: *mut CMS_ContentInfo, oid: *mut ASN1_OBJECT) -> i32; + pub fn i2d_CMS_bio(out: *mut BIO, cms: *mut CMS_ContentInfo) -> c_int; + pub fn i2d_CMS_ContentInfo(cms: *mut CMS_ContentInfo, out: *mut *mut u8) -> c_int; + pub fn OPENSSL_sk_num(stack: *const c_void) -> i32; + pub fn OPENSSL_sk_value(stack: *const c_void, idx: i32) -> *mut c_void; + pub fn free(ptr: *mut c_void); + pub fn EVP_PKEY_is_a(key: *const EVP_PKEY, name: *const c_char) -> c_int; + + pub fn TS_REQ_new() -> *mut TS_REQ; + pub fn TS_REQ_free(req: *mut TS_REQ); + pub fn TS_REQ_set_version(req: *mut TS_REQ, version: i32) -> i32; + pub fn TS_MSG_IMPRINT_new() -> *mut TS_MSG_IMPRINT; + pub fn TS_MSG_IMPRINT_free(imprint: *mut TS_MSG_IMPRINT); + pub fn TS_MSG_IMPRINT_dup(imprint: *mut TS_MSG_IMPRINT) -> *mut TS_MSG_IMPRINT; + pub fn TS_MSG_IMPRINT_set_algo(imprint: *mut TS_MSG_IMPRINT, algo: *mut X509_ALGOR) -> i32; + pub fn TS_MSG_IMPRINT_set_msg(imprint: *mut TS_MSG_IMPRINT, msg: *const u8, len: i32) -> i32; + pub fn TS_REQ_set_msg_imprint(req: *mut TS_REQ, imprint: *mut TS_MSG_IMPRINT) -> i32; + pub fn TS_REQ_get_msg_imprint(req: *mut TS_REQ) -> *mut TS_MSG_IMPRINT; + pub fn TS_REQ_set_cert_req(req: *mut TS_REQ, cert_req: i32) -> i32; + + pub fn TS_TST_INFO_free(info: *mut TS_TST_INFO); + pub fn TS_TST_INFO_new() -> *mut TS_TST_INFO; + pub fn TS_TST_INFO_set_version(tst: *mut TS_TST_INFO, version: i32) -> i32; + pub fn TS_TST_INFO_set_policy_id(tst: *mut TS_TST_INFO, policy: *mut ASN1_OBJECT) -> i32; + pub fn TS_TST_INFO_set_msg_imprint(tst: *mut TS_TST_INFO, msg: *mut TS_MSG_IMPRINT) -> i32; + pub fn TS_TST_INFO_set_serial(tst: *mut TS_TST_INFO, serial: *mut ASN1_INTEGER) -> i32; + pub fn TS_TST_INFO_set_time(tst: *mut TS_TST_INFO, gen_time: *mut ASN1_GENERALIZEDTIME) -> i32; + pub fn TS_TST_INFO_set_ordering(tst: *mut TS_TST_INFO, ordering: i32) -> i32; + pub fn TS_TST_INFO_set_tsa(tst: *mut TS_TST_INFO, tsa_name: *mut GENERAL_NAME) -> i32; + pub fn i2d_TS_TST_INFO(tst: *mut TS_TST_INFO, out: *mut *mut u8) -> i32; + + pub fn X509_ALGOR_new() -> *mut X509_ALGOR; + pub fn X509_ALGOR_set0( + alg: *mut X509_ALGOR, + obj: *mut ASN1_OBJECT, + algo_type: i32, + val: *mut c_void, + ) -> i32; +} + +unsafe fn drain_err_stack() { + loop { + let code = ERR_get_error(); + if code == 0 { break; } + info!("openssl code=0x{:08x}", code); + } +} + +fn generate_cms_with_hash( + cert: &x509::X509Ref, + pkey: &pkey::PKey, + digest: &[u8], + attributes: HashMap, +) -> Result<*mut CMS_ContentInfo> { + struct BioGuard(*mut BIO); + impl Drop for BioGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { BIO_free_all(self.0) }; + } + } + } + + struct CmsGuard(*mut CMS_ContentInfo); + impl Drop for CmsGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { CMS_ContentInfo_free(self.0) }; + } + } + } + unsafe { + // step1. generate cms structure + let data_bio = BIO_new_mem_buf( + digest.as_ptr() as *const c_void, + digest.len() + .try_into() + .map_err(|_| Error::InvalidArgumentError("digest too large".to_string()))?, + ); + let _data_bio_guard = BioGuard(data_bio); + let flags = CMS_DETACHED | CMS_BINARY | CMS_PARTIAL | CMS_NOSMIMECAP | CMS_KEY_PARAM; + let cms: *mut CMS_ContentInfo = CMS_sign( + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + data_bio, + flags, + ); + if cms.is_null() { + return Err(Error::InvalidArgumentError( + "CMS_sign (partial) failed".to_string(), + )); + } + let mut cms_guard = CmsGuard(cms); + // step2. specify the hash algorithm used, certificates, CRL and encryption + let digest_algo = attributes + .get(attributes::DIGEST_ALGO) + .ok_or_else(|| { + Error::InvalidArgumentError("missing digest algorithm attribute".to_string()) + })?; + let md = PkeyHashAlgo::get_openssl_c_digest_algo(digest_algo); + let si = CMS_add1_signer(cms, cert.as_ptr(), pkey.as_ptr(), md, flags); + if si.is_null() { + return Err(Error::InvalidArgumentError( + "CMS_add1_signer failed".to_string(), + )); + } + if let Some(crl_data) = attributes.get(options::CRL).filter(|s| !s.is_empty()) { + let crl = x509::X509Crl::from_pem(crl_data.as_bytes())?; + CMS_add1_crl(cms, crl.as_ptr()); + } + let pk_ctx = CMS_SignerInfo_get0_pkey_ctx(si); + if pk_ctx.is_null() { + return Err(Error::InvalidArgumentError( + "CMS_SignerInfo_get0_pkey_ctx failed".to_string(), + )); + } + EVP_PKEY_CTX_set_rsa_padding(pk_ctx, RSA_PKCS1_PSS_PADDING); + // step3. generate signature + let ret = CMS_final_digest( + cms, + digest.as_ptr(), + digest.len() as u32, + ptr::null_mut(), + flags, + ); + if ret != 1 { + drain_err_stack(); + return Err(Error::InvalidArgumentError( + "CMS_final_digest failed".to_string(), + )); + } + let cms_ptr = cms_guard.0; + cms_guard.0 = ptr::null_mut(); + Ok(cms_ptr) + } +} + +fn generate_timestamp_req( + cms: *mut CMS_ContentInfo, + attributes: HashMap, +) -> Result<*mut TS_REQ> { + struct TsGuard(*mut TS_REQ); + impl Drop for TsGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { TS_REQ_free(self.0) }; + } + } + } + + struct MsgImprintGuard(*mut TS_MSG_IMPRINT); + impl Drop for MsgImprintGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { TS_MSG_IMPRINT_free(self.0) }; + } + } + } + + struct AlgoGuard(*mut X509_ALGOR); + impl Drop for AlgoGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { X509_ALGOR_free(self.0) }; + } + } + } + + unsafe { + // step1. get signature from cms + let signatures = CMS_get0_SignerInfos(cms); + if signatures.is_null() { + return Err(Error::RemoteSignError( + "CMS_get0_SignerInfos failed".to_string(), + )); + } + + let count = OPENSSL_sk_num(signatures); + if count <= 0 { + return Err(Error::RemoteSignError( + "no signer in CMS".to_string(), + )); + } + + let si = OPENSSL_sk_value(signatures, 0); + if si.is_null() { + return Err(Error::RemoteSignError( + "OPENSSL_sk_value returned null signer".to_string(), + )); + } + + let signature = CMS_SignerInfo_get0_signature(si); + if signature.is_null() { + return Err(Error::RemoteSignError( + "CMS_SignerInfo_get0_signature failed".to_string(), + )); + } + let data_len = ASN1_STRING_length(signature); + let data_ptr = ASN1_STRING_data(signature); + if data_len <= 0 || data_ptr.is_null() { + return Err(Error::RemoteSignError( + "invalid signature ASN1 string".to_string(), + )); + } + + // step2. generate ts_req from signature + let ts_req = TS_REQ_new(); + let mut ts_guard = TsGuard(ts_req); + + if TS_REQ_set_version(ts_req, 1) != 1 { + return Err(Error::RemoteSignError( + "TS_REQ_set_version failed".to_string(), + )); + } + + let msg_imprint = TS_MSG_IMPRINT_new(); + let _msg_guard = MsgImprintGuard(msg_imprint); + let algo = X509_ALGOR_new(); + let _algo_guard = AlgoGuard(algo); + + let digest_algo = attributes + .get(attributes::DIGEST_ALGO) + .ok_or_else(|| { + Error::RemoteSignError("missing digest algorithm attribute".to_string()) + })?; + + let md = PkeyHashAlgo::get_openssl_c_digest_algo(digest_algo); + let nid = EVP_MD_type(md); + let obj = OBJ_nid2obj(nid); + X509_ALGOR_set0(algo, obj, V_ASN1_NULL, ptr::null_mut()); + + if TS_MSG_IMPRINT_set_algo(msg_imprint, algo) != 1 { + return Err(Error::RemoteSignError( + "TS_MSG_IMPRINT_set_algo failed".to_string(), + )); + } + + if TS_MSG_IMPRINT_set_msg( + msg_imprint, + data_ptr, + data_len, + ) != 1 + { + return Err(Error::RemoteSignError( + "TS_MSG_IMPRINT_set_msg failed".to_string(), + )); + } + + if TS_REQ_set_msg_imprint(ts_req, msg_imprint) != 1 { + return Err(Error::RemoteSignError( + "TS_REQ_set_msg_imprint failed".to_string(), + )); + } + + let ts_ptr = ts_guard.0; + ts_guard.0 = ptr::null_mut(); + Ok(ts_ptr) + } +} + +fn generate_timestamp_tst( + req: *mut TS_REQ, + tsa_cert: &x509::X509Ref, +) -> Result<*mut TS_TST_INFO> { + let _ = tsa_cert; + struct TstGuard(*mut TS_TST_INFO); + impl Drop for TstGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { TS_TST_INFO_free(self.0) }; + } + } + } + + struct Asn1IntGuard(*mut ASN1_INTEGER); + impl Drop for Asn1IntGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { ASN1_INTEGER_free(self.0) }; + } + } + } + + struct GenTimeGuard(*mut ASN1_GENERALIZEDTIME); + impl Drop for GenTimeGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { ASN1_GENERALIZEDTIME_free(self.0) }; + } + } + } + + struct Asn1ObjGuard(*mut ASN1_OBJECT); + impl Drop for Asn1ObjGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { ASN1_OBJECT_free(self.0) }; + } + } + } + + if req.is_null() { + return Err(Error::RemoteSignError("TS_REQ is null".to_string())); + } + + unsafe { + let tst = TS_TST_INFO_new(); + let mut tst_guard = TstGuard(tst); + if TS_TST_INFO_set_version(tst, 1) != 1 { + return Err(Error::RemoteSignError( + "TS_TST_INFO_set_version failed".to_string(), + )); + } + + let policy_str = CString::new("1.2.3.4.1") + .map_err(|_| Error::RemoteSignError("invalid policy OID".to_string()))?; + let policy = OBJ_txt2obj(policy_str.as_ptr(), 1); + let _policy_guard = Asn1ObjGuard(policy); + + if TS_TST_INFO_set_policy_id(tst, policy) != 1 { + return Err(Error::RemoteSignError( + "TS_TST_INFO_set_policy_id failed".to_string(), + )); + } + + + let msg_imprint = TS_REQ_get_msg_imprint(req); + if msg_imprint.is_null() { + return Err(Error::RemoteSignError( + "TS_REQ_get_msg_imprint failed".to_string(), + )); + } + + let msg_imprint_copy = TS_MSG_IMPRINT_dup(msg_imprint); + if msg_imprint_copy.is_null() { + return Err(Error::RemoteSignError( + "TS_MSG_IMPRINT_dup failed".to_string(), + )); + } + + if TS_TST_INFO_set_msg_imprint(tst, msg_imprint_copy) != 1 { + return Err(Error::RemoteSignError( + "TS_TST_INFO_set_msg_imprint failed".to_string(), + )); + } + + let serial = ASN1_INTEGER_new(); + let _serial_guard = Asn1IntGuard(serial); + + let random = OsRng.gen(); + + if ASN1_INTEGER_set_uint64(serial, random) != 1 { + return Err(Error::RemoteSignError( + "ASN1_INTEGER_set_uint64 failed".to_string(), + )); + } + + if TS_TST_INFO_set_serial(tst, serial) != 1 { + return Err(Error::RemoteSignError( + "TS_TST_INFO_set_serial failed".to_string(), + )); + } + + let gen_time = ASN1_GENERALIZEDTIME_new(); + let _gen_time_guard = GenTimeGuard(gen_time); + + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|_| { + Error::RemoteSignError("system time before UNIX_EPOCH".to_string()) + })? + .as_secs() as i64; + + if ASN1_GENERALIZEDTIME_set(gen_time, now).is_null() { + return Err(Error::RemoteSignError( + "ASN1_GENERALIZEDTIME_set failed".to_string(), + )); + } + + if TS_TST_INFO_set_time(tst, gen_time) != 1 { + return Err(Error::RemoteSignError( + "TS_TST_INFO_set_time failed".to_string(), + )); + } + + if TS_TST_INFO_set_ordering(tst, 1) != 1 { + return Err(Error::RemoteSignError( + "TS_TST_INFO_set_ordering failed".to_string(), + )); + } + + let tst_ptr = tst_guard.0; + tst_guard.0 = ptr::null_mut(); + Ok(tst_ptr) + } +} + +fn generate_timestamp_signature( + tsa_cert: &x509::X509Ref, + tsa_key: &pkey::PKey, + tst_info: *mut TS_TST_INFO, + attributes: HashMap, +) -> Result<*mut CMS_ContentInfo> { + struct BioGuard(*mut BIO); + impl Drop for BioGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { BIO_free_all(self.0) }; + } + } + } + + struct CmsGuard(*mut CMS_ContentInfo); + impl Drop for CmsGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { CMS_ContentInfo_free(self.0) }; + } + } + } + + struct Asn1ObjGuard(*mut ASN1_OBJECT); + impl Drop for Asn1ObjGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { ASN1_OBJECT_free(self.0) }; + } + } + } + + if tst_info.is_null() { + return Err(Error::InvalidArgumentError( + "TS_TST_INFO is null".to_string(), + )); + } + + unsafe { + let len: c_int = i2d_TS_TST_INFO(tst_info, ptr::null_mut()); + if len <= 0 { + return Err(Error::InvalidArgumentError( + "i2d_TS_TST_INFO (size calc) failed".to_string(), + )); + } + + let len_usize: usize = len + .try_into() + .map_err(|_| Error::InvalidArgumentError("TST_INFO too large".to_string()))?; + + let mut tst_der = vec![0u8; len_usize]; + + let mut p: *mut u8 = tst_der.as_mut_ptr(); + let written = i2d_TS_TST_INFO(tst_info, &mut p); + if written != len { + return Err(Error::InvalidArgumentError( + "i2d_TS_TST_INFO (encode) failed".to_string(), + )); + } + + let content = BIO_new_mem_buf( + tst_der.as_ptr() as *const c_void, + tst_der + .len() + .try_into() + .map_err(|_| Error::InvalidArgumentError("DER too large".to_string()))?, + ); + if content.is_null() { + return Err(Error::InvalidArgumentError( + "BIO_new_mem_buf failed".to_string(), + )); + } + let _bio_guard = BioGuard(content); + + let flags: c_uint = + (CMS_BINARY | CMS_PARTIAL | CMS_KEY_PARAM | CMS_NOSMIMECAP) as c_uint; + + let cms = CMS_sign( + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + flags, + ); + if cms.is_null() { + return Err(Error::InvalidArgumentError( + "CMS_sign (partial) failed".to_string(), + )); + } + let mut cms_guard = CmsGuard(cms); + let digest_algo = attributes + .get(attributes::DIGEST_ALGO) + .ok_or_else(|| { + Error::InvalidArgumentError( + "missing digest algorithm attribute".to_string(), + ) + })?; + + let md = PkeyHashAlgo::get_openssl_c_digest_algo(digest_algo); + + let si: *mut CMS_SignerInfo = + CMS_add1_signer(cms, tsa_cert.as_ptr(), tsa_key.as_ptr(), md, flags); + if si.is_null() { + return Err(Error::InvalidArgumentError( + "CMS_add1_signer failed".to_string(), + )); + } + + let pctx: *mut EVP_PKEY_CTX = CMS_SignerInfo_get0_pkey_ctx(si); + if pctx.is_null() { + return Err(Error::InvalidArgumentError( + "CMS_SignerInfo_get0_pkey_ctx failed".to_string(), + )); + } + EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING); + + let oid_str = CString::new("1.2.840.113549.1.9.16.1.4") + .map_err(|_| Error::InvalidArgumentError("invalid OID string".to_string()))?; + let tst_oid: *mut ASN1_OBJECT = OBJ_txt2obj(oid_str.as_ptr(), 1); + if tst_oid.is_null() { + return Err(Error::InvalidArgumentError( + "OBJ_txt2obj failed".to_string(), + )); + } + let _oid_guard = Asn1ObjGuard(tst_oid); + + if CMS_set1_eContentType(cms, tst_oid) != 1 { + return Err(Error::InvalidArgumentError( + "CMS_set1_eContentType failed".to_string(), + )); + } + + if CMS_final(cms, content, ptr::null_mut(), flags) != 1 { + return Err(Error::InvalidArgumentError( + "CMS_final failed".to_string(), + )); + } + + let cms_ptr = cms_guard.0; + cms_guard.0 = ptr::null_mut(); + Ok(cms_ptr) + } +} + +fn attach_timestamp_to_cms( + cms: *mut CMS_ContentInfo, + ts_token: *mut CMS_ContentInfo, +) -> Result<()> { + struct DerGuard(*mut u8); + impl Drop for DerGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { free(self.0 as *mut c_void) }; + } + } + } + + unsafe { + let signers = CMS_get0_SignerInfos(cms); + let signer_info = OPENSSL_sk_value(signers, 0); + if signer_info.is_null() { + return Err(Error::InvalidArgumentError( + "Failed to get SignerInfo from CMS.".to_string(), + )); + } + + let mut ts_der: *mut u8 = ptr::null_mut(); + let ts_len = i2d_CMS_ContentInfo(ts_token, &mut ts_der); + if ts_len <= 0 || ts_der.is_null() { + return Err(Error::InvalidArgumentError( + "Failed to convert TS token to DER format.".to_string(), + )); + } + + let _der_guard = DerGuard(ts_der); + let nid = 225; + let result = CMS_unsigned_add1_attr_by_NID( + signer_info as *mut CMS_SignerInfo, + nid, + V_ASN1_SEQUENCE, + ts_der as *mut _, + ts_len, + ); + + if result != 1 { + return Err(Error::InvalidArgumentError( + "Failed to add timestamp token to CMS.".to_string(), + )); + } + Ok(()) + } +} + + +pub struct CmsContext<'a> { + certificate: &'a x509::X509Ref, + private_key: &'a pkey::PKey, + content: &'a [u8], + options: &'a HashMap, + pub cms: *mut CMS_ContentInfo, + + // timpstamp + ts_req: *mut TS_REQ, + tst_info: *mut TS_TST_INFO, + timestamp: *mut CMS_ContentInfo, + timestamp_options: &'a HashMap, + tsa_cert_pem: &'a [u8], + tsa_key_pem: &'a [u8], + tsa_cert: Option, + tsa_key: Option>, +} + +impl<'a> CmsContext<'a> { + pub fn new( + certificate: &'a x509::X509Ref, + private_key: &'a pkey::PKey, + content: &'a [u8], + options: &'a HashMap, + timestamp_options: &'a HashMap, + tsa_cert_pem: &'a [u8], + tsa_key_pem: &'a [u8], + ) -> Self { + Self { + certificate, + private_key, + content, + options, + cms: std::ptr::null_mut(), + ts_req: std::ptr::null_mut(), + tst_info: std::ptr::null_mut(), + timestamp: std::ptr::null_mut(), + timestamp_options, + tsa_cert_pem, + tsa_key_pem, + tsa_cert: None, + tsa_key: None, + } + } +} + +pub type Step<'a> = &'a dyn Fn(&mut CmsContext) -> Result<()>; + +pub struct CmsPlugin; +impl CmsPlugin { + pub fn run_steps(ctx: &mut CmsContext, steps: &[Step]) -> Result<()> { + for (_idx, step) in steps.iter().enumerate() { + step(ctx)?; + } + Ok(()) + } + + pub fn step_generate_cms(ctx: &mut CmsContext) -> Result<()> { + let cms = generate_cms_with_hash( + ctx.certificate, + ctx.private_key, + ctx.content, + ctx.options.clone(), + )?; + ctx.cms = cms; + Ok(()) + } + + pub fn step_generate_ts_req(ctx: &mut CmsContext) -> Result<()> { + let ts_req = generate_timestamp_req(ctx.cms, ctx.options.clone())?; + ctx.ts_req = ts_req; + Ok(()) + } + + pub fn step_load_tsa_cert_key(ctx: &mut CmsContext) -> Result<()> { + let cert = x509::X509::from_pem(ctx.tsa_cert_pem).map_err(|_e| { + Error::RemoteSignError("load tsa certificate failed".to_string()) + })?; + let key = pkey::PKey::private_key_from_pem(ctx.tsa_key_pem).map_err(|_e| { + Error::RemoteSignError("load tsa private key failed".to_string()) + })?; + + ctx.tsa_cert = Some(cert); + ctx.tsa_key = Some(key); + Ok(()) + } + + pub fn step_generate_tst_info(ctx: &mut CmsContext) -> Result<()> { + let tsa_cert = ctx.tsa_cert.as_ref().ok_or_else(|| { + Error::RemoteSignError("tsa_cert not loaded".to_string()) + })?; + let tst_info = generate_timestamp_tst(ctx.ts_req, tsa_cert)?; + ctx.tst_info = tst_info; + Ok(()) + } + + pub fn step_generate_timestamp_token(ctx: &mut CmsContext) -> Result<()> { + let tsa_cert = ctx.tsa_cert.as_ref().ok_or_else(|| { + Error::RemoteSignError("tsa_cert not loaded".to_string()) + })?; + let tsa_key = ctx.tsa_key.as_ref().ok_or_else(|| { + Error::RemoteSignError("tsa_key not loaded".to_string()) + })?; + + let timestamp = generate_timestamp_signature( + tsa_cert, + tsa_key, + ctx.tst_info, + ctx.timestamp_options.clone(), + )?; + ctx.timestamp = timestamp; + Ok(()) + } + + pub fn step_attach_timestamp(ctx: &mut CmsContext) -> Result<()> { + attach_timestamp_to_cms(ctx.cms, ctx.timestamp)?; + Ok(()) + } + + + pub fn cms_to_vec(cms: *mut CMS_ContentInfo) -> Result> { + struct BioGuard(*mut openssl_sys::BIO); + impl Drop for BioGuard { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { openssl_sys::BIO_free_all(self.0) }; + } + } + } + unsafe { + let out_bio = BIO_new(BIO_s_mem()); + let _guard = BioGuard(out_bio); + if i2d_CMS_bio(out_bio, cms) != 1 { + return Err(Error::InvalidArgumentError( + "i2d_CMS_bio failed".to_string(), + )); + } + + let mut ptr: *mut c_char = ptr::null_mut(); + let len = BIO_get_mem_data(out_bio, &mut ptr) as usize; + if len == 0 || ptr.is_null() { + return Err(Error::InvalidArgumentError( + "BIO_get_mem_data got empty buffer".to_string(), + )); + } + + let data_ptr = ptr as *const u8; + let buf = slice::from_raw_parts(data_ptr, len).to_vec(); + Ok(buf) + } + } +} \ No newline at end of file diff --git a/src/infra/sign_plugin/mod.rs b/src/infra/sign_plugin/mod.rs index 4fa0dcc..1445acb 100644 --- a/src/infra/sign_plugin/mod.rs +++ b/src/infra/sign_plugin/mod.rs @@ -2,3 +2,4 @@ pub mod openpgp; pub mod signers; pub mod util; pub mod x509; +pub mod cms; diff --git a/src/infra/sign_plugin/openpgp.rs b/src/infra/sign_plugin/openpgp.rs index 4421c54..3a98e63 100644 --- a/src/infra/sign_plugin/openpgp.rs +++ b/src/infra/sign_plugin/openpgp.rs @@ -134,7 +134,7 @@ impl OpenPGPPlugin { } impl SignPlugins for OpenPGPPlugin { - fn new(db: SecDataKey) -> Result { + fn new(db: SecDataKey, _timestamp_key: Option) -> Result { let mut secret_key = None; let mut public_key = None; if !db.private_key.unsecure().is_empty() { diff --git a/src/infra/sign_plugin/signers.rs b/src/infra/sign_plugin/signers.rs index fe791f2..1de616a 100644 --- a/src/infra/sign_plugin/signers.rs +++ b/src/infra/sign_plugin/signers.rs @@ -29,11 +29,12 @@ impl Signers { pub fn load_from_data_key( key_type: &KeyType, data_key: SecDataKey, + timestamp_key: Option, ) -> Result> { match key_type { - KeyType::OpenPGP => Ok(Box::new(OpenPGPPlugin::new(data_key)?)), + KeyType::OpenPGP => Ok(Box::new(OpenPGPPlugin::new(data_key, None)?)), KeyType::X509CA | KeyType::X509ICA | KeyType::X509EE => { - Ok(Box::new(X509Plugin::new(data_key)?)) + Ok(Box::new(X509Plugin::new(data_key, timestamp_key)?)) } } } diff --git a/src/infra/sign_plugin/x509.rs b/src/infra/sign_plugin/x509.rs index 7f37bed..7b7bb7b 100644 --- a/src/infra/sign_plugin/x509.rs +++ b/src/infra/sign_plugin/x509.rs @@ -24,11 +24,12 @@ use crate::domain::datakey::plugins::x509::{ }; use crate::domain::sign_plugin::SignPlugins; use crate::util::attributes; -use crate::util::attributes::PkeyHashAlgo; use crate::util::error::{Error, Result}; use crate::util::key::{decode_hex_string_to_u8, encode_u8_to_hex_string}; use crate::util::options; use crate::util::sign::SignType; +use crate::infra::sign_plugin::cms::{CmsPlugin, EVP_PKEY_is_a, CmsContext, Step}; + use chrono::{DateTime, Utc}; #[allow(unused_imports)] use enum_iterator::all; @@ -48,57 +49,21 @@ use openssl::x509::extension::{ }; use openssl::x509::{X509Crl, X509Extension}; use openssl_sys::{ - BIO_free_all, BIO_get_mem_data, BIO_new, BIO_new_mem_buf, BIO_s_mem, CMS_ContentInfo, - CMS_ContentInfo_free, CMS_sign, X509_CRL_add0_revoked, X509_CRL_new, X509_CRL_set1_lastUpdate, - X509_CRL_set1_nextUpdate, X509_CRL_set_issuer_name, X509_CRL_sign, X509_REVOKED_new, - X509_REVOKED_set_revocationDate, X509_REVOKED_set_serialNumber, BIO, CMS_BINARY, CMS_DETACHED, CMS_KEY_PARAM, - CMS_NOSMIMECAP, CMS_PARTIAL, EVP_MD, EVP_PKEY, EVP_PKEY_CTX, EVP_PKEY_CTX_set_rsa_padding, X509, X509_CRL, RSA_PKCS1_PSS_PADDING, + X509_CRL_add0_revoked, + X509_CRL_new, X509_CRL_set1_lastUpdate, X509_CRL_set1_nextUpdate, X509_CRL_set_issuer_name, + X509_CRL_sign, X509_REVOKED_new, X509_REVOKED_set_revocationDate, + X509_REVOKED_set_serialNumber, }; use secstr::SecVec; use serde::Deserialize; use std::collections::HashMap; use std::ffi::CString; -use std::ffi::{c_char, c_int, c_uchar, c_uint, c_void}; -use std::ptr; -use std::slice; use std::str::FromStr; use std::time::{Duration, SystemTime}; use validator::{Validate, ValidationError}; -#[repr(C)] -pub struct CMS_SignerInfo { - // 根据 OpenSSL 头文件添加字段 - pub cert: *mut X509, - pub pkey: *mut EVP_PKEY, - pub md: *const EVP_MD, - pub sig: *mut u8, - pub siglen: i32, -} - -extern "C" { - pub fn CMS_final_digest( - cms: *mut CMS_ContentInfo, - md: *const c_uchar, - mdlen: c_uint, - dcont: *mut BIO, - flags: c_uint, - ) -> c_int; - - pub fn CMS_add1_signer( - cms: *mut CMS_ContentInfo, - cert: *mut X509, - pkey: *mut EVP_PKEY, - md: *const EVP_MD, - flags: u32, - ) -> *mut CMS_SignerInfo; - pub fn CMS_SignerInfo_get0_pkey_ctx(si: *mut CMS_SignerInfo) -> *mut EVP_PKEY_CTX; - pub fn i2d_CMS_bio(out: *mut BIO, cms: *mut CMS_ContentInfo) -> c_int; - pub fn EVP_PKEY_is_a(pkey: *const EVP_PKEY, name: *const c_char) -> c_int; - pub fn CMS_add1_crl(cms: *mut CMS_ContentInfo, - crl: *mut X509_CRL) -> c_int; -} - #[derive(Debug, Validate, Deserialize)] #[validate(schema(function = "validate_x509_key_size_for_generation"))] + pub struct X509KeyGenerationParameter { #[validate(length(min = 1, max = 30, message = "invalid x509 subject 'CommonName'"))] common_name: String, @@ -217,8 +182,11 @@ pub struct X509Plugin { private_key: SecVec, public_key: SecVec, certificate: SecVec, + tsa_cert: SecVec, + tsa_key: SecVec, identity: String, attributes: HashMap, + tsa_attributes: HashMap, parent_key: Option, } @@ -576,118 +544,8 @@ impl X509Plugin { } } -fn cms_sign_with_hash( - cert: &x509::X509Ref, - pkey: &PKey, - digest: &[u8], - attribute: HashMap, -) -> Result> { - unsafe { - let data_bio = BIO_new_mem_buf(digest.as_ptr() as *const c_void, digest.len() as i32); - if data_bio.is_null() { - return Err(Error::InvalidArgumentError( - "BIO_new_mem_buf failed".to_string(), - )); - } - - struct BioGuard(*mut openssl_sys::BIO); - impl Drop for BioGuard { - fn drop(&mut self) { - if !self.0.is_null() { - unsafe { BIO_free_all(self.0) }; - } - } - } - let _data_bio_guard = BioGuard(data_bio); - - let flags = CMS_DETACHED | CMS_BINARY | CMS_PARTIAL | CMS_NOSMIMECAP | CMS_KEY_PARAM; - let cms: *mut CMS_ContentInfo = CMS_sign( - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - data_bio, - flags, - ); - if cms.is_null() { - return Err(Error::InvalidArgumentError( - "CMS_sign (partial) failed".to_string(), - )); - } - - struct CmsGuard(*mut CMS_ContentInfo); - impl Drop for CmsGuard { - fn drop(&mut self) { - if !self.0.is_null() { - unsafe { CMS_ContentInfo_free(self.0) }; - } - } - } - let _cms_guard = CmsGuard(cms); - - let md = PkeyHashAlgo::get_openssl_c_digest_algo(&attribute); - if md.is_null() { - return Err(Error::InvalidArgumentError( - "EVP_sha256 returned null".to_string(), - )); - } - - let si = CMS_add1_signer(cms, cert.as_ptr(), pkey.as_ptr(), md, flags); - if si.is_null() { - return Err(Error::InvalidArgumentError( - "CMS_add1_signer failed".to_string(), - )); - } - - let pk_ctx = CMS_SignerInfo_get0_pkey_ctx(si); - if pk_ctx.is_null() { - return Err(Error::InvalidArgumentError( - "CMS_SignerInfo_get0_pkey_ctx failed".to_string(), - )); - } - EVP_PKEY_CTX_set_rsa_padding(pk_ctx, RSA_PKCS1_PSS_PADDING); - - let ret = CMS_final_digest( - cms, - digest.as_ptr(), - digest.len() as u32, - ptr::null_mut(), - flags, - ); - if ret != 1 { - return Err(Error::InvalidArgumentError( - "CMS_final_digest failed".to_string(), - )); - } - - let out_bio = BIO_new(BIO_s_mem()); - if out_bio.is_null() { - return Err(Error::InvalidArgumentError( - "BIO_new(BIO_s_mem) failed".to_string(), - )); - } - let _out_bio_guard = BioGuard(out_bio); - - if i2d_CMS_bio(out_bio, cms) != 1 { - return Err(Error::InvalidArgumentError( - "i2d_CMS_bio failed".to_string(), - )); - } - - let mut ptr: *mut c_char = ptr::null_mut(); - let len = BIO_get_mem_data(out_bio, &mut ptr) as usize; - if len == 0 || ptr.is_null() { - return Err(Error::InvalidArgumentError( - "BIO_get_mem_data got empty buffer".to_string(), - )); - } - let data_ptr = ptr as *const u8; - let buf = slice::from_raw_parts(data_ptr, len).to_vec(); - Ok(buf) - } -} - impl SignPlugins for X509Plugin { - fn new(db: SecDataKey) -> Result { + fn new(db: SecDataKey, timestamp_key: Option) -> Result { let mut plugin = Self { name: db.name.clone(), private_key: db.private_key.clone(), @@ -696,10 +554,19 @@ impl SignPlugins for X509Plugin { identity: db.identity.clone(), attributes: db.attributes, parent_key: None, + tsa_cert: SecVec::new(Vec::new()), + tsa_key: SecVec::new(Vec::new()), + tsa_attributes: HashMap::new(), }; if let Some(parent) = db.parent { plugin.parent_key = Some(parent); } + + if let Some(ref key) = timestamp_key { + plugin.tsa_cert = key.certificate.clone(); + plugin.tsa_key = key.private_key.clone(); + plugin.tsa_attributes = key.attributes.clone(); + } Ok(plugin) } @@ -779,6 +646,10 @@ impl SignPlugins for X509Plugin { )?)?; } + for (key, value) in &options { + info!("key = {}, value = {}", key, value); + } + match SignType::from_str( options .get(options::SIGN_TYPE) @@ -845,9 +716,34 @@ impl SignPlugins for X509Plugin { Ok(cms_signature.to_der()?) } SignType::Cms => { - // common cms signature have the signedAttrs field - // after calculating the hash on the client side, it is sent to the server for signing - cms_sign_with_hash(&certificate, &private_key, &content, options) + let tsa_cert_pem = self.tsa_cert.unsecure(); + let tsa_key_pem = self.tsa_key.unsecure(); + let mut ctx = CmsContext::new(&certificate, &private_key, content.as_slice(), &options, &self.tsa_attributes, + tsa_cert_pem, + tsa_key_pem, + ); + + let need_ts = !tsa_cert_pem.is_empty() && !tsa_key_pem.is_empty(); + if need_ts { + info!("tsa cert & key present, timestamp will be attached"); + let steps: &[Step] = &[ + &CmsPlugin::step_generate_cms, + &CmsPlugin::step_generate_ts_req, + &CmsPlugin::step_load_tsa_cert_key, + &CmsPlugin::step_generate_tst_info, + &CmsPlugin::step_generate_timestamp_token, + &CmsPlugin::step_attach_timestamp, + ]; + CmsPlugin::run_steps(&mut ctx, steps)?; + } else { + info!("tsa cert or key is empty, skip timestamp"); + let steps: &[Step] = &[ + &CmsPlugin::step_generate_cms, + ]; + CmsPlugin::run_steps(&mut ctx, steps)?; + } + + CmsPlugin::cms_to_vec(ctx.cms) } SignType::RsaHash => { diff --git a/src/presentation/handler/data/sign_handler.rs b/src/presentation/handler/data/sign_handler.rs index b0f144a..dbd0fa9 100644 --- a/src/presentation/handler/data/sign_handler.rs +++ b/src/presentation/handler/data/sign_handler.rs @@ -101,7 +101,7 @@ where let key_id_or_name = request.key_id.to_string(); return match self .key_service - .get_by_type_and_name(request.key_type, request.key_id) + .get_by_type_and_name(Some(request.key_type), request.key_id) .await { Ok(datakey) => { diff --git a/src/util/attributes.rs b/src/util/attributes.rs index 199ee0b..c6fc83f 100644 --- a/src/util/attributes.rs +++ b/src/util/attributes.rs @@ -86,12 +86,9 @@ impl PkeyHashAlgo { digest_algo } - pub fn get_openssl_c_digest_algo(attributes: &HashMap) -> *const EVP_MD { + pub fn get_openssl_c_digest_algo(digest: &String) -> *const EVP_MD { unsafe { - let digest_algo = match attributes - .get(DIGEST_ALGO) - .expect("get algo failed") - .as_str() + let digest_algo = match digest.as_str() { "md5" => EVP_md5(), "sha1" => EVP_sha1(), diff --git a/src/util/options.rs b/src/util/options.rs index beff556..98122d2 100644 --- a/src/util/options.rs +++ b/src/util/options.rs @@ -19,3 +19,7 @@ pub const KEY_TYPE: &str = "key_type"; pub const SIGN_TYPE: &str = "sign_type"; pub const RPM_V3_SIGNATURE: &str = "rpm_signature_type"; pub const INCLUDE_PARENT_CERT: &str = "include_parent_cert"; +pub const TIMESTAMP_KEY: &str = "timestampe_key"; +pub const TSA_CERT: &str = "tsa_cert"; +pub const TSA_KET: &str = "tsa_key"; +pub const CRL: &str = "crl"; \ No newline at end of file -- Gitee