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 principal: None,
3380 }
3381 }
3382
3383 fn create_key(svc: &KmsService) -> String {
3384 let req = make_request("CreateKey", json!({}));
3385 let resp = svc.create_key(&req).unwrap();
3386 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3387 body["KeyMetadata"]["KeyId"].as_str().unwrap().to_string()
3388 }
3389
3390 #[test]
3391 fn list_keys_pagination_no_duplicates() {
3392 let svc = make_service();
3393 let mut all_key_ids: Vec<String> = Vec::new();
3394 for _ in 0..5 {
3395 all_key_ids.push(create_key(&svc));
3396 }
3397
3398 let mut collected_ids: Vec<String> = Vec::new();
3399 let mut marker: Option<String> = None;
3400
3401 loop {
3402 let mut body = json!({ "Limit": 2 });
3403 if let Some(ref m) = marker {
3404 body["Marker"] = json!(m);
3405 }
3406 let req = make_request("ListKeys", body);
3407 let resp = svc.list_keys(&req).unwrap();
3408 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3409
3410 for key in resp_body["Keys"].as_array().unwrap() {
3411 collected_ids.push(key["KeyId"].as_str().unwrap().to_string());
3412 }
3413
3414 if resp_body["Truncated"].as_bool().unwrap_or(false) {
3415 marker = resp_body["NextMarker"].as_str().map(|s| s.to_string());
3416 } else {
3417 break;
3418 }
3419 }
3420
3421 let mut deduped = collected_ids.clone();
3423 deduped.sort();
3424 deduped.dedup();
3425 assert_eq!(
3426 collected_ids.len(),
3427 deduped.len(),
3428 "pagination produced duplicate keys"
3429 );
3430
3431 for kid in &all_key_ids {
3433 assert!(
3434 collected_ids.contains(kid),
3435 "key {kid} missing from paginated results"
3436 );
3437 }
3438 }
3439
3440 #[test]
3441 fn list_retirable_grants_pagination() {
3442 let svc = make_service();
3443 let key_id = create_key(&svc);
3444 let retiring = "arn:aws:iam::123456789012:user/retiring-user";
3445
3446 for i in 0..5 {
3448 let req = make_request(
3449 "CreateGrant",
3450 json!({
3451 "KeyId": key_id,
3452 "GranteePrincipal": format!("arn:aws:iam::123456789012:user/grantee-{i}"),
3453 "RetiringPrincipal": retiring,
3454 "Operations": ["Encrypt"]
3455 }),
3456 );
3457 svc.create_grant(&req).unwrap();
3458 }
3459
3460 let mut collected_ids: Vec<String> = Vec::new();
3461 let mut marker: Option<String> = None;
3462
3463 loop {
3464 let mut body = json!({
3465 "RetiringPrincipal": retiring,
3466 "Limit": 2
3467 });
3468 if let Some(ref m) = marker {
3469 body["Marker"] = json!(m);
3470 }
3471 let req = make_request("ListRetirableGrants", body);
3472 let resp = svc.list_retirable_grants(&req).unwrap();
3473 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3474
3475 for grant in resp_body["Grants"].as_array().unwrap() {
3476 collected_ids.push(grant["GrantId"].as_str().unwrap().to_string());
3477 }
3478
3479 if resp_body["Truncated"].as_bool().unwrap_or(false) {
3480 marker = resp_body["NextMarker"].as_str().map(|s| s.to_string());
3481 } else {
3482 break;
3483 }
3484 }
3485
3486 let mut deduped = collected_ids.clone();
3488 deduped.sort();
3489 deduped.dedup();
3490 assert_eq!(
3491 collected_ids.len(),
3492 deduped.len(),
3493 "pagination produced duplicate grants"
3494 );
3495
3496 assert_eq!(collected_ids.len(), 5, "expected 5 grants total");
3498 }
3499
3500 fn create_key_with_opts(svc: &KmsService, body: Value) -> String {
3501 let req = make_request("CreateKey", body);
3502 let resp = svc.create_key(&req).unwrap();
3503 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3504 body["KeyMetadata"]["KeyId"].as_str().unwrap().to_string()
3505 }
3506
3507 #[test]
3508 fn generate_data_key_pair_returns_all_fields() {
3509 let svc = make_service();
3510 let key_id = create_key(&svc);
3511
3512 let req = make_request(
3513 "GenerateDataKeyPair",
3514 json!({ "KeyId": key_id, "KeyPairSpec": "RSA_2048" }),
3515 );
3516 let resp = svc.generate_data_key_pair(&req).unwrap();
3517 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3518
3519 assert!(body["PublicKey"].as_str().is_some());
3520 assert!(body["PrivateKeyPlaintext"].as_str().is_some());
3521 assert!(body["PrivateKeyCiphertextBlob"].as_str().is_some());
3522 assert_eq!(body["KeyPairSpec"].as_str().unwrap(), "RSA_2048");
3523 assert!(body["KeyId"].as_str().unwrap().contains(":key/"));
3524 }
3525
3526 #[test]
3527 fn generate_data_key_pair_disabled_key_fails() {
3528 let svc = make_service();
3529 let key_id = create_key(&svc);
3530
3531 let disable_req = make_request("DisableKey", json!({ "KeyId": key_id }));
3532 svc.disable_key(&disable_req).unwrap();
3533
3534 let req = make_request(
3535 "GenerateDataKeyPair",
3536 json!({ "KeyId": key_id, "KeyPairSpec": "RSA_2048" }),
3537 );
3538 assert!(svc.generate_data_key_pair(&req).is_err());
3539 }
3540
3541 #[test]
3542 fn generate_data_key_pair_without_plaintext_omits_private_plaintext() {
3543 let svc = make_service();
3544 let key_id = create_key(&svc);
3545
3546 let req = make_request(
3547 "GenerateDataKeyPairWithoutPlaintext",
3548 json!({ "KeyId": key_id, "KeyPairSpec": "ECC_NIST_P256" }),
3549 );
3550 let resp = svc.generate_data_key_pair_without_plaintext(&req).unwrap();
3551 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3552
3553 assert!(body["PublicKey"].as_str().is_some());
3554 assert!(body["PrivateKeyCiphertextBlob"].as_str().is_some());
3555 assert!(body.get("PrivateKeyPlaintext").is_none());
3556 assert_eq!(body["KeyPairSpec"].as_str().unwrap(), "ECC_NIST_P256");
3557 }
3558
3559 #[test]
3560 fn derive_shared_secret_success() {
3561 let svc = make_service();
3562 let key_id = create_key_with_opts(
3563 &svc,
3564 json!({
3565 "KeyUsage": "KEY_AGREEMENT",
3566 "KeySpec": "ECC_NIST_P256"
3567 }),
3568 );
3569
3570 let fake_pub = base64::engine::general_purpose::STANDARD.encode(b"fake-public-key");
3571 let req = make_request(
3572 "DeriveSharedSecret",
3573 json!({
3574 "KeyId": key_id,
3575 "KeyAgreementAlgorithm": "ECDH",
3576 "PublicKey": fake_pub
3577 }),
3578 );
3579 let resp = svc.derive_shared_secret(&req).unwrap();
3580 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3581
3582 assert!(body["SharedSecret"].as_str().is_some());
3583 assert!(body["KeyId"].as_str().unwrap().contains(":key/"));
3584 assert_eq!(body["KeyAgreementAlgorithm"].as_str().unwrap(), "ECDH");
3585 }
3586
3587 #[test]
3588 fn derive_shared_secret_wrong_usage_fails() {
3589 let svc = make_service();
3590 let key_id = create_key(&svc); let fake_pub = base64::engine::general_purpose::STANDARD.encode(b"fake-public-key");
3593 let req = make_request(
3594 "DeriveSharedSecret",
3595 json!({
3596 "KeyId": key_id,
3597 "KeyAgreementAlgorithm": "ECDH",
3598 "PublicKey": fake_pub
3599 }),
3600 );
3601 assert!(svc.derive_shared_secret(&req).is_err());
3602 }
3603
3604 #[test]
3605 fn get_parameters_for_import_success() {
3606 let svc = make_service();
3607 let key_id = create_key_with_opts(&svc, json!({ "Origin": "EXTERNAL" }));
3608
3609 let req = make_request(
3610 "GetParametersForImport",
3611 json!({ "KeyId": key_id, "WrappingAlgorithm": "RSAES_OAEP_SHA_256", "WrappingKeySpec": "RSA_2048" }),
3612 );
3613 let resp = svc.get_parameters_for_import(&req).unwrap();
3614 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3615
3616 assert!(body["ImportToken"].as_str().is_some());
3617 assert!(body["PublicKey"].as_str().is_some());
3618 assert!(body["ParametersValidTo"].as_f64().is_some());
3619 assert!(body["KeyId"].as_str().unwrap().contains(":key/"));
3620 }
3621
3622 #[test]
3623 fn get_parameters_for_import_non_external_fails() {
3624 let svc = make_service();
3625 let key_id = create_key(&svc); let req = make_request("GetParametersForImport", json!({ "KeyId": key_id }));
3628 assert!(svc.get_parameters_for_import(&req).is_err());
3629 }
3630
3631 #[test]
3632 fn import_key_material_lifecycle() {
3633 let svc = make_service();
3634 let key_id = create_key_with_opts(&svc, json!({ "Origin": "EXTERNAL" }));
3635
3636 let fake_token = base64::engine::general_purpose::STANDARD.encode(b"token");
3637 let fake_material = base64::engine::general_purpose::STANDARD.encode(b"material");
3638
3639 let req = make_request(
3641 "ImportKeyMaterial",
3642 json!({
3643 "KeyId": key_id,
3644 "ImportToken": fake_token,
3645 "EncryptedKeyMaterial": fake_material,
3646 "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE"
3647 }),
3648 );
3649 svc.import_key_material(&req).unwrap();
3650
3651 {
3653 let state = svc.state.read();
3654 let key = state.keys.get(&key_id).unwrap();
3655 assert!(key.imported_key_material);
3656 assert!(key.enabled);
3657 }
3658
3659 let req = make_request("DeleteImportedKeyMaterial", json!({ "KeyId": key_id }));
3661 svc.delete_imported_key_material(&req).unwrap();
3662
3663 {
3665 let state = svc.state.read();
3666 let key = state.keys.get(&key_id).unwrap();
3667 assert!(!key.imported_key_material);
3668 assert!(!key.enabled);
3669 assert_eq!(key.key_state, "PendingImport");
3670 }
3671 }
3672
3673 #[test]
3674 fn import_key_material_non_external_fails() {
3675 let svc = make_service();
3676 let key_id = create_key(&svc);
3677
3678 let fake_token = base64::engine::general_purpose::STANDARD.encode(b"token");
3679 let fake_material = base64::engine::general_purpose::STANDARD.encode(b"material");
3680
3681 let req = make_request(
3682 "ImportKeyMaterial",
3683 json!({
3684 "KeyId": key_id,
3685 "ImportToken": fake_token,
3686 "EncryptedKeyMaterial": fake_material
3687 }),
3688 );
3689 assert!(svc.import_key_material(&req).is_err());
3690 }
3691
3692 #[test]
3693 fn delete_imported_key_material_non_external_fails() {
3694 let svc = make_service();
3695 let key_id = create_key(&svc);
3696
3697 let req = make_request("DeleteImportedKeyMaterial", json!({ "KeyId": key_id }));
3698 assert!(svc.delete_imported_key_material(&req).is_err());
3699 }
3700
3701 #[test]
3702 fn update_primary_region_success() {
3703 let svc = make_service();
3704 let key_id = create_key_with_opts(&svc, json!({ "MultiRegion": true }));
3705
3706 let req = make_request(
3707 "UpdatePrimaryRegion",
3708 json!({ "KeyId": key_id, "PrimaryRegion": "eu-west-1" }),
3709 );
3710 svc.update_primary_region(&req).unwrap();
3711
3712 let state = svc.state.read();
3713 let key = state.keys.get(&key_id).unwrap();
3714 assert_eq!(key.primary_region.as_deref(), Some("eu-west-1"));
3715 assert!(key.arn.contains("eu-west-1"));
3716 }
3717
3718 #[test]
3719 fn update_primary_region_non_multi_region_fails() {
3720 let svc = make_service();
3721 let key_id = create_key(&svc); let req = make_request(
3724 "UpdatePrimaryRegion",
3725 json!({ "KeyId": key_id, "PrimaryRegion": "eu-west-1" }),
3726 );
3727 assert!(svc.update_primary_region(&req).is_err());
3728 }
3729
3730 #[test]
3731 fn custom_key_store_lifecycle() {
3732 let svc = make_service();
3733
3734 let req = make_request(
3736 "CreateCustomKeyStore",
3737 json!({
3738 "CustomKeyStoreName": "my-store",
3739 "CloudHsmClusterId": "cluster-1234",
3740 "TrustAnchorCertificate": "cert-data",
3741 "KeyStorePassword": "password123"
3742 }),
3743 );
3744 let resp = svc.create_custom_key_store(&req).unwrap();
3745 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3746 let store_id = body["CustomKeyStoreId"].as_str().unwrap().to_string();
3747 assert!(store_id.starts_with("cks-"));
3748
3749 let req = make_request(
3751 "DescribeCustomKeyStores",
3752 json!({ "CustomKeyStoreId": store_id }),
3753 );
3754 let resp = svc.describe_custom_key_stores(&req).unwrap();
3755 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3756 let stores = body["CustomKeyStores"].as_array().unwrap();
3757 assert_eq!(stores.len(), 1);
3758 assert_eq!(
3759 stores[0]["CustomKeyStoreName"].as_str().unwrap(),
3760 "my-store"
3761 );
3762 assert_eq!(
3763 stores[0]["ConnectionState"].as_str().unwrap(),
3764 "DISCONNECTED"
3765 );
3766 assert_eq!(
3767 stores[0]["CloudHsmClusterId"].as_str().unwrap(),
3768 "cluster-1234"
3769 );
3770
3771 let req = make_request(
3773 "ConnectCustomKeyStore",
3774 json!({ "CustomKeyStoreId": store_id }),
3775 );
3776 svc.connect_custom_key_store(&req).unwrap();
3777
3778 let req = make_request(
3780 "DescribeCustomKeyStores",
3781 json!({ "CustomKeyStoreId": store_id }),
3782 );
3783 let resp = svc.describe_custom_key_stores(&req).unwrap();
3784 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3785 assert_eq!(
3786 body["CustomKeyStores"][0]["ConnectionState"]
3787 .as_str()
3788 .unwrap(),
3789 "CONNECTED"
3790 );
3791
3792 let req = make_request(
3794 "DeleteCustomKeyStore",
3795 json!({ "CustomKeyStoreId": store_id }),
3796 );
3797 assert!(svc.delete_custom_key_store(&req).is_err());
3798
3799 let req = make_request(
3801 "DisconnectCustomKeyStore",
3802 json!({ "CustomKeyStoreId": store_id }),
3803 );
3804 svc.disconnect_custom_key_store(&req).unwrap();
3805
3806 let req = make_request(
3808 "UpdateCustomKeyStore",
3809 json!({
3810 "CustomKeyStoreId": store_id,
3811 "NewCustomKeyStoreName": "renamed-store"
3812 }),
3813 );
3814 svc.update_custom_key_store(&req).unwrap();
3815
3816 let req = make_request(
3818 "DescribeCustomKeyStores",
3819 json!({ "CustomKeyStoreId": store_id }),
3820 );
3821 let resp = svc.describe_custom_key_stores(&req).unwrap();
3822 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3823 assert_eq!(
3824 body["CustomKeyStores"][0]["CustomKeyStoreName"]
3825 .as_str()
3826 .unwrap(),
3827 "renamed-store"
3828 );
3829
3830 let req = make_request(
3832 "DeleteCustomKeyStore",
3833 json!({ "CustomKeyStoreId": store_id }),
3834 );
3835 svc.delete_custom_key_store(&req).unwrap();
3836
3837 let req = make_request("DescribeCustomKeyStores", json!({}));
3839 let resp = svc.describe_custom_key_stores(&req).unwrap();
3840 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3841 assert!(body["CustomKeyStores"].as_array().unwrap().is_empty());
3842 }
3843
3844 #[test]
3845 fn custom_key_store_duplicate_name_fails() {
3846 let svc = make_service();
3847
3848 let req = make_request(
3849 "CreateCustomKeyStore",
3850 json!({ "CustomKeyStoreName": "dup-store" }),
3851 );
3852 svc.create_custom_key_store(&req).unwrap();
3853
3854 let req = make_request(
3855 "CreateCustomKeyStore",
3856 json!({ "CustomKeyStoreName": "dup-store" }),
3857 );
3858 assert!(svc.create_custom_key_store(&req).is_err());
3859 }
3860
3861 #[test]
3862 fn describe_custom_key_store_not_found() {
3863 let svc = make_service();
3864
3865 let req = make_request(
3866 "DescribeCustomKeyStores",
3867 json!({ "CustomKeyStoreId": "cks-nonexistent" }),
3868 );
3869 assert!(svc.describe_custom_key_stores(&req).is_err());
3870 }
3871
3872 #[test]
3873 fn delete_nonexistent_custom_key_store_fails() {
3874 let svc = make_service();
3875
3876 let req = make_request(
3877 "DeleteCustomKeyStore",
3878 json!({ "CustomKeyStoreId": "cks-nonexistent" }),
3879 );
3880 assert!(svc.delete_custom_key_store(&req).is_err());
3881 }
3882
3883 #[test]
3884 fn connect_nonexistent_custom_key_store_fails() {
3885 let svc = make_service();
3886
3887 let req = make_request(
3888 "ConnectCustomKeyStore",
3889 json!({ "CustomKeyStoreId": "cks-nonexistent" }),
3890 );
3891 assert!(svc.connect_custom_key_store(&req).is_err());
3892 }
3893
3894 #[test]
3895 fn describe_custom_key_stores_by_name() {
3896 let svc = make_service();
3897
3898 let req = make_request(
3899 "CreateCustomKeyStore",
3900 json!({ "CustomKeyStoreName": "store-a" }),
3901 );
3902 svc.create_custom_key_store(&req).unwrap();
3903
3904 let req = make_request(
3905 "CreateCustomKeyStore",
3906 json!({ "CustomKeyStoreName": "store-b" }),
3907 );
3908 svc.create_custom_key_store(&req).unwrap();
3909
3910 let req = make_request(
3912 "DescribeCustomKeyStores",
3913 json!({ "CustomKeyStoreName": "store-a" }),
3914 );
3915 let resp = svc.describe_custom_key_stores(&req).unwrap();
3916 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3917 let stores = body["CustomKeyStores"].as_array().unwrap();
3918 assert_eq!(stores.len(), 1);
3919 assert_eq!(stores[0]["CustomKeyStoreName"].as_str().unwrap(), "store-a");
3920 }
3921
3922 #[test]
3923 fn update_custom_key_store_name_conflict() {
3924 let svc = make_service();
3925
3926 let req = make_request(
3927 "CreateCustomKeyStore",
3928 json!({ "CustomKeyStoreName": "store-x" }),
3929 );
3930 svc.create_custom_key_store(&req).unwrap();
3931
3932 let req = make_request(
3933 "CreateCustomKeyStore",
3934 json!({ "CustomKeyStoreName": "store-y" }),
3935 );
3936 let resp = svc.create_custom_key_store(&req).unwrap();
3937 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3938 let store_y_id = body["CustomKeyStoreId"].as_str().unwrap().to_string();
3939
3940 let req = make_request(
3942 "UpdateCustomKeyStore",
3943 json!({
3944 "CustomKeyStoreId": store_y_id,
3945 "NewCustomKeyStoreName": "store-x"
3946 }),
3947 );
3948 assert!(svc.update_custom_key_store(&req).is_err());
3949 }
3950
3951 #[test]
3952 fn derive_shared_secret_is_deterministic() {
3953 let svc = make_service();
3954 let key_id = create_key_with_opts(
3955 &svc,
3956 json!({
3957 "KeyUsage": "KEY_AGREEMENT",
3958 "KeySpec": "ECC_NIST_P256"
3959 }),
3960 );
3961
3962 let pub_key = base64::engine::general_purpose::STANDARD.encode(b"counterparty-public-key");
3963 let req = make_request(
3964 "DeriveSharedSecret",
3965 json!({
3966 "KeyId": key_id,
3967 "KeyAgreementAlgorithm": "ECDH",
3968 "PublicKey": pub_key
3969 }),
3970 );
3971
3972 let resp1 = svc.derive_shared_secret(&req).unwrap();
3973 let body1: Value = serde_json::from_slice(resp1.body.expect_bytes()).unwrap();
3974 let secret1 = body1["SharedSecret"].as_str().unwrap().to_string();
3975
3976 let resp2 = svc.derive_shared_secret(&req).unwrap();
3978 let body2: Value = serde_json::from_slice(resp2.body.expect_bytes()).unwrap();
3979 let secret2 = body2["SharedSecret"].as_str().unwrap().to_string();
3980
3981 assert_eq!(secret1, secret2, "DeriveSharedSecret must be deterministic");
3982
3983 let other_pub = base64::engine::general_purpose::STANDARD.encode(b"different-public-key");
3985 let req2 = make_request(
3986 "DeriveSharedSecret",
3987 json!({
3988 "KeyId": key_id,
3989 "KeyAgreementAlgorithm": "ECDH",
3990 "PublicKey": other_pub
3991 }),
3992 );
3993 let resp3 = svc.derive_shared_secret(&req2).unwrap();
3994 let body3: Value = serde_json::from_slice(resp3.body.expect_bytes()).unwrap();
3995 let secret3 = body3["SharedSecret"].as_str().unwrap().to_string();
3996 assert_ne!(
3997 secret1, secret3,
3998 "Different public keys must yield different shared secrets"
3999 );
4000 }
4001
4002 #[test]
4003 fn imported_key_material_encrypt_decrypt_roundtrip() {
4004 let svc = make_service();
4005 let key_id = create_key_with_opts(&svc, json!({ "Origin": "EXTERNAL" }));
4006
4007 let fake_token = base64::engine::general_purpose::STANDARD.encode(b"token");
4008 let material = b"my-secret-aes-key-material!12345";
4009 let fake_material = base64::engine::general_purpose::STANDARD.encode(material);
4010
4011 let req = make_request(
4013 "ImportKeyMaterial",
4014 json!({
4015 "KeyId": key_id,
4016 "ImportToken": fake_token,
4017 "EncryptedKeyMaterial": fake_material,
4018 }),
4019 );
4020 svc.import_key_material(&req).unwrap();
4021
4022 let plaintext = b"Hello imported key!";
4024 let plaintext_b64 = base64::engine::general_purpose::STANDARD.encode(plaintext);
4025 let req = make_request(
4026 "Encrypt",
4027 json!({ "KeyId": key_id, "Plaintext": plaintext_b64 }),
4028 );
4029 let resp = svc.encrypt(&req).unwrap();
4030 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4031 let ciphertext = body["CiphertextBlob"].as_str().unwrap().to_string();
4032
4033 let ct_bytes = base64::engine::general_purpose::STANDARD
4035 .decode(&ciphertext)
4036 .unwrap();
4037 let envelope = String::from_utf8(ct_bytes).unwrap();
4038 assert!(
4039 envelope.starts_with("fakecloud-imported:"),
4040 "Imported key should use fakecloud-imported envelope"
4041 );
4042
4043 let req = make_request("Decrypt", json!({ "CiphertextBlob": ciphertext }));
4045 let resp = svc.decrypt(&req).unwrap();
4046 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4047 let decrypted_b64 = body["Plaintext"].as_str().unwrap();
4048 let decrypted = base64::engine::general_purpose::STANDARD
4049 .decode(decrypted_b64)
4050 .unwrap();
4051 assert_eq!(
4052 decrypted, plaintext,
4053 "Decrypt must recover the original plaintext"
4054 );
4055 }
4056
4057 #[test]
4058 fn imported_key_material_decrypt_fails_after_deletion() {
4059 let svc = make_service();
4060 let key_id = create_key_with_opts(&svc, json!({ "Origin": "EXTERNAL" }));
4061
4062 let fake_token = base64::engine::general_purpose::STANDARD.encode(b"token");
4063 let fake_material =
4064 base64::engine::general_purpose::STANDARD.encode(b"some-key-material-32bytes!!");
4065
4066 svc.import_key_material(&make_request(
4068 "ImportKeyMaterial",
4069 json!({
4070 "KeyId": key_id,
4071 "ImportToken": fake_token,
4072 "EncryptedKeyMaterial": fake_material,
4073 }),
4074 ))
4075 .unwrap();
4076
4077 let plaintext_b64 = base64::engine::general_purpose::STANDARD.encode(b"secret");
4078 let resp = svc
4079 .encrypt(&make_request(
4080 "Encrypt",
4081 json!({ "KeyId": key_id, "Plaintext": plaintext_b64 }),
4082 ))
4083 .unwrap();
4084 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4085 let ciphertext = body["CiphertextBlob"].as_str().unwrap().to_string();
4086
4087 svc.delete_imported_key_material(&make_request(
4089 "DeleteImportedKeyMaterial",
4090 json!({ "KeyId": key_id }),
4091 ))
4092 .unwrap();
4093
4094 let result = svc.decrypt(&make_request(
4097 "Decrypt",
4098 json!({ "CiphertextBlob": ciphertext }),
4099 ));
4100 assert!(
4101 result.is_err(),
4102 "Decrypt should fail after key material deletion"
4103 );
4104 }
4105
4106 #[test]
4107 fn list_keys_rejects_non_integer_limit() {
4108 let svc = make_service();
4109 let req = make_request("ListKeys", json!({ "Limit": "abc" }));
4111 let result = svc.list_keys(&req);
4112 assert!(result.is_err(), "non-integer Limit should be rejected");
4113 }
4114
4115 #[test]
4116 fn list_keys_rejects_large_unsigned_limit() {
4117 let svc = make_service();
4118 let req = make_request("ListKeys", json!({ "Limit": u64::MAX }));
4120 let result = svc.list_keys(&req);
4121 assert!(result.is_err(), "large unsigned Limit should be rejected");
4122 }
4123
4124 #[test]
4125 fn list_keys_rejects_out_of_range_limit() {
4126 let svc = make_service();
4127 let req = make_request("ListKeys", json!({ "Limit": 0 }));
4128 let result = svc.list_keys(&req);
4129 assert!(result.is_err(), "Limit=0 should be rejected");
4130
4131 let req = make_request("ListKeys", json!({ "Limit": 1001 }));
4132 let result = svc.list_keys(&req);
4133 assert!(result.is_err(), "Limit=1001 should be rejected");
4134 }
4135
4136 #[test]
4137 fn enable_key_with_nonexistent_id_returns_error() {
4138 let svc = make_service();
4139 let key_id = create_key(&svc);
4142
4143 svc.state.write().keys.remove(&key_id);
4145
4146 let req = make_request("EnableKey", json!({ "KeyId": key_id }));
4147 let result = svc.enable_key(&req);
4148 assert!(result.is_err(), "Should return error for missing key");
4149 }
4150
4151 #[test]
4152 fn disable_key_with_nonexistent_id_returns_error() {
4153 let svc = make_service();
4154 let key_id = create_key(&svc);
4155 svc.state.write().keys.remove(&key_id);
4156
4157 let req = make_request("DisableKey", json!({ "KeyId": key_id }));
4158 let result = svc.disable_key(&req);
4159 assert!(result.is_err(), "Should return error for missing key");
4160 }
4161
4162 #[test]
4163 fn tag_resource_with_nonexistent_key_returns_error() {
4164 let svc = make_service();
4165 let key_id = create_key(&svc);
4166 svc.state.write().keys.remove(&key_id);
4167
4168 let req = make_request(
4169 "TagResource",
4170 json!({ "KeyId": key_id, "Tags": [{"TagKey": "k", "TagValue": "v"}] }),
4171 );
4172 let result = svc.tag_resource(&req);
4173 assert!(result.is_err(), "Should return error for missing key");
4174 }
4175
4176 #[test]
4177 fn cancel_key_deletion_re_enables_key() {
4178 let svc = make_service();
4179 let key_id = create_key(&svc);
4180
4181 let req = make_request(
4183 "ScheduleKeyDeletion",
4184 json!({ "KeyId": key_id, "PendingWindowInDays": 7 }),
4185 );
4186 let resp = svc.schedule_key_deletion(&req).unwrap();
4187 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4188 assert_eq!(body["KeyState"].as_str().unwrap(), "PendingDeletion");
4189
4190 {
4192 let state = svc.state.read();
4193 let key = state.keys.get(&key_id).unwrap();
4194 assert_eq!(key.key_state, "PendingDeletion");
4195 assert!(!key.enabled);
4196 assert!(key.deletion_date.is_some());
4197 }
4198
4199 let req = make_request("CancelKeyDeletion", json!({ "KeyId": key_id }));
4201 let resp = svc.cancel_key_deletion(&req).unwrap();
4202 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4203 assert_eq!(body["KeyId"].as_str().unwrap(), key_id);
4204
4205 {
4207 let state = svc.state.read();
4208 let key = state.keys.get(&key_id).unwrap();
4209 assert_eq!(key.key_state, "Disabled");
4210 assert!(key.deletion_date.is_none());
4211 }
4212
4213 let req = make_request("EnableKey", json!({ "KeyId": key_id }));
4215 svc.enable_key(&req).unwrap();
4216
4217 {
4218 let state = svc.state.read();
4219 let key = state.keys.get(&key_id).unwrap();
4220 assert!(key.enabled);
4221 assert_eq!(key.key_state, "Enabled");
4222 }
4223 }
4224
4225 #[test]
4226 fn key_rotation_lifecycle() {
4227 let svc = make_service();
4228 let key_id = create_key(&svc);
4229
4230 let req = make_request("GetKeyRotationStatus", json!({ "KeyId": key_id }));
4232 let resp = svc.get_key_rotation_status(&req).unwrap();
4233 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4234 assert!(!body["KeyRotationEnabled"].as_bool().unwrap());
4235
4236 let req = make_request("EnableKeyRotation", json!({ "KeyId": key_id }));
4238 svc.enable_key_rotation(&req).unwrap();
4239
4240 let req = make_request("GetKeyRotationStatus", json!({ "KeyId": key_id }));
4241 let resp = svc.get_key_rotation_status(&req).unwrap();
4242 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4243 assert!(body["KeyRotationEnabled"].as_bool().unwrap());
4244
4245 let req = make_request("DisableKeyRotation", json!({ "KeyId": key_id }));
4247 svc.disable_key_rotation(&req).unwrap();
4248
4249 let req = make_request("GetKeyRotationStatus", json!({ "KeyId": key_id }));
4250 let resp = svc.get_key_rotation_status(&req).unwrap();
4251 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4252 assert!(!body["KeyRotationEnabled"].as_bool().unwrap());
4253 }
4254
4255 #[test]
4256 fn rotate_key_on_demand_and_list_rotations() {
4257 let svc = make_service();
4258 let key_id = create_key(&svc);
4259
4260 let req = make_request("ListKeyRotations", json!({ "KeyId": key_id }));
4262 let resp = svc.list_key_rotations(&req).unwrap();
4263 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4264 assert!(body["Rotations"].as_array().unwrap().is_empty());
4265
4266 let req = make_request("RotateKeyOnDemand", json!({ "KeyId": key_id }));
4268 let resp = svc.rotate_key_on_demand(&req).unwrap();
4269 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4270 assert_eq!(body["KeyId"].as_str().unwrap(), key_id);
4271
4272 let req = make_request("RotateKeyOnDemand", json!({ "KeyId": key_id }));
4274 svc.rotate_key_on_demand(&req).unwrap();
4275
4276 let req = make_request("ListKeyRotations", json!({ "KeyId": key_id }));
4278 let resp = svc.list_key_rotations(&req).unwrap();
4279 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4280 let rotations = body["Rotations"].as_array().unwrap();
4281 assert_eq!(rotations.len(), 2);
4282 assert_eq!(rotations[0]["RotationType"].as_str().unwrap(), "ON_DEMAND");
4283 assert_eq!(rotations[0]["KeyId"].as_str().unwrap(), key_id);
4284 assert!(rotations[0]["RotationDate"].as_f64().is_some());
4285 }
4286
4287 #[test]
4288 fn key_policy_get_put_list() {
4289 let svc = make_service();
4290 let key_id = create_key(&svc);
4291
4292 let req = make_request("GetKeyPolicy", json!({ "KeyId": key_id }));
4294 let resp = svc.get_key_policy(&req).unwrap();
4295 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4296 let policy_str = body["Policy"].as_str().unwrap();
4297 assert!(policy_str.contains("Enable IAM User Permissions"));
4298
4299 let custom_policy = r#"{"Version":"2012-10-17","Statement":[]}"#;
4301 let req = make_request(
4302 "PutKeyPolicy",
4303 json!({ "KeyId": key_id, "Policy": custom_policy }),
4304 );
4305 svc.put_key_policy(&req).unwrap();
4306
4307 let req = make_request("GetKeyPolicy", json!({ "KeyId": key_id }));
4309 let resp = svc.get_key_policy(&req).unwrap();
4310 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4311 assert_eq!(body["Policy"].as_str().unwrap(), custom_policy);
4312
4313 let req = make_request("ListKeyPolicies", json!({ "KeyId": key_id }));
4315 let resp = svc.list_key_policies(&req).unwrap();
4316 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4317 let names = body["PolicyNames"].as_array().unwrap();
4318 assert_eq!(names.len(), 1);
4319 assert_eq!(names[0].as_str().unwrap(), "default");
4320 }
4321
4322 #[test]
4323 fn grant_create_list_revoke() {
4324 let svc = make_service();
4325 let key_id = create_key(&svc);
4326
4327 let req = make_request(
4329 "CreateGrant",
4330 json!({
4331 "KeyId": key_id,
4332 "GranteePrincipal": "arn:aws:iam::123456789012:user/alice",
4333 "Operations": ["Encrypt", "Decrypt"],
4334 "Name": "test-grant"
4335 }),
4336 );
4337 let resp = svc.create_grant(&req).unwrap();
4338 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4339 let grant_id = body["GrantId"].as_str().unwrap().to_string();
4340 let grant_token = body["GrantToken"].as_str().unwrap().to_string();
4341 assert!(!grant_id.is_empty());
4342 assert!(!grant_token.is_empty());
4343
4344 let req = make_request("ListGrants", json!({ "KeyId": key_id }));
4346 let resp = svc.list_grants(&req).unwrap();
4347 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4348 let grants = body["Grants"].as_array().unwrap();
4349 assert_eq!(grants.len(), 1);
4350 assert_eq!(grants[0]["GrantId"].as_str().unwrap(), grant_id);
4351 assert_eq!(
4352 grants[0]["GranteePrincipal"].as_str().unwrap(),
4353 "arn:aws:iam::123456789012:user/alice"
4354 );
4355 assert_eq!(grants[0]["Operations"].as_array().unwrap().len(), 2);
4356
4357 let req = make_request(
4359 "RevokeGrant",
4360 json!({ "KeyId": key_id, "GrantId": grant_id }),
4361 );
4362 svc.revoke_grant(&req).unwrap();
4363
4364 let req = make_request("ListGrants", json!({ "KeyId": key_id }));
4366 let resp = svc.list_grants(&req).unwrap();
4367 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4368 assert!(body["Grants"].as_array().unwrap().is_empty());
4369 }
4370
4371 #[test]
4372 fn grant_retire_by_token() {
4373 let svc = make_service();
4374 let key_id = create_key(&svc);
4375
4376 let req = make_request(
4377 "CreateGrant",
4378 json!({
4379 "KeyId": key_id,
4380 "GranteePrincipal": "arn:aws:iam::123456789012:user/bob",
4381 "RetiringPrincipal": "arn:aws:iam::123456789012:user/admin",
4382 "Operations": ["Encrypt"]
4383 }),
4384 );
4385 let resp = svc.create_grant(&req).unwrap();
4386 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4387 let grant_token = body["GrantToken"].as_str().unwrap().to_string();
4388
4389 let req = make_request("RetireGrant", json!({ "GrantToken": grant_token }));
4391 svc.retire_grant(&req).unwrap();
4392
4393 let req = make_request("ListGrants", json!({ "KeyId": key_id }));
4395 let resp = svc.list_grants(&req).unwrap();
4396 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4397 assert!(body["Grants"].as_array().unwrap().is_empty());
4398 }
4399
4400 #[test]
4401 fn grant_retire_by_key_and_grant_id() {
4402 let svc = make_service();
4403 let key_id = create_key(&svc);
4404
4405 let req = make_request(
4406 "CreateGrant",
4407 json!({
4408 "KeyId": key_id,
4409 "GranteePrincipal": "arn:aws:iam::123456789012:user/charlie",
4410 "Operations": ["Decrypt"]
4411 }),
4412 );
4413 let resp = svc.create_grant(&req).unwrap();
4414 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4415 let grant_id = body["GrantId"].as_str().unwrap().to_string();
4416
4417 let req = make_request(
4419 "RetireGrant",
4420 json!({ "KeyId": key_id, "GrantId": grant_id }),
4421 );
4422 svc.retire_grant(&req).unwrap();
4423
4424 let req = make_request("ListGrants", json!({ "KeyId": key_id }));
4426 let resp = svc.list_grants(&req).unwrap();
4427 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4428 assert!(body["Grants"].as_array().unwrap().is_empty());
4429 }
4430
4431 #[test]
4432 fn sign_verify_roundtrip() {
4433 let svc = make_service();
4434 let key_id = create_key_with_opts(
4435 &svc,
4436 json!({ "KeyUsage": "SIGN_VERIFY", "KeySpec": "RSA_2048" }),
4437 );
4438
4439 let message = b"data to sign";
4440 let message_b64 = base64::engine::general_purpose::STANDARD.encode(message);
4441
4442 let req = make_request(
4444 "Sign",
4445 json!({
4446 "KeyId": key_id,
4447 "Message": message_b64,
4448 "SigningAlgorithm": "RSASSA_PKCS1_V1_5_SHA_256"
4449 }),
4450 );
4451 let resp = svc.sign(&req).unwrap();
4452 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4453 let signature = body["Signature"].as_str().unwrap().to_string();
4454 assert!(!signature.is_empty());
4455 assert_eq!(
4456 body["SigningAlgorithm"].as_str().unwrap(),
4457 "RSASSA_PKCS1_V1_5_SHA_256"
4458 );
4459
4460 let req = make_request(
4462 "Verify",
4463 json!({
4464 "KeyId": key_id,
4465 "Message": message_b64,
4466 "Signature": signature,
4467 "SigningAlgorithm": "RSASSA_PKCS1_V1_5_SHA_256"
4468 }),
4469 );
4470 let resp = svc.verify(&req).unwrap();
4471 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4472 assert!(body["SignatureValid"].as_bool().unwrap());
4473
4474 let wrong_sig = base64::engine::general_purpose::STANDARD.encode(b"wrong-signature-data");
4476 let req = make_request(
4477 "Verify",
4478 json!({
4479 "KeyId": key_id,
4480 "Message": message_b64,
4481 "Signature": wrong_sig,
4482 "SigningAlgorithm": "RSASSA_PKCS1_V1_5_SHA_256"
4483 }),
4484 );
4485 let resp = svc.verify(&req).unwrap();
4486 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4487 assert!(!body["SignatureValid"].as_bool().unwrap());
4488 }
4489
4490 #[test]
4491 fn sign_with_ecc_key() {
4492 let svc = make_service();
4493 let key_id = create_key_with_opts(
4494 &svc,
4495 json!({ "KeyUsage": "SIGN_VERIFY", "KeySpec": "ECC_NIST_P256" }),
4496 );
4497
4498 let message_b64 = base64::engine::general_purpose::STANDARD.encode(b"ecc data");
4499 let req = make_request(
4500 "Sign",
4501 json!({
4502 "KeyId": key_id,
4503 "Message": message_b64,
4504 "SigningAlgorithm": "ECDSA_SHA_256"
4505 }),
4506 );
4507 let resp = svc.sign(&req).unwrap();
4508 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4509 assert!(body["Signature"].as_str().is_some());
4510 assert_eq!(body["SigningAlgorithm"].as_str().unwrap(), "ECDSA_SHA_256");
4511 }
4512
4513 #[test]
4514 fn sign_wrong_key_usage_fails() {
4515 let svc = make_service();
4516 let key_id = create_key(&svc); let message_b64 = base64::engine::general_purpose::STANDARD.encode(b"test");
4519 let req = make_request(
4520 "Sign",
4521 json!({
4522 "KeyId": key_id,
4523 "Message": message_b64,
4524 "SigningAlgorithm": "RSASSA_PKCS1_V1_5_SHA_256"
4525 }),
4526 );
4527 assert!(svc.sign(&req).is_err());
4528 }
4529
4530 #[test]
4531 fn generate_random_various_lengths() {
4532 let svc = make_service();
4533
4534 for num_bytes in [1, 16, 32, 64, 256, 1024] {
4535 let req = make_request("GenerateRandom", json!({ "NumberOfBytes": num_bytes }));
4536 let resp = svc.generate_random(&req).unwrap();
4537 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4538 let b64 = body["Plaintext"].as_str().unwrap();
4539 let decoded = base64::engine::general_purpose::STANDARD
4540 .decode(b64)
4541 .unwrap();
4542 assert_eq!(
4543 decoded.len(),
4544 num_bytes as usize,
4545 "GenerateRandom({num_bytes}) returned wrong length"
4546 );
4547 }
4548 }
4549
4550 #[test]
4551 fn generate_random_zero_bytes_fails() {
4552 let svc = make_service();
4553 let req = make_request("GenerateRandom", json!({ "NumberOfBytes": 0 }));
4554 assert!(svc.generate_random(&req).is_err());
4555 }
4556
4557 #[test]
4558 fn generate_random_too_many_bytes_fails() {
4559 let svc = make_service();
4560 let req = make_request("GenerateRandom", json!({ "NumberOfBytes": 1025 }));
4561 assert!(svc.generate_random(&req).is_err());
4562 }
4563
4564 #[test]
4565 fn generate_mac_verify_mac_roundtrip() {
4566 let svc = make_service();
4567 let key_id = create_key_with_opts(
4568 &svc,
4569 json!({ "KeyUsage": "GENERATE_VERIFY_MAC", "KeySpec": "HMAC_256" }),
4570 );
4571
4572 let message_b64 = base64::engine::general_purpose::STANDARD.encode(b"mac message");
4573
4574 let req = make_request(
4576 "GenerateMac",
4577 json!({
4578 "KeyId": key_id,
4579 "Message": message_b64,
4580 "MacAlgorithm": "HMAC_SHA_256"
4581 }),
4582 );
4583 let resp = svc.generate_mac(&req).unwrap();
4584 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4585 let mac = body["Mac"].as_str().unwrap().to_string();
4586 assert!(!mac.is_empty());
4587
4588 let req = make_request(
4590 "VerifyMac",
4591 json!({
4592 "KeyId": key_id,
4593 "Message": message_b64,
4594 "Mac": mac,
4595 "MacAlgorithm": "HMAC_SHA_256"
4596 }),
4597 );
4598 let resp = svc.verify_mac(&req).unwrap();
4599 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4600 assert!(body["MacValid"].as_bool().unwrap());
4601 }
4602
4603 #[test]
4604 fn verify_mac_wrong_mac_fails() {
4605 let svc = make_service();
4606 let key_id = create_key_with_opts(
4607 &svc,
4608 json!({ "KeyUsage": "GENERATE_VERIFY_MAC", "KeySpec": "HMAC_256" }),
4609 );
4610
4611 let message_b64 = base64::engine::general_purpose::STANDARD.encode(b"msg");
4612 let wrong_mac = base64::engine::general_purpose::STANDARD.encode(b"wrong-mac");
4613
4614 let req = make_request(
4615 "VerifyMac",
4616 json!({
4617 "KeyId": key_id,
4618 "Message": message_b64,
4619 "Mac": wrong_mac,
4620 "MacAlgorithm": "HMAC_SHA_256"
4621 }),
4622 );
4623 assert!(svc.verify_mac(&req).is_err());
4624 }
4625
4626 #[test]
4627 fn generate_mac_wrong_key_usage_fails() {
4628 let svc = make_service();
4629 let key_id = create_key(&svc); let message_b64 = base64::engine::general_purpose::STANDARD.encode(b"msg");
4632 let req = make_request(
4633 "GenerateMac",
4634 json!({
4635 "KeyId": key_id,
4636 "Message": message_b64,
4637 "MacAlgorithm": "HMAC_SHA_256"
4638 }),
4639 );
4640 assert!(svc.generate_mac(&req).is_err());
4641 }
4642
4643 #[test]
4644 fn re_encrypt_between_keys() {
4645 let svc = make_service();
4646 let key_a = create_key(&svc);
4647 let key_b = create_key(&svc);
4648
4649 let plaintext = b"re-encrypt test data";
4651 let plaintext_b64 = base64::engine::general_purpose::STANDARD.encode(plaintext);
4652 let req = make_request(
4653 "Encrypt",
4654 json!({ "KeyId": key_a, "Plaintext": plaintext_b64 }),
4655 );
4656 let resp = svc.encrypt(&req).unwrap();
4657 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4658 let ciphertext_a = body["CiphertextBlob"].as_str().unwrap().to_string();
4659
4660 let req = make_request(
4662 "ReEncrypt",
4663 json!({
4664 "CiphertextBlob": ciphertext_a,
4665 "DestinationKeyId": key_b
4666 }),
4667 );
4668 let resp = svc.re_encrypt(&req).unwrap();
4669 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4670 let ciphertext_b = body["CiphertextBlob"].as_str().unwrap().to_string();
4671 assert_ne!(ciphertext_a, ciphertext_b);
4672 assert!(body["KeyId"].as_str().unwrap().contains(&key_b));
4673 assert!(body["SourceKeyId"].as_str().unwrap().contains(&key_a));
4674
4675 let req = make_request("Decrypt", json!({ "CiphertextBlob": ciphertext_b }));
4677 let resp = svc.decrypt(&req).unwrap();
4678 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4679 let decrypted_b64 = body["Plaintext"].as_str().unwrap();
4680 let decrypted = base64::engine::general_purpose::STANDARD
4681 .decode(decrypted_b64)
4682 .unwrap();
4683 assert_eq!(decrypted, plaintext);
4684 }
4685
4686 #[test]
4687 fn update_alias_points_to_different_key() {
4688 let svc = make_service();
4689 let key_a = create_key(&svc);
4690 let key_b = create_key(&svc);
4691
4692 let req = make_request(
4694 "CreateAlias",
4695 json!({ "AliasName": "alias/switchable", "TargetKeyId": key_a }),
4696 );
4697 svc.create_alias(&req).unwrap();
4698
4699 {
4701 let state = svc.state.read();
4702 let alias = state.aliases.get("alias/switchable").unwrap();
4703 assert_eq!(alias.target_key_id, key_a);
4704 }
4705
4706 let req = make_request(
4708 "UpdateAlias",
4709 json!({ "AliasName": "alias/switchable", "TargetKeyId": key_b }),
4710 );
4711 svc.update_alias(&req).unwrap();
4712
4713 {
4715 let state = svc.state.read();
4716 let alias = state.aliases.get("alias/switchable").unwrap();
4717 assert_eq!(alias.target_key_id, key_b);
4718 }
4719 }
4720
4721 #[test]
4722 fn update_key_description_changes_description() {
4723 let svc = make_service();
4724 let key_id = create_key(&svc);
4725
4726 {
4728 let state = svc.state.read();
4729 let key = state.keys.get(&key_id).unwrap();
4730 assert_eq!(key.description, "");
4731 }
4732
4733 let req = make_request(
4735 "UpdateKeyDescription",
4736 json!({ "KeyId": key_id, "Description": "new description" }),
4737 );
4738 svc.update_key_description(&req).unwrap();
4739
4740 {
4741 let state = svc.state.read();
4742 let key = state.keys.get(&key_id).unwrap();
4743 assert_eq!(key.description, "new description");
4744 }
4745
4746 let req = make_request(
4748 "UpdateKeyDescription",
4749 json!({ "KeyId": key_id, "Description": "updated again" }),
4750 );
4751 svc.update_key_description(&req).unwrap();
4752
4753 {
4754 let state = svc.state.read();
4755 let key = state.keys.get(&key_id).unwrap();
4756 assert_eq!(key.description, "updated again");
4757 }
4758 }
4759
4760 #[test]
4761 fn get_public_key_for_asymmetric_key() {
4762 let svc = make_service();
4763
4764 let key_id = create_key_with_opts(
4766 &svc,
4767 json!({ "KeyUsage": "SIGN_VERIFY", "KeySpec": "RSA_2048" }),
4768 );
4769
4770 let req = make_request("GetPublicKey", json!({ "KeyId": key_id }));
4771 let resp = svc.get_public_key(&req).unwrap();
4772 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4773
4774 assert!(body["PublicKey"].as_str().is_some());
4775 assert_eq!(body["KeySpec"].as_str().unwrap(), "RSA_2048");
4776 assert_eq!(body["KeyUsage"].as_str().unwrap(), "SIGN_VERIFY");
4777 assert!(body["SigningAlgorithms"].as_array().is_some());
4778 assert!(body["KeyId"].as_str().unwrap().contains(":key/"));
4779
4780 let ecc_key_id = create_key_with_opts(
4782 &svc,
4783 json!({ "KeyUsage": "SIGN_VERIFY", "KeySpec": "ECC_NIST_P256" }),
4784 );
4785
4786 let req = make_request("GetPublicKey", json!({ "KeyId": ecc_key_id }));
4787 let resp = svc.get_public_key(&req).unwrap();
4788 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4789 assert!(body["PublicKey"].as_str().is_some());
4790 assert_eq!(body["KeySpec"].as_str().unwrap(), "ECC_NIST_P256");
4791 }
4792}