1use auths_verifier::core::Ed25519PublicKey;
4
5use crate::crypto::provider_bridge;
6use crate::crypto::signer::{decrypt_keypair, extract_seed_from_key_bytes};
7use crate::error::AgentError;
8use crate::storage::keychain::{IdentityDID, KeyAlias, KeyStorage};
9
10use crate::config::PassphraseCachePolicy;
11use crate::storage::passphrase_cache::PassphraseCache;
12
13use std::collections::HashMap;
14use std::sync::{Arc, Mutex};
15use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
16use zeroize::Zeroizing;
17
18type PassphraseCallback = dyn Fn(&str) -> Result<Zeroizing<String>, AgentError> + Send + Sync;
20
21#[derive(Debug, thiserror::Error)]
34#[non_exhaustive]
35pub enum DidResolverError {
36 #[error("Unsupported DID method: {0}")]
38 UnsupportedMethod(String),
39
40 #[error("Invalid did:key format: {0}")]
42 InvalidDidKey(String),
43
44 #[error("Invalid did:key format: {0}")]
46 InvalidDidKeyFormat(String),
47
48 #[error("did:key decoding failed: {0}")]
50 DidKeyDecodingFailed(String),
51
52 #[error("Invalid did:key multicodec prefix")]
54 InvalidDidKeyMulticodec,
55
56 #[error("Resolution error: {0}")]
58 Resolution(String),
59
60 #[error("Repository error: {0}")]
62 Repository(String),
63}
64
65#[derive(Debug, Clone)]
79pub enum ResolvedDid {
80 Key {
82 did: String,
84 public_key: Ed25519PublicKey,
86 },
87 Keri {
89 did: String,
91 public_key: Ed25519PublicKey,
93 sequence: u64,
95 can_rotate: bool,
97 },
98}
99
100impl ResolvedDid {
101 pub fn did(&self) -> &str {
103 match self {
104 ResolvedDid::Key { did, .. } | ResolvedDid::Keri { did, .. } => did,
105 }
106 }
107
108 pub fn public_key(&self) -> &Ed25519PublicKey {
110 match self {
111 ResolvedDid::Key { public_key, .. } | ResolvedDid::Keri { public_key, .. } => {
112 public_key
113 }
114 }
115 }
116
117 pub fn is_key(&self) -> bool {
119 matches!(self, ResolvedDid::Key { .. })
120 }
121
122 pub fn is_keri(&self) -> bool {
124 matches!(self, ResolvedDid::Keri { .. })
125 }
126}
127
128pub trait DidResolver: Send + Sync {
153 fn resolve(&self, did: &str) -> Result<ResolvedDid, DidResolverError>;
155}
156
157pub trait PassphraseProvider: Send + Sync {
163 fn get_passphrase(&self, prompt_message: &str) -> Result<Zeroizing<String>, AgentError>;
173
174 fn on_incorrect_passphrase(&self, _prompt_message: &str) {}
183}
184
185pub trait SecureSigner: Send + Sync {
188 fn sign_with_alias(
203 &self,
204 alias: &KeyAlias,
205 passphrase_provider: &dyn PassphraseProvider,
206 message: &[u8],
207 ) -> Result<Vec<u8>, AgentError>;
208
209 fn sign_for_identity(
228 &self,
229 identity_did: &IdentityDID,
230 passphrase_provider: &dyn PassphraseProvider,
231 message: &[u8],
232 ) -> Result<Vec<u8>, AgentError>;
233}
234
235pub struct StorageSigner<S: KeyStorage> {
240 storage: S,
242}
243
244impl<S: KeyStorage> StorageSigner<S> {
245 pub fn new(storage: S) -> Self {
247 Self { storage }
248 }
249
250 pub fn inner(&self) -> &S {
252 &self.storage
253 }
254}
255
256impl<S: KeyStorage + Send + Sync + 'static> SecureSigner for StorageSigner<S> {
257 fn sign_with_alias(
258 &self,
259 alias: &KeyAlias,
260 passphrase_provider: &dyn PassphraseProvider,
261 message: &[u8],
262 ) -> Result<Vec<u8>, AgentError> {
263 let (_identity_did, _role, encrypted_data) = self.storage.load_key(alias)?;
264
265 const MAX_ATTEMPTS: u8 = 3;
266 let mut attempt = 0u8;
267 let key_bytes = loop {
268 let prompt = if attempt == 0 {
269 format!("Enter passphrase for key '{}' to sign:", alias)
270 } else {
271 format!(
272 "Incorrect passphrase, try again ({}/{}):",
273 attempt + 1,
274 MAX_ATTEMPTS
275 )
276 };
277
278 let passphrase = passphrase_provider.get_passphrase(&prompt)?;
279
280 match decrypt_keypair(&encrypted_data, &passphrase) {
281 Ok(kb) => break kb,
282 Err(AgentError::IncorrectPassphrase) if attempt + 1 < MAX_ATTEMPTS => {
283 passphrase_provider.on_incorrect_passphrase(&prompt);
284 attempt += 1;
285 }
286 Err(e) => return Err(e),
287 }
288 };
289
290 let seed = extract_seed_from_key_bytes(&key_bytes)?;
291
292 provider_bridge::sign_ed25519_sync(&seed, message)
293 .map_err(|e| AgentError::CryptoError(format!("Ed25519 signing failed: {}", e)))
294 }
295
296 fn sign_for_identity(
297 &self,
298 identity_did: &IdentityDID,
299 passphrase_provider: &dyn PassphraseProvider,
300 message: &[u8],
301 ) -> Result<Vec<u8>, AgentError> {
302 let aliases = self.storage.list_aliases_for_identity(identity_did)?;
304
305 let alias = aliases.first().ok_or(AgentError::KeyNotFound)?;
307
308 self.sign_with_alias(alias, passphrase_provider, message)
310 }
311}
312
313pub struct CallbackPassphraseProvider {
329 callback: Box<PassphraseCallback>,
330}
331
332impl CallbackPassphraseProvider {
333 pub fn new<F>(callback: F) -> Self
338 where
339 F: Fn(&str) -> Result<Zeroizing<String>, AgentError> + Send + Sync + 'static,
340 {
341 Self {
342 callback: Box::new(callback),
343 }
344 }
345}
346
347impl PassphraseProvider for CallbackPassphraseProvider {
348 fn get_passphrase(&self, prompt_message: &str) -> Result<Zeroizing<String>, AgentError> {
349 (self.callback)(prompt_message)
350 }
351}
352
353pub struct CachedPassphraseProvider {
366 inner: Arc<dyn PassphraseProvider + Send + Sync>,
367 cache: Mutex<HashMap<String, (Zeroizing<String>, Instant)>>,
368 ttl: Duration,
369}
370
371impl CachedPassphraseProvider {
372 pub fn new(inner: Arc<dyn PassphraseProvider + Send + Sync>, ttl: Duration) -> Self {
378 Self {
379 inner,
380 cache: Mutex::new(HashMap::new()),
381 ttl,
382 }
383 }
384
385 pub fn clear_cache(&self) {
390 self.cache.lock().unwrap_or_else(|e| e.into_inner()).clear();
391 }
392}
393
394impl PassphraseProvider for CachedPassphraseProvider {
395 fn get_passphrase(&self, prompt_message: &str) -> Result<Zeroizing<String>, AgentError> {
396 let mut cache = self
397 .cache
398 .lock()
399 .map_err(|e| AgentError::MutexError(e.to_string()))?;
400
401 if let Some((passphrase, cached_at)) = cache.get(prompt_message) {
403 if cached_at.elapsed() < self.ttl {
404 return Ok(passphrase.clone());
406 }
407 cache.remove(prompt_message);
409 }
410
411 drop(cache); let passphrase = self.inner.get_passphrase(prompt_message)?;
414
415 let mut cache = self
417 .cache
418 .lock()
419 .map_err(|e| AgentError::MutexError(e.to_string()))?;
420 cache.insert(
421 prompt_message.to_string(),
422 (passphrase.clone(), Instant::now()),
423 );
424 Ok(passphrase)
425 }
426
427 fn on_incorrect_passphrase(&self, prompt_message: &str) {
428 self.cache
429 .lock()
430 .unwrap_or_else(|e| e.into_inner())
431 .remove(prompt_message);
432 }
433}
434
435pub struct KeychainPassphraseProvider {
464 inner: Arc<dyn PassphraseProvider + Send + Sync>,
465 cache: Box<dyn PassphraseCache>,
466 alias: String,
467 policy: PassphraseCachePolicy,
468 ttl_secs: Option<i64>,
469}
470
471impl KeychainPassphraseProvider {
472 pub fn new(
481 inner: Arc<dyn PassphraseProvider + Send + Sync>,
482 cache: Box<dyn PassphraseCache>,
483 alias: String,
484 policy: PassphraseCachePolicy,
485 ttl_secs: Option<i64>,
486 ) -> Self {
487 Self {
488 inner,
489 cache,
490 alias,
491 policy,
492 ttl_secs,
493 }
494 }
495
496 #[allow(clippy::disallowed_methods)] fn is_expired(&self, stored_at_unix: i64) -> bool {
498 match self.policy {
499 PassphraseCachePolicy::Always => false,
500 PassphraseCachePolicy::Never => true,
501 PassphraseCachePolicy::Session => true,
502 PassphraseCachePolicy::Duration => {
503 let ttl = self.ttl_secs.unwrap_or(3600);
504 let now = SystemTime::now()
505 .duration_since(UNIX_EPOCH)
506 .unwrap_or_default()
507 .as_secs() as i64;
508 now - stored_at_unix > ttl
509 }
510 }
511 }
512}
513
514impl PassphraseProvider for KeychainPassphraseProvider {
515 #[allow(clippy::disallowed_methods)] fn get_passphrase(&self, prompt_message: &str) -> Result<Zeroizing<String>, AgentError> {
517 if self.policy != PassphraseCachePolicy::Never
518 && let Ok(Some((passphrase, stored_at))) = self.cache.load(&self.alias)
519 {
520 if !self.is_expired(stored_at) {
521 return Ok(passphrase);
522 }
523 let _ = self.cache.delete(&self.alias);
524 }
525
526 let passphrase = self.inner.get_passphrase(prompt_message)?;
527
528 if self.policy != PassphraseCachePolicy::Never
529 && self.policy != PassphraseCachePolicy::Session
530 {
531 let now = SystemTime::now()
532 .duration_since(UNIX_EPOCH)
533 .unwrap_or_default()
534 .as_secs() as i64;
535 let _ = self.cache.store(&self.alias, &passphrase, now);
536 }
537
538 Ok(passphrase)
539 }
540
541 fn on_incorrect_passphrase(&self, prompt_message: &str) {
542 let _ = self.cache.delete(&self.alias);
543 self.inner.on_incorrect_passphrase(prompt_message);
544 }
545}
546
547pub struct PrefilledPassphraseProvider {
565 passphrase: Zeroizing<String>,
566}
567
568impl PrefilledPassphraseProvider {
569 pub fn new(passphrase: &str) -> Self {
579 Self {
580 passphrase: Zeroizing::new(passphrase.to_string()),
581 }
582 }
583}
584
585impl PassphraseProvider for PrefilledPassphraseProvider {
586 fn get_passphrase(&self, _prompt_message: &str) -> Result<Zeroizing<String>, AgentError> {
587 Ok(self.passphrase.clone())
588 }
589}
590
591pub struct UnifiedPassphraseProvider {
596 inner: Arc<dyn PassphraseProvider + Send + Sync>,
597 cached: Mutex<Option<Zeroizing<String>>>,
598}
599
600impl UnifiedPassphraseProvider {
601 pub fn new(inner: Arc<dyn PassphraseProvider + Send + Sync>) -> Self {
603 Self {
604 inner,
605 cached: Mutex::new(None),
606 }
607 }
608}
609
610impl PassphraseProvider for UnifiedPassphraseProvider {
611 fn get_passphrase(&self, prompt_message: &str) -> Result<Zeroizing<String>, AgentError> {
612 let mut guard = self
613 .cached
614 .lock()
615 .map_err(|e| AgentError::MutexError(e.to_string()))?;
616 if let Some(ref cached) = *guard {
617 return Ok(Zeroizing::new(cached.as_str().to_string()));
618 }
619 let passphrase = self.inner.get_passphrase(prompt_message)?;
620 *guard = Some(Zeroizing::new(passphrase.as_str().to_string()));
621 Ok(passphrase)
622 }
623}
624
625#[cfg(test)]
626mod tests {
627 use super::*;
628 use crate::crypto::signer::encrypt_keypair;
629 use ring::rand::SystemRandom;
630 use ring::signature::{ED25519, Ed25519KeyPair, KeyPair, UnparsedPublicKey};
631 use std::collections::HashMap;
632 use std::sync::Mutex;
633
634 use crate::storage::keychain::KeyRole;
635
636 struct MockKeyStorage {
638 #[allow(clippy::type_complexity)]
639 keys: Mutex<HashMap<String, (IdentityDID, KeyRole, Vec<u8>)>>,
640 }
641
642 impl MockKeyStorage {
643 fn new() -> Self {
644 Self {
645 keys: Mutex::new(HashMap::new()),
646 }
647 }
648 }
649
650 impl KeyStorage for MockKeyStorage {
651 fn store_key(
652 &self,
653 alias: &KeyAlias,
654 identity_did: &IdentityDID,
655 role: KeyRole,
656 encrypted_key_data: &[u8],
657 ) -> Result<(), AgentError> {
658 self.keys.lock().unwrap().insert(
659 alias.as_str().to_string(),
660 (identity_did.clone(), role, encrypted_key_data.to_vec()),
661 );
662 Ok(())
663 }
664
665 fn load_key(
666 &self,
667 alias: &KeyAlias,
668 ) -> Result<(IdentityDID, KeyRole, Vec<u8>), AgentError> {
669 self.keys
670 .lock()
671 .unwrap()
672 .get(alias.as_str())
673 .cloned()
674 .ok_or(AgentError::KeyNotFound)
675 }
676
677 fn delete_key(&self, alias: &KeyAlias) -> Result<(), AgentError> {
678 self.keys
679 .lock()
680 .unwrap()
681 .remove(alias.as_str())
682 .map(|_| ())
683 .ok_or(AgentError::KeyNotFound)
684 }
685
686 fn list_aliases(&self) -> Result<Vec<KeyAlias>, AgentError> {
687 Ok(self
688 .keys
689 .lock()
690 .unwrap()
691 .keys()
692 .map(|s| KeyAlias::new_unchecked(s.clone()))
693 .collect())
694 }
695
696 fn list_aliases_for_identity(
697 &self,
698 identity_did: &IdentityDID,
699 ) -> Result<Vec<KeyAlias>, AgentError> {
700 Ok(self
701 .keys
702 .lock()
703 .unwrap()
704 .iter()
705 .filter(|(_, (did, _role, _))| did == identity_did)
706 .map(|(alias, _)| KeyAlias::new_unchecked(alias.clone()))
707 .collect())
708 }
709
710 fn get_identity_for_alias(&self, alias: &KeyAlias) -> Result<IdentityDID, AgentError> {
711 self.keys
712 .lock()
713 .unwrap()
714 .get(alias.as_str())
715 .map(|(did, _role, _)| did.clone())
716 .ok_or(AgentError::KeyNotFound)
717 }
718
719 fn backend_name(&self) -> &'static str {
720 "MockKeyStorage"
721 }
722 }
723
724 struct MockPassphraseProvider {
726 passphrase: String,
727 }
728
729 impl MockPassphraseProvider {
730 fn new(passphrase: &str) -> Self {
731 Self {
732 passphrase: passphrase.to_string(),
733 }
734 }
735 }
736
737 impl PassphraseProvider for MockPassphraseProvider {
738 fn get_passphrase(&self, _prompt_message: &str) -> Result<Zeroizing<String>, AgentError> {
739 Ok(Zeroizing::new(self.passphrase.clone()))
740 }
741 }
742
743 fn generate_test_keypair() -> (Vec<u8>, Vec<u8>) {
744 let rng = SystemRandom::new();
745 let pkcs8_doc = Ed25519KeyPair::generate_pkcs8(&rng).expect("Failed to generate PKCS#8");
746 let pkcs8_bytes = pkcs8_doc.as_ref().to_vec();
747 let keypair = Ed25519KeyPair::from_pkcs8(&pkcs8_bytes).expect("Failed to parse PKCS#8");
748 let pubkey_bytes = keypair.public_key().as_ref().to_vec();
749 (pkcs8_bytes, pubkey_bytes)
750 }
751
752 #[test]
753 fn test_sign_for_identity_success() {
754 let (pkcs8_bytes, pubkey_bytes) = generate_test_keypair();
755 let passphrase = "Test-P@ss12345";
756 let identity_did = IdentityDID::new("did:keri:ABC123");
757 let alias = KeyAlias::new_unchecked("test-key-alias");
758
759 let encrypted = encrypt_keypair(&pkcs8_bytes, passphrase).expect("Failed to encrypt");
761
762 let storage = MockKeyStorage::new();
764 storage
765 .store_key(&alias, &identity_did, KeyRole::Primary, &encrypted)
766 .expect("Failed to store key");
767
768 let signer = StorageSigner::new(storage);
770 let passphrase_provider = MockPassphraseProvider::new(passphrase);
771
772 let message = b"test message for sign_for_identity";
774 let signature = signer
775 .sign_for_identity(&identity_did, &passphrase_provider, message)
776 .expect("Signing failed");
777
778 let public_key = UnparsedPublicKey::new(&ED25519, &pubkey_bytes);
780 assert!(public_key.verify(message, &signature).is_ok());
781 }
782
783 #[test]
784 fn test_sign_for_identity_no_key_for_identity() {
785 let storage = MockKeyStorage::new();
786 let signer = StorageSigner::new(storage);
787 let passphrase_provider = MockPassphraseProvider::new("any-passphrase");
788
789 let identity_did = IdentityDID::new("did:keri:NONEXISTENT");
790 let message = b"test message";
791
792 let result = signer.sign_for_identity(&identity_did, &passphrase_provider, message);
793 assert!(matches!(result, Err(AgentError::KeyNotFound)));
794 }
795
796 #[test]
797 fn test_sign_for_identity_multiple_aliases() {
798 let (pkcs8_bytes, pubkey_bytes) = generate_test_keypair();
800 let passphrase = "Test-P@ss12345";
801 let identity_did = IdentityDID::new("did:keri:MULTI123");
802
803 let encrypted = encrypt_keypair(&pkcs8_bytes, passphrase).expect("Failed to encrypt");
804
805 let storage = MockKeyStorage::new();
806 let alias = KeyAlias::new_unchecked("primary-alias");
808 storage
809 .store_key(&alias, &identity_did, KeyRole::Primary, &encrypted)
810 .expect("Failed to store key");
811
812 let signer = StorageSigner::new(storage);
813 let passphrase_provider = MockPassphraseProvider::new(passphrase);
814
815 let message = b"test message with multiple aliases";
816 let signature = signer
817 .sign_for_identity(&identity_did, &passphrase_provider, message)
818 .expect("Signing should succeed");
819
820 let public_key = UnparsedPublicKey::new(&ED25519, &pubkey_bytes);
822 assert!(public_key.verify(message, &signature).is_ok());
823 }
824
825 #[test]
826 fn test_callback_passphrase_provider() {
827 use std::sync::Arc;
828 use std::sync::atomic::{AtomicUsize, Ordering};
829
830 let call_count = Arc::new(AtomicUsize::new(0));
832 let call_count_clone = Arc::clone(&call_count);
833
834 let provider = CallbackPassphraseProvider::new(move |prompt| {
835 call_count_clone.fetch_add(1, Ordering::SeqCst);
836 assert!(prompt.contains("test-alias"));
837 Ok(Zeroizing::new("callback-passphrase".to_string()))
838 });
839
840 let result = provider.get_passphrase("Enter passphrase for test-alias:");
842 assert!(result.is_ok());
843 assert_eq!(*result.unwrap(), "callback-passphrase");
844 assert_eq!(call_count.load(Ordering::SeqCst), 1);
845
846 let result2 = provider.get_passphrase("Another prompt for test-alias");
848 assert!(result2.is_ok());
849 assert_eq!(call_count.load(Ordering::SeqCst), 2);
850 }
851
852 #[test]
853 fn test_callback_passphrase_provider_error() {
854 let provider =
855 CallbackPassphraseProvider::new(|_prompt| Err(AgentError::UserInputCancelled));
856
857 let result = provider.get_passphrase("Enter passphrase:");
858 assert!(matches!(result, Err(AgentError::UserInputCancelled)));
859 }
860
861 #[test]
862 fn test_cached_passphrase_provider_cache_hit() {
863 use std::sync::Arc;
864 use std::sync::atomic::{AtomicUsize, Ordering};
865 use std::time::Duration;
866
867 let call_count = Arc::new(AtomicUsize::new(0));
868 let call_count_clone = Arc::clone(&call_count);
869
870 let inner = Arc::new(CallbackPassphraseProvider::new(move |_prompt| {
871 call_count_clone.fetch_add(1, Ordering::SeqCst);
872 Ok(Zeroizing::new("cached-pass".to_string()))
873 }));
874
875 let cached = CachedPassphraseProvider::new(inner, Duration::from_secs(60));
876
877 let result1 = cached.get_passphrase("prompt1");
879 assert!(result1.is_ok());
880 assert_eq!(*result1.unwrap(), "cached-pass");
881 assert_eq!(call_count.load(Ordering::SeqCst), 1);
882
883 let result2 = cached.get_passphrase("prompt1");
885 assert!(result2.is_ok());
886 assert_eq!(*result2.unwrap(), "cached-pass");
887 assert_eq!(call_count.load(Ordering::SeqCst), 1); }
889
890 #[test]
891 fn test_cached_passphrase_provider_cache_miss() {
892 use std::sync::Arc;
893 use std::sync::atomic::{AtomicUsize, Ordering};
894 use std::time::Duration;
895
896 let call_count = Arc::new(AtomicUsize::new(0));
897 let call_count_clone = Arc::clone(&call_count);
898
899 let inner = Arc::new(CallbackPassphraseProvider::new(move |_prompt| {
900 call_count_clone.fetch_add(1, Ordering::SeqCst);
901 Ok(Zeroizing::new("pass".to_string()))
902 }));
903
904 let cached = CachedPassphraseProvider::new(inner, Duration::from_secs(60));
905
906 let _ = cached.get_passphrase("prompt1");
908 assert_eq!(call_count.load(Ordering::SeqCst), 1);
909
910 let _ = cached.get_passphrase("prompt2");
911 assert_eq!(call_count.load(Ordering::SeqCst), 2);
912
913 let _ = cached.get_passphrase("prompt3");
914 assert_eq!(call_count.load(Ordering::SeqCst), 3);
915 }
916
917 #[test]
918 fn test_cached_passphrase_provider_expiry() {
919 use std::sync::Arc;
920 use std::sync::atomic::{AtomicUsize, Ordering};
921 use std::time::Duration;
922
923 let call_count = Arc::new(AtomicUsize::new(0));
924 let call_count_clone = Arc::clone(&call_count);
925
926 let inner = Arc::new(CallbackPassphraseProvider::new(move |_prompt| {
927 call_count_clone.fetch_add(1, Ordering::SeqCst);
928 Ok(Zeroizing::new("pass".to_string()))
929 }));
930
931 let cached = CachedPassphraseProvider::new(inner, Duration::from_millis(10));
933
934 let _ = cached.get_passphrase("prompt");
936 assert_eq!(call_count.load(Ordering::SeqCst), 1);
937
938 std::thread::sleep(Duration::from_millis(20));
940
941 let _ = cached.get_passphrase("prompt");
943 assert_eq!(call_count.load(Ordering::SeqCst), 2);
944 }
945
946 #[test]
947 fn test_cached_passphrase_provider_clear_cache() {
948 use std::sync::Arc;
949 use std::sync::atomic::{AtomicUsize, Ordering};
950 use std::time::Duration;
951
952 let call_count = Arc::new(AtomicUsize::new(0));
953 let call_count_clone = Arc::clone(&call_count);
954
955 let inner = Arc::new(CallbackPassphraseProvider::new(move |_prompt| {
956 call_count_clone.fetch_add(1, Ordering::SeqCst);
957 Ok(Zeroizing::new("pass".to_string()))
958 }));
959
960 let cached = CachedPassphraseProvider::new(inner, Duration::from_secs(60));
961
962 let _ = cached.get_passphrase("prompt");
964 assert_eq!(call_count.load(Ordering::SeqCst), 1);
965
966 let _ = cached.get_passphrase("prompt");
968 assert_eq!(call_count.load(Ordering::SeqCst), 1);
969
970 cached.clear_cache();
972
973 let _ = cached.get_passphrase("prompt");
975 assert_eq!(call_count.load(Ordering::SeqCst), 2);
976 }
977
978 #[test]
979 fn test_prefilled_passphrase_provider_returns_stored_value() {
980 let provider = PrefilledPassphraseProvider::new("my-secret");
981 let result = provider.get_passphrase("any prompt").unwrap();
982 assert_eq!(*result, "my-secret");
983
984 let result2 = provider.get_passphrase("different prompt").unwrap();
985 assert_eq!(*result2, "my-secret");
986 }
987
988 #[test]
989 fn test_prefilled_passphrase_provider_empty_passphrase() {
990 let provider = PrefilledPassphraseProvider::new("");
991 let result = provider.get_passphrase("prompt").unwrap();
992 assert_eq!(*result, "");
993 }
994
995 #[test]
996 fn test_unified_passphrase_provider_prompts_once_for_multiple_keys() {
997 use std::sync::atomic::{AtomicUsize, Ordering};
998
999 let call_count = Arc::new(AtomicUsize::new(0));
1000 let count_clone = call_count.clone();
1001 let inner = CallbackPassphraseProvider::new(move |_prompt: &str| {
1002 count_clone.fetch_add(1, Ordering::SeqCst);
1003 Ok(Zeroizing::new("secret".to_string()))
1004 });
1005
1006 let provider = UnifiedPassphraseProvider::new(Arc::new(inner));
1007
1008 let p1 = provider
1010 .get_passphrase("Enter passphrase for DEVICE key 'dev':")
1011 .unwrap();
1012 let p2 = provider
1013 .get_passphrase("Enter passphrase for IDENTITY key 'id':")
1014 .unwrap();
1015
1016 assert_eq!(*p1, "secret");
1017 assert_eq!(*p2, "secret");
1018 assert_eq!(call_count.load(Ordering::SeqCst), 1); }
1020}