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