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 origin: input.origin.clone(),
209 multi_region: input.multi_region,
210 rotations: Vec::new(),
211 signing_algorithms: signing_algs,
212 encryption_algorithms: encryption_algs,
213 mac_algorithms: mac_algs,
214 custom_key_store_id: None,
215 imported_key_material: false,
216 imported_material_bytes: None,
217 private_key_seed: rand_bytes(32),
218 primary_region: None,
219 asymmetric_private_key_der: asym_priv,
220 asymmetric_public_key_der: asym_pub,
221 })
222}
223
224pub fn provision_key(
227 state: &SharedKmsState,
228 account_id: &str,
229 input: &KeyCreationInput,
230) -> Result<(String, String), String> {
231 let mut accounts = state.write();
232 let s = accounts.get_or_create(account_id);
233 let region = s.region.clone();
234 let key = build_kms_key(®ion, account_id, input)?;
235 let key_id = key.key_id.clone();
236 let arn = key.arn.clone();
237 s.keys.insert(key_id.clone(), key);
238 Ok((key_id, arn))
239}
240
241pub fn provision_replica_key(
248 state: &SharedKmsState,
249 account_id: &str,
250 primary_arn: &str,
251 description: Option<String>,
252 enabled: bool,
253 policy: Option<String>,
254 tags: BTreeMap<String, String>,
255) -> Result<(String, String), String> {
256 let parts: Vec<&str> = primary_arn.split(':').collect();
257 if parts.len() < 6 {
258 return Err(format!("Invalid PrimaryKeyArn: {primary_arn}"));
259 }
260 let primary_region = parts[3].to_string();
261 let key_id = parts[5]
262 .strip_prefix("key/")
263 .ok_or_else(|| format!("PrimaryKeyArn missing key/ segment: {primary_arn}"))?
264 .to_string();
265
266 let mut accounts = state.write();
267 let s = accounts.get_or_create(account_id);
268 let region = s.region.clone();
269 let source_storage_keys = [key_id.clone(), format!("{primary_region}:{key_id}")];
273 let source = source_storage_keys
274 .iter()
275 .find_map(|k| s.keys.get(k).cloned())
276 .ok_or_else(|| format!("Primary key {primary_arn} does not exist"))?;
277 if !source.multi_region {
278 return Err(format!(
279 "Primary key {primary_arn} is not a multi-region key"
280 ));
281 }
282
283 let replica_key_id = format!("mrk-replica-{}", Uuid::new_v4().as_simple());
284 let replica_arn =
285 Arn::new("kms", ®ion, account_id, &format!("key/{replica_key_id}")).to_string();
286 let mut replica = source;
287 replica.key_id = replica_key_id.clone();
288 replica.arn = replica_arn.clone();
289 if let Some(d) = description {
290 if !d.is_empty() {
291 replica.description = d;
292 }
293 }
294 replica.enabled = enabled;
295 replica.key_state = if enabled { "Enabled" } else { "Disabled" }.to_string();
296 if let Some(p) = policy {
297 if !p.is_empty() {
298 replica.policy = p;
299 }
300 }
301 if !tags.is_empty() {
302 replica.tags.extend(tags);
303 }
304 replica.deletion_date = None;
305 replica.key_rotation_enabled = false;
306 replica.multi_region = true;
307 replica.rotations = Vec::new();
308 replica.custom_key_store_id = None;
309 replica.imported_key_material = false;
310 replica.imported_material_bytes = None;
311 replica.primary_region = Some(primary_region);
312
313 s.keys.insert(replica_key_id.clone(), replica);
314 Ok((replica_key_id, replica_arn))
315}
316
317pub fn provision_alias(
321 state: &SharedKmsState,
322 account_id: &str,
323 alias_name: &str,
324 target_input: &str,
325) -> Result<String, String> {
326 if !alias_name.starts_with("alias/") {
327 return Err(format!(
328 "AliasName must start with 'alias/'; got '{alias_name}'"
329 ));
330 }
331 let mut accounts = state.write();
332 let s = accounts.get_or_create(account_id);
333 let target_key_id = if s.keys.contains_key(target_input) {
334 target_input.to_string()
335 } else if let Some(id) = target_input
336 .strip_prefix("arn:aws:kms:")
337 .and_then(|rest| rest.split(":key/").nth(1))
338 {
339 if s.keys.contains_key(id) {
340 id.to_string()
341 } else {
342 return Err(format!("KMS key '{target_input}' does not exist"));
343 }
344 } else {
345 return Err(format!("KMS key '{target_input}' does not exist"));
346 };
347 let alias_arn = Arn::new("kms", &s.region, &s.account_id, alias_name).to_string();
348 let alias = KmsAlias {
349 alias_name: alias_name.to_string(),
350 alias_arn,
351 target_key_id,
352 creation_date: Utc::now().timestamp() as f64,
353 };
354 s.aliases.insert(alias_name.to_string(), alias);
355 Ok(alias_name.to_string())
356}
357
358#[derive(Debug, Default, Clone)]
366pub struct KeyUpdate {
367 pub description: Option<String>,
368 pub enabled: Option<bool>,
369 pub key_rotation_enabled: Option<bool>,
370 pub policy: Option<String>,
371 pub tags: Option<BTreeMap<String, String>>,
372}
373
374pub fn update_key_properties(
377 state: &SharedKmsState,
378 account_id: &str,
379 key_id: &str,
380 update: KeyUpdate,
381) -> Result<(), String> {
382 let mut accounts = state.write();
383 let s = accounts.get_or_create(account_id);
384 let key = s
385 .keys
386 .get_mut(key_id)
387 .ok_or_else(|| format!("Key '{key_id}' does not exist"))?;
388 if let Some(d) = update.description {
389 key.description = d;
390 }
391 if let Some(e) = update.enabled {
392 key.enabled = e;
393 key.key_state = if e { "Enabled" } else { "Disabled" }.to_string();
394 }
395 if let Some(r) = update.key_rotation_enabled {
396 key.key_rotation_enabled = r;
397 }
398 if let Some(p) = update.policy {
399 if !p.is_empty() {
400 key.policy = p;
401 }
402 }
403 if let Some(t) = update.tags {
404 key.tags = t;
405 }
406 Ok(())
407}
408
409pub fn update_alias_target(
413 state: &SharedKmsState,
414 account_id: &str,
415 alias_name: &str,
416 target_input: &str,
417) -> Result<(), String> {
418 let mut accounts = state.write();
419 let s = accounts.get_or_create(account_id);
420 let target_key_id = if s.keys.contains_key(target_input) {
421 target_input.to_string()
422 } else if let Some(id) = target_input
423 .strip_prefix("arn:aws:kms:")
424 .and_then(|rest| rest.split(":key/").nth(1))
425 {
426 if s.keys.contains_key(id) {
427 id.to_string()
428 } else {
429 return Err(format!("KMS key '{target_input}' does not exist"));
430 }
431 } else {
432 return Err(format!("KMS key '{target_input}' does not exist"));
433 };
434 let alias = s
435 .aliases
436 .get_mut(alias_name)
437 .ok_or_else(|| format!("Alias '{alias_name}' does not exist"))?;
438 alias.target_key_id = target_key_id;
439 Ok(())
440}