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