use crate::codec::{read_message, write_message, ReadMessage, WriteMessage};
#[cfg(target_family = "windows")]
use interprocess::os::windows::named_pipe::{pipe_mode, DuplexPipeStream};
use ssh_key::public::KeyData;
use ssh_key::{Certificate, PrivateKey, PublicKey, Signature};
use std::borrow::Cow;
use std::io::{Read, Write};
#[cfg(target_family = "unix")]
use std::os::unix::net::UnixStream;
use std::path::Path;
mod codec;
mod error;
pub use self::error::Error;
pub use self::error::Result;
pub trait ReadWrite: Read + Write {}
impl<T> ReadWrite for T where T: Read + Write {}
pub struct Client {
socket: Box<dyn ReadWrite>,
}
#[derive(Debug, PartialEq, Clone)]
pub enum Identity<'a> {
PublicKey(Box<Cow<'a, PublicKey>>),
Certificate(Box<Cow<'a, Certificate>>),
}
impl<'a> From<PublicKey> for Identity<'a> {
fn from(value: PublicKey) -> Self {
Identity::PublicKey(Box::new(Cow::Owned(value)))
}
}
impl<'a> From<&'a PublicKey> for Identity<'a> {
fn from(value: &'a PublicKey) -> Self {
Identity::PublicKey(Box::new(Cow::Borrowed(value)))
}
}
impl<'a> From<Certificate> for Identity<'a> {
fn from(value: Certificate) -> Self {
Identity::Certificate(Box::new(Cow::Owned(value)))
}
}
impl<'a> From<&'a Certificate> for Identity<'a> {
fn from(value: &'a Certificate) -> Self {
Identity::Certificate(Box::new(Cow::Borrowed(value)))
}
}
impl<'a> From<&'a Identity<'a>> for &'a KeyData {
fn from(value: &'a Identity) -> Self {
match value {
Identity::PublicKey(pk) => pk.key_data(),
Identity::Certificate(cert) => cert.public_key(),
}
}
}
impl<'a> Client {
#[cfg(target_family = "unix")]
pub fn connect(path: &Path) -> Result<Client> {
let socket = Box::new(UnixStream::connect(path)?);
Ok(Client { socket })
}
#[cfg(target_family = "windows")]
pub fn connect(path: &Path) -> Result<Client> {
let pipe = DuplexPipeStream::<pipe_mode::Bytes>::connect_by_path(path)?;
Ok(Client {
socket: Box::new(pipe),
})
}
pub fn with_read_write(read_write: Box<dyn ReadWrite>) -> Client {
Client { socket: read_write }
}
#[deprecated(note = "Use list_all_identities() instead")]
pub fn list_identities(&mut self) -> Result<Vec<PublicKey>> {
self.list_all_identities().map(|identities| {
identities
.into_iter()
.filter_map(|i| match i {
Identity::PublicKey(pk) => Some(pk.into_owned()),
_ => None,
})
.collect()
})
}
pub fn list_all_identities(&mut self) -> Result<Vec<Identity<'static>>> {
write_message(&mut self.socket, WriteMessage::RequestIdentities)?;
match read_message(&mut self.socket)? {
ReadMessage::Identities(identities) => Ok(identities),
m => Err(unexpected_response(m)),
}
}
pub fn add_identity(&mut self, key: &PrivateKey) -> Result<()> {
write_message(&mut self.socket, WriteMessage::AddIdentity(key))?;
self.expect_success()
}
pub fn remove_identity(&mut self, key: &PrivateKey) -> Result<()> {
write_message(&mut self.socket, WriteMessage::RemoveIdentity(key))?;
self.expect_success()
}
pub fn remove_all_identities(&mut self) -> Result<()> {
write_message(&mut self.socket, WriteMessage::RemoveAllIdentities)?;
self.expect_success()
}
pub fn sign(&mut self, key: impl Into<Identity<'a>>, data: &[u8]) -> Result<Signature> {
self.sign_with_ref(&key.into(), data)
}
pub fn sign_with_ref(&mut self, identity: &Identity, data: &[u8]) -> Result<Signature> {
write_message(&mut self.socket, WriteMessage::Sign(identity, data))?;
match read_message(&mut self.socket)? {
ReadMessage::Signature(sig) => Ok(sig),
ReadMessage::Failure => Err(Error::RemoteFailure),
m => Err(unexpected_response(m)),
}
}
fn expect_success(&mut self) -> Result<()> {
let response = read_message(&mut self.socket)?;
match response {
ReadMessage::Success => Ok(()),
ReadMessage::Failure => Err(Error::RemoteFailure),
_ => Err(Error::InvalidMessage("Unexpected response".to_string())),
}
}
}
fn unexpected_response(message: ReadMessage) -> Error {
let error = format!("Agent responded with unexpected message '{message:?}'");
Error::InvalidMessage(error)
}