ssh_agent_client_rs/
lib.rs1use 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::public::KeyData;
26use ssh_key::{Certificate, PrivateKey, PublicKey, Signature};
27use std::borrow::Cow;
28use std::io::{Read, Write};
29#[cfg(target_family = "unix")]
30use std::os::unix::net::UnixStream;
31use std::path::Path;
32
33mod codec;
34mod error;
35
36pub use self::error::Error;
37pub use self::error::Result;
38
39pub trait ReadWrite: Read + Write {}
41
42impl<T> ReadWrite for T where T: Read + Write {}
45
46pub struct Client {
49 socket: Box<dyn ReadWrite>,
50}
51
52#[derive(Debug, PartialEq, Clone)]
53pub enum Identity<'a> {
54 PublicKey(Box<Cow<'a, PublicKey>>),
55 Certificate(Box<Cow<'a, Certificate>>),
56}
57
58impl<'a> From<PublicKey> for Identity<'a> {
59 fn from(value: PublicKey) -> Self {
60 Identity::PublicKey(Box::new(Cow::Owned(value)))
61 }
62}
63
64impl<'a> From<&'a PublicKey> for Identity<'a> {
65 fn from(value: &'a PublicKey) -> Self {
66 Identity::PublicKey(Box::new(Cow::Borrowed(value)))
67 }
68}
69
70impl<'a> From<Certificate> for Identity<'a> {
71 fn from(value: Certificate) -> Self {
72 Identity::Certificate(Box::new(Cow::Owned(value)))
73 }
74}
75
76impl<'a> From<&'a Certificate> for Identity<'a> {
77 fn from(value: &'a Certificate) -> Self {
78 Identity::Certificate(Box::new(Cow::Borrowed(value)))
79 }
80}
81
82impl<'a> From<&'a Identity<'a>> for &'a KeyData {
83 fn from(value: &'a Identity) -> Self {
84 match value {
85 Identity::PublicKey(pk) => pk.key_data(),
86 Identity::Certificate(cert) => cert.public_key(),
87 }
88 }
89}
90
91impl<'a> Client {
92 #[cfg(target_family = "unix")]
94 pub fn connect(path: &Path) -> Result<Client> {
95 let socket = Box::new(UnixStream::connect(path)?);
96 Ok(Client { socket })
97 }
98
99 #[cfg(target_family = "windows")]
102 pub fn connect(path: &Path) -> Result<Client> {
103 let pipe = DuplexPipeStream::<pipe_mode::Bytes>::connect_by_path(path)?;
104 Ok(Client {
105 socket: Box::new(pipe),
106 })
107 }
108
109 pub fn with_read_write(read_write: Box<dyn ReadWrite>) -> Client {
112 Client { socket: read_write }
113 }
114
115 #[deprecated(note = "Use list_all_identities() instead")]
118 pub fn list_identities(&mut self) -> Result<Vec<PublicKey>> {
119 self.list_all_identities().map(|identities| {
120 identities
121 .into_iter()
122 .filter_map(|i| match i {
123 Identity::PublicKey(pk) => Some(pk.into_owned()),
124 _ => None,
125 })
126 .collect()
127 })
128 }
129 pub fn list_all_identities(&mut self) -> Result<Vec<Identity<'static>>> {
131 write_message(&mut self.socket, WriteMessage::RequestIdentities)?;
132 match read_message(&mut self.socket)? {
133 ReadMessage::Identities(identities) => Ok(identities),
134 m => Err(unexpected_response(m)),
135 }
136 }
137
138 pub fn add_identity(&mut self, key: &PrivateKey) -> Result<()> {
140 write_message(&mut self.socket, WriteMessage::AddIdentity(key))?;
141 self.expect_success()
142 }
143
144 pub fn remove_identity(&mut self, key: &PrivateKey) -> Result<()> {
146 write_message(&mut self.socket, WriteMessage::RemoveIdentity(key))?;
147 self.expect_success()
148 }
149
150 pub fn remove_all_identities(&mut self) -> Result<()> {
152 write_message(&mut self.socket, WriteMessage::RemoveAllIdentities)?;
153 self.expect_success()
154 }
155
156 pub fn sign(&mut self, key: impl Into<Identity<'a>>, data: &[u8]) -> Result<Signature> {
160 self.sign_with_ref(&key.into(), data)
161 }
162 pub fn sign_with_ref(&mut self, identity: &Identity, data: &[u8]) -> Result<Signature> {
163 write_message(&mut self.socket, WriteMessage::Sign(identity, data))?;
164 match read_message(&mut self.socket)? {
165 ReadMessage::Signature(sig) => Ok(sig),
166 ReadMessage::Failure => Err(Error::RemoteFailure),
167 m => Err(unexpected_response(m)),
168 }
169 }
170
171 fn expect_success(&mut self) -> Result<()> {
172 let response = read_message(&mut self.socket)?;
173 match response {
174 ReadMessage::Success => Ok(()),
175 ReadMessage::Failure => Err(Error::RemoteFailure),
176 _ => Err(Error::InvalidMessage("Unexpected response".to_string())),
177 }
178 }
179}
180
181fn unexpected_response(message: ReadMessage) -> Error {
182 let error = format!("Agent responded with unexpected message '{message:?}'");
183 Error::InvalidMessage(error)
184}