From 21ebf0048f1eacbebe8969eabee7e6458aaf99ec Mon Sep 17 00:00:00 2001 From: TommyLike Date: Fri, 28 Jul 2023 11:47:22 +0800 Subject: [PATCH 1/2] Enable pgp&x509 key visibility attributes --- docs/private and public keys.md | 7 ++-- proto/signatrust.proto | 1 + scripts/initialize-user-and-keys.sh | 8 ++-- src/application/datakey.rs | 33 +++++++++++------ src/client/cmd/add.rs | 10 ++++- src/client/worker/signer.rs | 5 ++- src/control_admin_entrypoint.rs | 6 ++- src/domain/datakey/entity.rs | 23 +++++++++++- src/domain/datakey/repository.rs | 8 ++-- .../database/model/datakey/repository.rs | 23 +++++++----- .../handler/control/datakey_handler.rs | 26 ++++++++----- .../handler/control/model/datakey/dto.rs | 37 ++++++++++++++++--- .../handler/control/model/user/dto.rs | 2 +- src/presentation/handler/data/sign_handler.rs | 21 +++++++++++ src/presentation/server/control_server.rs | 8 ++-- src/util/key.rs | 8 ++++ 16 files changed, 170 insertions(+), 56 deletions(-) diff --git a/docs/private and public keys.md b/docs/private and public keys.md index 7610830..9e23694 100644 --- a/docs/private and public keys.md +++ b/docs/private and public keys.md @@ -8,8 +8,8 @@ Since these keys are only used for one specific project and can be a temporary k The `data_key` table will have a new string column named `visibility`. ## Control API changes 1. Create key pairs: Add an attribute `visibility` which only accepts the value `public` or `private`. for any private keys, the key name will be prefixed with user's email for example: `tommylikehu@gmail.com:copr-user1-project1` -2. List Key pairs: Add an query parameter `visibility` to filter keys accordingly. -3. The private key can be deleted by user directly, while the case of the public key is more complex. +2. List Key pairs: Add a query parameter `visibility` to filter keys accordingly, all public key will be returned if omitted. +3. User can delete the private key directly, while the case of the public key is more complex. ```shell >Requst delete by one administrator two more administrator confirmed deletion Key in Normal State <-------------------------------------> Pending Delete key<----------------------------------------->Deleted @@ -19,6 +19,7 @@ Key in Normal State <-------------------------------------> Pending Delete key<- For the purpose of limiting access to private keys, there are two possible solutions. 1. Use the SAN attribute in client certificate, the email `SAN` of the client can be verified whether it matches to the key owner's when requesting a private key pairs. But this requires the support of openssl/tonic library. 2. Add attribute `token` to the grpc requests, and that the verification can be performed without any library changes. -Note: this verification will be performed only when key name is prefixed with email address. +We proposed to add optional parameter 'token' to the grpc request, and note this verification will be performed only when key name is prefixed with email address. + diff --git a/proto/signatrust.proto b/proto/signatrust.proto index 3e84864..f60eece 100644 --- a/proto/signatrust.proto +++ b/proto/signatrust.proto @@ -13,6 +13,7 @@ message SignStreamRequest { string key_type = 2; string key_id = 3; map options = 4; + optional string token = 5; } message SignStreamResponse { diff --git a/scripts/initialize-user-and-keys.sh b/scripts/initialize-user-and-keys.sh index ee44bc9..e8bfbec 100755 --- a/scripts/initialize-user-and-keys.sh +++ b/scripts/initialize-user-and-keys.sh @@ -23,24 +23,24 @@ function create_default_admin { function create_default_x509_ca { echo "start to create default x509 CA identified with default-x509" RUST_LOG=info ./target/debug/control-admin --config ./config/server.toml generate-keys --name default-x509ca --description "used for test purpose only" --key-type x509ca --email tommylikehu@gmail.com --param-key-type rsa --param-key-size 2048 \ - --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-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 --visibility public } function create_default_x509_ica { echo "start to create default x509 ICA identified with default-x509" RUST_LOG=info ./target/debug/control-admin --config ./config/server.toml generate-keys --name default-x509ica --description "used for test purpose only" --key-type x509ica --email tommylikehu@gmail.com --param-key-type rsa --param-key-size 2048 \ - --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-x509ca + --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-x509ca --visibility public } function create_default_x509_ee { echo "start to create default x509 EE certificate identified with default-x509" RUST_LOG=info ./target/debug/control-admin --config ./config/server.toml generate-keys --name default-x509ee --description "used for test purpose only" --key-type x509ee --email tommylikehu@gmail.com --param-key-type rsa --param-key-size 2048 \ - --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 + --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 { 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 + 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 } diff --git a/src/application/datakey.rs b/src/application/datakey.rs index 37a730b..aa84612 100644 --- a/src/application/datakey.rs +++ b/src/application/datakey.rs @@ -18,7 +18,7 @@ use crate::domain::datakey::repository::Repository as DatakeyRepository; use crate::domain::sign_service::SignBackend; use crate::util::error::{Error, Result}; use async_trait::async_trait; -use crate::domain::datakey::entity::{DataKey, KeyAction, KeyState, KeyType, X509CRL, X509RevokeReason}; +use crate::domain::datakey::entity::{DataKey, KeyAction, KeyState, KeyType, Visibility, X509CRL, X509RevokeReason}; use tokio::time::{self}; use crate::util::signer_container::DataKeyContainer; @@ -32,10 +32,10 @@ use crate::presentation::handler::control::model::user::dto::UserIdentity; #[async_trait] pub trait KeyService: Send + Sync{ - async fn create(&self, data: &mut DataKey) -> Result; + async fn create(&self, user: UserIdentity, data: &mut DataKey) -> Result; async fn import(&self, data: &mut DataKey) -> Result; async fn get_by_name(&self, name: &str) -> Result; - async fn get_all(&self, key_type: Option) -> Result>; + async fn get_all(&self, key_type: Option, visibility: Visibility) -> Result>; async fn get_one(&self, user: Option, id_or_name: String) -> Result; //get keys content async fn export_one(&self, user: Option, id_or_name: String) -> Result; @@ -83,7 +83,7 @@ impl DBKeyService } } - async fn get_and_check_permission(&self, _user: Option, id_or_name: String, action: KeyAction) -> Result { + async fn get_and_check_permission(&self, user: Option, id_or_name: String, action: KeyAction) -> Result { let id = id_or_name.parse::(); let data_key: DataKey = match id { Ok(id) => { @@ -93,6 +93,10 @@ impl DBKeyService self.repository.get_by_name(&id_or_name).await? } }; + //check permission for private keys + if data_key.visibility == Visibility::Private && (user.is_none() || data_key.user != user.unwrap().id) { + return Err(Error::UnprivilegedError); + } self.validate_type_and_state(&data_key, action)?; Ok(data_key) } @@ -141,8 +145,15 @@ impl DBKeyService } Ok(()) } - async fn check_key_hierarchy(&self, data: &DataKey, parent_id: i32) -> Result<()> { + async fn check_key_hierarchy(&self, user: UserIdentity, data: &DataKey, parent_id: i32) -> Result<()> { let parent_key = self.repository.get_by_id(parent_id).await?; + //check permission for private keys + if parent_key.visibility == Visibility::Private && parent_key.user != user.id { + return Err(Error::UnprivilegedError); + } + if parent_key.visibility != data.visibility { + return Err(Error::ActionsNotAllowedError(format!("parent key '{}' visibility not equal to current datakey", parent_id))); + } if parent_key.key_state != KeyState::Enabled { return Err(Error::ActionsNotAllowedError(format!("parent key '{}' not in enable state", parent_id))); } @@ -168,10 +179,10 @@ where R: DatakeyRepository + Clone + 'static, S: SignBackend + ?Sized + 'static { - async fn create(&self, data: &mut DataKey) -> Result { + async fn create(&self, user: UserIdentity, data: &mut DataKey) -> Result { //check parent key is enabled,expire time is greater than child key and hierarchy is correct if let Some(parent_id) = data.parent_id { - self.check_key_hierarchy(data, parent_id).await?; + self.check_key_hierarchy(user, data, parent_id).await?; } //we need to create a key in database first, then generate sensitive data let mut key = self.repository.create(data.clone()).await?; @@ -196,8 +207,8 @@ where self.repository.get_by_name(name).await } - async fn get_all(&self, key_type: Option) -> Result> { - self.repository.get_all_keys(key_type).await + async fn get_all(&self, key_type: Option, visibility: Visibility) -> Result> { + self.repository.get_all_keys(key_type, visibility).await } async fn get_one(&self, user: Option, id_or_name: String) -> Result { @@ -229,7 +240,7 @@ where return Err(Error::ActionsNotAllowedError(format!("key '{}' is used by other keys, request delete is not allowed", key.name))); } } - self.repository.request_delete_key(user_id, user_email, key.id).await + self.repository.request_delete_key(user_id, user_email, key.id, key.visibility == Visibility::Public).await } async fn cancel_delete(&self, user: UserIdentity, id_or_name: String) -> Result<()> { @@ -242,7 +253,7 @@ where let user_id = user.id; let user_email = user.email.clone(); let key = self.get_and_check_permission(Some(user), id_or_name, KeyAction::Revoke).await?; - self.repository.request_revoke_key(user_id, user_email, key.id, key.parent_id.unwrap(), reason).await?; + self.repository.request_revoke_key(user_id, user_email, key.id, key.parent_id.unwrap(), reason, key.visibility == Visibility::Public).await?; Ok(()) } diff --git a/src/client/cmd/add.rs b/src/client/cmd/add.rs index 4c60f4c..7a800f2 100644 --- a/src/client/cmd/add.rs +++ b/src/client/cmd/add.rs @@ -89,6 +89,7 @@ pub struct CommandAddHandler { detached: bool, max_concurrency: usize, sign_type: SignType, + token: Option, } impl CommandAddHandler { @@ -160,6 +161,12 @@ impl SignCommand for CommandAddHandler { if !file_exists(&working_dir) { return Err(error::Error::FileFoundError(format!("working dir: {} not exists", working_dir))); } + let mut token = None; + if let Ok(t) = config.read()?.get_string("token") { + if t != "" { + token = Some(t); + } + } Ok(CommandAddHandler{ worker_threads, buffer_size: config.read()?.get_string("buffer_size")?.parse()?, @@ -173,6 +180,7 @@ impl SignCommand for CommandAddHandler { detached: command.detached, max_concurrency: config.read()?.get_string("max_concurrency")?.parse()?, sign_type: command.sign_type, + token, }) } @@ -214,7 +222,7 @@ impl SignCommand for CommandAddHandler { if let Err(err) = channel { return Some(err) } - let mut signer = RemoteSigner::new(channel.unwrap(), self.buffer_size); + let mut signer = RemoteSigner::new(channel.unwrap(), self.buffer_size, self.token.clone()); //split file let send_handlers = files.into_iter().map(|file|{ let task_split_s = split_s.clone(); diff --git a/src/client/worker/signer.rs b/src/client/worker/signer.rs index d00842c..9635676 100644 --- a/src/client/worker/signer.rs +++ b/src/client/worker/signer.rs @@ -34,15 +34,17 @@ use std::io::{Cursor, Read}; pub struct RemoteSigner { client: SignatrustClient, buffer_size: usize, + token: Option, } impl RemoteSigner { - pub fn new(channel: Channel, buffer_size: usize) -> Self { + pub fn new(channel: Channel, buffer_size: usize,token: Option) -> Self { Self { client: SignatrustClient::new(channel), buffer_size, + token } } } @@ -66,6 +68,7 @@ impl SignHandler for RemoteSigner { options: item.sign_options.borrow().clone(), key_type: format!("{}", item.key_type), key_id: item.key_id.clone(), + token: self.token.clone(), }); } if sign_segments.is_empty() { diff --git a/src/control_admin_entrypoint.rs b/src/control_admin_entrypoint.rs index 73f552f..6f1699b 100644 --- a/src/control_admin_entrypoint.rs +++ b/src/control_admin_entrypoint.rs @@ -124,6 +124,9 @@ pub struct CommandGenerateKeys { #[arg(long)] #[arg(help = "specify th type of key")] key_type: String, + #[arg(long)] + #[arg(help = "specify th visibility of key")] + visibility: String, } fn generate_keys_parameters(command: &CommandGenerateKeys) -> HashMap { @@ -172,6 +175,7 @@ async fn main() -> Result<()> { attributes: generate_keys_parameters(&generate_keys), key_type: generate_keys.key_type.clone(), parent_id: None, + visibility: Some(generate_keys.visibility.clone()), expire_at: format!("{}", now + Duration::days(30)), }; if generate_keys.key_type == KeyType::X509CA.to_string() { @@ -186,7 +190,7 @@ async fn main() -> Result<()> { } key.validate()?; - let keys = control_server.create_keys(&mut DataKey::create_from(key, UserIdentity::from_user(user))?).await?; + let keys = control_server.create_keys(&mut DataKey::create_from(key, UserIdentity::from_user(user.clone()))?, UserIdentity::from_user(user)).await?; info!("[Result]: Keys {} type {} has been successfully generated", &keys.name, &generate_keys.key_type) } None => {} diff --git a/src/domain/datakey/entity.rs b/src/domain/datakey/entity.rs index e86cce6..79af46d 100644 --- a/src/domain/datakey/entity.rs +++ b/src/domain/datakey/entity.rs @@ -347,8 +347,25 @@ pub struct DataKeyContent { pub enum Visibility { #[default] Public, - //NOTE: We don't support private key now. - //Private + Private +} + +impl Visibility { + pub fn from_parameter(s: Option) -> Result { + match s { + None => { + Ok(Visibility::Public) + } + Some(value) => { + if value == "public" { + return Ok(Visibility::Public); + } else if value == "private" { + return Ok(Visibility::Private); + } + Err(Error::UnsupportedTypeError(format!("unsupported data key visibility {}", value))) + } + } + } } impl FromStr for Visibility { @@ -357,6 +374,7 @@ impl FromStr for Visibility { fn from_str(s: &str) -> Result { match s { "public" => Ok(Visibility::Public), + "private" => Ok(Visibility::Private), _ => Err(Error::UnsupportedTypeError(format!("unsupported data key visibility {}", s))), } } @@ -366,6 +384,7 @@ impl Display for Visibility { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Visibility::Public => write!(f, "public"), + Visibility::Private => write!(f, "private"), } } } diff --git a/src/domain/datakey/repository.rs b/src/domain/datakey/repository.rs index 01dec67..0ff7971 100644 --- a/src/domain/datakey/repository.rs +++ b/src/domain/datakey/repository.rs @@ -18,20 +18,20 @@ use super::entity::DataKey; use crate::util::error::Result; use async_trait::async_trait; use chrono::Duration; -use crate::domain::datakey::entity::{KeyState, KeyType, RevokedKey, X509CRL, X509RevokeReason}; +use crate::domain::datakey::entity::{KeyState, KeyType, RevokedKey, Visibility, X509CRL, X509RevokeReason}; #[async_trait] 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(&self, key_type: Option) -> Result>; + async fn get_all_keys(&self, key_type: Option, visibility: Visibility) -> Result>; async fn get_by_id(&self, id: i32) -> Result; async fn get_by_name(&self, name: &str) -> Result; async fn update_state(&self, id: i32, state: KeyState) -> Result<()>; async fn update_key_data(&self, data_key: DataKey) -> Result<()>; async fn get_enabled_key_by_type_and_name(&self, key_type: String, name: String) -> Result; - async fn request_delete_key(&self, user_id: i32, user_email: String, id: i32) -> Result<()>; - async fn request_revoke_key(&self, user_id: i32, user_email: String, id: i32, parent_id: i32, reason: X509RevokeReason) -> Result<()>; + async fn request_delete_key(&self, user_id: i32, user_email: String, id: i32, public_key: bool) -> Result<()>; + async fn request_revoke_key(&self, user_id: i32, user_email: String, id: i32, parent_id: i32, reason: X509RevokeReason, public_key: bool) -> Result<()>; async fn cancel_delete_key(&self, user_id: i32, id: i32) -> Result<()>; async fn cancel_revoke_key(&self, user_id: i32, id: i32, parent_id: i32) -> Result<()>; //crl related methods diff --git a/src/infra/database/model/datakey/repository.rs b/src/infra/database/model/datakey/repository.rs index 69e9b90..974ab73 100644 --- a/src/infra/database/model/datakey/repository.rs +++ b/src/infra/database/model/datakey/repository.rs @@ -16,7 +16,7 @@ use super::dto::DataKeyDTO; use crate::infra::database::pool::DbPool; -use crate::domain::datakey::entity::{DataKey, KeyState, KeyType, ParentKey, RevokedKey, X509CRL, X509RevokeReason}; +use crate::domain::datakey::entity::{DataKey, KeyState, KeyType, ParentKey, RevokedKey, Visibility, X509CRL, X509RevokeReason}; use crate::domain::datakey::repository::Repository; use crate::util::error::{Result}; use async_trait::async_trait; @@ -28,7 +28,8 @@ use crate::infra::database::model::datakey::dto::X509CRLDTO; use crate::infra::database::model::request_delete::dto::{PendingOperationDTO, RequestType, RevokedKeyDTO}; use crate::util::error; -const PENDING_THRESHOLD: i32 = 3; +const PUBLICKEY_PENDING_THRESHOLD: i32 = 3; +const PRIVATEKEY_PENDING_THRESHOLD: i32 = 1; #[derive(Clone)] pub struct DataKeyRepository { @@ -143,7 +144,7 @@ impl Repository for DataKeyRepository { Ok(()) } - async fn get_all_keys(&self, key_type: Option) -> Result> { + async fn get_all_keys(&self, key_type: Option, visibility: Visibility) -> Result> { let dtos: Vec = match key_type { None => { sqlx::query_as( @@ -153,9 +154,10 @@ impl Repository for DataKeyRepository { INNER JOIN user U ON D.user = U.id \ LEFT JOIN pending_operation R ON D.id = R.key_id and R.request_type = 'delete' \ LEFT JOIN pending_operation K ON D.id = K.key_id and K.request_type = 'revoke' \ - WHERE D.key_state != ? \ + WHERE D.key_state != ? AND D.visibility = ? \ GROUP BY D.id") .bind(KeyState::Deleted.to_string()) + .bind(visibility.to_string()) .fetch_all(&self.db_pool) .await? } @@ -168,10 +170,11 @@ impl Repository for DataKeyRepository { LEFT JOIN pending_operation R ON D.id = R.key_id and R.request_type = 'delete' \ LEFT JOIN pending_operation K ON D.id = K.key_id and K.request_type = 'revoke' \ WHERE D.key_state != ? AND \ - D.key_type = ? \ + D.key_type = ? AND D.visibility = ? \ GROUP BY D.id") .bind(KeyState::Deleted.to_string()) .bind(key_t.to_string()) + .bind(visibility.to_string()) .fetch_all(&self.db_pool) .await? } @@ -326,8 +329,9 @@ impl Repository for DataKeyRepository { Ok(DataKey::try_from(dto)?) } - async fn request_delete_key(&self, user_id: i32, user_email: String, id: i32) -> Result<()> { + async fn request_delete_key(&self, user_id: i32, user_email: String, id: i32, public_key: bool) -> Result<()> { let mut tx = self.db_pool.begin().await?; + let threshold = if public_key { PUBLICKEY_PENDING_THRESHOLD } else { PRIVATEKEY_PENDING_THRESHOLD }; //1. update key state to pending delete if needed. let _: Option = sqlx::query_as( "UPDATE data_key SET key_state = ? \ @@ -347,15 +351,16 @@ impl Repository for DataKeyRepository { .bind(KeyState::Deleted.to_string()) .bind(id) .bind(id) - .bind(PENDING_THRESHOLD) + .bind(threshold) .fetch_optional(&mut tx) .await?; tx.commit().await?; Ok(()) } - async fn request_revoke_key(&self, user_id: i32, user_email: String, id: i32, parent_id: i32, reason: X509RevokeReason) -> Result<()> { + async fn request_revoke_key(&self, user_id: i32, user_email: String, id: i32, parent_id: i32, reason: X509RevokeReason, public_key: bool) -> Result<()> { let mut tx = self.db_pool.begin().await?; + let threshold = if public_key { PUBLICKEY_PENDING_THRESHOLD } else { PRIVATEKEY_PENDING_THRESHOLD }; //1. update key state to pending delete if needed. let _: Option = sqlx::query_as( "UPDATE data_key SET key_state = ? \ @@ -377,7 +382,7 @@ impl Repository for DataKeyRepository { .bind(KeyState::Revoked.to_string()) .bind(id) .bind(id) - .bind(PENDING_THRESHOLD) + .bind(threshold) .fetch_optional(&mut tx) .await?; tx.commit().await?; diff --git a/src/presentation/handler/control/datakey_handler.rs b/src/presentation/handler/control/datakey_handler.rs index d90fa41..268b836 100644 --- a/src/presentation/handler/control/datakey_handler.rs +++ b/src/presentation/handler/control/datakey_handler.rs @@ -24,12 +24,13 @@ use crate::presentation::handler::control::model::datakey::dto::{CertificateCont use crate::util::error::Error; use validator::Validate; use crate::application::datakey::KeyService; -use crate::domain::datakey::entity::{DataKey, KeyType, X509RevokeReason}; +use crate::domain::datakey::entity::{DataKey, KeyType, Visibility, X509RevokeReason}; +use crate::util::key::get_datakey_full_name; use super::model::user::dto::UserIdentity; /// Create new key /// -/// This will generate either a pgp key pairs or a x509 private/public/cert keys. +/// This will generate either a private/public pgp key pairs or a x509 private/public/cert keys. /// ## Naming convention /// The name of the key should be unique. /// ## Generate pgp key @@ -45,6 +46,7 @@ use super::model::user::dto::UserIdentity; /// "name": "test-pgp", /// "description": "hello world", /// "key_type": "pgp", +/// "visibility": "public", /// "attributes": { /// "digest_algorithm": "sha2_256", /// "key_type": "rsa", @@ -81,6 +83,7 @@ use super::model::user::dto::UserIdentity; /// "description": "hello world", /// "key_type": "x509CA", /// "parent_id": "1111", +/// "visibility": "public", /// "attributes": { /// "digest_algorithm": "sha2_256", /// "key_type": "rsa", @@ -117,8 +120,8 @@ use super::model::user::dto::UserIdentity; )] async fn create_data_key(user: UserIdentity, key_service: web::Data, datakey: web::Json,) -> Result { datakey.validate()?; - let mut key = DataKey::create_from(datakey.0, user)?; - Ok(HttpResponse::Created().json(DataKeyDTO::try_from(key_service.into_inner().create(&mut key).await?)?)) + let mut key = DataKey::create_from(datakey.0, user.clone())?; + Ok(HttpResponse::Created().json(DataKeyDTO::try_from(key_service.into_inner().create(user, &mut key).await?)?)) } /// Get all available keys from database. @@ -126,7 +129,7 @@ async fn create_data_key(user: UserIdentity, key_service: web::Data Some(KeyType::from_str(k)?), None => None, }; - let keys = key_service.into_inner().get_all(key_type).await?; + let visibility = Visibility::from_parameter(key.visibility.clone())?; + let keys = key_service.into_inner().get_all(key_type, visibility).await?; let mut results = vec![]; for k in keys { results.push(DataKeyDTO::try_from(k)?) @@ -467,7 +471,7 @@ async fn disable_data_key(user: UserIdentity, key_service: web::Data, name_exist: web::Query,) -> Result { +async fn key_name_identical(user: UserIdentity, key_service: web::Data, name_exist: web::Query,) -> Result { name_exist.validate()?; - match key_service.into_inner().get_by_name(&name_exist.name.clone()).await { + let visibility = Visibility::from_parameter(name_exist.visibility.clone())?; + let key_name = get_datakey_full_name(&name_exist.name, &user.email, &visibility); + match key_service.into_inner().get_by_name(&key_name).await { Ok(_) => Ok(HttpResponse::Conflict()), Err(_) => Ok(HttpResponse::Ok()), } @@ -494,7 +500,7 @@ async fn key_name_identical(_user: UserIdentity, key_service: web::Data, } #[derive(Deserialize, IntoParams, Validate, ToSchema)] @@ -69,6 +72,8 @@ pub struct ListKeyQuery { /// Key type, optional, should be one of x509ca, x509ica, x509ee, or pgp #[validate(custom = "validate_key_type")] pub key_type: Option, + #[validate(custom = "validate_key_visibility")] + pub visibility: Option, } @@ -80,6 +85,9 @@ pub struct CreateDataKeyDTO { /// Description, length between 0 and 100 #[validate(length(min = 0, max = 100))] pub description: String, + /// The key's visibility + #[validate(custom = "validate_key_visibility")] + pub visibility: Option, /// Attributes in map #[serde(serialize_with = "sorted_map")] pub attributes: HashMap, @@ -100,6 +108,9 @@ pub struct ImportDataKeyDTO { /// Description, length between 0 and 100 #[validate(length(min = 0, max = 100))] pub description: String, + /// The key's visibility + #[validate(custom = "validate_key_visibility")] + pub visibility: Option, /// Attributes in map pub attributes: HashMap, /// Key type current support pgp and x509 @@ -165,6 +176,18 @@ fn validate_utc_time(expire: &str) -> std::result::Result<(), ValidationError> { Ok(()) } +fn validate_key_visibility(visibility: &str) -> std::result::Result<(), ValidationError> { + match Visibility::from_str(visibility) { + Ok(_) => { + Ok(()) + } + Err(_) => { + Err(ValidationError::new("unsupported key visibility")) + } + } +} + + fn validate_key_type(key_type: &str) -> std::result::Result<(), ValidationError> { match KeyType::from_str(key_type) { Ok(_) => { @@ -189,10 +212,12 @@ impl DataKey { let mut combined_attributes = dto.attributes.clone(); combined_attributes.insert("name".to_string(), dto.name.clone()); combined_attributes.insert("create_at".to_string(), now.clone().to_string()); + let visibility = Visibility::from_parameter(dto.visibility)?; + let key_name = get_datakey_full_name(&dto.name, &identity.email, &visibility); Ok(DataKey { id: 0, - name: dto.name, - visibility: Visibility::Public, + name: key_name, + visibility, description: dto.description, user: identity.id, attributes: combined_attributes, @@ -219,10 +244,12 @@ impl DataKey { combined_attributes.insert("name".to_string(), dto.name.clone()); combined_attributes.insert("create_at".to_string(), now.clone().to_string()); combined_attributes.insert("expire_at".to_string(), dto.expire_at.clone()); + let visibility = Visibility::from_parameter(dto.visibility)?; + let key_name = get_datakey_full_name(&dto.name, &identity.email, &visibility); Ok(DataKey { id: 0, - name: dto.name, - visibility: Visibility::Public, + name: key_name, + visibility, description: dto.description, user: identity.id, attributes: combined_attributes, diff --git a/src/presentation/handler/control/model/user/dto.rs b/src/presentation/handler/control/model/user/dto.rs index a1f366c..45743aa 100644 --- a/src/presentation/handler/control/model/user/dto.rs +++ b/src/presentation/handler/control/model/user/dto.rs @@ -26,7 +26,7 @@ pub const AUTH_HEADER_NAME: &str = "Authorization"; pub const SET_COOKIE_HEADER: &str = "set-cookie"; -#[derive(Debug, Deserialize, Serialize, ToSchema)] +#[derive(Debug, Deserialize, Serialize, ToSchema, Clone)] pub struct UserIdentity { pub email: String, pub id: i32, diff --git a/src/presentation/handler/data/sign_handler.rs b/src/presentation/handler/data/sign_handler.rs index b68574b..bb8deeb 100644 --- a/src/presentation/handler/data/sign_handler.rs +++ b/src/presentation/handler/data/sign_handler.rs @@ -28,6 +28,8 @@ use signatrust::{ use tonic::{Request, Response, Status, Streaming}; use crate::application::datakey::KeyService; use crate::application::user::UserService; +use crate::util::error::Error; +use crate::util::error::Result as SignatrustResult; pub struct SignHandler where @@ -49,6 +51,16 @@ where user_service } } + async fn validate_private_key_token(&self, token: Option, name: &str) -> SignatrustResult<()> { + let names: Vec<_> = name.split(':').collect(); + if names.len() <= 1 { + return Ok(()) + } + if token.is_none() || !self.user_service.validate_token_and_email(names[0], &token.unwrap()).await? { + return Err(Error::AuthError("user token and email unmatched".to_string())) + } + Ok(()) + } } #[tonic::async_trait] @@ -66,12 +78,21 @@ where let mut key_name: String = "".to_string(); let mut key_type: String = "".to_string(); let mut options: HashMap = HashMap::new(); + let mut token: Option = None; while let Some(content) = binaries.next().await { let mut inner_result = content.unwrap(); data.append(&mut inner_result.data); key_name = inner_result.key_id; key_type = inner_result.key_type; options = inner_result.options; + token = inner_result.token; + } + //perform token validation on private keys + if let Err(err) = self.validate_private_key_token(token, &key_name).await { + return Ok(Response::new(SignStreamResponse { + signature: vec![], + error: err.to_string(), + })) } debug!("begin to sign key_type :{} key_name: {}", key_type, key_name); match self.key_service.sign(key_type, key_name, &options, data).await { diff --git a/src/presentation/server/control_server.rs b/src/presentation/server/control_server.rs index 58a5972..3ad0b50 100644 --- a/src/presentation/server/control_server.rs +++ b/src/presentation/server/control_server.rs @@ -44,7 +44,7 @@ use crate::infra::database::model::token::repository::TokenRepository; use crate::infra::database::model::user::repository::UserRepository; use crate::infra::sign_backend::factory::SignBackendFactory; use crate::application::user::{DBUserService, UserService}; -use crate::domain::datakey::entity::DataKey; +use crate::domain::datakey::entity::{DataKey}; use crate::domain::token::entity::Token; use crate::domain::user::entity::User; use crate::presentation::handler::control::model::token::dto::{CreateTokenDTO}; @@ -258,9 +258,9 @@ impl ControlServer { } //used for control admin cmd - pub async fn create_keys(&self, data: &mut DataKey) -> Result { - let key = self.key_service.create(data).await?; - self.key_service.enable(None, format!("{}", key.id)).await?; + pub async fn create_keys(&self, data: &mut DataKey, user: UserIdentity) -> Result { + let key = self.key_service.create(user.clone(), data).await?; + self.key_service.enable(Some(user), format!("{}", key.id)).await?; Ok(key) } diff --git a/src/util/key.rs b/src/util/key.rs index 44a2833..045d849 100644 --- a/src/util/key.rs +++ b/src/util/key.rs @@ -21,6 +21,7 @@ use serde::{Serialize, Serializer}; use std::collections::{HashMap, BTreeMap}; use std::path::Path; use sha2::{Sha256, Digest}; +use crate::domain::datakey::entity::Visibility; pub fn encode_u8_to_hex_string(value: &[u8]) -> String { value @@ -29,6 +30,13 @@ pub fn encode_u8_to_hex_string(value: &[u8]) -> String { .collect::() } +pub fn get_datakey_full_name(name: &str, email: &str, visibility: &Visibility) -> String { + if visibility.to_owned() == Visibility::Private { + return format!("{}:{}", email, name); + } + name.to_string() +} + pub fn decode_hex_string_to_u8(value: &String) -> Vec { hex::decode(value).unwrap() } -- Gitee From ad1f12acf6feefdb5a1e35879d9eb9e2a10468c8 Mon Sep 17 00:00:00 2001 From: TommyLike Date: Fri, 28 Jul 2023 13:11:10 +0800 Subject: [PATCH 2/2] Update proto build arguments --- build.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build.rs b/build.rs index df177bb..f2af3e3 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,6 @@ fn main() { - tonic_build::compile_protos("proto/signatrust.proto").unwrap(); - tonic_build::compile_protos("proto/health.proto").unwrap(); + let sign_proto = "./proto/signatrust.proto"; + let health_proto = "./proto/health.proto"; + tonic_build::configure().protoc_arg("--experimental_allow_proto3_optional" + ).compile(&[sign_proto, health_proto], &["proto"]).unwrap(); } -- Gitee