Skip to content

Commit 5f4f2d3

Browse files
committed
Add the most minimal example of an agent
Signed-off-by: Wiktor Kwapisiewicz <[email protected]>
1 parent 933166c commit 5f4f2d3

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-0
lines changed

examples/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ The examples in this directory show slightly more elaborate use-cases that can b
44

55
## Agents
66

7+
### `random-key`
8+
9+
Generates a new random key and supports only the basic operations used by the OpenSSH client: retrieving supported public keys (`request_identities`) and signing using the ephemeral key (`sign_request`).
10+
711
### `key-storage`
812

913
Implements a simple agent which remembers RSA private keys (added via `ssh-add`) and allows fetching their public keys and signing using three different signing mechanisms.

examples/random-key.rs

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use std::sync::{Arc, Mutex};
2+
3+
use async_trait::async_trait;
4+
use rsa::pkcs1v15::SigningKey;
5+
use rsa::sha2::{Sha256, Sha512};
6+
use rsa::signature::{RandomizedSigner, SignatureEncoding};
7+
use sha1::Sha1;
8+
#[cfg(windows)]
9+
use ssh_agent_lib::agent::NamedPipeListener as Listener;
10+
use ssh_agent_lib::agent::{listen, Session};
11+
use ssh_agent_lib::error::AgentError;
12+
use ssh_agent_lib::proto::{message, signature, SignRequest};
13+
use ssh_key::private::RsaKeypair;
14+
use ssh_key::{
15+
private::{KeypairData, PrivateKey},
16+
public::PublicKey,
17+
Algorithm, Signature,
18+
};
19+
#[cfg(not(windows))]
20+
use tokio::net::UnixListener as Listener;
21+
22+
#[derive(Clone)]
23+
struct RandomKey {
24+
private_key: Arc<Mutex<Option<PrivateKey>>>,
25+
}
26+
27+
impl RandomKey {
28+
pub fn new() -> Result<Self, AgentError> {
29+
let rsa = RsaKeypair::random(&mut rand::thread_rng(), 2048).map_err(AgentError::other)?;
30+
let privkey = PrivateKey::new(KeypairData::Rsa(rsa), "automatically generated RSA key")
31+
.map_err(AgentError::other)?;
32+
Ok(Self {
33+
private_key: Arc::new(Mutex::new(Some(privkey))),
34+
})
35+
}
36+
}
37+
38+
#[crate::async_trait]
39+
impl Session for RandomKey {
40+
async fn sign(&mut self, sign_request: SignRequest) -> Result<Signature, AgentError> {
41+
let pubkey: PublicKey = sign_request.pubkey.clone().into();
42+
43+
if let Some(private_key) = self.private_key.lock().unwrap().as_ref() {
44+
if PublicKey::from(private_key) != pubkey {
45+
return Err(std::io::Error::other("Key not found").into());
46+
}
47+
match private_key.key_data() {
48+
KeypairData::Rsa(ref key) => {
49+
let algorithm;
50+
51+
let private_key: rsa::RsaPrivateKey =
52+
key.try_into().map_err(AgentError::other)?;
53+
let mut rng = rand::thread_rng();
54+
let data = &sign_request.data;
55+
56+
let signature = if sign_request.flags & signature::RSA_SHA2_512 != 0 {
57+
algorithm = "rsa-sha2-512";
58+
SigningKey::<Sha512>::new(private_key).sign_with_rng(&mut rng, data)
59+
} else if sign_request.flags & signature::RSA_SHA2_256 != 0 {
60+
algorithm = "rsa-sha2-256";
61+
SigningKey::<Sha256>::new(private_key).sign_with_rng(&mut rng, data)
62+
} else {
63+
algorithm = "ssh-rsa";
64+
SigningKey::<Sha1>::new(private_key).sign_with_rng(&mut rng, data)
65+
};
66+
eprintln!("algo: {algorithm}, bytes: {signature:?}");
67+
Ok(Signature::new(
68+
Algorithm::new(algorithm).map_err(AgentError::other)?,
69+
signature.to_bytes().to_vec(),
70+
)
71+
.map_err(AgentError::other)?)
72+
}
73+
_ => Err(std::io::Error::other("Signature for key type not implemented").into()),
74+
}
75+
} else {
76+
Err(std::io::Error::other("Failed to create signature: identity not found").into())
77+
}
78+
}
79+
80+
async fn request_identities(&mut self) -> Result<Vec<message::Identity>, AgentError> {
81+
let mut identities = vec![];
82+
if let Some(identity) = self.private_key.lock().unwrap().as_ref() {
83+
identities.push(message::Identity {
84+
pubkey: PublicKey::from(identity).into(),
85+
comment: identity.comment().into(),
86+
})
87+
}
88+
Ok(identities)
89+
}
90+
}
91+
92+
#[tokio::main]
93+
async fn main() -> Result<(), AgentError> {
94+
env_logger::init();
95+
96+
#[cfg(not(windows))]
97+
let socket = "ssh-agent.sock";
98+
#[cfg(windows)]
99+
let socket = r"\\.\pipe\agent";
100+
101+
let _ = std::fs::remove_file(socket); // remove the socket if exists
102+
103+
// This is only used for integration tests on Windows:
104+
#[cfg(windows)]
105+
std::fs::File::create("server-started")?;
106+
107+
listen(Listener::bind(socket)?, RandomKey::new()?).await?;
108+
Ok(())
109+
}

0 commit comments

Comments
 (0)