Skip to main content

auths_cli/adapters/
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)]
8use auths_core::agent::{AgentStatus, add_identity, agent_sign, check_agent_status};
9#[cfg(unix)]
10use auths_core::crypto::ssh::{construct_sshsig_pem, construct_sshsig_signed_data};
11use auths_sdk::ports::agent::{AgentSigningError, AgentSigningPort};
12
13#[cfg(unix)]
14use 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)]
27pub struct CliAgentAdapter;
28
29#[cfg(unix)]
30impl 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}