diff --git a/async/src/agent.rs b/async/src/agent.rs new file mode 100644 index 0000000000000000000000000000000000000000..c0a40f960185699a4abbb0794f0147656d6340e5 --- /dev/null +++ b/async/src/agent.rs @@ -0,0 +1,188 @@ +#[allow(unused_imports)] +use { + sunset::{Error, Result}, + log::{debug, error, info, log, trace, warn}, +}; + +use std::path::Path; + +use pretty_hex::PrettyHex; +use tokio::net::UnixStream; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +use sunset_sshwire_derive::*; + +use crate::*; +use sunset::sshwire; +use sunset::{PubKey, AuthSigMsg, Signature}; +use sshwire::{WireError, WireResult, BinString, TextString, Blob, SSHSink, SSHSource, SSHDecode, SSHEncode}; +use sshwire::{SSHEncodeEnum, SSHDecodeEnum}; +use sunset::sign::{OwnedSig, SignKey}; +use sunset::sshnames::*; + +/* Must be sufficient for the list of all public keys */ +const BUF_SIZE: usize = 10240; + +#[derive(Debug, SSHEncode)] +struct AgentSignRequest<'a> { + pub key_blob: Blob<PubKey<'a>>, + pub msg: Blob<&'a AuthSigMsg<'a>>, + pub flags: u32, +} + +#[derive(Debug, SSHDecode)] +struct AgentSignResponse<'a> { + pub sig: Blob<Signature<'a>>, +} + +#[derive(Debug)] +struct AgentIdentitiesAnswer<'a> { + // [(key blob, comment)] + pub keys: Vec<(PubKey<'a>, TextString<'a>)>, +} + +#[derive(Debug)] +enum AgentRequest<'a> { + SignRequest(AgentSignRequest<'a>), + RequestIdentities, +} + +impl SSHEncode for AgentRequest<'_> { + fn enc<S>(&self, s: &mut S) -> WireResult<()> + where S: SSHSink { + match self { + Self::SignRequest(a) => { + let n = AgentMessageNum::SSH_AGENTC_SIGN_REQUEST as u8; + n.enc(s)?; + a.enc(s)?; + } + Self::RequestIdentities => { + let n = AgentMessageNum::SSH_AGENTC_REQUEST_IDENTITIES as u8; + n.enc(s)?; + } + } + Ok(()) + } +} + +/// The subset of responses we recognise +#[derive(Debug)] +enum AgentResponse<'a> { + IdentitiesAnswer(AgentIdentitiesAnswer<'a>), + SignResponse(AgentSignResponse<'a>), +} + +impl<'de: 'a, 'a> SSHDecode<'de> for AgentResponse<'a> { + fn dec<S>(s: &mut S) -> WireResult<Self> + where S: SSHSource<'de> { + let number = u8::dec(s)?; + if number == AgentMessageNum::SSH_AGENT_IDENTITIES_ANSWER as u8 { + Ok(Self::IdentitiesAnswer(AgentIdentitiesAnswer::dec(s)?)) + } else if number == AgentMessageNum::SSH_AGENT_SIGN_RESPONSE as u8 { + Ok(Self::SignResponse(AgentSignResponse::dec(s)?)) + } else { + Err(WireError::UnknownPacket { number }) + } + } +} + +impl<'de: 'a, 'a> SSHDecode<'de> for AgentIdentitiesAnswer<'a> { + fn dec<S>(s: &mut S) -> WireResult<Self> where S: SSHSource<'de> { + // uint32 nkeys + // Where "nkeys" indicates the number of keys to follow. Following the + // preamble are zero or more keys, each encoded as: + // string key blob + // string comment + let l = u32::dec(s)?; + let mut keys = vec![]; + for _ in 0..l { + let kb = Blob::<PubKey>::dec(s)?; + let comment = TextString::dec(s)?; + keys.push((kb.0, comment)) + } + Ok(AgentIdentitiesAnswer { + keys, + }) + } +} + +pub struct AgentClient { + conn: UnixStream, + buf: Vec<u8>, +} + +impl AgentClient { + pub async fn new(path: impl AsRef<Path>) -> Result<Self, std::io::Error> { + let conn = UnixStream::connect(path).await?; + Ok(Self { + conn, + buf: vec![0u8; BUF_SIZE], + }) + } + + async fn request(&mut self, r: AgentRequest<'_>) -> Result<AgentResponse> { + let mut ctx = sunset::packets::ParseContext::new(); + ctx.method_pubkey_force_sig_bool = true; + + let l = sshwire::write_ssh_ctx(&mut self.buf, &Blob(r), ctx)?; + let b = &self.buf[..l]; + + trace!("agent request {:?}", b.hex_dump()); + + self.conn.write_all(b).await?; + self.response().await + } + + async fn response(&mut self) -> Result<AgentResponse> { + let mut l = [0u8; 4]; + self.conn.read_exact(&mut l).await?; + let l = u32::from_be_bytes(l) as usize; + if l > BUF_SIZE { + return Err(Error::msg("Bad buffer size")); + } + let b = &mut self.buf[..l]; + self.conn.read_exact(b).await?; + let r: AgentResponse = sshwire::read_ssh(b, None)?; + Ok(r) + } + + pub async fn keys(&mut self) -> Result<Vec<SignKey>> { + match self.request(AgentRequest::RequestIdentities).await? { + AgentResponse::IdentitiesAnswer(i) => { + let mut keys = vec![]; + for (pk, comment) in i.keys.iter() { + match SignKey::from_agent_pubkey(pk) { + Ok(k) => keys.push(k), + Err(e) => debug!("skipping agent key {comment:?}: {e}") + } + } + Ok(keys) + } + resp => { + debug!("response: {resp:?}"); + Err(Error::msg("Unexpected agent response")) + } + } + } + + pub async fn sign_auth(&mut self, key: &SignKey, msg: &AuthSigMsg<'_>) -> Result<OwnedSig> { + // TODO: rsa needs SSH_AGENT_FLAG_RSA_SHA2_256 + let flags = 0u32; + let r = AgentRequest::SignRequest(AgentSignRequest { + key_blob: Blob(key.pubkey()), + msg: Blob(msg), + flags, + }); + + match self.request(r).await? { + AgentResponse::SignResponse(s) => { + s.sig.0.try_into() + } + resp => { + debug!("response: {resp:?}"); + Err(Error::msg("Unexpected agent response")) + } + } + } + +}