]>
Commit | Line | Data |
---|---|---|
1 | /* Copyright (C) 2020 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::ike::ikev1::{handle_ikev1, IkeV1Header, Ikev1Container}; | |
27 | use crate::ike::ikev2::{handle_ikev2, Ikev2Container}; | |
28 | use crate::ike::parser::*; | |
29 | use nom; | |
30 | use std; | |
31 | use std::collections::HashSet; | |
32 | use std::ffi::CString; | |
33 | ||
34 | #[derive(AppLayerEvent)] | |
35 | pub enum IkeEvent { | |
36 | MalformedData, | |
37 | NoEncryption, | |
38 | WeakCryptoEnc, | |
39 | WeakCryptoPrf, | |
40 | WeakCryptoDh, | |
41 | WeakCryptoAuth, | |
42 | WeakCryptoNoDh, | |
43 | WeakCryptoNoAuth, | |
44 | InvalidProposal, | |
45 | UnknownProposal, | |
46 | PayloadExtraData, | |
47 | MultipleServerProposal, | |
48 | } | |
49 | ||
50 | pub struct IkeHeaderWrapper { | |
51 | pub spi_initiator: String, | |
52 | pub spi_responder: String, | |
53 | pub maj_ver: u8, | |
54 | pub min_ver: u8, | |
55 | pub msg_id: u32, | |
56 | pub flags: u8, | |
57 | pub ikev1_transforms: Vec<Vec<SaAttribute>>, | |
58 | pub ikev2_transforms: Vec<IkeV2Transform>, | |
59 | pub ikev1_header: IkeV1Header, | |
60 | pub ikev2_header: IkeV2Header, | |
61 | } | |
62 | ||
63 | impl IkeHeaderWrapper { | |
64 | pub fn new() -> IkeHeaderWrapper { | |
65 | IkeHeaderWrapper { | |
66 | spi_initiator: String::new(), | |
67 | spi_responder: String::new(), | |
68 | maj_ver: 0, | |
69 | min_ver: 0, | |
70 | msg_id: 0, | |
71 | flags: 0, | |
72 | ikev1_transforms: Vec::new(), | |
73 | ikev2_transforms: Vec::new(), | |
74 | ikev1_header: IkeV1Header::default(), | |
75 | ikev2_header: IkeV2Header { | |
76 | init_spi: 0, | |
77 | resp_spi: 0, | |
78 | next_payload: IkePayloadType::NoNextPayload, | |
79 | maj_ver: 0, | |
80 | min_ver: 0, | |
81 | exch_type: IkeExchangeType(0), | |
82 | flags: 0, | |
83 | msg_id: 0, | |
84 | length: 0, | |
85 | }, | |
86 | } | |
87 | } | |
88 | } | |
89 | ||
90 | #[derive(Default)] | |
91 | pub struct IkePayloadWrapper { | |
92 | pub ikev1_payload_types: Option<HashSet<u8>>, | |
93 | pub ikev2_payload_types: Vec<IkePayloadType>, | |
94 | } | |
95 | ||
96 | pub struct IKETransaction { | |
97 | tx_id: u64, | |
98 | ||
99 | pub ike_version: u8, | |
100 | pub hdr: IkeHeaderWrapper, | |
101 | pub payload_types: IkePayloadWrapper, | |
102 | pub notify_types: Vec<NotifyType>, | |
103 | ||
104 | /// errors seen during exchange | |
105 | pub errors: u32, | |
106 | ||
107 | logged: LoggerFlags, | |
108 | tx_data: applayer::AppLayerTxData, | |
109 | } | |
110 | ||
111 | impl Transaction for IKETransaction { | |
112 | fn id(&self) -> u64 { | |
113 | self.tx_id | |
114 | } | |
115 | } | |
116 | ||
117 | impl IKETransaction { | |
118 | pub fn new() -> IKETransaction { | |
119 | IKETransaction { | |
120 | tx_id: 0, | |
121 | ike_version: 0, | |
122 | hdr: IkeHeaderWrapper::new(), | |
123 | payload_types: Default::default(), | |
124 | notify_types: vec![], | |
125 | logged: LoggerFlags::new(), | |
126 | tx_data: applayer::AppLayerTxData::new(), | |
127 | errors: 0, | |
128 | } | |
129 | } | |
130 | ||
131 | /// Set an event. | |
132 | pub fn set_event(&mut self, event: IkeEvent) { | |
133 | self.tx_data.set_event(event as u8); | |
134 | } | |
135 | } | |
136 | ||
137 | #[derive(Default)] | |
138 | pub struct IKEState { | |
139 | tx_id: u64, | |
140 | pub transactions: Vec<IKETransaction>, | |
141 | ||
142 | pub ikev1_container: Ikev1Container, | |
143 | pub ikev2_container: Ikev2Container, | |
144 | } | |
145 | ||
146 | impl State<IKETransaction> for IKEState { | |
147 | fn get_transactions(&self) -> &[IKETransaction] { | |
148 | &self.transactions | |
149 | } | |
150 | } | |
151 | ||
152 | impl IKEState { | |
153 | // Free a transaction by ID. | |
154 | fn free_tx(&mut self, tx_id: u64) { | |
155 | let tx = self | |
156 | .transactions | |
157 | .iter() | |
158 | .position(|tx| tx.tx_id == tx_id + 1); | |
159 | debug_assert!(tx != None); | |
160 | if let Some(idx) = tx { | |
161 | let _ = self.transactions.remove(idx); | |
162 | } | |
163 | } | |
164 | ||
165 | pub fn get_tx(&mut self, tx_id: u64) -> Option<&mut IKETransaction> { | |
166 | for tx in &mut self.transactions { | |
167 | if tx.tx_id == tx_id + 1 { | |
168 | return Some(tx); | |
169 | } | |
170 | } | |
171 | return None; | |
172 | } | |
173 | ||
174 | pub fn new_tx(&mut self) -> IKETransaction { | |
175 | let mut tx = IKETransaction::new(); | |
176 | self.tx_id += 1; | |
177 | tx.tx_id = self.tx_id; | |
178 | return tx; | |
179 | } | |
180 | ||
181 | /// Set an event. The event is set on the most recent transaction. | |
182 | pub fn set_event(&mut self, event: IkeEvent) { | |
183 | if let Some(tx) = self.transactions.last_mut() { | |
184 | tx.set_event(event); | |
185 | } else { | |
186 | SCLogDebug!( | |
187 | "IKE: trying to set event {} on non-existing transaction", | |
188 | event as u32 | |
189 | ); | |
190 | } | |
191 | } | |
192 | ||
193 | fn handle_input(&mut self, input: &[u8], direction: Direction) -> AppLayerResult { | |
194 | // We're not interested in empty requests. | |
195 | if input.len() == 0 { | |
196 | return AppLayerResult::ok(); | |
197 | } | |
198 | ||
199 | let mut current = input; | |
200 | match parse_isakmp_header(current) { | |
201 | Ok((rem, isakmp_header)) => { | |
202 | current = rem; | |
203 | ||
204 | if isakmp_header.maj_ver != 1 && isakmp_header.maj_ver != 2 { | |
205 | SCLogDebug!("Unsupported ISAKMP major_version"); | |
206 | return AppLayerResult::err(); | |
207 | } | |
208 | ||
209 | if isakmp_header.maj_ver == 1 { | |
210 | handle_ikev1(self, current, isakmp_header, direction); | |
211 | } else if isakmp_header.maj_ver == 2 { | |
212 | handle_ikev2(self, current, isakmp_header, direction); | |
213 | } else { | |
214 | return AppLayerResult::err(); | |
215 | } | |
216 | return AppLayerResult::ok(); // todo either remove outer loop or check header length-field if we have completely read everything | |
217 | } | |
218 | Err(nom::Err::Incomplete(_)) => { | |
219 | SCLogDebug!("Insufficient data while parsing IKE"); | |
220 | return AppLayerResult::err(); | |
221 | } | |
222 | Err(_) => { | |
223 | SCLogDebug!("Error while parsing IKE packet"); | |
224 | return AppLayerResult::err(); | |
225 | } | |
226 | } | |
227 | } | |
228 | } | |
229 | ||
230 | /// Probe to see if this input looks like a request or response. | |
231 | fn probe(input: &[u8], direction: Direction, rdir: *mut u8) -> bool { | |
232 | match parse_isakmp_header(input) { | |
233 | Ok((_, isakmp_header)) => { | |
234 | if isakmp_header.maj_ver == 1 { | |
235 | if isakmp_header.resp_spi == 0 && direction != Direction::ToServer { | |
236 | unsafe { | |
237 | *rdir = Direction::ToServer.into(); | |
238 | } | |
239 | } | |
240 | return true; | |
241 | } else if isakmp_header.maj_ver == 2 { | |
242 | if isakmp_header.min_ver != 0 { | |
243 | SCLogDebug!( | |
244 | "ipsec_probe: could be ipsec, but with unsupported/invalid version {}.{}", | |
245 | isakmp_header.maj_ver, | |
246 | isakmp_header.min_ver | |
247 | ); | |
248 | return false; | |
249 | } | |
250 | if isakmp_header.exch_type < 34 || isakmp_header.exch_type > 37 { | |
251 | SCLogDebug!("ipsec_probe: could be ipsec, but with unsupported/invalid exchange type {}", | |
252 | isakmp_header.exch_type); | |
253 | return false; | |
254 | } | |
255 | if isakmp_header.length as usize != input.len() { | |
256 | SCLogDebug!("ipsec_probe: could be ipsec, but length does not match"); | |
257 | return false; | |
258 | } | |
259 | ||
260 | if isakmp_header.resp_spi == 0 && direction != Direction::ToServer { | |
261 | unsafe { | |
262 | *rdir = Direction::ToServer.into(); | |
263 | } | |
264 | } | |
265 | return true; | |
266 | } | |
267 | ||
268 | return false; | |
269 | } | |
270 | Err(_) => return false, | |
271 | } | |
272 | } | |
273 | ||
274 | // C exports. | |
275 | ||
276 | /// C entry point for a probing parser. | |
277 | #[no_mangle] | |
278 | pub unsafe extern "C" fn rs_ike_probing_parser( | |
279 | _flow: *const Flow, direction: u8, input: *const u8, input_len: u32, rdir: *mut u8, | |
280 | ) -> AppProto { | |
281 | if input_len < 28 { | |
282 | // at least the ISAKMP_HEADER must be there, not ALPROTO_UNKNOWN because over UDP | |
283 | return ALPROTO_FAILED; | |
284 | } | |
285 | ||
286 | if !input.is_null() { | |
287 | let slice = build_slice!(input, input_len as usize); | |
288 | if probe(slice, direction.into(), rdir) { | |
289 | return ALPROTO_IKE; | |
290 | } | |
291 | } | |
292 | return ALPROTO_FAILED; | |
293 | } | |
294 | ||
295 | #[no_mangle] | |
296 | pub extern "C" fn rs_ike_state_new( | |
297 | _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, | |
298 | ) -> *mut std::os::raw::c_void { | |
299 | let state = IKEState::default(); | |
300 | let boxed = Box::new(state); | |
301 | return Box::into_raw(boxed) as *mut _; | |
302 | } | |
303 | ||
304 | #[no_mangle] | |
305 | pub unsafe extern "C" fn rs_ike_state_free(state: *mut std::os::raw::c_void) { | |
306 | // Just unbox... | |
307 | std::mem::drop(Box::from_raw(state as *mut IKEState)); | |
308 | } | |
309 | ||
310 | #[no_mangle] | |
311 | pub unsafe extern "C" fn rs_ike_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { | |
312 | let state = cast_pointer!(state, IKEState); | |
313 | state.free_tx(tx_id); | |
314 | } | |
315 | ||
316 | #[no_mangle] | |
317 | pub unsafe extern "C" fn rs_ike_parse_request( | |
318 | _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, | |
319 | input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8, | |
320 | ) -> AppLayerResult { | |
321 | let state = cast_pointer!(state, IKEState); | |
322 | let buf = build_slice!(input, input_len as usize); | |
323 | ||
324 | return state.handle_input(buf, Direction::ToServer); | |
325 | } | |
326 | ||
327 | #[no_mangle] | |
328 | pub unsafe extern "C" fn rs_ike_parse_response( | |
329 | _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, | |
330 | input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8, | |
331 | ) -> AppLayerResult { | |
332 | let state = cast_pointer!(state, IKEState); | |
333 | let buf = build_slice!(input, input_len as usize); | |
334 | return state.handle_input(buf, Direction::ToClient); | |
335 | } | |
336 | ||
337 | #[no_mangle] | |
338 | pub unsafe extern "C" fn rs_ike_state_get_tx( | |
339 | state: *mut std::os::raw::c_void, tx_id: u64, | |
340 | ) -> *mut std::os::raw::c_void { | |
341 | let state = cast_pointer!(state, IKEState); | |
342 | match state.get_tx(tx_id) { | |
343 | Some(tx) => { | |
344 | return tx as *const _ as *mut _; | |
345 | } | |
346 | None => { | |
347 | return std::ptr::null_mut(); | |
348 | } | |
349 | } | |
350 | } | |
351 | ||
352 | #[no_mangle] | |
353 | pub unsafe extern "C" fn rs_ike_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { | |
354 | let state = cast_pointer!(state, IKEState); | |
355 | return state.tx_id; | |
356 | } | |
357 | ||
358 | #[no_mangle] | |
359 | pub extern "C" fn rs_ike_state_progress_completion_status(_direction: u8) -> std::os::raw::c_int { | |
360 | // This parser uses 1 to signal transaction completion status. | |
361 | return 1; | |
362 | } | |
363 | ||
364 | #[no_mangle] | |
365 | pub extern "C" fn rs_ike_tx_get_alstate_progress( | |
366 | _tx: *mut std::os::raw::c_void, _direction: u8, | |
367 | ) -> std::os::raw::c_int { | |
368 | return 1; | |
369 | } | |
370 | ||
371 | #[no_mangle] | |
372 | pub unsafe extern "C" fn rs_ike_tx_get_logged( | |
373 | _state: *mut std::os::raw::c_void, tx: *mut std::os::raw::c_void, | |
374 | ) -> u32 { | |
375 | let tx = cast_pointer!(tx, IKETransaction); | |
376 | return tx.logged.get(); | |
377 | } | |
378 | ||
379 | #[no_mangle] | |
380 | pub unsafe extern "C" fn rs_ike_tx_set_logged( | |
381 | _state: *mut std::os::raw::c_void, tx: *mut std::os::raw::c_void, logged: u32, | |
382 | ) { | |
383 | let tx = cast_pointer!(tx, IKETransaction); | |
384 | tx.logged.set(logged); | |
385 | } | |
386 | ||
387 | static mut ALPROTO_IKE : AppProto = ALPROTO_UNKNOWN; | |
388 | ||
389 | // Parser name as a C style string. | |
390 | const PARSER_NAME: &'static [u8] = b"ike\0"; | |
391 | const PARSER_ALIAS: &'static [u8] = b"ikev2\0"; | |
392 | ||
393 | export_tx_data_get!(rs_ike_get_tx_data, IKETransaction); | |
394 | ||
395 | #[no_mangle] | |
396 | pub unsafe extern "C" fn rs_ike_register_parser() { | |
397 | let default_port = CString::new("500").unwrap(); | |
398 | let parser = RustParser { | |
399 | name : PARSER_NAME.as_ptr() as *const std::os::raw::c_char, | |
400 | default_port : default_port.as_ptr(), | |
401 | ipproto : core::IPPROTO_UDP, | |
402 | probe_ts : Some(rs_ike_probing_parser), | |
403 | probe_tc : Some(rs_ike_probing_parser), | |
404 | min_depth : 0, | |
405 | max_depth : 16, | |
406 | state_new : rs_ike_state_new, | |
407 | state_free : rs_ike_state_free, | |
408 | tx_free : rs_ike_state_tx_free, | |
409 | parse_ts : rs_ike_parse_request, | |
410 | parse_tc : rs_ike_parse_response, | |
411 | get_tx_count : rs_ike_state_get_tx_count, | |
412 | get_tx : rs_ike_state_get_tx, | |
413 | tx_comp_st_ts : 1, | |
414 | tx_comp_st_tc : 1, | |
415 | tx_get_progress : rs_ike_tx_get_alstate_progress, | |
416 | get_eventinfo : Some(IkeEvent::get_event_info), | |
417 | get_eventinfo_byid : Some(IkeEvent::get_event_info_by_id), | |
418 | localstorage_new : None, | |
419 | localstorage_free : None, | |
420 | get_files : None, | |
421 | get_tx_iterator : Some(applayer::state_get_tx_iterator::<IKEState, IKETransaction>), | |
422 | get_tx_data : rs_ike_get_tx_data, | |
423 | apply_tx_config : None, | |
424 | flags : APP_LAYER_PARSER_OPT_UNIDIR_TXS, | |
425 | truncate : None, | |
426 | }; | |
427 | ||
428 | let ip_proto_str = CString::new("udp").unwrap(); | |
429 | ||
430 | if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { | |
431 | let alproto = AppLayerRegisterProtocolDetection(&parser, 1); | |
432 | ALPROTO_IKE = alproto; | |
433 | if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { | |
434 | let _ = AppLayerRegisterParser(&parser, alproto); | |
435 | } | |
436 | ||
437 | AppLayerRegisterParserAlias( | |
438 | PARSER_NAME.as_ptr() as *const std::os::raw::c_char, | |
439 | PARSER_ALIAS.as_ptr() as *const std::os::raw::c_char, | |
440 | ); | |
441 | SCLogDebug!("Rust IKE parser registered."); | |
442 | } else { | |
443 | SCLogDebug!("Protocol detector and parser disabled for IKE."); | |
444 | } | |
445 | } |