Skip to content

Commit 031fb08

Browse files
committed
Add blocking client API
Fixes: #74 Signed-off-by: Wiktor Kwapisiewicz <wiktor@metacode.biz>
1 parent 3ba5c29 commit 031fb08

File tree

3 files changed

+198
-0
lines changed

3 files changed

+198
-0
lines changed

examples/ssh-agent-client-blocking.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
mod extensions;
2+
3+
#[cfg(unix)]
4+
fn main() -> testresult::TestResult {
5+
use std::os::unix::net::UnixStream;
6+
7+
use extensions::{DecryptIdentities, RequestDecryptIdentities};
8+
use ssh_agent_lib::{blocking::Client, proto::Extension};
9+
10+
let mut client = Client::new(UnixStream::connect(std::env::var("SSH_AUTH_SOCK")?)?);
11+
12+
eprintln!(
13+
"Identities that this agent knows of: {:#?}",
14+
client.request_identities()?
15+
);
16+
17+
if let Ok(Some(identities)) =
18+
client.extension(Extension::new_message(RequestDecryptIdentities)?)
19+
{
20+
let identities = identities.parse_message::<DecryptIdentities>()?;
21+
eprintln!("Decrypt identities that this agent knows of: {identities:#?}",);
22+
} else {
23+
eprintln!("No decryption identities found.");
24+
}
25+
26+
Ok(())
27+
}
28+
29+
#[cfg(windows)]
30+
fn main() {
31+
eprintln!("Sadly, there are no high-quality sync named pipe crates as of 2024");
32+
}

