Skip to content

Commit

Permalink
Add blocking client API
Browse files Browse the repository at this point in the history
Fixes: #74
Signed-off-by: Wiktor Kwapisiewicz <[email protected]>
  • Loading branch information
wiktor-k committed Jun 7, 2024
1 parent 23c15e3 commit 347a806
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 0 deletions.
26 changes: 26 additions & 0 deletions examples/ssh-agent-client-blocking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#![cfg(unix)]
use std::os::unix::net::UnixStream;

use ssh_agent_lib::{blocking::Client, proto::Extension};
mod extensions;
use extensions::{DecryptIdentities, RequestDecryptIdentities};

fn main() -> testresult::TestResult {
let mut client = Client::new(UnixStream::connect(std::env::var("SSH_AUTH_SOCK")?)?);

eprintln!(
"Identities that this agent knows of: {:#?}",
client.request_identities()?
);

if let Ok(Some(identities)) =
client.extension(Extension::new_message(RequestDecryptIdentities)?)
{
let identities = identities.parse_message::<DecryptIdentities>()?;
eprintln!("Decrypt identities that this agent knows of: {identities:#?}",);
} else {
eprintln!("No decryption identities found.");
}

Ok(())
}
160 changes: 160 additions & 0 deletions src/blocking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//! Blocking API.
use std::io::{Read, Write};

use byteorder::{BigEndian, ByteOrder};
use ssh_encoding::{Decode, Encode};
use ssh_key::Signature;

use crate::{
error::AgentError,
proto::{
AddIdentity, AddIdentityConstrained, AddSmartcardKeyConstrained, Extension, Identity,
ProtoError, RemoveIdentity, Request, Response, SignRequest, SmartcardKey,
},
};

/// Blocking SSH agent client.
#[derive(Debug)]
pub struct Client<S: Read + Write> {
stream: S,
}

impl<S: Read + Write> Client<S> {
/// Construct a new SSH agent client for a given transport stream.
pub fn new(stream: S) -> Self {
Self { stream }
}

fn handle(&mut self, request: Request) -> Result<Response, ProtoError> {
// send the request
let mut bytes = Vec::new();
let len = request.encoded_len()? as u32;
len.encode(&mut bytes)?;
request.encode(&mut bytes)?;
self.stream.write_all(&bytes)?;

// read the response
let mut len: [u8; 4] = [0; 4];
self.stream.read_exact(&mut len[..])?;
let len = BigEndian::read_u32(&len) as usize;
bytes.resize(len, 0);
self.stream.read_exact(&mut bytes)?;

Response::decode(&mut &bytes[..])
}

/// Request a list of keys managed by this session.
pub fn request_identities(&mut self) -> Result<Vec<Identity>, AgentError> {
if let Response::IdentitiesAnswer(identities) = self.handle(Request::RequestIdentities)? {
Ok(identities)
} else {
Err(ProtoError::UnexpectedResponse.into())
}
}

/// Perform a private key signature operation.
pub fn sign(&mut self, request: SignRequest) -> Result<Signature, AgentError> {
if let Response::SignResponse(response) = self.handle(Request::SignRequest(request))? {
Ok(response)
} else {
Err(ProtoError::UnexpectedResponse.into())
}
}

/// Add a private key to the agent.
pub fn add_identity(&mut self, identity: AddIdentity) -> Result<(), AgentError> {
if let Response::Success = self.handle(Request::AddIdentity(identity))? {
Ok(())
} else {
Err(ProtoError::UnexpectedResponse.into())
}
}

/// Add a private key to the agent with a set of constraints.
pub fn add_identity_constrained(
&mut self,
identity: AddIdentityConstrained,
) -> Result<(), AgentError> {
if let Response::Success = self.handle(Request::AddIdConstrained(identity))? {
Ok(())
} else {
Err(ProtoError::UnexpectedResponse.into())
}
}

/// Remove private key from an agent.
pub fn remove_identity(&mut self, identity: RemoveIdentity) -> Result<(), AgentError> {
if let Response::Success = self.handle(Request::RemoveIdentity(identity))? {
Ok(())
} else {
Err(ProtoError::UnexpectedResponse.into())
}
}

/// Remove all keys from an agent.
pub fn remove_all_identities(&mut self) -> Result<(), AgentError> {
if let Response::Success = self.handle(Request::RemoveAllIdentities)? {
Ok(())
} else {
Err(ProtoError::UnexpectedResponse.into())
}
}

/// Add a key stored on a smartcard.
pub fn add_smartcard_key(&mut self, key: SmartcardKey) -> Result<(), AgentError> {
if let Response::Success = self.handle(Request::AddSmartcardKey(key))? {
Ok(())
} else {
Err(ProtoError::UnexpectedResponse.into())
}
}

/// Add a key stored on a smartcard with a set of constraints.
pub fn add_smartcard_key_constrained(
&mut self,
key: AddSmartcardKeyConstrained,
) -> Result<(), AgentError> {
if let Response::Success = self.handle(Request::AddSmartcardKeyConstrained(key))? {
Ok(())
} else {
Err(ProtoError::UnexpectedResponse.into())
}
}

/// Remove a smartcard key from the agent.
pub fn remove_smartcard_key(&mut self, key: SmartcardKey) -> Result<(), AgentError> {
if let Response::Success = self.handle(Request::RemoveSmartcardKey(key))? {
Ok(())
} else {
Err(ProtoError::UnexpectedResponse.into())
}
}

/// Temporarily lock the agent with a password.
pub fn lock(&mut self, key: String) -> Result<(), AgentError> {
if let Response::Success = self.handle(Request::Lock(key))? {
Ok(())
} else {
Err(ProtoError::UnexpectedResponse.into())
}
}

/// Unlock the agent with a password.
pub fn unlock(&mut self, key: String) -> Result<(), AgentError> {
if let Response::Success = self.handle(Request::Unlock(key))? {
Ok(())
} else {
Err(ProtoError::UnexpectedResponse.into())
}
}

/// Invoke a custom, vendor-specific extension on the agent.
pub fn extension(&mut self, extension: Extension) -> Result<Option<Extension>, AgentError> {
match self.handle(Request::Extension(extension))? {
Response::Success => Ok(None),
Response::ExtensionResponse(response) => Ok(Some(response)),
_ => Err(ProtoError::UnexpectedResponse.into()),
}
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod proto;

#[cfg(feature = "agent")]
pub mod agent;
pub mod blocking;
#[cfg(feature = "agent")]
pub mod client;
#[cfg(feature = "codec")]
Expand Down

0 comments on commit 347a806

Please sign in to comment.