From 11afbc13ddde528ba700547a93ee38c2a8a39629 Mon Sep 17 00:00:00 2001 From: TommyLike Date: Thu, 20 Apr 2023 15:12:04 +0800 Subject: [PATCH] Support store keys' fingerprint in database --- docs/fingerprint.md | 49 +++++++++++++++++++ .../20221220070859_initialize-table.up.sql | 1 + src/control_admin_entrypoint.rs | 1 + src/domain/datakey/entity.rs | 13 ++++- src/domain/sign_plugin.rs | 4 +- src/infra/database/model/datakey/dto.rs | 7 ++- .../database/model/datakey/repository.rs | 3 +- src/infra/sign_backend/memory/backend.rs | 9 ++-- src/infra/sign_plugin/openpgp.rs | 16 +++--- src/infra/sign_plugin/signers.rs | 4 +- src/infra/sign_plugin/x509.rs | 17 ++++--- .../handler/control/model/datakey/dto.rs | 5 ++ src/util/error.rs | 2 + 13 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 docs/fingerprint.md diff --git a/docs/fingerprint.md b/docs/fingerprint.md new file mode 100644 index 0000000..b2228c8 --- /dev/null +++ b/docs/fingerprint.md @@ -0,0 +1,49 @@ +# How to identify the fingerprint of datakey +Signatrust will keep the fingerprint of datakey in database as following: +```json +{ + "id": 2, + "name": "default-x509", + "email": "tommylikehu@gmail.com", + "description": "used for test purpose only", + "user": 1, + "attributes": { + "common_name": "Infra", + "country_name": "CN", + "create_at": "2023-04-20 07:00:57.695607 UTC", + "digest_algorithm": "sha2_256", + "expire_at": "2023-05-20 07:00:57.695607 UTC", + "key_length": "2048", + "key_type": "rsa", + "locality": "ShenZhen", + "name": "default-x509", + "organization": "openEuler", + "organizational_unit": "Infra", + "province_name": "GuangDong" + }, + "key_type": "x509", + "fingerprint": "94195AD20235DF8535E3E7DDA7188C6296323A8E", + "create_at": "2023-04-20 07:00:58 UTC", + "expire_at": "2023-05-20 07:00:58 UTC", + "key_state": "enabled" + } +``` +Once you exported the certificate or public key, you may use the commands below to verify whether the fingerprint matches: +## openPGP +```shell +➜ codes gpg --import .file +gpg: key 524817AA41D02F6E: public key "default-pgp " imported +gpg: Total number processed: 1 +gpg: imported: 1 +➜ codes gpg --list-keys +/Users/tommylike/.gnupg/pubring.kbx +----------------------------------- +pub rsa2048 2023-04-20 [SC] + B96554CCFB546583ACA3D88B524817AA41D02F6E +uid [ unknown] default-pgp +``` +## x509 +```shell +openssl x509 -noout -fingerprint -sha1 -inform pem -in .file +SHA1 Fingerprint=94:19:5A:D2:02:35:DF:85:35:E3:E7:DD:A7:18:8C:62:96:32:3A:8E +``` diff --git a/migrations/20221220070859_initialize-table.up.sql b/migrations/20221220070859_initialize-table.up.sql index 055b890..401ce20 100644 --- a/migrations/20221220070859_initialize-table.up.sql +++ b/migrations/20221220070859_initialize-table.up.sql @@ -22,6 +22,7 @@ CREATE TABLE data_key ( email VARCHAR(40) NOT NULL, attributes VARCHAR(1000), key_type VARCHAR(10) NOT NULL, + fingerprint VARCHAR(90) NOT NULL, private_key TEXT, public_key TEXT, certificate TEXT, diff --git a/src/control_admin_entrypoint.rs b/src/control_admin_entrypoint.rs index 5993714..5a5d9c5 100644 --- a/src/control_admin_entrypoint.rs +++ b/src/control_admin_entrypoint.rs @@ -165,6 +165,7 @@ async fn main() -> Result<()> { email: user.email.clone(), attributes: generate_keys_parameters(&generate_keys), key_type: generate_keys.key_type.to_string(), + fingerprint: "".to_string(), create_at: format!("{}", now), expire_at: format!("{}", now + Duration::days(30)), key_state: Default::default(), diff --git a/src/domain/datakey/entity.rs b/src/domain/datakey/entity.rs index b66493c..6dcaa96 100644 --- a/src/domain/datakey/entity.rs +++ b/src/domain/datakey/entity.rs @@ -97,6 +97,7 @@ pub struct DataKey { pub email: String, pub attributes: HashMap, pub key_type: KeyType, + pub fingerprint: String, pub private_key: Vec, pub public_key: Vec, pub certificate: Vec, @@ -121,12 +122,13 @@ impl ExtendableAttributes for DataKey { impl Identity for DataKey { fn get_identity(&self) -> String { format!( - "", + "", self.id, self.name, self.email, self.user, - self.key_type + self.key_type, + self.fingerprint ) } } @@ -151,3 +153,10 @@ impl SecDataKey { } } +pub struct DataKeyContent { + pub private_key: Vec, + pub public_key: Vec, + pub certificate: Vec, + pub fingerprint: String, +} + diff --git a/src/domain/sign_plugin.rs b/src/domain/sign_plugin.rs index 772f9d1..4107365 100644 --- a/src/domain/sign_plugin.rs +++ b/src/domain/sign_plugin.rs @@ -16,7 +16,7 @@ use crate::util::error::Result; use std::collections::HashMap; -use crate::domain::datakey::entity::SecDataKey; +use crate::domain::datakey::entity::{DataKeyContent, SecDataKey}; pub trait SignPlugins: Send + Sync { fn new(db: SecDataKey) -> Result @@ -31,7 +31,7 @@ pub trait SignPlugins: Send + Sync { Self: Sized; fn generate_keys( attributes: &HashMap, - ) -> Result<(Vec, Vec, Vec)> + ) -> Result where Self: Sized; fn sign(&self, content: Vec, options: HashMap) -> Result>; diff --git a/src/infra/database/model/datakey/dto.rs b/src/infra/database/model/datakey/dto.rs index 546addc..568286a 100644 --- a/src/infra/database/model/datakey/dto.rs +++ b/src/infra/database/model/datakey/dto.rs @@ -33,6 +33,7 @@ pub(super) struct DataKeyDTO { pub email: String, pub attributes: String, pub key_type: String, + pub fingerprint: String, pub private_key: String, pub public_key: String, pub certificate: String, @@ -46,7 +47,7 @@ pub(super) struct DataKeyDTO { impl TryFrom for DataKey { type Error = Error; - fn try_from(dto: DataKeyDTO) -> std::result::Result { + fn try_from(dto: DataKeyDTO) -> Result { Ok(DataKey { id: dto.id, name: dto.name.clone(), @@ -55,6 +56,7 @@ impl TryFrom for DataKey { email: dto.email.clone(), attributes: serde_json::from_str(dto.attributes.as_str())?, key_type: KeyType::from_str(&dto.key_type)?, + fingerprint: dto.fingerprint.clone(), 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), @@ -69,7 +71,7 @@ impl TryFrom for DataKey { impl TryFrom for DataKeyDTO { type Error = Error; - fn try_from(data_key: DataKey) -> std::result::Result { + fn try_from(data_key: DataKey) -> Result { Ok(DataKeyDTO { id: data_key.id, name: data_key.name.clone(), @@ -78,6 +80,7 @@ impl TryFrom for DataKeyDTO { email: data_key.email.clone(), attributes: data_key.serialize_attributes()?, key_type: data_key.key_type.to_string(), + fingerprint: data_key.fingerprint.clone(), private_key: key::encode_u8_to_hex_string( &data_key.private_key ), diff --git a/src/infra/database/model/datakey/repository.rs b/src/infra/database/model/datakey/repository.rs index 41454df..50e486b 100644 --- a/src/infra/database/model/datakey/repository.rs +++ b/src/infra/database/model/datakey/repository.rs @@ -39,13 +39,14 @@ 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, email, attributes, key_type, private_key, public_key, certificate, create_at, expire_at, key_state, soft_delete) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + let record : u64 = sqlx::query("INSERT INTO data_key(name, description, user, email, attributes, key_type, fingerprint, private_key, public_key, certificate, create_at, expire_at, key_state, soft_delete) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") .bind(&dto.name) .bind(&dto.description) .bind(&dto.user) .bind(dto.email) .bind(dto.attributes) .bind(dto.key_type) + .bind(dto.fingerprint) .bind(dto.private_key) .bind(dto.public_key) .bind(dto.certificate) diff --git a/src/infra/sign_backend/memory/backend.rs b/src/infra/sign_backend/memory/backend.rs index 2a94dd0..ae3eae5 100644 --- a/src/infra/sign_backend/memory/backend.rs +++ b/src/infra/sign_backend/memory/backend.rs @@ -80,10 +80,11 @@ impl MemorySignBackend { #[async_trait] impl SignBackend for MemorySignBackend { async fn generate_keys(&self, data_key: &mut DataKey) -> Result<()> { - let (private_key, public_key, certificate) = Signers::generate_keys(&data_key.key_type, &data_key.attributes)?; - data_key.private_key = self.engine.encode(private_key).await?; - data_key.public_key = self.engine.encode(public_key).await?; - data_key.certificate = self.engine.encode(certificate).await?; + 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; Ok(()) } diff --git a/src/infra/sign_plugin/openpgp.rs b/src/infra/sign_plugin/openpgp.rs index 33f4804..852cbae 100644 --- a/src/infra/sign_plugin/openpgp.rs +++ b/src/infra/sign_plugin/openpgp.rs @@ -38,7 +38,8 @@ use std::str::from_utf8; use validator::{Validate, ValidationError}; use pgp::composed::StandaloneSignature; -use crate::domain::datakey::entity::SecDataKey; +use crate::domain::datakey::entity::{DataKeyContent, SecDataKey}; +use crate::util::key::encode_u8_to_hex_string; use super::util::{validate_utc_time_not_expire, validate_utc_time}; const DETACHED_SIGNATURE: &str = "detached"; @@ -160,7 +161,7 @@ impl SignPlugins for OpenPGPPlugin { fn generate_keys( attributes: &HashMap, - ) -> Result<(Vec, Vec, Vec)> { + ) -> Result { let parameter = OpenPGPPlugin::attributes_validate(attributes)?; let mut key_params = SecretKeyParamsBuilder::default(); let create_at = parameter.create_at.parse()?; @@ -182,11 +183,12 @@ impl SignPlugins for OpenPGPPlugin { let signed_secret_key = secret_key.sign(passwd_fn)?; let public_key = signed_secret_key.public_key(); let signed_public_key = public_key.sign(&signed_secret_key, passwd_fn)?; - Ok(( - signed_secret_key.to_armored_bytes(None)?, - signed_public_key.to_armored_bytes(None)?, - vec![], - )) + Ok(DataKeyContent{ + private_key: signed_secret_key.to_armored_bytes(None)?, + public_key: signed_public_key.to_armored_bytes(None)?, + certificate: vec![], + fingerprint: encode_u8_to_hex_string(&signed_secret_key.fingerprint()), + }) } fn sign(&self, content: Vec, options: HashMap) -> Result> { diff --git a/src/infra/sign_plugin/signers.rs b/src/infra/sign_plugin/signers.rs index 2bab8f3..d7c74a5 100644 --- a/src/infra/sign_plugin/signers.rs +++ b/src/infra/sign_plugin/signers.rs @@ -17,7 +17,7 @@ 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::{KeyType}; +use crate::domain::datakey::entity::{DataKeyContent, KeyType}; use crate::util::error::Result; use std::collections::HashMap; @@ -39,7 +39,7 @@ impl Signers { pub fn generate_keys( key_type: &KeyType, value: &HashMap, - ) -> Result<(Vec, Vec, Vec)> { + ) -> Result { match key_type { KeyType::OpenPGP => OpenPGPPlugin::generate_keys(value), KeyType::X509 => X509Plugin::generate_keys(value), diff --git a/src/infra/sign_plugin/x509.rs b/src/infra/sign_plugin/x509.rs index de1fae6..913d3fc 100644 --- a/src/infra/sign_plugin/x509.rs +++ b/src/infra/sign_plugin/x509.rs @@ -28,9 +28,10 @@ use secstr::SecVec; use serde::Deserialize; use validator::{Validate, ValidationError}; -use crate::domain::datakey::entity::SecDataKey; +use crate::domain::datakey::entity::{DataKeyContent, SecDataKey}; use crate::util::error::{Error, Result}; use crate::domain::sign_plugin::SignPlugins; +use crate::util::key::encode_u8_to_hex_string; use super::util::{validate_utc_time_not_expire, validate_utc_time}; #[derive(Debug, Validate, Deserialize)] @@ -164,7 +165,7 @@ impl SignPlugins for X509Plugin { fn generate_keys( attributes: &HashMap, - ) -> Result<(Vec, Vec, Vec)> { + ) -> Result { let parameter = X509Plugin::attributes_validate(attributes)?; let keys = parameter.get_key()?; let mut generator = x509::X509Builder::new()?; @@ -176,11 +177,13 @@ impl SignPlugins for X509Plugin { generator.set_not_after(Asn1Time::days_from_now(days_in_duration(¶meter.expire_at)? as u32)?.as_ref())?; generator.sign(keys.as_ref(), parameter.get_digest_algorithm()?)?; let cert = generator.build(); - Ok(( - keys.private_key_to_pem_pkcs8()?, - keys.public_key_to_pem()?, - cert.to_pem()? - )) + 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()) + }) } fn sign(&self, content: Vec, _options: HashMap) -> Result> { diff --git a/src/presentation/handler/control/model/datakey/dto.rs b/src/presentation/handler/control/model/datakey/dto.rs index 63b920a..6ca027c 100644 --- a/src/presentation/handler/control/model/datakey/dto.rs +++ b/src/presentation/handler/control/model/datakey/dto.rs @@ -51,6 +51,9 @@ pub struct DataKeyDTO { pub attributes: HashMap, /// Key type current support pgp and x509 pub key_type: String, + /// Fingerprint, leave empty when creating + #[serde(skip_deserializing)] + pub fingerprint: String, /// Create utc time, format: 2023-04-08 13:36:35.328324 UTC #[validate(custom = "validate_utc_time")] pub create_at: String, @@ -83,6 +86,7 @@ impl DataKey { email: identity.email, attributes: combined_attributes, key_type: KeyType::from_str(dto.key_type.as_str())?, + fingerprint: "".to_string(), private_key: vec![], public_key: vec![], certificate: vec![], @@ -106,6 +110,7 @@ impl TryFrom for DataKeyDTO { email: dto.email, attributes: dto.attributes, key_type: dto.key_type.to_string(), + fingerprint: dto.fingerprint, create_at: dto.create_at.to_string(), expire_at: dto.expire_at.to_string(), key_state: dto.key_state.to_string(), diff --git a/src/util/error.rs b/src/util/error.rs index 2ab0dcc..8968725 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -89,6 +89,8 @@ pub enum Error { RedisError(String), #[error("token has expired: {0}")] TokenExpiredError(String), + #[error("failed to generate keys: {0}")] + GeneratingKeyError(String), //client error #[error("file type not supported {0}")] -- Gitee