1use crate::agent::AgentCore;
9use crate::agent::AgentHandle;
10#[cfg(unix)]
11use crate::agent::AgentSession;
12use crate::crypto::provider_bridge;
13use crate::crypto::signer::extract_seed_from_key_bytes;
14use crate::crypto::signer::{decrypt_keypair, encrypt_keypair};
15use crate::error::AgentError;
16use crate::signing::{PassphraseProvider, PrefilledPassphraseProvider};
17use crate::storage::keychain::{KeyAlias, KeyRole, KeyStorage};
18use log::{debug, error, info, warn};
19#[cfg(target_os = "macos")]
20use p256::pkcs8::DecodePrivateKey;
21#[cfg(target_os = "macos")]
22use pkcs8::PrivateKeyInfo;
23#[cfg(target_os = "macos")]
24use pkcs8::der::Decode;
25#[cfg(target_os = "macos")]
26use pkcs8::der::asn1::OctetString;
27use serde::Serialize;
28#[cfg(unix)]
29use ssh_agent_lib;
30#[cfg(unix)]
31use ssh_agent_lib::agent::listen;
32use ssh_key::private::{Ed25519Keypair as SshEdKeypair, KeypairData};
33use ssh_key::{
34 self, LineEnding, PrivateKey as SshPrivateKey, PublicKey as SshPublicKey,
35 public::Ed25519PublicKey as SshEd25519PublicKey,
36};
37#[cfg(unix)]
38use std::io;
39#[cfg(unix)]
40use std::sync::Arc;
41#[cfg(unix)]
42use tokio::net::UnixListener;
43use zeroize::Zeroizing;
44
45#[cfg(target_os = "macos")]
46use std::io::Write;
47
48#[cfg(target_os = "macos")]
49use {
50 std::fs::{self, Permissions},
51 std::os::unix::fs::PermissionsExt,
52 tempfile::Builder as TempFileBuilder,
53};
54
55#[cfg(target_os = "macos")]
56#[derive(Debug)]
57enum SshRegError {
58 Agent(crate::ports::ssh_agent::SshAgentError),
59 Io(std::io::Error),
60 Conversion(String),
61 BadSeedLength(usize),
62}
63
64#[cfg(target_os = "macos")]
65impl std::fmt::Display for SshRegError {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 Self::Agent(e) => write!(f, "ssh agent error: {e}"),
69 Self::Io(e) => write!(f, "I/O error: {e}"),
70 Self::Conversion(s) => write!(f, "key conversion failed: {s}"),
71 Self::BadSeedLength(n) => {
72 write!(f, "invalid PKCS#8 seed length: expected 32 bytes, got {n}")
73 }
74 }
75 }
76}
77
78#[cfg(target_os = "macos")]
80fn compute_ssh_fingerprint(pubkey_bytes: &[u8], curve: auths_crypto::CurveType) -> String {
81 let key_data = match curve {
82 auths_crypto::CurveType::Ed25519 => SshEd25519PublicKey::try_from(pubkey_bytes)
83 .map(ssh_key::public::KeyData::Ed25519)
84 .ok(),
85 auths_crypto::CurveType::P256 => {
86 ssh_key::public::EcdsaPublicKey::from_sec1_bytes(pubkey_bytes)
87 .ok()
88 .map(ssh_key::public::KeyData::Ecdsa)
89 }
90 };
91 match key_data {
92 Some(kd) => SshPublicKey::new(kd, "")
93 .fingerprint(Default::default())
94 .to_string(),
95 None => {
96 warn!("Could not build public key for fingerprint computation");
97 "unknown_fingerprint".to_string()
98 }
99 }
100}
101
102#[cfg(target_os = "macos")]
104fn build_ssh_keypair_data(
105 pkcs8_bytes: &[u8],
106 curve: auths_crypto::CurveType,
107) -> Result<KeypairData, SshRegError> {
108 match curve {
109 auths_crypto::CurveType::Ed25519 => {
110 let private_key_info = PrivateKeyInfo::from_der(pkcs8_bytes)
111 .map_err(|e| SshRegError::Conversion(e.to_string()))?;
112 let seed_octet_string = OctetString::from_der(private_key_info.private_key)
113 .map_err(|e| SshRegError::Conversion(e.to_string()))?;
114 let seed_bytes = seed_octet_string.as_bytes();
115 if seed_bytes.len() != 32 {
116 return Err(SshRegError::BadSeedLength(seed_bytes.len()));
117 }
118 #[allow(clippy::expect_used)]
119 let seed_array: [u8; 32] = seed_bytes.try_into().expect("Length checked");
121 Ok(KeypairData::Ed25519(SshEdKeypair::from_seed(&seed_array)))
122 }
123 auths_crypto::CurveType::P256 => {
124 use p256::elliptic_curve::sec1::ToEncodedPoint;
125 use ssh_key::private::{EcdsaKeypair, EcdsaPrivateKey};
126
127 let secret = p256::SecretKey::from_pkcs8_der(pkcs8_bytes)
128 .map_err(|e| SshRegError::Conversion(format!("P-256 PKCS#8 parse failed: {e}")))?;
129 let public = secret.public_key();
130 Ok(KeypairData::Ecdsa(EcdsaKeypair::NistP256 {
131 public: public.to_encoded_point(false),
132 private: EcdsaPrivateKey::from(secret),
133 }))
134 }
135 }
136}
137
138#[derive(Serialize, Debug, Clone)]
142pub struct KeyLoadStatus {
143 pub alias: KeyAlias,
145 pub loaded: bool,
147 pub error: Option<String>,
149}
150
151#[derive(Serialize, Debug, Clone, PartialEq, Eq)]
153pub enum RegistrationOutcome {
154 Added,
156 AlreadyExists,
158 AgentNotFound,
160 CommandFailed,
162 UnsupportedKeyType,
164 ConversionFailed,
166 IoError,
168 InternalError,
170}
171
172#[derive(Serialize, Debug, Clone)]
174pub struct KeyRegistrationStatus {
175 pub fingerprint: String,
177 pub status: RegistrationOutcome,
179 pub message: Option<String>,
181}
182
183pub fn clear_agent_keys_with_handle(handle: &AgentHandle) -> Result<(), AgentError> {
201 info!("Clearing all keys from agent handle.");
202 let mut agent_guard = handle.lock()?;
203 agent_guard.clear_keys();
204 debug!("Agent keys cleared.");
205 Ok(())
206}
207
208pub fn load_keys_into_agent_with_handle(
224 handle: &AgentHandle,
225 aliases: Vec<String>,
226 passphrase_provider: &dyn PassphraseProvider,
227 keychain: &(dyn KeyStorage + Send + Sync),
228) -> Result<Vec<KeyLoadStatus>, AgentError> {
229 info!(
230 "Attempting to load keys into agent handle for aliases: {:?}",
231 aliases
232 );
233 if aliases.is_empty() {
234 warn!("load_keys_into_agent_with_handle called with empty alias list. Clearing agent.");
235 clear_agent_keys_with_handle(handle)?;
236 return Ok(vec![]);
237 }
238
239 let mut load_statuses = Vec::new();
240 let mut temp_unlocked_core = AgentCore::default();
241
242 for alias in aliases {
243 debug!("Processing alias for agent load: {}", alias);
244 let key_alias = KeyAlias::new_unchecked(&alias);
245 let mut status = KeyLoadStatus {
246 alias: key_alias.clone(),
247 loaded: false,
248 error: None,
249 };
250
251 let load_result = || -> Result<Zeroizing<Vec<u8>>, AgentError> {
252 if keychain.is_hardware_backend() {
253 return Err(AgentError::HardwareKeyNotExportable {
254 operation: "agent key load".to_string(),
255 });
256 }
257 let (_controller_did, _role, encrypted_pkcs8) = keychain.load_key(&key_alias)?;
258 let prompt = format!(
259 "Enter passphrase to unlock key '{}' for agent session:",
260 key_alias
261 );
262 let passphrase = passphrase_provider.get_passphrase(&prompt)?;
263 let pkcs8_bytes = decrypt_keypair(&encrypted_pkcs8, &passphrase)?;
264 let _ = extract_seed_from_key_bytes(&pkcs8_bytes).map_err(|e| {
265 AgentError::KeyDeserializationError(format!(
266 "Failed to parse key for alias '{}' after decryption: {}",
267 key_alias, e
268 ))
269 })?;
270 Ok(pkcs8_bytes)
271 }();
272
273 match load_result {
274 Ok(pkcs8_bytes) => {
275 info!("Successfully unlocked key for alias '{}'", key_alias);
276 match temp_unlocked_core.register_key(pkcs8_bytes) {
277 Ok(()) => status.loaded = true,
278 Err(e) => {
279 error!(
280 "Failed to register key '{}' in agent core state after successful unlock/parse: {}",
281 key_alias, e
282 );
283 status.error = Some(format!(
284 "Internal error: Failed to register key in agent core state: {}",
285 e
286 ));
287 }
288 }
289 }
290 Err(e) => {
291 error!(
292 "Failed to load/decrypt key for alias '{}': {}",
293 key_alias, e
294 );
295 match e {
296 AgentError::IncorrectPassphrase => {
297 status.error = Some("Incorrect passphrase".to_string())
298 }
299 AgentError::KeyNotFound => status.error = Some("Key not found".to_string()),
300 AgentError::UserInputCancelled => {
301 status.error = Some("Operation cancelled by user".to_string())
302 }
303 AgentError::KeyDeserializationError(_) => {
304 status.error = Some(format!("Failed to parse key after decryption: {}", e))
305 }
306 _ => status.error = Some(e.to_string()),
307 }
308 }
309 }
310 load_statuses.push(status);
311 }
312
313 let mut agent_guard = handle.lock()?;
315 info!(
316 "Replacing agent core with {} unlocked keys ({} aliases attempted).",
317 temp_unlocked_core.key_count(),
318 load_statuses.len()
319 );
320 *agent_guard = temp_unlocked_core;
321
322 Ok(load_statuses)
323}
324
325pub fn rotate_key(
348 alias: &str,
349 new_passphrase: &str,
350 keychain: &(dyn KeyStorage + Send + Sync),
351) -> Result<(), AgentError> {
352 info!(
353 "[API] Attempting secure storage key rotation for local alias: {}",
354 alias
355 );
356 if alias.trim().is_empty() {
357 return Err(AgentError::InvalidInput(
358 "Alias cannot be empty".to_string(),
359 ));
360 }
361 if new_passphrase.is_empty() {
362 return Err(AgentError::InvalidInput(
363 "New passphrase cannot be empty".to_string(),
364 ));
365 }
366
367 let key_alias = KeyAlias::new_unchecked(alias);
369 let existing_did = keychain.get_identity_for_alias(&key_alias)?;
370 info!(
371 "Found existing key for alias '{}', associated with Controller DID '{}'. Proceeding with rotation.",
372 alias, existing_did
373 );
374
375 let (seed, pubkey) = provider_bridge::generate_ed25519_keypair_sync()
377 .map_err(|e| AgentError::CryptoError(format!("Failed to generate new keypair: {}", e)))?;
378 let new_pkcs8_bytes = auths_crypto::build_ed25519_pkcs8_v2(seed.as_bytes(), &pubkey);
380 debug!("Generated new keypair via CryptoProvider.");
381
382 let encrypted_new_key = encrypt_keypair(&new_pkcs8_bytes, new_passphrase)?;
384 debug!("Encrypted new keypair with provided passphrase.");
385
386 keychain.store_key(
389 &key_alias,
390 &existing_did,
391 KeyRole::Primary,
392 &encrypted_new_key,
393 )?;
394 info!(
395 "Successfully overwrote secure storage for alias '{}' with new encrypted key.",
396 alias
397 );
398
399 warn!(
400 "Secure storage key rotated for alias '{}'. This did NOT update any Git identity representation. The running agent may still hold the old decrypted key. Consider reloading keys into the agent.",
401 alias
402 );
403 Ok(())
404}
405
406pub fn agent_sign_with_handle(
421 handle: &AgentHandle,
422 pubkey: &[u8],
423 data: &[u8],
424) -> Result<Vec<u8>, AgentError> {
425 debug!(
426 "Agent sign request for pubkey starting with: {:x?}...",
427 &pubkey[..core::cmp::min(pubkey.len(), 8)]
428 );
429
430 handle.sign(pubkey, data)
432}
433
434pub fn export_key_openssh_pem(
446 alias: &str,
447 passphrase: &str,
448 keychain: &(dyn KeyStorage + Send + Sync),
449) -> Result<Zeroizing<String>, AgentError> {
450 info!("Exporting PEM for local alias: {}", alias);
451 if alias.trim().is_empty() {
452 return Err(AgentError::InvalidInput(
453 "Alias cannot be empty".to_string(),
454 ));
455 }
456 if keychain.is_hardware_backend() {
457 return Err(AgentError::HardwareKeyNotExportable {
458 operation: "OpenSSH private key export".to_string(),
459 });
460 }
461 let key_alias = KeyAlias::new_unchecked(alias);
463 let (_controller_did, _role, encrypted_pkcs8) = keychain.load_key(&key_alias)?;
464
465 let pkcs8_bytes = decrypt_keypair(&encrypted_pkcs8, passphrase)?;
467
468 let parsed = auths_crypto::parse_key_material(&pkcs8_bytes[..]).map_err(|e| {
470 AgentError::KeyDeserializationError(format!(
471 "Failed to parse key material for alias '{}': {}",
472 alias, e
473 ))
474 })?;
475
476 let keypair_data = build_openssh_keypair_data(&parsed).map_err(|e| {
477 AgentError::CryptoError(format!(
478 "Failed to build SSH keypair for alias '{}': {}",
479 alias, e
480 ))
481 })?;
482
483 let ssh_private_key = SshPrivateKey::new(keypair_data, "").map_err(|e| {
484 AgentError::CryptoError(format!(
485 "Failed to create ssh_key::PrivateKey for alias '{}': {}",
486 alias, e
487 ))
488 })?;
489
490 let pem = ssh_private_key.to_openssh(LineEnding::LF).map_err(|e| {
491 AgentError::CryptoError(format!(
492 "Failed to encode OpenSSH PEM for alias '{}': {}",
493 alias, e
494 ))
495 })?;
496
497 debug!("Successfully generated PEM for alias '{}'", alias);
498 Ok(pem)
499}
500
501fn build_openssh_keypair_data(parsed: &auths_crypto::ParsedKey) -> Result<KeypairData, String> {
503 match parsed.seed.curve() {
504 auths_crypto::CurveType::Ed25519 => Ok(KeypairData::Ed25519(SshEdKeypair::from_seed(
505 parsed.seed.as_bytes(),
506 ))),
507 auths_crypto::CurveType::P256 => {
508 use p256::elliptic_curve::sec1::ToEncodedPoint;
509 use ssh_key::private::{EcdsaKeypair, EcdsaPrivateKey};
510
511 let secret = p256::SecretKey::from_slice(parsed.seed.as_bytes())
512 .map_err(|e| format!("P-256 secret key parse: {e}"))?;
513 let public = secret.public_key();
514 Ok(KeypairData::Ecdsa(EcdsaKeypair::NistP256 {
515 public: public.to_encoded_point(false),
516 private: EcdsaPrivateKey::from(secret),
517 }))
518 }
519 }
520}
521
522pub fn export_key_openssh_pub(
535 alias: &str,
536 passphrase: &str,
537 keychain: &(dyn KeyStorage + Send + Sync),
538) -> Result<String, AgentError> {
539 info!("Exporting OpenSSH public key for local alias: {}", alias);
540 if alias.trim().is_empty() {
541 return Err(AgentError::InvalidInput(
542 "Alias cannot be empty".to_string(),
543 ));
544 }
545 let key_alias = KeyAlias::new_unchecked(alias);
547 let passphrase_provider = PrefilledPassphraseProvider::new(passphrase);
548 let (pubkey_bytes, curve) = crate::storage::keychain::extract_public_key_bytes(
549 keychain,
550 &key_alias,
551 &passphrase_provider,
552 )?;
553 let key_data = match curve {
554 auths_crypto::CurveType::Ed25519 => {
555 let pk = SshEd25519PublicKey::try_from(pubkey_bytes.as_slice()).map_err(|e| {
556 AgentError::CryptoError(format!(
557 "Failed to create Ed25519PublicKey from bytes: {}",
558 e
559 ))
560 })?;
561 ssh_key::public::KeyData::Ed25519(pk)
562 }
563 auths_crypto::CurveType::P256 => {
564 let pk = ssh_key::public::EcdsaPublicKey::from_sec1_bytes(pubkey_bytes.as_slice())
565 .map_err(|e| {
566 AgentError::CryptoError(format!(
567 "Failed to create EcdsaPublicKey from bytes: {}",
568 e
569 ))
570 })?;
571 ssh_key::public::KeyData::Ecdsa(pk)
572 }
573 };
574
575 let ssh_pub_key = SshPublicKey::new(key_data, ""); let pubkey_base = ssh_pub_key.to_openssh().map_err(|e| {
580 AgentError::CryptoError(format!("Failed to format OpenSSH pubkey base: {}", e))
582 })?;
583
584 let formatted_pubkey = format!("{} {}", pubkey_base, alias);
586
587 debug!(
588 "Successfully generated OpenSSH public key string for alias '{}'",
589 alias
590 );
591 Ok(formatted_pubkey)
592}
593
594pub fn get_agent_key_count_with_handle(handle: &AgentHandle) -> Result<usize, AgentError> {
602 handle.key_count()
603}
604
605#[cfg(target_os = "macos")]
624#[allow(clippy::disallowed_methods)]
625#[allow(clippy::disallowed_types)]
627pub fn register_keys_with_macos_agent_with_handle(
628 handle: &AgentHandle,
629 ssh_agent_socket: Option<&std::path::Path>,
630 ssh_agent: &dyn crate::ports::ssh_agent::SshAgentPort,
631) -> Result<Vec<KeyRegistrationStatus>, AgentError> {
632 info!("Attempting to register keys from agent handle with system ssh-agent...");
633 if ssh_agent_socket.is_none() {
634 warn!("SSH_AUTH_SOCK not configured. System ssh-agent may not be running or configured.");
635 }
636
637 let keys_to_register: Vec<(Vec<u8>, auths_crypto::CurveType, Zeroizing<Vec<u8>>)> = {
638 let agent_guard = handle.lock()?;
639 agent_guard
640 .keys
641 .iter()
642 .filter_map(|(pubkey, stored)| {
643 let typed_seed = match stored.curve {
644 auths_crypto::CurveType::Ed25519 => {
645 auths_crypto::TypedSeed::Ed25519(*stored.seed.as_bytes())
646 }
647 auths_crypto::CurveType::P256 => {
648 auths_crypto::TypedSeed::P256(*stored.seed.as_bytes())
649 }
650 };
651 let signer = auths_crypto::TypedSignerKey::from_seed(typed_seed).ok()?;
652 let pkcs8 = signer.to_pkcs8().ok()?;
653 Some((
654 pubkey.clone(),
655 stored.curve,
656 Zeroizing::new(pkcs8.as_ref().to_vec()),
657 ))
658 })
659 .collect()
660 };
661
662 register_keys_with_macos_agent_internal(keys_to_register, ssh_agent)
663}
664
665#[cfg(not(target_os = "macos"))]
667pub fn register_keys_with_macos_agent_with_handle(
668 _handle: &AgentHandle,
669 _ssh_agent_socket: Option<&std::path::Path>,
670 _ssh_agent: &dyn crate::ports::ssh_agent::SshAgentPort,
671) -> Result<Vec<KeyRegistrationStatus>, AgentError> {
672 info!("Not on macOS, skipping system ssh-agent registration.");
673 Ok(vec![])
674}
675
676#[cfg(target_os = "macos")]
681#[allow(clippy::too_many_lines)]
682fn register_keys_with_macos_agent_internal(
683 keys_to_register: Vec<(Vec<u8>, auths_crypto::CurveType, Zeroizing<Vec<u8>>)>,
684 ssh_agent: &dyn crate::ports::ssh_agent::SshAgentPort,
685) -> Result<Vec<KeyRegistrationStatus>, AgentError> {
686 use crate::ports::ssh_agent::SshAgentError;
687
688 if keys_to_register.is_empty() {
689 info!("No keys to register with system agent.");
690 return Ok(vec![]);
691 }
692 info!(
693 "Found {} keys to attempt registration with system agent.",
694 keys_to_register.len()
695 );
696
697 let mut results = Vec::with_capacity(keys_to_register.len());
698
699 for (pubkey_bytes, curve, pkcs8_bytes_zeroizing) in keys_to_register.into_iter() {
700 let fingerprint_str = compute_ssh_fingerprint(&pubkey_bytes, curve);
701
702 let mut status = KeyRegistrationStatus {
703 fingerprint: fingerprint_str.clone(),
704 status: RegistrationOutcome::InternalError,
705 message: None,
706 };
707
708 let result: Result<(), SshRegError> = (|| {
709 let pkcs8_bytes = pkcs8_bytes_zeroizing.as_ref();
710 let keypair_data = build_ssh_keypair_data(pkcs8_bytes, curve)?;
711 let ssh_private_key = SshPrivateKey::new(keypair_data, "")
712 .map_err(|e| SshRegError::Conversion(e.to_string()))?;
713 let pem_zeroizing = ssh_private_key
714 .to_openssh(LineEnding::LF)
715 .map_err(|e| SshRegError::Conversion(e.to_string()))?;
716 let pem_string = pem_zeroizing.to_string();
717
718 let mut temp_file_guard = TempFileBuilder::new()
719 .prefix("auths-key-")
720 .suffix(".pem")
721 .rand_bytes(5)
722 .tempfile()
723 .map_err(SshRegError::Io)?;
724 if let Err(e) =
725 fs::set_permissions(temp_file_guard.path(), Permissions::from_mode(0o600))
726 {
727 warn!(
728 "Failed to set 600 permissions on temp file {:?}: {}. Continuing...",
729 temp_file_guard.path(),
730 e
731 );
732 }
733 temp_file_guard
734 .write_all(pem_string.as_bytes())
735 .map_err(SshRegError::Io)?;
736 temp_file_guard.flush().map_err(SshRegError::Io)?;
737 let temp_file_path = temp_file_guard.path().to_path_buf();
738
739 debug!(
740 "Attempting ssh-add for temporary key file: {:?}",
741 temp_file_path
742 );
743 ssh_agent
744 .register_key(&temp_file_path)
745 .map_err(SshRegError::Agent)?;
746 debug!("ssh-add finished for {:?}", temp_file_path);
747 Ok(())
748 })();
749
750 match result {
751 Ok(()) => {
752 info!(
753 "ssh-add successful for {}: Identity added.",
754 fingerprint_str
755 );
756 status.status = RegistrationOutcome::Added;
757 status.message = Some("Identity added via ssh-agent port".to_string());
758 }
759 Err(e) => {
760 match &e {
761 SshRegError::Agent(SshAgentError::NotAvailable(_)) => {
762 status.status = RegistrationOutcome::AgentNotFound;
763 }
764 SshRegError::Agent(SshAgentError::CommandFailed(_)) => {
765 status.status = RegistrationOutcome::CommandFailed;
766 }
767 SshRegError::Agent(SshAgentError::IoError(_)) | SshRegError::Io(_) => {
768 status.status = RegistrationOutcome::IoError;
769 }
770 SshRegError::Conversion(_) | SshRegError::BadSeedLength(_) => {
771 status.status = RegistrationOutcome::ConversionFailed;
772 }
773 }
774 error!(
775 "Error during registration process for {}: {:?}",
776 fingerprint_str, e
777 );
778 status.message = Some(format!("Registration error: {}", e));
779 }
780 }
781 results.push(status);
782 }
783
784 info!(
785 "Finished attempting system agent registration for {} keys.",
786 results.len()
787 );
788 Ok(results)
789}
790
791#[cfg(unix)]
806#[allow(clippy::disallowed_methods)] pub async fn start_agent_listener_with_handle(handle: Arc<AgentHandle>) -> Result<(), AgentError> {
808 let socket_path = handle.socket_path();
809 info!("Attempting to start agent listener at {:?}", socket_path);
810
811 if let Some(parent) = socket_path.parent()
813 && !parent.exists()
814 {
815 debug!("Creating parent directory for socket: {:?}", parent);
816 if let Err(e) = std::fs::create_dir_all(parent) {
817 error!("Failed to create parent directory {:?}: {}", parent, e);
818 return Err(AgentError::IO(e));
819 }
820 }
821
822 match std::fs::remove_file(socket_path) {
824 Ok(()) => info!("Removed existing socket file at {:?}", socket_path),
825 Err(e) if e.kind() == io::ErrorKind::NotFound => {
826 debug!(
827 "No existing socket file found at {:?}, proceeding.",
828 socket_path
829 );
830 }
831 Err(e) => {
832 warn!(
833 "Failed to remove existing socket file at {:?}: {}. Binding might fail.",
834 socket_path, e
835 );
836 }
837 }
838
839 let listener = UnixListener::bind(socket_path).map_err(|e| {
841 error!("Failed to bind listener socket at {:?}: {}", socket_path, e);
842 AgentError::IO(e)
843 })?;
844
845 let actual_path = socket_path
847 .canonicalize()
848 .unwrap_or_else(|_| socket_path.to_path_buf());
849 info!(
850 "🚀 Agent listener started successfully at {:?}",
851 actual_path
852 );
853 info!(" Set SSH_AUTH_SOCK={:?} to use this agent.", actual_path);
854
855 handle.set_running(true);
857
858 let session = AgentSession::new(handle.clone());
860
861 let result = listen(listener, session).await;
863
864 handle.set_running(false);
866
867 if let Err(e) = result {
868 error!("SSH Agent listener failed: {:?}", e);
869 return Err(AgentError::IO(io::Error::other(format!(
870 "SSH Agent listener failed: {}",
871 e
872 ))));
873 }
874
875 warn!("Agent listener loop exited unexpectedly without error.");
876 Ok(())
877}
878
879#[cfg(unix)]
894pub async fn start_agent_listener(socket_path_str: String) -> Result<(), AgentError> {
895 use std::path::PathBuf;
896 let handle = Arc::new(AgentHandle::new(PathBuf::from(&socket_path_str)));
897 start_agent_listener_with_handle(handle).await
898}