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 validate_optional_string_length(
318 "customKeyStoreId",
319 body["CustomKeyStoreId"].as_str(),
320 1,
321 64,
322 )?;
323 validate_optional_string_length("description", body["Description"].as_str(), 0, 8192)?;
324 validate_optional_enum(
325 "keyUsage",
326 body["KeyUsage"].as_str(),
327 &[
328 "SIGN_VERIFY",
329 "ENCRYPT_DECRYPT",
330 "GENERATE_VERIFY_MAC",
331 "KEY_AGREEMENT",
332 ],
333 )?;
334 validate_optional_enum(
335 "origin",
336 body["Origin"].as_str(),
337 &["AWS_KMS", "EXTERNAL", "AWS_CLOUDHSM", "EXTERNAL_KEY_STORE"],
338 )?;
339 validate_optional_string_length("policy", body["Policy"].as_str(), 1, 131072)?;
340 validate_optional_string_length("xksKeyId", body["XksKeyId"].as_str(), 1, 64)?;
341
342 let key_spec = body["KeySpec"]
343 .as_str()
344 .or_else(|| body["CustomerMasterKeySpec"].as_str())
345 .unwrap_or("SYMMETRIC_DEFAULT")
346 .to_string();
347 if !VALID_KEY_SPECS.contains(&key_spec.as_str()) {
348 return Err(AwsServiceError::aws_error(
349 StatusCode::BAD_REQUEST,
350 "ValidationException",
351 format!(
352 "1 validation error detected: Value '{key_spec}' at 'KeySpec' failed to satisfy constraint: Member must satisfy enum value set: {}",
353 fmt_enum_set(&VALID_KEY_SPECS.iter().map(|s| s.to_string()).collect::<Vec<_>>())
354 ),
355 ));
356 }
357
358 let tags: BTreeMap<String, String> = body["Tags"]
359 .as_array()
360 .map(|arr| {
361 arr.iter()
362 .filter_map(|t| {
363 let k = t["TagKey"].as_str()?;
364 let v = t["TagValue"].as_str()?;
365 Some((k.to_string(), v.to_string()))
366 })
367 .collect()
368 })
369 .unwrap_or_default();
370
371 Ok(Self {
372 custom_key_store_id: body["CustomKeyStoreId"].as_str().map(|s| s.to_string()),
373 description: body["Description"].as_str().unwrap_or("").to_string(),
374 key_usage: body["KeyUsage"]
375 .as_str()
376 .unwrap_or("ENCRYPT_DECRYPT")
377 .to_string(),
378 key_spec,
379 origin: body["Origin"].as_str().unwrap_or("AWS_KMS").to_string(),
380 multi_region: body["MultiRegion"].as_bool().unwrap_or(false),
381 policy: body["Policy"].as_str().map(|s| s.to_string()),
382 tags,
383 })
384 }
385}
386
387impl KmsService {
388 fn resolve_key_id_for(
389 &self,
390 account_id: &str,
391 region: &str,
392 key_id_or_arn: &str,
393 ) -> Option<String> {
394 let accounts = self.state.read();
395 let empty = KmsState::new(account_id, region);
396 let state = accounts.get(account_id).unwrap_or(&empty);
397 Self::resolve_key_id_with_state(state, key_id_or_arn)
398 }
399
400 pub(crate) fn resolve_key_id_with_state(
401 state: &crate::state::KmsState,
402 key_id_or_arn: &str,
403 ) -> Option<String> {
404 if state.keys.contains_key(key_id_or_arn) {
406 return Some(key_id_or_arn.to_string());
407 }
408
409 if key_id_or_arn.starts_with("arn:aws:kms:") {
411 if key_id_or_arn.contains(":key/") {
413 if let Some(id) = key_id_or_arn.rsplit('/').next() {
414 if state.keys.contains_key(id) {
415 return Some(id.to_string());
416 }
417 }
418 }
419 if key_id_or_arn.contains(":alias/") {
421 if let Some(alias_part) = key_id_or_arn.split(':').next_back() {
422 if let Some(alias) = state.aliases.get(alias_part) {
423 return Some(alias.target_key_id.clone());
424 }
425 }
426 }
427 }
428
429 if key_id_or_arn.starts_with("alias/") {
431 if let Some(alias) = state.aliases.get(key_id_or_arn) {
432 return Some(alias.target_key_id.clone());
433 }
434 }
435
436 None
437 }
438
439 fn require_key_id(body: &Value) -> Result<String, AwsServiceError> {
440 body["KeyId"]
441 .as_str()
442 .map(|s| s.to_string())
443 .ok_or_else(|| {
444 AwsServiceError::aws_error(
445 StatusCode::BAD_REQUEST,
446 "ValidationException",
447 "KeyId is required",
448 )
449 })
450 }
451
452 fn resolve_required_key(
453 &self,
454 req: &AwsRequest,
455 body: &Value,
456 ) -> Result<String, AwsServiceError> {
457 let key_id_input = Self::require_key_id(body)?;
458 self.resolve_key_id_for(&req.account_id, &req.region, &key_id_input)
459 .ok_or_else(|| {
460 AwsServiceError::aws_error(
461 StatusCode::BAD_REQUEST,
462 "NotFoundException",
463 format!("Key '{key_id_input}' does not exist"),
464 )
465 })
466 }
467
468 fn create_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
469 let input = CreateKeyInput::from_body(&req.json_body())?;
470
471 let mut accounts = self.state.write();
472 let state = accounts.get_or_create(&req.account_id);
473
474 let key_id = if input.multi_region {
475 format!("mrk-{}", Uuid::new_v4().as_simple())
476 } else {
477 Uuid::new_v4().to_string()
478 };
479
480 let arn = format!(
481 "arn:aws:kms:{}:{}:key/{}",
482 state.region, state.account_id, key_id
483 );
484 let now = Utc::now().timestamp() as f64;
485
486 let signing_algs = if input.key_usage == "SIGN_VERIFY" {
487 signing_algorithms_for_key_spec(&input.key_spec)
488 } else {
489 None
490 };
491 let encryption_algs = encryption_algorithms_for_key(&input.key_usage, &input.key_spec);
492 let mac_algs = if input.key_usage == "GENERATE_VERIFY_MAC" {
493 mac_algorithms_for_key_spec(&input.key_spec)
494 } else {
495 None
496 };
497
498 let key_policy = input
499 .policy
500 .unwrap_or_else(|| default_key_policy(&state.account_id));
501
502 let mut asym_priv: Option<Vec<u8>> = None;
503 let mut asym_pub: Option<Vec<u8>> = None;
504 if let Some((p, k)) = asym::generate_keypair(&input.key_spec).map_err(|e| {
505 AwsServiceError::aws_error(
506 StatusCode::INTERNAL_SERVER_ERROR,
507 "KMSInternalException",
508 format!("failed to generate asymmetric key: {e}"),
509 )
510 })? {
511 asym_priv = Some(p);
512 asym_pub = Some(k);
513 } else if let Some((p, k)) = asym_ecdsa::generate_keypair(&input.key_spec).map_err(|e| {
514 AwsServiceError::aws_error(
515 StatusCode::INTERNAL_SERVER_ERROR,
516 "KMSInternalException",
517 format!("failed to generate ecdsa key: {e}"),
518 )
519 })? {
520 asym_priv = Some(p);
521 asym_pub = Some(k);
522 }
523
524 let is_asymmetric = input.key_spec.starts_with("ECC_")
529 || input.key_spec.starts_with("RSA_")
530 || input.key_spec == "SM2";
531 if is_asymmetric && asym_priv.is_none() {
532 return Err(AwsServiceError::aws_error(
533 StatusCode::BAD_REQUEST,
534 "UnsupportedOperationException",
535 format!(
536 "KeySpec '{}' is not supported by this fakecloud build; \
537 no fake-signature fallback is provided",
538 input.key_spec
539 ),
540 ));
541 }
542
543 let key = KmsKey {
544 key_id: key_id.clone(),
545 arn: arn.clone(),
546 creation_date: now,
547 description: input.description,
548 enabled: true,
549 key_usage: input.key_usage,
550 key_spec: input.key_spec,
551 key_manager: "CUSTOMER".to_string(),
552 key_state: "Enabled".to_string(),
553 deletion_date: None,
554 tags: input.tags,
555 policy: key_policy,
556 key_rotation_enabled: false,
557 origin: input.origin,
558 multi_region: input.multi_region,
559 rotations: Vec::new(),
560 signing_algorithms: signing_algs,
561 encryption_algorithms: encryption_algs,
562 mac_algorithms: mac_algs,
563 custom_key_store_id: input.custom_key_store_id,
564 imported_key_material: false,
565 imported_material_bytes: None,
566 private_key_seed: rand_bytes(32),
567 primary_region: None,
568 asymmetric_private_key_der: asym_priv,
569 asymmetric_public_key_der: asym_pub,
570 };
571
572 let metadata = key_metadata_json(&key, &state.account_id);
573 state.keys.insert(key_id, key);
574
575 Ok(AwsResponse::json(
576 StatusCode::OK,
577 serde_json::to_string(&json!({ "KeyMetadata": metadata })).unwrap(),
578 ))
579 }
580
581 fn describe_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
582 let body = req.json_body();
583 let key_id_input = body["KeyId"].as_str().ok_or_else(|| {
584 AwsServiceError::aws_error(
585 StatusCode::BAD_REQUEST,
586 "ValidationException",
587 "KeyId is required",
588 )
589 })?;
590
591 let accounts = self.state.read();
592 let empty = KmsState::new(&req.account_id, &req.region);
593 let state = accounts.get(&req.account_id).unwrap_or(&empty);
594
595 let resolved = Self::resolve_key_id_with_state(state, key_id_input).ok_or_else(|| {
597 AwsServiceError::aws_error(
598 StatusCode::BAD_REQUEST,
599 "NotFoundException",
600 format!("Key '{key_id_input}' does not exist"),
601 )
602 })?;
603
604 let key = state.keys.get(&resolved).ok_or_else(|| {
605 AwsServiceError::aws_error(
606 StatusCode::BAD_REQUEST,
607 "NotFoundException",
608 format!("Key '{key_id_input}' does not exist"),
609 )
610 })?;
611
612 check_policy_deny(key, "kms:DescribeKey")?;
614
615 let metadata = key_metadata_json(key, &state.account_id);
616 Ok(AwsResponse::json(
617 StatusCode::OK,
618 serde_json::to_string(&json!({ "KeyMetadata": metadata })).unwrap(),
619 ))
620 }
621
622 fn list_keys(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
623 let body = req.json_body();
624
625 validate_optional_json_range("limit", &body["Limit"], 1, 1000)?;
626 validate_optional_string_length("marker", body["Marker"].as_str(), 1, 320)?;
627
628 let limit = body["Limit"].as_i64().unwrap_or(1000) as usize;
629 let marker = body["Marker"].as_str();
630
631 let accounts = self.state.read();
632 let empty = KmsState::new(&req.account_id, &req.region);
633 let state = accounts.get(&req.account_id).unwrap_or(&empty);
634 let all_keys: Vec<Value> = state
635 .keys
636 .values()
637 .map(|k| {
638 json!({
639 "KeyId": k.key_id,
640 "KeyArn": k.arn,
641 })
642 })
643 .collect();
644
645 let start = if let Some(m) = marker {
646 all_keys
647 .iter()
648 .position(|k| k["KeyId"].as_str() == Some(m))
649 .map(|pos| pos + 1)
650 .unwrap_or(0)
651 } else {
652 0
653 };
654
655 let page = &all_keys[start..all_keys.len().min(start + limit)];
656 let truncated = start + limit < all_keys.len();
657
658 let mut result = json!({
659 "Keys": page,
660 "Truncated": truncated,
661 });
662
663 if truncated {
664 if let Some(last) = page.last() {
665 result["NextMarker"] = last["KeyId"].clone();
666 }
667 }
668
669 Ok(AwsResponse::json(
670 StatusCode::OK,
671 serde_json::to_string(&result).unwrap(),
672 ))
673 }
674
675 fn enable_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
676 let body = req.json_body();
677 let resolved = self.resolve_required_key(req, &body)?;
678
679 let mut accounts = self.state.write();
680 let state = accounts.get_or_create(&req.account_id);
681 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
682 AwsServiceError::aws_error(
683 StatusCode::INTERNAL_SERVER_ERROR,
684 "KMSInternalException",
685 "Key state became inconsistent",
686 )
687 })?;
688 key.enabled = true;
689 key.key_state = "Enabled".to_string();
690
691 Ok(AwsResponse::json(StatusCode::OK, "{}"))
692 }
693
694 fn disable_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
695 let body = req.json_body();
696 let resolved = self.resolve_required_key(req, &body)?;
697
698 let mut accounts = self.state.write();
699 let state = accounts.get_or_create(&req.account_id);
700 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
701 AwsServiceError::aws_error(
702 StatusCode::INTERNAL_SERVER_ERROR,
703 "KMSInternalException",
704 "Key state became inconsistent",
705 )
706 })?;
707 key.enabled = false;
708 key.key_state = "Disabled".to_string();
709
710 Ok(AwsResponse::json(StatusCode::OK, "{}"))
711 }
712
713 fn schedule_key_deletion(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
714 let body = req.json_body();
715 let resolved = self.resolve_required_key(req, &body)?;
716 let pending_days = body["PendingWindowInDays"].as_i64().unwrap_or(30);
717
718 let mut accounts = self.state.write();
719 let state = accounts.get_or_create(&req.account_id);
720 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
721 AwsServiceError::aws_error(
722 StatusCode::INTERNAL_SERVER_ERROR,
723 "KMSInternalException",
724 "Key state became inconsistent",
725 )
726 })?;
727 let deletion_date =
728 Utc::now().timestamp() as f64 + (pending_days as f64 * 24.0 * 60.0 * 60.0);
729 key.key_state = "PendingDeletion".to_string();
730 key.enabled = false;
731 key.deletion_date = Some(deletion_date);
732
733 Ok(AwsResponse::json(
734 StatusCode::OK,
735 serde_json::to_string(&json!({
736 "KeyId": key.key_id,
737 "DeletionDate": deletion_date,
738 "KeyState": "PendingDeletion",
739 "PendingWindowInDays": pending_days,
740 }))
741 .unwrap(),
742 ))
743 }
744
745 fn cancel_key_deletion(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
746 let body = req.json_body();
747 let resolved = self.resolve_required_key(req, &body)?;
748
749 let mut accounts = self.state.write();
750 let state = accounts.get_or_create(&req.account_id);
751 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
752 AwsServiceError::aws_error(
753 StatusCode::INTERNAL_SERVER_ERROR,
754 "KMSInternalException",
755 "Key state became inconsistent",
756 )
757 })?;
758 key.key_state = "Disabled".to_string();
759 key.deletion_date = None;
760
761 Ok(AwsResponse::json(
762 StatusCode::OK,
763 serde_json::to_string(&json!({
764 "KeyId": key.key_id,
765 }))
766 .unwrap(),
767 ))
768 }
769
770 fn tag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
771 let body = req.json_body();
772 let key_id = Self::require_key_id(&body)?;
773
774 let resolved = self
775 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
776 .ok_or_else(|| {
777 AwsServiceError::aws_error(
778 StatusCode::BAD_REQUEST,
779 "NotFoundException",
780 format!("Invalid keyId {key_id}"),
781 )
782 })?;
783
784 let mut accounts = self.state.write();
785 let state = accounts.get_or_create(&req.account_id);
786 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
787 AwsServiceError::aws_error(
788 StatusCode::INTERNAL_SERVER_ERROR,
789 "KMSInternalException",
790 "Key state became inconsistent",
791 )
792 })?;
793
794 fakecloud_core::tags::apply_tags(&mut key.tags, &body, "Tags", "TagKey", "TagValue")
795 .map_err(|f| {
796 AwsServiceError::aws_error(
797 StatusCode::BAD_REQUEST,
798 "ValidationException",
799 format!("{f} must be a list"),
800 )
801 })?;
802
803 Ok(AwsResponse::json(StatusCode::OK, "{}"))
804 }
805
806 fn untag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
807 let body = req.json_body();
808 let key_id = Self::require_key_id(&body)?;
809
810 let resolved = self
811 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
812 .ok_or_else(|| {
813 AwsServiceError::aws_error(
814 StatusCode::BAD_REQUEST,
815 "NotFoundException",
816 format!("Invalid keyId {key_id}"),
817 )
818 })?;
819
820 let mut accounts = self.state.write();
821 let state = accounts.get_or_create(&req.account_id);
822 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
823 AwsServiceError::aws_error(
824 StatusCode::INTERNAL_SERVER_ERROR,
825 "KMSInternalException",
826 "Key state became inconsistent",
827 )
828 })?;
829
830 fakecloud_core::tags::remove_tags(&mut key.tags, &body, "TagKeys").map_err(|f| {
831 AwsServiceError::aws_error(
832 StatusCode::BAD_REQUEST,
833 "ValidationException",
834 format!("{f} must be a list"),
835 )
836 })?;
837
838 Ok(AwsResponse::json(StatusCode::OK, "{}"))
839 }
840
841 fn list_resource_tags(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
842 let body = req.json_body();
843 let key_id = Self::require_key_id(&body)?;
844
845 let resolved = self
846 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
847 .ok_or_else(|| {
848 AwsServiceError::aws_error(
849 StatusCode::BAD_REQUEST,
850 "NotFoundException",
851 format!("Invalid keyId {key_id}"),
852 )
853 })?;
854
855 let accounts = self.state.read();
856 let empty = KmsState::new(&req.account_id, &req.region);
857 let state = accounts.get(&req.account_id).unwrap_or(&empty);
858 let key = state.keys.get(&resolved).ok_or_else(|| {
859 AwsServiceError::aws_error(
860 StatusCode::INTERNAL_SERVER_ERROR,
861 "KMSInternalException",
862 "Key state became inconsistent",
863 )
864 })?;
865 let tags = fakecloud_core::tags::tags_to_json(&key.tags, "TagKey", "TagValue");
866
867 Ok(AwsResponse::json(
868 StatusCode::OK,
869 serde_json::to_string(&json!({
870 "Tags": tags,
871 "Truncated": false,
872 }))
873 .unwrap(),
874 ))
875 }
876
877 fn update_key_description(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
878 let body = req.json_body();
879 let resolved = self.resolve_required_key(req, &body)?;
880 let description = body["Description"].as_str().unwrap_or("").to_string();
881
882 let mut accounts = self.state.write();
883 let state = accounts.get_or_create(&req.account_id);
884 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
885 AwsServiceError::aws_error(
886 StatusCode::INTERNAL_SERVER_ERROR,
887 "KMSInternalException",
888 "Key state became inconsistent",
889 )
890 })?;
891 key.description = description;
892
893 Ok(AwsResponse::json(StatusCode::OK, "{}"))
894 }
895
896 fn get_key_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
897 let body = req.json_body();
898 let key_id = Self::require_key_id(&body)?;
899
900 if key_id.starts_with("alias/") {
902 return Err(AwsServiceError::aws_error(
903 StatusCode::BAD_REQUEST,
904 "NotFoundException",
905 format!("Invalid keyId {key_id}"),
906 ));
907 }
908
909 let resolved = self
910 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
911 .ok_or_else(|| {
912 AwsServiceError::aws_error(
913 StatusCode::BAD_REQUEST,
914 "NotFoundException",
915 format!("Key '{key_id}' does not exist"),
916 )
917 })?;
918
919 let accounts = self.state.read();
920 let empty = KmsState::new(&req.account_id, &req.region);
921 let state = accounts.get(&req.account_id).unwrap_or(&empty);
922 let key = state.keys.get(&resolved).ok_or_else(|| {
923 AwsServiceError::aws_error(
924 StatusCode::INTERNAL_SERVER_ERROR,
925 "KMSInternalException",
926 "Key state became inconsistent",
927 )
928 })?;
929
930 Ok(AwsResponse::json(
931 StatusCode::OK,
932 serde_json::to_string(&json!({
933 "Policy": key.policy,
934 }))
935 .unwrap(),
936 ))
937 }
938
939 fn put_key_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
940 let body = req.json_body();
941 let key_id = Self::require_key_id(&body)?;
942
943 if key_id.starts_with("alias/") {
945 return Err(AwsServiceError::aws_error(
946 StatusCode::BAD_REQUEST,
947 "NotFoundException",
948 format!("Invalid keyId {key_id}"),
949 ));
950 }
951
952 let resolved = self
953 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
954 .ok_or_else(|| {
955 AwsServiceError::aws_error(
956 StatusCode::BAD_REQUEST,
957 "NotFoundException",
958 format!("Key '{key_id}' does not exist"),
959 )
960 })?;
961
962 let policy = body["Policy"].as_str().unwrap_or("").to_string();
963
964 let mut accounts = self.state.write();
965 let state = accounts.get_or_create(&req.account_id);
966 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
967 AwsServiceError::aws_error(
968 StatusCode::INTERNAL_SERVER_ERROR,
969 "KMSInternalException",
970 "Key state became inconsistent",
971 )
972 })?;
973 key.policy = policy;
974
975 Ok(AwsResponse::json(StatusCode::OK, "{}"))
976 }
977
978 fn list_key_policies(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
979 let body = req.json_body();
980 let _resolved = self.resolve_required_key(req, &body)?;
981
982 Ok(AwsResponse::json(
983 StatusCode::OK,
984 serde_json::to_string(&json!({
985 "PolicyNames": ["default"],
986 "Truncated": false,
987 }))
988 .unwrap(),
989 ))
990 }
991
992 fn get_key_rotation_status(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
993 let body = req.json_body();
994 let key_id = Self::require_key_id(&body)?;
995
996 let resolved = self
998 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
999 .ok_or_else(|| {
1000 AwsServiceError::aws_error(
1001 StatusCode::BAD_REQUEST,
1002 "NotFoundException",
1003 format!("Key '{key_id}' does not exist"),
1004 )
1005 })?;
1006
1007 let accounts = self.state.read();
1008 let empty = KmsState::new(&req.account_id, &req.region);
1009 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1010 let key = state.keys.get(&resolved).ok_or_else(|| {
1011 AwsServiceError::aws_error(
1012 StatusCode::INTERNAL_SERVER_ERROR,
1013 "KMSInternalException",
1014 "Key state became inconsistent",
1015 )
1016 })?;
1017
1018 Ok(AwsResponse::json(
1019 StatusCode::OK,
1020 serde_json::to_string(&json!({
1021 "KeyRotationEnabled": key.key_rotation_enabled,
1022 }))
1023 .unwrap(),
1024 ))
1025 }
1026
1027 fn enable_key_rotation(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1028 let body = req.json_body();
1029 let key_id = Self::require_key_id(&body)?;
1030
1031 let resolved = self
1035 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1036 .ok_or_else(|| {
1037 AwsServiceError::aws_error(
1038 StatusCode::BAD_REQUEST,
1039 "NotFoundException",
1040 format!("Key '{key_id}' does not exist"),
1041 )
1042 })?;
1043
1044 let mut accounts = self.state.write();
1045 let state = accounts.get_or_create(&req.account_id);
1046 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1047 AwsServiceError::aws_error(
1048 StatusCode::INTERNAL_SERVER_ERROR,
1049 "KMSInternalException",
1050 "Key state became inconsistent",
1051 )
1052 })?;
1053 key.key_rotation_enabled = true;
1054
1055 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1056 }
1057
1058 fn disable_key_rotation(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1059 let body = req.json_body();
1060 let key_id = Self::require_key_id(&body)?;
1061
1062 let resolved = self
1064 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1065 .ok_or_else(|| {
1066 AwsServiceError::aws_error(
1067 StatusCode::BAD_REQUEST,
1068 "NotFoundException",
1069 format!("Key '{key_id}' does not exist"),
1070 )
1071 })?;
1072
1073 let mut accounts = self.state.write();
1074 let state = accounts.get_or_create(&req.account_id);
1075 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1076 AwsServiceError::aws_error(
1077 StatusCode::INTERNAL_SERVER_ERROR,
1078 "KMSInternalException",
1079 "Key state became inconsistent",
1080 )
1081 })?;
1082 key.key_rotation_enabled = false;
1083
1084 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1085 }
1086
1087 fn rotate_key_on_demand(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1088 let body = req.json_body();
1089 let resolved = self.resolve_required_key(req, &body)?;
1090
1091 let mut accounts = self.state.write();
1092 let state = accounts.get_or_create(&req.account_id);
1093 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1094 AwsServiceError::aws_error(
1095 StatusCode::INTERNAL_SERVER_ERROR,
1096 "KMSInternalException",
1097 "Key state became inconsistent",
1098 )
1099 })?;
1100
1101 let rotation = KeyRotation {
1102 key_id: key.key_id.clone(),
1103 rotation_date: Utc::now().timestamp() as f64,
1104 rotation_type: "ON_DEMAND".to_string(),
1105 };
1106 key.rotations.push(rotation);
1107
1108 Ok(AwsResponse::json(
1109 StatusCode::OK,
1110 serde_json::to_string(&json!({
1111 "KeyId": key.key_id,
1112 }))
1113 .unwrap(),
1114 ))
1115 }
1116
1117 fn list_key_rotations(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1118 let body = req.json_body();
1119 let resolved = self.resolve_required_key(req, &body)?;
1120 validate_optional_json_range("limit", &body["Limit"], 1, 1000)?;
1121 let limit = body["Limit"].as_i64().unwrap_or(1000) as usize;
1122 let marker = body["Marker"].as_str();
1123
1124 let accounts = self.state.read();
1125 let empty = KmsState::new(&req.account_id, &req.region);
1126 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1127 let key = state.keys.get(&resolved).ok_or_else(|| {
1128 AwsServiceError::aws_error(
1129 StatusCode::INTERNAL_SERVER_ERROR,
1130 "KMSInternalException",
1131 "Key state became inconsistent",
1132 )
1133 })?;
1134
1135 let start_index = if let Some(marker) = marker {
1136 marker.parse::<usize>().unwrap_or(0)
1137 } else {
1138 0
1139 };
1140
1141 let rotations: Vec<Value> = key
1142 .rotations
1143 .iter()
1144 .skip(start_index)
1145 .take(limit)
1146 .map(|r| {
1147 json!({
1148 "KeyId": r.key_id,
1149 "RotationDate": r.rotation_date,
1150 "RotationType": r.rotation_type,
1151 })
1152 })
1153 .collect();
1154
1155 let total_after_start = key.rotations.len().saturating_sub(start_index);
1156 let truncated = total_after_start > limit;
1157
1158 let mut response = json!({
1159 "Rotations": rotations,
1160 "Truncated": truncated,
1161 });
1162
1163 if truncated {
1164 response["NextMarker"] = json!((start_index + limit).to_string());
1165 }
1166
1167 Ok(AwsResponse::json(
1168 StatusCode::OK,
1169 serde_json::to_string(&response).unwrap(),
1170 ))
1171 }
1172
1173 fn replicate_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1174 let body = req.json_body();
1175 let key_id = Self::require_key_id(&body)?;
1176 let replica_region = body["ReplicaRegion"].as_str().unwrap_or("").to_string();
1177
1178 let resolved = self
1179 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1180 .ok_or_else(|| {
1181 AwsServiceError::aws_error(
1182 StatusCode::BAD_REQUEST,
1183 "NotFoundException",
1184 format!("Key '{key_id}' does not exist"),
1185 )
1186 })?;
1187
1188 let mut accounts = self.state.write();
1189 let state = accounts.get_or_create(&req.account_id);
1190
1191 let source_key = state
1194 .keys
1195 .get(&resolved)
1196 .ok_or_else(|| {
1197 AwsServiceError::aws_error(
1198 StatusCode::INTERNAL_SERVER_ERROR,
1199 "KMSInternalException",
1200 "Key state became inconsistent",
1201 )
1202 })?
1203 .clone();
1204 let account_id = state.account_id.clone();
1205 let source_region = state.region.clone();
1206
1207 let replica_arn = format!(
1208 "arn:aws:kms:{}:{}:key/{}",
1209 replica_region, account_id, source_key.key_id
1210 );
1211
1212 let metadata = json!({
1213 "KeyId": source_key.key_id,
1214 "Arn": replica_arn,
1215 "AWSAccountId": account_id,
1216 "CreationDate": source_key.creation_date,
1217 "Description": source_key.description,
1218 "Enabled": source_key.enabled,
1219 "KeyUsage": source_key.key_usage,
1220 "KeySpec": source_key.key_spec,
1221 "CustomerMasterKeySpec": source_key.key_spec,
1222 "KeyManager": source_key.key_manager,
1223 "KeyState": source_key.key_state,
1224 "Origin": source_key.origin,
1225 "MultiRegion": true,
1226 "MultiRegionConfiguration": {
1227 "MultiRegionKeyType": "REPLICA",
1228 "PrimaryKey": {
1229 "Arn": source_key.arn,
1230 "Region": source_region,
1231 },
1232 "ReplicaKeys": [],
1233 },
1234 });
1235
1236 let replica_storage_key = format!("{}:{}", replica_region, source_key.key_id);
1237 let source_policy = source_key.policy.clone();
1238 let replica_key = KmsKey {
1239 arn: replica_arn,
1240 deletion_date: None,
1241 key_rotation_enabled: false,
1242 multi_region: true,
1243 rotations: Vec::new(),
1244 custom_key_store_id: None,
1245 imported_key_material: false,
1246 imported_material_bytes: None,
1247 private_key_seed: rand_bytes(32),
1248 primary_region: None,
1249 ..source_key
1250 };
1251
1252 state.keys.insert(replica_storage_key, replica_key);
1253
1254 Ok(AwsResponse::json(
1255 StatusCode::OK,
1256 serde_json::to_string(&json!({
1257 "ReplicaKeyMetadata": metadata,
1258 "ReplicaPolicy": source_policy,
1259 }))
1260 .unwrap(),
1261 ))
1262 }
1263
1264 fn update_primary_region(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1265 let body = req.json_body();
1266 let key_id = Self::require_key_id(&body)?;
1267 let primary_region = body["PrimaryRegion"]
1268 .as_str()
1269 .ok_or_else(|| {
1270 AwsServiceError::aws_error(
1271 StatusCode::BAD_REQUEST,
1272 "ValidationException",
1273 "PrimaryRegion is required",
1274 )
1275 })?
1276 .to_string();
1277
1278 let resolved = self
1279 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1280 .ok_or_else(|| {
1281 AwsServiceError::aws_error(
1282 StatusCode::BAD_REQUEST,
1283 "NotFoundException",
1284 format!("Key '{key_id}' does not exist"),
1285 )
1286 })?;
1287
1288 let mut accounts = self.state.write();
1289 let state = accounts.get_or_create(&req.account_id);
1290 let account_id = state.account_id.clone();
1291 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1292 AwsServiceError::aws_error(
1293 StatusCode::BAD_REQUEST,
1294 "NotFoundException",
1295 format!("Key '{key_id}' does not exist"),
1296 )
1297 })?;
1298
1299 if !key.multi_region {
1300 return Err(AwsServiceError::aws_error(
1301 StatusCode::BAD_REQUEST,
1302 "UnsupportedOperationException",
1303 format!("Key '{}' is not a multi-Region key", key.arn),
1304 ));
1305 }
1306 key.primary_region = Some(primary_region.clone());
1307 key.arn = format!(
1309 "arn:aws:kms:{}:{}:key/{}",
1310 primary_region, account_id, key.key_id
1311 );
1312
1313 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1314 }
1315}
1316
1317#[path = "asym.rs"]
1318pub(crate) mod asym;
1319#[path = "asym_ecdsa.rs"]
1320pub(crate) mod asym_ecdsa;
1321#[path = "mac.rs"]
1322pub(crate) mod mac;
1323#[path = "service_aliases.rs"]
1324mod service_aliases;
1325#[path = "service_crypto.rs"]
1326mod service_crypto;
1327#[path = "service_custom_store.rs"]
1328mod service_custom_store;
1329#[path = "service_grants.rs"]
1330mod service_grants;
1331
1332#[path = "helpers.rs"]
1333mod helpers;
1334pub(crate) use helpers::*;
1335
1336#[path = "provisioner.rs"]
1337pub mod provisioner;
1338
1339#[cfg(test)]
1340#[path = "service_tests.rs"]
1341mod tests;