diff --git a/Cargo.toml b/Cargo.toml
index c22074eb82ee1a3cc33ce6f5ff520b2e6b6067a4..c9e816aa4162d51d09d1816eba229f67ad8eb416 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -61,7 +61,9 @@ sha1 = "0.10.5"
sha2 = "0.10.6"
bincode = "2.0.0-rc.3"
secstr = "0.5.1"
-openssl = "0.10.45"
+openssl-sys = "0.9.90"
+openssl= "0.10.55"
+foreign-types-shared = "0.1.1"
actix-identity = "0.5.2"
actix-session = { version = "0.7.2", features = ["redis-rs-session"]}
actix-limitation = "0.4.0"
diff --git a/app/src/pages/listShow/ListShow.vue b/app/src/pages/listShow/ListShow.vue
index f7cfbc5d0dbf3b45d94a28b97369ec2f628fe29e..cdd1f1053ab05a8a84578f3b49e34a41bde58f92 100644
--- a/app/src/pages/listShow/ListShow.vue
+++ b/app/src/pages/listShow/ListShow.vue
@@ -11,7 +11,6 @@ import PublicTable from "./PublicTable.vue";
diff --git a/config/client.toml b/config/client.toml
index d017e483eb24189129b5373ffbc2286dc33576c0..55e84ec768ed8a191409f8115f82aab433b1638c 100644
--- a/config/client.toml
+++ b/config/client.toml
@@ -3,7 +3,6 @@ worker_threads = 8
buffer_size = 20480
# consider the memory consumption if number bumped since all binaries will be stored in memory
max_concurrency = 100
-token = ""
[server]
domain_name = "signatrust.test.osinfra.cn"
tls_cert = "/Users/tommylike/Work/codes/rust-projects/signatrust/.data/certs/client/server.crt"
diff --git a/config/server.toml b/config/server.toml
index e1123eeb78f34f4f4c3710df5cc6927ff1418fcf..b56f092a290bccbf2cdf5936702041670e250db9 100644
--- a/config/server.toml
+++ b/config/server.toml
@@ -10,6 +10,8 @@ server_ip = "0.0.0.0"
server_port = "8080"
cookie_key = "2B5AEC57F7CC4FF8B4120AA7E4527C7B597CAF43183E453A9B981991E6FACB76"
redis_connection = "redis://:signatrust-redis@127.0.0.1:6379"
+domain_name = "signatrust.test.osinfra.cn"
+crl_refresh_interval_days = 7
limits_per_minute = 100
[oidc]
client_id = ""
diff --git a/migrations/20230614032140_support-ca-and-crl.down.sql b/migrations/20230614032140_support-ca-and-crl.down.sql
new file mode 100644
index 0000000000000000000000000000000000000000..45ab6eb93a923f8c68254af7e32cae34eb1dc34d
--- /dev/null
+++ b/migrations/20230614032140_support-ca-and-crl.down.sql
@@ -0,0 +1,18 @@
+-- Add down migration script here
+DROP TABLE IF EXISTS x509_keys_revoked;
+DROP TABLE IF EXISTS x509_crl_content;
+ALTER TABLE data_key DROP parent_id;
+ALTER TABLE data_key DROP serial_number;
+ALTER TABLE data_key MODIFY COLUMN key_state varchar(10);
+
+DROP TABLE IF EXISTS pending_operation;
+CREATE TABLE request_delete (
+ id INT AUTO_INCREMENT,
+ user_id INT NOT NULL,
+ key_id INT NOT NULL,
+ create_at DATETIME,
+ PRIMARY KEY(id),
+ FOREIGN KEY (user_id) REFERENCES user(id),
+ FOREIGN KEY (key_id) REFERENCES data_key(id),
+ UNIQUE KEY `unique_user_and_key` (`user_id`,`key_id`)
+);
diff --git a/migrations/20230614032140_support-ca-and-crl.up.sql b/migrations/20230614032140_support-ca-and-crl.up.sql
new file mode 100644
index 0000000000000000000000000000000000000000..e3d5b10cff2ef324b4266294b8fcca69e20f2c8d
--- /dev/null
+++ b/migrations/20230614032140_support-ca-and-crl.up.sql
@@ -0,0 +1,42 @@
+-- Add up migration script here
+# Support for parent_id and serial_number
+ALTER TABLE data_key ADD parent_id INT AFTER `key_type`;
+ALTER TABLE data_key ADD serial_number VARCHAR(90) AFTER `fingerprint`;
+ALTER TABLE data_key MODIFY COLUMN key_state varchar(20);
+
+# Break changes: request delete will be dropped&recreated to support different pending operation
+DROP TABLE IF EXISTS request_delete;
+CREATE TABLE pending_operation (
+ id INT AUTO_INCREMENT,
+ user_id INT NOT NULL,
+ key_id INT NOT NULL,
+ request_type VARCHAR(30) NOT NULL,
+ user_email varchar(60) NOT NULL,
+ create_at DATETIME,
+ PRIMARY KEY(id),
+ FOREIGN KEY (user_id) REFERENCES user(id),
+ FOREIGN KEY (key_id) REFERENCES data_key(id),
+ UNIQUE KEY `unique_user_and_key_and_type` (`user_id`,`key_id`, `request_type`)
+);
+
+# Add new table for crl content and revoked certificates
+CREATE TABLE x509_crl_content (
+ id INT AUTO_INCREMENT,
+ ca_id INT NOT NULL UNIQUE,
+ create_at DATETIME,
+ update_at DATETIME,
+ data TEXT NOT NULL,
+ PRIMARY KEY(id),
+ FOREIGN KEY (ca_id) REFERENCES data_key(id)
+);
+CREATE TABLE x509_keys_revoked (
+ id INT AUTO_INCREMENT,
+ ca_id INT NOT NULL,
+ key_id INT NOT NULL,
+ create_at DATETIME,
+ reason VARCHAR(30),
+ FOREIGN KEY (ca_id) REFERENCES data_key(id),
+ FOREIGN KEY (key_id) REFERENCES data_key(id),
+ UNIQUE KEY `unique_ca_and_key` (`ca_id`,`key_id`),
+ PRIMARY KEY(id)
+);
\ No newline at end of file
diff --git a/proto/signatrust.proto b/proto/signatrust.proto
index 723df8745999ce9337bf6435c718bb8a5590c029..3e848640ac27eacbce005e2b9edb22b74e9b20d9 100644
--- a/proto/signatrust.proto
+++ b/proto/signatrust.proto
@@ -13,7 +13,6 @@ message SignStreamRequest {
string key_type = 2;
string key_id = 3;
map options = 4;
- string token = 5;
}
message SignStreamResponse {
diff --git a/scripts/initialize-user-and-keys.sh b/scripts/initialize-user-and-keys.sh
index 317592bce83bea2ec58fdd451d4613ab55608f77..ee44bc9c387a183e590e1244173b9be3b4fefc85 100755
--- a/scripts/initialize-user-and-keys.sh
+++ b/scripts/initialize-user-and-keys.sh
@@ -20,19 +20,30 @@ function create_default_admin {
RUST_LOG=info ./target/debug/control-admin --config ./config/server.toml create-admin --email tommylikehu@gmail.com
}
-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
+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
}
+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
+}
+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
+}
-function create_default_x509_keys {
- echo "start to create default x509 keys identified with default-x509"
- RUST_LOG=info ./target/debug/control-admin --config ./config/server.toml generate-keys --name default-x509 --description "used for test purpose only" --key-type x509 --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
+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
}
+
echo "Preparing basic keys for signatrust......"
check-binary
@@ -41,6 +52,11 @@ echo "==========================================="
create_default_admin
+create_default_x509_ca
+
+create_default_x509_ica
+
+create_default_x509_ee
+
create_default_openpgp_keys
-create_default_x509_keys
\ No newline at end of file
diff --git a/src/application/datakey.rs b/src/application/datakey.rs
index b460a98afb158a2fbd8aa03361672f1546e5e41d..2cb07ae4e3dab5d14888d1fed3854f6ac738004b 100644
--- a/src/application/datakey.rs
+++ b/src/application/datakey.rs
@@ -18,34 +18,44 @@ 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, KeyState, Visibility};
-use tokio::time::{Duration, self};
+use crate::domain::datakey::entity::{DataKey, KeyAction, KeyState, KeyType, X509CRL, X509RevokeReason};
+use tokio::time::{self};
use crate::util::signer_container::DataKeyContainer;
use std::collections::HashMap;
use std::sync::{Arc};
+use chrono::{Duration, Utc};
use tokio::sync::RwLock;
use tokio_util::sync::CancellationToken;
+use crate::domain::datakey::entity::KeyType::{OpenPGP, X509CA, X509EE, X509ICA};
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 import(&self, data: &mut DataKey) -> Result;
- async fn key_name_exists(&self, name: &String) -> Result;
- async fn get_by_visibility(&self, user: Option, visibility: Visibility) -> Result>;
- async fn get_all(&self) -> Result>;
- async fn get_one(&self, user: Option, id: i32) -> Result;
- async fn request_delete(&self, user: UserIdentity, id: i32) -> Result<()>;
- async fn cancel_delete(&self, user: UserIdentity, id: i32) -> Result<()>;
- async fn export_one(&self, user: Option, id: i32) -> Result;
- async fn enable(&self, user: Option, id: i32) -> Result<()>;
- async fn disable(&self, user: Option, id: i32) -> Result<()>;
+ async fn get_by_name(&self, name: &str) -> Result;
+ async fn get_all(&self, key_type: Option) -> 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;
+ async fn export_cert_crl(&self, user: Option,id_or_name: String) -> Result;
+ //keys related operation
+ async fn request_delete(&self, user: UserIdentity, id_or_name: String) -> Result<()>;
+ async fn cancel_delete(&self, user: UserIdentity, id_or_name: String) -> Result<()>;
+ async fn request_revoke(&self, user: UserIdentity, id_or_name: String, reason: X509RevokeReason) -> Result<()>;
+ async fn cancel_revoke(&self, user: UserIdentity, id_or_name: String) -> Result<()>;
+ async fn enable(&self, user: Option, id_or_name: String) -> Result<()>;
+ async fn disable(&self, user: Option, id_or_name: String) -> Result<()>;
+ //used for data server
async fn sign(&self, key_type: String, key_name: String, options: &HashMap, data: Vec) ->Result>;
//method below used for maintenance
fn start_cache_cleanup_loop(&self, cancel_token: CancellationToken) -> Result<()>;
fn start_key_rotate_loop(&self, cancel_token: CancellationToken) -> Result<()>;
+
+ //method below used for x509 crl
+ fn start_key_plugin_maintenance(&self, cancel_token: CancellationToken, refresh_days: i32) -> Result<()>;
}
@@ -73,15 +83,63 @@ impl DBKeyService
}
}
- async fn get_and_check_permission(&self, user: Option, id: i32) -> Result {
- let key = self.repository.get_by_id(id).await?;
- if key.visibility == Visibility::Public {
- return Ok(key);
+ 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) => {
+ self.repository.get_by_id(id).await?
+ }
+ Err(_) => {
+ self.repository.get_by_name(&id_or_name).await?
+ }
+ };
+ self.validate_type_and_state(&data_key, action)?;
+ Ok(data_key)
+ }
+
+ fn validate_type_and_state(&self, key: &DataKey, key_action: KeyAction) -> Result<()> {
+ let valid_action_by_key_type = HashMap::from([
+ (OpenPGP, vec![KeyAction::Delete, KeyAction::CancelDelete, KeyAction::Disable, KeyAction::Enable, KeyAction::Sign, KeyAction::Read]),
+ (X509CA, vec![KeyAction::Delete, KeyAction::CancelDelete, KeyAction::Disable, KeyAction::Enable, KeyAction::IssueCert, KeyAction::Read]),
+ (X509ICA, vec![KeyAction::Delete, KeyAction::CancelDelete, KeyAction::Revoke, KeyAction::CancelRevoke, KeyAction::Disable, KeyAction::Enable, KeyAction::Read, KeyAction::IssueCert]),
+ (X509EE, vec![KeyAction::Delete, KeyAction::CancelDelete, KeyAction::Revoke, KeyAction::CancelRevoke, KeyAction::Disable, KeyAction::Enable, KeyAction::Read, KeyAction::Sign]),
+ ]);
+
+ let valid_state_by_key_action = HashMap::from([
+ (KeyAction::Delete, vec![KeyState::Disabled, KeyState::Revoked, KeyState::PendingDelete]),
+ (KeyAction::CancelDelete, vec![KeyState::PendingDelete]),
+ (KeyAction::Revoke, vec![KeyState::Disabled, KeyState::PendingRevoke]),
+ (KeyAction::CancelRevoke, vec![KeyState::PendingRevoke]),
+ (KeyAction::Enable, vec![KeyState::Disabled]),
+ (KeyAction::Disable, vec![KeyState::Enabled]),
+ (KeyAction::Sign, vec![KeyState::Enabled, KeyState::PendingDelete, KeyState::PendingRevoke]),
+ (KeyAction::IssueCert, vec![KeyState::Enabled, KeyState::PendingDelete, KeyState::PendingRevoke]),
+ (KeyAction::Read, vec![KeyState::Enabled, KeyState::PendingDelete, KeyState::PendingRevoke]),
+ ]);
+ match valid_action_by_key_type.get(&key.key_type) {
+ None => {
+ return Err(Error::ConfigError("key type is missing, please check the key type".to_string()));
+ }
+ Some(actions) => {
+ if !actions.contains(&key_action) {
+ return Err(Error::ActionsNotAllowedError(format!("action '{}' is not permitted for key type '{}'", key_action, key.key_type)));
+ }
+ }
}
- if user.is_none() || key.user != user.unwrap().id {
- return Err(Error::UnprivilegedError);
+ match valid_state_by_key_action.get(&key_action) {
+ None => {
+ return Err(Error::ConfigError("key action is missing, please check the key action".to_string()))
+ }
+ Some(states) => {
+ if !states.contains(&key.key_state) {
+ return Err(Error::ActionsNotAllowedError(format!("action '{}' is not permitted for state '{}'", key_action, key.key_state)))
+ }
+ }
}
- Ok(key)
+ if (key_action == KeyAction::Revoke || key_action == KeyAction::CancelRevoke) && key.parent_id.is_none() {
+ return Err(Error::ActionsNotAllowedError(format!("action '{}' is not permitted for key without parent", key_action)))
+ }
+ Ok(())
}
}
@@ -92,8 +150,18 @@ where
S: SignBackend + ?Sized + 'static
{
async fn create(&self, data: &mut DataKey) -> Result {
- self.sign_service.read().await.generate_keys(data).await?;
- self.repository.create(data.clone()).await
+ //we need to create a key in database first, then generate sensitive data
+ let mut key = self.repository.create(data.clone()).await?;
+ match self.sign_service.read().await.generate_keys(&mut key).await {
+ Ok(_) => {
+ self.repository.update_key_data(key.clone()).await?;
+ Ok(key)
+ }
+ Err(e) => {
+ self.repository.delete(key.id).await?;
+ Err(e)
+ }
+ }
}
async fn import(&self, data: &mut DataKey) -> Result {
@@ -101,81 +169,78 @@ where
self.repository.create(data.clone()).await
}
- async fn key_name_exists(&self, name: &String) -> Result {
- if self.repository.get_by_name(name).await.is_ok() {
- return Ok(true);
- }
- Ok(false)
+ async fn get_by_name(&self, name: &str) -> Result {
+ self.repository.get_by_name(name).await
}
- async fn get_by_visibility(&self, user: Option, visibility: Visibility) -> Result> {
- if visibility == Visibility::Private {
- if user.is_none() {
- return Err(Error::UnprivilegedError);
- }
- return self.repository.get_private_keys(user.unwrap().id).await;
- }
- self.repository.get_public_keys().await
+ async fn get_all(&self, key_type: Option) -> Result> {
+ self.repository.get_all_keys(key_type).await
+ }
+
+ async fn get_one(&self, user: Option, id_or_name: String) -> Result {
+ let datakey = self.get_and_check_permission(user, id_or_name, KeyAction::Read).await?;
+ Ok(datakey)
+
}
- async fn get_all(&self) -> Result> {
- self.repository.get_all_keys().await
+ async fn export_one(&self, user: Option, id_or_name: String) -> Result {
+ let mut key = self.get_and_check_permission(user, id_or_name, KeyAction::Read).await?;
+ self.sign_service.read().await.decode_public_keys(&mut key).await?;
+ Ok(key)
}
- async fn get_one(&self, user: Option, id: i32) -> Result {
- self.get_and_check_permission(user, id).await
+ async fn export_cert_crl(&self, user: Option, id_or_name: String) -> Result {
+ let key = self.get_and_check_permission(user, id_or_name, KeyAction::Read).await?;
+ let crl = self.repository.get_x509_crl_by_ca_id(key.id).await?;
+ Ok(crl)
}
- async fn request_delete(&self, user: UserIdentity, id: i32) -> Result<()> {
+ async fn request_delete(&self, user: UserIdentity, id_or_name: String) -> Result<()> {
let user_id = user.id;
let user_email = user.email.clone();
- let key = self.get_and_check_permission(Some(user), id).await?;
- if key.key_state == KeyState::Enabled {
- return Err(Error::ParameterError("enabled key does not support delete".to_string()));
- }
- match key.visibility {
- Visibility::Public => {
- self.repository.request_delete_public_key(user_id, user_email, key.id).await
- }
- Visibility::Private => {
- self.repository.delete_private_key(key.id, user_id).await
- }
- }
+ let key = self.get_and_check_permission(Some(user), id_or_name, KeyAction::Delete).await?;
+ self.repository.request_delete_key(user_id, user_email, key.id).await
}
- async fn cancel_delete(&self, user: UserIdentity, id: i32) -> Result<()> {
+ async fn cancel_delete(&self, user: UserIdentity, id_or_name: String) -> Result<()> {
let user_id = user.id;
- let key = self.get_and_check_permission(Some(user), id).await?;
- if key.visibility == Visibility::Private {
- return Err(Error::ParameterError("private key does not support cancel delete".to_string()));
- }
- self.repository.cancel_delete_public_key(user_id, key.id).await
+ let key = self.get_and_check_permission(Some(user), id_or_name, KeyAction::CancelDelete).await?;
+ self.repository.cancel_delete_key(user_id, key.id).await
}
- async fn export_one(&self, user: Option, id: i32) -> Result {
- let mut key = self.get_and_check_permission(user, id).await?;
- self.sign_service.read().await.decode_public_keys(&mut key).await?;
- Ok(key)
+ async fn request_revoke(&self, user: UserIdentity, id_or_name: String, reason: X509RevokeReason) -> Result<()> {
+ 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?;
+ Ok(())
}
- async fn enable(&self, user: Option, id: i32) -> Result<()> {
- let key = self.get_and_check_permission(user, id).await?;
+ async fn cancel_revoke(&self, user: UserIdentity, id_or_name: String) -> Result<()> {
+ let user_id = user.id;
+ let key = self.get_and_check_permission(Some(user), id_or_name, KeyAction::CancelRevoke).await?;
+ self.repository.cancel_revoke_key(user_id, key.id, key.parent_id.unwrap()).await?;
+ Ok(())
+ }
+
+ async fn enable(&self, user: Option, id_or_name: String) -> Result<()> {
+ let key = self.get_and_check_permission(user, id_or_name, KeyAction::Enable).await?;
self.repository.update_state(key.id, KeyState::Enabled).await
}
- async fn disable(&self, user: Option, id: i32) -> Result<()> {
- let key = self.get_and_check_permission(user, id).await?;
+ async fn disable(&self, user: Option, id_or_name: String) -> Result<()> {
+ let key = self.get_and_check_permission(user, id_or_name, KeyAction::Disable).await?;
self.repository.update_state(key.id, KeyState::Disabled).await
}
async fn sign(&self, key_type: String, key_name: String, options: &HashMap, data: Vec) -> Result> {
- self.sign_service.read().await.sign(
- &self.container.get_data_key(key_type, key_name).await?, data, options.clone()).await
+ let key = self.container.get_data_key(key_type, key_name).await?;
+ self.sign_service.read().await.sign(&key, data, options.clone()).await
}
fn start_cache_cleanup_loop(&self, cancel_token: CancellationToken) -> Result<()> {
let container = self.container.clone();
- let mut interval = time::interval(Duration::from_secs(120));
+ let mut interval = time::interval(Duration::seconds(120).to_std()?);
tokio::spawn(async move {
loop {
tokio::select! {
@@ -196,7 +261,7 @@ where
fn start_key_rotate_loop(&self, cancel_token: CancellationToken) -> Result<()> {
let sign_service = self.sign_service.clone();
- let mut interval = time::interval(Duration::from_secs(60 * 60 * 2));
+ let mut interval = time::interval(Duration::seconds(60 * 60 * 2).to_std()?);
tokio::spawn(async move {
loop {
tokio::select! {
@@ -223,4 +288,43 @@ where
});
Ok(())
}
+
+ fn start_key_plugin_maintenance(&self, cancel_token: CancellationToken, refresh_days: i32) -> Result<()> {
+ let mut interval = time::interval(Duration::hours(2).to_std()?);
+ let duration = Duration::days(refresh_days as i64);
+ let repository = self.repository.clone();
+ let sign_service = self.sign_service.clone();
+ tokio::spawn(async move {
+ loop { tokio::select! {
+ _ = interval.tick() => {
+ info!("start to update execute key plugin maintenance");
+ match repository.get_keys_for_crl_update(duration).await {
+ Ok(keys) => {
+ let now = Utc::now();
+ for key in keys {
+ match repository.get_revoked_serial_number_by_parent_id(key.id).await {
+ Ok(revoke_keys) => {
+ match sign_service.read().await.generate_crl_content(&key, revoke_keys, now, now + duration).await {
+ Ok(data) => {
+ let crl_content = X509CRL::new(key.id, data, now, now);
+ if let Err(e) = repository.upsert_x509_crl(crl_content).await {
+ error!("Failed to update CRL content for key: {} {}, {}", key.key_state, key.id, e);
+ } else {
+ info!("CRL has been successfully updated for key: {} {}", key.key_type, key.id);
+ }}
+ Err(e) => {
+ error!("failed to update CRL content for key: {} {} and error {}", key.key_state, key.id, e);
+ }}}
+ Err(e) => {
+ error!("failed to get revoked keys for key {} {}, error {}", key.key_state, key.id, e);
+ }}}}
+ Err(e) => {
+ error!("failed to get keys for CRL update: {}", e);
+ }}}
+ _ = cancel_token.cancelled() => {
+ info!("cancel token received, will quit key plugin maintenance loop");
+ break;
+ }}}});
+ Ok(())
+ }
}
diff --git a/src/client/cmd/add.rs b/src/client/cmd/add.rs
index 1bf79689f758410db8e1550b3dcb3cf6b29e59f5..1fa7f5fef04024a7fc7c7591bfb60eef4ac335d7 100644
--- a/src/client/cmd/add.rs
+++ b/src/client/cmd/add.rs
@@ -87,7 +87,6 @@ pub struct CommandAddHandler {
detached: bool,
max_concurrency: usize,
sign_type: SignType,
- token: String,
}
impl CommandAddHandler {
@@ -168,7 +167,6 @@ impl SignCommand for CommandAddHandler {
detached: command.detached,
max_concurrency: config.read()?.get_string("max_concurrency")?.parse()?,
sign_type: command.sign_type,
- token: config.read()?.get_string("token").unwrap_or("".to_string()),
})
}
@@ -204,7 +202,7 @@ impl SignCommand for CommandAddHandler {
runtime.block_on(async {
let channel = ChannelFactory::new(
&lb_config).await.unwrap().get_channel().unwrap();
- let mut signer = RemoteSigner::new(channel, self.buffer_size, self.token.clone());
+ let mut signer = RemoteSigner::new(channel, self.buffer_size);
//split file
let send_handlers = files.into_iter().map(|file|{
let task_split_s = split_s.clone();
diff --git a/src/client/file_handler/checksum.rs b/src/client/file_handler/checksum.rs
index 4d1333fb5cb64a97c084b473611c7fabe8a00305..8baa303d9364055c7fb9f8091927c3e5c7062b68 100644
--- a/src/client/file_handler/checksum.rs
+++ b/src/client/file_handler/checksum.rs
@@ -103,7 +103,7 @@ mod test {
let result = handler.validate_options(&options);
assert!(result.is_ok());
- options.insert(options::KEY_TYPE.to_string(), KeyType::X509.to_string());
+ options.insert(options::KEY_TYPE.to_string(), KeyType::X509EE.to_string());
let result = handler.validate_options(&options);
assert!(result.is_err());
assert_eq!(
diff --git a/src/client/file_handler/efi.rs b/src/client/file_handler/efi.rs
index 1b759073b2aec243024acb7b0c262032b3669a86..9b9aa9589486d276901a8e66bd3a129be1a619ff 100644
--- a/src/client/file_handler/efi.rs
+++ b/src/client/file_handler/efi.rs
@@ -29,7 +29,7 @@ impl FileHandler for EfiFileHandler {
}
if let Some(key_type) = sign_options.get(options::KEY_TYPE) {
- if key_type != KeyType::X509.to_string().as_str() {
+ if key_type != KeyType::X509EE.to_string().as_str() {
return Err(Error::InvalidArgumentError(
"EFI image only support x509 key type".to_string(),
));
diff --git a/src/client/file_handler/kernel_module.rs b/src/client/file_handler/kernel_module.rs
index 0a6a701e7046032744153a35923d13eb329ee187..6e6b706e76b0f20e3723d5bf93282ea03d948db3 100644
--- a/src/client/file_handler/kernel_module.rs
+++ b/src/client/file_handler/kernel_module.rs
@@ -154,7 +154,7 @@ impl KernelModuleFileHandler {
impl FileHandler for KernelModuleFileHandler {
fn validate_options(&self, sign_options: &HashMap) -> Result<()> {
if let Some(key_type) = sign_options.get(options::KEY_TYPE) {
- if key_type != KeyType::X509.to_string().as_str() {
+ if key_type != KeyType::X509EE.to_string().as_str() {
return Err(Error::InvalidArgumentError(
"kernel module file only support x509 signature".to_string(),
));
@@ -305,7 +305,7 @@ mod test {
"invalid argument: kernel module file only support x509 signature"
);
- options.insert(options::KEY_TYPE.to_string(), KeyType::X509.to_string());
+ options.insert(options::KEY_TYPE.to_string(), KeyType::X509EE.to_string());
options.insert(options::SIGN_TYPE.to_string(), SignType::Authenticode.to_string());
let result = handler.validate_options(&options);
assert!(result.is_err());
diff --git a/src/client/file_handler/rpm.rs b/src/client/file_handler/rpm.rs
index aa6dba6db8e41e2f1adb453aa93983f12a8f88fb..ee34b519e7899ad489895418e54f72914c2412c5 100644
--- a/src/client/file_handler/rpm.rs
+++ b/src/client/file_handler/rpm.rs
@@ -137,7 +137,7 @@ impl FileHandler for RpmFileHandler {
mod test {
use super::*;
use std::env;
- use std::fs;
+
use std::io::Write;
fn get_signed_rpm() -> Result {
@@ -167,7 +167,7 @@ mod test {
#[test]
fn test_validate_options() {
let mut options = HashMap::new();
- options.insert(options::KEY_TYPE.to_string(), KeyType::X509.to_string());
+ options.insert(options::KEY_TYPE.to_string(), KeyType::X509EE.to_string());
let handler = RpmFileHandler::new();
let result = handler.validate_options(&options);
assert!(result.is_err());
@@ -206,7 +206,7 @@ mod test {
let mut sign_options = HashMap::new();
let file_handler = RpmFileHandler::new();
let path = generate_invalid_rpm().expect("generate invalid rpm failed");
- let raw_content = file_handler.split_data(&path, &mut sign_options).await.expect_err("split invalid rpm file would failed");
+ let _raw_content = file_handler.split_data(&path, &mut sign_options).await.expect_err("split invalid rpm file would failed");
}
#[tokio::test]
@@ -215,7 +215,7 @@ mod test {
let file_handler = RpmFileHandler::new();
let path = generate_signed_rpm().expect("generate signed rpm failed");
let fake_signature = vec![vec![1,2,3,4], vec![1,2,3,4]];
- let raw_content = file_handler.assemble_data(&path, fake_signature, &env::temp_dir(), &mut sign_options).await.expect("assemble data failed");
+ let _raw_content = file_handler.assemble_data(&path, fake_signature, &env::temp_dir(), &mut sign_options).await.expect("assemble data failed");
}
}
diff --git a/src/client/worker/signer.rs b/src/client/worker/signer.rs
index 8c6afafdd702f81f440bf1d2c265deb7c0d003cf..815e8fee394c8076dfb38d40966f4f713ee770a3 100644
--- a/src/client/worker/signer.rs
+++ b/src/client/worker/signer.rs
@@ -34,17 +34,15 @@ use std::io::{Cursor, Read};
pub struct RemoteSigner {
client: SignatrustClient,
buffer_size: usize,
- token: String
}
impl RemoteSigner {
- pub fn new(channel: Channel, buffer_size: usize, token: String) -> Self {
+ pub fn new(channel: Channel, buffer_size: usize) -> Self {
Self {
client: SignatrustClient::new(channel),
buffer_size,
- token,
}
}
}
@@ -68,7 +66,6 @@ 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()
});
}
let result = self.client.sign_stream(
diff --git a/src/control_admin_entrypoint.rs b/src/control_admin_entrypoint.rs
index 6e42b6d6a1fded97afe044b824547aa388f316bf..6995f2cd90646435b5fd7529f83dcfd9c3bbd549 100644
--- a/src/control_admin_entrypoint.rs
+++ b/src/control_admin_entrypoint.rs
@@ -21,7 +21,7 @@ use std::env;
use validator::Validate;
use chrono::{Duration, Utc};
use crate::util::error::{Result};
-use crate::util::sign::KeyType;
+use crate::domain::datakey::entity::KeyType as EntityKeyTpe;
use clap::{Parser, Subcommand};
use clap::{Args};
use tokio_util::sync::CancellationToken;
@@ -29,6 +29,8 @@ use crate::domain::datakey::entity::{DataKey};
use crate::domain::user::entity::User;
use crate::presentation::handler::control::model::datakey::dto::{CreateDataKeyDTO};
use crate::presentation::handler::control::model::user::dto::UserIdentity;
+use std::str::FromStr;
+
mod util;
mod infra;
@@ -116,12 +118,14 @@ pub struct CommandGenerateKeys {
#[arg(help = "specify the 'CountryName' used for x509 key generation. ")]
param_x509_country_name: Option,
#[arg(long)]
+ #[arg(help = "specify the name of the CA or ICA which used for cert issuing. ")]
+ param_x509_parent_name: Option,
+ #[arg(long)]
#[arg(help = "specify the email of admin which this key bounds to")]
email: String,
#[arg(long)]
- #[arg(value_enum)]
#[arg(help = "specify th type of key")]
- key_type: KeyType,
+ key_type: String,
}
fn generate_keys_parameters(command: &CommandGenerateKeys) -> HashMap {
@@ -129,10 +133,11 @@ fn generate_keys_parameters(command: &CommandGenerateKeys) -> HashMap Result<()> {
let user = control_server.get_user_by_email(&generate_keys.email).await?;
let now = Utc::now();
- let key = CreateDataKeyDTO {
+ let mut key = CreateDataKeyDTO {
name: generate_keys.name.clone(),
description: generate_keys.description.clone(),
- visibility: "public".to_string(),
attributes: generate_keys_parameters(&generate_keys),
- key_type: generate_keys.key_type.to_string(),
+ key_type: generate_keys.key_type.clone(),
+ parent_id: None,
expire_at: format!("{}", now + Duration::days(30)),
};
+ if let Some(id) = generate_keys.param_x509_parent_name {
+ let data_key = control_server.get_key_by_name(&id).await?;
+ key.parent_id = Some(data_key.id);
+ }
key.validate()?;
let keys = control_server.create_keys(&mut DataKey::create_from(key, UserIdentity::from(user))?).await?;
diff --git a/src/domain/datakey/entity.rs b/src/domain/datakey/entity.rs
index f9a9bf1f13502c45104f6a6c2b8b54a1620298bc..23cb90028b3c26ad5d9b10003a283fd10036cef8 100644
--- a/src/domain/datakey/entity.rs
+++ b/src/domain/datakey/entity.rs
@@ -26,6 +26,8 @@ use std::str::FromStr;
use crate::domain::encryption_engine::EncryptionEngine;
+pub const INFRA_CONFIG_DOMAIN_NAME: &str = "domain_name";
+
#[derive(Debug, Clone, Default, PartialEq)]
@@ -33,6 +35,8 @@ pub enum KeyState {
Enabled,
#[default]
Disabled,
+ PendingRevoke,
+ Revoked,
PendingDelete,
Deleted
}
@@ -44,7 +48,9 @@ impl FromStr for KeyState {
match s {
"enabled" => Ok(KeyState::Enabled),
"disabled" => Ok(KeyState::Disabled),
- "pending" => Ok(KeyState::PendingDelete),
+ "pending_revoke" => Ok(KeyState::PendingRevoke),
+ "revoked" => Ok(KeyState::Revoked),
+ "pending_delete" => Ok(KeyState::PendingDelete),
"deleted" => Ok(KeyState::Deleted),
_ => Err(Error::UnsupportedTypeError(format!("unsupported data key state {}", s))),
}
@@ -52,20 +58,75 @@ impl FromStr for KeyState {
}
impl Display for KeyState {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
KeyState::Enabled => write!(f, "enabled"),
KeyState::Disabled => write!(f, "disabled"),
- KeyState::PendingDelete => write!(f, "pending"),
+ KeyState::PendingRevoke => write!(f, "pending_revoke"),
+ KeyState::Revoked => write!(f, "revoked"),
+ KeyState::PendingDelete => write!(f, "pending_delete"),
KeyState::Deleted => write!(f, "deleted"),
}
}
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Eq, Hash, PartialEq)]
+pub enum KeyAction {
+ Revoke,
+ CancelRevoke,
+ Delete,
+ CancelDelete,
+ Disable,
+ Enable,
+ IssueCert,
+ Sign,
+ Read,
+}
+
+impl FromStr for KeyAction {
+ type Err = Error;
+
+ fn from_str(s: &str) -> Result {
+ match s {
+ "revoke" => Ok(KeyAction::Revoke),
+ "cancel_revoke" => Ok(KeyAction::CancelRevoke),
+ "delete" => Ok(KeyAction::Delete),
+ "cancel_delete" => Ok(KeyAction::CancelDelete),
+ "disable" => Ok(KeyAction::Disable),
+ "enable" => Ok(KeyAction::Enable),
+ "issue_cert" => Ok(KeyAction::IssueCert),
+ "sign" => Ok(KeyAction::Sign),
+ "read" => Ok(KeyAction::Read),
+ _ => Err(Error::UnsupportedTypeError(format!("unsupported data key action {}", s))),
+ }
+ }
+}
+
+impl Display for KeyAction {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ match self {
+ KeyAction::Revoke => write!(f, "revoke"),
+ KeyAction::CancelRevoke => write!(f, "cancel_revoke"),
+ KeyAction::Delete => write!(f, "delete"),
+ KeyAction::CancelDelete => write!(f, "cancel_delete"),
+ KeyAction::Disable => write!(f, "disable"),
+ KeyAction::Enable => write!(f, "enable"),
+ KeyAction::IssueCert => write!(f, "issue_cert"),
+ KeyAction::Read => write!(f, "read"),
+ KeyAction::Sign => write!(f, "sign"),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub enum KeyType {
OpenPGP,
- X509
+ // X509 Certificate Authority
+ X509CA,
+ // X509 Intermediate Certificate Authority
+ X509ICA,
+ // X509 End Entity
+ X509EE,
}
impl FromStr for KeyType {
@@ -74,7 +135,9 @@ impl FromStr for KeyType {
fn from_str(s: &str) -> Result {
match s {
"pgp" => Ok(KeyType::OpenPGP),
- "x509" => Ok(KeyType::X509),
+ "x509ca" => Ok(KeyType::X509CA),
+ "x509ica" => Ok(KeyType::X509ICA),
+ "x509ee" => Ok(KeyType::X509EE),
_ => Err(Error::UnsupportedTypeError(format!("unsupported data key type {}", s))),
}
}
@@ -84,11 +147,101 @@ impl Display for KeyType {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
KeyType::OpenPGP => write!(f, "pgp"),
- KeyType::X509 => write!(f, "x509"),
+ KeyType::X509CA => write!(f, "x509ca"),
+ KeyType::X509ICA => write!(f, "x509ica"),
+ KeyType::X509EE => write!(f, "x509ee"),
}
}
}
+#[derive(Debug, Clone)]
+pub struct X509CRL {
+ pub id: i32,
+ pub ca_id: i32,
+ pub data: Vec,
+ pub create_at: DateTime,
+ pub update_at: DateTime,
+}
+
+impl X509CRL {
+ pub fn new(ca_id: i32, data: Vec, create_at: DateTime, update_at: DateTime) -> Self {
+ X509CRL {
+ id: 0,
+ ca_id,
+ data,
+ create_at,
+ update_at,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum X509RevokeReason {
+ Unspecified,
+ KeyCompromise,
+ CACompromise,
+ AffiliationChanged,
+ Superseded,
+ CessationOfOperation,
+ CertificateHold,
+ PrivilegeWithdrawn,
+ AACompromise,
+}
+
+impl FromStr for X509RevokeReason {
+ type Err = Error;
+
+ fn from_str(s: &str) -> std::result::Result {
+ match s {
+ "unspecified" => Ok(X509RevokeReason::Unspecified),
+ "key_compromise" => Ok(X509RevokeReason::KeyCompromise),
+ "ca_compromise" => Ok(X509RevokeReason::CACompromise),
+ "affiliation_changed" => Ok(X509RevokeReason::AffiliationChanged),
+ "superseded" => Ok(X509RevokeReason::Superseded),
+ "cessation_of_operation" => Ok(X509RevokeReason::CessationOfOperation),
+ "certificate_hold" => Ok(X509RevokeReason::CertificateHold),
+ "privilege_withdrawn" => Ok(X509RevokeReason::PrivilegeWithdrawn),
+ "aa_compromise" => Ok(X509RevokeReason::AACompromise),
+ _ => Err(Error::UnsupportedTypeError(format!("unsupported x509 revoke reason {}", s))),
+ }
+ }
+}
+
+impl Display for X509RevokeReason {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ match self {
+ X509RevokeReason::Unspecified => write!(f, "unspecified"),
+ X509RevokeReason::KeyCompromise => write!(f, "key_compromise"),
+ X509RevokeReason::CACompromise => write!(f, "ca_compromise"),
+ X509RevokeReason::AffiliationChanged => write!(f, "affiliation_changed"),
+ X509RevokeReason::Superseded => write!(f, "superseded"),
+ X509RevokeReason::CessationOfOperation => write!(f, "cessation_of_operation"),
+ X509RevokeReason::CertificateHold => write!(f, "certificate_hold"),
+ X509RevokeReason::PrivilegeWithdrawn => write!(f, "privilege_withdrawn"),
+ X509RevokeReason::AACompromise => write!(f, "aa_compromise"),
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct ParentKey {
+ pub name: String,
+ pub private_key: Vec,
+ pub public_key: Vec,
+ pub certificate: Vec,
+ pub attributes: HashMap,
+}
+
+#[derive(Debug, Clone)]
+pub struct RevokedKey {
+ pub id: i32,
+ pub key_id: i32,
+ pub ca_id: i32,
+ pub reason: X509RevokeReason,
+ pub create_at: DateTime,
+ pub serial_number: Option
+}
+
#[derive(Debug, Clone)]
pub struct DataKey {
pub id: i32,
@@ -98,7 +251,9 @@ pub struct DataKey {
pub user: i32,
pub attributes: HashMap,
pub key_type: KeyType,
+ pub parent_id: Option,
pub fingerprint: String,
+ pub serial_number: Option,
pub private_key: Vec,
pub public_key: Vec,
pub certificate: Vec,
@@ -107,6 +262,8 @@ pub struct DataKey {
pub key_state: KeyState,
pub user_email: Option,
pub request_delete_users: Option,
+ pub request_revoke_users: Option,
+ pub parent_key: Option
}
impl ExtendableAttributes for DataKey {
@@ -134,23 +291,47 @@ impl Identity for DataKey {
}
}
+#[derive(Clone)]
+pub struct SecParentDateKey {
+ pub name: String,
+ pub private_key: SecVec,
+ pub public_key: SecVec,
+ pub certificate: SecVec,
+ pub attributes: HashMap
+}
+
pub struct SecDataKey {
+ pub name: String,
pub private_key: SecVec,
pub public_key: SecVec,
pub certificate: SecVec,
pub identity: String,
- pub attributes: HashMap
+ pub attributes: HashMap,
+ pub parent: Option
}
impl SecDataKey {
pub async fn load(data_key: &DataKey, engine: &Box) -> Result {
- Ok(Self {
+ let mut sec_datakey = Self {
+ name: data_key.name.clone(),
private_key: SecVec::new(engine.decode(data_key.private_key.clone()).await?),
public_key: SecVec::new(engine.decode(data_key.public_key.clone()).await?),
certificate: SecVec::new(engine.decode(data_key.certificate.clone()).await?),
identity: data_key.get_identity(),
attributes: data_key.attributes.clone(),
- })
+ parent: None,
+ };
+ if let Some(parent_key) = data_key.parent_key.clone() {
+ let sec_parent_key = SecParentDateKey {
+ name: parent_key.name,
+ private_key: SecVec::new(engine.decode(parent_key.private_key).await?),
+ public_key: SecVec::new(engine.decode(parent_key.public_key).await?),
+ certificate: SecVec::new(engine.decode(parent_key.certificate).await?),
+ attributes: parent_key.attributes,
+ };
+ sec_datakey.parent = Some(sec_parent_key);
+ }
+ Ok(sec_datakey)
}
}
@@ -159,13 +340,15 @@ pub struct DataKeyContent {
pub public_key: Vec,
pub certificate: Vec,
pub fingerprint: String,
+ pub serial_number: Option,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub enum Visibility {
#[default]
Public,
- Private,
+ //NOTE: We don't support private key now.
+ //Private
}
impl FromStr for Visibility {
@@ -174,7 +357,6 @@ 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))),
}
}
@@ -184,7 +366,6 @@ 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 8584bdb83e8ffc853192928c9f5c8ab9dcbcb40a..9623e64a69d7abcd8912e565caaed7c863076672 100644
--- a/src/domain/datakey/repository.rs
+++ b/src/domain/datakey/repository.rs
@@ -17,19 +17,26 @@
use super::entity::DataKey;
use crate::util::error::Result;
use async_trait::async_trait;
-use crate::domain::datakey::entity::{KeyState};
+use chrono::Duration;
+use crate::domain::datakey::entity::{KeyState, KeyType, RevokedKey, X509CRL, X509RevokeReason};
#[async_trait]
pub trait Repository: Send + Sync {
async fn create(&self, data_key: DataKey) -> Result;
- async fn get_all_keys(&self) -> Result>;
- async fn get_public_keys(&self) -> Result>;
- async fn get_private_keys(&self, user_id: i32) -> Result>;
+ async fn delete(&self, id: i32) -> Result<()>;
+ async fn get_all_keys(&self, key_type: Option) -> Result>;
async fn get_by_id(&self, id: i32) -> Result;
- async fn get_by_name(&self, name: &String) -> 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 delete_private_key(&self, id: i32, user_id: i32) -> Result<()>;
- async fn request_delete_public_key(&self, user_id: i32, user_email: String, id: i32) -> Result<()>;
- async fn cancel_delete_public_key(&self, user_id: i32, id: i32) -> 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 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
+ async fn get_x509_crl_by_ca_id(&self, id: i32) -> Result;
+ async fn upsert_x509_crl(&self, crl: X509CRL) -> Result<()>;
+ async fn get_keys_for_crl_update(&self, duration: Duration) -> Result>;
+ async fn get_revoked_serial_number_by_parent_id(&self, id: i32) -> Result>;
}
diff --git a/src/domain/sign_plugin.rs b/src/domain/sign_plugin.rs
index 89dc83940d5f5c52318469dba8d709076df7429c..6ad2557fa5bd7e470e24d823fe2f63ac910908d8 100644
--- a/src/domain/sign_plugin.rs
+++ b/src/domain/sign_plugin.rs
@@ -16,7 +16,8 @@
use crate::util::error::Result;
use std::collections::HashMap;
-use crate::domain::datakey::entity::{DataKey, DataKeyContent, SecDataKey};
+use chrono::{DateTime, Utc};
+use crate::domain::datakey::entity::{DataKey, DataKeyContent, KeyType, RevokedKey, SecDataKey};
pub trait SignPlugins: Send + Sync {
fn new(db: SecDataKey) -> Result
@@ -32,10 +33,7 @@ pub trait SignPlugins: Send + Sync {
) -> HashMap
where
Self: Sized;
- fn generate_keys(
- attributes: &HashMap,
- ) -> Result
- where
- Self: Sized;
+ fn generate_keys(&self, key_type: &KeyType, infra_configs: &HashMap) -> Result;
fn sign(&self, content: Vec, options: HashMap) -> Result>;
+ fn generate_crl_content(&self, revoked_keys: Vec, last_update: DateTime, next_update: DateTime) -> Result>;
}
diff --git a/src/domain/sign_service.rs b/src/domain/sign_service.rs
index d3fe97617b70b556d2b1af324788453fdb6326a9..b4e53ab42ba0a5124107215684f1c28f204e2127 100644
--- a/src/domain/sign_service.rs
+++ b/src/domain/sign_service.rs
@@ -18,8 +18,9 @@ use crate::util::error::{Result, Error};
use std::collections::HashMap;
use std::str::FromStr;
-use crate::domain::datakey::entity::DataKey;
+use crate::domain::datakey::entity::{DataKey, RevokedKey};
use async_trait::async_trait;
+use chrono::{DateTime, Utc};
#[derive(Debug, PartialEq)]
pub enum SignBackendType {
@@ -44,6 +45,7 @@ pub trait SignBackend: Send + Sync{
async fn rotate_key(&mut self) -> Result;
async fn sign(&self, data_key: &DataKey, content: Vec, options: HashMap) -> Result>;
async fn decode_public_keys(&self, data_key: &mut DataKey) -> Result<()>;
+ async fn generate_crl_content(&self, data_key: &DataKey, revoked_keys: Vec, last_update: DateTime, next_update: DateTime) -> Result>;
}
diff --git a/src/infra/database/model/datakey/dto.rs b/src/infra/database/model/datakey/dto.rs
index b6d55590bb7539f675d86771124d1b0d49ddd1ee..5dcfad08384bb34cffcb9fb9f5cc127950f658d6 100644
--- a/src/infra/database/model/datakey/dto.rs
+++ b/src/infra/database/model/datakey/dto.rs
@@ -14,7 +14,7 @@
*
*/
-use crate::domain::datakey::entity::{DataKey, KeyState, Visibility};
+use crate::domain::datakey::entity::{DataKey, KeyState, Visibility, X509CRL};
use crate::domain::datakey::entity::KeyType;
use crate::domain::datakey::traits::ExtendableAttributes;
use crate::util::error::{Error};
@@ -33,15 +33,23 @@ pub(super) struct DataKeyDTO {
pub user: i32,
pub attributes: String,
pub key_type: String,
+ pub parent_id: Option,
pub fingerprint: String,
+ pub serial_number: Option,
pub private_key: String,
pub public_key: String,
pub certificate: String,
pub create_at: DateTime,
pub expire_at: DateTime,
pub key_state: String,
+ #[sqlx(default)]
pub user_email: Option,
+ #[sqlx(default)]
pub request_delete_users: Option,
+ #[sqlx(default)]
+ pub request_revoke_users: Option,
+ #[sqlx(default)]
+ pub x509_crl_update_at: Option>
}
@@ -57,7 +65,9 @@ impl TryFrom for DataKey {
user: dto.user,
attributes: serde_json::from_str(dto.attributes.as_str())?,
key_type: KeyType::from_str(&dto.key_type)?,
+ parent_id: dto.parent_id,
fingerprint: dto.fingerprint.clone(),
+ serial_number: dto.serial_number,
private_key: key::decode_hex_string_to_u8(&dto.private_key),
public_key: key::decode_hex_string_to_u8(&dto.public_key),
certificate: key::decode_hex_string_to_u8(&dto.certificate),
@@ -65,7 +75,9 @@ impl TryFrom for DataKey {
expire_at: dto.expire_at,
key_state: KeyState::from_str(&dto.key_state)?,
user_email: dto.user_email,
- request_delete_users: dto.request_delete_users
+ request_delete_users: dto.request_delete_users,
+ request_revoke_users: dto.request_revoke_users,
+ parent_key: None,
})
}
}
@@ -82,7 +94,9 @@ impl TryFrom for DataKeyDTO {
user: data_key.user,
attributes: data_key.serialize_attributes()?,
key_type: data_key.key_type.to_string(),
+ parent_id: data_key.parent_id,
fingerprint: data_key.fingerprint.clone(),
+ serial_number: data_key.serial_number,
private_key: key::encode_u8_to_hex_string(
&data_key.private_key
),
@@ -96,7 +110,46 @@ impl TryFrom for DataKeyDTO {
expire_at: data_key.expire_at,
key_state: data_key.key_state.to_string(),
user_email: None,
- request_delete_users: None
+ request_delete_users: None,
+ request_revoke_users: None,
+ x509_crl_update_at: None,
+ })
+ }
+}
+
+#[derive(Debug, FromRow)]
+pub struct X509CRLDTO {
+ pub id: i32,
+ pub ca_id: i32,
+ pub data: String,
+ pub create_at: DateTime,
+ pub update_at: DateTime,
+}
+
+impl TryFrom for X509CRL {
+ type Error = Error;
+
+ fn try_from(value: X509CRLDTO) -> Result {
+ Ok(X509CRL {
+ id: value.id,
+ ca_id: value.ca_id,
+ data: key::decode_hex_string_to_u8(&value.data),
+ create_at: value.create_at,
+ update_at: value.update_at,
+ })
+ }
+}
+
+impl TryFrom for X509CRLDTO {
+ type Error = Error;
+
+ fn try_from(value: X509CRL) -> Result {
+ Ok(X509CRLDTO {
+ id: value.id,
+ ca_id: value.ca_id,
+ data: key::encode_u8_to_hex_string(&value.data),
+ create_at: value.create_at,
+ update_at: value.update_at,
})
}
}
diff --git a/src/infra/database/model/datakey/repository.rs b/src/infra/database/model/datakey/repository.rs
index 6f0f7012fa4d52b9ee5841aa77a63eddb673aa52..32aa0278047ea4850bed7abdd4ac12ad06667844 100644
--- a/src/infra/database/model/datakey/repository.rs
+++ b/src/infra/database/model/datakey/repository.rs
@@ -16,16 +16,19 @@
use super::dto::DataKeyDTO;
use crate::infra::database::pool::DbPool;
-use crate::domain::datakey::entity::{DataKey, KeyState, Visibility};
+use crate::domain::datakey::entity::{DataKey, KeyState, KeyType, ParentKey, RevokedKey, X509CRL, X509RevokeReason};
use crate::domain::datakey::repository::Repository;
use crate::util::error::{Result};
use async_trait::async_trait;
use std::boxed::Box;
-use chrono::{Utc};
+use chrono::Duration;
+use chrono::Utc;
use sqlx::{MySql, Transaction};
-use crate::infra::database::model::request_delete::dto::RequestDeleteDTO;
+use crate::infra::database::model::datakey::dto::X509CRLDTO;
+use crate::infra::database::model::request_delete::dto::{PendingOperationDTO, RequestType, RevokedKeyDTO};
+use crate::util::error;
-const DELETE_THRESHOLD: i32 = 3;
+const PENDING_THRESHOLD: i32 = 3;
#[derive(Clone)]
pub struct DataKeyRepository {
@@ -39,21 +42,44 @@ impl DataKeyRepository {
}
}
- async fn create_request_delete(&self, user_id: i32, user_email: String, id: i32, tx: &mut Transaction<'_, MySql>) -> Result<()> {
- let _ : Option = sqlx::query_as("INSERT IGNORE INTO request_delete(user_id, key_id, user_email, create_at) VALUES (?, ?, ?, ?)")
- .bind(user_id)
- .bind(id)
- .bind(user_email)
- .bind(Utc::now())
+ async fn create_pending_operation(&self, pending_operation: PendingOperationDTO, tx: &mut Transaction<'_, MySql>) -> Result<()> {
+ let _ : Option = sqlx::query_as("INSERT IGNORE INTO pending_operation(user_id, key_id, user_email, create_at, request_type) VALUES (?, ?, ?, ?, ?)")
+ .bind(pending_operation.user_id)
+ .bind(pending_operation.key_id)
+ .bind(pending_operation.user_email)
+ .bind(pending_operation.create_at)
+ .bind(pending_operation.request_type.to_string())
.fetch_optional(tx)
.await?;
Ok(())
}
- async fn delete_request_delete(&self, user_id: i32, id: i32, tx: &mut Transaction<'_, MySql>) -> Result<()> {
- let _ : Option = sqlx::query_as("DELETE FROM request_delete WHERE user_id = ? AND key_id = ?")
+ async fn delete_pending_operation(&self, user_id: i32, id: i32, request_type: RequestType, tx: &mut Transaction<'_, MySql>) -> Result<()> {
+ let _ : Option = sqlx::query_as("DELETE FROM pending_operation WHERE user_id = ? AND key_id = ? and request_type = ?")
.bind(user_id)
.bind(id)
+ .bind(request_type.to_string())
+ .fetch_optional(tx)
+ .await?;
+ Ok(())
+ }
+
+ async fn create_revoke_record(&self, key_id: i32, ca_id: i32, reason: X509RevokeReason, tx: &mut Transaction<'_, MySql>) -> Result<()> {
+ let revoked = RevokedKeyDTO::new(key_id, ca_id, reason);
+ let _ : Option = sqlx::query_as("INSERT IGNORE INTO x509_keys_revoked(ca_id, key_id, create_at, reason) VALUES (?, ?, ?, ?)")
+ .bind(revoked.ca_id)
+ .bind(revoked.key_id)
+ .bind(revoked.create_at)
+ .bind(revoked.reason)
+ .fetch_optional(tx)
+ .await?;
+ Ok(())
+ }
+
+ async fn delete_revoke_record(&self, key_id: i32, ca_id: i32, tx: &mut Transaction<'_, MySql>) -> Result<()> {
+ let _ : Option = sqlx::query_as("DELETE FROM x509_keys_revoked WHERE key_id = ? AND ca_id = ?")
+ .bind(key_id)
+ .bind(ca_id)
.fetch_optional(tx)
.await?;
Ok(())
@@ -64,7 +90,7 @@ impl DataKeyRepository {
impl Repository for DataKeyRepository {
async fn create(&self, data_key: DataKey) -> Result {
let dto = DataKeyDTO::try_from(data_key)?;
- let record : u64 = sqlx::query("INSERT INTO data_key(name, description, user, attributes, key_type, fingerprint, private_key, public_key, certificate, create_at, expire_at, key_state, visibility) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
+ let record : u64 = sqlx::query("INSERT INTO data_key(name, description, user, attributes, key_type, fingerprint, private_key, public_key, certificate, create_at, expire_at, key_state, visibility, parent_id, serial_number) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.bind(&dto.name)
.bind(&dto.description)
.bind(dto.user)
@@ -78,23 +104,78 @@ impl Repository for DataKeyRepository {
.bind(dto.expire_at)
.bind(dto.key_state)
.bind(dto.visibility)
+ .bind(dto.parent_id)
+ .bind(dto.serial_number)
.execute(&self.db_pool)
.await?.last_insert_id();
- return self.get_by_id(record as i32).await
+ let mut datakey = self.get_by_id(record as i32).await?;
+ //fetch parent key if 'parent_id' exists.
+ if let Some(parent) = datakey.parent_id {
+ let result = self.get_by_id(parent).await;
+ match result {
+ Ok(parent) => {
+ datakey.parent_key = Some(ParentKey {
+ name: parent.name,
+ private_key: parent.private_key.clone(),
+ public_key: parent.public_key.clone(),
+ certificate: parent.certificate.clone(),
+ attributes: parent.attributes
+ })
+ }
+ Err(error::Error::NotFoundError) => {
+ let _ = self.delete(record as i32).await;
+ return Err(error::Error::InvalidArgumentError("parent key does not exist".to_string()));
+ }
+ _ => {
+ let _ = self.delete(record as i32).await;
+ return Err(error::Error::DatabaseError("unable to find parent key".to_string()));
+ }
+ }
+ }
+ Ok(datakey)
}
- async fn get_public_keys(&self) -> Result> {
- let dtos: Vec = sqlx::query_as(
- "SELECT D.*, U.email AS user_email, GROUP_CONCAT(R.user_email) as request_delete_users \
+ async fn delete(&self, id: i32) -> Result<()> {
+ let _: Option = sqlx::query_as("DELETE FROM data_key WHERE id = ?")
+ .bind(id)
+ .fetch_optional(&self.db_pool)
+ .await?;
+ Ok(())
+ }
+
+ async fn get_all_keys(&self, key_type: Option) -> Result> {
+ let dtos: Vec = match key_type {
+ None => {
+ sqlx::query_as(
+ "SELECT D.*, U.email AS user_email, GROUP_CONCAT(R.user_email) as request_delete_users, \
+ GROUP_CONCAT(K.user_email) as request_revoke_users \
FROM data_key D \
INNER JOIN user U ON D.user = U.id \
- LEFT JOIN request_delete R ON D.id = R.key_id \
- WHERE D.key_state != ? and D.visibility = ? \
+ 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 != ? \
GROUP BY D.id")
- .bind(KeyState::Deleted.to_string())
- .bind(Visibility::Public.to_string())
- .fetch_all(&self.db_pool)
- .await?;
+ .bind(KeyState::Deleted.to_string())
+ .fetch_all(&self.db_pool)
+ .await?
+ }
+ Some(key_t) => {
+ sqlx::query_as(
+ "SELECT D.*, U.email AS user_email, GROUP_CONCAT(R.user_email) as request_delete_users, \
+ GROUP_CONCAT(K.user_email) as request_revoke_users \
+ FROM data_key D \
+ 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 != ? AND \
+ D.key_type = ? \
+ GROUP BY D.id")
+ .bind(KeyState::Deleted.to_string())
+ .bind(key_t.to_string())
+ .fetch_all(&self.db_pool)
+ .await?
+ }
+ };
let mut results = vec![];
for dto in dtos.into_iter() {
results.push(DataKey::try_from(dto)?);
@@ -102,49 +183,57 @@ impl Repository for DataKeyRepository {
Ok(results)
}
- async fn get_all_keys(&self) -> Result> {
+ async fn get_keys_for_crl_update(&self, duration: Duration) -> Result> {
+ let now = Utc::now();
let dtos: Vec = sqlx::query_as(
- "SELECT D.*, U.email AS user_email, GROUP_CONCAT(R.user_email) as request_delete_users \
+ "SELECT D.id, D.name, D.description, D.user, D.attributes, D.key_type, D.fingerprint, D.private_key, D.public_key, D.certificate, D.create_at, D.expire_at, D.key_state, D.visibility, D.parent_id, D.serial_number, R.update_at AS x509_crl_update_at \
FROM data_key D \
- INNER JOIN user U ON D.user = U.id \
- LEFT JOIN request_delete R ON D.id = R.key_id \
- WHERE D.key_state != ? \
- GROUP BY D.id")
+ LEFT JOIN x509_crl_content R ON D.id = R.ca_id \
+ WHERE (D.key_type = ? OR D.key_type = ?) AND D.key_state != ?")
+ .bind(KeyType::X509ICA.to_string())
+ .bind(KeyType::X509CA.to_string())
.bind(KeyState::Deleted.to_string())
.fetch_all(&self.db_pool)
.await?;
let mut results = vec![];
for dto in dtos.into_iter() {
- results.push(DataKey::try_from(dto)?);
+ if dto.x509_crl_update_at.is_none() {
+ results.push(DataKey::try_from(dto)?);
+ } else {
+ let update_at = dto.x509_crl_update_at.unwrap();
+ if update_at + duration <= now {
+ results.push(DataKey::try_from(dto)?);
+ }
+ }
}
Ok(results)
}
- async fn get_private_keys(&self, user_id: i32) -> Result> {
- let dtos: Vec = sqlx::query_as(
- "SELECT D.*, U.email AS user_email, GROUP_CONCAT(R.user_email) as request_delete_users \
- FROM data_key D \
- INNER JOIN user U ON D.user = U.id \
- LEFT JOIN request_delete R ON D.id = R.key_id \
- WHERE D.key_state != ? and D.visibility = ? and D.user = ? \
- GROUP BY D.id")
- .bind(KeyState::Deleted.to_string())
- .bind(Visibility::Private.to_string())
- .bind(user_id)
+
+ async fn get_revoked_serial_number_by_parent_id(&self, id: i32) -> Result> {
+ let dtos : Vec = sqlx::query_as(
+ "SELECT R.*, D.serial_number \
+ FROM x509_keys_revoked R \
+ INNER JOIN data_key D ON R.key_id = D.id \
+ WHERE R.ca_id = ? AND D.key_state = ?")
+ .bind(id)
+ .bind(KeyState::Revoked.to_string())
.fetch_all(&self.db_pool)
.await?;
let mut results = vec![];
for dto in dtos.into_iter() {
- results.push(DataKey::try_from(dto)?);
+ results.push(RevokedKey::try_from(dto)?);
}
Ok(results)
}
async fn get_by_id(&self, id: i32) -> Result {
let dto: DataKeyDTO = sqlx::query_as(
- "SELECT D.*, U.email AS user_email, GROUP_CONCAT(R.user_email) as request_delete_users \
+ "SELECT D.*, U.email AS user_email, GROUP_CONCAT(R.user_email) as request_delete_users, \
+ GROUP_CONCAT(K.user_email) as request_revoke_users \
FROM data_key D \
INNER JOIN user U ON D.user = U.id \
- LEFT JOIN request_delete R ON D.id = R.key_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.id = ? AND D.key_state != ? \
GROUP BY D.id")
.bind(id)
@@ -154,12 +243,14 @@ impl Repository for DataKeyRepository {
Ok(DataKey::try_from(dto)?)
}
- async fn get_by_name(&self, name: &String) -> Result {
+ async fn get_by_name(&self, name: &str) -> Result {
let dto: DataKeyDTO = sqlx::query_as(
- "SELECT D.*, U.email AS user_email GROUP_CONCAT(R.user_email) as request_delete_users \
+ "SELECT D.*, U.email AS user_email, GROUP_CONCAT(R.user_email) as request_delete_users, \
+ GROUP_CONCAT(K.user_email) as request_revoke_users \
FROM data_key D \
INNER JOIN user U ON D.user = U.id \
- LEFT JOIN request_delete R ON D.id = R.key_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.name = ? AND D.key_state != ? \
GROUP BY D.id")
.bind(name)
@@ -180,12 +271,30 @@ impl Repository for DataKeyRepository {
Ok(())
}
+ async fn update_key_data(&self, data_key: DataKey) -> Result<()> {
+ //Note: if the key in deleted status, it cannot be updated to other states
+ let dto = DataKeyDTO::try_from(data_key)?;
+ let _: Option = sqlx::query_as("UPDATE data_key SET serial_number = ?, fingerprint = ?, private_key = ?, public_key = ?, certificate = ? WHERE id = ? AND key_state != ?")
+ .bind(dto.serial_number)
+ .bind(dto.fingerprint)
+ .bind(dto.private_key)
+ .bind(dto.public_key)
+ .bind(dto.certificate)
+ .bind(dto.id)
+ .bind(KeyState::Deleted.to_string())
+ .fetch_optional(&self.db_pool)
+ .await?;
+ Ok(())
+ }
+
async fn get_enabled_key_by_type_and_name(&self, key_type: String, name: String) -> Result {
let dto: DataKeyDTO = sqlx::query_as(
- "SELECT D.*, U.email AS user_email, GROUP_CONCAT(R.user_email) as request_delete_users \
+ "SELECT D.*, U.email AS user_email, GROUP_CONCAT(R.user_email) as request_delete_users, \
+ GROUP_CONCAT(K.user_email) as request_revoke_users \
FROM data_key D \
INNER JOIN user U ON D.user = U.id \
- LEFT JOIN request_delete R ON D.id = R.key_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.name = ? AND D.key_type = ? AND D.key_state = ? \
GROUP BY D.id")
.bind(name)
@@ -196,59 +305,96 @@ impl Repository for DataKeyRepository {
Ok(DataKey::try_from(dto)?)
}
- async fn delete_private_key(&self, id: i32, user_id: i32) -> Result<()> {
- let _: Option = sqlx::query_as("UPDATE data_key SET key_state = ? WHERE id = ? AND visibility = ? AND user = ?")
+ async fn request_delete_key(&self, user_id: i32, user_email: String, id: i32) -> Result<()> {
+ let mut tx = self.db_pool.begin().await?;
+ //1. update key state to pending delete if needed.
+ let _: Option = sqlx::query_as(
+ "UPDATE data_key SET key_state = ? \
+ WHERE id = ?")
+ .bind(KeyState::PendingDelete.to_string())
+ .bind(id)
+ .fetch_optional(&mut tx)
+ .await?;
+ //2. add request delete record
+ let pending_delete = PendingOperationDTO::new_for_delete(id, user_id, user_email);
+ self.create_pending_operation(pending_delete, &mut tx).await?;
+ //3. delete datakey if pending delete count >= threshold
+ let _: Option = sqlx::query_as(
+ "UPDATE data_key SET key_state = ? \
+ WHERE id = ? AND ( \
+ SELECT COUNT(*) FROM pending_operation WHERE key_id = ?) >= ?")
.bind(KeyState::Deleted.to_string())
.bind(id)
- .bind(Visibility::Private.to_string())
- .bind(user_id)
- .fetch_optional(&self.db_pool)
+ .bind(id)
+ .bind(PENDING_THRESHOLD)
+ .fetch_optional(&mut tx)
.await?;
+ tx.commit().await?;
Ok(())
}
- async fn request_delete_public_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<()> {
let mut tx = self.db_pool.begin().await?;
//1. update key state to pending delete if needed.
let _: Option = sqlx::query_as(
"UPDATE data_key SET key_state = ? \
- WHERE id = ? AND visibility = ? AND key_state != ?")
- .bind(KeyState::PendingDelete.to_string())
+ WHERE id = ?")
+ .bind(KeyState::PendingRevoke.to_string())
.bind(id)
- .bind(Visibility::Public.to_string())
- .bind(KeyState::PendingDelete.to_string())
.fetch_optional(&mut tx)
.await?;
- //2. add request delete record
- self.create_request_delete(user_id, user_email, id, &mut tx).await?;
- //3. delete datakey if pending delete count >= threshold
+ //2. add request revoke pending record
+ let pending_revoke = PendingOperationDTO::new_for_revoke(id, user_id, user_email);
+ self.create_pending_operation(pending_revoke, &mut tx).await?;
+ //3. add revoked record
+ self.create_revoke_record(id, parent_id, reason, &mut tx).await?;
+ //4. mark datakey revoked if pending revoke count >= threshold
let _: Option = sqlx::query_as(
"UPDATE data_key SET key_state = ? \
- WHERE id = ? AND visibility = ? AND ( \
- SELECT COUNT(*) FROM request_delete WHERE key_id = ?) >= ?")
- .bind(KeyState::Deleted.to_string())
+ WHERE id = ? AND ( \
+ SELECT COUNT(*) FROM pending_operation WHERE key_id = ?) >= ?")
+ .bind(KeyState::Revoked.to_string())
.bind(id)
- .bind(Visibility::Public.to_string())
.bind(id)
- .bind(DELETE_THRESHOLD)
+ .bind(PENDING_THRESHOLD)
.fetch_optional(&mut tx)
.await?;
tx.commit().await?;
Ok(())
}
- async fn cancel_delete_public_key(&self, user_id: i32, id: i32) -> Result<()> {
+ async fn cancel_delete_key(&self, user_id: i32, id: i32) -> Result<()> {
let mut tx = self.db_pool.begin().await?;
//1. delete pending delete record
- self.delete_request_delete(user_id, id, &mut tx).await?;
+ self.delete_pending_operation(user_id, id, RequestType::Delete, &mut tx).await?;
//2. update status if there is not any pending delete record.
let _: Option = sqlx::query_as(
"UPDATE data_key SET key_state = ? \
- WHERE id = ? AND visibility = ? AND ( \
- SELECT COUNT(*) FROM request_delete WHERE key_id = ?) = ?")
+ WHERE id = ? AND ( \
+ SELECT COUNT(*) FROM pending_operation WHERE key_id = ?) = ?")
+ .bind(KeyState::Disabled.to_string())
+ .bind(id)
+ .bind(id)
+ .bind(0)
+ .fetch_optional(&mut tx)
+ .await?;
+ tx.commit().await?;
+ Ok(())
+ }
+
+ async fn cancel_revoke_key(&self, user_id: i32, id: i32, parent_id: i32) -> Result<()> {
+ let mut tx = self.db_pool.begin().await?;
+ //1. delete pending delete record
+ self.delete_pending_operation(user_id, id, RequestType::Revoke, &mut tx).await?;
+ //2. delete revoked record
+ self.delete_revoke_record(id, parent_id, &mut tx).await?;
+ //3. update status if there is not any pending delete record.
+ let _: Option = sqlx::query_as(
+ "UPDATE data_key SET key_state = ? \
+ WHERE id = ? AND ( \
+ SELECT COUNT(*) FROM pending_operation WHERE key_id = ?) = ?")
.bind(KeyState::Disabled.to_string())
.bind(id)
- .bind(Visibility::Public.to_string())
.bind(id)
.bind(0)
.fetch_optional(&mut tx)
@@ -256,4 +402,40 @@ impl Repository for DataKeyRepository {
tx.commit().await?;
Ok(())
}
+
+ async fn get_x509_crl_by_ca_id(&self, id: i32) -> Result {
+ let dto: X509CRLDTO = sqlx::query_as(
+ "SELECT * from x509_crl_content WHERE ca_id = ?")
+ .bind(id)
+ .fetch_one(&self.db_pool)
+ .await?;
+ Ok( X509CRL::try_from(dto)?)
+ }
+
+ async fn upsert_x509_crl(&self, crl: X509CRL) -> Result<()> {
+ let dto = X509CRLDTO::try_from(crl)?;
+ match self.get_x509_crl_by_ca_id(dto.ca_id).await {
+ Ok(_) => {
+ sqlx::query(
+ "UPDATE x509_crl_content SET data = ?, create_at = ?, update_at = ? WHERE ca_id = ?")
+ .bind(dto.data)
+ .bind(dto.create_at)
+ .bind(dto.update_at)
+ .bind(dto.ca_id)
+ .execute(&self.db_pool)
+ .await?;
+ }
+ Err(_) => {
+ sqlx::query(
+ "INSERT INTO x509_crl_content(ca_id, data, create_at, update_at) VALUES (?, ?, ?, ?)")
+ .bind(dto.ca_id)
+ .bind(dto.data)
+ .bind(dto.create_at)
+ .bind(dto.update_at)
+ .execute(&self.db_pool)
+ .await?;
+ }
+ }
+ Ok(())
+ }
}
diff --git a/src/infra/database/model/request_delete/dto.rs b/src/infra/database/model/request_delete/dto.rs
index 1f0c8f2d65842018398d9669e3bdc1948075daca..72438a86aa555c3f0dbcfc4bcaf1f5936380ec76 100644
--- a/src/infra/database/model/request_delete/dto.rs
+++ b/src/infra/database/model/request_delete/dto.rs
@@ -13,26 +13,112 @@
* * // See the Mulan PSL v2 for more details.
*
*/
+use std::fmt::{Display, Formatter};
+use std::str::FromStr;
use sqlx::FromRow;
use chrono::{DateTime, Utc};
+use crate::domain::datakey::entity::{RevokedKey, X509RevokeReason};
+use crate::util::error::Error;
+
+#[derive(Debug, Clone, PartialEq, sqlx::Type)]
+pub enum RequestType {
+ #[sqlx(rename = "delete")]
+ Delete,
+ #[sqlx(rename = "revoke")]
+ Revoke,
+}
+
+impl Display for RequestType {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ RequestType::Delete => write!(f, "delete"),
+ RequestType::Revoke => write!(f, "revoke"),
+ }
+ }
+}
+
+impl FromStr for RequestType {
+ type Err = Error;
+
+ fn from_str(s: &str) -> Result {
+ match s {
+ "delete" => Ok(RequestType::Delete),
+ "revoke" => Ok(RequestType::Revoke),
+ _ => Err(Error::UnsupportedTypeError(s.to_string()))
+ }
+ }
+}
+
+#[derive(Debug, FromRow)]
+pub struct RevokedKeyDTO {
+ pub id: i32,
+ pub key_id: i32,
+ pub ca_id: i32,
+ pub reason: String,
+ pub serial_number: Option,
+ pub create_at: DateTime,
+}
+
+impl RevokedKeyDTO {
+ pub fn new(key_id: i32, ca_id: i32, reason: X509RevokeReason) -> Self {
+ Self {
+ id: 0,
+ key_id,
+ ca_id,
+ create_at: Utc::now(),
+ reason: reason.to_string(),
+ serial_number: None,
+ }
+ }
+}
+
+impl TryFrom for RevokedKey {
+ type Error = Error;
+
+ fn try_from(dto: RevokedKeyDTO) -> Result {
+ Ok(RevokedKey {
+ id: dto.id,
+ key_id: dto.key_id,
+ ca_id: dto.ca_id,
+ reason: X509RevokeReason::from_str(&dto.reason)?,
+ create_at: dto.create_at,
+ serial_number: dto.serial_number,
+ })
+ }
+}
+
+
#[derive(Debug, FromRow)]
-pub struct RequestDeleteDTO {
+pub struct PendingOperationDTO {
pub id: i32,
pub user_id: i32,
pub key_id: i32,
+ pub request_type: RequestType,
pub user_email: String,
pub create_at: DateTime,
}
-impl RequestDeleteDTO {
- pub fn new(key_id: i32, user_id: i32, user_email: String) -> Self {
+impl PendingOperationDTO {
+ pub fn new_for_delete(key_id: i32, user_id: i32, user_email: String) -> Self {
+ Self {
+ id: 0,
+ user_id,
+ key_id,
+ user_email,
+ create_at: Utc::now(),
+ request_type: RequestType::Delete,
+ }
+ }
+
+ pub fn new_for_revoke(key_id: i32, user_id: i32, user_email: String) -> Self {
Self {
id: 0,
user_id,
key_id,
user_email,
- create_at: Utc::now()
+ create_at: Utc::now(),
+ request_type: RequestType::Revoke,
}
}
}
\ No newline at end of file
diff --git a/src/infra/encryption/dummy_engine.rs b/src/infra/encryption/dummy_engine.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4551de540275b10a2ddffe3592110ee58ef6912c
--- /dev/null
+++ b/src/infra/encryption/dummy_engine.rs
@@ -0,0 +1,44 @@
+/*
+ * // Copyright (c) 2023 Huawei Technologies Co.,Ltd. All rights reserved.
+ * //
+ * // signatrust is licensed under Mulan PSL v2.
+ * // You can use this software according to the terms and conditions of the Mulan
+ * // PSL v2.
+ * // You may obtain a copy of Mulan PSL v2 at:
+ * // http://license.coscl.org.cn/MulanPSL2
+ * // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
+ * // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * // See the Mulan PSL v2 for more details.
+ */
+
+use crate::domain::encryption_engine::EncryptionEngine;
+use crate::util::error::Result;
+use async_trait::async_trait;
+
+
+#[derive(Default)]
+pub struct DummyEngine {}
+
+#[async_trait]
+impl EncryptionEngine for DummyEngine {
+ async fn initialize(&mut self) -> Result<()> {
+ warn!("dummy engine used for encryption, please don't use it in production environment");
+ Ok(())
+ }
+
+ async fn rotate_key(&mut self) -> Result {
+ warn!("dummy engine used for encryption, please don't use it in production environment");
+ Ok(true)
+ }
+
+ async fn encode(&self, content: Vec) -> Result> {
+ warn!("dummy engine used for encryption, please don't use it in production environment");
+ Ok(content)
+ }
+
+ async fn decode(&self, content: Vec) -> Result> {
+ warn!("dummy engine used for encryption, please don't use it in production environment");
+ Ok(content)
+ }
+}
\ No newline at end of file
diff --git a/src/infra/encryption/engine.rs b/src/infra/encryption/engine.rs
index 4eb603d38f2b83c53daf4ebdfbee31c676bbd75c..1857630429a3dc8cca22b81e72d107026620013d 100644
--- a/src/infra/encryption/engine.rs
+++ b/src/infra/encryption/engine.rs
@@ -163,6 +163,9 @@ where
}
async fn encode(&self, content: Vec) -> Result> {
+ if content.is_empty() {
+ return Ok(content);
+ }
//always use latest cluster key to encode data
let mut secret = self
.encryptor
@@ -171,6 +174,9 @@ where
}
async fn decode(&self, content: Vec) -> Result> {
+ if content.is_empty() {
+ return Ok(content);
+ }
//1. obtain cluster key id from content
//2. use cluster key to decrypt data
let sec_cluster_key = self.get_used_sec_cluster_key(&content[0..KEY_SIZE]).await?;
diff --git a/src/infra/encryption/mod.rs b/src/infra/encryption/mod.rs
index 383cc5978f798043366ed3765201068dfb957024..64efc9873dd9f93c27c9cddefd5f71fbf8888871 100644
--- a/src/infra/encryption/mod.rs
+++ b/src/infra/encryption/mod.rs
@@ -1,2 +1,3 @@
pub mod algorithm;
pub mod engine;
+pub mod dummy_engine;
diff --git a/src/infra/sign_backend/memory/backend.rs b/src/infra/sign_backend/memory/backend.rs
index 7d261168d9e37b3ed1a40e9410a30726b05af0ce..a1b51b4bbd36f41cc3ede0c29f3652d6da192cbc 100644
--- a/src/infra/sign_backend/memory/backend.rs
+++ b/src/infra/sign_backend/memory/backend.rs
@@ -28,18 +28,20 @@ use crate::infra::database::pool::{DbPool};
use crate::infra::kms::factory;
use crate::infra::encryption::engine::{EncryptionEngineWithClusterKey};
use crate::domain::encryption_engine::EncryptionEngine;
-use crate::domain::datakey::entity::SecDataKey;
+use crate::domain::datakey::entity::{INFRA_CONFIG_DOMAIN_NAME, RevokedKey, SecDataKey};
use crate::infra::sign_plugin::signers::Signers;
use crate::domain::datakey::entity::DataKey;
use crate::util::error::{Error, Result};
use async_trait::async_trait;
+use chrono::{DateTime, Utc};
use crate::infra::encryption::algorithm::factory::AlgorithmFactory;
/// Memory Sign Backend will perform all sensitive operations directly in host memory.
pub struct MemorySignBackend {
server_config: Arc>,
- engine: Box
+ engine: Box,
+ infra_configs: HashMap,
}
impl MemorySignBackend {
@@ -70,8 +72,13 @@ impl MemorySignBackend {
)?;
engine.initialize().await?;
+ let infra_configs = HashMap::from([
+ (INFRA_CONFIG_DOMAIN_NAME.to_string(), server_config.read()?.get_string("control-server.domain_name")?),
+ ]);
+
Ok(MemorySignBackend {
server_config,
+ infra_configs,
engine: Box::new(engine),
})
}
@@ -90,11 +97,14 @@ impl SignBackend for MemorySignBackend {
}
async fn generate_keys(&self, data_key: &mut DataKey) -> Result<()> {
- let keys = Signers::generate_keys(&data_key.key_type, &data_key.attributes)?;
- data_key.private_key = self.engine.encode(keys.private_key).await?;
- data_key.public_key = self.engine.encode(keys.public_key).await?;
- data_key.certificate = self.engine.encode(keys.certificate).await?;
- data_key.fingerprint = keys.fingerprint;
+ let sec_key = SecDataKey::load(data_key, &self.engine).await?;
+ let content = Signers::load_from_data_key(&data_key.key_type, sec_key)?.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?;
+ data_key.certificate = self.engine.encode(content.certificate).await?;
+ data_key.fingerprint = content.fingerprint;
+ data_key.serial_number = content.serial_number;
Ok(())
}
@@ -112,4 +122,9 @@ impl SignBackend for MemorySignBackend {
data_key.certificate = self.engine.decode(data_key.certificate.clone()).await?;
Ok(())
}
+
+ async fn generate_crl_content(&self, data_key: &DataKey, revoked_keys: Vec, last_update: DateTime, 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(revoked_keys, last_update, next_update)
+ }
}
\ No newline at end of file
diff --git a/src/infra/sign_plugin/openpgp.rs b/src/infra/sign_plugin/openpgp.rs
index 0fa57ce789f9b75889b0a43c781c7d74525dbe64..ea1222dcd39fd0c50c32078d11a04d094cbd4fdb 100644
--- a/src/infra/sign_plugin/openpgp.rs
+++ b/src/infra/sign_plugin/openpgp.rs
@@ -35,7 +35,7 @@ use std::io::{Cursor};
use std::str::from_utf8;
use validator::{Validate, ValidationError};
use pgp::composed::StandaloneSignature;
-use crate::domain::datakey::entity::{DataKey, DataKeyContent, SecDataKey};
+use crate::domain::datakey::entity::{DataKey, DataKeyContent, SecDataKey, KeyType as EntityKeyType, RevokedKey};
use crate::util::key::encode_u8_to_hex_string;
use super::util::{validate_utc_time_not_expire, validate_utc_time, attributes_validate};
@@ -134,10 +134,11 @@ fn validate_key_size(key_size: &str) -> std::result::Result<(), ValidationError>
}
pub struct OpenPGPPlugin {
- secret_key: SignedSecretKey,
- public_key: SignedPublicKey,
+ name: String,
+ secret_key: Option,
+ public_key: Option,
identity: String,
- attributes: HashMap
+ attributes: HashMap,
}
impl OpenPGPPlugin {
@@ -153,13 +154,23 @@ impl OpenPGPPlugin {
impl SignPlugins for OpenPGPPlugin {
fn new(db: SecDataKey) -> Result {
- let private = from_utf8(db.private_key.unsecure()).map_err(|e| Error::KeyParseError(e.to_string()))?;
- let (secret_key, _) =
- SignedSecretKey::from_string(private).map_err(|e| Error::KeyParseError(e.to_string()))?;
- let public = from_utf8(db.public_key.unsecure()).map_err(|e| Error::KeyParseError(e.to_string()))?;
- let (public_key, _) =
- SignedPublicKey::from_string(public).map_err(|e| Error::KeyParseError(e.to_string()))?;
+ let mut secret_key = None;
+ let mut public_key = None;
+ if !db.private_key.unsecure().is_empty() {
+ let private = from_utf8(db.private_key.unsecure()).map_err(|e| Error::KeyParseError(e.to_string()))?;
+ let (value, _) =
+ SignedSecretKey::from_string(private).map_err(|e| Error::KeyParseError(e.to_string()))?;
+ secret_key = Some(value);
+ }
+
+ if !db.public_key.unsecure().is_empty() {
+ let public = from_utf8(db.public_key.unsecure()).map_err(|e| Error::KeyParseError(e.to_string()))?;
+ let (value, _) =
+ SignedPublicKey::from_string(public).map_err(|e| Error::KeyParseError(e.to_string()))?;
+ public_key = Some(value);
+ }
Ok(Self {
+ name: db.name.clone(),
secret_key,
public_key,
identity: db.identity.clone(),
@@ -200,10 +211,8 @@ impl SignPlugins for OpenPGPPlugin {
todo!()
}
- fn generate_keys(
- attributes: &HashMap,
- ) -> Result {
- let parameter = attributes_validate::(attributes)?;
+ fn generate_keys(&self, _key_type: &EntityKeyType, _infra_config: &HashMap) -> Result {
+ let parameter = attributes_validate::(&self.attributes)?;
let mut key_params = SecretKeyParamsBuilder::default();
let create_at = parameter.create_at.parse()?;
let expire :DateTime = parameter.expire_at.parse()?;
@@ -236,6 +245,7 @@ impl SignPlugins for OpenPGPPlugin {
public_key: signed_public_key.to_armored_bytes(None)?,
certificate: vec![],
fingerprint: encode_u8_to_hex_string(&signed_secret_key.fingerprint()),
+ serial_number: Some(encode_u8_to_hex_string(&signed_secret_key.fingerprint())),
})
}
@@ -253,22 +263,23 @@ impl SignPlugins for OpenPGPPlugin {
}
};
let now = Utc::now();
+ let secret_key_id = self.secret_key.clone().unwrap().key_id();
let sig_cfg = SignatureConfig {
version: SignatureVersion::V4,
typ: SignatureType::Binary,
- pub_alg: self.public_key.primary_key.algorithm(),
+ pub_alg: self.public_key.clone().unwrap().primary_key.algorithm(),
hash_alg: digest,
- issuer: Some(self.secret_key.key_id()),
+ issuer: Some(secret_key_id.clone()),
created: Some(now),
unhashed_subpackets: vec![],
hashed_subpackets: vec![
Subpacket::SignatureCreationTime(now),
- Subpacket::Issuer(self.secret_key.key_id()),
+ Subpacket::Issuer(secret_key_id),
],
};
let read_cursor = Cursor::new(content);
let signature_packet = sig_cfg
- .sign(&self.secret_key, passwd_fn, read_cursor)
+ .sign(&self.secret_key.clone().unwrap(), passwd_fn, read_cursor)
.map_err(|e| Error::SignError(self.identity.clone(), e.to_string()))?;
@@ -285,6 +296,10 @@ impl SignPlugins for OpenPGPPlugin {
.map_err(|e| Error::SignError(self.identity.clone(), e.to_string()))?;
Ok(signature_bytes)
}
+
+ fn generate_crl_content(&self, _revoked_keys: Vec, _last_update: DateTime, _next_update: DateTime) -> Result> {
+ todo!()
+ }
}
#[cfg(test)]
@@ -295,8 +310,14 @@ mod test {
use secstr::SecVec;
use crate::domain::datakey::entity::{KeyState, Visibility};
use crate::domain::datakey::entity::{KeyType};
+ use crate::domain::encryption_engine::EncryptionEngine;
+ use crate::infra::encryption::dummy_engine::DummyEngine;
use crate::util::options::DETACHED;
+ fn get_encryption_engine() -> Box {
+ Box::new(DummyEngine::default())
+ }
+
fn get_default_parameter() -> HashMap {
HashMap::from([
("name".to_string(), "fake_name".to_string()),
@@ -310,9 +331,9 @@ mod test {
])
}
- fn get_default_datakey() -> DataKey {
+ fn get_default_datakey(name: Option, parameter: Option>) -> DataKey {
let now = Utc::now();
- DataKey {
+ let mut datakey = DataKey {
id: 0,
name: "fake".to_string(),
visibility: Visibility::Public,
@@ -320,7 +341,9 @@ mod test {
user: 1,
attributes: get_default_parameter(),
key_type: KeyType::OpenPGP,
+ parent_id: None,
fingerprint: "".to_string(),
+ serial_number: None,
private_key: vec![],
public_key: vec![],
certificate: vec![],
@@ -329,7 +352,16 @@ mod test {
key_state: KeyState::Enabled,
user_email: None,
request_delete_users: None,
+ request_revoke_users: None,
+ parent_key: None,
+ };
+ if let Some(name) = name {
+ datakey.name = name;
}
+ if let Some(parameter) = parameter {
+ datakey.attributes = parameter;
+ }
+ datakey
}
#[test]
@@ -406,42 +438,66 @@ mod test {
attributes_validate::(¶meter).expect("valid email");
}
- #[test]
- fn test_generate_key_with_possible_digest_hash() {
+ #[tokio::test]
+ async fn test_generate_key_with_possible_digest_hash() {
let mut parameter = get_default_parameter();
+ let dummy_engine = get_encryption_engine();
//choose 3 random digest algorithm
for _ in [1,2,3] {
let num = rand::thread_rng().gen_range(0..VALID_DIGEST_ALGORITHM.len());
parameter.insert("digest_algorithm".to_string(), VALID_DIGEST_ALGORITHM[num].to_string());
- OpenPGPPlugin::generate_keys(¶meter).expect(format!("generate key with digest {} successfully", VALID_DIGEST_ALGORITHM[num]).as_str());
+ let sec_datakey = SecDataKey::load(
+ &get_default_datakey(
+ None, Some(parameter.clone())), &dummy_engine).await.expect("load sec datakey successfully");
+ let plugin = OpenPGPPlugin::new(sec_datakey).expect("create openpgp plugin successfully");
+ plugin.generate_keys(&KeyType::OpenPGP, &HashMap::new()).expect(format!("generate key with digest {} successfully", VALID_DIGEST_ALGORITHM[num]).as_str());
}
}
- #[test]
- fn test_generate_key_with_possible_length() {
+ #[tokio::test]
+ async fn test_generate_key_with_possible_length() {
+ let mut parameter = get_default_parameter();
+ let dummy_engine = get_encryption_engine();
for key_size in VALID_KEY_SIZE{
- let mut parameter = get_default_parameter();
parameter.insert("key_size".to_string(), key_size.to_string());
- OpenPGPPlugin::generate_keys(¶meter).expect("generate key successfully");
+ let sec_datakey = SecDataKey::load(
+ &get_default_datakey(
+ None, Some(parameter.clone())), &dummy_engine).await.expect("load sec datakey successfully");
+ let plugin = OpenPGPPlugin::new(sec_datakey).expect("create openpgp plugin successfully");
+ plugin.generate_keys(&KeyType::OpenPGP, &HashMap::new()).expect(format!("generate key with key size {} successfully", key_size).as_str());
}
}
- #[test]
- fn test_generate_key_with_possible_key_type() {
+ #[tokio::test]
+ async fn test_generate_key_with_possible_key_type() {
+ let mut parameter = get_default_parameter();
+ let dummy_engine = get_encryption_engine();
for key_type in VALID_KEY_TYPE{
- let mut parameter = get_default_parameter();
parameter.insert("key_type".to_string(), key_type.to_string());
- OpenPGPPlugin::generate_keys(¶meter).expect("generate key successfully");
+ let sec_datakey = SecDataKey::load(
+ &get_default_datakey(
+ None, Some(parameter.clone())), &dummy_engine).await.expect("load sec datakey successfully");
+ let plugin = OpenPGPPlugin::new(sec_datakey).expect("create openpgp plugin successfully");
+ plugin.generate_keys(&KeyType::OpenPGP, &HashMap::new()).expect(format!("generate key with key type {} successfully", key_type).as_str());
}
}
- #[test]
- fn test_generate_key_with_without_passphrase() {
+ #[tokio::test]
+ async fn test_generate_key_with_without_passphrase() {
let mut parameter = get_default_parameter();
- OpenPGPPlugin::generate_keys(¶meter).expect("generate key successfully");
+ let dummy_engine = get_encryption_engine();
+ let sec_datakey = SecDataKey::load(
+ &get_default_datakey(
+ None, Some(parameter.clone())), &dummy_engine).await.expect("load sec datakey successfully");
+ let plugin = OpenPGPPlugin::new(sec_datakey).expect("create openpgp plugin successfully");
+ plugin.generate_keys(&KeyType::OpenPGP, &HashMap::new()).expect(format!("generate key with no passphrase successfully").as_str());
parameter.insert("passphrase".to_string(), "".to_string());
- OpenPGPPlugin::generate_keys(¶meter).expect("generate key successfully");
+ let sec_datakey = SecDataKey::load(
+ &get_default_datakey(
+ None, Some(parameter.clone())), &dummy_engine).await.expect("load sec datakey successfully");
+ let plugin = OpenPGPPlugin::new(sec_datakey).expect("create openpgp plugin successfully");
+ plugin.generate_keys(&KeyType::OpenPGP, &HashMap::new()).expect(format!("generate key with passphrase successfully").as_str());
}
#[test]
@@ -513,25 +569,32 @@ BCQ921xH/nCtAw20cymy0Wf822PaJRZolSCm1aEDNE45UpGVu88ilLARklDXGhZZ
j0AZp6WE
=eDZk
-----END PGP PRIVATE KEY BLOCK-----";
- let mut datakey = get_default_datakey();
+ let mut datakey = get_default_datakey(None, None);
datakey.public_key = public_key.as_bytes().to_vec();
datakey.private_key = private_key.as_bytes().to_vec();
OpenPGPPlugin::validate_and_update(&mut datakey).expect("validate and update should work");
assert_eq!("60780E80350801A395B1B08302A5B5FB87CD058E", datakey.fingerprint);
}
- #[test]
- fn test_sign_with_armored_text() {
+ #[tokio::test]
+ async fn test_sign_with_armored_text() {
let content = "hello world".as_bytes();
let mut parameter = get_default_parameter();
parameter.insert(DETACHED.to_string(), "true".to_string());
- let keys = OpenPGPPlugin::generate_keys(¶meter).expect("generate key successfully");
+ let dummy_engine = get_encryption_engine();
+ let sec_datakey = SecDataKey::load(
+ &get_default_datakey(
+ None, Some(parameter.clone())), &dummy_engine).await.expect("load sec datakey successfully");
+ let plugin = OpenPGPPlugin::new(sec_datakey).expect("create openpgp plugin successfully");
+ let keys = plugin.generate_keys(&KeyType::OpenPGP, &HashMap::new()).expect(format!("generate key successfully").as_str());
let sec_keys = SecDataKey {
+ name: "".to_string(),
private_key: SecVec::new(keys.private_key.clone()),
public_key: SecVec::new(keys.public_key.clone()),
certificate: SecVec::new(keys.certificate.clone()),
identity: "".to_string(),
attributes: Default::default(),
+ parent: None,
};
let instance = OpenPGPPlugin::new(sec_keys).expect("create openpgp instance successfully");
let signature = instance.sign(content.to_vec(), parameter).expect("sign successfully");
diff --git a/src/infra/sign_plugin/signers.rs b/src/infra/sign_plugin/signers.rs
index 231173dc259dbe9997387ce6206715d418b3d30e..0576be696e0152d93cb847af564694c2a484f59d 100644
--- a/src/infra/sign_plugin/signers.rs
+++ b/src/infra/sign_plugin/signers.rs
@@ -17,9 +17,8 @@
use crate::domain::sign_plugin::SignPlugins;
use crate::infra::sign_plugin::openpgp::OpenPGPPlugin;
use crate::infra::sign_plugin::x509::X509Plugin;
-use crate::domain::datakey::entity::{DataKey, DataKeyContent, KeyType};
+use crate::domain::datakey::entity::{DataKey, KeyType};
use crate::util::error::Result;
-use std::collections::HashMap;
use crate::domain::datakey::entity::SecDataKey;
@@ -31,25 +30,15 @@ impl Signers {
pub fn load_from_data_key(key_type: &KeyType, data_key: SecDataKey) -> Result> {
match key_type {
KeyType::OpenPGP => Ok(Box::new(OpenPGPPlugin::new(data_key)?)),
- KeyType::X509 => Ok(Box::new(X509Plugin::new(data_key)?)),
+ KeyType::X509CA | KeyType::X509ICA | KeyType::X509EE => Ok(Box::new(X509Plugin::new(data_key)?)),
}
}
- //generating new key, including private & public keys and the certificate, empty if not required.
- pub fn generate_keys(
- key_type: &KeyType,
- value: &HashMap,
- ) -> Result {
- match key_type {
- KeyType::OpenPGP => OpenPGPPlugin::generate_keys(value),
- KeyType::X509 => X509Plugin::generate_keys(value),
- }
- }
pub fn validate_and_update(datakey: &mut DataKey) -> Result<()> {
match datakey.key_type {
KeyType::OpenPGP => OpenPGPPlugin::validate_and_update(datakey),
- KeyType::X509 => X509Plugin::validate_and_update(datakey),
+ KeyType::X509CA | KeyType::X509ICA | KeyType::X509EE => X509Plugin::validate_and_update(datakey),
}
}
}
diff --git a/src/infra/sign_plugin/x509.rs b/src/infra/sign_plugin/x509.rs
index d9f6e0b418a6ba535350f1ddee70a3a5fb7b064c..ed626abf89240f0c2e5315446a90348a1556cf1b 100644
--- a/src/infra/sign_plugin/x509.rs
+++ b/src/infra/sign_plugin/x509.rs
@@ -19,25 +19,31 @@ use std::str::FromStr;
use std::time::{SystemTime, Duration};
use chrono::{DateTime, Utc};
-use openssl::asn1::Asn1Time;
+use openssl::asn1::{Asn1Integer, Asn1Time};
+use openssl::bn::{BigNum, MsbOption};
use openssl::cms::{CmsContentInfo, CMSOptions};
use openssl::dsa::Dsa;
use openssl::hash::MessageDigest;
+use openssl::nid::Nid;
use openssl::pkcs7::{Pkcs7, Pkcs7Flags};
use openssl::pkey::{PKey, Private};
use openssl::rsa::Rsa;
use openssl::stack::Stack;
use openssl::x509;
+use openssl::x509::extension::{AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectKeyIdentifier};
+use openssl::x509::{X509Crl, X509Extension};
use secstr::SecVec;
use serde::Deserialize;
+use foreign_types_shared::{ForeignType, ForeignTypeRef};
+use openssl_sys::{X509_CRL_new, X509_CRL_set_issuer_name, X509_CRL_set1_lastUpdate, X509_CRL_add0_revoked, X509_CRL_sign, X509_CRL_set1_nextUpdate, X509_REVOKED_new, X509_REVOKED_set_serialNumber, X509_REVOKED_set_revocationDate};
use validator::{Validate, ValidationError};
use crate::util::options;
use crate::util::sign::SignType;
-use crate::domain::datakey::entity::{DataKey, DataKeyContent, SecDataKey};
+use crate::domain::datakey::entity::{DataKey, DataKeyContent, INFRA_CONFIG_DOMAIN_NAME, KeyType, RevokedKey, SecDataKey, SecParentDateKey};
use crate::util::error::{Error, Result};
use crate::domain::sign_plugin::SignPlugins;
-use crate::util::key::encode_u8_to_hex_string;
+use crate::util::key::{decode_hex_string_to_u8, encode_u8_to_hex_string};
use super::util::{validate_utc_time_not_expire, validate_utc_time, attributes_validate};
const VALID_KEY_TYPE: [&str; 2] = ["rsa", "dsa"];
@@ -150,22 +156,188 @@ fn validate_x509_digest_algorithm_type(key_type: &str) -> std::result::Result<()
}
pub struct X509Plugin {
+ name: String,
private_key: SecVec,
public_key: SecVec,
certificate: SecVec,
identity: String,
- attributes: HashMap
+ attributes: HashMap,
+ parent_key: Option
+}
+
+impl X509Plugin {
+
+ fn generate_serial_number() -> Result {
+ let mut serial_number = BigNum::new()?;
+ serial_number.rand(128, MsbOption::MAYBE_ZERO, true)?;
+ Ok(serial_number)
+ }
+
+ fn generate_crl_endpoint(&self, name: &str, infra_config: &HashMap) -> Result{
+ let domain_name = infra_config.get(INFRA_CONFIG_DOMAIN_NAME).ok_or(
+ Error::GeneratingKeyError(format!("{} is not configured", INFRA_CONFIG_DOMAIN_NAME)))?;
+ Ok(format!("URI:https://{}/api/v1/keys/{}/crl", domain_name, name))
+ }
+
+ //The openssl config for ca would be like:
+ // [ v3_ca ]
+ // basicConstraints = critical, CA:TRUE, pathlen:1
+ // subjectKeyIdentifier = hash
+ // authorityKeyIdentifier = keyid:always, issuer:always
+ // keyUsage = critical, cRLSign, digitalSignature, keyCertSign
+ // nsCertType = objCA
+ // nsComment = "Signatrust Root CA"
+ #[allow(deprecated)]
+ fn generate_x509ca_keys(&self, _infra_config: &HashMap) -> Result {
+ let parameter = attributes_validate::(&self.attributes)?;
+ //generate self signed certificate
+ let keys = parameter.get_key()?;
+ let mut generator = x509::X509Builder::new()?;
+ let serial_number = X509Plugin::generate_serial_number()?;
+ generator.set_subject_name(parameter.get_subject_name()?.as_ref())?;
+ generator.set_issuer_name(parameter.get_subject_name()?.as_ref())?;
+ generator.set_pubkey(keys.as_ref())?;
+ generator.set_version(2)?;
+ generator.set_serial_number(Asn1Integer::from_bn(serial_number.as_ref())?.as_ref())?;
+ generator.set_not_before(Asn1Time::days_from_now(days_in_duration(¶meter.create_at)? as u32)?.as_ref())?;
+ generator.set_not_after(Asn1Time::days_from_now(days_in_duration(¶meter.expire_at)? as u32)?.as_ref())?;
+ //ca profile
+ generator.append_extension(BasicConstraints::new().ca().pathlen(1).critical().build()?)?;
+ generator.append_extension(SubjectKeyIdentifier::new().build(&generator.x509v3_context(None, None))?)?;
+ generator.append_extension(AuthorityKeyIdentifier::new().keyid(true).issuer(true).build(&generator.x509v3_context(None, None))?)?;
+ generator.append_extension(KeyUsage::new().crl_sign().digital_signature().key_cert_sign().critical().build()?)?;
+ generator.append_extension(X509Extension::new_nid(None, None, Nid::NETSCAPE_COMMENT, "Signatrust Root CA")?)?;
+ generator.append_extension(X509Extension::new_nid(None, None, Nid::NETSCAPE_CERT_TYPE, "objCA")?)?;
+
+ generator.sign(keys.as_ref(), parameter.get_digest_algorithm()?)?;
+ let cert = generator.build();
+ Ok(DataKeyContent{
+ private_key: keys.private_key_to_pem_pkcs8()?,
+ public_key: keys.public_key_to_pem()?,
+ certificate: cert.to_pem()?,
+ fingerprint: encode_u8_to_hex_string(cert.digest(
+ MessageDigest::from_name("sha1").ok_or(Error::GeneratingKeyError("unable to generate digester".to_string()))?)?.as_ref()),
+ serial_number: Some(encode_u8_to_hex_string(&serial_number.to_vec())),
+ })
+ }
+
+ //The openssl config for ca would be like:
+ // [ v3_ica ]
+ // basicConstraints = critical, CA:TRUE, pathlen:0
+ // subjectKeyIdentifier = hash
+ // authorityKeyIdentifier = keyid:always, issuer:always
+ // keyUsage = critical, cRLSign, digitalSignature, keyCertSign
+ // authorityInfoAccess = OCSP;URI:, caIssuers;URI: