1use alloy::primitives::keccak256;
34use async_trait::async_trait;
35use aws_config::{meta::region::RegionProviderChain, BehaviorVersion, Region};
36use aws_sdk_kms::{
37 primitives::Blob,
38 types::{MessageType, SigningAlgorithmSpec},
39 Client,
40};
41use once_cell::sync::Lazy;
42use serde::Serialize;
43use std::collections::HashMap;
44use tokio::sync::RwLock;
45
46use crate::{
47 models::{Address, AwsKmsSignerConfig},
48 services::signer::evm::utils::recover_evm_signature_from_der,
49 utils::{
50 self, derive_ethereum_address_from_der, derive_solana_address_from_der,
51 derive_stellar_address_from_der,
52 },
53};
54use tracing::debug;
55
56#[cfg(test)]
57use mockall::{automock, mock};
58
59#[derive(Clone, Debug, thiserror::Error, Serialize)]
60pub enum AwsKmsError {
61 #[error("AWS KMS response parse error: {0}")]
62 ParseError(String),
63 #[error("AWS KMS config error: {0}")]
64 ConfigError(String),
65 #[error("AWS KMS get error: {0}")]
66 GetError(String),
67 #[error("AWS KMS signing error: {0}")]
68 SignError(String),
69 #[error("AWS KMS permissions error: {0}")]
70 PermissionError(String),
71 #[error("AWS KMS public key error: {0}")]
72 RecoveryError(#[from] utils::Secp256k1Error),
73 #[error("AWS KMS conversion error: {0}")]
74 ConvertError(String),
75 #[error("AWS KMS Other error: {0}")]
76 Other(String),
77}
78
79pub type AwsKmsResult<T> = Result<T, AwsKmsError>;
80
81#[async_trait]
82#[cfg_attr(test, automock)]
83pub trait AwsKmsEvmService: Send + Sync {
84 async fn get_evm_address(&self) -> AwsKmsResult<Address>;
86 async fn sign_payload_evm(&self, payload: &[u8]) -> AwsKmsResult<Vec<u8>>;
96
97 async fn sign_hash_evm(&self, hash: &[u8; 32]) -> AwsKmsResult<Vec<u8>>;
107}
108
109#[async_trait]
110#[cfg_attr(test, automock)]
111pub trait AwsKmsK256: Send + Sync {
112 async fn get_der_public_key<'a, 'b>(&'a self, key_id: &'b str) -> AwsKmsResult<Vec<u8>>;
114 async fn sign_digest<'a, 'b>(
116 &'a self,
117 key_id: &'b str,
118 digest: [u8; 32],
119 ) -> AwsKmsResult<Vec<u8>>;
120}
121
122#[async_trait]
125#[cfg_attr(test, automock)]
126pub trait AwsKmsEd25519: Send + Sync {
127 async fn get_ed25519_public_key<'a, 'b>(&'a self, key_id: &'b str) -> AwsKmsResult<Vec<u8>>;
129 async fn sign_ed25519<'a, 'b>(
132 &'a self,
133 key_id: &'b str,
134 message: &'b [u8],
135 ) -> AwsKmsResult<Vec<u8>>;
136}
137
138#[async_trait]
140#[cfg_attr(test, automock)]
141pub trait AwsKmsSolanaService: Send + Sync {
142 async fn get_solana_address(&self) -> AwsKmsResult<Address>;
144 async fn sign_solana(&self, message: &[u8]) -> AwsKmsResult<Vec<u8>>;
146}
147
148#[async_trait]
150#[cfg_attr(test, automock)]
151pub trait AwsKmsStellarService: Send + Sync {
152 async fn get_stellar_address(&self) -> AwsKmsResult<Address>;
154 async fn sign_stellar(&self, message: &[u8]) -> AwsKmsResult<Vec<u8>>;
156}
157
158#[cfg(test)]
159mock! {
160 pub AwsKmsClient { }
161 impl Clone for AwsKmsClient {
162 fn clone(&self) -> Self;
163 }
164
165 #[async_trait]
166 impl AwsKmsK256 for AwsKmsClient {
167 async fn get_der_public_key<'a, 'b>(&'a self, key_id: &'b str) -> AwsKmsResult<Vec<u8>>;
168 async fn sign_digest<'a, 'b>(
169 &'a self,
170 key_id: &'b str,
171 digest: [u8; 32],
172 ) -> AwsKmsResult<Vec<u8>>;
173 }
174
175 #[async_trait]
176 impl AwsKmsEd25519 for AwsKmsClient {
177 async fn get_ed25519_public_key<'a, 'b>(&'a self, key_id: &'b str) -> AwsKmsResult<Vec<u8>>;
178 async fn sign_ed25519<'a, 'b>(
179 &'a self,
180 key_id: &'b str,
181 message: &'b [u8],
182 ) -> AwsKmsResult<Vec<u8>>;
183 }
184}
185
186static KMS_DER_PK_CACHE: Lazy<RwLock<HashMap<String, Vec<u8>>>> =
188 Lazy::new(|| RwLock::new(HashMap::new()));
189
190static KMS_ED25519_PK_CACHE: Lazy<RwLock<HashMap<String, Vec<u8>>>> =
192 Lazy::new(|| RwLock::new(HashMap::new()));
193
194#[derive(Debug, Clone)]
195pub struct AwsKmsClient {
196 inner: Client,
197}
198
199#[async_trait]
200impl AwsKmsK256 for AwsKmsClient {
201 async fn get_der_public_key<'a, 'b>(&'a self, key_id: &'b str) -> AwsKmsResult<Vec<u8>> {
202 let cached = {
204 let cache_read = KMS_DER_PK_CACHE.read().await;
205 cache_read.get(key_id).cloned()
206 };
207 if let Some(cached) = cached {
208 return Ok(cached);
209 }
210
211 let get_output = self
213 .inner
214 .get_public_key()
215 .key_id(key_id)
216 .send()
217 .await
218 .map_err(|e| {
219 AwsKmsError::GetError(format!(
220 "Failed to get secp256k1 public key for key '{key_id}': {e:?}"
221 ))
222 })?;
223
224 let der_pk_blob = get_output
225 .public_key
226 .ok_or(AwsKmsError::GetError(
227 "No public key blob found".to_string(),
228 ))?
229 .into_inner();
230
231 let mut cache_write = KMS_DER_PK_CACHE.write().await;
233 cache_write.insert(key_id.to_string(), der_pk_blob.clone());
234 drop(cache_write);
235
236 Ok(der_pk_blob)
237 }
238
239 async fn sign_digest<'a, 'b>(
240 &'a self,
241 key_id: &'b str,
242 digest: [u8; 32],
243 ) -> AwsKmsResult<Vec<u8>> {
244 let sign_result = self
246 .inner
247 .sign()
248 .key_id(key_id)
249 .signing_algorithm(SigningAlgorithmSpec::EcdsaSha256)
250 .message_type(MessageType::Digest)
251 .message(Blob::new(digest))
252 .send()
253 .await;
254
255 let der_signature = sign_result
257 .map_err(|e| AwsKmsError::PermissionError(e.to_string()))?
258 .signature
259 .ok_or(AwsKmsError::SignError(
260 "Signature not found in response".to_string(),
261 ))?
262 .into_inner();
263
264 Ok(der_signature)
265 }
266}
267
268#[async_trait]
269impl AwsKmsEd25519 for AwsKmsClient {
270 async fn get_ed25519_public_key<'a, 'b>(&'a self, key_id: &'b str) -> AwsKmsResult<Vec<u8>> {
271 let cached = {
273 let cache_read = KMS_ED25519_PK_CACHE.read().await;
274 cache_read.get(key_id).cloned()
275 };
276 if let Some(cached) = cached {
277 return Ok(cached);
278 }
279
280 let get_output = self
282 .inner
283 .get_public_key()
284 .key_id(key_id)
285 .send()
286 .await
287 .map_err(|e| {
288 AwsKmsError::GetError(format!(
289 "Failed to get Ed25519 public key for key '{key_id}': {e:?}"
290 ))
291 })?;
292
293 let der_pk_blob = get_output
294 .public_key
295 .ok_or(AwsKmsError::GetError(
296 "No public key blob found".to_string(),
297 ))?
298 .into_inner();
299
300 let mut cache_write = KMS_ED25519_PK_CACHE.write().await;
302 cache_write.insert(key_id.to_string(), der_pk_blob.clone());
303 drop(cache_write);
304
305 Ok(der_pk_blob)
306 }
307
308 async fn sign_ed25519<'a, 'b>(
309 &'a self,
310 key_id: &'b str,
311 message: &'b [u8],
312 ) -> AwsKmsResult<Vec<u8>> {
313 debug!("Signing Ed25519 message with AWS KMS, key_id: {}", key_id);
314
315 let sign_result = self
318 .inner
319 .sign()
320 .key_id(key_id)
321 .signing_algorithm(SigningAlgorithmSpec::Ed25519Sha512)
322 .message_type(MessageType::Raw)
323 .message(Blob::new(message))
324 .send()
325 .await;
326
327 let signature = sign_result
329 .map_err(|e| AwsKmsError::SignError(e.to_string()))?
330 .signature
331 .ok_or(AwsKmsError::SignError(
332 "Signature not found in response".to_string(),
333 ))?
334 .into_inner();
335
336 if signature.len() != 64 {
338 return Err(AwsKmsError::SignError(format!(
339 "Invalid Ed25519 signature length: expected 64 bytes, got {}",
340 signature.len()
341 )));
342 }
343
344 Ok(signature)
345 }
346}
347
348#[derive(Debug, Clone)]
349pub struct AwsKmsService<T: AwsKmsK256 + AwsKmsEd25519 + Clone = AwsKmsClient> {
350 pub kms_key_id: String,
351 client: T,
352}
353
354impl AwsKmsService<AwsKmsClient> {
355 pub async fn new(config: AwsKmsSignerConfig) -> AwsKmsResult<Self> {
356 let region_provider =
357 RegionProviderChain::first_try(config.region.map(Region::new)).or_default_provider();
358
359 let auth_config = aws_config::defaults(BehaviorVersion::latest())
360 .region(region_provider)
361 .load()
362 .await;
363 let client = AwsKmsClient {
364 inner: Client::new(&auth_config),
365 };
366
367 Ok(Self {
368 kms_key_id: config.key_id,
369 client,
370 })
371 }
372}
373
374#[cfg(test)]
375impl<T: AwsKmsK256 + AwsKmsEd25519 + Clone> AwsKmsService<T> {
376 pub fn new_for_testing(client: T, config: AwsKmsSignerConfig) -> Self {
377 Self {
378 client,
379 kms_key_id: config.key_id,
380 }
381 }
382}
383
384impl<T: AwsKmsK256 + AwsKmsEd25519 + Clone> AwsKmsService<T> {
385 async fn sign_and_recover_evm(
394 &self,
395 digest: [u8; 32],
396 original_bytes: &[u8],
397 use_prehash_recovery: bool,
398 ) -> AwsKmsResult<Vec<u8>> {
399 let der_signature = self.client.sign_digest(&self.kms_key_id, digest).await?;
401
402 let der_pk = self.client.get_der_public_key(&self.kms_key_id).await?;
404
405 recover_evm_signature_from_der(
407 &der_signature,
408 &der_pk,
409 digest,
410 original_bytes,
411 use_prehash_recovery,
412 )
413 .map_err(|e| AwsKmsError::ParseError(e.to_string()))
414 }
415
416 pub async fn sign_payload_evm(&self, bytes: &[u8]) -> AwsKmsResult<Vec<u8>> {
426 let digest = keccak256(bytes).0;
427 self.sign_and_recover_evm(digest, bytes, false).await
428 }
429
430 pub async fn sign_hash_evm(&self, hash: &[u8; 32]) -> AwsKmsResult<Vec<u8>> {
440 self.sign_and_recover_evm(*hash, hash, true).await
441 }
442}
443
444#[async_trait]
445impl<T: AwsKmsK256 + AwsKmsEd25519 + Clone> AwsKmsEvmService for AwsKmsService<T> {
446 async fn get_evm_address(&self) -> AwsKmsResult<Address> {
447 let der = self.client.get_der_public_key(&self.kms_key_id).await?;
448 let eth_address = derive_ethereum_address_from_der(&der)
449 .map_err(|e| AwsKmsError::ParseError(e.to_string()))?;
450 Ok(Address::Evm(eth_address))
451 }
452
453 async fn sign_payload_evm(&self, message: &[u8]) -> AwsKmsResult<Vec<u8>> {
454 let digest = keccak256(message).0;
455 self.sign_and_recover_evm(digest, message, false).await
456 }
457
458 async fn sign_hash_evm(&self, hash: &[u8; 32]) -> AwsKmsResult<Vec<u8>> {
459 self.sign_and_recover_evm(*hash, hash, true).await
461 }
462}
463
464#[async_trait]
465impl<T: AwsKmsK256 + AwsKmsEd25519 + Clone> AwsKmsSolanaService for AwsKmsService<T> {
466 async fn get_solana_address(&self) -> AwsKmsResult<Address> {
467 let der = self.client.get_ed25519_public_key(&self.kms_key_id).await?;
468 let solana_address = derive_solana_address_from_der(&der)
469 .map_err(|e| AwsKmsError::ParseError(e.to_string()))?;
470 Ok(Address::Solana(solana_address))
471 }
472
473 async fn sign_solana(&self, message: &[u8]) -> AwsKmsResult<Vec<u8>> {
474 self.client.sign_ed25519(&self.kms_key_id, message).await
475 }
476}
477
478#[async_trait]
479impl<T: AwsKmsK256 + AwsKmsEd25519 + Clone> AwsKmsStellarService for AwsKmsService<T> {
480 async fn get_stellar_address(&self) -> AwsKmsResult<Address> {
481 let der = self.client.get_ed25519_public_key(&self.kms_key_id).await?;
482 let stellar_address = derive_stellar_address_from_der(&der)
483 .map_err(|e| AwsKmsError::ParseError(e.to_string()))?;
484 Ok(Address::Stellar(stellar_address))
485 }
486
487 async fn sign_stellar(&self, message: &[u8]) -> AwsKmsResult<Vec<u8>> {
488 self.client.sign_ed25519(&self.kms_key_id, message).await
489 }
490}
491
492#[cfg(test)]
493pub mod tests {
494 use super::*;
495
496 use alloy::primitives::utils::eip191_message;
497 use k256::{
498 ecdsa::SigningKey,
499 elliptic_curve::rand_core::OsRng,
500 pkcs8::{der::Encode, EncodePublicKey},
501 };
502 use mockall::predicate::{eq, ne};
503
504 pub struct TestEd25519Keys {
506 pub public_key_der: Vec<u8>,
507 pub public_key_raw: [u8; 32],
508 }
509
510 impl Default for TestEd25519Keys {
511 fn default() -> Self {
512 Self::new()
513 }
514 }
515
516 impl TestEd25519Keys {
517 pub fn new() -> Self {
518 let public_key_raw: [u8; 32] = [
520 0x9d, 0x45, 0x7e, 0x45, 0xe4, 0x16, 0xc4, 0xc6, 0x77, 0x67, 0x6a, 0x42, 0xff, 0x96,
521 0x8e, 0x3c, 0xf8, 0xdc, 0x73, 0xc8, 0xf3, 0x3a, 0x8d, 0x19, 0x81, 0x29, 0x7b, 0xfa,
522 0x3e, 0x00, 0x30, 0xba,
523 ];
524
525 let mut public_key_der = vec![
527 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00, ];
533 public_key_der.extend_from_slice(&public_key_raw);
534
535 Self {
536 public_key_der,
537 public_key_raw,
538 }
539 }
540 }
541
542 pub fn setup_mock_kms_client() -> (MockAwsKmsClient, SigningKey) {
543 let mut client = MockAwsKmsClient::new();
544 let signing_key = SigningKey::random(&mut OsRng);
545 let s = signing_key
546 .verifying_key()
547 .to_public_key_der()
548 .unwrap()
549 .to_der()
550 .unwrap();
551
552 client
553 .expect_get_der_public_key()
554 .with(eq("test-key-id"))
555 .return_const(Ok(s));
556 client
557 .expect_get_der_public_key()
558 .with(ne("test-key-id"))
559 .return_const(Err(AwsKmsError::GetError("Key does not exist".to_string())));
560
561 client
562 .expect_sign_digest()
563 .withf(|key_id, _| key_id.ne("test-key-id"))
564 .return_const(Err(AwsKmsError::SignError(
565 "Key does not exist".to_string(),
566 )));
567
568 let key = signing_key.clone();
569 client
570 .expect_sign_digest()
571 .withf(|key_id, _| key_id.eq("test-key-id"))
572 .returning(move |_, digest| {
573 let (signature, _) = signing_key
574 .sign_prehash_recoverable(&digest)
575 .map_err(|e| AwsKmsError::SignError(e.to_string()))?;
576 let der_signature = signature.to_der().as_bytes().to_vec();
577 Ok(der_signature)
578 });
579
580 let test_ed25519_keys = TestEd25519Keys::new();
582 client
583 .expect_get_ed25519_public_key()
584 .with(eq("test-key-id"))
585 .return_const(Ok(test_ed25519_keys.public_key_der.clone()));
586 client
587 .expect_get_ed25519_public_key()
588 .with(ne("test-key-id"))
589 .return_const(Err(AwsKmsError::GetError("Key does not exist".to_string())));
590
591 client
593 .expect_sign_ed25519()
594 .withf(|key_id, _| key_id.eq("test-key-id"))
595 .returning(|_, _| Ok(vec![0u8; 64]));
596 client
597 .expect_sign_ed25519()
598 .withf(|key_id, _| key_id.ne("test-key-id"))
599 .return_const(Err(AwsKmsError::SignError(
600 "Key does not exist".to_string(),
601 )));
602
603 client.expect_clone().return_once(MockAwsKmsClient::new);
604
605 (client, key)
606 }
607
608 #[tokio::test]
609 async fn test_get_public_key() {
610 let (mock_client, key) = setup_mock_kms_client();
611 let kms = AwsKmsService::new_for_testing(
612 mock_client,
613 AwsKmsSignerConfig {
614 region: Some("us-east-1".to_string()),
615 key_id: "test-key-id".to_string(),
616 },
617 );
618
619 let result = kms.get_evm_address().await;
620 assert!(result.is_ok());
621 if let Ok(Address::Evm(evm_address)) = result {
622 let expected_address = derive_ethereum_address_from_der(
623 key.verifying_key().to_public_key_der().unwrap().as_bytes(),
624 )
625 .unwrap();
626 assert_eq!(expected_address, evm_address);
627 }
628 }
629
630 #[tokio::test]
631 async fn test_get_public_key_fail() {
632 let (mock_client, _) = setup_mock_kms_client();
633 let kms = AwsKmsService::new_for_testing(
634 mock_client,
635 AwsKmsSignerConfig {
636 region: Some("us-east-1".to_string()),
637 key_id: "invalid-key-id".to_string(),
638 },
639 );
640
641 let result = kms.get_evm_address().await;
642 assert!(result.is_err());
643 if let Err(err) = result {
644 assert!(matches!(err, AwsKmsError::GetError(_)))
645 }
646 }
647
648 #[tokio::test]
649 async fn test_sign_digest() {
650 let (mock_client, _) = setup_mock_kms_client();
651 let kms = AwsKmsService::new_for_testing(
652 mock_client,
653 AwsKmsSignerConfig {
654 region: Some("us-east-1".to_string()),
655 key_id: "test-key-id".to_string(),
656 },
657 );
658
659 let message_eip = eip191_message(b"Hello World!");
660 let result = kms.sign_payload_evm(&message_eip).await;
661
662 assert!(result.is_ok());
664 }
665
666 #[tokio::test]
667 async fn test_sign_digest_fail() {
668 let (mock_client, _) = setup_mock_kms_client();
669 let kms = AwsKmsService::new_for_testing(
670 mock_client,
671 AwsKmsSignerConfig {
672 region: Some("us-east-1".to_string()),
673 key_id: "invalid-key-id".to_string(),
674 },
675 );
676
677 let message_eip = eip191_message(b"Hello World!");
678 let result = kms.sign_payload_evm(&message_eip).await;
679 assert!(result.is_err());
680 if let Err(err) = result {
681 assert!(matches!(err, AwsKmsError::SignError(_)))
682 }
683 }
684
685 #[tokio::test]
686 async fn test_get_solana_address() {
687 let (mock_client, _) = setup_mock_kms_client();
688 let kms = AwsKmsService::new_for_testing(
689 mock_client,
690 AwsKmsSignerConfig {
691 region: Some("us-east-1".to_string()),
692 key_id: "test-key-id".to_string(),
693 },
694 );
695
696 let result = kms.get_solana_address().await;
697 assert!(result.is_ok());
698 if let Ok(Address::Solana(solana_address)) = result {
699 assert!(!solana_address.is_empty());
701 assert!(solana_address.len() >= 32 && solana_address.len() <= 44);
702 let test_keys = TestEd25519Keys::new();
704 let expected_address = bs58::encode(test_keys.public_key_raw).into_string();
705 assert_eq!(solana_address, expected_address);
706 } else {
707 panic!("Expected Solana address");
708 }
709 }
710
711 #[tokio::test]
712 async fn test_get_solana_address_fail() {
713 let (mock_client, _) = setup_mock_kms_client();
714 let kms = AwsKmsService::new_for_testing(
715 mock_client,
716 AwsKmsSignerConfig {
717 region: Some("us-east-1".to_string()),
718 key_id: "invalid-key-id".to_string(),
719 },
720 );
721
722 let result = kms.get_solana_address().await;
723 assert!(result.is_err());
724 if let Err(err) = result {
725 assert!(matches!(err, AwsKmsError::GetError(_)))
726 }
727 }
728
729 #[tokio::test]
730 async fn test_sign_solana() {
731 let (mock_client, _) = setup_mock_kms_client();
732 let kms = AwsKmsService::new_for_testing(
733 mock_client,
734 AwsKmsSignerConfig {
735 region: Some("us-east-1".to_string()),
736 key_id: "test-key-id".to_string(),
737 },
738 );
739
740 let message = b"Test Solana message";
741 let result = kms.sign_solana(message).await;
742 assert!(result.is_ok());
743 let signature = result.unwrap();
744 assert_eq!(signature.len(), 64); }
746
747 #[tokio::test]
748 async fn test_sign_solana_fail() {
749 let (mock_client, _) = setup_mock_kms_client();
750 let kms = AwsKmsService::new_for_testing(
751 mock_client,
752 AwsKmsSignerConfig {
753 region: Some("us-east-1".to_string()),
754 key_id: "invalid-key-id".to_string(),
755 },
756 );
757
758 let message = b"Test Solana message";
759 let result = kms.sign_solana(message).await;
760 assert!(result.is_err());
761 if let Err(err) = result {
762 assert!(matches!(err, AwsKmsError::SignError(_)))
763 }
764 }
765
766 #[tokio::test]
767 async fn test_get_stellar_address() {
768 let (mock_client, _) = setup_mock_kms_client();
769 let kms = AwsKmsService::new_for_testing(
770 mock_client,
771 AwsKmsSignerConfig {
772 region: Some("us-east-1".to_string()),
773 key_id: "test-key-id".to_string(),
774 },
775 );
776
777 let result = kms.get_stellar_address().await;
778 assert!(result.is_ok());
779 if let Ok(Address::Stellar(stellar_address)) = result {
780 assert!(stellar_address.starts_with('G'));
782 assert_eq!(stellar_address.len(), 56);
784 } else {
785 panic!("Expected Stellar address");
786 }
787 }
788
789 #[tokio::test]
790 async fn test_get_stellar_address_fail() {
791 let (mock_client, _) = setup_mock_kms_client();
792 let kms = AwsKmsService::new_for_testing(
793 mock_client,
794 AwsKmsSignerConfig {
795 region: Some("us-east-1".to_string()),
796 key_id: "invalid-key-id".to_string(),
797 },
798 );
799
800 let result = kms.get_stellar_address().await;
801 assert!(result.is_err());
802 if let Err(err) = result {
803 assert!(matches!(err, AwsKmsError::GetError(_)))
804 }
805 }
806
807 #[tokio::test]
808 async fn test_sign_stellar() {
809 let (mock_client, _) = setup_mock_kms_client();
810 let kms = AwsKmsService::new_for_testing(
811 mock_client,
812 AwsKmsSignerConfig {
813 region: Some("us-east-1".to_string()),
814 key_id: "test-key-id".to_string(),
815 },
816 );
817
818 let message = b"Test Stellar message";
819 let result = kms.sign_stellar(message).await;
820 assert!(result.is_ok());
821 let signature = result.unwrap();
822 assert_eq!(signature.len(), 64); }
824
825 #[tokio::test]
826 async fn test_sign_stellar_fail() {
827 let (mock_client, _) = setup_mock_kms_client();
828 let kms = AwsKmsService::new_for_testing(
829 mock_client,
830 AwsKmsSignerConfig {
831 region: Some("us-east-1".to_string()),
832 key_id: "invalid-key-id".to_string(),
833 },
834 );
835
836 let message = b"Test Stellar message";
837 let result = kms.sign_stellar(message).await;
838 assert!(result.is_err());
839 if let Err(err) = result {
840 assert!(matches!(err, AwsKmsError::SignError(_)))
841 }
842 }
843
844 }