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