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