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