1use std::collections::BTreeMap;
17
18use chrono::Utc;
19use fakecloud_aws::arn::Arn;
20use uuid::Uuid;
21
22use super::asym;
23use super::asym_ecdsa;
24use super::helpers::{
25 default_key_policy, encryption_algorithms_for_key, mac_algorithms_for_key_spec, rand_bytes,
26 signing_algorithms_for_key_spec,
27};
28use crate::state::{KmsAlias, KmsKey, SharedKmsState};
29
30#[derive(Debug, Clone)]
36pub struct KeyCreationInput {
37 pub description: String,
38 pub key_usage: String,
39 pub key_spec: String,
40 pub origin: String,
41 pub enabled: bool,
42 pub multi_region: bool,
43 pub key_rotation_enabled: bool,
44 pub policy: Option<String>,
45 pub tags: BTreeMap<String, String>,
46}
47
48impl Default for KeyCreationInput {
49 fn default() -> Self {
50 Self {
51 description: String::new(),
52 key_usage: "ENCRYPT_DECRYPT".to_string(),
53 key_spec: "SYMMETRIC_DEFAULT".to_string(),
54 origin: "AWS_KMS".to_string(),
55 enabled: true,
56 multi_region: false,
57 key_rotation_enabled: false,
58 policy: None,
59 tags: BTreeMap::new(),
60 }
61 }
62}
63
64pub const VALID_KEY_SPECS: &[&str] = &[
66 "SYMMETRIC_DEFAULT",
67 "RSA_2048",
68 "RSA_3072",
69 "RSA_4096",
70 "ECC_NIST_P256",
71 "ECC_NIST_P384",
72 "ECC_NIST_P521",
73 "ECC_SECG_P256K1",
74 "HMAC_224",
75 "HMAC_256",
76 "HMAC_384",
77 "HMAC_512",
78 "SM2",
79];
80
81pub const VALID_KEY_USAGES: &[&str] = &[
83 "ENCRYPT_DECRYPT",
84 "SIGN_VERIFY",
85 "GENERATE_VERIFY_MAC",
86 "KEY_AGREEMENT",
87];
88
89pub fn validate_key_usage_for_spec(key_usage: &str, key_spec: &str) -> Result<(), String> {
94 if !VALID_KEY_USAGES.contains(&key_usage) {
95 return Err(format!("Unsupported KeyUsage: {key_usage}"));
96 }
97 if !VALID_KEY_SPECS.contains(&key_spec) {
98 return Err(format!("Unsupported KeySpec: {key_spec}"));
99 }
100 let is_symmetric = key_spec == "SYMMETRIC_DEFAULT";
101 let is_hmac = key_spec.starts_with("HMAC_");
102 let is_rsa = key_spec.starts_with("RSA_");
103 let is_ecc = key_spec.starts_with("ECC_");
104 let is_sm2 = key_spec == "SM2";
105 match key_usage {
106 "ENCRYPT_DECRYPT" => {
107 if !(is_symmetric || is_rsa || is_sm2) {
108 return Err(format!(
109 "KeySpec {key_spec} does not support KeyUsage ENCRYPT_DECRYPT"
110 ));
111 }
112 }
113 "SIGN_VERIFY" => {
114 if !(is_rsa || is_ecc || is_sm2) {
115 return Err(format!(
116 "KeySpec {key_spec} does not support KeyUsage SIGN_VERIFY"
117 ));
118 }
119 }
120 "GENERATE_VERIFY_MAC" => {
121 if !is_hmac {
122 return Err(format!(
123 "KeySpec {key_spec} does not support KeyUsage GENERATE_VERIFY_MAC"
124 ));
125 }
126 }
127 "KEY_AGREEMENT" => {
128 if !is_ecc {
129 return Err(format!(
130 "KeySpec {key_spec} does not support KeyUsage KEY_AGREEMENT"
131 ));
132 }
133 }
134 _ => unreachable!(),
135 }
136 Ok(())
137}
138
139pub fn build_kms_key(
149 region: &str,
150 account_id: &str,
151 input: &KeyCreationInput,
152) -> Result<KmsKey, String> {
153 validate_key_usage_for_spec(&input.key_usage, &input.key_spec)?;
154
155 let key_id = if input.multi_region {
156 format!("mrk-{}", Uuid::new_v4().as_simple())
157 } else {
158 Uuid::new_v4().to_string()
159 };
160 let arn = Arn::new("kms", region, account_id, &format!("key/{key_id}")).to_string();
161 let now = Utc::now().timestamp() as f64;
162
163 let signing_algs = if input.key_usage == "SIGN_VERIFY" {
164 signing_algorithms_for_key_spec(&input.key_spec)
165 } else {
166 None
167 };
168 let encryption_algs = encryption_algorithms_for_key(&input.key_usage, &input.key_spec);
169 let mac_algs = if input.key_usage == "GENERATE_VERIFY_MAC" {
170 mac_algorithms_for_key_spec(&input.key_spec)
171 } else {
172 None
173 };
174
175 let mut asym_priv: Option<Vec<u8>> = None;
176 let mut asym_pub: Option<Vec<u8>> = None;
177 if let Some((p, k)) =
178 asym::generate_keypair(&input.key_spec).map_err(|e| format!("rsa keygen failed: {e}"))?
179 {
180 asym_priv = Some(p);
181 asym_pub = Some(k);
182 } else if let Some((p, k)) = asym_ecdsa::generate_keypair(&input.key_spec)
183 .map_err(|e| format!("ecdsa keygen failed: {e}"))?
184 {
185 asym_priv = Some(p);
186 asym_pub = Some(k);
187 }
188
189 let policy = input
190 .policy
191 .clone()
192 .unwrap_or_else(|| default_key_policy(account_id));
193
194 Ok(KmsKey {
195 key_id,
196 arn,
197 creation_date: now,
198 description: input.description.clone(),
199 enabled: input.enabled,
200 key_usage: input.key_usage.clone(),
201 key_spec: input.key_spec.clone(),
202 key_manager: "CUSTOMER".to_string(),
203 key_state: if input.enabled { "Enabled" } else { "Disabled" }.to_string(),
204 deletion_date: None,
205 tags: input.tags.clone(),
206 policy,
207 key_rotation_enabled: input.key_rotation_enabled,
208 rotation_period_in_days: None,
209 origin: input.origin.clone(),
210 multi_region: input.multi_region,
211 rotations: Vec::new(),
212 signing_algorithms: signing_algs,
213 encryption_algorithms: encryption_algs,
214 mac_algorithms: mac_algs,
215 custom_key_store_id: None,
216 imported_key_material: false,
217 imported_material_bytes: None,
218 private_key_seed: rand_bytes(32),
219 primary_region: None,
220 asymmetric_private_key_der: asym_priv,
221 asymmetric_public_key_der: asym_pub,
222 })
223}
224
225pub fn provision_key(
228 state: &SharedKmsState,
229 account_id: &str,
230 input: &KeyCreationInput,
231) -> Result<(String, String), String> {
232 let mut accounts = state.write();
233 let s = accounts.get_or_create(account_id);
234 let region = s.region.clone();
235 let key = build_kms_key(®ion, account_id, input)?;
236 let key_id = key.key_id.clone();
237 let arn = key.arn.clone();
238 s.keys.insert(key_id.clone(), key);
239 Ok((key_id, arn))
240}
241
242pub fn provision_replica_key(
249 state: &SharedKmsState,
250 account_id: &str,
251 primary_arn: &str,
252 description: Option<String>,
253 enabled: bool,
254 policy: Option<String>,
255 tags: BTreeMap<String, String>,
256) -> Result<(String, String), String> {
257 let parts: Vec<&str> = primary_arn.split(':').collect();
258 if parts.len() < 6 {
259 return Err(format!("Invalid PrimaryKeyArn: {primary_arn}"));
260 }
261 let primary_region = parts[3].to_string();
262 let key_id = parts[5]
263 .strip_prefix("key/")
264 .ok_or_else(|| format!("PrimaryKeyArn missing key/ segment: {primary_arn}"))?
265 .to_string();
266
267 let mut accounts = state.write();
268 let s = accounts.get_or_create(account_id);
269 let region = s.region.clone();
270 let source_storage_keys = [key_id.clone(), format!("{primary_region}:{key_id}")];
274 let source = source_storage_keys
275 .iter()
276 .find_map(|k| s.keys.get(k).cloned())
277 .ok_or_else(|| format!("Primary key {primary_arn} does not exist"))?;
278 if !source.multi_region {
279 return Err(format!(
280 "Primary key {primary_arn} is not a multi-region key"
281 ));
282 }
283
284 let replica_key_id = format!("mrk-replica-{}", Uuid::new_v4().as_simple());
285 let replica_arn =
286 Arn::new("kms", ®ion, account_id, &format!("key/{replica_key_id}")).to_string();
287 let mut replica = source;
288 replica.key_id = replica_key_id.clone();
289 replica.arn = replica_arn.clone();
290 if let Some(d) = description {
291 if !d.is_empty() {
292 replica.description = d;
293 }
294 }
295 replica.enabled = enabled;
296 replica.key_state = if enabled { "Enabled" } else { "Disabled" }.to_string();
297 if let Some(p) = policy {
298 if !p.is_empty() {
299 replica.policy = p;
300 }
301 }
302 if !tags.is_empty() {
303 replica.tags.extend(tags);
304 }
305 replica.deletion_date = None;
306 replica.key_rotation_enabled = false;
307 replica.multi_region = true;
308 replica.rotations = Vec::new();
309 replica.custom_key_store_id = None;
310 replica.imported_key_material = false;
311 replica.imported_material_bytes = None;
312 replica.primary_region = Some(primary_region);
313
314 s.keys.insert(replica_key_id.clone(), replica);
315 Ok((replica_key_id, replica_arn))
316}
317
318pub fn provision_alias(
322 state: &SharedKmsState,
323 account_id: &str,
324 alias_name: &str,
325 target_input: &str,
326) -> Result<String, String> {
327 if !alias_name.starts_with("alias/") {
328 return Err(format!(
329 "AliasName must start with 'alias/'; got '{alias_name}'"
330 ));
331 }
332 let mut accounts = state.write();
333 let s = accounts.get_or_create(account_id);
334 let target_key_id = if s.keys.contains_key(target_input) {
335 target_input.to_string()
336 } else if let Some(id) = target_input
337 .strip_prefix("arn:aws:kms:")
338 .and_then(|rest| rest.split(":key/").nth(1))
339 {
340 if s.keys.contains_key(id) {
341 id.to_string()
342 } else {
343 return Err(format!("KMS key '{target_input}' does not exist"));
344 }
345 } else {
346 return Err(format!("KMS key '{target_input}' does not exist"));
347 };
348 let alias_arn = Arn::new("kms", &s.region, &s.account_id, alias_name).to_string();
349 let alias = KmsAlias {
350 alias_name: alias_name.to_string(),
351 alias_arn,
352 target_key_id,
353 creation_date: Utc::now().timestamp() as f64,
354 };
355 s.aliases.insert(alias_name.to_string(), alias);
356 Ok(alias_name.to_string())
357}
358
359#[derive(Debug, Default, Clone)]
367pub struct KeyUpdate {
368 pub description: Option<String>,
369 pub enabled: Option<bool>,
370 pub key_rotation_enabled: Option<bool>,
371 pub policy: Option<String>,
372 pub tags: Option<BTreeMap<String, String>>,
373}
374
375pub fn update_key_properties(
378 state: &SharedKmsState,
379 account_id: &str,
380 key_id: &str,
381 update: KeyUpdate,
382) -> Result<(), String> {
383 let mut accounts = state.write();
384 let s = accounts.get_or_create(account_id);
385 let key = s
386 .keys
387 .get_mut(key_id)
388 .ok_or_else(|| format!("Key '{key_id}' does not exist"))?;
389 if let Some(d) = update.description {
390 key.description = d;
391 }
392 if let Some(e) = update.enabled {
393 key.enabled = e;
394 key.key_state = if e { "Enabled" } else { "Disabled" }.to_string();
395 }
396 if let Some(r) = update.key_rotation_enabled {
397 key.key_rotation_enabled = r;
398 }
399 if let Some(p) = update.policy {
400 if !p.is_empty() {
401 key.policy = p;
402 }
403 }
404 if let Some(t) = update.tags {
405 key.tags = t;
406 }
407 Ok(())
408}
409
410pub fn update_alias_target(
414 state: &SharedKmsState,
415 account_id: &str,
416 alias_name: &str,
417 target_input: &str,
418) -> Result<(), String> {
419 let mut accounts = state.write();
420 let s = accounts.get_or_create(account_id);
421 let target_key_id = if s.keys.contains_key(target_input) {
422 target_input.to_string()
423 } else if let Some(id) = target_input
424 .strip_prefix("arn:aws:kms:")
425 .and_then(|rest| rest.split(":key/").nth(1))
426 {
427 if s.keys.contains_key(id) {
428 id.to_string()
429 } else {
430 return Err(format!("KMS key '{target_input}' does not exist"));
431 }
432 } else {
433 return Err(format!("KMS key '{target_input}' does not exist"));
434 };
435 let alias = s
436 .aliases
437 .get_mut(alias_name)
438 .ok_or_else(|| format!("Alias '{alias_name}' does not exist"))?;
439 alias.target_key_id = target_key_id;
440 Ok(())
441}