ssh_agent_lib/proto/extension/
message.rs

1//! SSH agent protocol extension messages
2//!
3//! Includes extension message definitions from both:
4//! - [draft-miller-ssh-agent-14](https://www.ietf.org/archive/id/draft-miller-ssh-agent-14.html)
5//! - [OpenSSH `PROTOCOL.agent`](https://github.com/openssh/openssh-portable/blob/cbbdf868bce431a59e2fa36ca244d5739429408d/PROTOCOL.agent)
6
7use signature::Verifier;
8use ssh_encoding::{CheckedSum, Decode, Encode, Error as EncodingError, Reader, Writer};
9use ssh_key::{public::KeyData, Signature};
10
11use super::MessageExtension;
12use crate::proto::ProtoError;
13
14/// `query` message extension.
15///
16/// An optional extension request "query" is defined to allow a
17/// client to query which, if any, extensions are supported by an agent.
18///
19/// Described in [draft-miller-ssh-agent-14 § 3.8.1](https://www.ietf.org/archive/id/draft-miller-ssh-agent-14.html#section-3.8.1)
20#[derive(Debug, Clone, PartialEq)]
21pub struct QueryResponse {
22    /// List of supported message extension names
23    pub extensions: Vec<String>,
24}
25
26impl Encode for QueryResponse {
27    fn encoded_len(&self) -> Result<usize, EncodingError> {
28        self.extensions.encoded_len()
29    }
30
31    fn encode(&self, writer: &mut impl Writer) -> Result<(), EncodingError> {
32        self.extensions.encode(writer)
33    }
34}
35
36impl Decode for QueryResponse {
37    type Error = ProtoError;
38
39    fn decode(reader: &mut impl Reader) -> Result<Self, Self::Error> {
40        let extensions = Vec::<String>::decode(reader)?;
41
42        Ok(Self { extensions })
43    }
44}
45
46impl MessageExtension for QueryResponse {
47    const NAME: &'static str = "query";
48}
49
50/// `session-bind@openssh.com` message extension.
51///
52/// This message extension allows an SSH client to bind an
53/// agent connection to a particular SSH session.
54///
55/// *Note*: This is an OpenSSH-specific extension to the agent protocol.
56///
57/// Described in [OpenSSH PROTOCOL.agent § 1](https://github.com/openssh/openssh-portable/blob/cbbdf868bce431a59e2fa36ca244d5739429408d/PROTOCOL.agent#L6)
58#[derive(Debug, Clone, PartialEq)]
59pub struct SessionBind {
60    /// Server host public key.
61    pub host_key: KeyData,
62
63    /// Hash derived from the initial key exchange.
64    pub session_id: Vec<u8>,
65
66    /// Server's signature of the session identifier using the private hostkey.
67    pub signature: Signature,
68
69    /// Flag indicating whether this connection should be bound for user authentication or forwarding.
70    pub is_forwarding: bool,
71}
72
73impl Decode for SessionBind {
74    type Error = crate::proto::error::ProtoError;
75
76    fn decode(reader: &mut impl Reader) -> Result<Self, Self::Error> {
77        let host_key = reader.read_prefixed(KeyData::decode)?;
78        let session_id = Vec::decode(reader)?;
79        let signature = reader.read_prefixed(Signature::decode)?;
80        Ok(Self {
81            host_key,
82            session_id,
83            signature,
84            is_forwarding: u8::decode(reader)? != 0,
85        })
86    }
87}
88
89impl Encode for SessionBind {
90    fn encoded_len(&self) -> ssh_encoding::Result<usize> {
91        [
92            self.host_key.encoded_len_prefixed()?,
93            self.session_id.encoded_len()?,
94            self.signature.encoded_len_prefixed()?,
95            1u8.encoded_len()?,
96        ]
97        .checked_sum()
98    }
99
100    fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
101        self.host_key.encode_prefixed(writer)?;
102        self.session_id.encode(writer)?;
103        self.signature.encode_prefixed(writer)?;
104
105        if self.is_forwarding {
106            1u8.encode(writer)
107        } else {
108            0u8.encode(writer)
109        }
110    }
111}
112
113impl SessionBind {
114    /// Verify the server's signature of the session identifier
115    /// using the public `host_key`.
116    ///
117    /// > When an agent receives \[a `session-bind@openssh.com` message\],
118    /// > it will verify the signature.
119    ///
120    /// Described in [OpenSSH PROTOCOL.agent § 1](https://github.com/openssh/openssh-portable/blob/cbbdf868bce431a59e2fa36ca244d5739429408d/PROTOCOL.agent#L31)
121    pub fn verify_signature(&self) -> Result<(), ProtoError> {
122        self.host_key
123            .verify(self.session_id.as_slice(), &self.signature)?;
124        Ok(())
125    }
126}
127
128impl MessageExtension for SessionBind {
129    const NAME: &'static str = "session-bind@openssh.com";
130}
131
132#[cfg(test)]
133mod tests {
134    use testresult::TestResult;
135
136    use super::*;
137
138    fn round_trip<T>(msg: T) -> TestResult
139    where
140        T: Encode + Decode<Error = ProtoError> + std::fmt::Debug + std::cmp::PartialEq,
141    {
142        let mut buf: Vec<u8> = vec![];
143        msg.encode(&mut buf)?;
144        let mut re_encoded = &buf[..];
145
146        let msg2 = T::decode(&mut re_encoded)?;
147        assert_eq!(msg, msg2);
148
149        Ok(())
150    }
151
152    #[test]
153    fn parse_bind() -> TestResult {
154        let mut buffer: &[u8] = &[
155            0, 0, 0, 51, 0, 0, 0, 11, 115, 115, 104, 45, 101, 100, 50, 53, 53, 49, 57, 0, 0, 0, 32,
156            177, 185, 198, 92, 165, 45, 127, 95, 202, 195, 226, 63, 6, 115, 10, 104, 18, 137, 172,
157            240, 153, 154, 174, 74, 83, 7, 1, 204, 14, 177, 153, 40, 0, 0, 0, 32, 138, 165, 196,
158            144, 149, 107, 183, 188, 222, 182, 34, 173, 59, 118, 9, 35, 186, 147, 114, 114, 50,
159            106, 41, 182, 196, 119, 226, 82, 233, 148, 236, 135, 0, 0, 0, 83, 0, 0, 0, 11, 115,
160            115, 104, 45, 101, 100, 50, 53, 53, 49, 57, 0, 0, 0, 64, 95, 212, 52, 189, 8, 162, 17,
161            3, 15, 218, 2, 4, 136, 7, 47, 57, 121, 6, 194, 165, 221, 27, 175, 241, 6, 57, 84, 141,
162            77, 55, 235, 9, 77, 160, 32, 76, 11, 227, 240, 235, 122, 178, 80, 133, 183, 91, 89, 89,
163            142, 115, 145, 15, 78, 112, 139, 28, 201, 8, 197, 222, 117, 141, 88, 5, 0,
164        ];
165        let bind = SessionBind::decode(&mut buffer)?;
166        eprintln!("Bind: {bind:#?}");
167
168        // Check `signature` (of `session_id`) against
169        // server public-key `host_key`
170        bind.verify_signature()?;
171
172        round_trip(bind)?;
173
174        Ok(())
175    }
176}