ssh_agent_client_rs/
lib.rs

1//! # ssh-agent-client-rs
2//!
3//! An ssh-agent client implementation in rust, aiming to provide a robust,
4//! well tested and easy to use synchronous API to interact with an ssh-agent.
5//!
6//! # Examples
7//! ```no_run
8//! use ssh_agent_client_rs::Client;
9//! # use std::env;
10//! # use std::path::Path;
11//! # use ssh_agent_client_rs::Error;
12//! use ssh_key::PublicKey;
13//!
14//! # let env = env::var("SSH_AUTH_SOCK").unwrap();
15//! # let path_to_ssh_auth_socket = Path::new(env.as_str());
16//! let mut client = Client::connect(path_to_ssh_auth_socket).expect("failed to connect");
17//!
18//! // List the identities that the connected ssh-agent makes available
19//! let identities: Vec<PublicKey> = client.list_identities().expect("failed to list identities");
20//! ```
21
22use crate::codec::{read_message, write_message, ReadMessage, WriteMessage};
23#[cfg(target_family = "windows")]
24use interprocess::os::windows::named_pipe::{pipe_mode, DuplexPipeStream};
25use ssh_key::{PrivateKey, PublicKey, Signature};
26use std::io::{Read, Write};
27#[cfg(target_family = "unix")]
28use std::os::unix::net::UnixStream;
29use std::path::Path;
30
31mod codec;
32mod error;
33
34pub use self::error::Error;
35pub use self::error::Result;
36
37/// A combination of the std::io::Read and std::io::Write traits.
38pub trait ReadWrite: Read + Write {}
39
40/// A Client instance is an object that can be used to interact with an ssh-agent,
41/// typically using a Unix socket
42pub struct Client {
43    socket: Box<dyn ReadWrite>,
44}
45
46impl<T> ReadWrite for T where T: Read + Write {}
47
48impl Client {
49    /// Constructs a Client connected to a unix socket referenced by path.
50    #[cfg(target_family = "unix")]
51    pub fn connect(path: &Path) -> Result<Client> {
52        let socket = Box::new(UnixStream::connect(path)?);
53        Ok(Client { socket })
54    }
55
56    // If you want to communicate with the ssh-agent shipped with windows you probably want to pass
57    // Path::new(r"\\.\pipe\openssh-ssh-agent")
58    #[cfg(target_family = "windows")]
59    pub fn connect(path: &Path) -> Result<Client> {
60        let pipe = DuplexPipeStream::<pipe_mode::Bytes>::connect_by_path(path)?;
61        Ok(Client {
62            socket: Box::new(pipe),
63        })
64    }
65
66    /// Construct a Client backed by an implementation of ReadWrite, mainly useful for
67    /// testing.
68    pub fn with_read_write(read_write: Box<dyn ReadWrite>) -> Client {
69        Client { socket: read_write }
70    }
71
72    /// List the identities that has been added to the connected ssh-agent.
73    pub fn list_identities(&mut self) -> Result<Vec<PublicKey>> {
74        write_message(&mut self.socket, WriteMessage::RequestIdentities)?;
75        match read_message(&mut self.socket)? {
76            ReadMessage::Identities(identities) => Ok(identities),
77            m => Err(unexpected_response(m)),
78        }
79    }
80
81    /// Add an identity to the connected ssh-agent.
82    pub fn add_identity(&mut self, key: &PrivateKey) -> Result<()> {
83        write_message(&mut self.socket, WriteMessage::AddIdentity(key))?;
84        self.expect_success()
85    }
86
87    /// Remove an identity from the connected ssh-agent.
88    pub fn remove_identity(&mut self, key: &PrivateKey) -> Result<()> {
89        write_message(&mut self.socket, WriteMessage::RemoveIdentity(key))?;
90        self.expect_success()
91    }
92
93    /// Remove all identities from the connected ssh-agent.
94    pub fn remove_all_identities(&mut self) -> Result<()> {
95        write_message(&mut self.socket, WriteMessage::RemoveAllIdentities)?;
96        self.expect_success()
97    }
98
99    /// Instruct the connected ssh-agent to sign data with the private key associated with the
100    /// provided public key. For now, sign requests with RSA keys are hard coded to use the
101    /// SHA-512 hashing algorithm.
102    pub fn sign(&mut self, key: &PublicKey, data: &[u8]) -> Result<Signature> {
103        write_message(&mut self.socket, WriteMessage::Sign(key, data))?;
104        match read_message(&mut self.socket)? {
105            ReadMessage::Signature(sig) => Ok(sig),
106            ReadMessage::Failure => Err(Error::RemoteFailure),
107            m => Err(unexpected_response(m)),
108        }
109    }
110
111    fn expect_success(&mut self) -> Result<()> {
112        let response = read_message(&mut self.socket)?;
113        match response {
114            ReadMessage::Success => Ok(()),
115            ReadMessage::Failure => Err(Error::RemoteFailure),
116            _ => Err(Error::InvalidMessage("Unexpected response".to_string())),
117        }
118    }
119}
120
121fn unexpected_response(message: ReadMessage) -> Error {
122    let error = format!("Agent responded with unexpected message '{:?}'", message);
123    Error::InvalidMessage(error)
124}