From c99b9462d712f92f12c573a59a55024225267beb Mon Sep 17 00:00:00 2001 From: Pierre Chifflier Date: Wed, 31 Jan 2018 08:13:41 +0100 Subject: [PATCH] Add new parser: IKEv2 Add a new parser for Internet Key Exchange version (IKEv2), defined in RFC 7296. The IKEv2 parser itself is external. The embedded code includes the parser state and associated variables, the state machine, and the detection code. The parser looks the first two messages of a connection, and analyzes the client and server proposals to check the cryptographic parameters. --- rust/Cargo.toml.in | 3 +- rust/gen-c-headers.py | 2 + rust/src/ikev2/ikev2.rs | 701 +++++++++++++++++++++++++++++++++++ rust/src/ikev2/mod.rs | 23 ++ rust/src/ikev2/state.rs | 57 +++ rust/src/lib.rs | 3 + src/Makefile.am | 1 + src/app-layer-detect-proto.c | 4 + src/app-layer-ikev2.c | 66 ++++ src/app-layer-ikev2.h | 34 ++ src/app-layer-parser.c | 2 + src/app-layer-protos.c | 4 + src/app-layer-protos.h | 1 + suricata.yaml.in | 3 + 14 files changed, 903 insertions(+), 1 deletion(-) create mode 100644 rust/src/ikev2/ikev2.rs create mode 100644 rust/src/ikev2/mod.rs create mode 100644 rust/src/ikev2/state.rs create mode 100644 src/app-layer-ikev2.c create mode 100644 src/app-layer-ikev2.h diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in index c56f8a0b5f..53e5a2630f 100644 --- a/rust/Cargo.toml.in +++ b/rust/Cargo.toml.in @@ -10,7 +10,7 @@ debug = true [features] lua = [] -experimental = ["ntp-parser"] +experimental = ["ntp-parser", "ipsec-parser"] strict = [] debug = [] @@ -22,3 +22,4 @@ der-parser = "0.5.2" kerberos-parser = "0.1.0" ntp-parser = { version = "^0", optional = true } +ipsec-parser = { version = "0.3", optional = true } diff --git a/rust/gen-c-headers.py b/rust/gen-c-headers.py index d0b737222f..d5b61bd2f8 100755 --- a/rust/gen-c-headers.py +++ b/rust/gen-c-headers.py @@ -84,6 +84,8 @@ type_map = { "TFTPState": "TFTPState", "SMBState": "SMBState", "SMBTransaction": "SMBTransaction", + "IKEV2State": "IKEV2State", + "IKEV2Transaction": "IKEV2Transaction", "JsonT": "json_t", "DetectEngineState": "DetectEngineState", "core::DetectEngineState": "DetectEngineState", diff --git a/rust/src/ikev2/ikev2.rs b/rust/src/ikev2/ikev2.rs new file mode 100644 index 0000000000..4fa5a259ad --- /dev/null +++ b/rust/src/ikev2/ikev2.rs @@ -0,0 +1,701 @@ +/* Copyright (C) 2017-2018 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Pierre Chifflier + +use ikev2::ipsec_parser::*; +use ikev2::state::IKEV2ConnectionState; +use core; +use core::{AppProto,Flow,ALPROTO_UNKNOWN,ALPROTO_FAILED,STREAM_TOSERVER,STREAM_TOCLIENT}; +use applayer; +use parser::*; +use libc; +use std; +use std::ffi::{CStr,CString}; + +use log::*; + +use nom::IResult; + +#[repr(u32)] +pub enum IKEV2Event { + MalformedData = 0, + NoEncryption, + WeakCryptoEnc, + WeakCryptoPRF, + WeakCryptoDH, + WeakCryptoAuth, + WeakCryptoNoDH, + WeakCryptoNoAuth, + InvalidProposal, + UnknownProposal, +} + +pub struct IKEV2State { + /// List of transactions for this session + transactions: Vec, + + /// Events counter + events: u16, + + /// tx counter for assigning incrementing id's to tx's + tx_id: u64, + + /// The connection state + connection_state: IKEV2ConnectionState, + + /// The transforms proposed by the initiator + pub client_transforms : Vec>, + + /// The transforms selected by the responder + pub server_transforms : Vec>, + + /// The encryption algorithm selected by the responder + pub alg_enc: IkeTransformEncType, + /// The authentication algorithm selected by the responder + pub alg_auth: IkeTransformAuthType, + /// The PRF algorithm selected by the responder + pub alg_prf: IkeTransformPRFType, + /// The Diffie-Hellman algorithm selected by the responder + pub alg_dh: IkeTransformDHType, + /// The extended sequence numbers parameter selected by the responder + pub alg_esn: IkeTransformESNType, + + /// The Diffie-Hellman group from the server KE message, if present. + pub dh_group: IkeTransformDHType, + +} + +#[derive(Debug)] +pub struct IKEV2Transaction { + /// The IKEV2 reference ID + pub xid: u64, + + pub hdr: IkeV2Header, + + /// The internal transaction id + id: u64, + + /// The detection engine state, if present + de_state: Option<*mut core::DetectEngineState>, + + /// The events associated with this transaction + events: *mut core::AppLayerDecoderEvents, + + logged: applayer::LoggerFlags, +} + + + +impl IKEV2State { + pub fn new() -> IKEV2State { + IKEV2State{ + transactions: Vec::new(), + events: 0, + tx_id: 0, + connection_state: IKEV2ConnectionState::Init, + dh_group: IkeTransformDHType::None, + client_transforms: Vec::new(), + server_transforms: Vec::new(), + alg_enc: IkeTransformEncType::ENCR_NULL, + alg_auth: IkeTransformAuthType::NONE, + alg_prf: IkeTransformPRFType::PRF_NULL, + alg_dh: IkeTransformDHType::None, + alg_esn: IkeTransformESNType::NoESN, + } + } +} + +impl IKEV2State { + /// Parse an IKEV2 request message + /// + /// Returns The number of messages parsed, or -1 on error + fn parse(&mut self, i: &[u8], direction: u8) -> i8 { + match parse_ikev2_header(i) { + IResult::Done(rem,ref hdr) => { + SCLogDebug!("parse_ikev2: {:?}",hdr); + if rem.len() == 0 && hdr.length == 28 { + return 1; + } + // Rule 0: check version + if hdr.maj_ver != 2 || hdr.min_ver != 0 { + SCLogInfo!("Unknown header version: {}.{}", hdr.maj_ver, hdr.min_ver); + } + if hdr.init_spi == 0 { + SCLogInfo!("Malformed SPI - wrong length"); + self.set_event(IKEV2Event::MalformedData); + return -1; + } + // only analyse IKE_SA, other payloads are encrypted + if hdr.exch_type != IkeExchangeType::IKE_SA_INIT { + return 0; + } + let mut tx = self.new_tx(); + // use init_spi as transaction identifier + tx.xid = hdr.init_spi; + tx.hdr = (*hdr).clone(); + match parse_ikev2_payload_list(rem,hdr.next_payload) { + IResult::Done(_,Ok(ref p)) => { + for payload in p { + match payload.content { + IkeV2PayloadContent::Dummy => (), + IkeV2PayloadContent::SA(ref prop) => { + // if hdr.flags & IKEV2_FLAG_INITIATOR != 0 { + self.add_proposals(prop, direction); + // } + }, + IkeV2PayloadContent::KE(ref kex) => { + SCLogDebug!("KEX {:?}", kex.dh_group); + if direction == STREAM_TOCLIENT { + self.dh_group = kex.dh_group; + } + }, + IkeV2PayloadContent::Nonce(ref n) => { + SCLogDebug!("Nonce: {:?}", n); + }, + IkeV2PayloadContent::Notify(ref n) => { + SCLogDebug!("Notify: {:?}", n); + }, + // XXX CertificateRequest + // XXX Certificate + // XXX Authentication + // XXX TSi + // XXX TSr + // XXX IDr + _ => { + SCLogInfo!("Unknown payload content {:?}", payload.content); + }, + } + self.connection_state = self.connection_state.advance(payload); + }; + }, + e @ _ => SCLogInfo!("parse_ikev2_payload_with_type: {:?}",e), + } + self.transactions.push(tx); + 1 + }, + IResult::Incomplete(_) => { + SCLogDebug!("Insufficient data while parsing IKEV2 data"); + self.set_event(IKEV2Event::MalformedData); + -1 + }, + IResult::Error(_) => { + SCLogDebug!("Error while parsing IKEV2 data"); + self.set_event(IKEV2Event::MalformedData); + -1 + }, + } + } + + fn free(&mut self) { + // All transactions are freed when the `transactions` object is freed. + // But let's be explicit + self.transactions.clear(); + } + + fn new_tx(&mut self) -> IKEV2Transaction { + self.tx_id += 1; + IKEV2Transaction::new(self.tx_id) + } + + fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&IKEV2Transaction> { + self.transactions.iter().find(|&tx| tx.id == tx_id + 1) + } + + fn free_tx(&mut self, tx_id: u64) { + let tx = self.transactions.iter().position(|ref tx| tx.id == tx_id + 1); + debug_assert!(tx != None); + if let Some(idx) = tx { + let _ = self.transactions.remove(idx); + } + } + + /// Set an event. The event is set on the most recent transaction. + fn set_event(&mut self, event: IKEV2Event) { + if let Some(tx) = self.transactions.last_mut() { + let ev = event as u8; + core::sc_app_layer_decoder_events_set_event_raw(&mut tx.events, ev); + self.events += 1; + } + } + + fn add_proposals(&mut self, prop: &Vec, direction: u8) { + for ref p in prop { + let transforms : Vec = p.transforms.iter().map(|x| x.into()).collect(); + // Rule 1: warn on weak or unknown transforms + for xform in &transforms { + match *xform { + IkeV2Transform::Encryption(ref enc) => { + match *enc { + IkeTransformEncType::ENCR_DES_IV64 | + IkeTransformEncType::ENCR_DES | + IkeTransformEncType::ENCR_3DES | + IkeTransformEncType::ENCR_RC5 | + IkeTransformEncType::ENCR_IDEA | + IkeTransformEncType::ENCR_CAST | + IkeTransformEncType::ENCR_BLOWFISH | + IkeTransformEncType::ENCR_3IDEA | + IkeTransformEncType::ENCR_DES_IV32 | + IkeTransformEncType::ENCR_NULL => { + SCLogDebug!("Weak Encryption: {:?}", enc); + // XXX send event only if direction == STREAM_TOCLIENT ? + self.set_event(IKEV2Event::WeakCryptoEnc); + }, + _ => (), + } + }, + IkeV2Transform::PRF(ref prf) => { + match *prf { + IkeTransformPRFType::PRF_NULL => { + SCLogDebug!("'Null' PRF transform proposed"); + self.set_event(IKEV2Event::InvalidProposal); + }, + IkeTransformPRFType::PRF_HMAC_MD5 | + IkeTransformPRFType::PRF_HMAC_SHA1 => { + SCLogDebug!("Weak PRF: {:?}", prf); + self.set_event(IKEV2Event::WeakCryptoPRF); + }, + _ => (), + } + }, + IkeV2Transform::Auth(ref auth) => { + match *auth { + IkeTransformAuthType::NONE => { + // Note: this could be expected with an AEAD encription alg. + // See rule 4 + () + }, + IkeTransformAuthType::AUTH_HMAC_MD5_96 | + IkeTransformAuthType::AUTH_HMAC_SHA1_96 | + IkeTransformAuthType::AUTH_DES_MAC | + IkeTransformAuthType::AUTH_KPDK_MD5 | + IkeTransformAuthType::AUTH_AES_XCBC_96 | + IkeTransformAuthType::AUTH_HMAC_MD5_128 | + IkeTransformAuthType::AUTH_HMAC_SHA1_160 => { + SCLogDebug!("Weak auth: {:?}", auth); + self.set_event(IKEV2Event::WeakCryptoAuth); + }, + _ => (), + } + }, + IkeV2Transform::DH(ref dh) => { + match *dh { + IkeTransformDHType::None => { + SCLogDebug!("'None' DH transform proposed"); + self.set_event(IKEV2Event::InvalidProposal); + }, + IkeTransformDHType::Modp768 | + IkeTransformDHType::Modp1024 | + IkeTransformDHType::Modp1024s160 | + IkeTransformDHType::Modp1536 => { + SCLogDebug!("Weak DH: {:?}", dh); + self.set_event(IKEV2Event::WeakCryptoDH); + }, + _ => (), + } + }, + IkeV2Transform::Unknown(tx_type,tx_id) => { + SCLogDebug!("Unknown proposal: type={:?}, id={}", tx_type, tx_id); + self.set_event(IKEV2Event::UnknownProposal); + }, + _ => (), + } + } + // Rule 2: check if no DH was proposed + if ! transforms.iter().any(|x| { + match *x { + IkeV2Transform::DH(_) => true, + _ => false + } + }) + { + SCLogDebug!("No DH transform found"); + self.set_event(IKEV2Event::WeakCryptoNoDH); + } + // Rule 3: check if proposing AH ([RFC7296] section 3.3.1) + if p.protocol_id == ProtocolID::AH { + SCLogDebug!("Proposal uses protocol AH - no confidentiality"); + self.set_event(IKEV2Event::NoEncryption); + } + // Rule 4: lack of integrity is accepted only if using an AEAD proposal + // Look if no auth was proposed, including if proposal is Auth::None + if ! transforms.iter().any(|x| { + match *x { + IkeV2Transform::Auth(IkeTransformAuthType::NONE) => false, + IkeV2Transform::Auth(_) => true, + _ => false, + } + }) + { + if ! transforms.iter().any(|x| { + match *x { + IkeV2Transform::Encryption(ref enc) => enc.is_aead(), + _ => false + } + }) { + SCLogDebug!("No integrity transform found"); + self.set_event(IKEV2Event::WeakCryptoNoAuth); + } + } + // Finally + if direction == STREAM_TOCLIENT { + transforms.iter().for_each(|t| + match *t { + IkeV2Transform::Encryption(ref e) => self.alg_enc = *e, + IkeV2Transform::Auth(ref a) => self.alg_auth = *a, + IkeV2Transform::PRF(ref p) => self.alg_prf = *p, + IkeV2Transform::DH(ref dh) => self.alg_dh = *dh, + IkeV2Transform::ESN(ref e) => self.alg_esn = *e, + _ => (), + }); + SCLogDebug!("Selected transforms: {:?}", transforms); + self.server_transforms.push(transforms); + } else { + SCLogDebug!("Proposed transforms: {:?}", transforms); + self.client_transforms.push(transforms); + } + } + } +} + +impl IKEV2Transaction { + pub fn new(id: u64) -> IKEV2Transaction { + IKEV2Transaction { + xid: 0, + hdr: IkeV2Header { + init_spi: 0, + resp_spi: 0, + next_payload: IkePayloadType::NoNextPayload, + maj_ver: 0, + min_ver: 0, + exch_type: IkeExchangeType(0), + flags: 0, + msg_id: 0, + length: 0, + }, + id: id, + de_state: None, + events: std::ptr::null_mut(), + logged: applayer::LoggerFlags::new(), + } + } + + fn free(&mut self) { + if self.events != std::ptr::null_mut() { + core::sc_app_layer_decoder_events_free_events(&mut self.events); + } + } +} + +impl Drop for IKEV2Transaction { + fn drop(&mut self) { + self.free(); + } +} + + + + + + +/// Returns *mut IKEV2State +#[no_mangle] +pub extern "C" fn rs_ikev2_state_new() -> *mut libc::c_void { + let state = IKEV2State::new(); + let boxed = Box::new(state); + return unsafe{std::mem::transmute(boxed)}; +} + +/// Params: +/// - state: *mut IKEV2State as void pointer +#[no_mangle] +pub extern "C" fn rs_ikev2_state_free(state: *mut libc::c_void) { + // Just unbox... + let mut ikev2_state: Box = unsafe{std::mem::transmute(state)}; + ikev2_state.free(); +} + +#[no_mangle] +pub extern "C" fn rs_ikev2_parse_request(_flow: *const core::Flow, + state: *mut libc::c_void, + _pstate: *mut libc::c_void, + input: *const libc::uint8_t, + input_len: u32, + _data: *const libc::c_void) -> i8 { + let buf = build_slice!(input,input_len as usize); + let state = cast_pointer!(state,IKEV2State); + state.parse(buf, STREAM_TOSERVER) +} + +#[no_mangle] +pub extern "C" fn rs_ikev2_parse_response(_flow: *const core::Flow, + state: *mut libc::c_void, + pstate: *mut libc::c_void, + input: *const libc::uint8_t, + input_len: u32, + _data: *const libc::c_void) -> i8 { + let buf = build_slice!(input,input_len as usize); + let state = cast_pointer!(state,IKEV2State); + let res = state.parse(buf, STREAM_TOCLIENT); + if state.connection_state == IKEV2ConnectionState::ParsingDone { + unsafe{ + AppLayerParserStateSetFlag(pstate, APP_LAYER_PARSER_NO_INSPECTION | + APP_LAYER_PARSER_NO_REASSEMBLY | + APP_LAYER_PARSER_BYPASS_READY) + }; + } + res +} + +#[no_mangle] +pub extern "C" fn rs_ikev2_state_get_tx(state: *mut libc::c_void, + tx_id: libc::uint64_t) + -> *mut libc::c_void +{ + let state = cast_pointer!(state,IKEV2State); + match state.get_tx_by_id(tx_id) { + Some(tx) => unsafe{std::mem::transmute(tx)}, + None => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub extern "C" fn rs_ikev2_state_get_tx_count(state: *mut libc::c_void) + -> libc::uint64_t +{ + let state = cast_pointer!(state,IKEV2State); + state.tx_id +} + +#[no_mangle] +pub extern "C" fn rs_ikev2_state_tx_free(state: *mut libc::c_void, + tx_id: libc::uint64_t) +{ + let state = cast_pointer!(state,IKEV2State); + state.free_tx(tx_id); +} + +#[no_mangle] +pub extern "C" fn rs_ikev2_state_progress_completion_status( + _direction: libc::uint8_t) + -> libc::c_int +{ + return 1; +} + +#[no_mangle] +pub extern "C" fn rs_ikev2_tx_get_alstate_progress(_tx: *mut libc::c_void, + _direction: libc::uint8_t) + -> libc::c_int +{ + 1 +} + + + + + +#[no_mangle] +pub extern "C" fn rs_ikev2_tx_set_logged(_state: *mut libc::c_void, + tx: *mut libc::c_void, + logged: libc::uint32_t) +{ + let tx = cast_pointer!(tx,IKEV2Transaction); + tx.logged.set(logged); +} + +#[no_mangle] +pub extern "C" fn rs_ikev2_tx_get_logged(_state: *mut libc::c_void, + tx: *mut libc::c_void) + -> u32 +{ + let tx = cast_pointer!(tx,IKEV2Transaction); + return tx.logged.get(); +} + + +#[no_mangle] +pub extern "C" fn rs_ikev2_state_set_tx_detect_state( + tx: *mut libc::c_void, + de_state: &mut core::DetectEngineState) -> libc::c_int +{ + let tx = cast_pointer!(tx,IKEV2Transaction); + tx.de_state = Some(de_state); + 0 +} + +#[no_mangle] +pub extern "C" fn rs_ikev2_state_get_tx_detect_state( + tx: *mut libc::c_void) + -> *mut core::DetectEngineState +{ + let tx = cast_pointer!(tx,IKEV2Transaction); + match tx.de_state { + Some(ds) => ds, + None => std::ptr::null_mut(), + } +} + + +#[no_mangle] +pub extern "C" fn rs_ikev2_state_get_events(state: *mut libc::c_void, + tx_id: libc::uint64_t) + -> *mut core::AppLayerDecoderEvents +{ + let state = cast_pointer!(state,IKEV2State); + match state.get_tx_by_id(tx_id) { + Some(tx) => tx.events, + _ => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub extern "C" fn rs_ikev2_state_get_event_info(event_name: *const libc::c_char, + event_id: *mut libc::c_int, + event_type: *mut core::AppLayerEventType) + -> libc::c_int +{ + if event_name == std::ptr::null() { return -1; } + let c_event_name: &CStr = unsafe { CStr::from_ptr(event_name) }; + let event = match c_event_name.to_str() { + Ok(s) => { + match s { + "malformed_data" => IKEV2Event::MalformedData as i32, + "no_encryption" => IKEV2Event::NoEncryption as i32, + "weak_crypto_enc" => IKEV2Event::WeakCryptoEnc as i32, + "weak_crypto_prf" => IKEV2Event::WeakCryptoPRF as i32, + "weak_crypto_auth" => IKEV2Event::WeakCryptoAuth as i32, + "weak_crypto_dh" => IKEV2Event::WeakCryptoDH as i32, + "weak_crypto_nodh" => IKEV2Event::WeakCryptoNoDH as i32, + "weak_crypto_noauth" => IKEV2Event::WeakCryptoNoAuth as i32, + "invalid_proposal" => IKEV2Event::InvalidProposal as i32, + "unknown_proposal" => IKEV2Event::UnknownProposal as i32, + _ => -1, // unknown event + } + }, + Err(_) => -1, // UTF-8 conversion failed + }; + unsafe{ + *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION; + *event_id = event as libc::c_int; + }; + 0 +} + + +static mut ALPROTO_IKEV2 : AppProto = ALPROTO_UNKNOWN; + +#[no_mangle] +pub extern "C" fn rs_ikev2_probing_parser(_flow: *const Flow, input:*const libc::uint8_t, input_len: u32, _offset: *const u32) -> AppProto { + let slice = build_slice!(input,input_len as usize); + let alproto = unsafe{ ALPROTO_IKEV2 }; + match parse_ikev2_header(slice) { + IResult::Done(_, ref hdr) => { + if hdr.maj_ver != 2 || hdr.min_ver != 0 { + SCLogDebug!("ipsec_probe: could be ipsec, but with unsupported/invalid version {}.{}", + hdr.maj_ver, hdr.min_ver); + return unsafe{ALPROTO_FAILED}; + } + if hdr.exch_type.0 < 34 || hdr.exch_type.0 > 37 { + SCLogDebug!("ipsec_probe: could be ipsec, but with unsupported/invalid exchange type {}", + hdr.exch_type.0); + return unsafe{ALPROTO_FAILED}; + } + if hdr.length as usize != slice.len() { + SCLogDebug!("ipsec_probe: could be ipsec, but length does not match"); + return unsafe{ALPROTO_FAILED}; + } + return alproto; + }, + IResult::Incomplete(_) => { + return ALPROTO_UNKNOWN; + }, + IResult::Error(_) => { + return unsafe{ALPROTO_FAILED}; + }, + } +} + +const PARSER_NAME : &'static [u8] = b"ikev2\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_register_ikev2_parser() { + let default_port = CString::new("500").unwrap(); + let parser = RustParser { + name : PARSER_NAME.as_ptr() as *const libc::c_char, + default_port : default_port.as_ptr(), + ipproto : libc::IPPROTO_UDP, + probe_ts : rs_ikev2_probing_parser, + probe_tc : rs_ikev2_probing_parser, + min_depth : 0, + max_depth : 16, + state_new : rs_ikev2_state_new, + state_free : rs_ikev2_state_free, + tx_free : rs_ikev2_state_tx_free, + parse_ts : rs_ikev2_parse_request, + parse_tc : rs_ikev2_parse_response, + get_tx_count : rs_ikev2_state_get_tx_count, + get_tx : rs_ikev2_state_get_tx, + tx_get_comp_st : rs_ikev2_state_progress_completion_status, + tx_get_progress : rs_ikev2_tx_get_alstate_progress, + get_tx_logged : Some(rs_ikev2_tx_get_logged), + set_tx_logged : Some(rs_ikev2_tx_set_logged), + get_de_state : rs_ikev2_state_get_tx_detect_state, + set_de_state : rs_ikev2_state_set_tx_detect_state, + get_events : Some(rs_ikev2_state_get_events), + get_eventinfo : Some(rs_ikev2_state_get_event_info), + localstorage_new : None, + localstorage_free : None, + get_tx_mpm_id : None, + set_tx_mpm_id : None, + get_files : None, + }; + + let ip_proto_str = CString::new("udp").unwrap(); + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + // store the allocated ID for the probe function + ALPROTO_IKEV2 = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + } else { + SCLogDebug!("Protocol detecter and parser disabled for IKEV2."); + } +} + + +#[cfg(test)] +mod tests { + use super::IKEV2State; + + #[test] + fn test_ikev2_parse_request_valid() { + // A UDP IKEV2 v4 request, in client mode + const REQ : &[u8] = &[ + 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x20, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x57, 0xab, 0xc3, 0x4a, 0x5f, 0x2c, 0xfe + ]; + + let mut state = IKEV2State::new(); + assert_eq!(1, state.parse(REQ, 0)); + } +} diff --git a/rust/src/ikev2/mod.rs b/rust/src/ikev2/mod.rs new file mode 100644 index 0000000000..0ca702b50f --- /dev/null +++ b/rust/src/ikev2/mod.rs @@ -0,0 +1,23 @@ +/* Copyright (C) 2017-2018 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Pierre Chifflier + +extern crate ipsec_parser; + +pub mod ikev2; +pub mod state; diff --git a/rust/src/ikev2/state.rs b/rust/src/ikev2/state.rs new file mode 100644 index 0000000000..22f074b05c --- /dev/null +++ b/rust/src/ikev2/state.rs @@ -0,0 +1,57 @@ +/* Copyright (C) 2018 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Pierre Chifflier + +extern crate ipsec_parser; +use self::ipsec_parser::*; + +#[derive(Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum IKEV2ConnectionState { + Init, + InitSASent, + InitKESent, + InitNonceSent, + RespSASent, + RespKESent, + RespNonceSent, + RespCertReqSent, + + ParsingDone, + + Invalid, +} + +impl IKEV2ConnectionState { + pub fn advance(&self, payload: &IkeV2Payload) -> IKEV2ConnectionState { + use self::IKEV2ConnectionState::*; + match (self, &payload.content) { + (&Init, &IkeV2PayloadContent::SA(_)) => InitSASent, + (&InitSASent, &IkeV2PayloadContent::KE(_)) => InitKESent, + (&InitKESent, &IkeV2PayloadContent::Nonce(_)) => InitNonceSent, + (&InitNonceSent, &IkeV2PayloadContent::SA(_)) => RespSASent, + (&RespSASent, &IkeV2PayloadContent::KE(_)) => RespKESent, + (&RespKESent, &IkeV2PayloadContent::Nonce(_)) => ParsingDone, // RespNonceSent, + (&RespNonceSent, &IkeV2PayloadContent::CertificateRequest(_)) => ParsingDone, // RespCertReqSent, + (&ParsingDone,_) => self.clone(), + (_, &IkeV2PayloadContent::Notify(_)) => self.clone(), + (_, &IkeV2PayloadContent::Dummy) => self.clone(), + (_,_) => Invalid, + } + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 0ecc7829a0..d3bacd3528 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -50,6 +50,9 @@ pub mod nfs; pub mod ftp; pub mod smb; +#[cfg(feature = "experimental")] +pub mod ikev2; + #[cfg(feature = "experimental")] pub mod ntp; pub mod tftp; diff --git a/src/Makefile.am b/src/Makefile.am index 485da7e273..a35807db8e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -46,6 +46,7 @@ app-layer-nfs-udp.c app-layer-nfs-udp.h \ app-layer-ntp.c app-layer-ntp.h \ app-layer-register.c app-layer-register.h \ app-layer-tftp.c app-layer-tftp.h \ +app-layer-ikev2.c app-layer-ikev2.h \ app-layer-template.c app-layer-template.h \ app-layer-ssh.c app-layer-ssh.h \ app-layer-ssl.c app-layer-ssl.h \ diff --git a/src/app-layer-detect-proto.c b/src/app-layer-detect-proto.c index 3f91ffef32..2cfeaaa92a 100644 --- a/src/app-layer-detect-proto.c +++ b/src/app-layer-detect-proto.c @@ -725,6 +725,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar printf(" alproto: ALPROTO_NTP\n"); else if (pp_pe->alproto == ALPROTO_TFTP) printf(" alproto: ALPROTO_TFTP\n"); + else if (pp_pe->alproto == ALPROTO_IKEV2) + printf(" alproto: ALPROTO_IKEV2\n"); else if (pp_pe->alproto == ALPROTO_TEMPLATE) printf(" alproto: ALPROTO_TEMPLATE\n"); else if (pp_pe->alproto == ALPROTO_DNP3) @@ -790,6 +792,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar printf(" alproto: ALPROTO_NTP\n"); else if (pp_pe->alproto == ALPROTO_TFTP) printf(" alproto: ALPROTO_TFTP\n"); + else if (pp_pe->alproto == ALPROTO_IKEV2) + printf(" alproto: ALPROTO_IKEV2\n"); else if (pp_pe->alproto == ALPROTO_TEMPLATE) printf(" alproto: ALPROTO_TEMPLATE\n"); else if (pp_pe->alproto == ALPROTO_DNP3) diff --git a/src/app-layer-ikev2.c b/src/app-layer-ikev2.c new file mode 100644 index 0000000000..884b3a50c2 --- /dev/null +++ b/src/app-layer-ikev2.c @@ -0,0 +1,66 @@ +/* Copyright (C) 2015 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Pierre Chifflier + * + * Parser for IKEv2 application layer running on UDP port 500. + */ + +#include "suricata-common.h" +#include "stream.h" +#include "conf.h" + +#include "util-unittest.h" + +#include "app-layer-detect-proto.h" +#include "app-layer-parser.h" + +#include "app-layer-ikev2.h" + +#if defined(HAVE_RUST) && defined(HAVE_RUST_EXTERNAL) + +#include "rust-ikev2-ikev2-gen.h" + +void RegisterIKEV2Parsers(void) +{ + rs_register_ikev2_parser(); + +#ifdef UNITTESTS + AppLayerParserRegisterProtocolUnittests(IPPROTO_UDP, ALPROTO_IKEV2, + IKEV2ParserRegisterTests); +#endif +} + +#ifdef UNITTESTS +#endif + +void IKEV2ParserRegisterTests(void) +{ +#ifdef UNITTESTS +#endif +} + +#else /* HAVE_RUST */ + +void RegisterIKEV2Parsers(void) +{ +} + +#endif /* HAVE_RUST */ diff --git a/src/app-layer-ikev2.h b/src/app-layer-ikev2.h new file mode 100644 index 0000000000..37fa7860a1 --- /dev/null +++ b/src/app-layer-ikev2.h @@ -0,0 +1,34 @@ +/* Copyright (C) 2015 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Pierre Chifflier + */ + +#ifndef __APP_LAYER_IKEV2_H__ +#define __APP_LAYER_IKEV2_H__ + +void RegisterIKEV2Parsers(void); +void IKEV2ParserRegisterTests(void); + +/** Opaque Rust types. */ +typedef struct IKEV2State_ IKEV2State; +typedef struct IKEV2Transaction_ IKEV2Transaction; + +#endif /* __APP_LAYER_IKEV2_H__ */ diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index ef054e4939..8707ac270f 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -64,6 +64,7 @@ #include "app-layer-nfs-udp.h" #include "app-layer-ntp.h" #include "app-layer-tftp.h" +#include "app-layer-ikev2.h" #include "app-layer-template.h" #include "conf.h" @@ -1450,6 +1451,7 @@ void AppLayerParserRegisterProtocolParsers(void) RegisterNFSUDPParsers(); RegisterNTPParsers(); RegisterTFTPParsers(); + RegisterIKEV2Parsers(); RegisterTemplateParsers(); /** IMAP */ diff --git a/src/app-layer-protos.c b/src/app-layer-protos.c index 9c5cd50d19..14dc1f704a 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -93,6 +93,9 @@ const char *AppProtoToString(AppProto alproto) case ALPROTO_TFTP: proto_name = "tftp"; break; + case ALPROTO_IKEV2: + proto_name = "ikev2"; + break; case ALPROTO_TEMPLATE: proto_name = "template"; break; @@ -132,6 +135,7 @@ AppProto StringToAppProto(const char *proto_name) if (strcmp(proto_name,"dnp3")==0) return ALPROTO_DNP3; if (strcmp(proto_name,"nfs")==0) return ALPROTO_NFS; if (strcmp(proto_name,"ntp")==0) return ALPROTO_NTP; + if (strcmp(proto_name,"ikev2")==0) return ALPROTO_IKEV2; if (strcmp(proto_name,"template")==0) return ALPROTO_TEMPLATE; if (strcmp(proto_name,"failed")==0) return ALPROTO_FAILED; diff --git a/src/app-layer-protos.h b/src/app-layer-protos.h index ddf9578675..3293e71d25 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -48,6 +48,7 @@ enum AppProtoEnum { ALPROTO_NTP, ALPROTO_FTPDATA, ALPROTO_TFTP, + ALPROTO_IKEV2, ALPROTO_TEMPLATE, /* used by the probing parser when alproto detection fails diff --git a/suricata.yaml.in b/suricata.yaml.in index 724faaac77..fc52dedba9 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -268,6 +268,7 @@ outputs: @rust_config_comment@- nfs @rust_config_comment@- smb @rust_config_comment@- tftp + @rust_config_comment@- ikev2 - ssh - stats: totals: yes # stats for all threads merged together @@ -774,6 +775,8 @@ pcap-file: # "detection-only" enables protocol detection only (parser disabled). app-layer: protocols: + ikev2: + enabled: yes tls: enabled: yes detection-ports: