/src/suricata/rust/src/ike/ike.rs
Line | Count | Source |
1 | | /* Copyright (C) 2020-2021 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | // Author: Frank Honza <frank.honza@dcso.de> |
19 | | |
20 | | extern crate ipsec_parser; |
21 | | use self::ipsec_parser::*; |
22 | | |
23 | | use crate::applayer; |
24 | | use crate::applayer::*; |
25 | | use crate::core::{self, *}; |
26 | | use crate::direction::Direction; |
27 | | use crate::flow::Flow; |
28 | | use crate::ike::ikev1::{handle_ikev1, IkeV1Header, Ikev1Container}; |
29 | | use crate::ike::ikev2::{handle_ikev2, Ikev2Container}; |
30 | | use crate::ike::parser::*; |
31 | | use nom8::Err; |
32 | | use std; |
33 | | use std::collections::HashSet; |
34 | | use std::ffi::CString; |
35 | | use suricata_sys::sys::{ |
36 | | AppLayerParserState, AppProto, SCAppLayerParserConfParserEnabled, |
37 | | SCAppLayerProtoDetectConfProtoDetectionEnabled, SCAppLayerRegisterParserAlias, |
38 | | }; |
39 | | |
40 | | #[derive(AppLayerEvent)] |
41 | | pub enum IkeEvent { |
42 | | MalformedData, |
43 | | NoEncryption, |
44 | | WeakCryptoEnc, |
45 | | WeakCryptoPrf, |
46 | | WeakCryptoDh, |
47 | | WeakCryptoAuth, |
48 | | WeakCryptoNoDh, |
49 | | WeakCryptoNoAuth, |
50 | | InvalidProposal, |
51 | | UnknownProposal, |
52 | | PayloadExtraData, |
53 | | MultipleServerProposal, |
54 | | } |
55 | | |
56 | | pub struct IkeHeaderWrapper { |
57 | | pub spi_initiator: String, |
58 | | pub spi_responder: String, |
59 | | pub maj_ver: u8, |
60 | | pub min_ver: u8, |
61 | | pub msg_id: u32, |
62 | | pub flags: u8, |
63 | | pub ikev1_transforms: Vec<Vec<SaAttribute>>, |
64 | | pub ikev2_transforms: Vec<IkeV2Transform>, |
65 | | pub ikev1_header: IkeV1Header, |
66 | | pub ikev2_header: IkeV2Header, |
67 | | } |
68 | | |
69 | | impl Default for IkeHeaderWrapper { |
70 | 294k | fn default() -> Self { |
71 | 294k | Self::new() |
72 | 294k | } |
73 | | } |
74 | | |
75 | | impl IkeHeaderWrapper { |
76 | 294k | pub fn new() -> Self { |
77 | 294k | Self { |
78 | 294k | spi_initiator: String::new(), |
79 | 294k | spi_responder: String::new(), |
80 | 294k | maj_ver: 0, |
81 | 294k | min_ver: 0, |
82 | 294k | msg_id: 0, |
83 | 294k | flags: 0, |
84 | 294k | ikev1_transforms: Vec::new(), |
85 | 294k | ikev2_transforms: Vec::new(), |
86 | 294k | ikev1_header: IkeV1Header::default(), |
87 | 294k | ikev2_header: IkeV2Header { |
88 | 294k | init_spi: 0, |
89 | 294k | resp_spi: 0, |
90 | 294k | next_payload: IkePayloadType::NoNextPayload, |
91 | 294k | maj_ver: 0, |
92 | 294k | min_ver: 0, |
93 | 294k | exch_type: IkeExchangeType(0), |
94 | 294k | flags: 0, |
95 | 294k | msg_id: 0, |
96 | 294k | length: 0, |
97 | 294k | }, |
98 | 294k | } |
99 | 294k | } |
100 | | } |
101 | | |
102 | | #[derive(Default)] |
103 | | pub struct IkePayloadWrapper { |
104 | | pub ikev1_payload_types: Option<HashSet<u8>>, |
105 | | pub ikev2_payload_types: Vec<IkePayloadType>, |
106 | | } |
107 | | |
108 | | #[derive(Default)] |
109 | | pub struct IKETransaction { |
110 | | tx_id: u64, |
111 | | |
112 | | pub ike_version: u8, |
113 | | pub direction: Direction, |
114 | | pub hdr: IkeHeaderWrapper, |
115 | | pub payload_types: IkePayloadWrapper, |
116 | | pub notify_types: Vec<NotifyType>, |
117 | | |
118 | | /// errors seen during exchange |
119 | | pub errors: u32, |
120 | | |
121 | | pub tx_data: applayer::AppLayerTxData, |
122 | | } |
123 | | |
124 | | impl Transaction for IKETransaction { |
125 | 563k | fn id(&self) -> u64 { |
126 | 563k | self.tx_id |
127 | 563k | } |
128 | | } |
129 | | |
130 | | impl IKETransaction { |
131 | 294k | pub fn new(direction: Direction) -> Self { |
132 | 294k | Self { |
133 | 294k | direction, |
134 | 294k | tx_data: applayer::AppLayerTxData::for_direction(direction), |
135 | 294k | ..Default::default() |
136 | 294k | } |
137 | 294k | } |
138 | | |
139 | | /// Set an event. |
140 | 227k | pub fn set_event(&mut self, event: IkeEvent) { |
141 | 227k | self.tx_data.set_event(event as u8); |
142 | 227k | } |
143 | | } |
144 | | |
145 | | #[derive(Default)] |
146 | | pub struct IKEState { |
147 | | state_data: AppLayerStateData, |
148 | | tx_id: u64, |
149 | | pub transactions: Vec<IKETransaction>, |
150 | | |
151 | | pub ikev1_container: Ikev1Container, |
152 | | pub ikev2_container: Ikev2Container, |
153 | | } |
154 | | |
155 | | impl State<IKETransaction> for IKEState { |
156 | 322k | fn get_transaction_count(&self) -> usize { |
157 | 322k | self.transactions.len() |
158 | 322k | } |
159 | | |
160 | 281k | fn get_transaction_by_index(&self, index: usize) -> Option<&IKETransaction> { |
161 | 281k | self.transactions.get(index) |
162 | 281k | } |
163 | | } |
164 | | |
165 | | impl IKEState { |
166 | | // Free a transaction by ID. |
167 | 253k | fn free_tx(&mut self, tx_id: u64) { |
168 | 253k | let tx = self |
169 | 253k | .transactions |
170 | 253k | .iter() |
171 | 253k | .position(|tx| tx.tx_id == tx_id + 1); |
172 | 253k | debug_assert!(tx.is_some()); |
173 | 253k | if let Some(idx) = tx { |
174 | 253k | let _ = self.transactions.remove(idx); |
175 | 253k | } |
176 | 253k | } |
177 | | |
178 | 6.39k | pub fn get_tx(&mut self, tx_id: u64) -> Option<&mut IKETransaction> { |
179 | 6.39k | self.transactions |
180 | 6.39k | .iter_mut() |
181 | 6.39k | .find(|tx| tx.tx_id == tx_id + 1) |
182 | 6.39k | } |
183 | | |
184 | 294k | pub fn new_tx(&mut self, direction: Direction) -> IKETransaction { |
185 | 294k | let mut tx = IKETransaction::new(direction); |
186 | 294k | self.tx_id += 1; |
187 | 294k | tx.tx_id = self.tx_id; |
188 | 294k | return tx; |
189 | 294k | } |
190 | | |
191 | | /// Set an event. The event is set on the most recent transaction. |
192 | 58.3k | pub fn set_event(&mut self, event: IkeEvent) { |
193 | 58.3k | if let Some(tx) = self.transactions.last_mut() { |
194 | 0 | tx.set_event(event); |
195 | 58.3k | } else { |
196 | 58.3k | SCLogDebug!( |
197 | 58.3k | "IKE: trying to set event {} on non-existing transaction", |
198 | 58.3k | event as u32 |
199 | 58.3k | ); |
200 | 58.3k | } |
201 | 58.3k | } |
202 | | |
203 | 295k | fn handle_input(&mut self, input: &[u8], direction: Direction) -> AppLayerResult { |
204 | | // We're not interested in empty requests. |
205 | 295k | if input.is_empty() { |
206 | 0 | return AppLayerResult::ok(); |
207 | 295k | } |
208 | | |
209 | 295k | let mut current = input; |
210 | 295k | match parse_isakmp_header(current) { |
211 | 295k | Ok((rem, isakmp_header)) => { |
212 | 295k | current = rem; |
213 | | |
214 | 295k | if isakmp_header.maj_ver != 1 && isakmp_header.maj_ver != 2 { |
215 | | SCLogDebug!("Unsupported ISAKMP major_version"); |
216 | 1.27k | return AppLayerResult::err(); |
217 | 294k | } |
218 | | |
219 | 294k | if isakmp_header.maj_ver == 1 { |
220 | 156k | handle_ikev1(self, current, isakmp_header, direction); |
221 | 156k | } else if isakmp_header.maj_ver == 2 { |
222 | 137k | handle_ikev2(self, current, isakmp_header, direction); |
223 | 137k | } else { |
224 | 0 | return AppLayerResult::err(); |
225 | | } |
226 | 294k | return AppLayerResult::ok(); // todo either remove outer loop or check header length-field if we have completely read everything |
227 | | } |
228 | | Err(Err::Incomplete(_)) => { |
229 | | SCLogDebug!("Insufficient data while parsing IKE"); |
230 | 373 | return AppLayerResult::err(); |
231 | | } |
232 | | Err(_) => { |
233 | | SCLogDebug!("Error while parsing IKE packet"); |
234 | 0 | return AppLayerResult::err(); |
235 | | } |
236 | | } |
237 | 295k | } |
238 | | } |
239 | | |
240 | | /// Probe to see if this input looks like a request or response. |
241 | 2.25k | fn probe(input: &[u8], direction: Direction, rdir: *mut u8) -> bool { |
242 | 2.25k | match parse_isakmp_header(input) { |
243 | 2.25k | Ok((_, isakmp_header)) => { |
244 | 2.25k | if isakmp_header.maj_ver == 1 { |
245 | 1.49k | if isakmp_header.resp_spi == 0 && direction != Direction::ToServer { |
246 | 2 | unsafe { |
247 | 2 | *rdir = Direction::ToServer.into(); |
248 | 2 | } |
249 | 1.48k | } |
250 | 1.49k | return true; |
251 | 766 | } else if isakmp_header.maj_ver == 2 { |
252 | 251 | if isakmp_header.min_ver != 0 { |
253 | | SCLogDebug!( |
254 | | "ipsec_probe: could be ipsec, but with unsupported/invalid version {}.{}", |
255 | | isakmp_header.maj_ver, |
256 | | isakmp_header.min_ver |
257 | | ); |
258 | 160 | return false; |
259 | 91 | } |
260 | 91 | if isakmp_header.exch_type < 34 || isakmp_header.exch_type > 37 { |
261 | | SCLogDebug!("ipsec_probe: could be ipsec, but with unsupported/invalid exchange type {}", |
262 | | isakmp_header.exch_type); |
263 | 4 | return false; |
264 | 87 | } |
265 | 87 | if isakmp_header.length as usize != input.len() { |
266 | | SCLogDebug!("ipsec_probe: could be ipsec, but length does not match"); |
267 | 3 | return false; |
268 | 84 | } |
269 | | |
270 | 84 | if isakmp_header.resp_spi == 0 && direction != Direction::ToServer { |
271 | 1 | unsafe { |
272 | 1 | *rdir = Direction::ToServer.into(); |
273 | 1 | } |
274 | 83 | } |
275 | 84 | return true; |
276 | 515 | } |
277 | | |
278 | 515 | return false; |
279 | | } |
280 | 0 | Err(_) => return false, |
281 | | } |
282 | 2.25k | } |
283 | | |
284 | | // C exports. |
285 | | |
286 | | /// C entry point for a probing parser. |
287 | 2.26k | unsafe extern "C" fn ike_probing_parser( |
288 | 2.26k | _flow: *const Flow, direction: u8, input: *const u8, input_len: u32, rdir: *mut u8, |
289 | 2.26k | ) -> AppProto { |
290 | 2.26k | if input_len < 28 { |
291 | | // at least the ISAKMP_HEADER must be there, not ALPROTO_UNKNOWN because over UDP |
292 | 11 | return ALPROTO_FAILED; |
293 | 2.25k | } |
294 | | |
295 | 2.25k | if !input.is_null() { |
296 | 2.25k | let slice = build_slice!(input, input_len as usize); |
297 | 2.25k | if probe(slice, direction.into(), rdir) { |
298 | 1.57k | return ALPROTO_IKE; |
299 | 682 | } |
300 | 0 | } |
301 | 682 | return ALPROTO_FAILED; |
302 | 2.26k | } |
303 | | |
304 | 5.43k | extern "C" fn ike_state_new( |
305 | 5.43k | _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, |
306 | 5.43k | ) -> *mut std::os::raw::c_void { |
307 | 5.43k | let state = IKEState::default(); |
308 | 5.43k | let boxed = Box::new(state); |
309 | 5.43k | return Box::into_raw(boxed) as *mut _; |
310 | 5.43k | } |
311 | | |
312 | 5.43k | unsafe extern "C" fn ike_state_free(state: *mut std::os::raw::c_void) { |
313 | | // Just unbox... |
314 | 5.43k | std::mem::drop(Box::from_raw(state as *mut IKEState)); |
315 | 5.43k | } |
316 | | |
317 | 253k | unsafe extern "C" fn ike_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { |
318 | 253k | let state = cast_pointer!(state, IKEState); |
319 | 253k | state.free_tx(tx_id); |
320 | 253k | } |
321 | | |
322 | 142k | unsafe extern "C" fn ike_parse_request( |
323 | 142k | _flow: *mut Flow, state: *mut std::os::raw::c_void, _pstate: *mut AppLayerParserState, |
324 | 142k | stream_slice: StreamSlice, _data: *mut std::os::raw::c_void, |
325 | 142k | ) -> AppLayerResult { |
326 | 142k | let state = cast_pointer!(state, IKEState); |
327 | 142k | return state.handle_input(stream_slice.as_slice(), Direction::ToServer); |
328 | 142k | } |
329 | | |
330 | 153k | unsafe extern "C" fn ike_parse_response( |
331 | 153k | _flow: *mut Flow, state: *mut std::os::raw::c_void, _pstate: *mut AppLayerParserState, |
332 | 153k | stream_slice: StreamSlice, _data: *mut std::os::raw::c_void, |
333 | 153k | ) -> AppLayerResult { |
334 | 153k | let state = cast_pointer!(state, IKEState); |
335 | 153k | return state.handle_input(stream_slice.as_slice(), Direction::ToClient); |
336 | 153k | } |
337 | | |
338 | 6.39k | unsafe extern "C" fn ike_state_get_tx( |
339 | 6.39k | state: *mut std::os::raw::c_void, tx_id: u64, |
340 | 6.39k | ) -> *mut std::os::raw::c_void { |
341 | 6.39k | let state = cast_pointer!(state, IKEState); |
342 | 6.39k | match state.get_tx(tx_id) { |
343 | 5.82k | Some(tx) => { |
344 | 5.82k | return tx as *const _ as *mut _; |
345 | | } |
346 | | None => { |
347 | 572 | return std::ptr::null_mut(); |
348 | | } |
349 | | } |
350 | 6.39k | } |
351 | | |
352 | 912k | unsafe extern "C" fn ike_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { |
353 | 912k | let state = cast_pointer!(state, IKEState); |
354 | 912k | return state.tx_id; |
355 | 912k | } |
356 | | |
357 | 555k | extern "C" fn ike_tx_get_alstate_progress( |
358 | 555k | _tx: *mut std::os::raw::c_void, _direction: u8, |
359 | 555k | ) -> std::os::raw::c_int { |
360 | 555k | return 1; |
361 | 555k | } |
362 | | |
363 | | pub(super) static mut ALPROTO_IKE: AppProto = ALPROTO_UNKNOWN; |
364 | | |
365 | | // Parser name as a C style string. |
366 | | const PARSER_NAME: &[u8] = b"ike\0"; |
367 | | const PARSER_ALIAS: &[u8] = b"ikev2\0"; |
368 | | |
369 | | export_tx_data_get!(ike_get_tx_data, IKETransaction); |
370 | | export_state_data_get!(ike_get_state_data, IKEState); |
371 | | |
372 | | #[no_mangle] |
373 | 41 | pub unsafe extern "C" fn SCRegisterIkeParser() { |
374 | 41 | let default_port = CString::new("500").unwrap(); |
375 | 41 | let parser = RustParser { |
376 | 41 | name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, |
377 | 41 | default_port: default_port.as_ptr(), |
378 | 41 | ipproto: core::IPPROTO_UDP, |
379 | 41 | probe_ts: Some(ike_probing_parser), |
380 | 41 | probe_tc: Some(ike_probing_parser), |
381 | 41 | min_depth: 0, |
382 | 41 | max_depth: 16, |
383 | 41 | state_new: ike_state_new, |
384 | 41 | state_free: ike_state_free, |
385 | 41 | tx_free: ike_state_tx_free, |
386 | 41 | parse_ts: ike_parse_request, |
387 | 41 | parse_tc: ike_parse_response, |
388 | 41 | get_tx_count: ike_state_get_tx_count, |
389 | 41 | get_tx: ike_state_get_tx, |
390 | 41 | tx_comp_st_ts: 1, |
391 | 41 | tx_comp_st_tc: 1, |
392 | 41 | tx_get_progress: ike_tx_get_alstate_progress, |
393 | 41 | get_eventinfo: Some(IkeEvent::get_event_info), |
394 | 41 | get_eventinfo_byid: Some(IkeEvent::get_event_info_by_id), |
395 | 41 | localstorage_new: None, |
396 | 41 | localstorage_free: None, |
397 | 41 | get_tx_files: None, |
398 | 41 | get_tx_iterator: Some(applayer::state_get_tx_iterator::<IKEState, IKETransaction>), |
399 | 41 | get_tx_data: ike_get_tx_data, |
400 | 41 | get_state_data: ike_get_state_data, |
401 | 41 | apply_tx_config: None, |
402 | 41 | flags: 0, |
403 | 41 | get_frame_id_by_name: None, |
404 | 41 | get_frame_name_by_id: None, |
405 | 41 | get_state_id_by_name: None, |
406 | 41 | get_state_name_by_id: None, |
407 | 41 | }; |
408 | | |
409 | 41 | let ip_proto_str = CString::new("udp").unwrap(); |
410 | | |
411 | 41 | if SCAppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { |
412 | 41 | let alproto = applayer_register_protocol_detection(&parser, 1); |
413 | 41 | ALPROTO_IKE = alproto; |
414 | 41 | if SCAppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { |
415 | 41 | let _ = AppLayerRegisterParser(&parser, alproto); |
416 | 41 | } |
417 | | |
418 | 41 | SCAppLayerRegisterParserAlias( |
419 | 41 | PARSER_NAME.as_ptr() as *const std::os::raw::c_char, |
420 | 41 | PARSER_ALIAS.as_ptr() as *const std::os::raw::c_char, |
421 | | ); |
422 | | SCLogDebug!("Rust IKE parser registered."); |
423 | 0 | } else { |
424 | 0 | SCLogDebug!("Protocol detector and parser disabled for IKE."); |
425 | 0 | } |
426 | 41 | } |