iris_core/protocols/stream/ssh/
parser.rs1use super::handshake::*;
6use super::Ssh;
7use crate::conntrack::pdu::L4Pdu;
8use crate::protocols::stream::{
9 ConnParsable, ParseResult, ParsingState, ProbeResult, Session, SessionData,
10};
11
12use ssh_parser::*;
13
14#[derive(Debug)]
15pub struct SshParser {
16 sessions: Vec<Ssh>,
17}
18
19impl Default for SshParser {
20 fn default() -> Self {
21 SshParser {
22 sessions: vec![Ssh::new()],
23 }
24 }
25}
26
27impl ConnParsable for SshParser {
28 fn parse(&mut self, pdu: &L4Pdu) -> ParseResult {
29 log::debug!("Updating parser ssh");
30 let offset = pdu.offset();
31 let length = pdu.length();
32 if length == 0 {
33 return ParseResult::Skipped;
34 }
35
36 if let Ok(data) = (pdu.mbuf_ref()).get_data_slice(offset, length) {
37 if !self.sessions.is_empty() {
38 return self.sessions[0].process(data, pdu.dir);
39 }
40 ParseResult::Skipped
41 } else {
42 log::warn!("Malformed packet on parse");
43 ParseResult::Skipped
44 }
45 }
46
47 fn probe(&self, pdu: &L4Pdu) -> ProbeResult {
48 let offset = pdu.offset();
49 let length = pdu.length();
50
51 if length < 4 {
52 return ProbeResult::Unsure;
53 }
54
55 if let Ok(data) = (pdu.mbuf).get_data_slice(offset, length) {
56 match &data[..4] {
58 b"SSH-" => ProbeResult::Certain,
59 _ => ProbeResult::NotForUs,
60 }
61 } else {
62 log::warn!("Malformed packet");
63 ProbeResult::Error
64 }
65 }
66
67 fn remove_session(&mut self, _session_id: usize) -> Option<Session> {
68 self.sessions.pop().map(|ssh| Session {
69 data: SessionData::Ssh(Box::new(ssh)),
70 id: 0,
71 })
72 }
73
74 fn drain_sessions(&mut self) -> Vec<Session> {
75 self.sessions
76 .drain(..)
77 .map(|ssh| Session {
78 data: SessionData::Ssh(Box::new(ssh)),
79 id: 0,
80 })
81 .collect()
82 }
83
84 fn session_parsed_state(&self) -> ParsingState {
85 ParsingState::Stop
86 }
87
88 fn body_offset(&mut self) -> Option<usize> {
89 match self.sessions.last_mut() {
90 Some(session) => std::mem::take(&mut session.last_body_offset),
91 None => None,
92 }
93 }
94}
95
96impl Ssh {
97 pub(crate) fn new() -> Ssh {
99 Ssh {
100 client_version_exchange: None,
101 server_version_exchange: None,
102 key_exchange: None,
103 client_dh_key_exchange: None,
104 server_dh_key_exchange: None,
105 client_new_keys: None,
106 server_new_keys: None,
107 last_body_offset: None,
108 }
109 }
110
111 fn byte_to_string(&mut self, b: &[u8]) -> String {
112 String::from_utf8(b.to_vec()).unwrap()
113 }
114
115 pub(crate) fn parse_version_exchange(&mut self, data: &[u8], dir: bool) {
116 let ssh_identifier = b"SSH-";
117 if let Some(contains_ssh_identifier) = data
118 .windows(ssh_identifier.len())
119 .position(|window| window == ssh_identifier)
120 .map(|p| &data[p..])
121 {
122 match ssh_parser::parse_ssh_identification(contains_ssh_identifier) {
123 Ok((_, (_, ssh_id_string))) => {
124 let version_exchange = SshVersionExchange {
125 protoversion: Some(self.byte_to_string(ssh_id_string.proto)),
126 softwareversion: Some(self.byte_to_string(ssh_id_string.software)),
127 comments: ssh_id_string.comments.map(|c| self.byte_to_string(c)),
128 };
129
130 if dir {
131 self.client_version_exchange = Some(version_exchange);
132 } else {
133 self.server_version_exchange = Some(version_exchange);
134 }
135 }
136 e => log::debug!("Not a valid SSH version exchange message: {:?}", e),
137 }
138 }
139 }
140
141 fn bytes_to_string_vec(&mut self, data: &[u8]) -> Vec<String> {
142 data.split(|&b| b == b',')
143 .map(|chunk| String::from_utf8(chunk.to_vec()).unwrap())
144 .collect()
145 }
146
147 pub(crate) fn parse_key_exchange(&mut self, data: &[u8]) {
148 match ssh_parser::parse_ssh_packet(data) {
149 Ok((_, (pkt, _))) => match pkt {
150 SshPacket::KeyExchange(pkt) => {
151 let key_exchange = SshKeyExchange {
152 cookie: pkt.cookie.to_vec(),
153 kex_algs: self.bytes_to_string_vec(pkt.kex_algs),
154 server_host_key_algs: self.bytes_to_string_vec(pkt.server_host_key_algs),
155 encryption_algs_client_to_server: self
156 .bytes_to_string_vec(pkt.encr_algs_client_to_server),
157 encryption_algs_server_to_client: self
158 .bytes_to_string_vec(pkt.encr_algs_server_to_client),
159 mac_algs_client_to_server: self
160 .bytes_to_string_vec(pkt.mac_algs_client_to_server),
161 mac_algs_server_to_client: self
162 .bytes_to_string_vec(pkt.mac_algs_server_to_client),
163 compression_algs_client_to_server: self
164 .bytes_to_string_vec(pkt.comp_algs_client_to_server),
165 compression_algs_server_to_client: self
166 .bytes_to_string_vec(pkt.comp_algs_server_to_client),
167 languages_client_to_server: self
168 .bytes_to_string_vec(pkt.langs_client_to_server),
169 languages_server_to_client: self
170 .bytes_to_string_vec(pkt.langs_server_to_client),
171 first_kex_packet_follows: pkt.first_kex_packet_follows,
172 };
173
174 self.key_exchange = Some(key_exchange);
175 }
176 e => log::debug!("Could not parse data as a SSH KeyExchange packet: {:?}", e),
177 },
178 e => log::debug!("Could not parse data as a SSH packet: {:?}", e),
179 }
180 }
181
182 pub(crate) fn parse_dh_client_init(&mut self, data: &[u8]) {
183 match ssh_parser::parse_ssh_packet(data) {
184 Ok((_, (pkt, _))) => match pkt {
185 SshPacket::DiffieHellmanInit(pkt) => {
186 let dh_init = SshDhInit { e: pkt.e.to_vec() };
187
188 self.client_dh_key_exchange = Some(dh_init);
189 }
190 e => log::debug!(
191 "Could not parse data as a SSH DiffieHellmanInit packet: {:?}",
192 e
193 ),
194 },
195 e => log::debug!("Could not parse data as a SSH packet: {:?}", e),
196 }
197 }
198
199 pub(crate) fn parse_dh_server_response(&mut self, data: &[u8]) {
200 match ssh_parser::parse_ssh_packet(data) {
201 Ok((_, (pkt, _))) => match pkt {
202 SshPacket::DiffieHellmanReply(pkt) => {
203 let dh_response = SshDhResponse {
204 pubkey_and_certs: pkt.pubkey_and_cert.to_vec(),
205 f: pkt.f.to_vec(),
206 signature: pkt.signature.to_vec(),
207 };
208
209 self.server_dh_key_exchange = Some(dh_response);
210 }
211 e => log::debug!(
212 "Could not parse data as a SSH DiffieHellmanReply packet: {:?}",
213 e
214 ),
215 },
216 e => log::debug!("Could not parse data as a SSH packet: {:?}", e),
217 }
218 }
219
220 pub(crate) fn parse_new_keys(&mut self, data: &[u8], dir: bool) -> usize {
222 let mut remaining = 0;
223 match ssh_parser::parse_ssh_packet(data) {
224 Ok((rem, (pkt, _))) => match pkt {
225 SshPacket::NewKeys => {
226 let new_keys = SshNewKeys;
227 remaining = rem.len();
228 if dir {
229 self.client_new_keys = Some(new_keys);
230 } else {
231 self.server_new_keys = Some(new_keys);
232 }
233 }
234 e => log::debug!("Could not parse data as a SSH NewKeys packet: {:?}", e),
235 },
236 e => log::debug!("Could not parse data as a SSH packet: {:?}", e),
237 }
238 remaining
239 }
240
241 pub(crate) fn process(&mut self, data: &[u8], dir: bool) -> ParseResult {
242 let mut status = ParseResult::Continue(0);
243 log::trace!("process ({} bytes)", data.len());
244
245 let ssh_identifier = b"SSH-";
246 if data
247 .windows(ssh_identifier.len())
248 .position(|window| window == ssh_identifier)
249 .map(|p| &data[p..])
250 .is_some()
251 {
252 self.parse_version_exchange(data, dir);
253 status = ParseResult::Continue(0);
254 } else {
255 match ssh_parser::parse_ssh_packet(data) {
256 Ok((_, (pkt, _))) => {
257 match pkt {
258 SshPacket::KeyExchange(_) => {
259 self.parse_key_exchange(data);
260 status = ParseResult::Continue(0);
261 }
262 SshPacket::DiffieHellmanInit(_) => {
263 self.parse_dh_client_init(data);
264 status = ParseResult::Continue(0);
265 }
266 SshPacket::DiffieHellmanReply(_) => {
267 self.parse_dh_server_response(data);
268 status = ParseResult::Continue(0);
269 }
270 SshPacket::NewKeys => {
271 let remaining = self.parse_new_keys(data, dir);
272
273 if self.client_new_keys.is_some() && self.server_new_keys.is_some() {
275 if remaining > 0 && remaining < data.len() {
276 self.last_body_offset = Some(data.len() - remaining - 1);
277 }
278 return ParseResult::HeadersDone(0);
279 }
280 status = ParseResult::Continue(0);
281 }
282 _ => (),
283 }
284 }
285 e => {
286 log::debug!("parse error: {:?}", e);
287 status = ParseResult::Skipped;
288 }
289 }
290 }
291 status
292 }
293}