1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use base64::Engine;
6use chrono::Utc;
7use http::StatusCode;
8use serde_json::{json, Value};
9use tokio::sync::Mutex as AsyncMutex;
10use uuid::Uuid;
11
12use fakecloud_aws::arn::Arn;
13use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
14use fakecloud_core::validation::*;
15use fakecloud_persistence::SnapshotStore;
16
17use crate::state::{
18 CustomKeyStore, KeyRotation, KmsAlias, KmsGrant, KmsKey, KmsSnapshot, KmsState, SharedKmsState,
19 KMS_SNAPSHOT_SCHEMA_VERSION,
20};
21
22const FAKE_ENVELOPE_PREFIX: &str = "fakecloud-kms:";
23const IMPORTED_ENVELOPE_PREFIX: &str = "fakecloud-imported:";
24const RSA_ENVELOPE_PREFIX: &str = "fakecloud-rsa:";
25
26pub(crate) struct DecodedCiphertext {
31 source_arn: String,
32 plaintext_b64: String,
33 encryption_algorithm: String,
37}
38
39const VALID_KEY_SPECS: &[&str] = &[
40 "ECC_NIST_P256",
41 "ECC_NIST_P384",
42 "ECC_NIST_P521",
43 "ECC_SECG_P256K1",
44 "HMAC_224",
45 "HMAC_256",
46 "HMAC_384",
47 "HMAC_512",
48 "RSA_2048",
49 "RSA_3072",
50 "RSA_4096",
51 "SM2",
52 "SYMMETRIC_DEFAULT",
53];
54
55const VALID_SIGNING_ALGORITHMS: &[&str] = &[
56 "RSASSA_PKCS1_V1_5_SHA_256",
57 "RSASSA_PKCS1_V1_5_SHA_384",
58 "RSASSA_PKCS1_V1_5_SHA_512",
59 "RSASSA_PSS_SHA_256",
60 "RSASSA_PSS_SHA_384",
61 "RSASSA_PSS_SHA_512",
62 "ECDSA_SHA_256",
63 "ECDSA_SHA_384",
64 "ECDSA_SHA_512",
65];
66
67static KMS_ACTIONS: &[&str] = &[
71 "CreateKey",
72 "DescribeKey",
73 "GetKeyLastUsage",
74 "ListKeys",
75 "EnableKey",
76 "DisableKey",
77 "ScheduleKeyDeletion",
78 "CancelKeyDeletion",
79 "Encrypt",
80 "Decrypt",
81 "ReEncrypt",
82 "GenerateDataKey",
83 "GenerateDataKeyWithoutPlaintext",
84 "GenerateRandom",
85 "CreateAlias",
86 "DeleteAlias",
87 "UpdateAlias",
88 "ListAliases",
89 "TagResource",
90 "UntagResource",
91 "ListResourceTags",
92 "UpdateKeyDescription",
93 "GetKeyPolicy",
94 "PutKeyPolicy",
95 "ListKeyPolicies",
96 "GetKeyRotationStatus",
97 "EnableKeyRotation",
98 "DisableKeyRotation",
99 "RotateKeyOnDemand",
100 "ListKeyRotations",
101 "Sign",
102 "Verify",
103 "GetPublicKey",
104 "CreateGrant",
105 "ListGrants",
106 "ListRetirableGrants",
107 "RevokeGrant",
108 "RetireGrant",
109 "GenerateMac",
110 "VerifyMac",
111 "ReplicateKey",
112 "GenerateDataKeyPair",
113 "GenerateDataKeyPairWithoutPlaintext",
114 "DeriveSharedSecret",
115 "GetParametersForImport",
116 "ImportKeyMaterial",
117 "DeleteImportedKeyMaterial",
118 "UpdatePrimaryRegion",
119 "CreateCustomKeyStore",
120 "DeleteCustomKeyStore",
121 "DescribeCustomKeyStores",
122 "ConnectCustomKeyStore",
123 "DisconnectCustomKeyStore",
124 "UpdateCustomKeyStore",
125];
126
127pub struct KmsService {
128 state: SharedKmsState,
129 snapshot_store: Option<Arc<dyn SnapshotStore>>,
130 snapshot_lock: Arc<AsyncMutex<()>>,
131}
132
133impl KmsService {
134 pub fn new(state: SharedKmsState) -> Self {
135 if tokio::runtime::Handle::try_current().is_ok() {
141 tokio::task::spawn_blocking(|| {
142 let _ = self::asym::generate_keypair("RSA_2048");
143 let _ = self::asym::generate_keypair("RSA_3072");
144 let _ = self::asym::generate_keypair("RSA_4096");
145 });
146 }
147 Self {
148 state,
149 snapshot_store: None,
150 snapshot_lock: Arc::new(AsyncMutex::new(())),
151 }
152 }
153
154 pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
155 self.snapshot_store = Some(store);
156 self
157 }
158
159 async fn save_snapshot(&self) {
163 save_kms_snapshot(
164 &self.state,
165 self.snapshot_store.clone(),
166 &self.snapshot_lock,
167 )
168 .await;
169 }
170
171 pub fn snapshot_hook(&self) -> Option<fakecloud_persistence::SnapshotHook> {
176 let store = self.snapshot_store.clone()?;
177 let state = self.state.clone();
178 let lock = self.snapshot_lock.clone();
179 Some(Arc::new(move || {
180 let state = state.clone();
181 let store = store.clone();
182 let lock = lock.clone();
183 Box::pin(async move {
184 save_kms_snapshot(&state, Some(store), &lock).await;
185 })
186 }))
187 }
188}
189
190pub async fn save_kms_snapshot(
196 state: &SharedKmsState,
197 store: Option<Arc<dyn SnapshotStore>>,
198 lock: &AsyncMutex<()>,
199) {
200 let Some(store) = store else {
201 return;
202 };
203 let _guard = lock.lock().await;
204 let snapshot = KmsSnapshot {
205 schema_version: KMS_SNAPSHOT_SCHEMA_VERSION,
206 state: None,
207 accounts: Some(state.read().clone()),
208 };
209 let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
210 let bytes = serde_json::to_vec(&snapshot)
211 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
212 store.save(&bytes)
213 })
214 .await;
215 match join {
216 Ok(Ok(())) => {}
217 Ok(Err(err)) => tracing::error!(%err, "failed to write kms snapshot"),
218 Err(err) => tracing::error!(%err, "kms snapshot task panicked"),
219 }
220}
221
222#[async_trait]
223impl AwsService for KmsService {
224 fn service_name(&self) -> &str {
225 "kms"
226 }
227
228 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
229 let mutates = is_mutating_action(req.action.as_str());
230 let result = match req.action.as_str() {
231 "CreateKey" => self.create_key(&req),
232 "DescribeKey" => self.describe_key(&req),
233 "GetKeyLastUsage" => self.get_key_last_usage(&req),
234 "ListKeys" => self.list_keys(&req),
235 "EnableKey" => self.enable_key(&req),
236 "DisableKey" => self.disable_key(&req),
237 "ScheduleKeyDeletion" => self.schedule_key_deletion(&req),
238 "CancelKeyDeletion" => self.cancel_key_deletion(&req),
239 "Encrypt" => self.encrypt(&req),
240 "Decrypt" => self.decrypt(&req),
241 "ReEncrypt" => self.re_encrypt(&req),
242 "GenerateDataKey" => self.generate_data_key(&req),
243 "GenerateDataKeyWithoutPlaintext" => self.generate_data_key_without_plaintext(&req),
244 "GenerateRandom" => self.generate_random(&req),
245 "CreateAlias" => self.create_alias(&req),
246 "DeleteAlias" => self.delete_alias(&req),
247 "UpdateAlias" => self.update_alias(&req),
248 "ListAliases" => self.list_aliases(&req),
249 "TagResource" => self.tag_resource(&req),
250 "UntagResource" => self.untag_resource(&req),
251 "ListResourceTags" => self.list_resource_tags(&req),
252 "UpdateKeyDescription" => self.update_key_description(&req),
253 "GetKeyPolicy" => self.get_key_policy(&req),
254 "PutKeyPolicy" => self.put_key_policy(&req),
255 "ListKeyPolicies" => self.list_key_policies(&req),
256 "GetKeyRotationStatus" => self.get_key_rotation_status(&req),
257 "EnableKeyRotation" => self.enable_key_rotation(&req),
258 "DisableKeyRotation" => self.disable_key_rotation(&req),
259 "RotateKeyOnDemand" => self.rotate_key_on_demand(&req),
260 "ListKeyRotations" => self.list_key_rotations(&req),
261 "Sign" => self.sign(&req),
262 "Verify" => self.verify(&req),
263 "GetPublicKey" => self.get_public_key(&req),
264 "CreateGrant" => self.create_grant(&req),
265 "ListGrants" => self.list_grants(&req),
266 "ListRetirableGrants" => self.list_retirable_grants(&req),
267 "RevokeGrant" => self.revoke_grant(&req),
268 "RetireGrant" => self.retire_grant(&req),
269 "GenerateMac" => self.generate_mac(&req),
270 "VerifyMac" => self.verify_mac(&req),
271 "ReplicateKey" => self.replicate_key(&req),
272 "GenerateDataKeyPair" => self.generate_data_key_pair(&req),
273 "GenerateDataKeyPairWithoutPlaintext" => {
274 self.generate_data_key_pair_without_plaintext(&req)
275 }
276 "DeriveSharedSecret" => self.derive_shared_secret(&req),
277 "GetParametersForImport" => self.get_parameters_for_import(&req),
278 "ImportKeyMaterial" => self.import_key_material(&req),
279 "DeleteImportedKeyMaterial" => self.delete_imported_key_material(&req),
280 "UpdatePrimaryRegion" => self.update_primary_region(&req),
281 "CreateCustomKeyStore" => self.create_custom_key_store(&req),
282 "DeleteCustomKeyStore" => self.delete_custom_key_store(&req),
283 "DescribeCustomKeyStores" => self.describe_custom_key_stores(&req),
284 "ConnectCustomKeyStore" => self.connect_custom_key_store(&req),
285 "DisconnectCustomKeyStore" => self.disconnect_custom_key_store(&req),
286 "UpdateCustomKeyStore" => self.update_custom_key_store(&req),
287 _ => Err(AwsServiceError::action_not_implemented("kms", &req.action)),
288 };
289 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
290 self.save_snapshot().await;
291 }
292 result
293 }
294
295 fn supported_actions(&self) -> &[&str] {
296 KMS_ACTIONS
297 }
298
299 fn iam_enforceable(&self) -> bool {
300 true
301 }
302
303 fn iam_action_for(&self, request: &AwsRequest) -> Option<fakecloud_core::auth::IamAction> {
304 let action = KMS_ACTIONS.iter().copied().find(|a| *a == request.action)?;
305 let resource = kms_resource_for(action, &self.state, request);
306 Some(fakecloud_core::auth::IamAction {
307 service: "kms",
308 action,
309 resource,
310 })
311 }
312
313 fn resource_tags_for(
314 &self,
315 resource_arn: &str,
316 ) -> Option<std::collections::HashMap<String, String>> {
317 if resource_arn == "*" {
318 return Some(std::collections::HashMap::new());
319 }
320 let key_id = resource_arn.rsplit_once(":key/")?.1;
321 let account_id = resource_arn.split(':').nth(4).unwrap_or("").to_string();
322 let accounts = self.state.read();
323 let state = accounts.get(&account_id)?;
324 let key = state.keys.get(key_id)?;
325 Some(
326 key.tags
327 .iter()
328 .map(|(k, v)| (k.clone(), v.clone()))
329 .collect(),
330 )
331 }
332
333 fn request_tags_from(
334 &self,
335 request: &AwsRequest,
336 action: &str,
337 ) -> Option<std::collections::HashMap<String, String>> {
338 match action {
339 "CreateKey" | "TagResource" => {
340 let body = request.json_body();
341 let mut tags = std::collections::HashMap::new();
342 if let Some(arr) = body["Tags"].as_array() {
343 for tag in arr {
344 if let (Some(k), Some(v)) =
345 (tag["TagKey"].as_str(), tag["TagValue"].as_str())
346 {
347 tags.insert(k.to_string(), v.to_string());
348 }
349 }
350 }
351 Some(tags)
352 }
353 _ => Some(std::collections::HashMap::new()),
354 }
355 }
356}
357
358struct CreateKeyInput {
360 custom_key_store_id: Option<String>,
361 description: String,
362 key_usage: String,
363 key_spec: String,
364 origin: String,
365 multi_region: bool,
366 policy: Option<String>,
367 tags: BTreeMap<String, String>,
368}
369
370impl CreateKeyInput {
371 fn from_body(body: &Value) -> Result<Self, AwsServiceError> {
372 recoded("CustomKeyStoreInvalidStateException", || {
375 validate_optional_string_length(
376 "customKeyStoreId",
377 body["CustomKeyStoreId"].as_str(),
378 1,
379 64,
380 )
381 })?;
382 recoded("UnsupportedOperationException", || {
383 validate_optional_string_length("description", body["Description"].as_str(), 0, 8192)
384 })?;
385 recoded("UnsupportedOperationException", || {
386 validate_optional_enum(
387 "keyUsage",
388 body["KeyUsage"].as_str(),
389 &[
390 "SIGN_VERIFY",
391 "ENCRYPT_DECRYPT",
392 "GENERATE_VERIFY_MAC",
393 "KEY_AGREEMENT",
394 ],
395 )
396 })?;
397 recoded("UnsupportedOperationException", || {
398 validate_optional_enum(
399 "origin",
400 body["Origin"].as_str(),
401 &["AWS_KMS", "EXTERNAL", "AWS_CLOUDHSM", "EXTERNAL_KEY_STORE"],
402 )
403 })?;
404 recoded("MalformedPolicyDocumentException", || {
405 validate_optional_string_length("policy", body["Policy"].as_str(), 1, 131072)
406 })?;
407 recoded("XksKeyInvalidConfigurationException", || {
408 validate_optional_string_length("xksKeyId", body["XksKeyId"].as_str(), 1, 64)
409 })?;
410
411 let key_spec = body["KeySpec"]
412 .as_str()
413 .or_else(|| body["CustomerMasterKeySpec"].as_str())
414 .unwrap_or("SYMMETRIC_DEFAULT")
415 .to_string();
416 if !VALID_KEY_SPECS.contains(&key_spec.as_str()) {
417 return Err(AwsServiceError::aws_error(
418 StatusCode::BAD_REQUEST,
419 "UnsupportedOperationException",
420 format!(
421 "1 validation error detected: Value '{key_spec}' at 'KeySpec' failed to satisfy constraint: Member must satisfy enum value set: {}",
422 fmt_enum_set(&VALID_KEY_SPECS.iter().map(|s| s.to_string()).collect::<Vec<_>>())
423 ),
424 ));
425 }
426
427 let tags: BTreeMap<String, String> = body["Tags"]
428 .as_array()
429 .map(|arr| {
430 arr.iter()
431 .filter_map(|t| {
432 let k = t["TagKey"].as_str()?;
433 let v = t["TagValue"].as_str()?;
434 Some((k.to_string(), v.to_string()))
435 })
436 .collect()
437 })
438 .unwrap_or_default();
439
440 Ok(Self {
441 custom_key_store_id: body["CustomKeyStoreId"].as_str().map(|s| s.to_string()),
442 description: body["Description"].as_str().unwrap_or("").to_string(),
443 key_usage: body["KeyUsage"]
444 .as_str()
445 .unwrap_or("ENCRYPT_DECRYPT")
446 .to_string(),
447 key_spec,
448 origin: body["Origin"].as_str().unwrap_or("AWS_KMS").to_string(),
449 multi_region: body["MultiRegion"].as_bool().unwrap_or(false),
450 policy: body["Policy"].as_str().map(|s| s.to_string()),
451 tags,
452 })
453 }
454}
455
456impl KmsService {
457 fn resolve_key_id_for(
458 &self,
459 account_id: &str,
460 region: &str,
461 key_id_or_arn: &str,
462 ) -> Option<String> {
463 let accounts = self.state.read();
464 let empty = KmsState::new(account_id, region);
465 let state = accounts.get(account_id).unwrap_or(&empty);
466 Self::resolve_key_id_with_state(state, key_id_or_arn)
467 }
468
469 pub(crate) fn resolve_key_id_with_state(
470 state: &crate::state::KmsState,
471 key_id_or_arn: &str,
472 ) -> Option<String> {
473 if state.keys.contains_key(key_id_or_arn) {
475 return Some(key_id_or_arn.to_string());
476 }
477
478 if key_id_or_arn.starts_with("arn:aws:kms:") {
480 if key_id_or_arn.contains(":key/") {
482 if let Some(id) = key_id_or_arn.rsplit('/').next() {
483 if state.keys.contains_key(id) {
484 return Some(id.to_string());
485 }
486 }
487 }
488 if key_id_or_arn.contains(":alias/") {
490 if let Some(alias_part) = key_id_or_arn.split(':').next_back() {
491 if let Some(alias) = state.aliases.get(alias_part) {
492 return Some(alias.target_key_id.clone());
493 }
494 }
495 }
496 }
497
498 if key_id_or_arn.starts_with("alias/") {
500 if let Some(alias) = state.aliases.get(key_id_or_arn) {
501 return Some(alias.target_key_id.clone());
502 }
503 }
504
505 None
506 }
507
508 fn require_key_id(body: &Value) -> Result<String, AwsServiceError> {
509 body["KeyId"]
510 .as_str()
511 .map(|s| s.to_string())
512 .ok_or_else(|| {
513 AwsServiceError::aws_error(
514 StatusCode::BAD_REQUEST,
515 "ValidationException",
516 "KeyId is required",
517 )
518 })
519 }
520
521 fn resolve_required_key(
522 &self,
523 req: &AwsRequest,
524 body: &Value,
525 ) -> Result<String, AwsServiceError> {
526 let key_id_input = Self::require_key_id(body)?;
527 self.resolve_key_id_for(&req.account_id, &req.region, &key_id_input)
528 .ok_or_else(|| {
529 AwsServiceError::aws_error(
530 StatusCode::BAD_REQUEST,
531 "NotFoundException",
532 format!("Key '{key_id_input}' does not exist"),
533 )
534 })
535 }
536
537 fn create_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
538 let input = CreateKeyInput::from_body(&req.json_body())?;
539
540 let mut accounts = self.state.write();
541 let state = accounts.get_or_create(&req.account_id);
542
543 let key_id = if input.multi_region {
544 format!("mrk-{}", Uuid::new_v4().as_simple())
545 } else {
546 Uuid::new_v4().to_string()
547 };
548
549 let arn = format!(
550 "arn:aws:kms:{}:{}:key/{}",
551 state.region, state.account_id, key_id
552 );
553 let now = Utc::now().timestamp() as f64;
554
555 let signing_algs = if input.key_usage == "SIGN_VERIFY" {
556 signing_algorithms_for_key_spec(&input.key_spec)
557 } else {
558 None
559 };
560 let encryption_algs = encryption_algorithms_for_key(&input.key_usage, &input.key_spec);
561 let mac_algs = if input.key_usage == "GENERATE_VERIFY_MAC" {
562 mac_algorithms_for_key_spec(&input.key_spec)
563 } else {
564 None
565 };
566
567 let key_policy = input
568 .policy
569 .unwrap_or_else(|| default_key_policy(&state.account_id));
570
571 let mut asym_priv: Option<Vec<u8>> = None;
572 let mut asym_pub: Option<Vec<u8>> = None;
573 if let Some((p, k)) = asym::generate_keypair(&input.key_spec).map_err(|e| {
574 AwsServiceError::aws_error(
575 StatusCode::INTERNAL_SERVER_ERROR,
576 "KMSInternalException",
577 format!("failed to generate asymmetric key: {e}"),
578 )
579 })? {
580 asym_priv = Some(p);
581 asym_pub = Some(k);
582 } else if let Some((p, k)) = asym_ecdsa::generate_keypair(&input.key_spec).map_err(|e| {
583 AwsServiceError::aws_error(
584 StatusCode::INTERNAL_SERVER_ERROR,
585 "KMSInternalException",
586 format!("failed to generate ecdsa key: {e}"),
587 )
588 })? {
589 asym_priv = Some(p);
590 asym_pub = Some(k);
591 }
592
593 let is_asymmetric = input.key_spec.starts_with("ECC_")
598 || input.key_spec.starts_with("RSA_")
599 || input.key_spec == "SM2";
600 if is_asymmetric && asym_priv.is_none() {
601 return Err(AwsServiceError::aws_error(
602 StatusCode::BAD_REQUEST,
603 "UnsupportedOperationException",
604 format!(
605 "KeySpec '{}' is not supported by this fakecloud build; \
606 no fake-signature fallback is provided",
607 input.key_spec
608 ),
609 ));
610 }
611
612 let pending_import = input.origin == "EXTERNAL";
618 let key = KmsKey {
619 key_id: key_id.clone(),
620 arn: arn.clone(),
621 creation_date: now,
622 description: input.description,
623 enabled: !pending_import,
624 key_usage: input.key_usage,
625 key_spec: input.key_spec,
626 key_manager: "CUSTOMER".to_string(),
627 key_state: if pending_import {
628 "PendingImport".to_string()
629 } else {
630 "Enabled".to_string()
631 },
632 deletion_date: None,
633 tags: input.tags,
634 policy: key_policy,
635 key_rotation_enabled: false,
636 rotation_period_in_days: None,
637 origin: input.origin,
638 multi_region: input.multi_region,
639 rotations: Vec::new(),
640 signing_algorithms: signing_algs,
641 encryption_algorithms: encryption_algs,
642 mac_algorithms: mac_algs,
643 custom_key_store_id: input.custom_key_store_id,
644 imported_key_material: false,
645 imported_material_bytes: None,
646 private_key_seed: rand_bytes(32),
647 primary_region: None,
648 asymmetric_private_key_der: asym_priv,
649 asymmetric_public_key_der: asym_pub,
650 };
651
652 let metadata = key_metadata_json(&key, &state.account_id);
653 state.keys.insert(key_id, key);
654
655 Ok(AwsResponse::json(
656 StatusCode::OK,
657 serde_json::to_string(&json!({ "KeyMetadata": metadata })).unwrap(),
658 ))
659 }
660
661 fn describe_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
662 let body = req.json_body();
663 let key_id_input = body["KeyId"].as_str().ok_or_else(|| {
664 AwsServiceError::aws_error(
665 StatusCode::BAD_REQUEST,
666 "ValidationException",
667 "KeyId is required",
668 )
669 })?;
670
671 let accounts = self.state.read();
672 let empty = KmsState::new(&req.account_id, &req.region);
673 let state = accounts.get(&req.account_id).unwrap_or(&empty);
674
675 let resolved = Self::resolve_key_id_with_state(state, key_id_input).ok_or_else(|| {
677 AwsServiceError::aws_error(
678 StatusCode::BAD_REQUEST,
679 "NotFoundException",
680 format!("Key '{key_id_input}' does not exist"),
681 )
682 })?;
683
684 let key = state.keys.get(&resolved).ok_or_else(|| {
685 AwsServiceError::aws_error(
686 StatusCode::BAD_REQUEST,
687 "NotFoundException",
688 format!("Key '{key_id_input}' does not exist"),
689 )
690 })?;
691
692 check_policy_deny(key, "kms:DescribeKey")?;
694
695 let metadata = key_metadata_json(key, &state.account_id);
696 Ok(AwsResponse::json(
697 StatusCode::OK,
698 serde_json::to_string(&json!({ "KeyMetadata": metadata })).unwrap(),
699 ))
700 }
701
702 fn get_key_last_usage(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
703 let body = req.json_body();
704 let key_id_input = body["KeyId"].as_str().ok_or_else(|| {
705 AwsServiceError::aws_error(
706 StatusCode::BAD_REQUEST,
707 "ValidationException",
708 "KeyId is required",
709 )
710 })?;
711
712 let accounts = self.state.read();
713 let empty = KmsState::new(&req.account_id, &req.region);
714 let state = accounts.get(&req.account_id).unwrap_or(&empty);
715
716 let resolved = Self::resolve_key_id_with_state(state, key_id_input).ok_or_else(|| {
717 AwsServiceError::aws_error(
718 StatusCode::BAD_REQUEST,
719 "NotFoundException",
720 format!("Key '{key_id_input}' does not exist"),
721 )
722 })?;
723 let key = state.keys.get(&resolved).ok_or_else(|| {
724 AwsServiceError::aws_error(
725 StatusCode::BAD_REQUEST,
726 "NotFoundException",
727 format!("Key '{key_id_input}' does not exist"),
728 )
729 })?;
730
731 Ok(AwsResponse::json(
735 StatusCode::OK,
736 serde_json::to_string(&json!({
737 "KeyId": key.key_id,
738 "KeyCreationDate": key.creation_date,
739 "TrackingStartDate": key.creation_date,
740 }))
741 .unwrap(),
742 ))
743 }
744
745 fn list_keys(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
746 let body = req.json_body();
747
748 recoded("InvalidMarkerException", || {
752 validate_optional_json_range("limit", &body["Limit"], 1, 1000)
753 })?;
754 recoded("InvalidMarkerException", || {
755 validate_optional_string_length("marker", body["Marker"].as_str(), 1, 320)
756 })?;
757
758 let limit = body["Limit"].as_i64().unwrap_or(1000) as usize;
759 let marker = body["Marker"].as_str();
760
761 let accounts = self.state.read();
762 let empty = KmsState::new(&req.account_id, &req.region);
763 let state = accounts.get(&req.account_id).unwrap_or(&empty);
764 let all_keys: Vec<Value> = state
765 .keys
766 .values()
767 .map(|k| {
768 json!({
769 "KeyId": k.key_id,
770 "KeyArn": k.arn,
771 })
772 })
773 .collect();
774
775 let start = if let Some(m) = marker {
776 all_keys
777 .iter()
778 .position(|k| k["KeyId"].as_str() == Some(m))
779 .map(|pos| pos + 1)
780 .unwrap_or(0)
781 } else {
782 0
783 };
784
785 let page = &all_keys[start..all_keys.len().min(start + limit)];
786 let truncated = start + limit < all_keys.len();
787
788 let mut result = json!({
789 "Keys": page,
790 "Truncated": truncated,
791 });
792
793 if truncated {
794 if let Some(last) = page.last() {
795 result["NextMarker"] = last["KeyId"].clone();
796 }
797 }
798
799 Ok(AwsResponse::json(
800 StatusCode::OK,
801 serde_json::to_string(&result).unwrap(),
802 ))
803 }
804
805 fn enable_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
806 let body = req.json_body();
807 let resolved = self.resolve_required_key(req, &body)?;
808
809 let mut accounts = self.state.write();
810 let state = accounts.get_or_create(&req.account_id);
811 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
812 AwsServiceError::aws_error(
813 StatusCode::INTERNAL_SERVER_ERROR,
814 "KMSInternalException",
815 "Key state became inconsistent",
816 )
817 })?;
818 key.enabled = true;
819 key.key_state = "Enabled".to_string();
820
821 Ok(AwsResponse::json(StatusCode::OK, "{}"))
822 }
823
824 fn disable_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
825 let body = req.json_body();
826 let resolved = self.resolve_required_key(req, &body)?;
827
828 let mut accounts = self.state.write();
829 let state = accounts.get_or_create(&req.account_id);
830 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
831 AwsServiceError::aws_error(
832 StatusCode::INTERNAL_SERVER_ERROR,
833 "KMSInternalException",
834 "Key state became inconsistent",
835 )
836 })?;
837 key.enabled = false;
838 key.key_state = "Disabled".to_string();
839
840 Ok(AwsResponse::json(StatusCode::OK, "{}"))
841 }
842
843 fn schedule_key_deletion(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
844 let body = req.json_body();
845 let resolved = self.resolve_required_key(req, &body)?;
846 let pending_days = body["PendingWindowInDays"].as_i64().unwrap_or(30);
847
848 let mut accounts = self.state.write();
849 let state = accounts.get_or_create(&req.account_id);
850 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
851 AwsServiceError::aws_error(
852 StatusCode::INTERNAL_SERVER_ERROR,
853 "KMSInternalException",
854 "Key state became inconsistent",
855 )
856 })?;
857 let deletion_date =
858 Utc::now().timestamp() as f64 + (pending_days as f64 * 24.0 * 60.0 * 60.0);
859 key.key_state = "PendingDeletion".to_string();
860 key.enabled = false;
861 key.deletion_date = Some(deletion_date);
862
863 Ok(AwsResponse::json(
864 StatusCode::OK,
865 serde_json::to_string(&json!({
866 "KeyId": key.key_id,
867 "DeletionDate": deletion_date,
868 "KeyState": "PendingDeletion",
869 "PendingWindowInDays": pending_days,
870 }))
871 .unwrap(),
872 ))
873 }
874
875 fn cancel_key_deletion(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
876 let body = req.json_body();
877 let resolved = self.resolve_required_key(req, &body)?;
878
879 let mut accounts = self.state.write();
880 let state = accounts.get_or_create(&req.account_id);
881 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
882 AwsServiceError::aws_error(
883 StatusCode::INTERNAL_SERVER_ERROR,
884 "KMSInternalException",
885 "Key state became inconsistent",
886 )
887 })?;
888 key.key_state = "Disabled".to_string();
889 key.deletion_date = None;
890
891 Ok(AwsResponse::json(
892 StatusCode::OK,
893 serde_json::to_string(&json!({
894 "KeyId": key.key_id,
895 }))
896 .unwrap(),
897 ))
898 }
899
900 fn tag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
901 let body = req.json_body();
902 let key_id = Self::require_key_id(&body)?;
903
904 let resolved = self
905 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
906 .ok_or_else(|| {
907 AwsServiceError::aws_error(
908 StatusCode::BAD_REQUEST,
909 "NotFoundException",
910 format!("Invalid keyId {key_id}"),
911 )
912 })?;
913
914 let mut accounts = self.state.write();
915 let state = accounts.get_or_create(&req.account_id);
916 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
917 AwsServiceError::aws_error(
918 StatusCode::INTERNAL_SERVER_ERROR,
919 "KMSInternalException",
920 "Key state became inconsistent",
921 )
922 })?;
923
924 fakecloud_core::tags::apply_tags(&mut key.tags, &body, "Tags", "TagKey", "TagValue")
925 .map_err(|f| {
926 AwsServiceError::aws_error(
927 StatusCode::BAD_REQUEST,
928 "ValidationException",
929 format!("{f} must be a list"),
930 )
931 })?;
932
933 Ok(AwsResponse::json(StatusCode::OK, "{}"))
934 }
935
936 fn untag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
937 let body = req.json_body();
938 let key_id = Self::require_key_id(&body)?;
939
940 let resolved = self
941 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
942 .ok_or_else(|| {
943 AwsServiceError::aws_error(
944 StatusCode::BAD_REQUEST,
945 "NotFoundException",
946 format!("Invalid keyId {key_id}"),
947 )
948 })?;
949
950 let mut accounts = self.state.write();
951 let state = accounts.get_or_create(&req.account_id);
952 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
953 AwsServiceError::aws_error(
954 StatusCode::INTERNAL_SERVER_ERROR,
955 "KMSInternalException",
956 "Key state became inconsistent",
957 )
958 })?;
959
960 fakecloud_core::tags::remove_tags(&mut key.tags, &body, "TagKeys").map_err(|f| {
961 AwsServiceError::aws_error(
962 StatusCode::BAD_REQUEST,
963 "ValidationException",
964 format!("{f} must be a list"),
965 )
966 })?;
967
968 Ok(AwsResponse::json(StatusCode::OK, "{}"))
969 }
970
971 fn list_resource_tags(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
972 let body = req.json_body();
973 let key_id = Self::require_key_id(&body)?;
974
975 let resolved = self
976 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
977 .ok_or_else(|| {
978 AwsServiceError::aws_error(
979 StatusCode::BAD_REQUEST,
980 "NotFoundException",
981 format!("Invalid keyId {key_id}"),
982 )
983 })?;
984
985 let accounts = self.state.read();
986 let empty = KmsState::new(&req.account_id, &req.region);
987 let state = accounts.get(&req.account_id).unwrap_or(&empty);
988 let key = state.keys.get(&resolved).ok_or_else(|| {
989 AwsServiceError::aws_error(
990 StatusCode::INTERNAL_SERVER_ERROR,
991 "KMSInternalException",
992 "Key state became inconsistent",
993 )
994 })?;
995 let tags = fakecloud_core::tags::tags_to_json(&key.tags, "TagKey", "TagValue");
996
997 Ok(AwsResponse::json(
998 StatusCode::OK,
999 serde_json::to_string(&json!({
1000 "Tags": tags,
1001 "Truncated": false,
1002 }))
1003 .unwrap(),
1004 ))
1005 }
1006
1007 fn update_key_description(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1008 let body = req.json_body();
1009 let resolved = self.resolve_required_key(req, &body)?;
1010 let description = body["Description"].as_str().unwrap_or("").to_string();
1011
1012 let mut accounts = self.state.write();
1013 let state = accounts.get_or_create(&req.account_id);
1014 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1015 AwsServiceError::aws_error(
1016 StatusCode::INTERNAL_SERVER_ERROR,
1017 "KMSInternalException",
1018 "Key state became inconsistent",
1019 )
1020 })?;
1021 key.description = description;
1022
1023 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1024 }
1025
1026 fn get_key_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1027 let body = req.json_body();
1028 let key_id = Self::require_key_id(&body)?;
1029
1030 if key_id.starts_with("alias/") {
1032 return Err(AwsServiceError::aws_error(
1033 StatusCode::BAD_REQUEST,
1034 "NotFoundException",
1035 format!("Invalid keyId {key_id}"),
1036 ));
1037 }
1038
1039 let resolved = self
1040 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1041 .ok_or_else(|| {
1042 AwsServiceError::aws_error(
1043 StatusCode::BAD_REQUEST,
1044 "NotFoundException",
1045 format!("Key '{key_id}' does not exist"),
1046 )
1047 })?;
1048
1049 let accounts = self.state.read();
1050 let empty = KmsState::new(&req.account_id, &req.region);
1051 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1052 let key = state.keys.get(&resolved).ok_or_else(|| {
1053 AwsServiceError::aws_error(
1054 StatusCode::INTERNAL_SERVER_ERROR,
1055 "KMSInternalException",
1056 "Key state became inconsistent",
1057 )
1058 })?;
1059
1060 Ok(AwsResponse::json(
1061 StatusCode::OK,
1062 serde_json::to_string(&json!({
1063 "Policy": key.policy,
1064 }))
1065 .unwrap(),
1066 ))
1067 }
1068
1069 fn put_key_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1070 let body = req.json_body();
1071 let key_id = Self::require_key_id(&body)?;
1072
1073 if key_id.starts_with("alias/") {
1075 return Err(AwsServiceError::aws_error(
1076 StatusCode::BAD_REQUEST,
1077 "NotFoundException",
1078 format!("Invalid keyId {key_id}"),
1079 ));
1080 }
1081
1082 let resolved = self
1083 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1084 .ok_or_else(|| {
1085 AwsServiceError::aws_error(
1086 StatusCode::BAD_REQUEST,
1087 "NotFoundException",
1088 format!("Key '{key_id}' does not exist"),
1089 )
1090 })?;
1091
1092 let policy = body["Policy"].as_str().unwrap_or("").to_string();
1093
1094 let mut accounts = self.state.write();
1095 let state = accounts.get_or_create(&req.account_id);
1096 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1097 AwsServiceError::aws_error(
1098 StatusCode::INTERNAL_SERVER_ERROR,
1099 "KMSInternalException",
1100 "Key state became inconsistent",
1101 )
1102 })?;
1103 key.policy = policy;
1104
1105 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1106 }
1107
1108 fn list_key_policies(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1109 let body = req.json_body();
1110 let _resolved = self.resolve_required_key(req, &body)?;
1111
1112 Ok(AwsResponse::json(
1113 StatusCode::OK,
1114 serde_json::to_string(&json!({
1115 "PolicyNames": ["default"],
1116 "Truncated": false,
1117 }))
1118 .unwrap(),
1119 ))
1120 }
1121
1122 fn get_key_rotation_status(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1123 let body = req.json_body();
1124 let key_id = Self::require_key_id(&body)?;
1125
1126 let resolved = self
1128 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1129 .ok_or_else(|| {
1130 AwsServiceError::aws_error(
1131 StatusCode::BAD_REQUEST,
1132 "NotFoundException",
1133 format!("Key '{key_id}' does not exist"),
1134 )
1135 })?;
1136
1137 let accounts = self.state.read();
1138 let empty = KmsState::new(&req.account_id, &req.region);
1139 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1140 let key = state.keys.get(&resolved).ok_or_else(|| {
1141 AwsServiceError::aws_error(
1142 StatusCode::INTERNAL_SERVER_ERROR,
1143 "KMSInternalException",
1144 "Key state became inconsistent",
1145 )
1146 })?;
1147
1148 let mut result = json!({
1149 "KeyRotationEnabled": key.key_rotation_enabled,
1150 });
1151 if key.key_rotation_enabled {
1154 result["RotationPeriodInDays"] = json!(key.rotation_period_in_days.unwrap_or(365));
1155 }
1156
1157 Ok(AwsResponse::json(
1158 StatusCode::OK,
1159 serde_json::to_string(&result).unwrap(),
1160 ))
1161 }
1162
1163 fn enable_key_rotation(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1164 let body = req.json_body();
1165 let key_id = Self::require_key_id(&body)?;
1166
1167 let resolved = self
1171 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1172 .ok_or_else(|| {
1173 AwsServiceError::aws_error(
1174 StatusCode::BAD_REQUEST,
1175 "NotFoundException",
1176 format!("Key '{key_id}' does not exist"),
1177 )
1178 })?;
1179
1180 let mut accounts = self.state.write();
1181 let state = accounts.get_or_create(&req.account_id);
1182 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1183 AwsServiceError::aws_error(
1184 StatusCode::INTERNAL_SERVER_ERROR,
1185 "KMSInternalException",
1186 "Key state became inconsistent",
1187 )
1188 })?;
1189 if let Some(period) = body.get("RotationPeriodInDays").and_then(|v| v.as_i64()) {
1193 if !(90..=2560).contains(&period) {
1194 return Err(AwsServiceError::aws_error(
1195 StatusCode::BAD_REQUEST,
1196 "ValidationException",
1197 format!("RotationPeriodInDays must be between 90 and 2560, got {period}"),
1198 ));
1199 }
1200 key.rotation_period_in_days = Some(period as i32);
1201 }
1202 key.key_rotation_enabled = true;
1203
1204 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1205 }
1206
1207 fn disable_key_rotation(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1208 let body = req.json_body();
1209 let key_id = Self::require_key_id(&body)?;
1210
1211 let resolved = self
1213 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1214 .ok_or_else(|| {
1215 AwsServiceError::aws_error(
1216 StatusCode::BAD_REQUEST,
1217 "NotFoundException",
1218 format!("Key '{key_id}' does not exist"),
1219 )
1220 })?;
1221
1222 let mut accounts = self.state.write();
1223 let state = accounts.get_or_create(&req.account_id);
1224 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1225 AwsServiceError::aws_error(
1226 StatusCode::INTERNAL_SERVER_ERROR,
1227 "KMSInternalException",
1228 "Key state became inconsistent",
1229 )
1230 })?;
1231 key.key_rotation_enabled = false;
1232
1233 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1234 }
1235
1236 fn rotate_key_on_demand(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1237 let body = req.json_body();
1238 let resolved = self.resolve_required_key(req, &body)?;
1239
1240 let mut accounts = self.state.write();
1241 let state = accounts.get_or_create(&req.account_id);
1242 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1243 AwsServiceError::aws_error(
1244 StatusCode::INTERNAL_SERVER_ERROR,
1245 "KMSInternalException",
1246 "Key state became inconsistent",
1247 )
1248 })?;
1249
1250 let rotation = KeyRotation {
1251 key_id: key.key_id.clone(),
1252 rotation_date: Utc::now().timestamp() as f64,
1253 rotation_type: "ON_DEMAND".to_string(),
1254 };
1255 key.rotations.push(rotation);
1256
1257 Ok(AwsResponse::json(
1258 StatusCode::OK,
1259 serde_json::to_string(&json!({
1260 "KeyId": key.key_id,
1261 }))
1262 .unwrap(),
1263 ))
1264 }
1265
1266 fn list_key_rotations(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1267 let body = req.json_body();
1268 let resolved = self.resolve_required_key(req, &body)?;
1269 validate_optional_json_range("limit", &body["Limit"], 1, 1000)?;
1270 let limit = body["Limit"].as_i64().unwrap_or(1000) as usize;
1271 let marker = body["Marker"].as_str();
1272
1273 let accounts = self.state.read();
1274 let empty = KmsState::new(&req.account_id, &req.region);
1275 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1276 let key = state.keys.get(&resolved).ok_or_else(|| {
1277 AwsServiceError::aws_error(
1278 StatusCode::INTERNAL_SERVER_ERROR,
1279 "KMSInternalException",
1280 "Key state became inconsistent",
1281 )
1282 })?;
1283
1284 let start_index = if let Some(marker) = marker {
1285 marker.parse::<usize>().unwrap_or(0)
1286 } else {
1287 0
1288 };
1289
1290 let rotations: Vec<Value> = key
1291 .rotations
1292 .iter()
1293 .skip(start_index)
1294 .take(limit)
1295 .map(|r| {
1296 json!({
1297 "KeyId": r.key_id,
1298 "RotationDate": r.rotation_date,
1299 "RotationType": r.rotation_type,
1300 })
1301 })
1302 .collect();
1303
1304 let total_after_start = key.rotations.len().saturating_sub(start_index);
1305 let truncated = total_after_start > limit;
1306
1307 let mut response = json!({
1308 "Rotations": rotations,
1309 "Truncated": truncated,
1310 });
1311
1312 if truncated {
1313 response["NextMarker"] = json!((start_index + limit).to_string());
1314 }
1315
1316 Ok(AwsResponse::json(
1317 StatusCode::OK,
1318 serde_json::to_string(&response).unwrap(),
1319 ))
1320 }
1321
1322 fn replicate_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1323 let body = req.json_body();
1324 let key_id = Self::require_key_id(&body)?;
1325 let replica_region = body["ReplicaRegion"].as_str().unwrap_or("").to_string();
1326
1327 let resolved = self
1328 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1329 .ok_or_else(|| {
1330 AwsServiceError::aws_error(
1331 StatusCode::BAD_REQUEST,
1332 "NotFoundException",
1333 format!("Key '{key_id}' does not exist"),
1334 )
1335 })?;
1336
1337 let mut accounts = self.state.write();
1338 let state = accounts.get_or_create(&req.account_id);
1339
1340 let source_key = state
1343 .keys
1344 .get(&resolved)
1345 .ok_or_else(|| {
1346 AwsServiceError::aws_error(
1347 StatusCode::INTERNAL_SERVER_ERROR,
1348 "KMSInternalException",
1349 "Key state became inconsistent",
1350 )
1351 })?
1352 .clone();
1353 let account_id = state.account_id.clone();
1354 let source_region = state.region.clone();
1355
1356 let replica_arn = format!(
1357 "arn:aws:kms:{}:{}:key/{}",
1358 replica_region, account_id, source_key.key_id
1359 );
1360
1361 let metadata = json!({
1362 "KeyId": source_key.key_id,
1363 "Arn": replica_arn,
1364 "AWSAccountId": account_id,
1365 "CreationDate": source_key.creation_date,
1366 "Description": source_key.description,
1367 "Enabled": source_key.enabled,
1368 "KeyUsage": source_key.key_usage,
1369 "KeySpec": source_key.key_spec,
1370 "CustomerMasterKeySpec": source_key.key_spec,
1371 "KeyManager": source_key.key_manager,
1372 "KeyState": source_key.key_state,
1373 "Origin": source_key.origin,
1374 "MultiRegion": true,
1375 "MultiRegionConfiguration": {
1376 "MultiRegionKeyType": "REPLICA",
1377 "PrimaryKey": {
1378 "Arn": source_key.arn,
1379 "Region": source_region,
1380 },
1381 "ReplicaKeys": [],
1382 },
1383 });
1384
1385 let replica_storage_key = format!("{}:{}", replica_region, source_key.key_id);
1386 let source_policy = source_key.policy.clone();
1387 let replica_key = KmsKey {
1388 arn: replica_arn,
1389 deletion_date: None,
1390 key_rotation_enabled: false,
1391 rotation_period_in_days: None,
1392 multi_region: true,
1393 rotations: Vec::new(),
1394 custom_key_store_id: None,
1395 imported_key_material: false,
1396 imported_material_bytes: None,
1397 private_key_seed: rand_bytes(32),
1398 primary_region: None,
1399 ..source_key
1400 };
1401
1402 state.keys.insert(replica_storage_key, replica_key);
1403
1404 Ok(AwsResponse::json(
1405 StatusCode::OK,
1406 serde_json::to_string(&json!({
1407 "ReplicaKeyMetadata": metadata,
1408 "ReplicaPolicy": source_policy,
1409 }))
1410 .unwrap(),
1411 ))
1412 }
1413
1414 fn update_primary_region(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1415 let body = req.json_body();
1416 let key_id = Self::require_key_id(&body)?;
1417 let primary_region = body["PrimaryRegion"]
1418 .as_str()
1419 .ok_or_else(|| {
1420 AwsServiceError::aws_error(
1421 StatusCode::BAD_REQUEST,
1422 "ValidationException",
1423 "PrimaryRegion is required",
1424 )
1425 })?
1426 .to_string();
1427
1428 let resolved = self
1429 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1430 .ok_or_else(|| {
1431 AwsServiceError::aws_error(
1432 StatusCode::BAD_REQUEST,
1433 "NotFoundException",
1434 format!("Key '{key_id}' does not exist"),
1435 )
1436 })?;
1437
1438 let mut accounts = self.state.write();
1439 let state = accounts.get_or_create(&req.account_id);
1440 let account_id = state.account_id.clone();
1441 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1442 AwsServiceError::aws_error(
1443 StatusCode::BAD_REQUEST,
1444 "NotFoundException",
1445 format!("Key '{key_id}' does not exist"),
1446 )
1447 })?;
1448
1449 if !key.multi_region {
1450 return Err(AwsServiceError::aws_error(
1451 StatusCode::BAD_REQUEST,
1452 "UnsupportedOperationException",
1453 format!("Key '{}' is not a multi-Region key", key.arn),
1454 ));
1455 }
1456 key.primary_region = Some(primary_region.clone());
1457 key.arn = format!(
1459 "arn:aws:kms:{}:{}:key/{}",
1460 primary_region, account_id, key.key_id
1461 );
1462
1463 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1464 }
1465}
1466
1467#[path = "asym.rs"]
1468pub(crate) mod asym;
1469#[path = "asym_ecdsa.rs"]
1470pub(crate) mod asym_ecdsa;
1471#[path = "mac.rs"]
1472pub(crate) mod mac;
1473#[path = "service_aliases.rs"]
1474mod service_aliases;
1475#[path = "service_crypto.rs"]
1476mod service_crypto;
1477#[path = "service_custom_store.rs"]
1478mod service_custom_store;
1479#[path = "service_grants.rs"]
1480mod service_grants;
1481
1482#[path = "helpers.rs"]
1483mod helpers;
1484pub(crate) use helpers::*;
1485
1486#[path = "provisioner.rs"]
1487pub mod provisioner;
1488
1489#[cfg(test)]
1490#[path = "service_tests.rs"]
1491mod tests;