auths_sdk/ports/agent.rs
1//! Agent-based signing port for delegating cryptographic operations to a running agent process.
2//!
3//! The agent signing port abstracts the IPC-based signing protocol so that
4//! the SDK workflow layer remains platform-independent. On Unix, the CLI
5//! wires a concrete adapter that speaks the auths-agent wire protocol over
6//! a Unix domain socket. On Windows, WASM, and other targets the
7//! [`NoopAgentProvider`] is used instead.
8
9/// Errors from agent signing operations.
10#[derive(Debug, thiserror::Error)]
11#[non_exhaustive]
12pub enum AgentSigningError {
13 /// The agent is not available on this platform or is not installed.
14 #[error("agent unavailable: {0}")]
15 Unavailable(String),
16
17 /// The agent socket exists but the connection failed.
18 #[error("agent connection failed: {0}")]
19 ConnectionFailed(String),
20
21 /// The agent accepted the request but signing failed.
22 #[error("agent signing failed: {0}")]
23 SigningFailed(String),
24
25 /// The agent could not be started.
26 #[error("agent startup failed: {0}")]
27 StartupFailed(String),
28}
29
30/// Port for delegating signing operations to a running agent process.
31///
32/// Implementations must convert the agent's native response format into an
33/// SSHSIG PEM string (`-----BEGIN SSH SIGNATURE----- … -----END SSH SIGNATURE-----`)
34/// so that callers receive the same format as [`crate::signing::sign_with_seed`].
35///
36/// Args:
37/// * Trait methods accept namespace identifiers, public key bytes, and raw data to sign.
38///
39/// Usage:
40/// ```ignore
41/// let pem = agent.try_sign("git", &pubkey_bytes, &commit_data)?;
42/// agent.ensure_running()?;
43/// agent.add_identity("git", &pkcs8_der_bytes)?;
44/// ```
45pub trait AgentSigningPort: Send + Sync + 'static {
46 /// Attempt to sign `data` via the running agent.
47 ///
48 /// Returns an SSHSIG PEM string on success. The adapter is responsible for
49 /// converting raw Ed25519 bytes from the agent wire protocol into PEM.
50 ///
51 /// Args:
52 /// * `namespace`: The SSH namespace for the signature (e.g. `"git"`).
53 /// * `pubkey`: The Ed25519 public key bytes to identify the signing key.
54 /// * `data`: The raw bytes to sign.
55 fn try_sign(
56 &self,
57 namespace: &str,
58 pubkey: &[u8],
59 data: &[u8],
60 ) -> Result<String, AgentSigningError>;
61
62 /// Start the agent daemon if it is not already running.
63 ///
64 /// Implementations should suppress all stdout/stderr output (quiet mode).
65 fn ensure_running(&self) -> Result<(), AgentSigningError>;
66
67 /// Load a decrypted PKCS#8 DER-encoded keypair into the running agent.
68 ///
69 /// Args:
70 /// * `namespace`: The namespace to associate with the loaded key.
71 /// * `pkcs8_der`: Decrypted PKCS#8 DER bytes — the output of keychain
72 /// decryption (i.e. `decrypt_keypair()` result), not a raw seed or
73 /// encrypted blob.
74 fn add_identity(&self, namespace: &str, pkcs8_der: &[u8]) -> Result<(), AgentSigningError>;
75}
76
77/// No-op agent provider for platforms without agent support.
78///
79/// All methods return [`AgentSigningError::Unavailable`].
80pub struct NoopAgentProvider;
81
82impl AgentSigningPort for NoopAgentProvider {
83 fn try_sign(
84 &self,
85 _namespace: &str,
86 _pubkey: &[u8],
87 _data: &[u8],
88 ) -> Result<String, AgentSigningError> {
89 Err(AgentSigningError::Unavailable(
90 "agent not supported on this platform".into(),
91 ))
92 }
93
94 fn ensure_running(&self) -> Result<(), AgentSigningError> {
95 Err(AgentSigningError::Unavailable(
96 "agent not supported on this platform".into(),
97 ))
98 }
99
100 fn add_identity(&self, _namespace: &str, _pkcs8_der: &[u8]) -> Result<(), AgentSigningError> {
101 Err(AgentSigningError::Unavailable(
102 "agent not supported on this platform".into(),
103 ))
104 }
105}