1use std::collections::HashMap;
2
3use async_trait::async_trait;
4use base64::Engine;
5use chrono::Utc;
6use http::StatusCode;
7use serde_json::{json, Value};
8use uuid::Uuid;
9
10use fakecloud_aws::arn::Arn;
11use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
12use fakecloud_core::validation::*;
13
14use crate::state::{CustomKeyStore, KeyRotation, KmsAlias, KmsGrant, KmsKey, SharedKmsState};
15
16const FAKE_ENVELOPE_PREFIX: &str = "fakecloud-kms:";
17
18const VALID_KEY_SPECS: &[&str] = &[
19 "ECC_NIST_P256",
20 "ECC_NIST_P384",
21 "ECC_NIST_P521",
22 "ECC_SECG_P256K1",
23 "HMAC_224",
24 "HMAC_256",
25 "HMAC_384",
26 "HMAC_512",
27 "RSA_2048",
28 "RSA_3072",
29 "RSA_4096",
30 "SM2",
31 "SYMMETRIC_DEFAULT",
32];
33
34const VALID_SIGNING_ALGORITHMS: &[&str] = &[
35 "RSASSA_PKCS1_V1_5_SHA_256",
36 "RSASSA_PKCS1_V1_5_SHA_384",
37 "RSASSA_PKCS1_V1_5_SHA_512",
38 "RSASSA_PSS_SHA_256",
39 "RSASSA_PSS_SHA_384",
40 "RSASSA_PSS_SHA_512",
41 "ECDSA_SHA_256",
42 "ECDSA_SHA_384",
43 "ECDSA_SHA_512",
44];
45
46pub struct KmsService {
47 state: SharedKmsState,
48}
49
50impl KmsService {
51 pub fn new(state: SharedKmsState) -> Self {
52 Self { state }
53 }
54}
55
56#[async_trait]
57impl AwsService for KmsService {
58 fn service_name(&self) -> &str {
59 "kms"
60 }
61
62 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
63 match req.action.as_str() {
64 "CreateKey" => self.create_key(&req),
65 "DescribeKey" => self.describe_key(&req),
66 "ListKeys" => self.list_keys(&req),
67 "EnableKey" => self.enable_key(&req),
68 "DisableKey" => self.disable_key(&req),
69 "ScheduleKeyDeletion" => self.schedule_key_deletion(&req),
70 "CancelKeyDeletion" => self.cancel_key_deletion(&req),
71 "Encrypt" => self.encrypt(&req),
72 "Decrypt" => self.decrypt(&req),
73 "ReEncrypt" => self.re_encrypt(&req),
74 "GenerateDataKey" => self.generate_data_key(&req),
75 "GenerateDataKeyWithoutPlaintext" => self.generate_data_key_without_plaintext(&req),
76 "GenerateRandom" => self.generate_random(&req),
77 "CreateAlias" => self.create_alias(&req),
78 "DeleteAlias" => self.delete_alias(&req),
79 "UpdateAlias" => self.update_alias(&req),
80 "ListAliases" => self.list_aliases(&req),
81 "TagResource" => self.tag_resource(&req),
82 "UntagResource" => self.untag_resource(&req),
83 "ListResourceTags" => self.list_resource_tags(&req),
84 "UpdateKeyDescription" => self.update_key_description(&req),
85 "GetKeyPolicy" => self.get_key_policy(&req),
86 "PutKeyPolicy" => self.put_key_policy(&req),
87 "ListKeyPolicies" => self.list_key_policies(&req),
88 "GetKeyRotationStatus" => self.get_key_rotation_status(&req),
89 "EnableKeyRotation" => self.enable_key_rotation(&req),
90 "DisableKeyRotation" => self.disable_key_rotation(&req),
91 "RotateKeyOnDemand" => self.rotate_key_on_demand(&req),
92 "ListKeyRotations" => self.list_key_rotations(&req),
93 "Sign" => self.sign(&req),
94 "Verify" => self.verify(&req),
95 "GetPublicKey" => self.get_public_key(&req),
96 "CreateGrant" => self.create_grant(&req),
97 "ListGrants" => self.list_grants(&req),
98 "ListRetirableGrants" => self.list_retirable_grants(&req),
99 "RevokeGrant" => self.revoke_grant(&req),
100 "RetireGrant" => self.retire_grant(&req),
101 "GenerateMac" => self.generate_mac(&req),
102 "VerifyMac" => self.verify_mac(&req),
103 "ReplicateKey" => self.replicate_key(&req),
104 "GenerateDataKeyPair" => self.generate_data_key_pair(&req),
105 "GenerateDataKeyPairWithoutPlaintext" => {
106 self.generate_data_key_pair_without_plaintext(&req)
107 }
108 "DeriveSharedSecret" => self.derive_shared_secret(&req),
109 "GetParametersForImport" => self.get_parameters_for_import(&req),
110 "ImportKeyMaterial" => self.import_key_material(&req),
111 "DeleteImportedKeyMaterial" => self.delete_imported_key_material(&req),
112 "UpdatePrimaryRegion" => self.update_primary_region(&req),
113 "CreateCustomKeyStore" => self.create_custom_key_store(&req),
114 "DeleteCustomKeyStore" => self.delete_custom_key_store(&req),
115 "DescribeCustomKeyStores" => self.describe_custom_key_stores(&req),
116 "ConnectCustomKeyStore" => self.connect_custom_key_store(&req),
117 "DisconnectCustomKeyStore" => self.disconnect_custom_key_store(&req),
118 "UpdateCustomKeyStore" => self.update_custom_key_store(&req),
119 _ => Err(AwsServiceError::action_not_implemented("kms", &req.action)),
120 }
121 }
122
123 fn supported_actions(&self) -> &[&str] {
124 &[
125 "CreateKey",
126 "DescribeKey",
127 "ListKeys",
128 "EnableKey",
129 "DisableKey",
130 "ScheduleKeyDeletion",
131 "CancelKeyDeletion",
132 "Encrypt",
133 "Decrypt",
134 "ReEncrypt",
135 "GenerateDataKey",
136 "GenerateDataKeyWithoutPlaintext",
137 "GenerateRandom",
138 "CreateAlias",
139 "DeleteAlias",
140 "UpdateAlias",
141 "ListAliases",
142 "TagResource",
143 "UntagResource",
144 "ListResourceTags",
145 "UpdateKeyDescription",
146 "GetKeyPolicy",
147 "PutKeyPolicy",
148 "ListKeyPolicies",
149 "GetKeyRotationStatus",
150 "EnableKeyRotation",
151 "DisableKeyRotation",
152 "RotateKeyOnDemand",
153 "ListKeyRotations",
154 "Sign",
155 "Verify",
156 "GetPublicKey",
157 "CreateGrant",
158 "ListGrants",
159 "ListRetirableGrants",
160 "RevokeGrant",
161 "RetireGrant",
162 "GenerateMac",
163 "VerifyMac",
164 "ReplicateKey",
165 "GenerateDataKeyPair",
166 "GenerateDataKeyPairWithoutPlaintext",
167 "DeriveSharedSecret",
168 "GetParametersForImport",
169 "ImportKeyMaterial",
170 "DeleteImportedKeyMaterial",
171 "UpdatePrimaryRegion",
172 "CreateCustomKeyStore",
173 "DeleteCustomKeyStore",
174 "DescribeCustomKeyStores",
175 "ConnectCustomKeyStore",
176 "DisconnectCustomKeyStore",
177 "UpdateCustomKeyStore",
178 ]
179 }
180}
181
182fn default_key_policy(account_id: &str) -> String {
183 serde_json::to_string(&json!({
184 "Version": "2012-10-17",
185 "Id": "key-default-1",
186 "Statement": [
187 {
188 "Sid": "Enable IAM User Permissions",
189 "Effect": "Allow",
190 "Principal": {"AWS": Arn::global("iam", account_id, "root").to_string()},
191 "Action": "kms:*",
192 "Resource": "*",
193 }
194 ],
195 }))
196 .unwrap()
197}
198
199fn signing_algorithms_for_key_spec(key_spec: &str) -> Option<Vec<String>> {
200 match key_spec {
201 "RSA_2048" | "RSA_3072" | "RSA_4096" => Some(vec![
202 "RSASSA_PKCS1_V1_5_SHA_256".into(),
203 "RSASSA_PKCS1_V1_5_SHA_384".into(),
204 "RSASSA_PKCS1_V1_5_SHA_512".into(),
205 "RSASSA_PSS_SHA_256".into(),
206 "RSASSA_PSS_SHA_384".into(),
207 "RSASSA_PSS_SHA_512".into(),
208 ]),
209 "ECC_NIST_P256" | "ECC_SECG_P256K1" => Some(vec!["ECDSA_SHA_256".into()]),
210 "ECC_NIST_P384" => Some(vec!["ECDSA_SHA_384".into()]),
211 "ECC_NIST_P521" => Some(vec!["ECDSA_SHA_512".into()]),
212 _ => None,
213 }
214}
215
216fn encryption_algorithms_for_key(key_usage: &str, key_spec: &str) -> Option<Vec<String>> {
217 if key_usage == "ENCRYPT_DECRYPT" {
218 match key_spec {
219 "SYMMETRIC_DEFAULT" => Some(vec!["SYMMETRIC_DEFAULT".into()]),
220 "RSA_2048" | "RSA_3072" | "RSA_4096" => {
221 Some(vec!["RSAES_OAEP_SHA_1".into(), "RSAES_OAEP_SHA_256".into()])
222 }
223 _ => None,
224 }
225 } else {
226 None
227 }
228}
229
230fn mac_algorithms_for_key_spec(key_spec: &str) -> Option<Vec<String>> {
231 match key_spec {
232 "HMAC_224" => Some(vec!["HMAC_SHA_224".into()]),
233 "HMAC_256" => Some(vec!["HMAC_SHA_256".into()]),
234 "HMAC_384" => Some(vec!["HMAC_SHA_384".into()]),
235 "HMAC_512" => Some(vec!["HMAC_SHA_512".into()]),
236 _ => None,
237 }
238}
239
240fn rand_bytes(n: usize) -> Vec<u8> {
241 (0..n)
242 .map(|_| {
243 let u = Uuid::new_v4();
244 u.as_bytes()[0]
245 })
246 .collect()
247}
248
249impl KmsService {
250 fn resolve_key_id(&self, key_id_or_arn: &str) -> Option<String> {
251 let state = self.state.read();
252 Self::resolve_key_id_with_state(&state, key_id_or_arn)
253 }
254
255 fn resolve_key_id_with_state(
256 state: &crate::state::KmsState,
257 key_id_or_arn: &str,
258 ) -> Option<String> {
259 if state.keys.contains_key(key_id_or_arn) {
261 return Some(key_id_or_arn.to_string());
262 }
263
264 if key_id_or_arn.starts_with("arn:aws:kms:") {
266 if key_id_or_arn.contains(":key/") {
268 if let Some(id) = key_id_or_arn.rsplit('/').next() {
269 if state.keys.contains_key(id) {
270 return Some(id.to_string());
271 }
272 }
273 }
274 if key_id_or_arn.contains(":alias/") {
276 if let Some(alias_part) = key_id_or_arn.split(':').next_back() {
277 if let Some(alias) = state.aliases.get(alias_part) {
278 return Some(alias.target_key_id.clone());
279 }
280 }
281 }
282 }
283
284 if key_id_or_arn.starts_with("alias/") {
286 if let Some(alias) = state.aliases.get(key_id_or_arn) {
287 return Some(alias.target_key_id.clone());
288 }
289 }
290
291 None
292 }
293
294 fn require_key_id(body: &Value) -> Result<String, AwsServiceError> {
295 body["KeyId"]
296 .as_str()
297 .map(|s| s.to_string())
298 .ok_or_else(|| {
299 AwsServiceError::aws_error(
300 StatusCode::BAD_REQUEST,
301 "ValidationException",
302 "KeyId is required",
303 )
304 })
305 }
306
307 fn resolve_required_key(&self, body: &Value) -> Result<String, AwsServiceError> {
308 let key_id_input = Self::require_key_id(body)?;
309 self.resolve_key_id(&key_id_input).ok_or_else(|| {
310 AwsServiceError::aws_error(
311 StatusCode::BAD_REQUEST,
312 "NotFoundException",
313 format!("Key '{key_id_input}' does not exist"),
314 )
315 })
316 }
317
318 fn create_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
319 let body = req.json_body();
320
321 validate_optional_string_length(
322 "customKeyStoreId",
323 body["CustomKeyStoreId"].as_str(),
324 1,
325 64,
326 )?;
327
328 validate_optional_string_length("description", body["Description"].as_str(), 0, 8192)?;
329 validate_optional_enum(
330 "keyUsage",
331 body["KeyUsage"].as_str(),
332 &[
333 "SIGN_VERIFY",
334 "ENCRYPT_DECRYPT",
335 "GENERATE_VERIFY_MAC",
336 "KEY_AGREEMENT",
337 ],
338 )?;
339 validate_optional_enum(
340 "origin",
341 body["Origin"].as_str(),
342 &["AWS_KMS", "EXTERNAL", "AWS_CLOUDHSM", "EXTERNAL_KEY_STORE"],
343 )?;
344 validate_optional_string_length("policy", body["Policy"].as_str(), 1, 131072)?;
345 validate_optional_string_length("xksKeyId", body["XksKeyId"].as_str(), 1, 64)?;
346
347 let custom_key_store_id = body["CustomKeyStoreId"].as_str().map(|s| s.to_string());
348 let description = body["Description"].as_str().unwrap_or("").to_string();
349 let key_usage = body["KeyUsage"]
350 .as_str()
351 .unwrap_or("ENCRYPT_DECRYPT")
352 .to_string();
353 let key_spec = body["KeySpec"]
354 .as_str()
355 .or_else(|| body["CustomerMasterKeySpec"].as_str())
356 .unwrap_or("SYMMETRIC_DEFAULT")
357 .to_string();
358 let origin = body["Origin"].as_str().unwrap_or("AWS_KMS").to_string();
359 let multi_region = body["MultiRegion"].as_bool().unwrap_or(false);
360 let policy = body["Policy"].as_str().map(|s| s.to_string());
361
362 if !VALID_KEY_SPECS.contains(&key_spec.as_str()) {
364 return Err(AwsServiceError::aws_error(
365 StatusCode::BAD_REQUEST,
366 "ValidationException",
367 format!(
368 "1 validation error detected: Value '{}' at 'KeySpec' failed to satisfy constraint: Member must satisfy enum value set: {}",
369 key_spec, fmt_enum_set(&VALID_KEY_SPECS.iter().map(|s| s.to_string()).collect::<Vec<_>>())
370 ),
371 ));
372 }
373
374 let mut state = self.state.write();
375
376 let key_id = if multi_region {
377 format!("mrk-{}", Uuid::new_v4().as_simple())
378 } else {
379 Uuid::new_v4().to_string()
380 };
381
382 let arn = format!(
383 "arn:aws:kms:{}:{}:key/{}",
384 state.region, state.account_id, key_id
385 );
386 let now = Utc::now().timestamp() as f64;
387
388 let tags: HashMap<String, String> = body["Tags"]
389 .as_array()
390 .map(|arr| {
391 arr.iter()
392 .filter_map(|t| {
393 let k = t["TagKey"].as_str()?;
394 let v = t["TagValue"].as_str()?;
395 Some((k.to_string(), v.to_string()))
396 })
397 .collect()
398 })
399 .unwrap_or_default();
400
401 let signing_algs = if key_usage == "SIGN_VERIFY" {
402 signing_algorithms_for_key_spec(&key_spec)
403 } else {
404 None
405 };
406
407 let encryption_algs = encryption_algorithms_for_key(&key_usage, &key_spec);
408
409 let mac_algs = if key_usage == "GENERATE_VERIFY_MAC" {
410 mac_algorithms_for_key_spec(&key_spec)
411 } else {
412 None
413 };
414
415 let default_policy = default_key_policy(&state.account_id);
416 let key_policy = policy.unwrap_or(default_policy);
417
418 let key = KmsKey {
419 key_id: key_id.clone(),
420 arn: arn.clone(),
421 creation_date: now,
422 description,
423 enabled: true,
424 key_usage,
425 key_spec,
426 key_manager: "CUSTOMER".to_string(),
427 key_state: "Enabled".to_string(),
428 deletion_date: None,
429 tags,
430 policy: key_policy,
431 key_rotation_enabled: false,
432 origin,
433 multi_region,
434 rotations: Vec::new(),
435 signing_algorithms: signing_algs,
436 encryption_algorithms: encryption_algs,
437 mac_algorithms: mac_algs,
438 custom_key_store_id,
439 imported_key_material: false,
440 imported_material_bytes: None,
441 private_key_seed: rand_bytes(32),
442 primary_region: None,
443 };
444
445 let metadata = key_metadata_json(&key, &state.account_id);
446 state.keys.insert(key_id, key);
447
448 Ok(AwsResponse::json(
449 StatusCode::OK,
450 serde_json::to_string(&json!({ "KeyMetadata": metadata })).unwrap(),
451 ))
452 }
453
454 fn describe_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
455 let body = req.json_body();
456 let key_id_input = body["KeyId"].as_str().ok_or_else(|| {
457 AwsServiceError::aws_error(
458 StatusCode::BAD_REQUEST,
459 "ValidationException",
460 "KeyId is required",
461 )
462 })?;
463
464 let state = self.state.read();
465
466 let resolved = Self::resolve_key_id_with_state(&state, key_id_input).ok_or_else(|| {
468 AwsServiceError::aws_error(
469 StatusCode::BAD_REQUEST,
470 "NotFoundException",
471 format!("Key '{key_id_input}' does not exist"),
472 )
473 })?;
474
475 let key = state.keys.get(&resolved).ok_or_else(|| {
476 AwsServiceError::aws_error(
477 StatusCode::BAD_REQUEST,
478 "NotFoundException",
479 format!("Key '{key_id_input}' does not exist"),
480 )
481 })?;
482
483 check_policy_deny(key, "kms:DescribeKey")?;
485
486 let metadata = key_metadata_json(key, &state.account_id);
487 Ok(AwsResponse::json(
488 StatusCode::OK,
489 serde_json::to_string(&json!({ "KeyMetadata": metadata })).unwrap(),
490 ))
491 }
492
493 fn list_keys(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
494 let body = req.json_body();
495
496 validate_optional_json_range("limit", &body["Limit"], 1, 1000)?;
497 validate_optional_string_length("marker", body["Marker"].as_str(), 1, 320)?;
498
499 let limit = body["Limit"].as_i64().unwrap_or(1000) as usize;
500 let marker = body["Marker"].as_str();
501
502 let state = self.state.read();
503 let all_keys: Vec<Value> = state
504 .keys
505 .values()
506 .map(|k| {
507 json!({
508 "KeyId": k.key_id,
509 "KeyArn": k.arn,
510 })
511 })
512 .collect();
513
514 let start = if let Some(m) = marker {
515 all_keys
516 .iter()
517 .position(|k| k["KeyId"].as_str() == Some(m))
518 .map(|pos| pos + 1)
519 .unwrap_or(0)
520 } else {
521 0
522 };
523
524 let page = &all_keys[start..all_keys.len().min(start + limit)];
525 let truncated = start + limit < all_keys.len();
526
527 let mut result = json!({
528 "Keys": page,
529 "Truncated": truncated,
530 });
531
532 if truncated {
533 if let Some(last) = page.last() {
534 result["NextMarker"] = last["KeyId"].clone();
535 }
536 }
537
538 Ok(AwsResponse::json(
539 StatusCode::OK,
540 serde_json::to_string(&result).unwrap(),
541 ))
542 }
543
544 fn enable_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
545 let body = req.json_body();
546 let resolved = self.resolve_required_key(&body)?;
547
548 let mut state = self.state.write();
549 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
550 AwsServiceError::aws_error(
551 StatusCode::INTERNAL_SERVER_ERROR,
552 "KMSInternalException",
553 "Key state became inconsistent",
554 )
555 })?;
556 key.enabled = true;
557 key.key_state = "Enabled".to_string();
558
559 Ok(AwsResponse::json(StatusCode::OK, "{}"))
560 }
561
562 fn disable_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
563 let body = req.json_body();
564 let resolved = self.resolve_required_key(&body)?;
565
566 let mut state = self.state.write();
567 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
568 AwsServiceError::aws_error(
569 StatusCode::INTERNAL_SERVER_ERROR,
570 "KMSInternalException",
571 "Key state became inconsistent",
572 )
573 })?;
574 key.enabled = false;
575 key.key_state = "Disabled".to_string();
576
577 Ok(AwsResponse::json(StatusCode::OK, "{}"))
578 }
579
580 fn schedule_key_deletion(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
581 let body = req.json_body();
582 let resolved = self.resolve_required_key(&body)?;
583 let pending_days = body["PendingWindowInDays"].as_i64().unwrap_or(30);
584
585 let mut state = self.state.write();
586 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
587 AwsServiceError::aws_error(
588 StatusCode::INTERNAL_SERVER_ERROR,
589 "KMSInternalException",
590 "Key state became inconsistent",
591 )
592 })?;
593 let deletion_date =
594 Utc::now().timestamp() as f64 + (pending_days as f64 * 24.0 * 60.0 * 60.0);
595 key.key_state = "PendingDeletion".to_string();
596 key.enabled = false;
597 key.deletion_date = Some(deletion_date);
598
599 Ok(AwsResponse::json(
600 StatusCode::OK,
601 serde_json::to_string(&json!({
602 "KeyId": key.key_id,
603 "DeletionDate": deletion_date,
604 "KeyState": "PendingDeletion",
605 "PendingWindowInDays": pending_days,
606 }))
607 .unwrap(),
608 ))
609 }
610
611 fn cancel_key_deletion(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
612 let body = req.json_body();
613 let resolved = self.resolve_required_key(&body)?;
614
615 let mut state = self.state.write();
616 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
617 AwsServiceError::aws_error(
618 StatusCode::INTERNAL_SERVER_ERROR,
619 "KMSInternalException",
620 "Key state became inconsistent",
621 )
622 })?;
623 key.key_state = "Disabled".to_string();
624 key.deletion_date = None;
625
626 Ok(AwsResponse::json(
627 StatusCode::OK,
628 serde_json::to_string(&json!({
629 "KeyId": key.key_id,
630 }))
631 .unwrap(),
632 ))
633 }
634
635 fn encrypt(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
636 let body = req.json_body();
637 let key_id = Self::require_key_id(&body)?;
638 let plaintext_b64 = body["Plaintext"].as_str().ok_or_else(|| {
639 AwsServiceError::aws_error(
640 StatusCode::BAD_REQUEST,
641 "ValidationException",
642 "Plaintext is required",
643 )
644 })?;
645
646 let plaintext_bytes = base64::engine::general_purpose::STANDARD
648 .decode(plaintext_b64)
649 .unwrap_or_default();
650
651 if plaintext_bytes.is_empty() {
652 return Err(AwsServiceError::aws_error(
653 StatusCode::BAD_REQUEST,
654 "ValidationException",
655 "1 validation error detected: Value at 'plaintext' failed to satisfy constraint: Member must have length greater than or equal to 1",
656 ));
657 }
658
659 if plaintext_bytes.len() > 4096 {
660 return Err(AwsServiceError::aws_error(
661 StatusCode::BAD_REQUEST,
662 "ValidationException",
663 "1 validation error detected: Value at 'plaintext' failed to satisfy constraint: Member must have length less than or equal to 4096",
664 ));
665 }
666
667 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
668 AwsServiceError::aws_error(
669 StatusCode::BAD_REQUEST,
670 "NotFoundException",
671 format!("Key '{key_id}' does not exist"),
672 )
673 })?;
674
675 let state = self.state.read();
676 let key = state.keys.get(&resolved).ok_or_else(|| {
677 AwsServiceError::aws_error(
678 StatusCode::INTERNAL_SERVER_ERROR,
679 "KMSInternalException",
680 "Key state became inconsistent",
681 )
682 })?;
683 if !key.enabled {
684 return Err(AwsServiceError::aws_error(
685 StatusCode::BAD_REQUEST,
686 "DisabledException",
687 format!("Key '{}' is disabled", key.arn),
688 ));
689 }
690
691 let envelope = if let Some(ref material) = key.imported_material_bytes {
694 let xored: Vec<u8> = plaintext_bytes
695 .iter()
696 .enumerate()
697 .map(|(i, b)| b ^ material[i % material.len()])
698 .collect();
699 let xored_b64 = base64::engine::general_purpose::STANDARD.encode(&xored);
700 format!("fakecloud-imported:{}:{xored_b64}", key.key_id)
701 } else {
702 format!("{FAKE_ENVELOPE_PREFIX}{}:{plaintext_b64}", key.key_id)
704 };
705 let ciphertext_b64 = base64::engine::general_purpose::STANDARD.encode(envelope.as_bytes());
706
707 Ok(AwsResponse::json(
708 StatusCode::OK,
709 serde_json::to_string(&json!({
710 "CiphertextBlob": ciphertext_b64,
711 "KeyId": key.arn,
712 "EncryptionAlgorithm": "SYMMETRIC_DEFAULT",
713 }))
714 .unwrap(),
715 ))
716 }
717
718 fn decrypt(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
719 let body = req.json_body();
720 let ciphertext_b64 = body["CiphertextBlob"].as_str().ok_or_else(|| {
721 AwsServiceError::aws_error(
722 StatusCode::BAD_REQUEST,
723 "ValidationException",
724 "CiphertextBlob is required",
725 )
726 })?;
727
728 let ciphertext_bytes = base64::engine::general_purpose::STANDARD
729 .decode(ciphertext_b64)
730 .map_err(|_| {
731 AwsServiceError::aws_error(
732 StatusCode::BAD_REQUEST,
733 "InvalidCiphertextException",
734 "The ciphertext is invalid",
735 )
736 })?;
737
738 let envelope = String::from_utf8(ciphertext_bytes).map_err(|_| {
739 AwsServiceError::aws_error(
740 StatusCode::BAD_REQUEST,
741 "InvalidCiphertextException",
742 "The ciphertext is invalid",
743 )
744 })?;
745
746 const IMPORTED_PREFIX: &str = "fakecloud-imported:";
747
748 if let Some(rest) = envelope.strip_prefix(IMPORTED_PREFIX) {
749 let (key_id, xored_b64) = rest.split_once(':').ok_or_else(|| {
751 AwsServiceError::aws_error(
752 StatusCode::BAD_REQUEST,
753 "InvalidCiphertextException",
754 "The ciphertext is invalid",
755 )
756 })?;
757
758 let xored_bytes = base64::engine::general_purpose::STANDARD
759 .decode(xored_b64)
760 .map_err(|_| {
761 AwsServiceError::aws_error(
762 StatusCode::BAD_REQUEST,
763 "InvalidCiphertextException",
764 "The ciphertext is invalid",
765 )
766 })?;
767
768 let state = self.state.read();
769 let key = state.keys.get(key_id).ok_or_else(|| {
770 AwsServiceError::aws_error(
771 StatusCode::BAD_REQUEST,
772 "NotFoundException",
773 format!("Key '{key_id}' does not exist"),
774 )
775 })?;
776
777 let material = key.imported_material_bytes.as_ref().ok_or_else(|| {
778 AwsServiceError::aws_error(
779 StatusCode::BAD_REQUEST,
780 "InvalidCiphertextException",
781 "Key material has been deleted",
782 )
783 })?;
784
785 let plaintext_bytes: Vec<u8> = xored_bytes
786 .iter()
787 .enumerate()
788 .map(|(i, b)| b ^ material[i % material.len()])
789 .collect();
790 let plaintext_b64 = base64::engine::general_purpose::STANDARD.encode(&plaintext_bytes);
791
792 Ok(AwsResponse::json(
793 StatusCode::OK,
794 serde_json::to_string(&json!({
795 "Plaintext": plaintext_b64,
796 "KeyId": key.arn,
797 "EncryptionAlgorithm": "SYMMETRIC_DEFAULT",
798 }))
799 .unwrap(),
800 ))
801 } else if let Some(rest) = envelope.strip_prefix(FAKE_ENVELOPE_PREFIX) {
802 let (key_id, plaintext_b64) = rest.split_once(':').ok_or_else(|| {
803 AwsServiceError::aws_error(
804 StatusCode::BAD_REQUEST,
805 "InvalidCiphertextException",
806 "The ciphertext is invalid",
807 )
808 })?;
809
810 let state = self.state.read();
811 let key = state.keys.get(key_id).ok_or_else(|| {
812 AwsServiceError::aws_error(
813 StatusCode::BAD_REQUEST,
814 "NotFoundException",
815 format!("Key '{key_id}' does not exist"),
816 )
817 })?;
818
819 Ok(AwsResponse::json(
820 StatusCode::OK,
821 serde_json::to_string(&json!({
822 "Plaintext": plaintext_b64,
823 "KeyId": key.arn,
824 "EncryptionAlgorithm": "SYMMETRIC_DEFAULT",
825 }))
826 .unwrap(),
827 ))
828 } else {
829 Err(AwsServiceError::aws_error(
830 StatusCode::BAD_REQUEST,
831 "InvalidCiphertextException",
832 "The ciphertext is not a valid FakeCloud KMS ciphertext",
833 ))
834 }
835 }
836
837 fn re_encrypt(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
838 let body = req.json_body();
839 let ciphertext_b64 = body["CiphertextBlob"].as_str().ok_or_else(|| {
840 AwsServiceError::aws_error(
841 StatusCode::BAD_REQUEST,
842 "ValidationException",
843 "CiphertextBlob is required",
844 )
845 })?;
846 let dest_key_id = body["DestinationKeyId"].as_str().ok_or_else(|| {
847 AwsServiceError::aws_error(
848 StatusCode::BAD_REQUEST,
849 "ValidationException",
850 "DestinationKeyId is required",
851 )
852 })?;
853
854 let ciphertext_bytes = base64::engine::general_purpose::STANDARD
856 .decode(ciphertext_b64)
857 .map_err(|_| {
858 AwsServiceError::aws_error(
859 StatusCode::BAD_REQUEST,
860 "InvalidCiphertextException",
861 "The ciphertext is invalid",
862 )
863 })?;
864
865 let envelope = String::from_utf8(ciphertext_bytes).map_err(|_| {
866 AwsServiceError::aws_error(
867 StatusCode::BAD_REQUEST,
868 "InvalidCiphertextException",
869 "The ciphertext is invalid",
870 )
871 })?;
872
873 const IMPORTED_PREFIX: &str = "fakecloud-imported:";
874
875 let (source_key_id, plaintext_b64) =
877 if let Some(rest) = envelope.strip_prefix(IMPORTED_PREFIX) {
878 let (kid, xored_b64) = rest.split_once(':').ok_or_else(|| {
879 AwsServiceError::aws_error(
880 StatusCode::BAD_REQUEST,
881 "InvalidCiphertextException",
882 "The ciphertext is invalid",
883 )
884 })?;
885 let xored_bytes = base64::engine::general_purpose::STANDARD
886 .decode(xored_b64)
887 .map_err(|_| {
888 AwsServiceError::aws_error(
889 StatusCode::BAD_REQUEST,
890 "InvalidCiphertextException",
891 "The ciphertext is invalid",
892 )
893 })?;
894 let state = self.state.read();
895 let key = state.keys.get(kid).ok_or_else(|| {
896 AwsServiceError::aws_error(
897 StatusCode::BAD_REQUEST,
898 "NotFoundException",
899 format!("Key '{kid}' does not exist"),
900 )
901 })?;
902 let material = key.imported_material_bytes.as_ref().ok_or_else(|| {
903 AwsServiceError::aws_error(
904 StatusCode::BAD_REQUEST,
905 "InvalidCiphertextException",
906 "Key material has been deleted",
907 )
908 })?;
909 let plaintext_bytes: Vec<u8> = xored_bytes
910 .iter()
911 .enumerate()
912 .map(|(i, b)| b ^ material[i % material.len()])
913 .collect();
914 (
915 kid.to_string(),
916 base64::engine::general_purpose::STANDARD.encode(&plaintext_bytes),
917 )
918 } else if let Some(rest) = envelope.strip_prefix(FAKE_ENVELOPE_PREFIX) {
919 let (kid, pt) = rest.split_once(':').ok_or_else(|| {
920 AwsServiceError::aws_error(
921 StatusCode::BAD_REQUEST,
922 "InvalidCiphertextException",
923 "The ciphertext is invalid",
924 )
925 })?;
926 (kid.to_string(), pt.to_string())
927 } else {
928 return Err(AwsServiceError::aws_error(
929 StatusCode::BAD_REQUEST,
930 "InvalidCiphertextException",
931 "The ciphertext is invalid",
932 ));
933 };
934
935 let state = self.state.read();
936
937 let source_key = state.keys.get(source_key_id.as_str()).ok_or_else(|| {
938 AwsServiceError::aws_error(
939 StatusCode::BAD_REQUEST,
940 "NotFoundException",
941 format!("Key '{source_key_id}' does not exist"),
942 )
943 })?;
944 let source_arn = source_key.arn.clone();
945
946 let dest_resolved =
948 Self::resolve_key_id_with_state(&state, dest_key_id).ok_or_else(|| {
949 AwsServiceError::aws_error(
950 StatusCode::BAD_REQUEST,
951 "NotFoundException",
952 format!("Key '{dest_key_id}' does not exist"),
953 )
954 })?;
955
956 let dest_key = state.keys.get(&dest_resolved).ok_or_else(|| {
957 AwsServiceError::aws_error(
958 StatusCode::INTERNAL_SERVER_ERROR,
959 "KMSInternalException",
960 "Key state became inconsistent",
961 )
962 })?;
963
964 let new_envelope = if let Some(ref material) = dest_key.imported_material_bytes {
966 let plaintext_bytes = base64::engine::general_purpose::STANDARD
967 .decode(&plaintext_b64)
968 .unwrap_or_default();
969 let xored: Vec<u8> = plaintext_bytes
970 .iter()
971 .enumerate()
972 .map(|(i, b)| b ^ material[i % material.len()])
973 .collect();
974 let xored_b64 = base64::engine::general_purpose::STANDARD.encode(&xored);
975 format!("fakecloud-imported:{}:{xored_b64}", dest_key.key_id)
976 } else {
977 format!("{FAKE_ENVELOPE_PREFIX}{}:{plaintext_b64}", dest_key.key_id)
978 };
979 let new_ciphertext_b64 =
980 base64::engine::general_purpose::STANDARD.encode(new_envelope.as_bytes());
981
982 Ok(AwsResponse::json(
983 StatusCode::OK,
984 serde_json::to_string(&json!({
985 "CiphertextBlob": new_ciphertext_b64,
986 "KeyId": dest_key.arn,
987 "SourceKeyId": source_arn,
988 "SourceEncryptionAlgorithm": "SYMMETRIC_DEFAULT",
989 "DestinationEncryptionAlgorithm": "SYMMETRIC_DEFAULT",
990 }))
991 .unwrap(),
992 ))
993 }
994
995 fn generate_data_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
996 let body = req.json_body();
997 let key_id = Self::require_key_id(&body)?;
998
999 let resolved = self.resolve_key_id(&key_id).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 state = self.state.read();
1008 let key = state.keys.get(&resolved).ok_or_else(|| {
1009 AwsServiceError::aws_error(
1010 StatusCode::INTERNAL_SERVER_ERROR,
1011 "KMSInternalException",
1012 "Key state became inconsistent",
1013 )
1014 })?;
1015 if !key.enabled {
1016 return Err(AwsServiceError::aws_error(
1017 StatusCode::BAD_REQUEST,
1018 "DisabledException",
1019 format!("Key '{}' is disabled", key.arn),
1020 ));
1021 }
1022
1023 let num_bytes = data_key_size_from_body(&body)?;
1024
1025 let data_key_bytes: Vec<u8> = rand_bytes(num_bytes);
1026 let plaintext_b64 = base64::engine::general_purpose::STANDARD.encode(&data_key_bytes);
1027
1028 let envelope = format!("{FAKE_ENVELOPE_PREFIX}{}:{plaintext_b64}", key.key_id);
1030 let ciphertext_b64 = base64::engine::general_purpose::STANDARD.encode(envelope.as_bytes());
1031
1032 Ok(AwsResponse::json(
1033 StatusCode::OK,
1034 serde_json::to_string(&json!({
1035 "Plaintext": plaintext_b64,
1036 "CiphertextBlob": ciphertext_b64,
1037 "KeyId": key.arn,
1038 }))
1039 .unwrap(),
1040 ))
1041 }
1042
1043 fn generate_data_key_without_plaintext(
1044 &self,
1045 req: &AwsRequest,
1046 ) -> Result<AwsResponse, AwsServiceError> {
1047 let body = req.json_body();
1048 let key_id = Self::require_key_id(&body)?;
1049
1050 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
1051 AwsServiceError::aws_error(
1052 StatusCode::BAD_REQUEST,
1053 "NotFoundException",
1054 format!("Key '{key_id}' does not exist"),
1055 )
1056 })?;
1057
1058 let state = self.state.read();
1059 let key = state.keys.get(&resolved).ok_or_else(|| {
1060 AwsServiceError::aws_error(
1061 StatusCode::INTERNAL_SERVER_ERROR,
1062 "KMSInternalException",
1063 "Key state became inconsistent",
1064 )
1065 })?;
1066 if !key.enabled {
1067 return Err(AwsServiceError::aws_error(
1068 StatusCode::BAD_REQUEST,
1069 "DisabledException",
1070 format!("Key '{}' is disabled", key.arn),
1071 ));
1072 }
1073
1074 let num_bytes = data_key_size_from_body(&body)?;
1075 let data_key_bytes: Vec<u8> = rand_bytes(num_bytes);
1076 let plaintext_b64 = base64::engine::general_purpose::STANDARD.encode(&data_key_bytes);
1077 let envelope = format!("{FAKE_ENVELOPE_PREFIX}{}:{plaintext_b64}", key.key_id);
1078 let ciphertext_b64 = base64::engine::general_purpose::STANDARD.encode(envelope.as_bytes());
1079
1080 Ok(AwsResponse::json(
1081 StatusCode::OK,
1082 serde_json::to_string(&json!({
1083 "CiphertextBlob": ciphertext_b64,
1084 "KeyId": key.arn,
1085 }))
1086 .unwrap(),
1087 ))
1088 }
1089
1090 fn generate_random(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1091 let body = req.json_body();
1092
1093 validate_optional_string_length(
1096 "customKeyStoreId",
1097 body["CustomKeyStoreId"].as_str(),
1098 1,
1099 64,
1100 )?;
1101
1102 let num_bytes = body["NumberOfBytes"].as_u64().unwrap_or(32) as usize;
1103
1104 validate_range_i64("numberOfBytes", num_bytes as i64, 1, 1024)?;
1105
1106 let random_bytes = rand_bytes(num_bytes);
1107 let b64 = base64::engine::general_purpose::STANDARD.encode(&random_bytes);
1108
1109 Ok(AwsResponse::json(
1110 StatusCode::OK,
1111 serde_json::to_string(&json!({
1112 "Plaintext": b64,
1113 }))
1114 .unwrap(),
1115 ))
1116 }
1117
1118 fn create_alias(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1119 let body = req.json_body();
1120 let alias_name = body["AliasName"]
1121 .as_str()
1122 .ok_or_else(|| {
1123 AwsServiceError::aws_error(
1124 StatusCode::BAD_REQUEST,
1125 "ValidationException",
1126 "AliasName is required",
1127 )
1128 })?
1129 .to_string();
1130 let target_key_id = body["TargetKeyId"]
1131 .as_str()
1132 .ok_or_else(|| {
1133 AwsServiceError::aws_error(
1134 StatusCode::BAD_REQUEST,
1135 "ValidationException",
1136 "TargetKeyId is required",
1137 )
1138 })?
1139 .to_string();
1140
1141 if !alias_name.starts_with("alias/") {
1143 return Err(AwsServiceError::aws_error(
1144 StatusCode::BAD_REQUEST,
1145 "ValidationException",
1146 "Invalid identifier",
1147 ));
1148 }
1149
1150 if alias_name.starts_with("alias/aws/") {
1152 return Err(AwsServiceError::aws_error(
1153 StatusCode::BAD_REQUEST,
1154 "NotAuthorizedException",
1155 "",
1156 ));
1157 }
1158
1159 let alias_suffix = &alias_name["alias/".len()..];
1161 if alias_suffix.contains(':') {
1162 return Err(AwsServiceError::aws_error(
1163 StatusCode::BAD_REQUEST,
1164 "ValidationException",
1165 format!("{alias_name} contains invalid characters for an alias"),
1166 ));
1167 }
1168
1169 let valid_chars = alias_name
1171 .chars()
1172 .all(|c| c.is_alphanumeric() || c == '/' || c == '_' || c == '-' || c == ':');
1173 if !valid_chars {
1174 return Err(AwsServiceError::aws_error(
1175 StatusCode::BAD_REQUEST,
1176 "ValidationException",
1177 format!(
1178 "1 validation error detected: Value '{}' at 'aliasName' failed to satisfy constraint: Member must satisfy regular expression pattern: ^[a-zA-Z0-9:/_-]+$",
1179 alias_name
1180 ),
1181 ));
1182 }
1183
1184 if target_key_id.starts_with("alias/") {
1186 return Err(AwsServiceError::aws_error(
1187 StatusCode::BAD_REQUEST,
1188 "ValidationException",
1189 "Aliases must refer to keys. Not aliases",
1190 ));
1191 }
1192
1193 let resolved = self.resolve_key_id(&target_key_id).ok_or_else(|| {
1194 AwsServiceError::aws_error(
1195 StatusCode::BAD_REQUEST,
1196 "NotFoundException",
1197 format!("Key '{target_key_id}' does not exist"),
1198 )
1199 })?;
1200
1201 let mut state = self.state.write();
1202
1203 if state.aliases.contains_key(&alias_name) {
1204 let alias_arn = format!(
1205 "arn:aws:kms:{}:{}:{}",
1206 state.region, state.account_id, alias_name
1207 );
1208 return Err(AwsServiceError::aws_error(
1209 StatusCode::BAD_REQUEST,
1210 "AlreadyExistsException",
1211 format!("An alias with the name {alias_arn} already exists"),
1212 ));
1213 }
1214
1215 let alias_arn = format!(
1216 "arn:aws:kms:{}:{}:{}",
1217 state.region, state.account_id, alias_name
1218 );
1219
1220 state.aliases.insert(
1221 alias_name.clone(),
1222 KmsAlias {
1223 alias_name,
1224 alias_arn,
1225 target_key_id: resolved,
1226 creation_date: Utc::now().timestamp() as f64,
1227 },
1228 );
1229
1230 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1231 }
1232
1233 fn delete_alias(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1234 let body = req.json_body();
1235 let alias_name = body["AliasName"].as_str().ok_or_else(|| {
1236 AwsServiceError::aws_error(
1237 StatusCode::BAD_REQUEST,
1238 "ValidationException",
1239 "AliasName is required",
1240 )
1241 })?;
1242
1243 if !alias_name.starts_with("alias/") {
1244 return Err(AwsServiceError::aws_error(
1245 StatusCode::BAD_REQUEST,
1246 "ValidationException",
1247 "Invalid identifier",
1248 ));
1249 }
1250
1251 let mut state = self.state.write();
1252 if state.aliases.remove(alias_name).is_none() {
1253 let alias_arn = format!(
1254 "arn:aws:kms:{}:{}:{}",
1255 state.region, state.account_id, alias_name
1256 );
1257 return Err(AwsServiceError::aws_error(
1258 StatusCode::BAD_REQUEST,
1259 "NotFoundException",
1260 format!("Alias {alias_arn} is not found."),
1261 ));
1262 }
1263
1264 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1265 }
1266
1267 fn update_alias(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1268 let body = req.json_body();
1269 let alias_name = body["AliasName"].as_str().ok_or_else(|| {
1270 AwsServiceError::aws_error(
1271 StatusCode::BAD_REQUEST,
1272 "ValidationException",
1273 "AliasName is required",
1274 )
1275 })?;
1276 let target_key_id = body["TargetKeyId"].as_str().ok_or_else(|| {
1277 AwsServiceError::aws_error(
1278 StatusCode::BAD_REQUEST,
1279 "ValidationException",
1280 "TargetKeyId is required",
1281 )
1282 })?;
1283
1284 let resolved = self.resolve_key_id(target_key_id).ok_or_else(|| {
1285 AwsServiceError::aws_error(
1286 StatusCode::BAD_REQUEST,
1287 "NotFoundException",
1288 format!("Key '{target_key_id}' does not exist"),
1289 )
1290 })?;
1291
1292 let mut state = self.state.write();
1293 let alias = state.aliases.get_mut(alias_name).ok_or_else(|| {
1294 AwsServiceError::aws_error(
1295 StatusCode::BAD_REQUEST,
1296 "NotFoundException",
1297 format!("Alias '{alias_name}' does not exist"),
1298 )
1299 })?;
1300
1301 alias.target_key_id = resolved;
1302
1303 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1304 }
1305
1306 fn list_aliases(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1307 let body = req.json_body();
1308
1309 validate_optional_json_range("limit", &body["Limit"], 1, 100)?;
1310 validate_optional_string_length("marker", body["Marker"].as_str(), 1, 320)?;
1311
1312 if !body["KeyId"].is_null() && !body["KeyId"].is_string() {
1313 return Err(AwsServiceError::aws_error(
1314 StatusCode::BAD_REQUEST,
1315 "ValidationException",
1316 "KeyId must be a string",
1317 ));
1318 }
1319 validate_optional_string_length("keyId", body["KeyId"].as_str(), 1, 2048)?;
1320
1321 let key_id_filter = body["KeyId"].as_str();
1322
1323 let state = self.state.read();
1324
1325 let resolved_filter =
1327 key_id_filter.and_then(|kid| Self::resolve_key_id_with_state(&state, kid));
1328
1329 let aliases: Vec<Value> = state
1330 .aliases
1331 .values()
1332 .filter(|a| match (&resolved_filter, key_id_filter) {
1333 (Some(r), _) => a.target_key_id == *r,
1334 (None, Some(_)) => false,
1335 (None, None) => true,
1336 })
1337 .map(|a| {
1338 json!({
1339 "AliasName": a.alias_name,
1340 "AliasArn": a.alias_arn,
1341 "TargetKeyId": a.target_key_id,
1342 })
1343 })
1344 .collect();
1345
1346 Ok(AwsResponse::json(
1347 StatusCode::OK,
1348 serde_json::to_string(&json!({
1349 "Aliases": aliases,
1350 "Truncated": false,
1351 }))
1352 .unwrap(),
1353 ))
1354 }
1355
1356 fn tag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1357 let body = req.json_body();
1358 let key_id = Self::require_key_id(&body)?;
1359
1360 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
1361 AwsServiceError::aws_error(
1362 StatusCode::BAD_REQUEST,
1363 "NotFoundException",
1364 format!("Invalid keyId {key_id}"),
1365 )
1366 })?;
1367
1368 let mut state = self.state.write();
1369 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1370 AwsServiceError::aws_error(
1371 StatusCode::INTERNAL_SERVER_ERROR,
1372 "KMSInternalException",
1373 "Key state became inconsistent",
1374 )
1375 })?;
1376
1377 fakecloud_core::tags::apply_tags(&mut key.tags, &body, "Tags", "TagKey", "TagValue")
1378 .map_err(|f| {
1379 AwsServiceError::aws_error(
1380 StatusCode::BAD_REQUEST,
1381 "ValidationException",
1382 format!("{f} must be a list"),
1383 )
1384 })?;
1385
1386 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1387 }
1388
1389 fn untag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1390 let body = req.json_body();
1391 let key_id = Self::require_key_id(&body)?;
1392
1393 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
1394 AwsServiceError::aws_error(
1395 StatusCode::BAD_REQUEST,
1396 "NotFoundException",
1397 format!("Invalid keyId {key_id}"),
1398 )
1399 })?;
1400
1401 let mut state = self.state.write();
1402 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1403 AwsServiceError::aws_error(
1404 StatusCode::INTERNAL_SERVER_ERROR,
1405 "KMSInternalException",
1406 "Key state became inconsistent",
1407 )
1408 })?;
1409
1410 fakecloud_core::tags::remove_tags(&mut key.tags, &body, "TagKeys").map_err(|f| {
1411 AwsServiceError::aws_error(
1412 StatusCode::BAD_REQUEST,
1413 "ValidationException",
1414 format!("{f} must be a list"),
1415 )
1416 })?;
1417
1418 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1419 }
1420
1421 fn list_resource_tags(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1422 let body = req.json_body();
1423 let key_id = Self::require_key_id(&body)?;
1424
1425 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
1426 AwsServiceError::aws_error(
1427 StatusCode::BAD_REQUEST,
1428 "NotFoundException",
1429 format!("Invalid keyId {key_id}"),
1430 )
1431 })?;
1432
1433 let state = self.state.read();
1434 let key = state.keys.get(&resolved).ok_or_else(|| {
1435 AwsServiceError::aws_error(
1436 StatusCode::INTERNAL_SERVER_ERROR,
1437 "KMSInternalException",
1438 "Key state became inconsistent",
1439 )
1440 })?;
1441 let tags = fakecloud_core::tags::tags_to_json(&key.tags, "TagKey", "TagValue");
1442
1443 Ok(AwsResponse::json(
1444 StatusCode::OK,
1445 serde_json::to_string(&json!({
1446 "Tags": tags,
1447 "Truncated": false,
1448 }))
1449 .unwrap(),
1450 ))
1451 }
1452
1453 fn update_key_description(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1454 let body = req.json_body();
1455 let resolved = self.resolve_required_key(&body)?;
1456 let description = body["Description"].as_str().unwrap_or("").to_string();
1457
1458 let mut state = self.state.write();
1459 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1460 AwsServiceError::aws_error(
1461 StatusCode::INTERNAL_SERVER_ERROR,
1462 "KMSInternalException",
1463 "Key state became inconsistent",
1464 )
1465 })?;
1466 key.description = description;
1467
1468 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1469 }
1470
1471 fn get_key_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1472 let body = req.json_body();
1473 let key_id = Self::require_key_id(&body)?;
1474
1475 if key_id.starts_with("alias/") {
1477 return Err(AwsServiceError::aws_error(
1478 StatusCode::BAD_REQUEST,
1479 "NotFoundException",
1480 format!("Invalid keyId {key_id}"),
1481 ));
1482 }
1483
1484 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
1485 AwsServiceError::aws_error(
1486 StatusCode::BAD_REQUEST,
1487 "NotFoundException",
1488 format!("Key '{key_id}' does not exist"),
1489 )
1490 })?;
1491
1492 let state = self.state.read();
1493 let key = state.keys.get(&resolved).ok_or_else(|| {
1494 AwsServiceError::aws_error(
1495 StatusCode::INTERNAL_SERVER_ERROR,
1496 "KMSInternalException",
1497 "Key state became inconsistent",
1498 )
1499 })?;
1500
1501 Ok(AwsResponse::json(
1502 StatusCode::OK,
1503 serde_json::to_string(&json!({
1504 "Policy": key.policy,
1505 }))
1506 .unwrap(),
1507 ))
1508 }
1509
1510 fn put_key_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1511 let body = req.json_body();
1512 let key_id = Self::require_key_id(&body)?;
1513
1514 if key_id.starts_with("alias/") {
1516 return Err(AwsServiceError::aws_error(
1517 StatusCode::BAD_REQUEST,
1518 "NotFoundException",
1519 format!("Invalid keyId {key_id}"),
1520 ));
1521 }
1522
1523 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
1524 AwsServiceError::aws_error(
1525 StatusCode::BAD_REQUEST,
1526 "NotFoundException",
1527 format!("Key '{key_id}' does not exist"),
1528 )
1529 })?;
1530
1531 let policy = body["Policy"].as_str().unwrap_or("").to_string();
1532
1533 let mut state = self.state.write();
1534 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1535 AwsServiceError::aws_error(
1536 StatusCode::INTERNAL_SERVER_ERROR,
1537 "KMSInternalException",
1538 "Key state became inconsistent",
1539 )
1540 })?;
1541 key.policy = policy;
1542
1543 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1544 }
1545
1546 fn list_key_policies(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1547 let body = req.json_body();
1548 let _resolved = self.resolve_required_key(&body)?;
1549
1550 Ok(AwsResponse::json(
1551 StatusCode::OK,
1552 serde_json::to_string(&json!({
1553 "PolicyNames": ["default"],
1554 "Truncated": false,
1555 }))
1556 .unwrap(),
1557 ))
1558 }
1559
1560 fn get_key_rotation_status(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1561 let body = req.json_body();
1562 let key_id = Self::require_key_id(&body)?;
1563
1564 if key_id.starts_with("alias/") {
1566 return Err(AwsServiceError::aws_error(
1567 StatusCode::BAD_REQUEST,
1568 "NotFoundException",
1569 format!("Invalid keyId {key_id}"),
1570 ));
1571 }
1572
1573 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
1574 AwsServiceError::aws_error(
1575 StatusCode::BAD_REQUEST,
1576 "NotFoundException",
1577 format!("Key '{key_id}' does not exist"),
1578 )
1579 })?;
1580
1581 let state = self.state.read();
1582 let key = state.keys.get(&resolved).ok_or_else(|| {
1583 AwsServiceError::aws_error(
1584 StatusCode::INTERNAL_SERVER_ERROR,
1585 "KMSInternalException",
1586 "Key state became inconsistent",
1587 )
1588 })?;
1589
1590 Ok(AwsResponse::json(
1591 StatusCode::OK,
1592 serde_json::to_string(&json!({
1593 "KeyRotationEnabled": key.key_rotation_enabled,
1594 }))
1595 .unwrap(),
1596 ))
1597 }
1598
1599 fn enable_key_rotation(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1600 let body = req.json_body();
1601 let key_id = Self::require_key_id(&body)?;
1602
1603 if key_id.starts_with("alias/") {
1604 return Err(AwsServiceError::aws_error(
1605 StatusCode::BAD_REQUEST,
1606 "NotFoundException",
1607 format!("Invalid keyId {key_id}"),
1608 ));
1609 }
1610
1611 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
1612 AwsServiceError::aws_error(
1613 StatusCode::BAD_REQUEST,
1614 "NotFoundException",
1615 format!("Key '{key_id}' does not exist"),
1616 )
1617 })?;
1618
1619 let mut state = self.state.write();
1620 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1621 AwsServiceError::aws_error(
1622 StatusCode::INTERNAL_SERVER_ERROR,
1623 "KMSInternalException",
1624 "Key state became inconsistent",
1625 )
1626 })?;
1627 key.key_rotation_enabled = true;
1628
1629 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1630 }
1631
1632 fn disable_key_rotation(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1633 let body = req.json_body();
1634 let key_id = Self::require_key_id(&body)?;
1635
1636 if key_id.starts_with("alias/") {
1637 return Err(AwsServiceError::aws_error(
1638 StatusCode::BAD_REQUEST,
1639 "NotFoundException",
1640 format!("Invalid keyId {key_id}"),
1641 ));
1642 }
1643
1644 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
1645 AwsServiceError::aws_error(
1646 StatusCode::BAD_REQUEST,
1647 "NotFoundException",
1648 format!("Key '{key_id}' does not exist"),
1649 )
1650 })?;
1651
1652 let mut state = self.state.write();
1653 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1654 AwsServiceError::aws_error(
1655 StatusCode::INTERNAL_SERVER_ERROR,
1656 "KMSInternalException",
1657 "Key state became inconsistent",
1658 )
1659 })?;
1660 key.key_rotation_enabled = false;
1661
1662 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1663 }
1664
1665 fn rotate_key_on_demand(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1666 let body = req.json_body();
1667 let resolved = self.resolve_required_key(&body)?;
1668
1669 let mut state = self.state.write();
1670 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1671 AwsServiceError::aws_error(
1672 StatusCode::INTERNAL_SERVER_ERROR,
1673 "KMSInternalException",
1674 "Key state became inconsistent",
1675 )
1676 })?;
1677
1678 let rotation = KeyRotation {
1679 key_id: key.key_id.clone(),
1680 rotation_date: Utc::now().timestamp() as f64,
1681 rotation_type: "ON_DEMAND".to_string(),
1682 };
1683 key.rotations.push(rotation);
1684
1685 Ok(AwsResponse::json(
1686 StatusCode::OK,
1687 serde_json::to_string(&json!({
1688 "KeyId": key.key_id,
1689 }))
1690 .unwrap(),
1691 ))
1692 }
1693
1694 fn list_key_rotations(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1695 let body = req.json_body();
1696 let resolved = self.resolve_required_key(&body)?;
1697 validate_optional_json_range("limit", &body["Limit"], 1, 1000)?;
1698 let limit = body["Limit"].as_i64().unwrap_or(1000) as usize;
1699 let marker = body["Marker"].as_str();
1700
1701 let state = self.state.read();
1702 let key = state.keys.get(&resolved).ok_or_else(|| {
1703 AwsServiceError::aws_error(
1704 StatusCode::INTERNAL_SERVER_ERROR,
1705 "KMSInternalException",
1706 "Key state became inconsistent",
1707 )
1708 })?;
1709
1710 let start_index = if let Some(marker) = marker {
1711 marker.parse::<usize>().unwrap_or(0)
1712 } else {
1713 0
1714 };
1715
1716 let rotations: Vec<Value> = key
1717 .rotations
1718 .iter()
1719 .skip(start_index)
1720 .take(limit)
1721 .map(|r| {
1722 json!({
1723 "KeyId": r.key_id,
1724 "RotationDate": r.rotation_date,
1725 "RotationType": r.rotation_type,
1726 })
1727 })
1728 .collect();
1729
1730 let total_after_start = key.rotations.len().saturating_sub(start_index);
1731 let truncated = total_after_start > limit;
1732
1733 let mut response = json!({
1734 "Rotations": rotations,
1735 "Truncated": truncated,
1736 });
1737
1738 if truncated {
1739 response["NextMarker"] = json!((start_index + limit).to_string());
1740 }
1741
1742 Ok(AwsResponse::json(
1743 StatusCode::OK,
1744 serde_json::to_string(&response).unwrap(),
1745 ))
1746 }
1747
1748 fn sign(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1749 let body = req.json_body();
1750 let key_id = Self::require_key_id(&body)?;
1751 let message_b64 = body["Message"].as_str().unwrap_or("");
1752 let signing_algorithm = body["SigningAlgorithm"].as_str().unwrap_or("");
1753
1754 let message_bytes = base64::engine::general_purpose::STANDARD
1756 .decode(message_b64)
1757 .unwrap_or_default();
1758
1759 if message_bytes.is_empty() {
1760 return Err(AwsServiceError::aws_error(
1761 StatusCode::BAD_REQUEST,
1762 "ValidationException",
1763 "1 validation error detected: Value at 'Message' failed to satisfy constraint: Member must have length greater than or equal to 1",
1764 ));
1765 }
1766
1767 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
1768 AwsServiceError::aws_error(
1769 StatusCode::BAD_REQUEST,
1770 "NotFoundException",
1771 format!("Key '{key_id}' does not exist"),
1772 )
1773 })?;
1774
1775 let state = self.state.read();
1776 let key = state.keys.get(&resolved).ok_or_else(|| {
1777 AwsServiceError::aws_error(
1778 StatusCode::INTERNAL_SERVER_ERROR,
1779 "KMSInternalException",
1780 "Key state became inconsistent",
1781 )
1782 })?;
1783
1784 if key.key_usage != "SIGN_VERIFY" {
1786 return Err(AwsServiceError::aws_error(
1787 StatusCode::BAD_REQUEST,
1788 "ValidationException",
1789 format!(
1790 "1 validation error detected: Value '{}' at 'KeyId' failed to satisfy constraint: Member must point to a key with usage: 'SIGN_VERIFY'",
1791 resolved
1792 ),
1793 ));
1794 }
1795
1796 let valid_algs = key.signing_algorithms.as_deref().unwrap_or(&[]);
1798 if !valid_algs.iter().any(|a| a == signing_algorithm) {
1799 let set: Vec<String> = if valid_algs.is_empty() {
1800 VALID_SIGNING_ALGORITHMS
1801 .iter()
1802 .map(|s| s.to_string())
1803 .collect()
1804 } else {
1805 valid_algs.to_vec()
1806 };
1807 return Err(AwsServiceError::aws_error(
1808 StatusCode::BAD_REQUEST,
1809 "ValidationException",
1810 format!(
1811 "1 validation error detected: Value '{}' at 'SigningAlgorithm' failed to satisfy constraint: Member must satisfy enum value set: {}",
1812 signing_algorithm, fmt_enum_set(&set)
1813 ),
1814 ));
1815 }
1816
1817 let sig_data = format!(
1819 "fakecloud-sig:{}:{}:{}",
1820 key.key_id, signing_algorithm, message_b64
1821 );
1822 let signature_b64 = base64::engine::general_purpose::STANDARD.encode(sig_data.as_bytes());
1823
1824 Ok(AwsResponse::json(
1825 StatusCode::OK,
1826 serde_json::to_string(&json!({
1827 "Signature": signature_b64,
1828 "SigningAlgorithm": signing_algorithm,
1829 "KeyId": key.arn,
1830 }))
1831 .unwrap(),
1832 ))
1833 }
1834
1835 fn verify(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1836 let body = req.json_body();
1837 let key_id = Self::require_key_id(&body)?;
1838 let message_b64 = body["Message"].as_str().unwrap_or("");
1839 let signature_b64 = body["Signature"].as_str().unwrap_or("");
1840 let signing_algorithm = body["SigningAlgorithm"].as_str().unwrap_or("");
1841
1842 let message_bytes = base64::engine::general_purpose::STANDARD
1844 .decode(message_b64)
1845 .unwrap_or_default();
1846
1847 if message_bytes.is_empty() {
1848 return Err(AwsServiceError::aws_error(
1849 StatusCode::BAD_REQUEST,
1850 "ValidationException",
1851 "1 validation error detected: Value at 'Message' failed to satisfy constraint: Member must have length greater than or equal to 1",
1852 ));
1853 }
1854
1855 let sig_bytes = base64::engine::general_purpose::STANDARD
1857 .decode(signature_b64)
1858 .unwrap_or_default();
1859 if sig_bytes.is_empty() {
1860 return Err(AwsServiceError::aws_error(
1861 StatusCode::BAD_REQUEST,
1862 "ValidationException",
1863 "1 validation error detected: Value at 'Signature' failed to satisfy constraint: Member must have length greater than or equal to 1",
1864 ));
1865 }
1866
1867 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
1868 AwsServiceError::aws_error(
1869 StatusCode::BAD_REQUEST,
1870 "NotFoundException",
1871 format!("Key '{key_id}' does not exist"),
1872 )
1873 })?;
1874
1875 let state = self.state.read();
1876 let key = state.keys.get(&resolved).ok_or_else(|| {
1877 AwsServiceError::aws_error(
1878 StatusCode::INTERNAL_SERVER_ERROR,
1879 "KMSInternalException",
1880 "Key state became inconsistent",
1881 )
1882 })?;
1883
1884 if key.key_usage != "SIGN_VERIFY" {
1886 return Err(AwsServiceError::aws_error(
1887 StatusCode::BAD_REQUEST,
1888 "ValidationException",
1889 format!(
1890 "1 validation error detected: Value '{}' at 'KeyId' failed to satisfy constraint: Member must point to a key with usage: 'SIGN_VERIFY'",
1891 resolved
1892 ),
1893 ));
1894 }
1895
1896 let valid_algs = key.signing_algorithms.as_deref().unwrap_or(&[]);
1898 if !valid_algs.iter().any(|a| a == signing_algorithm) {
1899 let set: Vec<String> = if valid_algs.is_empty() {
1900 VALID_SIGNING_ALGORITHMS
1901 .iter()
1902 .map(|s| s.to_string())
1903 .collect()
1904 } else {
1905 valid_algs.to_vec()
1906 };
1907 return Err(AwsServiceError::aws_error(
1908 StatusCode::BAD_REQUEST,
1909 "ValidationException",
1910 format!(
1911 "1 validation error detected: Value '{}' at 'SigningAlgorithm' failed to satisfy constraint: Member must satisfy enum value set: {}",
1912 signing_algorithm, fmt_enum_set(&set)
1913 ),
1914 ));
1915 }
1916
1917 let expected_sig_data = format!(
1919 "fakecloud-sig:{}:{}:{}",
1920 key.key_id, signing_algorithm, message_b64
1921 );
1922 let expected_signature_b64 =
1923 base64::engine::general_purpose::STANDARD.encode(expected_sig_data.as_bytes());
1924
1925 let signature_valid = signature_b64 == expected_signature_b64;
1926
1927 Ok(AwsResponse::json(
1928 StatusCode::OK,
1929 serde_json::to_string(&json!({
1930 "SignatureValid": signature_valid,
1931 "SigningAlgorithm": signing_algorithm,
1932 "KeyId": key.arn,
1933 }))
1934 .unwrap(),
1935 ))
1936 }
1937
1938 fn get_public_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1939 let body = req.json_body();
1940 let key_id = Self::require_key_id(&body)?;
1941
1942 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
1943 AwsServiceError::aws_error(
1944 StatusCode::BAD_REQUEST,
1945 "NotFoundException",
1946 format!("Key '{key_id}' does not exist"),
1947 )
1948 })?;
1949
1950 let state = self.state.read();
1951 let key = state.keys.get(&resolved).ok_or_else(|| {
1952 AwsServiceError::aws_error(
1953 StatusCode::INTERNAL_SERVER_ERROR,
1954 "KMSInternalException",
1955 "Key state became inconsistent",
1956 )
1957 })?;
1958
1959 let fake_public_key = generate_fake_public_key(&key.key_spec);
1961 let public_key_b64 = base64::engine::general_purpose::STANDARD.encode(&fake_public_key);
1962
1963 let mut response = json!({
1964 "KeyId": key.arn,
1965 "KeySpec": key.key_spec,
1966 "KeyUsage": key.key_usage,
1967 "PublicKey": public_key_b64,
1968 "CustomerMasterKeySpec": key.key_spec,
1969 });
1970
1971 if let Some(ref signing_algs) = key.signing_algorithms {
1972 response["SigningAlgorithms"] = json!(signing_algs);
1973 }
1974 if let Some(ref enc_algs) = key.encryption_algorithms {
1975 response["EncryptionAlgorithms"] = json!(enc_algs);
1976 }
1977
1978 Ok(AwsResponse::json(
1979 StatusCode::OK,
1980 serde_json::to_string(&response).unwrap(),
1981 ))
1982 }
1983
1984 fn create_grant(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1985 let body = req.json_body();
1986 let key_id = Self::require_key_id(&body)?;
1987
1988 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
1989 AwsServiceError::aws_error(
1990 StatusCode::BAD_REQUEST,
1991 "NotFoundException",
1992 format!("Key '{key_id}' does not exist"),
1993 )
1994 })?;
1995
1996 let grantee_principal = body["GranteePrincipal"].as_str().unwrap_or("").to_string();
1997 let retiring_principal = body["RetiringPrincipal"].as_str().map(|s| s.to_string());
1998 let operations: Vec<String> = body["Operations"]
1999 .as_array()
2000 .map(|arr| {
2001 arr.iter()
2002 .filter_map(|v| v.as_str().map(|s| s.to_string()))
2003 .collect()
2004 })
2005 .unwrap_or_default();
2006 let constraints = if body["Constraints"].is_null() {
2007 None
2008 } else {
2009 Some(body["Constraints"].clone())
2010 };
2011 let name = body["Name"].as_str().map(|s| s.to_string());
2012
2013 let grant_id = Uuid::new_v4().to_string();
2014 let grant_token = Uuid::new_v4().to_string();
2015
2016 let mut state = self.state.write();
2017 state.grants.push(KmsGrant {
2018 grant_id: grant_id.clone(),
2019 grant_token: grant_token.clone(),
2020 key_id: resolved,
2021 grantee_principal,
2022 retiring_principal,
2023 operations,
2024 constraints,
2025 name,
2026 creation_date: Utc::now().timestamp() as f64,
2027 });
2028
2029 Ok(AwsResponse::json(
2030 StatusCode::OK,
2031 serde_json::to_string(&json!({
2032 "GrantId": grant_id,
2033 "GrantToken": grant_token,
2034 }))
2035 .unwrap(),
2036 ))
2037 }
2038
2039 fn list_grants(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2040 let body = req.json_body();
2041 let key_id = Self::require_key_id(&body)?;
2042
2043 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
2044 AwsServiceError::aws_error(
2045 StatusCode::BAD_REQUEST,
2046 "NotFoundException",
2047 format!("Key '{key_id}' does not exist"),
2048 )
2049 })?;
2050
2051 let grant_id_filter = body["GrantId"].as_str();
2052
2053 let state = self.state.read();
2054 let grants: Vec<Value> = state
2055 .grants
2056 .iter()
2057 .filter(|g| g.key_id == resolved)
2058 .filter(|g| {
2059 if let Some(gid) = grant_id_filter {
2060 g.grant_id == gid
2061 } else {
2062 true
2063 }
2064 })
2065 .map(grant_to_json)
2066 .collect();
2067
2068 Ok(AwsResponse::json(
2069 StatusCode::OK,
2070 serde_json::to_string(&json!({
2071 "Grants": grants,
2072 "Truncated": false,
2073 }))
2074 .unwrap(),
2075 ))
2076 }
2077
2078 fn list_retirable_grants(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2079 let body = req.json_body();
2080
2081 validate_required("RetiringPrincipal", &body["RetiringPrincipal"])?;
2082 let retiring_principal = body["RetiringPrincipal"].as_str().ok_or_else(|| {
2083 AwsServiceError::aws_error(
2084 StatusCode::BAD_REQUEST,
2085 "ValidationException",
2086 "RetiringPrincipal must be a string",
2087 )
2088 })?;
2089 validate_string_length("retiringPrincipal", retiring_principal, 1, 256)?;
2090 validate_optional_json_range("limit", &body["Limit"], 1, 1000)?;
2091 validate_optional_string_length("marker", body["Marker"].as_str(), 1, 320)?;
2092
2093 let limit = body["Limit"].as_i64().unwrap_or(1000) as usize;
2094 let marker = body["Marker"].as_str();
2095
2096 let state = self.state.read();
2097 let all_grants: Vec<Value> = state
2098 .grants
2099 .iter()
2100 .filter(|g| {
2101 g.retiring_principal
2102 .as_deref()
2103 .is_some_and(|rp| rp == retiring_principal)
2104 })
2105 .map(grant_to_json)
2106 .collect();
2107
2108 let start = if let Some(m) = marker {
2109 all_grants
2110 .iter()
2111 .position(|g| g["GrantId"].as_str() == Some(m))
2112 .map(|pos| pos + 1)
2113 .unwrap_or(0)
2114 } else {
2115 0
2116 };
2117
2118 let page = &all_grants[start..all_grants.len().min(start + limit)];
2119 let truncated = start + limit < all_grants.len();
2120
2121 let mut result = json!({
2122 "Grants": page,
2123 "Truncated": truncated,
2124 });
2125
2126 if truncated {
2127 if let Some(last) = page.last() {
2128 result["NextMarker"] = last["GrantId"].clone();
2129 }
2130 }
2131
2132 Ok(AwsResponse::json(
2133 StatusCode::OK,
2134 serde_json::to_string(&result).unwrap(),
2135 ))
2136 }
2137
2138 fn revoke_grant(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2139 let body = req.json_body();
2140 let key_id = Self::require_key_id(&body)?;
2141 let grant_id = body["GrantId"].as_str().unwrap_or("");
2142
2143 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
2144 AwsServiceError::aws_error(
2145 StatusCode::BAD_REQUEST,
2146 "NotFoundException",
2147 format!("Key '{key_id}' does not exist"),
2148 )
2149 })?;
2150
2151 let mut state = self.state.write();
2152 let idx = state
2153 .grants
2154 .iter()
2155 .position(|g| g.key_id == resolved && g.grant_id == grant_id);
2156
2157 match idx {
2158 Some(i) => {
2159 state.grants.remove(i);
2160 Ok(AwsResponse::json(StatusCode::OK, "{}"))
2161 }
2162 None => Err(AwsServiceError::aws_error(
2163 StatusCode::BAD_REQUEST,
2164 "NotFoundException",
2165 format!("Grant ID {grant_id} not found"),
2166 )),
2167 }
2168 }
2169
2170 fn retire_grant(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2171 let body = req.json_body();
2172 let grant_token = body["GrantToken"].as_str();
2173 let grant_id = body["GrantId"].as_str();
2174 let key_id = body["KeyId"].as_str();
2175
2176 let mut state = self.state.write();
2177
2178 let idx = if let Some(token) = grant_token {
2179 state.grants.iter().position(|g| g.grant_token == token)
2180 } else if let (Some(kid), Some(gid)) = (key_id, grant_id) {
2181 let resolved = Self::resolve_key_id_with_state(&state, kid);
2182 resolved.and_then(|r| {
2183 state
2184 .grants
2185 .iter()
2186 .position(|g| g.key_id == r && g.grant_id == gid)
2187 })
2188 } else {
2189 None
2190 };
2191
2192 match idx {
2193 Some(i) => {
2194 state.grants.remove(i);
2195 Ok(AwsResponse::json(StatusCode::OK, "{}"))
2196 }
2197 None => Err(AwsServiceError::aws_error(
2198 StatusCode::BAD_REQUEST,
2199 "NotFoundException",
2200 "Grant not found",
2201 )),
2202 }
2203 }
2204
2205 fn generate_mac(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2206 let body = req.json_body();
2207 let key_id = Self::require_key_id(&body)?;
2208 let mac_algorithm = body["MacAlgorithm"].as_str().unwrap_or("").to_string();
2209 let message_b64 = body["Message"].as_str().unwrap_or("");
2210
2211 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
2212 AwsServiceError::aws_error(
2213 StatusCode::BAD_REQUEST,
2214 "NotFoundException",
2215 format!("Key '{key_id}' does not exist"),
2216 )
2217 })?;
2218
2219 let state = self.state.read();
2220 let key = state.keys.get(&resolved).ok_or_else(|| {
2221 AwsServiceError::aws_error(
2222 StatusCode::INTERNAL_SERVER_ERROR,
2223 "KMSInternalException",
2224 "Key state became inconsistent",
2225 )
2226 })?;
2227
2228 if key.key_usage != "GENERATE_VERIFY_MAC" {
2230 return Err(AwsServiceError::aws_error(
2231 StatusCode::BAD_REQUEST,
2232 "InvalidKeyUsageException",
2233 format!("Key '{}' is not a GENERATE_VERIFY_MAC key", key.arn),
2234 ));
2235 }
2236
2237 if key.mac_algorithms.is_none() {
2239 return Err(AwsServiceError::aws_error(
2240 StatusCode::BAD_REQUEST,
2241 "InvalidKeyUsageException",
2242 format!("Key '{}' does not support MAC operations", key.arn),
2243 ));
2244 }
2245
2246 let mac_data = format!(
2248 "fakecloud-mac:{}:{}:{}",
2249 key.key_id, mac_algorithm, message_b64
2250 );
2251 let mac_b64 = base64::engine::general_purpose::STANDARD.encode(mac_data.as_bytes());
2252
2253 Ok(AwsResponse::json(
2254 StatusCode::OK,
2255 serde_json::to_string(&json!({
2256 "Mac": mac_b64,
2257 "KeyId": key.key_id,
2258 "MacAlgorithm": mac_algorithm,
2259 }))
2260 .unwrap(),
2261 ))
2262 }
2263
2264 fn verify_mac(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2265 let body = req.json_body();
2266 let key_id = Self::require_key_id(&body)?;
2267 let mac_algorithm = body["MacAlgorithm"].as_str().unwrap_or("").to_string();
2268 let message_b64 = body["Message"].as_str().unwrap_or("");
2269 let mac_b64 = body["Mac"].as_str().unwrap_or("");
2270
2271 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
2272 AwsServiceError::aws_error(
2273 StatusCode::BAD_REQUEST,
2274 "NotFoundException",
2275 format!("Key '{key_id}' does not exist"),
2276 )
2277 })?;
2278
2279 let state = self.state.read();
2280 let key = state.keys.get(&resolved).ok_or_else(|| {
2281 AwsServiceError::aws_error(
2282 StatusCode::INTERNAL_SERVER_ERROR,
2283 "KMSInternalException",
2284 "Key state became inconsistent",
2285 )
2286 })?;
2287
2288 if key.key_usage != "GENERATE_VERIFY_MAC" {
2290 return Err(AwsServiceError::aws_error(
2291 StatusCode::BAD_REQUEST,
2292 "InvalidKeyUsageException",
2293 format!("Key '{}' is not a GENERATE_VERIFY_MAC key", key.arn),
2294 ));
2295 }
2296
2297 let expected_mac_data = format!(
2299 "fakecloud-mac:{}:{}:{}",
2300 key.key_id, mac_algorithm, message_b64
2301 );
2302 let expected_mac_b64 =
2303 base64::engine::general_purpose::STANDARD.encode(expected_mac_data.as_bytes());
2304
2305 let mac_valid = mac_b64 == expected_mac_b64;
2306
2307 if !mac_valid {
2308 return Err(AwsServiceError::aws_error(
2309 StatusCode::BAD_REQUEST,
2310 "KMSInvalidMacException",
2311 "MAC verification failed",
2312 ));
2313 }
2314
2315 Ok(AwsResponse::json(
2316 StatusCode::OK,
2317 serde_json::to_string(&json!({
2318 "KeyId": key.key_id,
2319 "MacAlgorithm": mac_algorithm,
2320 "MacValid": true,
2321 }))
2322 .unwrap(),
2323 ))
2324 }
2325
2326 fn replicate_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2327 let body = req.json_body();
2328 let key_id = Self::require_key_id(&body)?;
2329 let replica_region = body["ReplicaRegion"].as_str().unwrap_or("").to_string();
2330
2331 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
2332 AwsServiceError::aws_error(
2333 StatusCode::BAD_REQUEST,
2334 "NotFoundException",
2335 format!("Key '{key_id}' does not exist"),
2336 )
2337 })?;
2338
2339 let mut state = self.state.write();
2340
2341 let source_key = state.keys.get(&resolved).ok_or_else(|| {
2343 AwsServiceError::aws_error(
2344 StatusCode::INTERNAL_SERVER_ERROR,
2345 "KMSInternalException",
2346 "Key state became inconsistent",
2347 )
2348 })?;
2349 let source_key_id = source_key.key_id.clone();
2350 let source_arn = source_key.arn.clone();
2351 let source_creation_date = source_key.creation_date;
2352 let source_description = source_key.description.clone();
2353 let source_enabled = source_key.enabled;
2354 let source_key_usage = source_key.key_usage.clone();
2355 let source_key_spec = source_key.key_spec.clone();
2356 let source_key_manager = source_key.key_manager.clone();
2357 let source_key_state = source_key.key_state.clone();
2358 let source_origin = source_key.origin.clone();
2359 let source_tags = source_key.tags.clone();
2360 let source_policy = source_key.policy.clone();
2361 let source_signing_algorithms = source_key.signing_algorithms.clone();
2362 let source_encryption_algorithms = source_key.encryption_algorithms.clone();
2363 let source_mac_algorithms = source_key.mac_algorithms.clone();
2364 let account_id = state.account_id.clone();
2365 let source_region = state.region.clone();
2366
2367 let replica_arn = format!(
2368 "arn:aws:kms:{}:{}:key/{}",
2369 replica_region, account_id, source_key_id
2370 );
2371
2372 let metadata = json!({
2373 "KeyId": source_key_id,
2374 "Arn": replica_arn,
2375 "AWSAccountId": account_id,
2376 "CreationDate": source_creation_date,
2377 "Description": source_description,
2378 "Enabled": source_enabled,
2379 "KeyUsage": source_key_usage,
2380 "KeySpec": source_key_spec,
2381 "CustomerMasterKeySpec": source_key_spec,
2382 "KeyManager": source_key_manager,
2383 "KeyState": source_key_state,
2384 "Origin": source_origin,
2385 "MultiRegion": true,
2386 "MultiRegionConfiguration": {
2387 "MultiRegionKeyType": "REPLICA",
2388 "PrimaryKey": {
2389 "Arn": source_arn,
2390 "Region": source_region,
2391 },
2392 "ReplicaKeys": [],
2393 },
2394 });
2395
2396 let replica_key = KmsKey {
2397 key_id: source_key_id.clone(),
2398 arn: replica_arn,
2399 creation_date: source_creation_date,
2400 description: source_description,
2401 enabled: source_enabled,
2402 key_usage: source_key_usage,
2403 key_spec: source_key_spec,
2404 key_manager: source_key_manager,
2405 key_state: source_key_state,
2406 deletion_date: None,
2407 tags: source_tags,
2408 policy: source_policy.clone(),
2409 key_rotation_enabled: false,
2410 origin: source_origin,
2411 multi_region: true,
2412 rotations: Vec::new(),
2413 signing_algorithms: source_signing_algorithms,
2414 encryption_algorithms: source_encryption_algorithms,
2415 mac_algorithms: source_mac_algorithms,
2416 custom_key_store_id: None,
2417 imported_key_material: false,
2418 imported_material_bytes: None,
2419 private_key_seed: rand_bytes(32),
2420 primary_region: None,
2421 };
2422
2423 let replica_storage_key = format!("{}:{}", replica_region, source_key_id);
2424 state.keys.insert(replica_storage_key, replica_key);
2425
2426 Ok(AwsResponse::json(
2427 StatusCode::OK,
2428 serde_json::to_string(&json!({
2429 "ReplicaKeyMetadata": metadata,
2430 "ReplicaPolicy": source_policy,
2431 }))
2432 .unwrap(),
2433 ))
2434 }
2435
2436 fn generate_data_key_pair(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2437 let body = req.json_body();
2438 let key_id = Self::require_key_id(&body)?;
2439 let key_pair_spec = body["KeyPairSpec"]
2440 .as_str()
2441 .unwrap_or("RSA_2048")
2442 .to_string();
2443
2444 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
2445 AwsServiceError::aws_error(
2446 StatusCode::BAD_REQUEST,
2447 "NotFoundException",
2448 format!("Key '{key_id}' does not exist"),
2449 )
2450 })?;
2451
2452 let state = self.state.read();
2453 let key = state.keys.get(&resolved).ok_or_else(|| {
2454 AwsServiceError::aws_error(
2455 StatusCode::INTERNAL_SERVER_ERROR,
2456 "KMSInternalException",
2457 "Key state became inconsistent",
2458 )
2459 })?;
2460 if !key.enabled {
2461 return Err(AwsServiceError::aws_error(
2462 StatusCode::BAD_REQUEST,
2463 "DisabledException",
2464 format!("Key '{}' is disabled", key.arn),
2465 ));
2466 }
2467
2468 let public_key_bytes = generate_fake_public_key(&key_pair_spec);
2469 let private_key_bytes = rand_bytes(256);
2470 let public_key_b64 = base64::engine::general_purpose::STANDARD.encode(&public_key_bytes);
2471 let private_plaintext_b64 =
2472 base64::engine::general_purpose::STANDARD.encode(&private_key_bytes);
2473
2474 let envelope = format!(
2476 "{FAKE_ENVELOPE_PREFIX}{}:{private_plaintext_b64}",
2477 key.key_id
2478 );
2479 let private_ciphertext_b64 =
2480 base64::engine::general_purpose::STANDARD.encode(envelope.as_bytes());
2481
2482 Ok(AwsResponse::json(
2483 StatusCode::OK,
2484 serde_json::to_string(&json!({
2485 "KeyId": key.arn,
2486 "KeyPairSpec": key_pair_spec,
2487 "PublicKey": public_key_b64,
2488 "PrivateKeyPlaintext": private_plaintext_b64,
2489 "PrivateKeyCiphertextBlob": private_ciphertext_b64,
2490 }))
2491 .unwrap(),
2492 ))
2493 }
2494
2495 fn generate_data_key_pair_without_plaintext(
2496 &self,
2497 req: &AwsRequest,
2498 ) -> Result<AwsResponse, AwsServiceError> {
2499 let body = req.json_body();
2500 let key_id = Self::require_key_id(&body)?;
2501 let key_pair_spec = body["KeyPairSpec"]
2502 .as_str()
2503 .unwrap_or("RSA_2048")
2504 .to_string();
2505
2506 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
2507 AwsServiceError::aws_error(
2508 StatusCode::BAD_REQUEST,
2509 "NotFoundException",
2510 format!("Key '{key_id}' does not exist"),
2511 )
2512 })?;
2513
2514 let state = self.state.read();
2515 let key = state.keys.get(&resolved).ok_or_else(|| {
2516 AwsServiceError::aws_error(
2517 StatusCode::INTERNAL_SERVER_ERROR,
2518 "KMSInternalException",
2519 "Key state became inconsistent",
2520 )
2521 })?;
2522 if !key.enabled {
2523 return Err(AwsServiceError::aws_error(
2524 StatusCode::BAD_REQUEST,
2525 "DisabledException",
2526 format!("Key '{}' is disabled", key.arn),
2527 ));
2528 }
2529
2530 let public_key_bytes = generate_fake_public_key(&key_pair_spec);
2531 let private_key_bytes = rand_bytes(256);
2532 let public_key_b64 = base64::engine::general_purpose::STANDARD.encode(&public_key_bytes);
2533 let private_plaintext_b64 =
2534 base64::engine::general_purpose::STANDARD.encode(&private_key_bytes);
2535
2536 let envelope = format!(
2537 "{FAKE_ENVELOPE_PREFIX}{}:{private_plaintext_b64}",
2538 key.key_id
2539 );
2540 let private_ciphertext_b64 =
2541 base64::engine::general_purpose::STANDARD.encode(envelope.as_bytes());
2542
2543 Ok(AwsResponse::json(
2544 StatusCode::OK,
2545 serde_json::to_string(&json!({
2546 "KeyId": key.arn,
2547 "KeyPairSpec": key_pair_spec,
2548 "PublicKey": public_key_b64,
2549 "PrivateKeyCiphertextBlob": private_ciphertext_b64,
2550 }))
2551 .unwrap(),
2552 ))
2553 }
2554
2555 fn derive_shared_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2556 let body = req.json_body();
2557 let key_id = Self::require_key_id(&body)?;
2558 let _key_agreement_algorithm = body["KeyAgreementAlgorithm"]
2559 .as_str()
2560 .unwrap_or("ECDH")
2561 .to_string();
2562 let _public_key = body["PublicKey"].as_str().ok_or_else(|| {
2563 AwsServiceError::aws_error(
2564 StatusCode::BAD_REQUEST,
2565 "ValidationException",
2566 "PublicKey is required",
2567 )
2568 })?;
2569
2570 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
2571 AwsServiceError::aws_error(
2572 StatusCode::BAD_REQUEST,
2573 "NotFoundException",
2574 format!("Key '{key_id}' does not exist"),
2575 )
2576 })?;
2577
2578 let state = self.state.read();
2579 let key = state.keys.get(&resolved).ok_or_else(|| {
2580 AwsServiceError::aws_error(
2581 StatusCode::INTERNAL_SERVER_ERROR,
2582 "KMSInternalException",
2583 "Key state became inconsistent",
2584 )
2585 })?;
2586
2587 if !key.enabled {
2588 return Err(AwsServiceError::aws_error(
2589 StatusCode::BAD_REQUEST,
2590 "DisabledException",
2591 format!("Key '{}' is disabled", key.arn),
2592 ));
2593 }
2594
2595 if key.key_usage != "KEY_AGREEMENT" {
2597 return Err(AwsServiceError::aws_error(
2598 StatusCode::BAD_REQUEST,
2599 "InvalidKeyUsageException",
2600 format!(
2601 "Key '{}' usage is '{}', not KEY_AGREEMENT",
2602 key.arn, key.key_usage
2603 ),
2604 ));
2605 }
2606
2607 let public_key_bytes = base64::engine::general_purpose::STANDARD
2610 .decode(_public_key)
2611 .unwrap_or_default();
2612
2613 use sha2::{Digest, Sha256};
2614 let mut hasher = Sha256::new();
2615 hasher.update(&key.private_key_seed);
2616 hasher.update(&public_key_bytes);
2617 let shared_secret_bytes = hasher.finalize();
2618 let shared_secret_b64 =
2619 base64::engine::general_purpose::STANDARD.encode(shared_secret_bytes);
2620
2621 Ok(AwsResponse::json(
2622 StatusCode::OK,
2623 serde_json::to_string(&json!({
2624 "KeyId": key.arn,
2625 "SharedSecret": shared_secret_b64,
2626 "KeyAgreementAlgorithm": "ECDH",
2627 "KeyOrigin": key.origin,
2628 }))
2629 .unwrap(),
2630 ))
2631 }
2632
2633 fn get_parameters_for_import(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2634 let body = req.json_body();
2635 let key_id = Self::require_key_id(&body)?;
2636
2637 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
2638 AwsServiceError::aws_error(
2639 StatusCode::BAD_REQUEST,
2640 "NotFoundException",
2641 format!("Key '{key_id}' does not exist"),
2642 )
2643 })?;
2644
2645 let state = self.state.read();
2646 let key = state.keys.get(&resolved).ok_or_else(|| {
2647 AwsServiceError::aws_error(
2648 StatusCode::INTERNAL_SERVER_ERROR,
2649 "KMSInternalException",
2650 "Key state became inconsistent",
2651 )
2652 })?;
2653
2654 if key.origin != "EXTERNAL" {
2655 return Err(AwsServiceError::aws_error(
2656 StatusCode::BAD_REQUEST,
2657 "UnsupportedOperationException",
2658 format!("Key '{}' origin is '{}', not EXTERNAL", key.arn, key.origin),
2659 ));
2660 }
2661
2662 let import_token_bytes = rand_bytes(64);
2663 let import_token_b64 =
2664 base64::engine::general_purpose::STANDARD.encode(&import_token_bytes);
2665 let public_key_bytes = generate_fake_public_key("RSA_2048");
2666 let public_key_b64 = base64::engine::general_purpose::STANDARD.encode(&public_key_bytes);
2667
2668 let parameters_valid_to = Utc::now().timestamp() as f64 + 86400.0;
2670
2671 Ok(AwsResponse::json(
2672 StatusCode::OK,
2673 serde_json::to_string(&json!({
2674 "KeyId": key.arn,
2675 "ImportToken": import_token_b64,
2676 "PublicKey": public_key_b64,
2677 "ParametersValidTo": parameters_valid_to,
2678 }))
2679 .unwrap(),
2680 ))
2681 }
2682
2683 fn import_key_material(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2684 let body = req.json_body();
2685 let key_id = Self::require_key_id(&body)?;
2686
2687 let _import_token = body["ImportToken"].as_str().ok_or_else(|| {
2688 AwsServiceError::aws_error(
2689 StatusCode::BAD_REQUEST,
2690 "ValidationException",
2691 "ImportToken is required",
2692 )
2693 })?;
2694
2695 let encrypted_key_material = body["EncryptedKeyMaterial"].as_str().ok_or_else(|| {
2696 AwsServiceError::aws_error(
2697 StatusCode::BAD_REQUEST,
2698 "ValidationException",
2699 "EncryptedKeyMaterial is required",
2700 )
2701 })?;
2702
2703 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
2704 AwsServiceError::aws_error(
2705 StatusCode::BAD_REQUEST,
2706 "NotFoundException",
2707 format!("Key '{key_id}' does not exist"),
2708 )
2709 })?;
2710
2711 let mut state = self.state.write();
2712 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
2713 AwsServiceError::aws_error(
2714 StatusCode::BAD_REQUEST,
2715 "NotFoundException",
2716 format!("Key '{key_id}' does not exist"),
2717 )
2718 })?;
2719
2720 if key.origin != "EXTERNAL" {
2721 return Err(AwsServiceError::aws_error(
2722 StatusCode::BAD_REQUEST,
2723 "UnsupportedOperationException",
2724 format!("Key '{}' origin is '{}', not EXTERNAL", key.arn, key.origin),
2725 ));
2726 }
2727
2728 let material_bytes = base64::engine::general_purpose::STANDARD
2732 .decode(encrypted_key_material)
2733 .map_err(|_| {
2734 AwsServiceError::aws_error(
2735 StatusCode::BAD_REQUEST,
2736 "ValidationException",
2737 "EncryptedKeyMaterial is not valid base64",
2738 )
2739 })?;
2740 if material_bytes.is_empty() {
2741 return Err(AwsServiceError::aws_error(
2742 StatusCode::BAD_REQUEST,
2743 "ValidationException",
2744 "EncryptedKeyMaterial must not be empty",
2745 ));
2746 }
2747 key.imported_key_material = true;
2748 key.imported_material_bytes = Some(material_bytes);
2749 key.enabled = true;
2750 key.key_state = "Enabled".to_string();
2751
2752 Ok(AwsResponse::json(StatusCode::OK, "{}"))
2753 }
2754
2755 fn delete_imported_key_material(
2756 &self,
2757 req: &AwsRequest,
2758 ) -> Result<AwsResponse, AwsServiceError> {
2759 let body = req.json_body();
2760 let key_id = Self::require_key_id(&body)?;
2761
2762 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
2763 AwsServiceError::aws_error(
2764 StatusCode::BAD_REQUEST,
2765 "NotFoundException",
2766 format!("Key '{key_id}' does not exist"),
2767 )
2768 })?;
2769
2770 let mut state = self.state.write();
2771 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
2772 AwsServiceError::aws_error(
2773 StatusCode::BAD_REQUEST,
2774 "NotFoundException",
2775 format!("Key '{key_id}' does not exist"),
2776 )
2777 })?;
2778
2779 if key.origin != "EXTERNAL" {
2780 return Err(AwsServiceError::aws_error(
2781 StatusCode::BAD_REQUEST,
2782 "UnsupportedOperationException",
2783 format!("Key '{}' origin is '{}', not EXTERNAL", key.arn, key.origin),
2784 ));
2785 }
2786
2787 key.imported_key_material = false;
2788 key.imported_material_bytes = None;
2789 key.enabled = false;
2790 key.key_state = "PendingImport".to_string();
2791
2792 Ok(AwsResponse::json(StatusCode::OK, "{}"))
2793 }
2794
2795 fn update_primary_region(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2796 let body = req.json_body();
2797 let key_id = Self::require_key_id(&body)?;
2798 let primary_region = body["PrimaryRegion"]
2799 .as_str()
2800 .ok_or_else(|| {
2801 AwsServiceError::aws_error(
2802 StatusCode::BAD_REQUEST,
2803 "ValidationException",
2804 "PrimaryRegion is required",
2805 )
2806 })?
2807 .to_string();
2808
2809 let resolved = self.resolve_key_id(&key_id).ok_or_else(|| {
2810 AwsServiceError::aws_error(
2811 StatusCode::BAD_REQUEST,
2812 "NotFoundException",
2813 format!("Key '{key_id}' does not exist"),
2814 )
2815 })?;
2816
2817 let mut state = self.state.write();
2818 let account_id = state.account_id.clone();
2819 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
2820 AwsServiceError::aws_error(
2821 StatusCode::BAD_REQUEST,
2822 "NotFoundException",
2823 format!("Key '{key_id}' does not exist"),
2824 )
2825 })?;
2826
2827 if !key.multi_region {
2828 return Err(AwsServiceError::aws_error(
2829 StatusCode::BAD_REQUEST,
2830 "UnsupportedOperationException",
2831 format!("Key '{}' is not a multi-Region key", key.arn),
2832 ));
2833 }
2834 key.primary_region = Some(primary_region.clone());
2835 key.arn = format!(
2837 "arn:aws:kms:{}:{}:key/{}",
2838 primary_region, account_id, key.key_id
2839 );
2840
2841 Ok(AwsResponse::json(StatusCode::OK, "{}"))
2842 }
2843
2844 fn create_custom_key_store(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2845 let body = req.json_body();
2846
2847 let name = body["CustomKeyStoreName"]
2848 .as_str()
2849 .ok_or_else(|| {
2850 AwsServiceError::aws_error(
2851 StatusCode::BAD_REQUEST,
2852 "ValidationException",
2853 "CustomKeyStoreName is required",
2854 )
2855 })?
2856 .to_string();
2857
2858 validate_string_length("customKeyStoreName", &name, 1, 256)?;
2859
2860 let store_type = body["CustomKeyStoreType"]
2861 .as_str()
2862 .unwrap_or("AWS_CLOUDHSM")
2863 .to_string();
2864
2865 validate_optional_enum(
2866 "customKeyStoreType",
2867 Some(store_type.as_str()),
2868 &["AWS_CLOUDHSM", "EXTERNAL_KEY_STORE"],
2869 )?;
2870
2871 let mut state = self.state.write();
2872
2873 if state
2875 .custom_key_stores
2876 .values()
2877 .any(|s| s.custom_key_store_name == name)
2878 {
2879 return Err(AwsServiceError::aws_error(
2880 StatusCode::BAD_REQUEST,
2881 "CustomKeyStoreNameInUseException",
2882 format!("Custom key store name '{name}' is already in use"),
2883 ));
2884 }
2885
2886 let store_id = format!("cks-{}", Uuid::new_v4().as_simple());
2887 let now = Utc::now().timestamp() as f64;
2888
2889 let store = CustomKeyStore {
2890 custom_key_store_id: store_id.clone(),
2891 custom_key_store_name: name,
2892 custom_key_store_type: store_type,
2893 cloud_hsm_cluster_id: body["CloudHsmClusterId"].as_str().map(|s| s.to_string()),
2894 trust_anchor_certificate: body["TrustAnchorCertificate"]
2895 .as_str()
2896 .map(|s| s.to_string()),
2897 connection_state: "DISCONNECTED".to_string(),
2898 creation_date: now,
2899 xks_proxy_uri_endpoint: body["XksProxyUriEndpoint"].as_str().map(|s| s.to_string()),
2900 xks_proxy_uri_path: body["XksProxyUriPath"].as_str().map(|s| s.to_string()),
2901 xks_proxy_vpc_endpoint_service_name: body["XksProxyVpcEndpointServiceName"]
2902 .as_str()
2903 .map(|s| s.to_string()),
2904 xks_proxy_connectivity: body["XksProxyConnectivity"].as_str().map(|s| s.to_string()),
2905 };
2906
2907 state.custom_key_stores.insert(store_id.clone(), store);
2908
2909 Ok(AwsResponse::json(
2910 StatusCode::OK,
2911 serde_json::to_string(&json!({ "CustomKeyStoreId": store_id })).unwrap(),
2912 ))
2913 }
2914
2915 fn delete_custom_key_store(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2916 let body = req.json_body();
2917
2918 let store_id = body["CustomKeyStoreId"]
2919 .as_str()
2920 .ok_or_else(|| {
2921 AwsServiceError::aws_error(
2922 StatusCode::BAD_REQUEST,
2923 "ValidationException",
2924 "CustomKeyStoreId is required",
2925 )
2926 })?
2927 .to_string();
2928
2929 let mut state = self.state.write();
2930
2931 let store = state.custom_key_stores.get(&store_id).ok_or_else(|| {
2932 AwsServiceError::aws_error(
2933 StatusCode::BAD_REQUEST,
2934 "CustomKeyStoreNotFoundException",
2935 format!("Custom key store '{store_id}' does not exist"),
2936 )
2937 })?;
2938
2939 if store.connection_state == "CONNECTED" {
2940 return Err(AwsServiceError::aws_error(
2941 StatusCode::BAD_REQUEST,
2942 "CustomKeyStoreHasCMKsException",
2943 "Cannot delete a connected custom key store. Disconnect it first.",
2944 ));
2945 }
2946
2947 state.custom_key_stores.remove(&store_id);
2948
2949 Ok(AwsResponse::json(StatusCode::OK, "{}"))
2950 }
2951
2952 fn describe_custom_key_stores(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2953 let body = req.json_body();
2954 validate_optional_string_length(
2955 "customKeyStoreName",
2956 body["CustomKeyStoreName"].as_str(),
2957 1,
2958 256,
2959 )?;
2960 validate_optional_json_range("limit", &body["Limit"], 1, 1000)?;
2961 validate_optional_string_length("marker", body["Marker"].as_str(), 1, 1024)?;
2962
2963 let filter_id = body["CustomKeyStoreId"].as_str();
2964 let filter_name = body["CustomKeyStoreName"].as_str();
2965 let limit = body["Limit"].as_i64().unwrap_or(1000) as usize;
2966 let marker = body["Marker"].as_str();
2967
2968 let state = self.state.read();
2969
2970 let mut stores: Vec<&CustomKeyStore> = state
2971 .custom_key_stores
2972 .values()
2973 .filter(|s| {
2974 if let Some(id) = filter_id {
2975 return s.custom_key_store_id == id;
2976 }
2977 if let Some(name) = filter_name {
2978 return s.custom_key_store_name == name;
2979 }
2980 true
2981 })
2982 .collect();
2983
2984 stores.sort_by(|a, b| a.custom_key_store_id.cmp(&b.custom_key_store_id));
2985
2986 if let Some(id) = filter_id {
2988 if stores.is_empty() {
2989 return Err(AwsServiceError::aws_error(
2990 StatusCode::BAD_REQUEST,
2991 "CustomKeyStoreNotFoundException",
2992 format!("Custom key store '{id}' does not exist"),
2993 ));
2994 }
2995 }
2996
2997 let start = marker
2998 .and_then(|m| {
2999 stores
3000 .iter()
3001 .position(|s| s.custom_key_store_id == m)
3002 .map(|p| p + 1)
3003 })
3004 .unwrap_or(0);
3005
3006 let page: Vec<_> = stores.iter().skip(start).take(limit).collect();
3007 let truncated = start + page.len() < stores.len();
3008
3009 let entries: Vec<Value> = page.iter().map(|s| custom_key_store_json(s)).collect();
3010
3011 let mut resp = json!({ "CustomKeyStores": entries, "Truncated": truncated });
3012 if truncated {
3013 if let Some(last) = page.last() {
3014 resp["NextMarker"] = json!(last.custom_key_store_id);
3015 }
3016 }
3017
3018 Ok(AwsResponse::json(
3019 StatusCode::OK,
3020 serde_json::to_string(&resp).unwrap(),
3021 ))
3022 }
3023
3024 fn connect_custom_key_store(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
3025 let body = req.json_body();
3026
3027 let store_id = body["CustomKeyStoreId"]
3028 .as_str()
3029 .ok_or_else(|| {
3030 AwsServiceError::aws_error(
3031 StatusCode::BAD_REQUEST,
3032 "ValidationException",
3033 "CustomKeyStoreId is required",
3034 )
3035 })?
3036 .to_string();
3037
3038 let mut state = self.state.write();
3039
3040 let store = state.custom_key_stores.get_mut(&store_id).ok_or_else(|| {
3041 AwsServiceError::aws_error(
3042 StatusCode::BAD_REQUEST,
3043 "CustomKeyStoreNotFoundException",
3044 format!("Custom key store '{store_id}' does not exist"),
3045 )
3046 })?;
3047
3048 store.connection_state = "CONNECTED".to_string();
3049
3050 Ok(AwsResponse::json(StatusCode::OK, "{}"))
3051 }
3052
3053 fn disconnect_custom_key_store(
3054 &self,
3055 req: &AwsRequest,
3056 ) -> Result<AwsResponse, AwsServiceError> {
3057 let body = req.json_body();
3058
3059 let store_id = body["CustomKeyStoreId"]
3060 .as_str()
3061 .ok_or_else(|| {
3062 AwsServiceError::aws_error(
3063 StatusCode::BAD_REQUEST,
3064 "ValidationException",
3065 "CustomKeyStoreId is required",
3066 )
3067 })?
3068 .to_string();
3069
3070 let mut state = self.state.write();
3071
3072 let store = state.custom_key_stores.get_mut(&store_id).ok_or_else(|| {
3073 AwsServiceError::aws_error(
3074 StatusCode::BAD_REQUEST,
3075 "CustomKeyStoreNotFoundException",
3076 format!("Custom key store '{store_id}' does not exist"),
3077 )
3078 })?;
3079
3080 store.connection_state = "DISCONNECTED".to_string();
3081
3082 Ok(AwsResponse::json(StatusCode::OK, "{}"))
3083 }
3084
3085 fn update_custom_key_store(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
3086 let body = req.json_body();
3087
3088 let store_id = body["CustomKeyStoreId"]
3089 .as_str()
3090 .ok_or_else(|| {
3091 AwsServiceError::aws_error(
3092 StatusCode::BAD_REQUEST,
3093 "ValidationException",
3094 "CustomKeyStoreId is required",
3095 )
3096 })?
3097 .to_string();
3098
3099 let mut state = self.state.write();
3100
3101 if let Some(new_name) = body["NewCustomKeyStoreName"].as_str() {
3103 if state
3104 .custom_key_stores
3105 .values()
3106 .any(|s| s.custom_key_store_name == new_name && s.custom_key_store_id != store_id)
3107 {
3108 return Err(AwsServiceError::aws_error(
3109 StatusCode::BAD_REQUEST,
3110 "CustomKeyStoreNameInUseException",
3111 format!("Custom key store name '{new_name}' is already in use"),
3112 ));
3113 }
3114 }
3115
3116 let store = state.custom_key_stores.get_mut(&store_id).ok_or_else(|| {
3117 AwsServiceError::aws_error(
3118 StatusCode::BAD_REQUEST,
3119 "CustomKeyStoreNotFoundException",
3120 format!("Custom key store '{store_id}' does not exist"),
3121 )
3122 })?;
3123
3124 if let Some(new_name) = body["NewCustomKeyStoreName"].as_str() {
3125 store.custom_key_store_name = new_name.to_string();
3126 }
3127 if let Some(v) = body["CloudHsmClusterId"].as_str() {
3128 store.cloud_hsm_cluster_id = Some(v.to_string());
3129 }
3130 if let Some(v) = body["KeyStorePassword"].as_str() {
3131 let _ = v;
3134 }
3135 if let Some(v) = body["XksProxyUriEndpoint"].as_str() {
3136 store.xks_proxy_uri_endpoint = Some(v.to_string());
3137 }
3138 if let Some(v) = body["XksProxyUriPath"].as_str() {
3139 store.xks_proxy_uri_path = Some(v.to_string());
3140 }
3141 if let Some(v) = body["XksProxyVpcEndpointServiceName"].as_str() {
3142 store.xks_proxy_vpc_endpoint_service_name = Some(v.to_string());
3143 }
3144 if let Some(v) = body["XksProxyConnectivity"].as_str() {
3145 store.xks_proxy_connectivity = Some(v.to_string());
3146 }
3147
3148 Ok(AwsResponse::json(StatusCode::OK, "{}"))
3149 }
3150}
3151
3152fn custom_key_store_json(store: &CustomKeyStore) -> Value {
3153 let mut obj = json!({
3154 "CustomKeyStoreId": store.custom_key_store_id,
3155 "CustomKeyStoreName": store.custom_key_store_name,
3156 "CustomKeyStoreType": store.custom_key_store_type,
3157 "ConnectionState": store.connection_state,
3158 "CreationDate": store.creation_date,
3159 });
3160 if let Some(ref v) = store.cloud_hsm_cluster_id {
3161 obj["CloudHsmClusterId"] = json!(v);
3162 }
3163 if let Some(ref v) = store.trust_anchor_certificate {
3164 obj["TrustAnchorCertificate"] = json!(v);
3165 }
3166 if let Some(ref v) = store.xks_proxy_uri_endpoint {
3167 obj["XksProxyConfiguration"] = json!({});
3168 obj["XksProxyConfiguration"]["UriEndpoint"] = json!(v);
3169 if let Some(ref p) = store.xks_proxy_uri_path {
3170 obj["XksProxyConfiguration"]["UriPath"] = json!(p);
3171 }
3172 if let Some(ref c) = store.xks_proxy_connectivity {
3173 obj["XksProxyConfiguration"]["Connectivity"] = json!(c);
3174 }
3175 if let Some(ref s) = store.xks_proxy_vpc_endpoint_service_name {
3176 obj["XksProxyConfiguration"]["VpcEndpointServiceName"] = json!(s);
3177 }
3178 }
3179 obj
3180}
3181
3182fn key_metadata_json(key: &KmsKey, account_id: &str) -> Value {
3183 let mut meta = json!({
3184 "KeyId": key.key_id,
3185 "Arn": key.arn,
3186 "AWSAccountId": account_id,
3187 "CreationDate": key.creation_date,
3188 "Description": key.description,
3189 "Enabled": key.enabled,
3190 "KeyUsage": key.key_usage,
3191 "KeySpec": key.key_spec,
3192 "CustomerMasterKeySpec": key.key_spec,
3193 "KeyManager": key.key_manager,
3194 "KeyState": key.key_state,
3195 "Origin": key.origin,
3196 "MultiRegion": key.multi_region,
3197 });
3198
3199 if let Some(ref enc_algs) = key.encryption_algorithms {
3200 meta["EncryptionAlgorithms"] = json!(enc_algs);
3201 }
3202 if let Some(ref sig_algs) = key.signing_algorithms {
3203 meta["SigningAlgorithms"] = json!(sig_algs);
3204 }
3205 if let Some(ref mac_algs) = key.mac_algorithms {
3206 meta["MacAlgorithms"] = json!(mac_algs);
3207 }
3208 if let Some(dd) = key.deletion_date {
3209 meta["DeletionDate"] = json!(dd);
3210 }
3211 if let Some(ref cks_id) = key.custom_key_store_id {
3212 meta["CustomKeyStoreId"] = json!(cks_id);
3213 }
3214
3215 if key.multi_region {
3216 meta["MultiRegionConfiguration"] = json!({
3218 "MultiRegionKeyType": "PRIMARY",
3219 "PrimaryKey": {
3220 "Arn": key.arn,
3221 "Region": key.arn.split(':').nth(3).unwrap_or("us-east-1"),
3222 },
3223 "ReplicaKeys": [],
3224 });
3225 }
3226
3227 meta
3228}
3229
3230fn fmt_enum_set(items: &[String]) -> String {
3231 let inner: Vec<String> = items.iter().map(|s| format!("'{s}'")).collect();
3232 format!("[{}]", inner.join(", "))
3233}
3234
3235fn grant_to_json(grant: &KmsGrant) -> Value {
3236 let mut v = json!({
3237 "KeyId": grant.key_id,
3238 "GrantId": grant.grant_id,
3239 "GranteePrincipal": grant.grantee_principal,
3240 "Operations": grant.operations,
3241 "IssuingAccount": format!("arn:aws:iam::root"),
3242 "CreationDate": grant.creation_date,
3243 });
3244
3245 if let Some(ref rp) = grant.retiring_principal {
3246 v["RetiringPrincipal"] = json!(rp);
3247 }
3248 if let Some(ref c) = grant.constraints {
3249 v["Constraints"] = c.clone();
3250 }
3251 if let Some(ref n) = grant.name {
3252 v["Name"] = json!(n);
3253 }
3254
3255 v
3256}
3257
3258fn data_key_size_from_body(body: &Value) -> Result<usize, AwsServiceError> {
3259 let key_spec = body["KeySpec"].as_str();
3260 let number_of_bytes = body["NumberOfBytes"].as_u64();
3261
3262 match (key_spec, number_of_bytes) {
3263 (Some(_), Some(_)) => Err(AwsServiceError::aws_error(
3264 StatusCode::BAD_REQUEST,
3265 "ValidationException",
3266 "KeySpec and NumberOfBytes are mutually exclusive",
3267 )),
3268 (Some("AES_256"), None) => Ok(32),
3269 (Some("AES_128"), None) => Ok(16),
3270 (Some(spec), None) => Err(AwsServiceError::aws_error(
3271 StatusCode::BAD_REQUEST,
3272 "ValidationException",
3273 format!("1 validation error detected: Value '{spec}' at 'keySpec' failed to satisfy constraint: Member must satisfy enum value set: [AES_256, AES_128]"),
3274 )),
3275 (None, Some(n)) => {
3276 if n > 1024 {
3277 Err(AwsServiceError::aws_error(
3278 StatusCode::BAD_REQUEST,
3279 "ValidationException",
3280 format!("1 validation error detected: Value '{n}' at 'numberOfBytes' failed to satisfy constraint: Member must have value less than or equal to 1024"),
3281 ))
3282 } else {
3283 Ok(n as usize)
3284 }
3285 }
3286 (None, None) => Err(AwsServiceError::aws_error(
3287 StatusCode::BAD_REQUEST,
3288 "ValidationException",
3289 "KeySpec or NumberOfBytes is required",
3290 )),
3291 }
3292}
3293
3294fn generate_fake_public_key(key_spec: &str) -> Vec<u8> {
3295 match key_spec {
3298 "RSA_2048" | "RSA_3072" | "RSA_4096" => {
3299 let mut key = vec![
3301 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
3304 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, ];
3311 key.push(0x00);
3313 key.extend_from_slice(&rand_bytes(256));
3314 key.extend_from_slice(&[0x02, 0x03, 0x01, 0x00, 0x01]); key
3317 }
3318 "ECC_NIST_P256" | "ECC_SECG_P256K1" => {
3319 let mut key = vec![
3321 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, ];
3329 key.extend_from_slice(&rand_bytes(64)); key
3331 }
3332 "ECC_NIST_P384" => {
3333 let mut key = vec![
3334 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, ];
3342 key.extend_from_slice(&rand_bytes(96)); key
3344 }
3345 "ECC_NIST_P521" => {
3346 let mut key = vec![
3347 0x30, 0x81, 0x9b, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x23, 0x03, 0x81, 0x86, 0x00, 0x04, ];
3355 key.extend_from_slice(&rand_bytes(132)); key
3357 }
3358 _ => rand_bytes(32),
3359 }
3360}
3361
3362fn check_policy_deny(key: &KmsKey, action: &str) -> Result<(), AwsServiceError> {
3363 let policy: Value = match serde_json::from_str(&key.policy) {
3365 Ok(v) => v,
3366 Err(_) => return Ok(()), };
3368
3369 let statements = match policy["Statement"].as_array() {
3370 Some(s) => s,
3371 None => return Ok(()),
3372 };
3373
3374 for stmt in statements {
3375 let effect = stmt["Effect"].as_str().unwrap_or("");
3376 if !effect.eq_ignore_ascii_case("deny") {
3377 continue;
3378 }
3379
3380 let resource = &stmt["Resource"];
3382 let resource_matches = if let Some(r) = resource.as_str() {
3383 r == "*"
3384 } else if let Some(arr) = resource.as_array() {
3385 arr.iter().any(|r| r.as_str() == Some("*"))
3386 } else {
3387 false
3388 };
3389
3390 if !resource_matches {
3391 continue;
3392 }
3393
3394 let actions = if let Some(a) = stmt["Action"].as_str() {
3396 vec![a.to_string()]
3397 } else if let Some(arr) = stmt["Action"].as_array() {
3398 arr.iter()
3399 .filter_map(|a| a.as_str().map(|s| s.to_string()))
3400 .collect()
3401 } else {
3402 continue;
3403 };
3404
3405 for policy_action in &actions {
3406 if action_matches(policy_action, action) {
3407 return Err(AwsServiceError::aws_error(
3408 StatusCode::BAD_REQUEST,
3409 "AccessDeniedException",
3410 format!(
3411 "User is not authorized to perform: {} on resource: {}",
3412 action, key.arn
3413 ),
3414 ));
3415 }
3416 }
3417 }
3418
3419 Ok(())
3420}
3421
3422fn action_matches(policy_action: &str, requested_action: &str) -> bool {
3423 if policy_action == "kms:*" {
3424 return true;
3425 }
3426 if policy_action == requested_action {
3427 return true;
3428 }
3429 if let Some(prefix) = policy_action.strip_suffix('*') {
3431 if requested_action.starts_with(prefix) {
3432 return true;
3433 }
3434 }
3435 false
3436}
3437
3438#[cfg(test)]
3439mod tests {
3440 use super::*;
3441 use parking_lot::RwLock;
3442 use serde_json::json;
3443 use std::collections::HashMap;
3444 use std::sync::Arc;
3445
3446 fn make_service() -> KmsService {
3447 let state: SharedKmsState = Arc::new(RwLock::new(crate::state::KmsState::new(
3448 "123456789012",
3449 "us-east-1",
3450 )));
3451 KmsService::new(state)
3452 }
3453
3454 fn make_request(action: &str, body: Value) -> AwsRequest {
3455 AwsRequest {
3456 service: "kms".to_string(),
3457 action: action.to_string(),
3458 region: "us-east-1".to_string(),
3459 account_id: "123456789012".to_string(),
3460 request_id: "test-id".to_string(),
3461 headers: http::HeaderMap::new(),
3462 query_params: HashMap::new(),
3463 body: serde_json::to_vec(&body).unwrap().into(),
3464 path_segments: vec![],
3465 raw_path: "/".to_string(),
3466 raw_query: String::new(),
3467 method: http::Method::POST,
3468 is_query_protocol: false,
3469 access_key_id: None,
3470 }
3471 }
3472
3473 fn create_key(svc: &KmsService) -> String {
3474 let req = make_request("CreateKey", json!({}));
3475 let resp = svc.create_key(&req).unwrap();
3476 let body: Value = serde_json::from_slice(&resp.body).unwrap();
3477 body["KeyMetadata"]["KeyId"].as_str().unwrap().to_string()
3478 }
3479
3480 #[test]
3481 fn list_keys_pagination_no_duplicates() {
3482 let svc = make_service();
3483 let mut all_key_ids: Vec<String> = Vec::new();
3484 for _ in 0..5 {
3485 all_key_ids.push(create_key(&svc));
3486 }
3487
3488 let mut collected_ids: Vec<String> = Vec::new();
3489 let mut marker: Option<String> = None;
3490
3491 loop {
3492 let mut body = json!({ "Limit": 2 });
3493 if let Some(ref m) = marker {
3494 body["Marker"] = json!(m);
3495 }
3496 let req = make_request("ListKeys", body);
3497 let resp = svc.list_keys(&req).unwrap();
3498 let resp_body: Value = serde_json::from_slice(&resp.body).unwrap();
3499
3500 for key in resp_body["Keys"].as_array().unwrap() {
3501 collected_ids.push(key["KeyId"].as_str().unwrap().to_string());
3502 }
3503
3504 if resp_body["Truncated"].as_bool().unwrap_or(false) {
3505 marker = resp_body["NextMarker"].as_str().map(|s| s.to_string());
3506 } else {
3507 break;
3508 }
3509 }
3510
3511 let mut deduped = collected_ids.clone();
3513 deduped.sort();
3514 deduped.dedup();
3515 assert_eq!(
3516 collected_ids.len(),
3517 deduped.len(),
3518 "pagination produced duplicate keys"
3519 );
3520
3521 for kid in &all_key_ids {
3523 assert!(
3524 collected_ids.contains(kid),
3525 "key {kid} missing from paginated results"
3526 );
3527 }
3528 }
3529
3530 #[test]
3531 fn list_retirable_grants_pagination() {
3532 let svc = make_service();
3533 let key_id = create_key(&svc);
3534 let retiring = "arn:aws:iam::123456789012:user/retiring-user";
3535
3536 for i in 0..5 {
3538 let req = make_request(
3539 "CreateGrant",
3540 json!({
3541 "KeyId": key_id,
3542 "GranteePrincipal": format!("arn:aws:iam::123456789012:user/grantee-{i}"),
3543 "RetiringPrincipal": retiring,
3544 "Operations": ["Encrypt"]
3545 }),
3546 );
3547 svc.create_grant(&req).unwrap();
3548 }
3549
3550 let mut collected_ids: Vec<String> = Vec::new();
3551 let mut marker: Option<String> = None;
3552
3553 loop {
3554 let mut body = json!({
3555 "RetiringPrincipal": retiring,
3556 "Limit": 2
3557 });
3558 if let Some(ref m) = marker {
3559 body["Marker"] = json!(m);
3560 }
3561 let req = make_request("ListRetirableGrants", body);
3562 let resp = svc.list_retirable_grants(&req).unwrap();
3563 let resp_body: Value = serde_json::from_slice(&resp.body).unwrap();
3564
3565 for grant in resp_body["Grants"].as_array().unwrap() {
3566 collected_ids.push(grant["GrantId"].as_str().unwrap().to_string());
3567 }
3568
3569 if resp_body["Truncated"].as_bool().unwrap_or(false) {
3570 marker = resp_body["NextMarker"].as_str().map(|s| s.to_string());
3571 } else {
3572 break;
3573 }
3574 }
3575
3576 let mut deduped = collected_ids.clone();
3578 deduped.sort();
3579 deduped.dedup();
3580 assert_eq!(
3581 collected_ids.len(),
3582 deduped.len(),
3583 "pagination produced duplicate grants"
3584 );
3585
3586 assert_eq!(collected_ids.len(), 5, "expected 5 grants total");
3588 }
3589
3590 fn create_key_with_opts(svc: &KmsService, body: Value) -> String {
3591 let req = make_request("CreateKey", body);
3592 let resp = svc.create_key(&req).unwrap();
3593 let body: Value = serde_json::from_slice(&resp.body).unwrap();
3594 body["KeyMetadata"]["KeyId"].as_str().unwrap().to_string()
3595 }
3596
3597 #[test]
3598 fn generate_data_key_pair_returns_all_fields() {
3599 let svc = make_service();
3600 let key_id = create_key(&svc);
3601
3602 let req = make_request(
3603 "GenerateDataKeyPair",
3604 json!({ "KeyId": key_id, "KeyPairSpec": "RSA_2048" }),
3605 );
3606 let resp = svc.generate_data_key_pair(&req).unwrap();
3607 let body: Value = serde_json::from_slice(&resp.body).unwrap();
3608
3609 assert!(body["PublicKey"].as_str().is_some());
3610 assert!(body["PrivateKeyPlaintext"].as_str().is_some());
3611 assert!(body["PrivateKeyCiphertextBlob"].as_str().is_some());
3612 assert_eq!(body["KeyPairSpec"].as_str().unwrap(), "RSA_2048");
3613 assert!(body["KeyId"].as_str().unwrap().contains(":key/"));
3614 }
3615
3616 #[test]
3617 fn generate_data_key_pair_disabled_key_fails() {
3618 let svc = make_service();
3619 let key_id = create_key(&svc);
3620
3621 let disable_req = make_request("DisableKey", json!({ "KeyId": key_id }));
3622 svc.disable_key(&disable_req).unwrap();
3623
3624 let req = make_request(
3625 "GenerateDataKeyPair",
3626 json!({ "KeyId": key_id, "KeyPairSpec": "RSA_2048" }),
3627 );
3628 assert!(svc.generate_data_key_pair(&req).is_err());
3629 }
3630
3631 #[test]
3632 fn generate_data_key_pair_without_plaintext_omits_private_plaintext() {
3633 let svc = make_service();
3634 let key_id = create_key(&svc);
3635
3636 let req = make_request(
3637 "GenerateDataKeyPairWithoutPlaintext",
3638 json!({ "KeyId": key_id, "KeyPairSpec": "ECC_NIST_P256" }),
3639 );
3640 let resp = svc.generate_data_key_pair_without_plaintext(&req).unwrap();
3641 let body: Value = serde_json::from_slice(&resp.body).unwrap();
3642
3643 assert!(body["PublicKey"].as_str().is_some());
3644 assert!(body["PrivateKeyCiphertextBlob"].as_str().is_some());
3645 assert!(body.get("PrivateKeyPlaintext").is_none());
3646 assert_eq!(body["KeyPairSpec"].as_str().unwrap(), "ECC_NIST_P256");
3647 }
3648
3649 #[test]
3650 fn derive_shared_secret_success() {
3651 let svc = make_service();
3652 let key_id = create_key_with_opts(
3653 &svc,
3654 json!({
3655 "KeyUsage": "KEY_AGREEMENT",
3656 "KeySpec": "ECC_NIST_P256"
3657 }),
3658 );
3659
3660 let fake_pub = base64::engine::general_purpose::STANDARD.encode(b"fake-public-key");
3661 let req = make_request(
3662 "DeriveSharedSecret",
3663 json!({
3664 "KeyId": key_id,
3665 "KeyAgreementAlgorithm": "ECDH",
3666 "PublicKey": fake_pub
3667 }),
3668 );
3669 let resp = svc.derive_shared_secret(&req).unwrap();
3670 let body: Value = serde_json::from_slice(&resp.body).unwrap();
3671
3672 assert!(body["SharedSecret"].as_str().is_some());
3673 assert!(body["KeyId"].as_str().unwrap().contains(":key/"));
3674 assert_eq!(body["KeyAgreementAlgorithm"].as_str().unwrap(), "ECDH");
3675 }
3676
3677 #[test]
3678 fn derive_shared_secret_wrong_usage_fails() {
3679 let svc = make_service();
3680 let key_id = create_key(&svc); let fake_pub = base64::engine::general_purpose::STANDARD.encode(b"fake-public-key");
3683 let req = make_request(
3684 "DeriveSharedSecret",
3685 json!({
3686 "KeyId": key_id,
3687 "KeyAgreementAlgorithm": "ECDH",
3688 "PublicKey": fake_pub
3689 }),
3690 );
3691 assert!(svc.derive_shared_secret(&req).is_err());
3692 }
3693
3694 #[test]
3695 fn get_parameters_for_import_success() {
3696 let svc = make_service();
3697 let key_id = create_key_with_opts(&svc, json!({ "Origin": "EXTERNAL" }));
3698
3699 let req = make_request(
3700 "GetParametersForImport",
3701 json!({ "KeyId": key_id, "WrappingAlgorithm": "RSAES_OAEP_SHA_256", "WrappingKeySpec": "RSA_2048" }),
3702 );
3703 let resp = svc.get_parameters_for_import(&req).unwrap();
3704 let body: Value = serde_json::from_slice(&resp.body).unwrap();
3705
3706 assert!(body["ImportToken"].as_str().is_some());
3707 assert!(body["PublicKey"].as_str().is_some());
3708 assert!(body["ParametersValidTo"].as_f64().is_some());
3709 assert!(body["KeyId"].as_str().unwrap().contains(":key/"));
3710 }
3711
3712 #[test]
3713 fn get_parameters_for_import_non_external_fails() {
3714 let svc = make_service();
3715 let key_id = create_key(&svc); let req = make_request("GetParametersForImport", json!({ "KeyId": key_id }));
3718 assert!(svc.get_parameters_for_import(&req).is_err());
3719 }
3720
3721 #[test]
3722 fn import_key_material_lifecycle() {
3723 let svc = make_service();
3724 let key_id = create_key_with_opts(&svc, json!({ "Origin": "EXTERNAL" }));
3725
3726 let fake_token = base64::engine::general_purpose::STANDARD.encode(b"token");
3727 let fake_material = base64::engine::general_purpose::STANDARD.encode(b"material");
3728
3729 let req = make_request(
3731 "ImportKeyMaterial",
3732 json!({
3733 "KeyId": key_id,
3734 "ImportToken": fake_token,
3735 "EncryptedKeyMaterial": fake_material,
3736 "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE"
3737 }),
3738 );
3739 svc.import_key_material(&req).unwrap();
3740
3741 {
3743 let state = svc.state.read();
3744 let key = state.keys.get(&key_id).unwrap();
3745 assert!(key.imported_key_material);
3746 assert!(key.enabled);
3747 }
3748
3749 let req = make_request("DeleteImportedKeyMaterial", json!({ "KeyId": key_id }));
3751 svc.delete_imported_key_material(&req).unwrap();
3752
3753 {
3755 let state = svc.state.read();
3756 let key = state.keys.get(&key_id).unwrap();
3757 assert!(!key.imported_key_material);
3758 assert!(!key.enabled);
3759 assert_eq!(key.key_state, "PendingImport");
3760 }
3761 }
3762
3763 #[test]
3764 fn import_key_material_non_external_fails() {
3765 let svc = make_service();
3766 let key_id = create_key(&svc);
3767
3768 let fake_token = base64::engine::general_purpose::STANDARD.encode(b"token");
3769 let fake_material = base64::engine::general_purpose::STANDARD.encode(b"material");
3770
3771 let req = make_request(
3772 "ImportKeyMaterial",
3773 json!({
3774 "KeyId": key_id,
3775 "ImportToken": fake_token,
3776 "EncryptedKeyMaterial": fake_material
3777 }),
3778 );
3779 assert!(svc.import_key_material(&req).is_err());
3780 }
3781
3782 #[test]
3783 fn delete_imported_key_material_non_external_fails() {
3784 let svc = make_service();
3785 let key_id = create_key(&svc);
3786
3787 let req = make_request("DeleteImportedKeyMaterial", json!({ "KeyId": key_id }));
3788 assert!(svc.delete_imported_key_material(&req).is_err());
3789 }
3790
3791 #[test]
3792 fn update_primary_region_success() {
3793 let svc = make_service();
3794 let key_id = create_key_with_opts(&svc, json!({ "MultiRegion": true }));
3795
3796 let req = make_request(
3797 "UpdatePrimaryRegion",
3798 json!({ "KeyId": key_id, "PrimaryRegion": "eu-west-1" }),
3799 );
3800 svc.update_primary_region(&req).unwrap();
3801
3802 let state = svc.state.read();
3803 let key = state.keys.get(&key_id).unwrap();
3804 assert_eq!(key.primary_region.as_deref(), Some("eu-west-1"));
3805 assert!(key.arn.contains("eu-west-1"));
3806 }
3807
3808 #[test]
3809 fn update_primary_region_non_multi_region_fails() {
3810 let svc = make_service();
3811 let key_id = create_key(&svc); let req = make_request(
3814 "UpdatePrimaryRegion",
3815 json!({ "KeyId": key_id, "PrimaryRegion": "eu-west-1" }),
3816 );
3817 assert!(svc.update_primary_region(&req).is_err());
3818 }
3819
3820 #[test]
3821 fn custom_key_store_lifecycle() {
3822 let svc = make_service();
3823
3824 let req = make_request(
3826 "CreateCustomKeyStore",
3827 json!({
3828 "CustomKeyStoreName": "my-store",
3829 "CloudHsmClusterId": "cluster-1234",
3830 "TrustAnchorCertificate": "cert-data",
3831 "KeyStorePassword": "password123"
3832 }),
3833 );
3834 let resp = svc.create_custom_key_store(&req).unwrap();
3835 let body: Value = serde_json::from_slice(&resp.body).unwrap();
3836 let store_id = body["CustomKeyStoreId"].as_str().unwrap().to_string();
3837 assert!(store_id.starts_with("cks-"));
3838
3839 let req = make_request(
3841 "DescribeCustomKeyStores",
3842 json!({ "CustomKeyStoreId": store_id }),
3843 );
3844 let resp = svc.describe_custom_key_stores(&req).unwrap();
3845 let body: Value = serde_json::from_slice(&resp.body).unwrap();
3846 let stores = body["CustomKeyStores"].as_array().unwrap();
3847 assert_eq!(stores.len(), 1);
3848 assert_eq!(
3849 stores[0]["CustomKeyStoreName"].as_str().unwrap(),
3850 "my-store"
3851 );
3852 assert_eq!(
3853 stores[0]["ConnectionState"].as_str().unwrap(),
3854 "DISCONNECTED"
3855 );
3856 assert_eq!(
3857 stores[0]["CloudHsmClusterId"].as_str().unwrap(),
3858 "cluster-1234"
3859 );
3860
3861 let req = make_request(
3863 "ConnectCustomKeyStore",
3864 json!({ "CustomKeyStoreId": store_id }),
3865 );
3866 svc.connect_custom_key_store(&req).unwrap();
3867
3868 let req = make_request(
3870 "DescribeCustomKeyStores",
3871 json!({ "CustomKeyStoreId": store_id }),
3872 );
3873 let resp = svc.describe_custom_key_stores(&req).unwrap();
3874 let body: Value = serde_json::from_slice(&resp.body).unwrap();
3875 assert_eq!(
3876 body["CustomKeyStores"][0]["ConnectionState"]
3877 .as_str()
3878 .unwrap(),
3879 "CONNECTED"
3880 );
3881
3882 let req = make_request(
3884 "DeleteCustomKeyStore",
3885 json!({ "CustomKeyStoreId": store_id }),
3886 );
3887 assert!(svc.delete_custom_key_store(&req).is_err());
3888
3889 let req = make_request(
3891 "DisconnectCustomKeyStore",
3892 json!({ "CustomKeyStoreId": store_id }),
3893 );
3894 svc.disconnect_custom_key_store(&req).unwrap();
3895
3896 let req = make_request(
3898 "UpdateCustomKeyStore",
3899 json!({
3900 "CustomKeyStoreId": store_id,
3901 "NewCustomKeyStoreName": "renamed-store"
3902 }),
3903 );
3904 svc.update_custom_key_store(&req).unwrap();
3905
3906 let req = make_request(
3908 "DescribeCustomKeyStores",
3909 json!({ "CustomKeyStoreId": store_id }),
3910 );
3911 let resp = svc.describe_custom_key_stores(&req).unwrap();
3912 let body: Value = serde_json::from_slice(&resp.body).unwrap();
3913 assert_eq!(
3914 body["CustomKeyStores"][0]["CustomKeyStoreName"]
3915 .as_str()
3916 .unwrap(),
3917 "renamed-store"
3918 );
3919
3920 let req = make_request(
3922 "DeleteCustomKeyStore",
3923 json!({ "CustomKeyStoreId": store_id }),
3924 );
3925 svc.delete_custom_key_store(&req).unwrap();
3926
3927 let req = make_request("DescribeCustomKeyStores", json!({}));
3929 let resp = svc.describe_custom_key_stores(&req).unwrap();
3930 let body: Value = serde_json::from_slice(&resp.body).unwrap();
3931 assert!(body["CustomKeyStores"].as_array().unwrap().is_empty());
3932 }
3933
3934 #[test]
3935 fn custom_key_store_duplicate_name_fails() {
3936 let svc = make_service();
3937
3938 let req = make_request(
3939 "CreateCustomKeyStore",
3940 json!({ "CustomKeyStoreName": "dup-store" }),
3941 );
3942 svc.create_custom_key_store(&req).unwrap();
3943
3944 let req = make_request(
3945 "CreateCustomKeyStore",
3946 json!({ "CustomKeyStoreName": "dup-store" }),
3947 );
3948 assert!(svc.create_custom_key_store(&req).is_err());
3949 }
3950
3951 #[test]
3952 fn describe_custom_key_store_not_found() {
3953 let svc = make_service();
3954
3955 let req = make_request(
3956 "DescribeCustomKeyStores",
3957 json!({ "CustomKeyStoreId": "cks-nonexistent" }),
3958 );
3959 assert!(svc.describe_custom_key_stores(&req).is_err());
3960 }
3961
3962 #[test]
3963 fn delete_nonexistent_custom_key_store_fails() {
3964 let svc = make_service();
3965
3966 let req = make_request(
3967 "DeleteCustomKeyStore",
3968 json!({ "CustomKeyStoreId": "cks-nonexistent" }),
3969 );
3970 assert!(svc.delete_custom_key_store(&req).is_err());
3971 }
3972
3973 #[test]
3974 fn connect_nonexistent_custom_key_store_fails() {
3975 let svc = make_service();
3976
3977 let req = make_request(
3978 "ConnectCustomKeyStore",
3979 json!({ "CustomKeyStoreId": "cks-nonexistent" }),
3980 );
3981 assert!(svc.connect_custom_key_store(&req).is_err());
3982 }
3983
3984 #[test]
3985 fn describe_custom_key_stores_by_name() {
3986 let svc = make_service();
3987
3988 let req = make_request(
3989 "CreateCustomKeyStore",
3990 json!({ "CustomKeyStoreName": "store-a" }),
3991 );
3992 svc.create_custom_key_store(&req).unwrap();
3993
3994 let req = make_request(
3995 "CreateCustomKeyStore",
3996 json!({ "CustomKeyStoreName": "store-b" }),
3997 );
3998 svc.create_custom_key_store(&req).unwrap();
3999
4000 let req = make_request(
4002 "DescribeCustomKeyStores",
4003 json!({ "CustomKeyStoreName": "store-a" }),
4004 );
4005 let resp = svc.describe_custom_key_stores(&req).unwrap();
4006 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4007 let stores = body["CustomKeyStores"].as_array().unwrap();
4008 assert_eq!(stores.len(), 1);
4009 assert_eq!(stores[0]["CustomKeyStoreName"].as_str().unwrap(), "store-a");
4010 }
4011
4012 #[test]
4013 fn update_custom_key_store_name_conflict() {
4014 let svc = make_service();
4015
4016 let req = make_request(
4017 "CreateCustomKeyStore",
4018 json!({ "CustomKeyStoreName": "store-x" }),
4019 );
4020 svc.create_custom_key_store(&req).unwrap();
4021
4022 let req = make_request(
4023 "CreateCustomKeyStore",
4024 json!({ "CustomKeyStoreName": "store-y" }),
4025 );
4026 let resp = svc.create_custom_key_store(&req).unwrap();
4027 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4028 let store_y_id = body["CustomKeyStoreId"].as_str().unwrap().to_string();
4029
4030 let req = make_request(
4032 "UpdateCustomKeyStore",
4033 json!({
4034 "CustomKeyStoreId": store_y_id,
4035 "NewCustomKeyStoreName": "store-x"
4036 }),
4037 );
4038 assert!(svc.update_custom_key_store(&req).is_err());
4039 }
4040
4041 #[test]
4042 fn derive_shared_secret_is_deterministic() {
4043 let svc = make_service();
4044 let key_id = create_key_with_opts(
4045 &svc,
4046 json!({
4047 "KeyUsage": "KEY_AGREEMENT",
4048 "KeySpec": "ECC_NIST_P256"
4049 }),
4050 );
4051
4052 let pub_key = base64::engine::general_purpose::STANDARD.encode(b"counterparty-public-key");
4053 let req = make_request(
4054 "DeriveSharedSecret",
4055 json!({
4056 "KeyId": key_id,
4057 "KeyAgreementAlgorithm": "ECDH",
4058 "PublicKey": pub_key
4059 }),
4060 );
4061
4062 let resp1 = svc.derive_shared_secret(&req).unwrap();
4063 let body1: Value = serde_json::from_slice(&resp1.body).unwrap();
4064 let secret1 = body1["SharedSecret"].as_str().unwrap().to_string();
4065
4066 let resp2 = svc.derive_shared_secret(&req).unwrap();
4068 let body2: Value = serde_json::from_slice(&resp2.body).unwrap();
4069 let secret2 = body2["SharedSecret"].as_str().unwrap().to_string();
4070
4071 assert_eq!(secret1, secret2, "DeriveSharedSecret must be deterministic");
4072
4073 let other_pub = base64::engine::general_purpose::STANDARD.encode(b"different-public-key");
4075 let req2 = make_request(
4076 "DeriveSharedSecret",
4077 json!({
4078 "KeyId": key_id,
4079 "KeyAgreementAlgorithm": "ECDH",
4080 "PublicKey": other_pub
4081 }),
4082 );
4083 let resp3 = svc.derive_shared_secret(&req2).unwrap();
4084 let body3: Value = serde_json::from_slice(&resp3.body).unwrap();
4085 let secret3 = body3["SharedSecret"].as_str().unwrap().to_string();
4086 assert_ne!(
4087 secret1, secret3,
4088 "Different public keys must yield different shared secrets"
4089 );
4090 }
4091
4092 #[test]
4093 fn imported_key_material_encrypt_decrypt_roundtrip() {
4094 let svc = make_service();
4095 let key_id = create_key_with_opts(&svc, json!({ "Origin": "EXTERNAL" }));
4096
4097 let fake_token = base64::engine::general_purpose::STANDARD.encode(b"token");
4098 let material = b"my-secret-aes-key-material!12345";
4099 let fake_material = base64::engine::general_purpose::STANDARD.encode(material);
4100
4101 let req = make_request(
4103 "ImportKeyMaterial",
4104 json!({
4105 "KeyId": key_id,
4106 "ImportToken": fake_token,
4107 "EncryptedKeyMaterial": fake_material,
4108 }),
4109 );
4110 svc.import_key_material(&req).unwrap();
4111
4112 let plaintext = b"Hello imported key!";
4114 let plaintext_b64 = base64::engine::general_purpose::STANDARD.encode(plaintext);
4115 let req = make_request(
4116 "Encrypt",
4117 json!({ "KeyId": key_id, "Plaintext": plaintext_b64 }),
4118 );
4119 let resp = svc.encrypt(&req).unwrap();
4120 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4121 let ciphertext = body["CiphertextBlob"].as_str().unwrap().to_string();
4122
4123 let ct_bytes = base64::engine::general_purpose::STANDARD
4125 .decode(&ciphertext)
4126 .unwrap();
4127 let envelope = String::from_utf8(ct_bytes).unwrap();
4128 assert!(
4129 envelope.starts_with("fakecloud-imported:"),
4130 "Imported key should use fakecloud-imported envelope"
4131 );
4132
4133 let req = make_request("Decrypt", json!({ "CiphertextBlob": ciphertext }));
4135 let resp = svc.decrypt(&req).unwrap();
4136 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4137 let decrypted_b64 = body["Plaintext"].as_str().unwrap();
4138 let decrypted = base64::engine::general_purpose::STANDARD
4139 .decode(decrypted_b64)
4140 .unwrap();
4141 assert_eq!(
4142 decrypted, plaintext,
4143 "Decrypt must recover the original plaintext"
4144 );
4145 }
4146
4147 #[test]
4148 fn imported_key_material_decrypt_fails_after_deletion() {
4149 let svc = make_service();
4150 let key_id = create_key_with_opts(&svc, json!({ "Origin": "EXTERNAL" }));
4151
4152 let fake_token = base64::engine::general_purpose::STANDARD.encode(b"token");
4153 let fake_material =
4154 base64::engine::general_purpose::STANDARD.encode(b"some-key-material-32bytes!!");
4155
4156 svc.import_key_material(&make_request(
4158 "ImportKeyMaterial",
4159 json!({
4160 "KeyId": key_id,
4161 "ImportToken": fake_token,
4162 "EncryptedKeyMaterial": fake_material,
4163 }),
4164 ))
4165 .unwrap();
4166
4167 let plaintext_b64 = base64::engine::general_purpose::STANDARD.encode(b"secret");
4168 let resp = svc
4169 .encrypt(&make_request(
4170 "Encrypt",
4171 json!({ "KeyId": key_id, "Plaintext": plaintext_b64 }),
4172 ))
4173 .unwrap();
4174 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4175 let ciphertext = body["CiphertextBlob"].as_str().unwrap().to_string();
4176
4177 svc.delete_imported_key_material(&make_request(
4179 "DeleteImportedKeyMaterial",
4180 json!({ "KeyId": key_id }),
4181 ))
4182 .unwrap();
4183
4184 let result = svc.decrypt(&make_request(
4187 "Decrypt",
4188 json!({ "CiphertextBlob": ciphertext }),
4189 ));
4190 assert!(
4191 result.is_err(),
4192 "Decrypt should fail after key material deletion"
4193 );
4194 }
4195
4196 #[test]
4197 fn list_keys_rejects_non_integer_limit() {
4198 let svc = make_service();
4199 let req = make_request("ListKeys", json!({ "Limit": "abc" }));
4201 let result = svc.list_keys(&req);
4202 assert!(result.is_err(), "non-integer Limit should be rejected");
4203 }
4204
4205 #[test]
4206 fn list_keys_rejects_large_unsigned_limit() {
4207 let svc = make_service();
4208 let req = make_request("ListKeys", json!({ "Limit": u64::MAX }));
4210 let result = svc.list_keys(&req);
4211 assert!(result.is_err(), "large unsigned Limit should be rejected");
4212 }
4213
4214 #[test]
4215 fn list_keys_rejects_out_of_range_limit() {
4216 let svc = make_service();
4217 let req = make_request("ListKeys", json!({ "Limit": 0 }));
4218 let result = svc.list_keys(&req);
4219 assert!(result.is_err(), "Limit=0 should be rejected");
4220
4221 let req = make_request("ListKeys", json!({ "Limit": 1001 }));
4222 let result = svc.list_keys(&req);
4223 assert!(result.is_err(), "Limit=1001 should be rejected");
4224 }
4225
4226 #[test]
4227 fn enable_key_with_nonexistent_id_returns_error() {
4228 let svc = make_service();
4229 let key_id = create_key(&svc);
4232
4233 svc.state.write().keys.remove(&key_id);
4235
4236 let req = make_request("EnableKey", json!({ "KeyId": key_id }));
4237 let result = svc.enable_key(&req);
4238 assert!(result.is_err(), "Should return error for missing key");
4239 }
4240
4241 #[test]
4242 fn disable_key_with_nonexistent_id_returns_error() {
4243 let svc = make_service();
4244 let key_id = create_key(&svc);
4245 svc.state.write().keys.remove(&key_id);
4246
4247 let req = make_request("DisableKey", json!({ "KeyId": key_id }));
4248 let result = svc.disable_key(&req);
4249 assert!(result.is_err(), "Should return error for missing key");
4250 }
4251
4252 #[test]
4253 fn tag_resource_with_nonexistent_key_returns_error() {
4254 let svc = make_service();
4255 let key_id = create_key(&svc);
4256 svc.state.write().keys.remove(&key_id);
4257
4258 let req = make_request(
4259 "TagResource",
4260 json!({ "KeyId": key_id, "Tags": [{"TagKey": "k", "TagValue": "v"}] }),
4261 );
4262 let result = svc.tag_resource(&req);
4263 assert!(result.is_err(), "Should return error for missing key");
4264 }
4265
4266 #[test]
4267 fn cancel_key_deletion_re_enables_key() {
4268 let svc = make_service();
4269 let key_id = create_key(&svc);
4270
4271 let req = make_request(
4273 "ScheduleKeyDeletion",
4274 json!({ "KeyId": key_id, "PendingWindowInDays": 7 }),
4275 );
4276 let resp = svc.schedule_key_deletion(&req).unwrap();
4277 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4278 assert_eq!(body["KeyState"].as_str().unwrap(), "PendingDeletion");
4279
4280 {
4282 let state = svc.state.read();
4283 let key = state.keys.get(&key_id).unwrap();
4284 assert_eq!(key.key_state, "PendingDeletion");
4285 assert!(!key.enabled);
4286 assert!(key.deletion_date.is_some());
4287 }
4288
4289 let req = make_request("CancelKeyDeletion", json!({ "KeyId": key_id }));
4291 let resp = svc.cancel_key_deletion(&req).unwrap();
4292 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4293 assert_eq!(body["KeyId"].as_str().unwrap(), key_id);
4294
4295 {
4297 let state = svc.state.read();
4298 let key = state.keys.get(&key_id).unwrap();
4299 assert_eq!(key.key_state, "Disabled");
4300 assert!(key.deletion_date.is_none());
4301 }
4302
4303 let req = make_request("EnableKey", json!({ "KeyId": key_id }));
4305 svc.enable_key(&req).unwrap();
4306
4307 {
4308 let state = svc.state.read();
4309 let key = state.keys.get(&key_id).unwrap();
4310 assert!(key.enabled);
4311 assert_eq!(key.key_state, "Enabled");
4312 }
4313 }
4314
4315 #[test]
4316 fn key_rotation_lifecycle() {
4317 let svc = make_service();
4318 let key_id = create_key(&svc);
4319
4320 let req = make_request("GetKeyRotationStatus", json!({ "KeyId": key_id }));
4322 let resp = svc.get_key_rotation_status(&req).unwrap();
4323 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4324 assert!(!body["KeyRotationEnabled"].as_bool().unwrap());
4325
4326 let req = make_request("EnableKeyRotation", json!({ "KeyId": key_id }));
4328 svc.enable_key_rotation(&req).unwrap();
4329
4330 let req = make_request("GetKeyRotationStatus", json!({ "KeyId": key_id }));
4331 let resp = svc.get_key_rotation_status(&req).unwrap();
4332 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4333 assert!(body["KeyRotationEnabled"].as_bool().unwrap());
4334
4335 let req = make_request("DisableKeyRotation", json!({ "KeyId": key_id }));
4337 svc.disable_key_rotation(&req).unwrap();
4338
4339 let req = make_request("GetKeyRotationStatus", json!({ "KeyId": key_id }));
4340 let resp = svc.get_key_rotation_status(&req).unwrap();
4341 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4342 assert!(!body["KeyRotationEnabled"].as_bool().unwrap());
4343 }
4344
4345 #[test]
4346 fn rotate_key_on_demand_and_list_rotations() {
4347 let svc = make_service();
4348 let key_id = create_key(&svc);
4349
4350 let req = make_request("ListKeyRotations", json!({ "KeyId": key_id }));
4352 let resp = svc.list_key_rotations(&req).unwrap();
4353 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4354 assert!(body["Rotations"].as_array().unwrap().is_empty());
4355
4356 let req = make_request("RotateKeyOnDemand", json!({ "KeyId": key_id }));
4358 let resp = svc.rotate_key_on_demand(&req).unwrap();
4359 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4360 assert_eq!(body["KeyId"].as_str().unwrap(), key_id);
4361
4362 let req = make_request("RotateKeyOnDemand", json!({ "KeyId": key_id }));
4364 svc.rotate_key_on_demand(&req).unwrap();
4365
4366 let req = make_request("ListKeyRotations", json!({ "KeyId": key_id }));
4368 let resp = svc.list_key_rotations(&req).unwrap();
4369 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4370 let rotations = body["Rotations"].as_array().unwrap();
4371 assert_eq!(rotations.len(), 2);
4372 assert_eq!(rotations[0]["RotationType"].as_str().unwrap(), "ON_DEMAND");
4373 assert_eq!(rotations[0]["KeyId"].as_str().unwrap(), key_id);
4374 assert!(rotations[0]["RotationDate"].as_f64().is_some());
4375 }
4376
4377 #[test]
4378 fn key_policy_get_put_list() {
4379 let svc = make_service();
4380 let key_id = create_key(&svc);
4381
4382 let req = make_request("GetKeyPolicy", json!({ "KeyId": key_id }));
4384 let resp = svc.get_key_policy(&req).unwrap();
4385 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4386 let policy_str = body["Policy"].as_str().unwrap();
4387 assert!(policy_str.contains("Enable IAM User Permissions"));
4388
4389 let custom_policy = r#"{"Version":"2012-10-17","Statement":[]}"#;
4391 let req = make_request(
4392 "PutKeyPolicy",
4393 json!({ "KeyId": key_id, "Policy": custom_policy }),
4394 );
4395 svc.put_key_policy(&req).unwrap();
4396
4397 let req = make_request("GetKeyPolicy", json!({ "KeyId": key_id }));
4399 let resp = svc.get_key_policy(&req).unwrap();
4400 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4401 assert_eq!(body["Policy"].as_str().unwrap(), custom_policy);
4402
4403 let req = make_request("ListKeyPolicies", json!({ "KeyId": key_id }));
4405 let resp = svc.list_key_policies(&req).unwrap();
4406 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4407 let names = body["PolicyNames"].as_array().unwrap();
4408 assert_eq!(names.len(), 1);
4409 assert_eq!(names[0].as_str().unwrap(), "default");
4410 }
4411
4412 #[test]
4413 fn grant_create_list_revoke() {
4414 let svc = make_service();
4415 let key_id = create_key(&svc);
4416
4417 let req = make_request(
4419 "CreateGrant",
4420 json!({
4421 "KeyId": key_id,
4422 "GranteePrincipal": "arn:aws:iam::123456789012:user/alice",
4423 "Operations": ["Encrypt", "Decrypt"],
4424 "Name": "test-grant"
4425 }),
4426 );
4427 let resp = svc.create_grant(&req).unwrap();
4428 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4429 let grant_id = body["GrantId"].as_str().unwrap().to_string();
4430 let grant_token = body["GrantToken"].as_str().unwrap().to_string();
4431 assert!(!grant_id.is_empty());
4432 assert!(!grant_token.is_empty());
4433
4434 let req = make_request("ListGrants", json!({ "KeyId": key_id }));
4436 let resp = svc.list_grants(&req).unwrap();
4437 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4438 let grants = body["Grants"].as_array().unwrap();
4439 assert_eq!(grants.len(), 1);
4440 assert_eq!(grants[0]["GrantId"].as_str().unwrap(), grant_id);
4441 assert_eq!(
4442 grants[0]["GranteePrincipal"].as_str().unwrap(),
4443 "arn:aws:iam::123456789012:user/alice"
4444 );
4445 assert_eq!(grants[0]["Operations"].as_array().unwrap().len(), 2);
4446
4447 let req = make_request(
4449 "RevokeGrant",
4450 json!({ "KeyId": key_id, "GrantId": grant_id }),
4451 );
4452 svc.revoke_grant(&req).unwrap();
4453
4454 let req = make_request("ListGrants", json!({ "KeyId": key_id }));
4456 let resp = svc.list_grants(&req).unwrap();
4457 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4458 assert!(body["Grants"].as_array().unwrap().is_empty());
4459 }
4460
4461 #[test]
4462 fn grant_retire_by_token() {
4463 let svc = make_service();
4464 let key_id = create_key(&svc);
4465
4466 let req = make_request(
4467 "CreateGrant",
4468 json!({
4469 "KeyId": key_id,
4470 "GranteePrincipal": "arn:aws:iam::123456789012:user/bob",
4471 "RetiringPrincipal": "arn:aws:iam::123456789012:user/admin",
4472 "Operations": ["Encrypt"]
4473 }),
4474 );
4475 let resp = svc.create_grant(&req).unwrap();
4476 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4477 let grant_token = body["GrantToken"].as_str().unwrap().to_string();
4478
4479 let req = make_request("RetireGrant", json!({ "GrantToken": grant_token }));
4481 svc.retire_grant(&req).unwrap();
4482
4483 let req = make_request("ListGrants", json!({ "KeyId": key_id }));
4485 let resp = svc.list_grants(&req).unwrap();
4486 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4487 assert!(body["Grants"].as_array().unwrap().is_empty());
4488 }
4489
4490 #[test]
4491 fn grant_retire_by_key_and_grant_id() {
4492 let svc = make_service();
4493 let key_id = create_key(&svc);
4494
4495 let req = make_request(
4496 "CreateGrant",
4497 json!({
4498 "KeyId": key_id,
4499 "GranteePrincipal": "arn:aws:iam::123456789012:user/charlie",
4500 "Operations": ["Decrypt"]
4501 }),
4502 );
4503 let resp = svc.create_grant(&req).unwrap();
4504 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4505 let grant_id = body["GrantId"].as_str().unwrap().to_string();
4506
4507 let req = make_request(
4509 "RetireGrant",
4510 json!({ "KeyId": key_id, "GrantId": grant_id }),
4511 );
4512 svc.retire_grant(&req).unwrap();
4513
4514 let req = make_request("ListGrants", json!({ "KeyId": key_id }));
4516 let resp = svc.list_grants(&req).unwrap();
4517 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4518 assert!(body["Grants"].as_array().unwrap().is_empty());
4519 }
4520
4521 #[test]
4522 fn sign_verify_roundtrip() {
4523 let svc = make_service();
4524 let key_id = create_key_with_opts(
4525 &svc,
4526 json!({ "KeyUsage": "SIGN_VERIFY", "KeySpec": "RSA_2048" }),
4527 );
4528
4529 let message = b"data to sign";
4530 let message_b64 = base64::engine::general_purpose::STANDARD.encode(message);
4531
4532 let req = make_request(
4534 "Sign",
4535 json!({
4536 "KeyId": key_id,
4537 "Message": message_b64,
4538 "SigningAlgorithm": "RSASSA_PKCS1_V1_5_SHA_256"
4539 }),
4540 );
4541 let resp = svc.sign(&req).unwrap();
4542 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4543 let signature = body["Signature"].as_str().unwrap().to_string();
4544 assert!(!signature.is_empty());
4545 assert_eq!(
4546 body["SigningAlgorithm"].as_str().unwrap(),
4547 "RSASSA_PKCS1_V1_5_SHA_256"
4548 );
4549
4550 let req = make_request(
4552 "Verify",
4553 json!({
4554 "KeyId": key_id,
4555 "Message": message_b64,
4556 "Signature": signature,
4557 "SigningAlgorithm": "RSASSA_PKCS1_V1_5_SHA_256"
4558 }),
4559 );
4560 let resp = svc.verify(&req).unwrap();
4561 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4562 assert!(body["SignatureValid"].as_bool().unwrap());
4563
4564 let wrong_sig = base64::engine::general_purpose::STANDARD.encode(b"wrong-signature-data");
4566 let req = make_request(
4567 "Verify",
4568 json!({
4569 "KeyId": key_id,
4570 "Message": message_b64,
4571 "Signature": wrong_sig,
4572 "SigningAlgorithm": "RSASSA_PKCS1_V1_5_SHA_256"
4573 }),
4574 );
4575 let resp = svc.verify(&req).unwrap();
4576 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4577 assert!(!body["SignatureValid"].as_bool().unwrap());
4578 }
4579
4580 #[test]
4581 fn sign_with_ecc_key() {
4582 let svc = make_service();
4583 let key_id = create_key_with_opts(
4584 &svc,
4585 json!({ "KeyUsage": "SIGN_VERIFY", "KeySpec": "ECC_NIST_P256" }),
4586 );
4587
4588 let message_b64 = base64::engine::general_purpose::STANDARD.encode(b"ecc data");
4589 let req = make_request(
4590 "Sign",
4591 json!({
4592 "KeyId": key_id,
4593 "Message": message_b64,
4594 "SigningAlgorithm": "ECDSA_SHA_256"
4595 }),
4596 );
4597 let resp = svc.sign(&req).unwrap();
4598 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4599 assert!(body["Signature"].as_str().is_some());
4600 assert_eq!(body["SigningAlgorithm"].as_str().unwrap(), "ECDSA_SHA_256");
4601 }
4602
4603 #[test]
4604 fn sign_wrong_key_usage_fails() {
4605 let svc = make_service();
4606 let key_id = create_key(&svc); let message_b64 = base64::engine::general_purpose::STANDARD.encode(b"test");
4609 let req = make_request(
4610 "Sign",
4611 json!({
4612 "KeyId": key_id,
4613 "Message": message_b64,
4614 "SigningAlgorithm": "RSASSA_PKCS1_V1_5_SHA_256"
4615 }),
4616 );
4617 assert!(svc.sign(&req).is_err());
4618 }
4619
4620 #[test]
4621 fn generate_random_various_lengths() {
4622 let svc = make_service();
4623
4624 for num_bytes in [1, 16, 32, 64, 256, 1024] {
4625 let req = make_request("GenerateRandom", json!({ "NumberOfBytes": num_bytes }));
4626 let resp = svc.generate_random(&req).unwrap();
4627 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4628 let b64 = body["Plaintext"].as_str().unwrap();
4629 let decoded = base64::engine::general_purpose::STANDARD
4630 .decode(b64)
4631 .unwrap();
4632 assert_eq!(
4633 decoded.len(),
4634 num_bytes as usize,
4635 "GenerateRandom({num_bytes}) returned wrong length"
4636 );
4637 }
4638 }
4639
4640 #[test]
4641 fn generate_random_zero_bytes_fails() {
4642 let svc = make_service();
4643 let req = make_request("GenerateRandom", json!({ "NumberOfBytes": 0 }));
4644 assert!(svc.generate_random(&req).is_err());
4645 }
4646
4647 #[test]
4648 fn generate_random_too_many_bytes_fails() {
4649 let svc = make_service();
4650 let req = make_request("GenerateRandom", json!({ "NumberOfBytes": 1025 }));
4651 assert!(svc.generate_random(&req).is_err());
4652 }
4653
4654 #[test]
4655 fn generate_mac_verify_mac_roundtrip() {
4656 let svc = make_service();
4657 let key_id = create_key_with_opts(
4658 &svc,
4659 json!({ "KeyUsage": "GENERATE_VERIFY_MAC", "KeySpec": "HMAC_256" }),
4660 );
4661
4662 let message_b64 = base64::engine::general_purpose::STANDARD.encode(b"mac message");
4663
4664 let req = make_request(
4666 "GenerateMac",
4667 json!({
4668 "KeyId": key_id,
4669 "Message": message_b64,
4670 "MacAlgorithm": "HMAC_SHA_256"
4671 }),
4672 );
4673 let resp = svc.generate_mac(&req).unwrap();
4674 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4675 let mac = body["Mac"].as_str().unwrap().to_string();
4676 assert!(!mac.is_empty());
4677
4678 let req = make_request(
4680 "VerifyMac",
4681 json!({
4682 "KeyId": key_id,
4683 "Message": message_b64,
4684 "Mac": mac,
4685 "MacAlgorithm": "HMAC_SHA_256"
4686 }),
4687 );
4688 let resp = svc.verify_mac(&req).unwrap();
4689 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4690 assert!(body["MacValid"].as_bool().unwrap());
4691 }
4692
4693 #[test]
4694 fn verify_mac_wrong_mac_fails() {
4695 let svc = make_service();
4696 let key_id = create_key_with_opts(
4697 &svc,
4698 json!({ "KeyUsage": "GENERATE_VERIFY_MAC", "KeySpec": "HMAC_256" }),
4699 );
4700
4701 let message_b64 = base64::engine::general_purpose::STANDARD.encode(b"msg");
4702 let wrong_mac = base64::engine::general_purpose::STANDARD.encode(b"wrong-mac");
4703
4704 let req = make_request(
4705 "VerifyMac",
4706 json!({
4707 "KeyId": key_id,
4708 "Message": message_b64,
4709 "Mac": wrong_mac,
4710 "MacAlgorithm": "HMAC_SHA_256"
4711 }),
4712 );
4713 assert!(svc.verify_mac(&req).is_err());
4714 }
4715
4716 #[test]
4717 fn generate_mac_wrong_key_usage_fails() {
4718 let svc = make_service();
4719 let key_id = create_key(&svc); let message_b64 = base64::engine::general_purpose::STANDARD.encode(b"msg");
4722 let req = make_request(
4723 "GenerateMac",
4724 json!({
4725 "KeyId": key_id,
4726 "Message": message_b64,
4727 "MacAlgorithm": "HMAC_SHA_256"
4728 }),
4729 );
4730 assert!(svc.generate_mac(&req).is_err());
4731 }
4732
4733 #[test]
4734 fn re_encrypt_between_keys() {
4735 let svc = make_service();
4736 let key_a = create_key(&svc);
4737 let key_b = create_key(&svc);
4738
4739 let plaintext = b"re-encrypt test data";
4741 let plaintext_b64 = base64::engine::general_purpose::STANDARD.encode(plaintext);
4742 let req = make_request(
4743 "Encrypt",
4744 json!({ "KeyId": key_a, "Plaintext": plaintext_b64 }),
4745 );
4746 let resp = svc.encrypt(&req).unwrap();
4747 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4748 let ciphertext_a = body["CiphertextBlob"].as_str().unwrap().to_string();
4749
4750 let req = make_request(
4752 "ReEncrypt",
4753 json!({
4754 "CiphertextBlob": ciphertext_a,
4755 "DestinationKeyId": key_b
4756 }),
4757 );
4758 let resp = svc.re_encrypt(&req).unwrap();
4759 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4760 let ciphertext_b = body["CiphertextBlob"].as_str().unwrap().to_string();
4761 assert_ne!(ciphertext_a, ciphertext_b);
4762 assert!(body["KeyId"].as_str().unwrap().contains(&key_b));
4763 assert!(body["SourceKeyId"].as_str().unwrap().contains(&key_a));
4764
4765 let req = make_request("Decrypt", json!({ "CiphertextBlob": ciphertext_b }));
4767 let resp = svc.decrypt(&req).unwrap();
4768 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4769 let decrypted_b64 = body["Plaintext"].as_str().unwrap();
4770 let decrypted = base64::engine::general_purpose::STANDARD
4771 .decode(decrypted_b64)
4772 .unwrap();
4773 assert_eq!(decrypted, plaintext);
4774 }
4775
4776 #[test]
4777 fn update_alias_points_to_different_key() {
4778 let svc = make_service();
4779 let key_a = create_key(&svc);
4780 let key_b = create_key(&svc);
4781
4782 let req = make_request(
4784 "CreateAlias",
4785 json!({ "AliasName": "alias/switchable", "TargetKeyId": key_a }),
4786 );
4787 svc.create_alias(&req).unwrap();
4788
4789 {
4791 let state = svc.state.read();
4792 let alias = state.aliases.get("alias/switchable").unwrap();
4793 assert_eq!(alias.target_key_id, key_a);
4794 }
4795
4796 let req = make_request(
4798 "UpdateAlias",
4799 json!({ "AliasName": "alias/switchable", "TargetKeyId": key_b }),
4800 );
4801 svc.update_alias(&req).unwrap();
4802
4803 {
4805 let state = svc.state.read();
4806 let alias = state.aliases.get("alias/switchable").unwrap();
4807 assert_eq!(alias.target_key_id, key_b);
4808 }
4809 }
4810
4811 #[test]
4812 fn update_key_description_changes_description() {
4813 let svc = make_service();
4814 let key_id = create_key(&svc);
4815
4816 {
4818 let state = svc.state.read();
4819 let key = state.keys.get(&key_id).unwrap();
4820 assert_eq!(key.description, "");
4821 }
4822
4823 let req = make_request(
4825 "UpdateKeyDescription",
4826 json!({ "KeyId": key_id, "Description": "new description" }),
4827 );
4828 svc.update_key_description(&req).unwrap();
4829
4830 {
4831 let state = svc.state.read();
4832 let key = state.keys.get(&key_id).unwrap();
4833 assert_eq!(key.description, "new description");
4834 }
4835
4836 let req = make_request(
4838 "UpdateKeyDescription",
4839 json!({ "KeyId": key_id, "Description": "updated again" }),
4840 );
4841 svc.update_key_description(&req).unwrap();
4842
4843 {
4844 let state = svc.state.read();
4845 let key = state.keys.get(&key_id).unwrap();
4846 assert_eq!(key.description, "updated again");
4847 }
4848 }
4849
4850 #[test]
4851 fn get_public_key_for_asymmetric_key() {
4852 let svc = make_service();
4853
4854 let key_id = create_key_with_opts(
4856 &svc,
4857 json!({ "KeyUsage": "SIGN_VERIFY", "KeySpec": "RSA_2048" }),
4858 );
4859
4860 let req = make_request("GetPublicKey", json!({ "KeyId": key_id }));
4861 let resp = svc.get_public_key(&req).unwrap();
4862 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4863
4864 assert!(body["PublicKey"].as_str().is_some());
4865 assert_eq!(body["KeySpec"].as_str().unwrap(), "RSA_2048");
4866 assert_eq!(body["KeyUsage"].as_str().unwrap(), "SIGN_VERIFY");
4867 assert!(body["SigningAlgorithms"].as_array().is_some());
4868 assert!(body["KeyId"].as_str().unwrap().contains(":key/"));
4869
4870 let ecc_key_id = create_key_with_opts(
4872 &svc,
4873 json!({ "KeyUsage": "SIGN_VERIFY", "KeySpec": "ECC_NIST_P256" }),
4874 );
4875
4876 let req = make_request("GetPublicKey", json!({ "KeyId": ecc_key_id }));
4877 let resp = svc.get_public_key(&req).unwrap();
4878 let body: Value = serde_json::from_slice(&resp.body).unwrap();
4879 assert!(body["PublicKey"].as_str().is_some());
4880 assert_eq!(body["KeySpec"].as_str().unwrap(), "ECC_NIST_P256");
4881 }
4882}