1use nom::bytes::streaming::{is_not, tag, take, take_until};
7use nom::character::streaming::{crlf, line_ending, not_line_ending};
8use nom::combinator::{complete, map, map_parser, map_res, opt};
9use nom::error::{make_error, Error, ErrorKind};
10use nom::multi::{length_data, many_till, separated_list1};
11use nom::number::streaming::{be_u32, be_u8};
12use nom::sequence::{delimited, pair, terminated};
13use nom::{Err, IResult};
14use rusticata_macros::newtype_enum;
15use std::str;
16
17#[derive(Debug, PartialEq)]
25pub struct SshVersion<'a> {
26 pub proto: &'a [u8],
27 pub software: &'a [u8],
28 pub comments: Option<&'a [u8]>,
29}
30
31fn parse_version(i: &[u8]) -> IResult<&[u8], SshVersion> {
34 let (i, proto) = take_until("-")(i)?;
35 let (i, _) = tag("-")(i)?;
36 let (i, software) = is_not(" \r\n")(i)?;
37 let (i, comments) = opt(|d| {
38 let (d, _) = tag(" ")(d)?;
39 let (d, comments) = not_line_ending(d)?;
40 Ok((d, comments))
41 })(i)?;
42 let version = SshVersion {
43 proto,
44 software,
45 comments,
46 };
47 Ok((i, version))
48}
49
50pub fn parse_ssh_identification(i: &[u8]) -> IResult<&[u8], (Vec<&[u8]>, SshVersion)> {
57 many_till(
58 terminated(take_until("\r\n"), crlf),
59 delimited(tag("SSH-"), parse_version, line_ending),
60 )(i)
61}
62
63#[inline]
64fn parse_string(i: &[u8]) -> IResult<&[u8], &[u8]> {
65 length_data(be_u32)(i)
66}
67
68#[inline]
70fn is_us_ascii(c: u8) -> bool {
71 (0x20..=0x7e).contains(&c) && c != 0x2c
72}
73
74#[inline]
75fn parse_name(s: &[u8]) -> IResult<&[u8], &[u8]> {
76 use nom::bytes::complete::take_while1;
77 take_while1(is_us_ascii)(s)
78}
79
80fn parse_name_list<'a>(i: &'a [u8]) -> IResult<&'a [u8], Vec<&str>> {
81 use nom::bytes::complete::tag;
82 match separated_list1(tag(","), map_res(complete(parse_name), str::from_utf8))(i) {
83 Ok((rem, res)) => Ok((rem, res)),
84 Err(_) => Err(Err::Error(make_error(i, ErrorKind::SeparatedList))),
85 }
86}
87
88#[derive(Debug, PartialEq)]
99pub struct SshPacketKeyExchange<'a> {
100 pub cookie: &'a [u8],
101 pub kex_algs: &'a [u8],
102 pub server_host_key_algs: &'a [u8],
103 pub encr_algs_client_to_server: &'a [u8],
104 pub encr_algs_server_to_client: &'a [u8],
105 pub mac_algs_client_to_server: &'a [u8],
106 pub mac_algs_server_to_client: &'a [u8],
107 pub comp_algs_client_to_server: &'a [u8],
108 pub comp_algs_server_to_client: &'a [u8],
109 pub langs_client_to_server: &'a [u8],
110 pub langs_server_to_client: &'a [u8],
111 pub first_kex_packet_follows: bool,
112}
113
114fn parse_packet_key_exchange(i: &[u8]) -> IResult<&[u8], SshPacket> {
115 let (i, cookie) = take(16usize)(i)?;
116 let (i, kex_algs) = parse_string(i)?;
117 let (i, server_host_key_algs) = parse_string(i)?;
118 let (i, encr_algs_client_to_server) = parse_string(i)?;
119 let (i, encr_algs_server_to_client) = parse_string(i)?;
120 let (i, mac_algs_client_to_server) = parse_string(i)?;
121 let (i, mac_algs_server_to_client) = parse_string(i)?;
122 let (i, comp_algs_client_to_server) = parse_string(i)?;
123 let (i, comp_algs_server_to_client) = parse_string(i)?;
124 let (i, langs_client_to_server) = parse_string(i)?;
125 let (i, langs_server_to_client) = parse_string(i)?;
126 let (i, first_kex_packet_follows) = be_u8(i)?;
127 let (i, _) = be_u32(i)?;
128 let packet = SshPacketKeyExchange {
129 cookie,
130 kex_algs,
131 server_host_key_algs,
132 encr_algs_client_to_server,
133 encr_algs_server_to_client,
134 mac_algs_client_to_server,
135 mac_algs_server_to_client,
136 comp_algs_client_to_server,
137 comp_algs_server_to_client,
138 langs_client_to_server,
139 langs_server_to_client,
140 first_kex_packet_follows: first_kex_packet_follows > 0,
141 };
142 Ok((i, SshPacket::KeyExchange(packet)))
143}
144
145impl<'a> SshPacketKeyExchange<'a> {
146 pub fn get_kex_algs(&self) -> Result<Vec<&str>, nom::Err<Error<&[u8]>>> {
147 parse_name_list(self.kex_algs).map(|x| x.1)
148 }
149
150 pub fn get_server_host_key_algs(&self) -> Result<Vec<&str>, nom::Err<Error<&[u8]>>> {
151 parse_name_list(self.server_host_key_algs).map(|x| x.1)
152 }
153
154 pub fn get_encr_algs_client_to_server(&self) -> Result<Vec<&str>, nom::Err<Error<&[u8]>>> {
155 parse_name_list(self.encr_algs_client_to_server).map(|x| x.1)
156 }
157
158 pub fn get_encr_algs_server_to_client(&self) -> Result<Vec<&str>, nom::Err<Error<&'a [u8]>>> {
159 parse_name_list(self.encr_algs_server_to_client).map(|x| x.1)
160 }
161
162 pub fn get_mac_algs_client_to_server(&self) -> Result<Vec<&str>, nom::Err<Error<&'a [u8]>>> {
163 parse_name_list(self.mac_algs_client_to_server).map(|x| x.1)
164 }
165
166 pub fn get_mac_algs_server_to_client(&self) -> Result<Vec<&str>, nom::Err<Error<&'a [u8]>>> {
167 parse_name_list(self.mac_algs_server_to_client).map(|x| x.1)
168 }
169
170 pub fn get_comp_algs_client_to_server(&self) -> Result<Vec<&str>, nom::Err<Error<&'a [u8]>>> {
171 parse_name_list(self.comp_algs_client_to_server).map(|x| x.1)
172 }
173
174 pub fn get_comp_algs_server_to_client(&self) -> Result<Vec<&str>, nom::Err<Error<&'a [u8]>>> {
175 parse_name_list(self.comp_algs_server_to_client).map(|x| x.1)
176 }
177
178 pub fn get_langs_client_to_server(&self) -> Result<Vec<&str>, nom::Err<Error<&'a [u8]>>> {
179 parse_name_list(self.langs_client_to_server).map(|x| x.1)
180 }
181
182 pub fn get_langs_server_to_client(&self) -> Result<Vec<&str>, nom::Err<Error<&'a [u8]>>> {
183 parse_name_list(self.langs_server_to_client).map(|x| x.1)
184 }
185}
186
187#[derive(Debug, PartialEq)]
201pub struct SshPacketDhInit<'a> {
202 pub e: &'a [u8],
203}
204
205fn parse_packet_dh_init(i: &[u8]) -> IResult<&[u8], SshPacket> {
206 map(parse_string, |e| {
207 SshPacket::DiffieHellmanInit(SshPacketDhInit { e })
208 })(i)
209}
210
211#[derive(Debug, PartialEq)]
218pub struct SshPacketDhReply<'a> {
219 pub pubkey_and_cert: &'a [u8],
220 pub f: &'a [u8],
221 pub signature: &'a [u8],
222}
223
224fn parse_packet_dh_reply(i: &[u8]) -> IResult<&[u8], SshPacket> {
225 let (i, pubkey_and_cert) = parse_string(i)?;
226 let (i, f) = parse_string(i)?;
227 let (i, signature) = parse_string(i)?;
228 let reply = SshPacketDhReply {
229 pubkey_and_cert,
230 f,
231 signature,
232 };
233 Ok((i, SshPacket::DiffieHellmanReply(reply)))
234}
235
236impl<'a> SshPacketDhReply<'a> {
237 #[allow(clippy::type_complexity)]
241 pub fn get_ecdsa_signature(&self) -> Result<(&str, Vec<u8>), nom::Err<Error<&[u8]>>> {
242 let (i, identifier) = map_res(parse_string, str::from_utf8)(self.signature)?;
243 let (_, blob) = map_parser(parse_string, pair(parse_string, parse_string))(i)?;
244
245 let mut rs = Vec::new();
246
247 rs.extend_from_slice(blob.0);
248 rs.extend_from_slice(blob.1);
249
250 Ok((identifier, rs))
251 }
252}
253
254#[derive(Debug, PartialEq)]
258pub struct SshPacketDisconnect<'a> {
259 pub reason_code: u32,
260 pub description: &'a [u8],
261 pub lang: &'a [u8],
262}
263
264#[derive(Clone, Copy, Debug, PartialEq, Eq)]
268pub struct SshDisconnectReason(pub u32);
269
270newtype_enum! {
271impl display SshDisconnectReason {
272 HostNotAllowedToConnect = 1,
273 ProtocolError = 2,
274 KeyExchangeFailed = 3,
275 Reserved = 4,
276 MacError = 5,
277 CompressionError = 6,
278 ServiceNotAvailable = 7,
279 ProtocolVersionNotSupported = 8,
280 HostKeyNotVerifiable = 9,
281 ConnectionLost = 10,
282 ByApplication = 11,
283 TooManyConnections = 12,
284 AuthCancelledByUser = 13,
285 NoMoreAuthMethodsAvailable = 14,
286 IllegalUserName = 15,
287}
288}
289
290fn parse_packet_disconnect(i: &[u8]) -> IResult<&[u8], SshPacket> {
291 let (i, reason_code) = be_u32(i)?;
292 let (i, description) = parse_string(i)?;
293 let (i, lang) = parse_string(i)?;
294 let packet = SshPacketDisconnect {
295 reason_code,
296 description,
297 lang,
298 };
299 Ok((i, SshPacket::Disconnect(packet)))
300}
301
302impl<'a> SshPacketDisconnect<'a> {
303 pub fn get_description(&self) -> Result<&str, str::Utf8Error> {
305 str::from_utf8(self.description)
306 }
307
308 pub fn get_reason(&self) -> SshDisconnectReason {
310 SshDisconnectReason(self.reason_code)
311 }
312}
313
314#[derive(Debug, PartialEq)]
318pub struct SshPacketDebug<'a> {
319 pub always_display: bool,
320 pub message: &'a [u8],
321 pub lang: &'a [u8],
322}
323
324fn parse_packet_debug(i: &[u8]) -> IResult<&[u8], SshPacket> {
325 let (i, display) = be_u8(i)?;
326 let (i, message) = parse_string(i)?;
327 let (i, lang) = parse_string(i)?;
328 let packet = SshPacketDebug {
329 always_display: display > 0,
330 message,
331 lang,
332 };
333 Ok((i, SshPacket::Debug(packet)))
334}
335
336impl<'a> SshPacketDebug<'a> {
337 pub fn get_message(&self) -> Result<&str, str::Utf8Error> {
339 str::from_utf8(self.message)
340 }
341}
342
343#[derive(Debug, PartialEq)]
345pub enum SshPacket<'a> {
346 Disconnect(SshPacketDisconnect<'a>),
347 Ignore(&'a [u8]),
348 Unimplemented(u32),
349 Debug(SshPacketDebug<'a>),
350 ServiceRequest(&'a [u8]),
351 ServiceAccept(&'a [u8]),
352 KeyExchange(SshPacketKeyExchange<'a>),
353 NewKeys,
354 DiffieHellmanInit(SshPacketDhInit<'a>),
355 DiffieHellmanReply(SshPacketDhReply<'a>),
356}
357
358pub fn parse_ssh_packet(i: &[u8]) -> IResult<&[u8], (SshPacket, &[u8])> {
363 let (i, packet_length) = be_u32(i)?;
364 let (i, padding_length) = be_u8(i)?;
365 if padding_length as u32 + 1 > packet_length {
366 return Err(Err::Error(make_error(i, ErrorKind::LengthValue)));
367 }
368 let (i, payload) = map_parser(take(packet_length - padding_length as u32 - 1), |d| {
369 let (d, msg_type) = be_u8(d)?;
370 match msg_type {
371 1 => parse_packet_disconnect(d),
372 2 => map(parse_string, SshPacket::Ignore)(d),
373 3 => map(be_u32, SshPacket::Unimplemented)(d),
374 4 => parse_packet_debug(d),
375 5 => map(parse_string, SshPacket::ServiceRequest)(d),
376 6 => map(parse_string, SshPacket::ServiceAccept)(d),
377 20 => parse_packet_key_exchange(d),
378 21 => Ok((d, SshPacket::NewKeys)),
379 30 => parse_packet_dh_init(d),
380 31 => parse_packet_dh_reply(d),
381 _ => Err(Err::Error(make_error(d, ErrorKind::Switch))),
382 }
383 })(i)?;
384 let (i, padding) = take(padding_length)(i)?;
385 Ok((i, (payload, padding)))
386}
387
388#[cfg(test)]
389mod tests {
390
391 use super::*;
392 use nom::Err;
393
394 #[test]
395 fn test_name() {
396 let res = parse_name(b"ssh-rsa");
397 let expected = Ok((&b""[..], &b"ssh-rsa"[..]));
398 assert_eq!(res, expected);
399 }
400
401 #[test]
402 fn test_empty_name_list() {
403 let res = parse_name_list(b"");
404 let expected = Err(Err::Error(make_error(&b""[..], ErrorKind::SeparatedList)));
405 assert_eq!(res, expected);
406 }
407
408 #[test]
409 fn test_one_name_list() {
410 let res = parse_name_list(b"ssh-rsa");
411 let expected = Ok((&b""[..], vec!["ssh-rsa"]));
412 assert_eq!(res, expected);
413 }
414
415 #[test]
416 fn test_two_names_list() {
417 let res = parse_name_list(b"ssh-rsa,ssh-ecdsa");
418 let expected = Ok((&b""[..], vec!["ssh-rsa", "ssh-ecdsa"]));
419 assert_eq!(res, expected);
420 }
421}