Skip to main content

russh/keys/agent/
client.rs

1use core::str;
2
3use byteorder::{BigEndian, ByteOrder};
4use bytes::Bytes;
5use log::{debug, error};
6use ssh_encoding::{Decode, Encode, Reader};
7use ssh_key::{Algorithm, Certificate, HashAlg, PrivateKey, PublicKey, Signature};
8use tokio;
9use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
10
11use super::{AgentIdentity, Constraint, msg};
12use crate::CryptoVec;
13use crate::helpers::EncodedExt;
14use crate::keys::{Error, key};
15
16pub trait AgentStream: AsyncRead + AsyncWrite {}
17
18impl<S: AsyncRead + AsyncWrite> AgentStream for S {}
19
20const MAX_AGENT_FRAME_LEN: usize = 256 * 1024;
21
22/// SSH agent client.
23pub struct AgentClient<S: AgentStream> {
24    stream: S,
25    buf: Vec<u8>,
26}
27
28impl<S: AgentStream + Send + Unpin + 'static> AgentClient<S> {
29    /// Wraps the internal stream in a Box<dyn _>, allowing different client
30    /// implementations to have the same type
31    pub fn dynamic(self) -> AgentClient<Box<dyn AgentStream + Send + Unpin + 'static>> {
32        AgentClient {
33            stream: Box::new(self.stream),
34            buf: self.buf,
35        }
36    }
37
38    pub fn into_inner(self) -> Box<dyn AgentStream + Send + Unpin + 'static> {
39        Box::new(self.stream)
40    }
41}
42
43// https://tools.ietf.org/html/draft-miller-ssh-agent-00#section-4.1
44impl<S: AgentStream + Unpin> AgentClient<S> {
45    /// Build a future that connects to an SSH agent via the provided
46    /// stream (on Unix, usually a Unix-domain socket).
47    pub fn connect(stream: S) -> Self {
48        AgentClient {
49            stream,
50            buf: Vec::new(),
51        }
52    }
53}
54
55#[cfg(unix)]
56impl AgentClient<tokio::net::UnixStream> {
57    /// Connect to an SSH agent via the provided
58    /// stream (on Unix, usually a Unix-domain socket).
59    pub async fn connect_uds<P: AsRef<std::path::Path>>(path: P) -> Result<Self, Error> {
60        let stream = tokio::net::UnixStream::connect(path).await?;
61        Ok(AgentClient {
62            stream,
63            buf: Vec::new(),
64        })
65    }
66
67    /// Connect to an SSH agent specified by the SSH_AUTH_SOCK
68    /// environment variable.
69    pub async fn connect_env() -> Result<Self, Error> {
70        let var = if let Ok(var) = std::env::var("SSH_AUTH_SOCK") {
71            var
72        } else {
73            return Err(Error::EnvVar("SSH_AUTH_SOCK"));
74        };
75        match Self::connect_uds(var).await {
76            Err(Error::IO(io_err)) if io_err.kind() == std::io::ErrorKind::NotFound => {
77                Err(Error::BadAuthSock)
78            }
79            owise => owise,
80        }
81    }
82}
83
84#[cfg(windows)]
85const ERROR_PIPE_BUSY: u32 = 231u32;
86
87#[cfg(windows)]
88impl AgentClient<pageant::PageantStream> {
89    /// Connect to a running Pageant instance
90    pub async fn connect_pageant() -> Result<Self, Error> {
91        Ok(Self::connect(pageant::PageantStream::new().await?))
92    }
93}
94
95#[cfg(windows)]
96impl AgentClient<tokio::net::windows::named_pipe::NamedPipeClient> {
97    /// Connect to an SSH agent via a Windows named pipe
98    pub async fn connect_named_pipe<P: AsRef<std::ffi::OsStr>>(path: P) -> Result<Self, Error> {
99        let stream = loop {
100            match tokio::net::windows::named_pipe::ClientOptions::new().open(path.as_ref()) {
101                Ok(client) => break client,
102                Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY as i32) => (),
103                Err(e) => return Err(e.into()),
104            }
105
106            tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
107        };
108
109        Ok(AgentClient {
110            stream,
111            buf: Vec::new(),
112        })
113    }
114}
115
116impl<S: AgentStream + Unpin> AgentClient<S> {
117    async fn read_frame(&mut self) -> Result<(), Error> {
118        self.buf.clear();
119        self.buf.resize(4, 0);
120        self.stream.read_exact(&mut self.buf).await?;
121
122        let len = BigEndian::read_u32(&self.buf) as usize;
123        if len > MAX_AGENT_FRAME_LEN {
124            return Err(Error::AgentProtocolError);
125        }
126
127        self.buf.clear();
128        self.buf.resize(len, 0);
129        self.stream.read_exact(&mut self.buf).await?;
130        Ok(())
131    }
132
133    async fn read_response(&mut self) -> Result<(), Error> {
134        // Writing the message
135        self.stream.write_all(&self.buf).await?;
136        self.stream.flush().await?;
137        self.read_frame().await
138    }
139
140    async fn read_success(&mut self) -> Result<(), Error> {
141        self.read_response().await?;
142        if self.buf.first() == Some(&msg::SUCCESS) {
143            Ok(())
144        } else {
145            Err(Error::AgentFailure)
146        }
147    }
148
149    /// Send a key to the agent, with a (possibly empty) slice of
150    /// constraints to apply when using the key to sign.
151    pub async fn add_identity(
152        &mut self,
153        key: &PrivateKey,
154        constraints: &[Constraint],
155    ) -> Result<(), Error> {
156        // See IETF draft-miller-ssh-agent-13, section 3.2 for format.
157        // https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent
158        self.buf.clear();
159        self.buf.resize(4, 0);
160        if constraints.is_empty() {
161            self.buf.push(msg::ADD_IDENTITY)
162        } else {
163            self.buf.push(msg::ADD_ID_CONSTRAINED)
164        }
165
166        key.key_data().encode(&mut self.buf)?;
167        "".encode(&mut self.buf)?; // comment field
168
169        if !constraints.is_empty() {
170            for cons in constraints {
171                match *cons {
172                    Constraint::KeyLifetime { seconds } => {
173                        msg::CONSTRAIN_LIFETIME.encode(&mut self.buf)?;
174                        seconds.encode(&mut self.buf)?;
175                    }
176                    Constraint::Confirm => self.buf.push(msg::CONSTRAIN_CONFIRM),
177                    Constraint::Extensions {
178                        ref name,
179                        ref details,
180                    } => {
181                        msg::CONSTRAIN_EXTENSION.encode(&mut self.buf)?;
182                        name.encode(&mut self.buf)?;
183                        details.encode(&mut self.buf)?;
184                    }
185                }
186            }
187        }
188        let len = self.buf.len() - 4;
189        BigEndian::write_u32(&mut self.buf[..], len as u32);
190
191        self.read_success().await?;
192        Ok(())
193    }
194
195    /// Add a smart card to the agent, with a (possibly empty) set of
196    /// constraints to apply when signing.
197    pub async fn add_smartcard_key(
198        &mut self,
199        id: &str,
200        pin: &[u8],
201        constraints: &[Constraint],
202    ) -> Result<(), Error> {
203        self.buf.clear();
204        self.buf.resize(4, 0);
205        if constraints.is_empty() {
206            self.buf.push(msg::ADD_SMARTCARD_KEY)
207        } else {
208            self.buf.push(msg::ADD_SMARTCARD_KEY_CONSTRAINED)
209        }
210        id.encode(&mut self.buf)?;
211        pin.encode(&mut self.buf)?;
212        if !constraints.is_empty() {
213            (constraints.len() as u32).encode(&mut self.buf)?;
214            for cons in constraints {
215                match *cons {
216                    Constraint::KeyLifetime { seconds } => {
217                        msg::CONSTRAIN_LIFETIME.encode(&mut self.buf)?;
218                        seconds.encode(&mut self.buf)?;
219                    }
220                    Constraint::Confirm => self.buf.push(msg::CONSTRAIN_CONFIRM),
221                    Constraint::Extensions {
222                        ref name,
223                        ref details,
224                    } => {
225                        msg::CONSTRAIN_EXTENSION.encode(&mut self.buf)?;
226                        name.encode(&mut self.buf)?;
227                        details.encode(&mut self.buf)?;
228                    }
229                }
230            }
231        }
232        let len = self.buf.len() - 4;
233        BigEndian::write_u32(&mut self.buf[..], len as u32);
234        self.read_response().await?;
235        Ok(())
236    }
237
238    /// Lock the agent, making it refuse to sign until unlocked.
239    pub async fn lock(&mut self, passphrase: &[u8]) -> Result<(), Error> {
240        self.buf.clear();
241        self.buf.resize(4, 0);
242        self.buf.push(msg::LOCK);
243        passphrase.encode(&mut self.buf)?;
244        let len = self.buf.len() - 4;
245        BigEndian::write_u32(&mut self.buf[..], len as u32);
246        self.read_response().await?;
247        Ok(())
248    }
249
250    /// Unlock the agent, allowing it to sign again.
251    pub async fn unlock(&mut self, passphrase: &[u8]) -> Result<(), Error> {
252        self.buf.clear();
253        self.buf.resize(4, 0);
254        msg::UNLOCK.encode(&mut self.buf)?;
255        passphrase.encode(&mut self.buf)?;
256        let len = self.buf.len() - 4;
257        #[allow(clippy::indexing_slicing)] // static length
258        BigEndian::write_u32(&mut self.buf[..], len as u32);
259        self.read_response().await?;
260        Ok(())
261    }
262
263    /// Ask the agent for a list of identities, including certificates.
264    pub async fn request_identities(&mut self) -> Result<Vec<AgentIdentity>, Error> {
265        self.buf.clear();
266        self.buf.resize(4, 0);
267        msg::REQUEST_IDENTITIES.encode(&mut self.buf)?;
268        let len = self.buf.len() - 4;
269        BigEndian::write_u32(&mut self.buf[..], len as u32);
270
271        self.read_response().await?;
272        debug!("identities: {:?}", &self.buf[..]);
273        let mut identities = Vec::new();
274
275        #[allow(clippy::indexing_slicing)] // static length
276        if let Some((&msg::IDENTITIES_ANSWER, mut r)) = self.buf.split_first() {
277            let n = u32::decode(&mut r)?;
278            for _ in 0..n {
279                let key_blob = Bytes::decode(&mut r)?;
280                let comment = String::decode(&mut r)?;
281
282                // Check if blob starts with a certificate algorithm by reading the algorithm string.
283                // Certificate algorithms end with "-cert-v01@openssh.com".
284                // This avoids parsing the blob twice for regular keys.
285                let identity = if Self::is_certificate_blob(&key_blob) {
286                    match Certificate::decode(&mut key_blob.as_ref()) {
287                        Ok(cert) => AgentIdentity::Certificate { certificate: cert, comment },
288                        Err(_) => {
289                            // Fallback to public key if certificate parsing fails
290                            let key = key::parse_public_key(&key_blob)?;
291                            AgentIdentity::PublicKey { key, comment }
292                        }
293                    }
294                } else {
295                    let key = key::parse_public_key(&key_blob)?;
296                    AgentIdentity::PublicKey { key, comment }
297                };
298                identities.push(identity);
299            }
300        }
301
302        Ok(identities)
303    }
304
305    /// Check if a key blob appears to be a certificate by examining the algorithm prefix.
306    /// Certificate algorithms end with "-cert-v01@openssh.com".
307    fn is_certificate_blob(blob: &[u8]) -> bool {
308        // The blob starts with a length-prefixed string containing the algorithm name.
309        // Read the length (4 bytes, big-endian) and then the algorithm string.
310        let Some(len_bytes) = blob.get(..4) else {
311            return false;
312        };
313        let alg_len = BigEndian::read_u32(len_bytes) as usize;
314        let Some(alg_bytes) = blob.get(4..4 + alg_len) else {
315            return false;
316        };
317        if let Ok(alg_str) = str::from_utf8(alg_bytes) {
318            alg_str.ends_with("-cert-v01@openssh.com")
319        } else {
320            false
321        }
322    }
323
324    /// Ask the agent to sign the supplied piece of data.
325    pub async fn sign_request(
326        &mut self,
327        identity: &AgentIdentity,
328        hash_alg: Option<HashAlg>,
329        data: Vec<u8>,
330    ) -> Result<Vec<u8>, Error> {
331        match identity {
332            AgentIdentity::PublicKey { key, .. } => self.sign_request_pk(key, hash_alg, data).await,
333            AgentIdentity::Certificate { certificate, .. } => {
334                self.sign_request_cert(certificate, hash_alg, data).await
335            }
336        }
337    }
338
339    async fn sign_request_pk(
340        &mut self,
341        public: &PublicKey,
342        hash_alg: Option<HashAlg>,
343        mut data: Vec<u8>,
344    ) -> Result<Vec<u8>, Error> {
345        debug!("sign_request: {data:?}");
346        let hash = self.prepare_sign_request(public, hash_alg, &data)?;
347
348        self.read_response().await?;
349
350        match self.buf.split_first() {
351            Some((&msg::SIGN_RESPONSE, mut r)) => {
352                self.write_signature(&mut r, hash, &mut data)?;
353                Ok(data)
354            }
355            Some((&msg::FAILURE, _)) => Err(Error::AgentFailure),
356            _ => {
357                debug!("self.buf = {:?}", &self.buf[..]);
358                Err(Error::AgentProtocolError)
359            }
360        }
361    }
362
363    /// Ask the agent to sign data using a certificate identity.
364    ///
365    /// This sends the certificate blob to the agent (not just the public key),
366    /// allowing the agent to match it to the correct private key.
367    ///
368    /// For RSA certificates, you can specify the hash algorithm to use.
369    async fn sign_request_cert(
370        &mut self,
371        cert: &Certificate,
372        hash_alg: Option<HashAlg>,
373        mut data: Vec<u8>,
374    ) -> Result<Vec<u8>, Error> {
375        debug!("sign_request_cert: {data:?}");
376
377        self.buf.clear();
378        self.buf.resize(4, 0);
379        msg::SIGN_REQUEST.encode(&mut self.buf)?;
380        cert.to_bytes()?.encode(&mut self.buf)?;
381        data.encode(&mut self.buf)?;
382
383        // Calculate hash flag for RSA certificates (same logic as prepare_sign_request)
384        let hash = match cert.algorithm() {
385            Algorithm::Rsa { .. } => match hash_alg {
386                Some(HashAlg::Sha256) => 2,
387                Some(HashAlg::Sha512) => 4,
388                _ => 0,
389            },
390            _ => 0,
391        };
392
393        hash.encode(&mut self.buf)?;
394
395        let len = self.buf.len() - 4;
396        BigEndian::write_u32(&mut self.buf[..], len as u32);
397
398        self.read_response().await?;
399
400        match self.buf.split_first() {
401            Some((&msg::SIGN_RESPONSE, mut r)) => {
402                self.write_signature(&mut r, hash, &mut data)?;
403                Ok(data)
404            }
405            Some((&msg::FAILURE, _)) => Err(Error::AgentFailure),
406            _ => {
407                debug!("self.buf = {:?}", &self.buf[..]);
408                Err(Error::AgentProtocolError)
409            }
410        }
411    }
412
413    fn prepare_sign_request(
414        &mut self,
415        public: &ssh_key::PublicKey,
416        hash_alg: Option<HashAlg>,
417        data: &[u8],
418    ) -> Result<u32, Error> {
419        self.buf.clear();
420        self.buf.resize(4, 0);
421        msg::SIGN_REQUEST.encode(&mut self.buf)?;
422        public.key_data().encoded()?.encode(&mut self.buf)?;
423        data.encode(&mut self.buf)?;
424        debug!("public = {public:?}");
425
426        let hash = match public.algorithm() {
427            Algorithm::Rsa { .. } => match hash_alg {
428                Some(HashAlg::Sha256) => 2,
429                Some(HashAlg::Sha512) => 4,
430                _ => 0,
431            },
432            _ => 0,
433        };
434
435        hash.encode(&mut self.buf)?;
436        let len = self.buf.len() - 4;
437        BigEndian::write_u32(&mut self.buf[..], len as u32);
438        Ok(hash)
439    }
440
441    fn write_signature<R: Reader>(
442        &self,
443        r: &mut R,
444        hash: u32,
445        data: &mut Vec<u8>,
446    ) -> Result<(), Error> {
447        let mut resp = &Bytes::decode(r)?[..];
448        let t = String::decode(&mut resp)?;
449        if (hash == 2 && t == "rsa-sha2-256") || (hash == 4 && t == "rsa-sha2-512") || hash == 0 {
450            let sig = Bytes::decode(&mut resp)?;
451            let is_sk_signature = t.starts_with("sk-");
452            (t.len() + sig.len() + 8 + if is_sk_signature { 5 } else { 0 }).encode(data)?;
453            t.encode(data)?;
454            sig.encode(data)?;
455            if is_sk_signature {
456                let flags = u8::decode(&mut resp)?;
457                let counter = u32::decode(&mut resp)?;
458                flags.encode(data)?;
459                counter.encode(data)?;
460            }
461            Ok(())
462        } else {
463            error!("unexpected agent signature type: {t:?}");
464            Err(Error::AgentProtocolError)
465        }
466    }
467
468    /// Ask the agent to sign the supplied piece of data.
469    pub fn sign_request_base64(
470        mut self,
471        public: &ssh_key::PublicKey,
472        hash_alg: Option<HashAlg>,
473        data: &[u8],
474    ) -> impl futures::Future<Output = (Self, Result<String, Error>)> {
475        debug!("sign_request: {data:?}");
476        let r = self.prepare_sign_request(public, hash_alg, data);
477        async move {
478            if let Err(e) = r {
479                return (self, Err(e));
480            }
481
482            let resp = self.read_response().await;
483            if let Err(e) = resp {
484                return (self, Err(e));
485            }
486
487            #[allow(clippy::indexing_slicing)] // length is checked
488            if !self.buf.is_empty() && self.buf[0] == msg::SIGN_RESPONSE {
489                let base64 = data_encoding::BASE64_NOPAD.encode(&self.buf[1..]);
490                (self, Ok(base64))
491            } else {
492                (self, Ok(String::new()))
493            }
494        }
495    }
496
497    /// Ask the agent to sign the supplied piece of data, and return a `Signature`.
498    pub async fn sign_request_signature(
499        &mut self,
500        public: &ssh_key::PublicKey,
501        hash_alg: Option<HashAlg>,
502        data: &[u8],
503    ) -> Result<Signature, Error> {
504        debug!("sign_request: {data:?}");
505
506        self.prepare_sign_request(public, hash_alg, data)?;
507        self.read_response().await?;
508
509        match self.buf.split_first() {
510            Some((&msg::SIGN_RESPONSE, mut r)) => {
511                let mut resp = &Bytes::decode(&mut r)?[..];
512                let sig = Signature::decode(&mut resp)?;
513                Ok(sig)
514            }
515            _ => Err(Error::AgentProtocolError),
516        }
517    }
518
519    /// Ask the agent to remove a key from its memory.
520    pub async fn remove_identity(&mut self, public: &ssh_key::PublicKey) -> Result<(), Error> {
521        self.buf.clear();
522        self.buf.resize(4, 0);
523        self.buf.push(msg::REMOVE_IDENTITY);
524        public.key_data().encoded()?.encode(&mut self.buf)?;
525        let len = self.buf.len() - 4;
526        BigEndian::write_u32(&mut self.buf[..], len as u32);
527        self.read_response().await?;
528        Ok(())
529    }
530
531    /// Ask the agent to remove a smartcard from its memory.
532    pub async fn remove_smartcard_key(&mut self, id: &str, pin: &[u8]) -> Result<(), Error> {
533        self.buf.clear();
534        self.buf.resize(4, 0);
535        msg::REMOVE_SMARTCARD_KEY.encode(&mut self.buf)?;
536        id.encode(&mut self.buf)?;
537        pin.encode(&mut self.buf)?;
538        let len = self.buf.len() - 4;
539        BigEndian::write_u32(&mut self.buf[..], len as u32);
540        self.read_response().await?;
541        Ok(())
542    }
543
544    /// Ask the agent to forget all known keys.
545    pub async fn remove_all_identities(&mut self) -> Result<(), Error> {
546        self.buf.clear();
547        self.buf.resize(4, 0);
548        msg::REMOVE_ALL_IDENTITIES.encode(&mut self.buf)?;
549        1u32.encode(&mut self.buf)?;
550        self.read_success().await?;
551        Ok(())
552    }
553
554    /// Send a custom message to the agent.
555    pub async fn extension(&mut self, typ: &[u8], ext: &[u8]) -> Result<(), Error> {
556        self.buf.clear();
557        self.buf.resize(4, 0);
558        msg::EXTENSION.encode(&mut self.buf)?;
559        typ.encode(&mut self.buf)?;
560        ext.encode(&mut self.buf)?;
561        let len = self.buf.len() - 4;
562        (len as u32).encode(&mut self.buf)?;
563        self.read_response().await?;
564        Ok(())
565    }
566
567    /// Ask the agent what extensions about supported extensions.
568    pub async fn query_extension(&mut self, typ: &[u8], mut ext: CryptoVec) -> Result<bool, Error> {
569        self.buf.clear();
570        self.buf.resize(4, 0);
571        msg::EXTENSION.encode(&mut self.buf)?;
572        typ.encode(&mut self.buf)?;
573        let len = self.buf.len() - 4;
574        (len as u32).encode(&mut self.buf)?;
575        self.read_response().await?;
576
577        match self.buf.split_first() {
578            Some((&msg::SUCCESS, mut r)) => {
579                ext.extend(&Bytes::decode(&mut r)?);
580                Ok(true)
581            }
582            _ => Ok(false),
583        }
584    }
585}
586
587#[cfg(test)]
588mod tests {
589    use byteorder::{BigEndian, ByteOrder};
590    use tokio::io::{AsyncReadExt, AsyncWriteExt};
591
592    use super::{AgentClient, MAX_AGENT_FRAME_LEN};
593    use crate::keys::Error;
594
595    #[test]
596    fn oversized_agent_response_is_rejected_before_allocation() -> std::io::Result<()> {
597        let runtime = tokio::runtime::Builder::new_current_thread()
598            .enable_all()
599            .build()?;
600
601        runtime.block_on(async {
602            let (mut writer, reader) = tokio::io::duplex(64);
603            let server = tokio::spawn(async move {
604                let mut frame = [0u8; 4];
605                writer.read_exact(&mut frame).await?;
606                let len = BigEndian::read_u32(&frame) as usize;
607                let mut body = vec![0; len];
608                writer.read_exact(&mut body).await?;
609
610                BigEndian::write_u32(&mut frame, (MAX_AGENT_FRAME_LEN + 1) as u32);
611                writer.write_all(&frame).await?;
612                Ok::<(), std::io::Error>(())
613            });
614
615            let mut client = AgentClient::connect(reader);
616            let err = client.request_identities().await.unwrap_err();
617            assert!(matches!(err, Error::AgentProtocolError));
618            server.await.expect("server task")?;
619            Ok::<(), std::io::Error>(())
620        })?;
621
622        Ok(())
623    }
624}