use std::path::PathBuf;
use futures::lock::Mutex;
use lnk_thrussh_agent::{
client::{self, AgentClient, ClientStream},
Constraint,
};
pub use super::ed25519;
pub mod error {
use super::*;
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Connect {
#[error(transparent)]
Client(#[from] client::Error),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum AddKey {
#[error(transparent)]
Client(#[from] client::Error),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum RemoveKey {
#[error(transparent)]
Client(#[from] client::Error),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ListKeys {
#[error(transparent)]
Client(#[from] client::Error),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Sign {
#[error(transparent)]
Client(#[from] client::Error),
}
}
pub struct SshAgent {
key: ed25519::PublicKey,
path: Option<PathBuf>,
}
impl SshAgent {
pub fn new(key: ed25519::PublicKey) -> Self {
Self { key, path: None }
}
pub fn with_path(self, path: PathBuf) -> Self {
Self {
path: Some(path),
..self
}
}
pub async fn connect<S>(
&self,
) -> Result<impl ed25519::Signer<Error = error::Sign>, error::Connect>
where
S: ClientStream + Unpin,
{
let client = self
.client::<S>()
.await
.map(|client| Mutex::new(Some(client)))?;
Ok(Signer {
rfc: self.key,
client,
})
}
async fn client<S>(&self) -> Result<AgentClient<S>, client::Error>
where
S: ClientStream + Unpin,
{
match &self.path {
None => Ok(S::connect_env().await?),
Some(path) => Ok(S::connect_uds(path).await?),
}
}
}
type Client<S> = Mutex<Option<AgentClient<S>>>;
struct Signer<S> {
rfc: ed25519::PublicKey,
client: Client<S>,
}
pub async fn add_key<S>(
agent: &SshAgent,
secret: ed25519_zebra::SigningKey,
constraints: &[Constraint],
) -> Result<(), error::AddKey>
where
S: ClientStream + Unpin,
{
let mut client = agent.client::<S>().await?;
let secret = ed25519::SigningKey::from(secret);
client.add_identity(&secret, constraints).await?;
Ok(())
}
pub async fn remove_key<S>(
agent: &SshAgent,
key: &ed25519::PublicKey,
) -> Result<(), error::RemoveKey>
where
S: ClientStream + Unpin,
{
let mut client = agent.client::<S>().await?;
let keys = client.remove_identity(key).await?;
Ok(keys)
}
pub async fn list_keys<S>(agent: &SshAgent) -> Result<Vec<ed25519::PublicKey>, error::ListKeys>
where
S: ClientStream + Unpin,
{
let mut client = agent.client::<S>().await?;
let keys = client.request_identities().await?;
Ok(keys)
}
#[async_trait]
impl<S> ed25519::Signer for Signer<S>
where
S: ClientStream + Unpin,
{
type Error = error::Sign;
fn public_key(&self) -> ed25519::PublicKey {
self.rfc
}
async fn sign(&self, data: &[u8]) -> Result<ed25519::Signature, Self::Error> {
let mut guard = self.client.lock().await;
let client = match guard.take() {
None => ClientStream::connect_env().await?,
Some(client) => client,
};
let (client, sig) = client.sign_request_signature(&self.rfc, data).await;
*guard = Some(client);
Ok(sig?)
}
}