Skip to main content

fraiseql_core/security/kms/
models.rs

1//! KMS domain models for key management.
2//!
3//! Provides immutable value objects for representing encrypted data,
4//! key references, and rotation policies.
5
6use std::{collections::HashMap, fmt};
7
8use serde::{Deserialize, Serialize};
9use zeroize::Zeroizing;
10
11/// Intended use of the key.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14#[non_exhaustive]
15pub enum KeyPurpose {
16    /// Key used for encryption/decryption
17    EncryptDecrypt,
18    /// Key used for signing/verification
19    SignVerify,
20    /// Key used for message authentication codes
21    Mac,
22}
23
24impl fmt::Display for KeyPurpose {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        match self {
27            Self::EncryptDecrypt => write!(f, "encrypt_decrypt"),
28            Self::SignVerify => write!(f, "sign_verify"),
29            Self::Mac => write!(f, "mac"),
30        }
31    }
32}
33
34/// Current state of the key.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "snake_case")]
37#[non_exhaustive]
38pub enum KeyState {
39    /// Key is active and can be used
40    Enabled,
41    /// Key is disabled and cannot be used
42    Disabled,
43    /// Key is pending deletion
44    PendingDeletion,
45    /// Key has been destroyed
46    Destroyed,
47}
48
49impl fmt::Display for KeyState {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        match self {
52            Self::Enabled => write!(f, "enabled"),
53            Self::Disabled => write!(f, "disabled"),
54            Self::PendingDeletion => write!(f, "pending_deletion"),
55            Self::Destroyed => write!(f, "destroyed"),
56        }
57    }
58}
59
60/// Immutable reference to a key in KMS.
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct KeyReference {
63    /// Provider identifier (e.g., 'vault', 'aws', 'gcp')
64    pub provider:   String,
65    /// Provider-specific key identifier
66    pub key_id:     String,
67    /// Human-readable alias (optional)
68    pub key_alias:  Option<String>,
69    /// Intended use of the key
70    pub purpose:    KeyPurpose,
71    /// When the key was created (Unix timestamp)
72    pub created_at: i64,
73}
74
75impl KeyReference {
76    /// Create a new key reference.
77    pub const fn new(
78        provider: String,
79        key_id: String,
80        purpose: KeyPurpose,
81        created_at: i64,
82    ) -> Self {
83        Self {
84            provider,
85            key_id,
86            key_alias: None,
87            purpose,
88            created_at,
89        }
90    }
91
92    /// Set the key alias.
93    #[must_use]
94    pub fn with_alias(mut self, alias: String) -> Self {
95        self.key_alias = Some(alias);
96        self
97    }
98
99    /// Get the fully qualified key identifier.
100    #[must_use]
101    pub fn qualified_id(&self) -> String {
102        format!("{}:{}", self.provider, self.key_id)
103    }
104}
105
106/// Encrypted data with metadata.
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct EncryptedData {
109    /// The encrypted bytes (as hex string for JSON compatibility)
110    pub ciphertext:    String,
111    /// Reference to the key used
112    pub key_reference: KeyReference,
113    /// Encryption algorithm used
114    pub algorithm:     String,
115    /// When encryption occurred (Unix timestamp)
116    pub encrypted_at:  i64,
117    /// Additional authenticated data (AAD)
118    pub context:       HashMap<String, String>,
119}
120
121impl EncryptedData {
122    /// Create new encrypted data.
123    pub const fn new(
124        ciphertext: String,
125        key_reference: KeyReference,
126        algorithm: String,
127        encrypted_at: i64,
128        context: HashMap<String, String>,
129    ) -> Self {
130        Self {
131            ciphertext,
132            key_reference,
133            algorithm,
134            encrypted_at,
135            context,
136        }
137    }
138}
139
140/// Data key pair for envelope encryption.
141#[derive(Debug, Clone)]
142pub struct DataKeyPair {
143    /// Use immediately, never persist
144    pub plaintext_key: Zeroizing<Vec<u8>>,
145    /// Persist alongside encrypted data
146    pub encrypted_key: EncryptedData,
147    /// Master key used for wrapping
148    pub key_reference: KeyReference,
149}
150
151impl DataKeyPair {
152    /// Create a new data key pair.
153    pub fn new(
154        plaintext_key: Vec<u8>,
155        encrypted_key: EncryptedData,
156        key_reference: KeyReference,
157    ) -> Self {
158        Self {
159            plaintext_key: Zeroizing::new(plaintext_key),
160            encrypted_key,
161            key_reference,
162        }
163    }
164}
165
166/// Key rotation configuration.
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct RotationPolicy {
169    /// Whether automatic rotation is enabled
170    pub enabled:              bool,
171    /// Days between rotations
172    pub rotation_period_days: u32,
173    /// When key was last rotated (Unix timestamp, None if never)
174    pub last_rotation:        Option<i64>,
175    /// When key will next be rotated (Unix timestamp, None if not scheduled)
176    pub next_rotation:        Option<i64>,
177}
178
179impl RotationPolicy {
180    /// Create a new rotation policy.
181    pub const fn new(enabled: bool, rotation_period_days: u32) -> Self {
182        Self {
183            enabled,
184            rotation_period_days,
185            last_rotation: None,
186            next_rotation: None,
187        }
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn test_key_reference_qualified_id() {
197        let key_ref = KeyReference::new(
198            "vault".to_string(),
199            "my-key-123".to_string(),
200            KeyPurpose::EncryptDecrypt,
201            1_000_000,
202        );
203        assert_eq!(key_ref.qualified_id(), "vault:my-key-123");
204    }
205
206    #[test]
207    fn test_key_reference_with_alias() {
208        let key_ref = KeyReference::new(
209            "vault".to_string(),
210            "my-key-123".to_string(),
211            KeyPurpose::EncryptDecrypt,
212            1_000_000,
213        )
214        .with_alias("production-key".to_string());
215
216        assert_eq!(key_ref.key_alias, Some("production-key".to_string()));
217    }
218
219    #[test]
220    fn test_key_purpose_display() {
221        assert_eq!(KeyPurpose::EncryptDecrypt.to_string(), "encrypt_decrypt");
222        assert_eq!(KeyPurpose::SignVerify.to_string(), "sign_verify");
223        assert_eq!(KeyPurpose::Mac.to_string(), "mac");
224    }
225
226    #[test]
227    fn test_key_state_display() {
228        assert_eq!(KeyState::Enabled.to_string(), "enabled");
229        assert_eq!(KeyState::Disabled.to_string(), "disabled");
230    }
231
232    #[test]
233    fn test_rotation_policy_new() {
234        let policy = RotationPolicy::new(true, 90);
235        assert!(policy.enabled);
236        assert_eq!(policy.rotation_period_days, 90);
237        assert_eq!(policy.last_rotation, None);
238        assert_eq!(policy.next_rotation, None);
239    }
240}