src/blocking.rs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
//! Blocking API.
2+
3+
use std::io::{Read, Write};
4+
5+
use byteorder::{BigEndian, ByteOrder};
6+
use ssh_encoding::{Decode, Encode};
7+
use ssh_key::Signature;
8+
9+
use crate::{
10+
error::AgentError,
11+
proto::{
12+
AddIdentity, AddIdentityConstrained, AddSmartcardKeyConstrained, Extension, Identity,
13+
ProtoError, RemoveIdentity, Request, Response, SignRequest, SmartcardKey,
14+
},
15+
};
16+
17+
/// Blocking SSH agent client.
18+
#[derive(Debug)]
19+
pub struct Client<S: Read + Write> {
20+
stream: S,
21+
}
22+
23+
impl<S: Read + Write> Client<S> {
24+
/// Construct a new SSH agent client for a given transport stream.
25+
pub fn new(stream: S) -> Self {
26+
Self { stream }
27+
}
28+
29+
/// Extracts inner stream by consuming this object.
30+
pub fn into_inner(self) -> S {
31+
self.stream
32+
}
33+
34+
fn handle(&mut self, request: Request) -> Result<Response, ProtoError> {
35+
// send the request
36+
let mut bytes = Vec::new();
37+
let len = request.encoded_len()? as u32;
38+
len.encode(&mut bytes)?;
39+
request.encode(&mut bytes)?;
40+
self.stream.write_all(&bytes)?;
41+
42+
// read the response
43+
let mut len: [u8; 4] = [0; 4];
44+
self.stream.read_exact(&mut len[..])?;
45+
let len = BigEndian::read_u32(&len) as usize;
46+
bytes.resize(len, 0);
47+
self.stream.read_exact(&mut bytes)?;
48+
49+
Response::decode(&mut &bytes[..])
50+
}
51+
52+
/// Request a list of keys managed by this session.
53+
pub fn request_identities(&mut self) -> Result<Vec<Identity>, AgentError> {
54+
if let Response::IdentitiesAnswer(identities) = self.handle(Request::RequestIdentities)? {
55+
Ok(identities)
56+
} else {
57+
Err(ProtoError::UnexpectedResponse.into())
58+
}
59+
}
60+
61+
/// Perform a private key signature operation.
62+
pub fn sign(&mut self, request: SignRequest) -> Result<Signature, AgentError> {
63+
if let Response::SignResponse(response) = self.handle(Request::SignRequest(request))? {
64+
Ok(response)
65+
} else {
66+
Err(ProtoError::UnexpectedResponse.into())
67+
}
68+
}
69+
70+
/// Add a private key to the agent.
71+
pub fn add_identity(&mut self, identity: AddIdentity) -> Result<(), AgentError> {
72+
if let Response::Success = self.handle(Request::AddIdentity(identity))? {
73+
Ok(())
74+
} else {
75+
Err(ProtoError::UnexpectedResponse.into())
76+
}
77+
}
78+
79+
/// Add a private key to the agent with a set of constraints.
80+
pub fn add_identity_constrained(
81+
&mut self,
82+
identity: AddIdentityConstrained,
83+
) -> Result<(), AgentError> {
84+
if let Response::Success = self.handle(Request::AddIdConstrained(identity))? {
85+
Ok(())
86+
} else {
87+
Err(ProtoError::UnexpectedResponse.into())
88+
}
89+
}
90+
91+
/// Remove private key from an agent.
92+
pub fn remove_identity(&mut self, identity: RemoveIdentity) -> Result<(), AgentError> {
93+
if let Response::Success = self.handle(Request::RemoveIdentity(identity))? {
94+
Ok(())
95+
} else {
96+
Err(ProtoError::UnexpectedResponse.into())
97+
}
98+
}
99+
100+
/// Remove all keys from an agent.
101+
pub fn remove_all_identities(&mut self) -> Result<(), AgentError> {
102+
if let Response::Success = self.handle(Request::RemoveAllIdentities)? {
103+
Ok(())
104+
} else {
105+
Err(ProtoError::UnexpectedResponse.into())
106+
}
107+
}
108+
109+
/// Add a key stored on a smartcard.
110+
pub fn add_smartcard_key(&mut self, key: SmartcardKey) -> Result<(), AgentError> {
111+
if let Response::Success = self.handle(Request::AddSmartcardKey(key))? {
112+
Ok(())
113+
} else {
114+
Err(ProtoError::UnexpectedResponse.into())
115+
}
116+
}
117+
118+
/// Add a key stored on a smartcard with a set of constraints.
119+
pub fn add_smartcard_key_constrained(
120+
&mut self,
121+
key: AddSmartcardKeyConstrained,
122+
) -> Result<(), AgentError> {
123+
if let Response::Success = self.handle(Request::AddSmartcardKeyConstrained(key))? {
124+
Ok(())
125+
} else {
126+
Err(ProtoError::UnexpectedResponse.into())
127+
}
128+
}
129+
130+
/// Remove a smartcard key from the agent.
131+
pub fn remove_smartcard_key(&mut self, key: SmartcardKey) -> Result<(), AgentError> {
132+
if let Response::Success = self.handle(Request::RemoveSmartcardKey(key))? {
133+
Ok(())
134+
} else {
135+
Err(ProtoError::UnexpectedResponse.into())
136+
}
137+
}
138+
139+
/// Temporarily lock the agent with a password.
140+
pub fn lock(&mut self, key: String) -> Result<(), AgentError> {
141+
if let Response::Success = self.handle(Request::Lock(key))? {
142+
Ok(())
143+
} else {
144+
Err(ProtoError::UnexpectedResponse.into())
145+
}
146+
}
147+
148+
/// Unlock the agent with a password.
149+
pub fn unlock(&mut self, key: String) -> Result<(), AgentError> {
150+
if let Response::Success = self.handle(Request::Unlock(key))? {
151+
Ok(())
152+
} else {
153+
Err(ProtoError::UnexpectedResponse.into())
154+
}
155+
}
156+
157+
/// Invoke a custom, vendor-specific extension on the agent.
158+
pub fn extension(&mut self, extension: Extension) -> Result<Option<Extension>, AgentError> {
159+
match self.handle(Request::Extension(extension))? {
160+
Response::Success => Ok(None),
161+
Response::ExtensionResponse(response) => Ok(Some(response)),
162+
_ => Err(ProtoError::UnexpectedResponse.into()),
163+
}
164+
}
165+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub mod proto;
88

99
#[cfg(feature = "agent")]
1010
pub mod agent;
11+
pub mod blocking;
1112
#[cfg(feature = "agent")]
1213
pub mod client;
1314
#[cfg(feature = "codec")]

0 commit comments

Comments
 (0)