Skip to content

Commit 5740f47

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

File tree

2 files changed

+117
-0
lines changed

2 files changed

+117
-0
lines changed

examples/README.md

Lines changed: 4 additions & 0 deletions
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

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

0 commit comments

Comments
 (0)