/ crates / auths-cli / src / adapters / agent.rs
agent.rs
 1  //! CLI adapter for agent-based signing operations.
 2  //!
 3  //! Wraps the Unix-only agent client from `auths-core` behind the
 4  //! `AgentSigningPort` trait, producing SSHSIG PEM output compatible
 5  //! with `sign_with_seed()`.
 6  
 7  #[cfg(unix)]
 8  use auths_core::agent::{AgentStatus, add_identity, agent_sign, check_agent_status};
 9  #[cfg(unix)]
10  use auths_core::crypto::ssh::{construct_sshsig_pem, construct_sshsig_signed_data};
11  use auths_sdk::ports::agent::{AgentSigningError, AgentSigningPort};
12  
13  #[cfg(unix)]
14  use crate::commands::agent::{ensure_agent_running, get_default_socket_path};
15  
16  /// CLI adapter that delegates signing to the Unix SSH agent.
17  ///
18  /// On non-Unix platforms this struct is not compiled; the CLI wires
19  /// `NoopAgentProvider` instead.
20  ///
21  /// Usage:
22  /// ```ignore
23  /// let adapter = CliAgentAdapter;
24  /// let pem = adapter.try_sign("git", &pubkey, &data)?;
25  /// ```
26  #[cfg(unix)]
27  pub struct CliAgentAdapter;
28  
29  #[cfg(unix)]
30  impl AgentSigningPort for CliAgentAdapter {
31      fn try_sign(
32          &self,
33          namespace: &str,
34          pubkey: &[u8],
35          data: &[u8],
36      ) -> Result<String, AgentSigningError> {
37          if pubkey.len() != 32 {
38              return Err(AgentSigningError::Unavailable(
39                  "no public key available for agent signing".into(),
40              ));
41          }
42  
43          let socket_path =
44              get_default_socket_path().map_err(|e| AgentSigningError::Unavailable(e.to_string()))?;
45  
46          match check_agent_status(&socket_path) {
47              AgentStatus::Running { key_count } if key_count > 0 => {}
48              AgentStatus::Running { .. } => {
49                  return Err(AgentSigningError::Unavailable(
50                      "agent running but no keys loaded".into(),
51                  ));
52              }
53              AgentStatus::ConnectionFailed => {
54                  return Err(AgentSigningError::ConnectionFailed(
55                      "agent socket unreachable".into(),
56                  ));
57              }
58              AgentStatus::NotRunning => {
59                  return Err(AgentSigningError::Unavailable("agent not running".into()));
60              }
61          }
62  
63          let sig_data = construct_sshsig_signed_data(data, namespace)
64              .map_err(|e| AgentSigningError::SigningFailed(e.to_string()))?;
65  
66          let raw_sig = agent_sign(&socket_path, pubkey, &sig_data)
67              .map_err(|e| AgentSigningError::SigningFailed(e.to_string()))?;
68  
69          construct_sshsig_pem(pubkey, &raw_sig, namespace)
70              .map_err(|e| AgentSigningError::SigningFailed(e.to_string()))
71      }
72  
73      fn ensure_running(&self) -> Result<(), AgentSigningError> {
74          ensure_agent_running(true)
75              .map(|_| ())
76              .map_err(|e| AgentSigningError::StartupFailed(e.to_string()))
77      }
78  
79      fn add_identity(&self, _namespace: &str, pkcs8_der: &[u8]) -> Result<(), AgentSigningError> {
80          let socket_path = get_default_socket_path()
81              .map_err(|e| AgentSigningError::ConnectionFailed(e.to_string()))?;
82  
83          add_identity(&socket_path, pkcs8_der)
84              .map(|_| ())
85              .map_err(|e| AgentSigningError::SigningFailed(e.to_string()))
86      }
87  }