]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
Add new parser: IKEv2
authorPierre Chifflier <chifflier@wzdftpd.net>
Wed, 31 Jan 2018 07:13:41 +0000 (08:13 +0100)
committerVictor Julien <victor@inliniac.net>
Mon, 26 Mar 2018 09:04:30 +0000 (11:04 +0200)
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.

14 files changed:
rust/Cargo.toml.in
rust/gen-c-headers.py
rust/src/ikev2/ikev2.rs [new file with mode: 0644]
rust/src/ikev2/mod.rs [new file with mode: 0644]
rust/src/ikev2/state.rs [new file with mode: 0644]
rust/src/lib.rs
src/Makefile.am
src/app-layer-detect-proto.c
src/app-layer-ikev2.c [new file with mode: 0644]
src/app-layer-ikev2.h [new file with mode: 0644]
src/app-layer-parser.c
src/app-layer-protos.c
src/app-layer-protos.h
suricata.yaml.in

index c56f8a0b5f883f67ba31d0013ed6f7a32c088cad..53e5a2630fbcbef57787ff5857333d40ba8bd5c0 100644 (file)
@@ -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 }
index d0b737222f6db0769f3504f58c5fe95c5fe47181..d5b61bd2f8aa0106acca6e41dcce23e09416c05e 100755 (executable)
@@ -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 (file)
index 0000000..4fa5a25
--- /dev/null
@@ -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  <chifflier@wzdftpd.net>
+
+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<IKEV2Transaction>,
+
+    /// 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<Vec<IkeV2Transform>>,
+
+    /// The transforms selected by the responder
+    pub server_transforms : Vec<Vec<IkeV2Transform>>,
+
+    /// 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<IkeV2Proposal>, direction: u8) {
+        for ref p in prop {
+            let transforms : Vec<IkeV2Transform> = 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<IKEV2State> = 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 (file)
index 0000000..0ca702b
--- /dev/null
@@ -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  <chifflier@wzdftpd.net>
+
+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 (file)
index 0000000..22f074b
--- /dev/null
@@ -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  <chifflier@wzdftpd.net>
+
+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,
+        }
+    }
+}
index 0ecc7829a0dbae70b3efc5a482589faf95277985..d3bacd3528dbe4900705070d273a15201b31158c 100644 (file)
@@ -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;
index 485da7e273ecb541769d521ef47e4d8f3c882c8c..a35807db8e9f865430d5e39582cdb9312e4a5dcf 100644 (file)
@@ -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 \
index 3f91ffef3286e0aa96874124b8a7e16551f099cd..2cfeaaa92adda110c58d9917cc0dd116185f254a 100644 (file)
@@ -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 (file)
index 0000000..884b3a5
--- /dev/null
@@ -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 <chifflier@wzdftpd.net>
+ *
+ * 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 (file)
index 0000000..37fa786
--- /dev/null
@@ -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 <chifflier@wzdftpd.net>
+ */
+
+#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__ */
index ef054e49395c12ee6e9023e46b153e0d7874d245..8707ac270faecf037a5ff8bdd528d7569c7a7b6f 100644 (file)
@@ -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 */
index 9c5cd50d1969ac19b45e9f73123085cf9135dd64..14dc1f704ae86f129364664f78840145f76122ef 100644 (file)
@@ -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;
 
index ddf95786754ef125814e89d40ef7d411cb3a4d9f..3293e71d25e0d54caee361bffc6708a55a81b143 100644 (file)
@@ -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
index 724faaac77a6d7e3b4b66585fdd584404a757c65..fc52dedba9d64ff8d7ae12aaa787f68ca4a76da4 100644 (file)
@@ -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: