ssh_agent_lib/proto/message/
extension.rs

1//! Container for SSH agent protocol extension messages
2
3use ssh_encoding::{self, CheckedSum, Decode, Encode, Reader, Writer};
4
5use crate::proto::{
6    extension::{KeyConstraintExtension, MessageExtension},
7    Error, Result, Unparsed,
8};
9
10/// Container for SSH agent protocol extension messages
11///
12/// This structure is sent as part of a [`Request::Extension`](super::Request::Extension) (`SSH_AGENT_EXTENSION_RESPONSE`) message.
13///
14/// Described in [draft-miller-ssh-agent-14 § 3.8](https://www.ietf.org/archive/id/draft-miller-ssh-agent-14.html#section-3.8).
15#[derive(Clone, PartialEq, Debug)]
16pub struct Extension {
17    /// Indicates the type of the extension message (as a UTF-8 string)
18    ///
19    /// Extension names should be suffixed by the implementation domain
20    /// as per [RFC4251 § 4.2](https://www.rfc-editor.org/rfc/rfc4251.html#section-4.2),
21    /// e.g. "foo@example.com"
22    pub name: String,
23
24    /// Extension-specific content
25    pub details: Unparsed,
26}
27
28impl Extension {
29    /// Create a new [`Extension`] from a [`MessageExtension`]
30    /// structure implementing [`ssh_encoding::Encode`]
31    pub fn new_message<T>(extension: T) -> Result<Self>
32    where
33        T: MessageExtension + Encode,
34    {
35        let mut buffer: Vec<u8> = vec![];
36        extension.encode(&mut buffer)?;
37        Ok(Self {
38            name: T::NAME.into(),
39            details: buffer.into(),
40        })
41    }
42
43    /// Attempt to parse a an extension object into a
44    /// [`MessageExtension`] structure
45    /// implementing [`ssh_encoding::Decode`].
46    ///
47    /// If there is a mismatch between the extension name
48    /// and the [`MessageExtension::NAME`], this method
49    /// will return [`None`]
50    pub fn parse_message<T>(&self) -> std::result::Result<Option<T>, <T as Decode>::Error>
51    where
52        T: MessageExtension + Decode,
53    {
54        if T::NAME == self.name {
55            Ok(Some(self.details.parse::<T>()?))
56        } else {
57            Ok(None)
58        }
59    }
60
61    /// Create a new [`Extension`] from a [`KeyConstraintExtension`]
62    /// structure implementing [`ssh_encoding::Encode`]
63    pub fn new_key_constraint<T>(extension: T) -> Result<Self>
64    where
65        T: KeyConstraintExtension + Encode,
66    {
67        let mut buffer: Vec<u8> = vec![];
68        extension.encode(&mut buffer)?;
69        Ok(Self {
70            name: T::NAME.into(),
71            details: buffer.into(),
72        })
73    }
74
75    /// Attempt to parse a an extension object into a
76    /// [`KeyConstraintExtension`] structure
77    /// implementing [`ssh_encoding::Decode`].
78    ///
79    /// If there is a mismatch between the extension name
80    /// and the [`KeyConstraintExtension::NAME`], this method
81    /// will return [`None`]
82    pub fn parse_key_constraint<T>(&self) -> std::result::Result<Option<T>, <T as Decode>::Error>
83    where
84        T: KeyConstraintExtension + Decode,
85    {
86        if T::NAME == self.name {
87            Ok(Some(self.details.parse::<T>()?))
88        } else {
89            Ok(None)
90        }
91    }
92}
93
94impl Decode for Extension {
95    type Error = Error;
96
97    fn decode(reader: &mut impl Reader) -> Result<Self> {
98        let name = String::decode(reader)?;
99        let mut details = vec![0; reader.remaining_len()];
100        reader.read(&mut details)?;
101        Ok(Self {
102            name,
103            details: details.into(),
104        })
105    }
106}
107
108impl Encode for Extension {
109    fn encoded_len(&self) -> ssh_encoding::Result<usize> {
110        [self.name.encoded_len()?, self.details.encoded_len()?].checked_sum()
111    }
112
113    fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
114        self.name.encode(writer)?;
115        self.details.encode(writer)?;
116        Ok(())
117    }
118}