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