use std::cell::RefCell; use std::path::Path; pub use radicle_ssh as ssh; pub use ssh::agent::client::{AgentClient, Error}; use crate::{PublicKey, SecretKey, Signature, Signer}; use super::ExtendedSignature; pub struct Agent { client: AgentClient, } impl Agent { /// Connect to a running SSH agent. pub fn connect() -> Result { Ok(Self { client: AgentClient::connect_env()?, }) } /// Register a key with the agent. pub fn register(&mut self, key: &SecretKey) -> Result<(), ssh::Error> { self.client.add_identity(key, &[]) } pub fn unregister(&mut self, key: &PublicKey) -> Result<(), ssh::Error> { self.client.remove_identity(key) } pub fn unregister_all(&mut self) -> Result<(), ssh::Error> { self.client.remove_all_identities() } pub fn sign(&mut self, key: &PublicKey, data: &[u8]) -> Result<[u8; 64], ssh::Error> { self.client.sign(key, data) } /// Get a signer from this agent, given the public key. pub fn signer(self, key: PublicKey) -> AgentSigner { AgentSigner::new(self, key) } pub fn path(&self) -> Option<&Path> { self.client.path() } pub fn request_identities(&mut self) -> Result, ssh::agent::client::Error> { self.client.request_identities() } } /// A [`Signer`] that uses `ssh-agent`. pub struct AgentSigner { agent: RefCell, public: PublicKey, } impl signature::Signer for AgentSigner { fn try_sign(&self, msg: &[u8]) -> Result { let sig = self .agent .borrow_mut() .sign(&self.public, msg) .map_err(signature::Error::from_source)?; Ok(Signature::from(sig)) } } impl signature::Signer for AgentSigner { fn try_sign(&self, msg: &[u8]) -> Result { use signature::Keypair as _; Ok(ExtendedSignature { key: self.verifying_key(), sig: self.try_sign(msg)?, }) } } impl AsRef for AgentSigner { fn as_ref(&self) -> &PublicKey { &self.public } } impl signature::KeypairRef for AgentSigner { type VerifyingKey = PublicKey; } impl AgentSigner { pub fn new(agent: Agent, public: PublicKey) -> Self { let agent = RefCell::new(agent); Self { agent, public } } pub fn is_ready(&self) -> Result { let ids = self.agent.borrow_mut().request_identities()?; Ok(ids.contains(&self.public)) } /// Box this signer into a [`Signer`]. pub fn boxed(self) -> Box { Box::new(self) } }