Skip to content

Commit

Permalink
Add the most minimal example of an agent
Browse files Browse the repository at this point in the history
Signed-off-by: Wiktor Kwapisiewicz <wiktor@metacode.biz>
  • Loading branch information
wiktor-k committed Jan 20, 2025
1 parent 933166c commit 5f4f2d3
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 0 deletions.
4 changes: 4 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ The examples in this directory show slightly more elaborate use-cases that can b

## Agents

### `random-key`

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`).

### `key-storage`

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.
Expand Down
109 changes: 109 additions & 0 deletions examples/random-key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use std::sync::{Arc, Mutex};

use async_trait::async_trait;
use rsa::pkcs1v15::SigningKey;
use rsa::sha2::{Sha256, Sha512};
use rsa::signature::{RandomizedSigner, SignatureEncoding};
use sha1::Sha1;
#[cfg(windows)]
use ssh_agent_lib::agent::NamedPipeListener as Listener;
use ssh_agent_lib::agent::{listen, Session};
use ssh_agent_lib::error::AgentError;
use ssh_agent_lib::proto::{message, signature, SignRequest};
use ssh_key::private::RsaKeypair;
use ssh_key::{
private::{KeypairData, PrivateKey},
public::PublicKey,
Algorithm, Signature,
};
#[cfg(not(windows))]
use tokio::net::UnixListener as Listener;

#[derive(Clone)]
struct RandomKey {
private_key: Arc<Mutex<Option<PrivateKey>>>,
}

impl RandomKey {
pub fn new() -> Result<Self, AgentError> {
let rsa = RsaKeypair::random(&mut rand::thread_rng(), 2048).map_err(AgentError::other)?;
let privkey = PrivateKey::new(KeypairData::Rsa(rsa), "automatically generated RSA key")
.map_err(AgentError::other)?;
Ok(Self {
private_key: Arc::new(Mutex::new(Some(privkey))),
})
}
}

#[crate::async_trait]
impl Session for RandomKey {
async fn sign(&mut self, sign_request: SignRequest) -> Result<Signature, AgentError> {
let pubkey: PublicKey = sign_request.pubkey.clone().into();

if let Some(private_key) = self.private_key.lock().unwrap().as_ref() {
if PublicKey::from(private_key) != pubkey {
return Err(std::io::Error::other("Key not found").into());
}
match private_key.key_data() {
KeypairData::Rsa(ref key) => {
let algorithm;

let private_key: rsa::RsaPrivateKey =
key.try_into().map_err(AgentError::other)?;
let mut rng = rand::thread_rng();
let data = &sign_request.data;

let signature = if sign_request.flags & signature::RSA_SHA2_512 != 0 {
algorithm = "rsa-sha2-512";
SigningKey::<Sha512>::new(private_key).sign_with_rng(&mut rng, data)
} else if sign_request.flags & signature::RSA_SHA2_256 != 0 {
algorithm = "rsa-sha2-256";
SigningKey::<Sha256>::new(private_key).sign_with_rng(&mut rng, data)
} else {
algorithm = "ssh-rsa";
SigningKey::<Sha1>::new(private_key).sign_with_rng(&mut rng, data)
};
eprintln!("algo: {algorithm}, bytes: {signature:?}");
Ok(Signature::new(
Algorithm::new(algorithm).map_err(AgentError::other)?,
signature.to_bytes().to_vec(),
)
.map_err(AgentError::other)?)
}
_ => Err(std::io::Error::other("Signature for key type not implemented").into()),
}
} else {
Err(std::io::Error::other("Failed to create signature: identity not found").into())
}
}

async fn request_identities(&mut self) -> Result<Vec<message::Identity>, AgentError> {
let mut identities = vec![];
if let Some(identity) = self.private_key.lock().unwrap().as_ref() {
identities.push(message::Identity {
pubkey: PublicKey::from(identity).into(),
comment: identity.comment().into(),
})
}
Ok(identities)
}
}

#[tokio::main]
async fn main() -> Result<(), AgentError> {
env_logger::init();

#[cfg(not(windows))]
let socket = "ssh-agent.sock";
#[cfg(windows)]
let socket = r"\\.\pipe\agent";

let _ = std::fs::remove_file(socket); // remove the socket if exists

// This is only used for integration tests on Windows:
#[cfg(windows)]
std::fs::File::create("server-started")?;

listen(Listener::bind(socket)?, RandomKey::new()?).await?;
Ok(())
}

0 comments on commit 5f4f2d3

Please sign in to comment.