ssh/config/
algorithm.rs

1use std::{
2    fmt::{Debug, Display},
3    ops::{Deref, DerefMut},
4    str::FromStr,
5};
6use tracing::*;
7
8use crate::{
9    algorithm::{Compress, Enc, Kex, Mac, PubKey},
10    client::Client,
11    constant::ssh_transport_code,
12    error::{SshError, SshResult},
13    model::{Data, Packet, SecPacket},
14    util,
15};
16
17macro_rules! create_wrapped_type {
18    ($name: ident, $value_type: ty) => {
19        #[derive(Clone, Default)]
20        pub(crate) struct $name(Vec<$value_type>);
21        impl Deref for $name {
22            type Target = Vec<$value_type>;
23            fn deref(&self) -> &Self::Target {
24                &self.0
25            }
26        }
27
28        impl DerefMut for $name {
29            fn deref_mut(&mut self) -> &mut Self::Target {
30                &mut self.0
31            }
32        }
33
34        impl Display for $name {
35            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36                write!(
37                    f,
38                    "{}",
39                    self.iter()
40                        .map(|&x| x.as_ref().to_owned())
41                        .collect::<Vec<String>>()
42                        .join(",")
43                )
44            }
45        }
46
47        impl TryFrom<Vec<String>> for $name {
48            type Error = SshError;
49            fn try_from(v: Vec<String>) -> Result<Self, Self::Error> {
50                let v = v
51                    .iter()
52                    .filter_map(|x| <$value_type>::from_str(x.as_str()).ok())
53                    .collect::<Vec<$value_type>>();
54                Ok(Self(v))
55            }
56        }
57
58        impl From<Vec<$value_type>> for $name {
59            fn from(v: Vec<$value_type>) -> Self {
60                Self(v)
61            }
62        }
63    };
64}
65
66create_wrapped_type!(Kexs, Kex);
67create_wrapped_type!(PubKeys, PubKey);
68create_wrapped_type!(Encs, Enc);
69create_wrapped_type!(Macs, Mac);
70create_wrapped_type!(Compresses, Compress);
71
72#[derive(Clone, Default)]
73pub(crate) struct AlgList {
74    pub key_exchange: Kexs,
75    pub public_key: PubKeys,
76    pub c_encryption: Encs,
77    pub s_encryption: Encs,
78    pub c_mac: Macs,
79    pub s_mac: Macs,
80    pub c_compress: Compresses,
81    pub s_compress: Compresses,
82}
83
84impl Debug for AlgList {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        write!(f, "kex: \"{}\", ", self.key_exchange)?;
87        write!(f, "pubkey: \"{}\", ", self.public_key)?;
88        write!(f, "c_enc: \"{}\", ", self.c_encryption)?;
89        write!(f, "s_enc: \"{}\", ", self.s_encryption)?;
90        write!(f, "c_mac: \"{}\", ", self.c_mac)?;
91        write!(f, "s_mac: \"{}\", ", self.s_mac)?;
92        write!(f, "c_compress: \"{}\", ", self.c_compress)?;
93        write!(f, "s_compress: \"{}\"", self.s_compress)
94    }
95}
96
97impl AlgList {
98    pub fn new() -> Self {
99        AlgList {
100            ..Default::default()
101        }
102    }
103
104    pub fn client_default() -> Self {
105        AlgList {
106            key_exchange: vec![
107                Kex::Curve25519Sha256,
108                Kex::EcdhSha2Nistrp256,
109                Kex::DiffieHellmanGroup14Sha256,
110                Kex::DiffieHellmanGroup14Sha1,
111            ]
112            .into(),
113            public_key: vec![PubKey::RsaSha2_512, PubKey::RsaSha2_256].into(),
114            c_encryption: vec![
115                Enc::Chacha20Poly1305Openssh,
116                Enc::Aes128Ctr,
117                Enc::Aes192Ctr,
118                Enc::Aes256Ctr,
119            ]
120            .into(),
121            s_encryption: vec![
122                Enc::Chacha20Poly1305Openssh,
123                Enc::Aes128Ctr,
124                Enc::Aes192Ctr,
125                Enc::Aes256Ctr,
126            ]
127            .into(),
128            c_mac: vec![Mac::HmacSha2_256, Mac::HmacSha2_512, Mac::HmacSha1].into(),
129            s_mac: vec![Mac::HmacSha2_256, Mac::HmacSha2_512, Mac::HmacSha1].into(),
130            c_compress: vec![Compress::None, Compress::ZlibOpenSsh].into(),
131            s_compress: vec![Compress::None, Compress::ZlibOpenSsh].into(),
132        }
133    }
134
135    fn from(mut data: Data) -> SshResult<Self> {
136        data.get_u8();
137        // skip the 16-bit cookie
138        data.skip(16);
139        let mut server_algorithm = Self::new();
140
141        macro_rules! try_convert {
142            ($hint: literal, $field: ident) => {
143                let alg_string = util::vec_u8_to_string(data.get_u8s(), ",")?;
144                info!("server {}: {:?}", $hint, alg_string);
145                server_algorithm.$field = alg_string.try_into()?;
146            };
147        }
148        try_convert!("key exchange", key_exchange);
149        try_convert!("public key", public_key);
150        try_convert!("c2s encryption", c_encryption);
151        try_convert!("s2c encryption", s_encryption);
152        try_convert!("c2s mac", c_mac);
153        try_convert!("s2c mac", s_mac);
154        try_convert!("c2s compression", c_compress);
155        try_convert!("s2c compression", s_compress);
156        debug!("converted server algorithms: [{:?}]", server_algorithm);
157        Ok(server_algorithm)
158    }
159
160    pub fn match_with(&self, other: &Self) -> SshResult<Self> {
161        macro_rules! match_field {
162            ($our: expr,  $their:expr, $field: ident, $err_hint: literal) => {
163                $our.$field
164                    .iter()
165                    .find_map(|k| {
166                        if $their.$field.contains(k) {
167                            Some(k)
168                        } else {
169                            None
170                        }
171                    })
172                    .ok_or_else(|| {
173                        let err_msg = format!(
174                            "Key_agreement: the {} fails to match, \
175                        algorithms supported by the server: {},\
176                        algorithms supported by the client: {}",
177                            $err_hint, $their.$field, $our.$field
178                        );
179                        error!(err_msg);
180                        SshError::KexError(err_msg)
181                    })
182            };
183        }
184
185        // kex
186        let kex = match_field!(self, other, key_exchange, "DH algorithm")?;
187        // pubkey
188        let pubkey = match_field!(self, other, public_key, "signature algorithm")?;
189        // encryption
190        let c_enc = match_field!(self, other, c_encryption, "client encryption algorithm")?;
191        let s_enc = match_field!(self, other, s_encryption, "server encryption algorithm")?;
192
193        // mac
194        let c_mac = match_field!(self, other, c_mac, "client mac algorithm")?;
195        let s_mac = match_field!(self, other, s_mac, "server mac algorithm")?;
196
197        // compress
198        let c_compress = match_field!(self, other, c_compress, "client compression algorithm")?;
199        let s_compress = match_field!(self, other, s_compress, "server compression algorithm")?;
200
201        let negotiated = Self {
202            key_exchange: vec![*kex].into(),
203            public_key: vec![*pubkey].into(),
204            c_encryption: vec![*c_enc].into(),
205            s_encryption: vec![*s_enc].into(),
206            c_mac: vec![*c_mac].into(),
207            s_mac: vec![*s_mac].into(),
208            c_compress: vec![*c_compress].into(),
209            s_compress: vec![*s_compress].into(),
210        };
211
212        info!("matched algorithms [{:?}]", negotiated);
213
214        Ok(negotiated)
215    }
216
217    fn as_i(&self) -> Vec<u8> {
218        let mut data = Data::new();
219        data.put_str(&self.key_exchange.to_string());
220        data.put_str(&self.public_key.to_string());
221        data.put_str(&self.c_encryption.to_string());
222        data.put_str(&self.s_encryption.to_string());
223        data.put_str(&self.c_mac.to_string());
224        data.put_str(&self.s_mac.to_string());
225        data.put_str(&self.c_compress.to_string());
226        data.put_str(&self.s_compress.to_string());
227        data.to_vec()
228    }
229}
230
231impl<'a> Packet<'a> for AlgList {
232    fn pack(self, client: &'a mut Client) -> crate::model::SecPacket<'a> {
233        info!("client algorithms: [{:?}]", self);
234        let mut data = Data::new();
235        data.put_u8(ssh_transport_code::KEXINIT);
236        data.extend(util::cookie());
237        data.extend(self.as_i());
238        data.put_str("")
239            .put_str("")
240            .put_u8(false as u8)
241            .put_u32(0_u32);
242
243        (data, client).into()
244    }
245
246    fn unpack(pkt: SecPacket) -> SshResult<Self>
247    where
248        Self: Sized,
249    {
250        let data = pkt.into_inner();
251        assert_eq!(data[0], ssh_transport_code::KEXINIT);
252        AlgList::from(data)
253    }
254}