Skip to content

Commit 75682ad

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

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-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

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

0 commit comments

Comments
 (0)