From e2dbdd7fd5077e7e50862096fb5889a9cafa49a5 Mon Sep 17 00:00:00 2001 From: Sascha Steinbiss Date: Sun, 3 Jan 2021 23:42:24 +0100 Subject: [PATCH] ikev1: add ikev1 parser --- rust/src/ike/detect.rs | 242 ++++++ rust/src/ike/ike.rs | 870 ++++++++----------- rust/src/ike/ikev1.rs | 162 ++++ rust/src/ike/ikev2.rs | 340 ++++++++ rust/src/ike/log.rs | 67 -- rust/src/ike/logger.rs | 227 +++++ rust/src/ike/mod.rs | 9 +- rust/src/ike/parser.rs | 739 ++++++++++++++++ rust/src/ike/state.rs | 57 -- src/Makefile.am | 16 + src/app-layer-ike.c | 155 +++- src/app-layer-ike.h | 4 +- src/detect-engine-register.c | 17 + src/detect-engine-register.h | 10 + src/detect-ike-chosen-sa.c | 283 ++++++ src/detect-ike-chosen-sa.h | 29 + src/detect-ike-exch-type.c | 156 ++++ src/detect-ike-exch-type.h | 29 + src/detect-ike-key-exchange-payload-length.c | 162 ++++ src/detect-ike-key-exchange-payload-length.h | 28 + src/detect-ike-key-exchange-payload.c | 120 +++ src/detect-ike-key-exchange-payload.h | 28 + src/detect-ike-nonce-payload-length.c | 156 ++++ src/detect-ike-nonce-payload-length.h | 28 + src/detect-ike-nonce-payload.c | 119 +++ src/detect-ike-nonce-payload.h | 28 + src/detect-ike-spi.c | 172 ++++ src/detect-ike-spi.h | 28 + src/detect-ike-vendor.c | 211 +++++ src/detect-ike-vendor.h | 29 + src/output-json-ike.c | 29 +- 31 files changed, 3888 insertions(+), 662 deletions(-) create mode 100644 rust/src/ike/detect.rs create mode 100644 rust/src/ike/ikev1.rs create mode 100644 rust/src/ike/ikev2.rs delete mode 100644 rust/src/ike/log.rs create mode 100644 rust/src/ike/logger.rs create mode 100644 rust/src/ike/parser.rs delete mode 100644 rust/src/ike/state.rs create mode 100644 src/detect-ike-chosen-sa.c create mode 100644 src/detect-ike-chosen-sa.h create mode 100644 src/detect-ike-exch-type.c create mode 100644 src/detect-ike-exch-type.h create mode 100644 src/detect-ike-key-exchange-payload-length.c create mode 100644 src/detect-ike-key-exchange-payload-length.h create mode 100644 src/detect-ike-key-exchange-payload.c create mode 100644 src/detect-ike-key-exchange-payload.h create mode 100644 src/detect-ike-nonce-payload-length.c create mode 100644 src/detect-ike-nonce-payload-length.h create mode 100644 src/detect-ike-nonce-payload.c create mode 100644 src/detect-ike-nonce-payload.h create mode 100644 src/detect-ike-spi.c create mode 100644 src/detect-ike-spi.h create mode 100644 src/detect-ike-vendor.c create mode 100644 src/detect-ike-vendor.h diff --git a/rust/src/ike/detect.rs b/rust/src/ike/detect.rs new file mode 100644 index 0000000000..787032bde4 --- /dev/null +++ b/rust/src/ike/detect.rs @@ -0,0 +1,242 @@ +/* Copyright (C) 2020 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. + */ + +// Author: Frank Honza + +use super::ipsec_parser::IkeV2Transform; +use crate::ike::ike::*; +use std::ffi::CStr; +use std::ptr; + +#[no_mangle] +pub extern "C" fn rs_ike_state_get_exch_type(tx: &mut IKETransaction, exch_type: *mut u8) -> u8 { + debug_validate_bug_on!(exch_type == std::ptr::null_mut()); + + if tx.ike_version == 1 { + if let Some(r) = tx.hdr.ikev1_header.exchange_type { + unsafe { + *exch_type = r; + } + return 1; + } + } else if tx.ike_version == 2 { + unsafe { + *exch_type = tx.hdr.ikev2_header.exch_type.0; + } + return 1; + } + + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_ike_state_get_spi_initiator( + tx: &mut IKETransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + debug_validate_bug_on!(buffer == std::ptr::null_mut() || buffer_len == std::ptr::null_mut()); + + unsafe { + *buffer = tx.hdr.spi_initiator.as_ptr(); + *buffer_len = tx.hdr.spi_initiator.len() as u32; + } + return 1; +} + +#[no_mangle] +pub extern "C" fn rs_ike_state_get_spi_responder( + tx: &mut IKETransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + debug_validate_bug_on!(buffer == std::ptr::null_mut() || buffer_len == std::ptr::null_mut()); + + unsafe { + *buffer = tx.hdr.spi_responder.as_ptr(); + *buffer_len = tx.hdr.spi_responder.len() as u32; + } + return 1; +} + +#[no_mangle] +pub extern "C" fn rs_ike_state_get_nonce( + tx: &mut IKETransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + debug_validate_bug_on!(buffer == std::ptr::null_mut() || buffer_len == std::ptr::null_mut()); + + if tx.ike_version == 1 && !tx.hdr.ikev1_header.nonce.is_empty() { + let p = &tx.hdr.ikev1_header.nonce; + unsafe { + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + } + return 1; + } + + unsafe { + *buffer = ptr::null(); + *buffer_len = 0; + } + + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_ike_state_get_key_exchange( + tx: &mut IKETransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + debug_validate_bug_on!(buffer == std::ptr::null_mut() || buffer_len == std::ptr::null_mut()); + + if tx.ike_version == 1 && !tx.hdr.ikev1_header.key_exchange.is_empty() { + let p = &tx.hdr.ikev1_header.key_exchange; + unsafe { + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + } + return 1; + } + + unsafe { + *buffer = ptr::null(); + *buffer_len = 0; + } + + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_ike_tx_get_vendor( + tx: &IKETransaction, i: u16, buf: *mut *const u8, len: *mut u32, +) -> u8 { + if tx.ike_version == 1 && i < tx.hdr.ikev1_header.vendor_ids.len() as u16 { + unsafe { + *len = tx.hdr.ikev1_header.vendor_ids[i as usize].len() as u32; + *buf = tx.hdr.ikev1_header.vendor_ids[i as usize].as_ptr(); + } + return 1; + } + + unsafe { + *buf = ptr::null(); + *len = 0; + } + + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_ike_state_get_sa_attribute( + tx: &mut IKETransaction, sa_type: *const std::os::raw::c_char, value: *mut u32, +) -> u8 { + debug_validate_bug_on!(value == std::ptr::null_mut()); + let mut ret_val = 0; + let mut ret_code = 0; + let sa_type_s: Result<_,_>; + + unsafe { + sa_type_s = CStr::from_ptr(sa_type).to_str() + } + SCLogInfo!("{:#?}", sa_type_s); + + if let Ok(sa) = sa_type_s { + if tx.ike_version == 1 { + 'outer1: for (i, server_transform) in tx.hdr.ikev1_transforms.iter().enumerate() { + if i >= 1 { + debug_validate_bug_on!(true); + break; + } + for attr in server_transform { + if attr.attribute_type.to_string() == sa { + if let Some(numeric_value) = attr.numeric_value { + ret_val = numeric_value; + ret_code = 1; + break 'outer1; + } + } + } + } + } else if tx.ike_version == 2 { + 'outer2: for server_transform in tx.hdr.ikev2_transforms.iter() { + for attr in server_transform { + match attr { + IkeV2Transform::Encryption(e) => { + if sa == "alg_enc" { + ret_val = e.0 as u32; + ret_code = 1; + break 'outer2; + } + } + IkeV2Transform::Auth(e) => { + if sa == "alg_auth" { + ret_val = e.0 as u32; + ret_code = 1; + break 'outer2; + } + } + IkeV2Transform::PRF(ref e) => { + if sa == "alg_prf" { + ret_val = e.0 as u32; + ret_code = 1; + break 'outer2; + } + } + IkeV2Transform::DH(ref e) => { + if sa == "alg_dh" { + ret_val = e.0 as u32; + ret_code = 1; + break 'outer2; + } + } + _ => (), + } + } + } + } + } + + unsafe { + *value = ret_val; + } + return ret_code; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ike_state_get_key_exchange_payload_length( + tx: &mut IKETransaction, value: *mut u32, +) -> u8 { + debug_validate_bug_on!(value == std::ptr::null_mut()); + + if tx.ike_version == 1 && !tx.hdr.ikev1_header.key_exchange.is_empty() { + *value = tx.hdr.ikev1_header.key_exchange.len() as u32; + return 1; + } + + *value = 0; + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ike_state_get_nonce_payload_length( + tx: &mut IKETransaction, value: *mut u32, +) -> u8 { + debug_validate_bug_on!(value == std::ptr::null_mut()); + + if tx.ike_version == 1 && !tx.hdr.ikev1_header.nonce.is_empty() { + *value = tx.hdr.ikev1_header.nonce.len() as u32; + return 1; + } + + *value = 0; + return 0; +} diff --git a/rust/src/ike/ike.rs b/rust/src/ike/ike.rs index 2cae016044..8c87f1ab78 100644 --- a/rust/src/ike/ike.rs +++ b/rust/src/ike/ike.rs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2020 Open Information Security Foundation +/* Copyright (C) 2020 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 @@ -15,17 +15,24 @@ * 02110-1301, USA. */ -// written by Pierre Chifflier +// Author: Frank Honza -use crate::ike::ipsec_parser::*; -use crate::ike::state::IKEConnectionState; -use crate::core; -use crate::core::{AppProto,Flow,ALPROTO_UNKNOWN,ALPROTO_FAILED,STREAM_TOSERVER,STREAM_TOCLIENT}; -use crate::applayer::{self, *}; -use std; -use std::ffi::{CStr,CString}; +extern crate ipsec_parser; +use self::ipsec_parser::*; +use crate::applayer; +use crate::applayer::*; +use crate::core::{ + self, AppProto, Flow, ALPROTO_FAILED, ALPROTO_UNKNOWN, STREAM_TOCLIENT, STREAM_TOSERVER, +}; +use crate::ike::ikev1::{handle_ikev1, IkeV1Header, Ikev1Container}; +use crate::ike::ikev2::{handle_ikev2, Ikev2Container}; +use crate::ike::parser::*; use nom; +use std; +use std::collections::HashSet; +use std::ffi::{CStr, CString}; +use std::mem::transmute; #[repr(u32)] pub enum IkeEvent { @@ -39,10 +46,11 @@ pub enum IkeEvent { WeakCryptoNoAuth, InvalidProposal, UnknownProposal, + PayloadExtraData, } impl IkeEvent { - fn from_i32(value: i32) -> Option { + pub fn from_i32(value: i32) -> Option { match value { 0 => Some(IkeEvent::MalformedData), 1 => Some(IkeEvent::NoEncryption), @@ -54,547 +62,402 @@ impl IkeEvent { 7 => Some(IkeEvent::WeakCryptoNoAuth), 8 => Some(IkeEvent::InvalidProposal), 9 => Some(IkeEvent::UnknownProposal), + 10 => Some(IkeEvent::PayloadExtraData), _ => None, } } } -pub struct IKEState { - /// List of transactions for this session - transactions: Vec, - - /// tx counter for assigning incrementing id's to tx's - tx_id: u64, - - /// The connection state - connection_state: IKEConnectionState, - - /// 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, +pub struct IkeHeaderWrapper { + pub spi_initiator: String, + pub spi_responder: String, + pub maj_ver: u8, + pub min_ver: u8, + pub msg_id: u32, + pub flags: u8, + pub ikev1_transforms: Vec>, + pub ikev2_transforms: Vec>, + pub ikev1_header: IkeV1Header, + pub ikev2_header: IkeV2Header, +} - /// The Diffie-Hellman group from the server KE message, if present. - pub dh_group: IkeTransformDHType, +impl IkeHeaderWrapper { + pub fn new() -> IkeHeaderWrapper { + IkeHeaderWrapper { + spi_initiator: String::new(), + spi_responder: String::new(), + maj_ver: 0, + min_ver: 0, + msg_id: 0, + flags: 0, + ikev1_transforms: Vec::new(), + ikev2_transforms: Vec::new(), + ikev1_header: IkeV1Header::default(), + ikev2_header: 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, + }, + } + } +} +#[derive(Default)] +pub struct IkePayloadWrapper { + pub ikev1_payload_types: Option>, + pub ikev2_payload_types: Vec, } -#[derive(Debug)] pub struct IKETransaction { - /// The IKE reference ID - pub xid: u64, - - pub hdr: IkeV2Header, + tx_id: u64, - pub payload_types: Vec, + pub ike_version: u8, + pub hdr: IkeHeaderWrapper, + pub payload_types: IkePayloadWrapper, pub notify_types: Vec, - /// IKEv2 errors seen during exchange + /// errors seen during exchange pub errors: u32, - /// The internal transaction id - id: u64, - - /// The detection engine state, if present + logged: LoggerFlags, de_state: Option<*mut core::DetectEngineState>, - - /// The events associated with this transaction events: *mut core::AppLayerDecoderEvents, - tx_data: applayer::AppLayerTxData, } - - -impl IKEState { - pub fn new() -> IKEState { - IKEState{ - transactions: Vec::new(), +impl IKETransaction { + pub fn new() -> IKETransaction { + IKETransaction { tx_id: 0, - connection_state: IKEConnectionState::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, + ike_version: 0, + hdr: IkeHeaderWrapper::new(), + payload_types: Default::default(), + notify_types: vec![], + logged: LoggerFlags::new(), + de_state: None, + events: std::ptr::null_mut(), + tx_data: applayer::AppLayerTxData::new(), + errors: 0, } } -} -impl IKEState { - /// Parse an IKE request message - /// - /// Returns The number of messages parsed, or -1 on error - fn parse(&mut self, i: &[u8], direction: u8) -> i32 { - match parse_ikev2_header(i) { - Ok((rem,ref hdr)) => { - if rem.len() == 0 && hdr.length == 28 { - return 1; - } - // Rule 0: check version - if hdr.maj_ver != 2 || hdr.min_ver != 0 { - self.set_event(IkeEvent::MalformedData); - return -1; - } - if hdr.init_spi == 0 { - self.set_event(IkeEvent::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(); - self.transactions.push(tx); - let mut payload_types = Vec::new(); - let mut errors = 0; - let mut notify_types = Vec::new(); - match parse_ikev2_payload_list(rem,hdr.next_payload) { - Ok((_,Ok(ref p))) => { - for payload in p { - payload_types.push(payload.hdr.next_payload_type); - match payload.content { - IkeV2PayloadContent::Dummy => (), - IkeV2PayloadContent::SA(ref prop) => { - // if hdr.flags & IKE_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); - if n.notify_type.is_error() { - errors += 1; - } - notify_types.push(n.notify_type); - }, - // XXX CertificateRequest - // XXX Certificate - // XXX Authentication - // XXX TSi - // XXX TSr - // XXX IDr - _ => { - SCLogDebug!("Unknown payload content {:?}", payload.content); - }, - } - self.connection_state = self.connection_state.advance(payload); - if let Some(tx) = self.transactions.last_mut() { - // borrow back tx to update it - tx.payload_types.append(&mut payload_types); - tx.errors = errors; - tx.notify_types.append(&mut notify_types); - } - }; - }, - e => { SCLogDebug!("parse_ike_payload_with_type: {:?}",e); () }, - } - 1 - }, - Err(nom::Err::Incomplete(_)) => { - SCLogDebug!("Insufficient data while parsing IKE data"); - self.set_event(IkeEvent::MalformedData); - -1 - }, - Err(_) => { - SCLogDebug!("Error while parsing IKE data"); - self.set_event(IkeEvent::MalformedData); - -1 - }, + pub fn free(&mut self) { + if self.events != std::ptr::null_mut() { + core::sc_app_layer_decoder_events_free_events(&mut self.events); + } + if let Some(state) = self.de_state { + core::sc_detect_engine_state_free(state); } } +} - fn free(&mut self) { - // All transactions are freed when the `transactions` object is freed. - // But let's be explicit - self.transactions.clear(); +impl Drop for IKETransaction { + fn drop(&mut self) { + self.free(); } +} - fn new_tx(&mut self) -> IKETransaction { - self.tx_id += 1; - IKETransaction::new(self.tx_id) - } +#[derive(Default)] +pub struct IKEState { + tx_id: u64, + pub transactions: Vec, - fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&IKETransaction> { - self.transactions.iter().find(|&tx| tx.id == tx_id + 1) - } + pub ikev1_container: Ikev1Container, + pub ikev2_container: Ikev2Container, +} +impl IKEState { + // Free a transaction by ID. fn free_tx(&mut self, tx_id: u64) { - let tx = self.transactions.iter().position(|ref tx| tx.id == tx_id + 1); + let tx = self + .transactions + .iter() + .position(|ref tx| tx.tx_id == tx_id + 1); debug_assert!(tx != None); if let Some(idx) = tx { let _ = self.transactions.remove(idx); } } + pub fn get_tx(&mut self, tx_id: u64) -> Option<&mut IKETransaction> { + for tx in &mut self.transactions { + if tx.tx_id == tx_id + 1 { + return Some(tx); + } + } + return None; + } + + pub fn new_tx(&mut self) -> IKETransaction { + let mut tx = IKETransaction::new(); + self.tx_id += 1; + tx.tx_id = self.tx_id; + return tx; + } + /// Set an event. The event is set on the most recent transaction. - fn set_event(&mut self, event: IkeEvent) { + pub fn set_event(&mut self, event: IkeEvent) { 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); } else { - SCLogDebug!("IKEv2: trying to set event {} on non-existing transaction", event as u32); + SCLogDebug!( + "IKE: trying to set event {} on non-existing transaction", + event as u32 + ); } } - 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(IkeEvent::WeakCryptoEnc); - }, - _ => (), - } - }, - IkeV2Transform::PRF(ref prf) => { - match *prf { - IkeTransformPRFType::PRF_NULL => { - SCLogDebug!("'Null' PRF transform proposed"); - self.set_event(IkeEvent::InvalidProposal); - }, - IkeTransformPRFType::PRF_HMAC_MD5 | - IkeTransformPRFType::PRF_HMAC_SHA1 => { - SCLogDebug!("Weak PRF: {:?}", prf); - self.set_event(IkeEvent::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(IkeEvent::WeakCryptoAuth); - }, - _ => (), - } - }, - IkeV2Transform::DH(ref dh) => { - match *dh { - IkeTransformDHType::None => { - SCLogDebug!("'None' DH transform proposed"); - self.set_event(IkeEvent::InvalidProposal); - }, - IkeTransformDHType::Modp768 | - IkeTransformDHType::Modp1024 | - IkeTransformDHType::Modp1024s160 | - IkeTransformDHType::Modp1536 => { - SCLogDebug!("Weak DH: {:?}", dh); - self.set_event(IkeEvent::WeakCryptoDH); - }, - _ => (), - } - }, - IkeV2Transform::Unknown(tx_type,tx_id) => { - SCLogDebug!("Unknown proposal: type={:?}, id={}", tx_type, tx_id); - self.set_event(IkeEvent::UnknownProposal); - }, - _ => (), + fn handle_input(&mut self, input: &[u8], direction: u8) -> AppLayerResult { + // We're not interested in empty requests. + if input.len() == 0 { + return AppLayerResult::ok(); + } + + let mut current = input; + match parse_isakmp_header(current) { + Ok((rem, isakmp_header)) => { + current = rem; + + if isakmp_header.maj_ver != 1 && isakmp_header.maj_ver != 2 { + SCLogDebug!("Unsupported ISAKMP major_version"); + return AppLayerResult::err(); } - } - // Rule 2: check if no DH was proposed - if ! transforms.iter().any(|x| { - match *x { - IkeV2Transform::DH(_) => true, - _ => false + + if isakmp_header.maj_ver == 1 { + handle_ikev1(self, current, isakmp_header, direction); + } else if isakmp_header.maj_ver == 2 { + handle_ikev2(self, current, isakmp_header, direction); + } else { + return AppLayerResult::err(); } - }) - { - SCLogDebug!("No DH transform found"); - self.set_event(IkeEvent::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(IkeEvent::NoEncryption); + return AppLayerResult::ok(); // todo either remove outer loop or check header length-field if we have completely read everything } - // 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(IkeEvent::WeakCryptoNoAuth); - } + Err(nom::Err::Incomplete(_)) => { + SCLogDebug!("Insufficient data while parsing IKE"); + return AppLayerResult::err(); } - // 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); + Err(_) => { + SCLogDebug!("Error while parsing IKE packet"); + return AppLayerResult::err(); } } } -} -impl IKETransaction { - pub fn new(id: u64) -> IKETransaction { - IKETransaction { - 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, - }, - payload_types: Vec::new(), - notify_types: Vec::new(), - errors: 0, - id: id, - de_state: None, - events: std::ptr::null_mut(), - tx_data: applayer::AppLayerTxData::new(), + fn tx_iterator( + &mut self, min_tx_id: u64, state: &mut u64, + ) -> Option<(&IKETransaction, u64, bool)> { + let mut index = *state as usize; + let len = self.transactions.len(); + + while index < len { + let tx = &self.transactions[index]; + if tx.tx_id < min_tx_id + 1 { + index += 1; + continue; + } + *state = index as u64; + + return Some((tx, tx.tx_id - 1, (len - index) > 1)); } + + return None; } +} - fn free(&mut self) { - if self.events != std::ptr::null_mut() { - core::sc_app_layer_decoder_events_free_events(&mut self.events); - } - if let Some(state) = self.de_state { - core::sc_detect_engine_state_free(state); +/// Probe to see if this input looks like a request or response. +fn probe(input: &[u8], direction: u8, rdir: *mut u8) -> bool { + match parse_isakmp_header(input) { + Ok((_, isakmp_header)) => { + if isakmp_header.maj_ver == 1 { + if isakmp_header.resp_spi == 0 && direction != STREAM_TOSERVER { + unsafe { + *rdir = STREAM_TOSERVER; + } + } + return true; + } else if isakmp_header.maj_ver == 2 { + if isakmp_header.min_ver != 0 { + SCLogDebug!( + "ipsec_probe: could be ipsec, but with unsupported/invalid version {}.{}", + isakmp_header.maj_ver, + isakmp_header.min_ver + ); + return false; + } + if isakmp_header.exch_type < 34 || isakmp_header.exch_type > 37 { + SCLogDebug!("ipsec_probe: could be ipsec, but with unsupported/invalid exchange type {}", + isakmp_header.exch_type); + return false; + } + if isakmp_header.length as usize != input.len() { + SCLogDebug!("ipsec_probe: could be ipsec, but length does not match"); + return false; + } + + if isakmp_header.resp_spi == 0 && direction != STREAM_TOSERVER { + unsafe { + *rdir = STREAM_TOSERVER; + } + } + return true; + } + + return false; } + Err(_) => return false, } } -impl Drop for IKETransaction { - fn drop(&mut self) { - self.free(); +// C exports. +export_tx_get_detect_state!(rs_ike_tx_get_detect_state, IKETransaction); +export_tx_set_detect_state!(rs_ike_tx_set_detect_state, IKETransaction); + +/// C entry point for a probing parser. +#[no_mangle] +pub extern "C" fn rs_ike_probing_parser( + _flow: *const Flow, direction: u8, input: *const u8, input_len: u32, rdir: *mut u8, +) -> AppProto { + if input_len < 28 { + // at least the ISAKMP_HEADER must be there, not ALPROTO_UNKNOWN because over UDP + return unsafe { ALPROTO_FAILED }; + } + + if input != std::ptr::null_mut() { + let slice = build_slice!(input, input_len as usize); + if probe(slice, direction, rdir) { + return unsafe { ALPROTO_IKE }; + } } + return unsafe { ALPROTO_FAILED }; } -/// Returns *mut IKEState #[no_mangle] -pub extern "C" fn rs_ike_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { - let state = IKEState::new(); +pub extern "C" fn rs_ike_state_new( + _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, +) -> *mut std::os::raw::c_void { + let state = IKEState::default(); let boxed = Box::new(state); - return unsafe{std::mem::transmute(boxed)}; + return unsafe { transmute(boxed) }; } -/// Params: -/// - state: *mut IKEState as void pointer #[no_mangle] pub extern "C" fn rs_ike_state_free(state: *mut std::os::raw::c_void) { // Just unbox... - let mut ike_state: Box = unsafe{std::mem::transmute(state)}; - ike_state.free(); + let _drop: Box = unsafe { transmute(state) }; } #[no_mangle] -pub extern "C" fn rs_ike_parse_request(_flow: *const core::Flow, - state: *mut std::os::raw::c_void, - _pstate: *mut std::os::raw::c_void, - input: *const u8, - input_len: u32, - _data: *const std::os::raw::c_void, - _flags: u8) -> AppLayerResult { - let buf = build_slice!(input,input_len as usize); - let state = cast_pointer!(state,IKEState); - if state.parse(buf, STREAM_TOSERVER) < 0 { - return AppLayerResult::err(); - } - return AppLayerResult::ok(); +pub extern "C" fn rs_ike_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { + let state = cast_pointer!(state, IKEState); + state.free_tx(tx_id); } #[no_mangle] -pub extern "C" fn rs_ike_parse_response(_flow: *const core::Flow, - state: *mut std::os::raw::c_void, - pstate: *mut std::os::raw::c_void, - input: *const u8, - input_len: u32, - _data: *const std::os::raw::c_void, - _flags: u8) -> AppLayerResult { - let buf = build_slice!(input,input_len as usize); - let state = cast_pointer!(state,IKEState); - let res = state.parse(buf, STREAM_TOCLIENT); - if state.connection_state == IKEConnectionState::ParsingDone { - unsafe{ - AppLayerParserStateSetFlag(pstate, APP_LAYER_PARSER_NO_INSPECTION | - APP_LAYER_PARSER_NO_REASSEMBLY | - APP_LAYER_PARSER_BYPASS_READY) - }; - } - if res < 0 { - return AppLayerResult::err(); - } - return AppLayerResult::ok(); +pub extern "C" fn rs_ike_parse_request( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8, +) -> AppLayerResult { + let state = cast_pointer!(state, IKEState); + let buf = build_slice!(input, input_len as usize); + + return state.handle_input(buf, STREAM_TOSERVER); } #[no_mangle] -pub extern "C" fn rs_ike_state_get_tx(state: *mut std::os::raw::c_void, - tx_id: u64) - -> *mut std::os::raw::c_void -{ - let state = cast_pointer!(state,IKEState); - match state.get_tx_by_id(tx_id) { - Some(tx) => unsafe{std::mem::transmute(tx)}, - None => std::ptr::null_mut(), - } +pub extern "C" fn rs_ike_parse_response( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8, +) -> AppLayerResult { + let state = cast_pointer!(state, IKEState); + let buf = build_slice!(input, input_len as usize); + return state.handle_input(buf, STREAM_TOCLIENT); } #[no_mangle] -pub extern "C" fn rs_ike_state_get_tx_count(state: *mut std::os::raw::c_void) - -> u64 -{ - let state = cast_pointer!(state,IKEState); - state.tx_id +pub extern "C" fn rs_ike_state_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, IKEState); + match state.get_tx(tx_id) { + Some(tx) => { + return unsafe { transmute(tx) }; + } + None => { + return std::ptr::null_mut(); + } + } } #[no_mangle] -pub extern "C" fn rs_ike_state_tx_free(state: *mut std::os::raw::c_void, - tx_id: u64) -{ - let state = cast_pointer!(state,IKEState); - state.free_tx(tx_id); +pub extern "C" fn rs_ike_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { + let state = cast_pointer!(state, IKEState); + return state.tx_id; } #[no_mangle] -pub extern "C" fn rs_ike_state_progress_completion_status( - _direction: u8) - -> std::os::raw::c_int -{ +pub extern "C" fn rs_ike_state_progress_completion_status(_direction: u8) -> std::os::raw::c_int { + // This parser uses 1 to signal transaction completion status. return 1; } #[no_mangle] -pub extern "C" fn rs_ike_tx_get_alstate_progress(_tx: *mut std::os::raw::c_void, - _direction: u8) - -> std::os::raw::c_int -{ - 1 +pub extern "C" fn rs_ike_tx_get_alstate_progress( + _tx: *mut std::os::raw::c_void, _direction: u8, +) -> std::os::raw::c_int { + return 1; } #[no_mangle] -pub extern "C" fn rs_ike_state_set_tx_detect_state( - tx: *mut std::os::raw::c_void, - de_state: &mut core::DetectEngineState) -> std::os::raw::c_int -{ - let tx = cast_pointer!(tx,IKETransaction); - tx.de_state = Some(de_state); - 0 +pub extern "C" fn rs_ike_tx_get_logged( + _state: *mut std::os::raw::c_void, tx: *mut std::os::raw::c_void, +) -> u32 { + let tx = cast_pointer!(tx, IKETransaction); + return tx.logged.get(); } #[no_mangle] -pub extern "C" fn rs_ike_state_get_tx_detect_state( - tx: *mut std::os::raw::c_void) - -> *mut core::DetectEngineState -{ - let tx = cast_pointer!(tx,IKETransaction); - match tx.de_state { - Some(ds) => ds, - None => std::ptr::null_mut(), - } +pub extern "C" fn rs_ike_tx_set_logged( + _state: *mut std::os::raw::c_void, tx: *mut std::os::raw::c_void, logged: u32, +) { + let tx = cast_pointer!(tx, IKETransaction); + tx.logged.set(logged); } - #[no_mangle] -pub extern "C" fn rs_ike_state_get_events(tx: *mut std::os::raw::c_void) - -> *mut core::AppLayerDecoderEvents -{ +pub extern "C" fn rs_ike_state_get_events( + tx: *mut std::os::raw::c_void, +) -> *mut core::AppLayerDecoderEvents { let tx = cast_pointer!(tx, IKETransaction); return tx.events; } #[no_mangle] -pub extern "C" fn rs_ike_state_get_event_info_by_id(event_id: std::os::raw::c_int, - event_name: *mut *const std::os::raw::c_char, - event_type: *mut core::AppLayerEventType) - -> i8 -{ +pub extern "C" fn rs_ike_state_get_event_info_by_id( + event_id: std::os::raw::c_int, event_name: *mut *const std::os::raw::c_char, + event_type: *mut core::AppLayerEventType, +) -> i8 { if let Some(e) = IkeEvent::from_i32(event_id as i32) { let estr = match e { - IkeEvent::MalformedData => { "malformed_data\0" }, - IkeEvent::NoEncryption => { "no_encryption\0" }, - IkeEvent::WeakCryptoEnc => { "weak_crypto_enc\0" }, - IkeEvent::WeakCryptoPRF => { "weak_crypto_prf\0" }, - IkeEvent::WeakCryptoDH => { "weak_crypto_dh\0" }, - IkeEvent::WeakCryptoAuth => { "weak_crypto_auth\0" }, - IkeEvent::WeakCryptoNoDH => { "weak_crypto_nodh\0" }, - IkeEvent::WeakCryptoNoAuth => { "weak_crypto_noauth\0" }, - IkeEvent::InvalidProposal => { "invalid_proposal\0" }, - IkeEvent::UnknownProposal => { "unknown_proposal\0" }, + IkeEvent::MalformedData => "malformed_data\0", + IkeEvent::NoEncryption => "no_encryption\0", + IkeEvent::WeakCryptoEnc => "weak_crypto_enc\0", + IkeEvent::WeakCryptoPRF => "weak_crypto_prf\0", + IkeEvent::WeakCryptoDH => "weak_crypto_dh\0", + IkeEvent::WeakCryptoAuth => "weak_crypto_auth\0", + IkeEvent::WeakCryptoNoDH => "weak_crypto_nodh\0", + IkeEvent::WeakCryptoNoAuth => "weak_crypto_noauth\0", + IkeEvent::InvalidProposal => "invalid_proposal\0", + IkeEvent::UnknownProposal => "unknown_proposal\0", + IkeEvent::PayloadExtraData => "payload_extra_data\0", }; - unsafe{ + unsafe { *event_name = estr.as_ptr() as *const std::os::raw::c_char; *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION; }; @@ -605,32 +468,34 @@ pub extern "C" fn rs_ike_state_get_event_info_by_id(event_id: std::os::raw::c_in } #[no_mangle] -pub extern "C" fn rs_ike_state_get_event_info(event_name: *const std::os::raw::c_char, - event_id: *mut std::os::raw::c_int, - event_type: *mut core::AppLayerEventType) - -> std::os::raw::c_int -{ - if event_name == std::ptr::null() { return -1; } +pub extern "C" fn rs_ike_state_get_event_info( + event_name: *const std::os::raw::c_char, event_id: *mut std::os::raw::c_int, + event_type: *mut core::AppLayerEventType, +) -> std::os::raw::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" => IkeEvent::MalformedData as i32, - "no_encryption" => IkeEvent::NoEncryption as i32, - "weak_crypto_enc" => IkeEvent::WeakCryptoEnc as i32, - "weak_crypto_prf" => IkeEvent::WeakCryptoPRF as i32, - "weak_crypto_auth" => IkeEvent::WeakCryptoAuth as i32, - "weak_crypto_dh" => IkeEvent::WeakCryptoDH as i32, - "weak_crypto_nodh" => IkeEvent::WeakCryptoNoDH as i32, + "malformed_data" => IkeEvent::MalformedData as i32, + "no_encryption" => IkeEvent::NoEncryption as i32, + "weak_crypto_enc" => IkeEvent::WeakCryptoEnc as i32, + "weak_crypto_prf" => IkeEvent::WeakCryptoPRF as i32, + "weak_crypto_auth" => IkeEvent::WeakCryptoAuth as i32, + "weak_crypto_dh" => IkeEvent::WeakCryptoDH as i32, + "weak_crypto_nodh" => IkeEvent::WeakCryptoNoDH as i32, "weak_crypto_noauth" => IkeEvent::WeakCryptoNoAuth as i32, - "invalid_proposal" => IkeEvent::InvalidProposal as i32, - "unknown_proposal" => IkeEvent::UnknownProposal as i32, - _ => -1, // unknown event + "invalid_proposal" => IkeEvent::InvalidProposal as i32, + "unknown_proposal" => IkeEvent::UnknownProposal as i32, + "payload_extra_data" => IkeEvent::PayloadExtraData as i32, + _ => -1, // unknown event } - }, + } Err(_) => -1, // UTF-8 conversion failed }; - unsafe{ + unsafe { *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION; *event_id = event as std::os::raw::c_int; }; @@ -640,46 +505,31 @@ pub extern "C" fn rs_ike_state_get_event_info(event_name: *const std::os::raw::c static mut ALPROTO_IKE : AppProto = ALPROTO_UNKNOWN; #[no_mangle] -pub extern "C" fn rs_ike_probing_parser(_flow: *const Flow, - _direction: u8, - input:*const u8, input_len: u32, - _rdir: *mut u8) -> AppProto -{ - let slice = build_slice!(input,input_len as usize); - let alproto = unsafe{ ALPROTO_IKE }; - match parse_ikev2_header(slice) { - Ok((_, 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; - }, - Err(nom::Err::Incomplete(_)) => { - return ALPROTO_UNKNOWN; - }, - Err(_) => { - return unsafe{ALPROTO_FAILED}; - }, +pub extern "C" fn rs_ike_state_get_tx_iterator( + _ipproto: u8, _alproto: AppProto, state: *mut std::os::raw::c_void, min_tx_id: u64, + _max_tx_id: u64, istate: &mut u64, +) -> applayer::AppLayerGetTxIterTuple { + let state = cast_pointer!(state, IKEState); + match state.tx_iterator(min_tx_id, istate) { + Some((tx, out_tx_id, has_next)) => { + let c_tx = unsafe { transmute(tx) }; + let ires = applayer::AppLayerGetTxIterTuple::with_values(c_tx, out_tx_id, has_next); + return ires; + } + None => { + return applayer::AppLayerGetTxIterTuple::not_found(); + } } } -export_tx_data_get!(rs_ike_get_tx_data, IKETransaction); +// Parser name as a C style string. +const PARSER_NAME: &'static [u8] = b"ike\0"; +const PARSER_ALIAS: &'static [u8] = b"ikev2\0"; -const PARSER_NAME : &'static [u8] = b"ike\0"; +export_tx_data_get!(rs_ike_get_tx_data, IKETransaction); #[no_mangle] -pub unsafe extern "C" fn rs_register_ike_parser() { +pub unsafe extern "C" fn rs_ike_register_parser() { let default_port = CString::new("500").unwrap(); let parser = RustParser { name : PARSER_NAME.as_ptr() as *const std::os::raw::c_char, @@ -699,8 +549,8 @@ pub unsafe extern "C" fn rs_register_ike_parser() { tx_comp_st_ts : 1, tx_comp_st_tc : 1, tx_get_progress : rs_ike_tx_get_alstate_progress, - get_de_state : rs_ike_state_get_tx_detect_state, - set_de_state : rs_ike_state_set_tx_detect_state, + get_de_state : rs_ike_tx_get_detect_state, + set_de_state : rs_ike_tx_set_detect_state, get_events : Some(rs_ike_state_get_events), get_eventinfo : Some(rs_ike_state_get_event_info), get_eventinfo_byid : Some(rs_ike_state_get_event_info_by_id), @@ -715,36 +565,20 @@ pub unsafe extern "C" fn rs_register_ike_parser() { }; 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_IKE = alproto; if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { let _ = AppLayerRegisterParser(&parser, alproto); } + + AppLayerRegisterParserAlias( + PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + PARSER_ALIAS.as_ptr() as *const std::os::raw::c_char, + ); + SCLogDebug!("Rust IKE parser registered."); } else { SCLogDebug!("Protocol detector and parser disabled for IKE."); } } - - -#[cfg(test)] -mod tests { - use super::IKEState; - - #[test] - fn test_ike_parse_request_valid() { - // A UDP IKE 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 = IKEState::new(); - assert_eq!(1, state.parse(REQ, 0)); - } -} diff --git a/rust/src/ike/ikev1.rs b/rust/src/ike/ikev1.rs new file mode 100644 index 0000000000..043f2496f8 --- /dev/null +++ b/rust/src/ike/ikev1.rs @@ -0,0 +1,162 @@ +/* Copyright (C) 2020 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. + */ + +// Author: Frank Honza + +use crate::applayer::*; +use crate::common::to_hex; +use crate::core::STREAM_TOSERVER; +use crate::ike::ike::{IKEState, IkeEvent}; +use crate::ike::parser::*; +use nom; +use std; +use std::collections::HashSet; + +#[derive(Default)] +pub struct IkeV1Header { + pub exchange_type: Option, + pub encrypted_payloads: bool, + + pub key_exchange: Vec, + pub nonce: Vec, + pub vendor_ids: Vec, +} + +#[derive(Default)] +pub struct Ikev1ParticipantData { + pub key_exchange: String, + pub nonce: String, + pub vendor_ids: HashSet, + /// nested Vec, outer Vec per Proposal/Transform, inner Vec has the list of attributes. + pub transforms: Vec>, +} + +impl Ikev1ParticipantData { + pub fn reset(&mut self) { + self.key_exchange.clear(); + self.nonce.clear(); + self.vendor_ids.clear(); + self.transforms.clear(); + } + + pub fn update( + &mut self, key_exchange: &String, nonce: &String, vendor_ids: &Vec, + transforms: &Vec>, + ) { + self.key_exchange = key_exchange.clone(); + self.nonce = nonce.clone(); + self.vendor_ids.extend(vendor_ids.iter().cloned()); + self.transforms.extend(transforms.iter().cloned()); + } +} + +#[derive(Default)] +pub struct Ikev1Container { + pub domain_of_interpretation: Option, + pub client: Ikev1ParticipantData, + pub server: Ikev1ParticipantData, +} + +pub fn handle_ikev1( + state: &mut IKEState, current: &[u8], isakmp_header: IsakmpHeader, direction: u8, +) -> AppLayerResult { + let mut tx = state.new_tx(); + + tx.ike_version = 1; + tx.hdr.spi_initiator = format!("{:016x}", isakmp_header.init_spi); + tx.hdr.spi_responder = format!("{:016x}", isakmp_header.resp_spi); + tx.hdr.maj_ver = isakmp_header.maj_ver; + tx.hdr.min_ver = isakmp_header.min_ver; + tx.hdr.ikev1_header.exchange_type = Some(isakmp_header.exch_type); + tx.hdr.msg_id = isakmp_header.msg_id; + tx.hdr.flags = isakmp_header.flags; + + let mut cur_payload_type = isakmp_header.next_payload; + let mut payload_types: HashSet = HashSet::new(); + payload_types.insert(cur_payload_type); + + if isakmp_header.flags & 0x01 != 0x01 { + match parse_ikev1_payload_list(current) { + Ok((rem, payload_list)) => { + for isakmp_payload in payload_list { + if let Err(_) = parse_payload( + cur_payload_type, + isakmp_payload.data, + isakmp_payload.data.len() as u16, + &mut state.ikev1_container.domain_of_interpretation, + &mut tx.hdr.ikev1_header.key_exchange, + &mut tx.hdr.ikev1_header.nonce, + &mut tx.hdr.ikev1_transforms, + &mut tx.hdr.ikev1_header.vendor_ids, + &mut payload_types, + ) { + SCLogDebug!("Error while parsing IKEV1 payloads"); + return AppLayerResult::err(); + } + + cur_payload_type = isakmp_payload.payload_header.next_payload; + } + + if payload_types.contains(&(IsakmpPayloadType::SecurityAssociation as u8)) { + // clear transforms on a new SA in case there is happening a new key exchange + // on the same flow, elsewise properties would be added to the old/other SA + if direction == STREAM_TOSERVER { + state.ikev1_container.client.reset(); + } else { + state.ikev1_container.server.reset(); + } + } + + // add transaction values to state values + if direction == STREAM_TOSERVER { + state.ikev1_container.client.update( + &to_hex(tx.hdr.ikev1_header.key_exchange.as_ref()), + &to_hex(tx.hdr.ikev1_header.nonce.as_ref()), + &tx.hdr.ikev1_header.vendor_ids, + &tx.hdr.ikev1_transforms, + ); + } else { + state.ikev1_container.server.update( + &to_hex(tx.hdr.ikev1_header.key_exchange.as_ref()), + &to_hex(tx.hdr.ikev1_header.nonce.as_ref()), + &tx.hdr.ikev1_header.vendor_ids, + &tx.hdr.ikev1_transforms, + ); + } + + if rem.len() > 0 { + // more data left unread than should be + SCLogDebug!("Unread Payload Data"); + state.set_event(IkeEvent::PayloadExtraData); + } + } + Err(nom::Err::Incomplete(_)) => { + SCLogDebug!("Insufficient data while parsing IKEV1"); + return AppLayerResult::err(); + } + Err(_) => { + SCLogDebug!("Error while parsing payloads and adding to the state"); + return AppLayerResult::err(); + } + } + } + + tx.payload_types.ikev1_payload_types = Some(payload_types); + tx.hdr.ikev1_header.encrypted_payloads = isakmp_header.flags & 0x01 == 0x01; + state.transactions.push(tx); + return AppLayerResult::ok(); +} diff --git a/rust/src/ike/ikev2.rs b/rust/src/ike/ikev2.rs new file mode 100644 index 0000000000..8f97c26b11 --- /dev/null +++ b/rust/src/ike/ikev2.rs @@ -0,0 +1,340 @@ +/* Copyright (C) 2017-2020 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 crate::applayer::*; +use crate::core::STREAM_TOCLIENT; +use crate::ike::ipsec_parser::*; + +use super::ipsec_parser::IkeV2Transform; +use crate::ike::ike::{IKEState, IkeEvent}; +use crate::ike::parser::IsakmpHeader; +use ipsec_parser::{IkeExchangeType, IkePayloadType, IkeV2Header}; + +#[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, + } + } +} + +pub struct Ikev2Container { + /// The connection state + pub 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, +} + +impl Default for Ikev2Container { + fn default() -> Ikev2Container { + Ikev2Container { + 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, + } + } +} + +pub fn handle_ikev2( + mut state: &mut IKEState, current: &[u8], isakmp_header: IsakmpHeader, direction: u8, +) -> AppLayerResult { + let hdr = IkeV2Header { + init_spi: isakmp_header.init_spi, + resp_spi: isakmp_header.resp_spi, + next_payload: IkePayloadType(isakmp_header.next_payload), + maj_ver: isakmp_header.maj_ver, + min_ver: isakmp_header.min_ver, + exch_type: IkeExchangeType(isakmp_header.exch_type), + flags: isakmp_header.flags, + msg_id: isakmp_header.msg_id, + length: isakmp_header.length, + }; + + let mut tx = state.new_tx(); + tx.ike_version = 2; + // use init_spi as transaction identifier + // tx.xid = hdr.init_spi; todo is this used somewhere? + tx.hdr.ikev2_header = hdr.clone(); + tx.hdr.spi_initiator = format!("{:016x}", isakmp_header.init_spi); + tx.hdr.spi_responder = format!("{:016x}", isakmp_header.resp_spi); + tx.hdr.maj_ver = isakmp_header.maj_ver; + tx.hdr.min_ver = isakmp_header.min_ver; + tx.hdr.msg_id = isakmp_header.msg_id; + tx.hdr.flags = isakmp_header.flags; + state.transactions.push(tx); + let mut payload_types = Vec::new(); + let mut errors = 0; + let mut notify_types = Vec::new(); + match parse_ikev2_payload_list(current, hdr.next_payload) { + Ok((_, Ok(ref p))) => { + for payload in p { + payload_types.push(payload.hdr.next_payload_type); + match payload.content { + IkeV2PayloadContent::Dummy => (), + IkeV2PayloadContent::SA(ref prop) => { + // if hdr.flags & IKEV2_FLAG_INITIATOR != 0 { + add_proposals(state, prop, direction); + // } + } + IkeV2PayloadContent::KE(ref kex) => { + SCLogDebug!("KEX {:?}", kex.dh_group); + if direction == STREAM_TOCLIENT { + state.ikev2_container.dh_group = kex.dh_group; + } + } + IkeV2PayloadContent::Nonce(ref n) => { + SCLogDebug!("Nonce: {:?}", n); + } + IkeV2PayloadContent::Notify(ref n) => { + SCLogDebug!("Notify: {:?}", n); + if n.notify_type.is_error() { + errors += 1; + } + notify_types.push(n.notify_type); + } + // XXX CertificateRequest + // XXX Certificate + // XXX Authentication + // XXX TSi + // XXX TSr + // XXX IDr + _ => { + SCLogDebug!("Unknown payload content {:?}", payload.content); + } + } + state.ikev2_container.connection_state = + state.ikev2_container.connection_state.advance(payload); + if let Some(tx) = state.transactions.last_mut() { + // borrow back tx to update it + tx.payload_types + .ikev2_payload_types + .append(&mut payload_types); + tx.errors = errors; + tx.notify_types.append(&mut notify_types); + + if direction == STREAM_TOCLIENT + && state.ikev2_container.server_transforms.len() > 0 + { + tx.hdr.ikev2_transforms.clear(); + for transforms in &state.ikev2_container.server_transforms { + let mut proposal = Vec::new(); + transforms.iter().for_each(|t| match *t { + IkeV2Transform::Encryption(ref e) => { + proposal.push(IkeV2Transform::Encryption(*e)) + } + IkeV2Transform::Auth(ref e) => { + proposal.push(IkeV2Transform::Auth(*e)) + } + IkeV2Transform::PRF(ref e) => { + proposal.push(IkeV2Transform::PRF(*e)) + } + IkeV2Transform::DH(ref e) => proposal.push(IkeV2Transform::DH(*e)), + IkeV2Transform::ESN(ref e) => { + proposal.push(IkeV2Transform::ESN(*e)) + } + _ => (), + }); + tx.hdr.ikev2_transforms.push(proposal); + } + } + } + } + } + e => { + SCLogDebug!("parse_ikev2_payload_with_type: {:?}", e); + () + } + } + return AppLayerResult::ok(); +} + +fn add_proposals(state: &mut IKEState, 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 ? + state.set_event(IkeEvent::WeakCryptoEnc); + } + _ => (), + } + } + IkeV2Transform::PRF(ref prf) => match *prf { + IkeTransformPRFType::PRF_NULL => { + SCLogDebug!("'Null' PRF transform proposed"); + state.set_event(IkeEvent::InvalidProposal); + } + IkeTransformPRFType::PRF_HMAC_MD5 | IkeTransformPRFType::PRF_HMAC_SHA1 => { + SCLogDebug!("Weak PRF: {:?}", prf); + state.set_event(IkeEvent::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); + state.set_event(IkeEvent::WeakCryptoAuth); + } + _ => (), + } + } + IkeV2Transform::DH(ref dh) => match *dh { + IkeTransformDHType::None => { + SCLogDebug!("'None' DH transform proposed"); + state.set_event(IkeEvent::InvalidProposal); + } + IkeTransformDHType::Modp768 + | IkeTransformDHType::Modp1024 + | IkeTransformDHType::Modp1024s160 + | IkeTransformDHType::Modp1536 => { + SCLogDebug!("Weak DH: {:?}", dh); + state.set_event(IkeEvent::WeakCryptoDH); + } + _ => (), + }, + IkeV2Transform::Unknown(tx_type, tx_id) => { + SCLogDebug!("Unknown proposal: type={:?}, id={}", tx_type, tx_id); + state.set_event(IkeEvent::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"); + state.set_event(IkeEvent::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"); + state.set_event(IkeEvent::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"); + state.set_event(IkeEvent::WeakCryptoNoAuth); + } + } + // Finally + if direction == STREAM_TOCLIENT { + transforms.iter().for_each(|t| match *t { + IkeV2Transform::Encryption(ref e) => state.ikev2_container.alg_enc = *e, + IkeV2Transform::Auth(ref a) => state.ikev2_container.alg_auth = *a, + IkeV2Transform::PRF(ref p) => state.ikev2_container.alg_prf = *p, + IkeV2Transform::DH(ref dh) => state.ikev2_container.alg_dh = *dh, + IkeV2Transform::ESN(ref e) => state.ikev2_container.alg_esn = *e, + _ => (), + }); + SCLogDebug!("Selected transforms: {:?}", transforms); + state.ikev2_container.server_transforms.push(transforms); + } else { + SCLogDebug!("Proposed transforms: {:?}", transforms); + state.ikev2_container.client_transforms.push(transforms); + } + } +} diff --git a/rust/src/ike/log.rs b/rust/src/ike/log.rs deleted file mode 100644 index d1654e565e..0000000000 --- a/rust/src/ike/log.rs +++ /dev/null @@ -1,67 +0,0 @@ -/* Copyright (C) 2018-2020 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 crate::jsonbuilder::{JsonBuilder, JsonError}; -use crate::ike::ike::{IKEState,IKETransaction}; - -use crate::ike::ipsec_parser::IKEV2_FLAG_INITIATOR; - -fn ike_log_response(state: &mut IKEState, - tx: &mut IKETransaction, - jb: &mut JsonBuilder) - -> Result<(), JsonError> -{ - jb.set_uint("version_major", tx.hdr.maj_ver as u64)?; - jb.set_uint("version_minor", tx.hdr.min_ver as u64)?; - jb.set_uint("exchange_type", tx.hdr.exch_type.0 as u64)?; - jb.set_uint("message_id", tx.hdr.msg_id as u64)?; - jb.set_string("init_spi", &format!("{:016x}", tx.hdr.init_spi))?; - jb.set_string("resp_spi", &format!("{:016x}", tx.hdr.resp_spi))?; - if tx.hdr.flags & IKEV2_FLAG_INITIATOR != 0 { - jb.set_string("role", &"initiator")?; - } else { - jb.set_string("role", &"responder")?; - jb.set_string("alg_enc", &format!("{:?}", state.alg_enc))?; - jb.set_string("alg_auth", &format!("{:?}", state.alg_auth))?; - jb.set_string("alg_prf", &format!("{:?}", state.alg_prf))?; - jb.set_string("alg_dh", &format!("{:?}", state.alg_dh))?; - jb.set_string("alg_esn", &format!("{:?}", state.alg_esn))?; - } - jb.set_uint("errors", tx.errors as u64)?; - jb.open_array("payload")?; - for payload in tx.payload_types.iter() { - jb.append_string(&format!("{:?}", payload))?; - } - jb.close()?; - jb.open_array("notify")?; - for notify in tx.notify_types.iter() { - jb.append_string(&format!("{:?}", notify))?; - } - jb.close()?; - Ok(()) -} - -#[no_mangle] -pub extern "C" fn rs_ike_log_json_response(state: &mut IKEState, - tx: &mut IKETransaction, - jb: &mut JsonBuilder) - -> bool -{ - ike_log_response(state, tx, jb).is_ok() -} diff --git a/rust/src/ike/logger.rs b/rust/src/ike/logger.rs new file mode 100644 index 0000000000..3a022a1f99 --- /dev/null +++ b/rust/src/ike/logger.rs @@ -0,0 +1,227 @@ +/* Copyright (C) 2020 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. + */ + +use super::ike::{IKEState, IKETransaction}; +use super::ipsec_parser::IKEV2_FLAG_INITIATOR; +use crate::ike::parser::{ExchangeType, IsakmpPayloadType, SaAttribute}; +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use std; +use std::convert::TryFrom; +use num_traits::FromPrimitive; + +const LOG_EXTENDED: u32 = 0x01; + +fn add_attributes(transform: &Vec, js: &mut JsonBuilder) -> Result<(), JsonError> { + for attribute in transform { + js.set_string( + attribute.attribute_type.to_string().as_str(), + format!("{}", attribute.attribute_value.to_string()).as_str(), + )?; + + if let Some(numeric_value) = attribute.numeric_value { + js.set_uint( + format!("{}_raw", attribute.attribute_type).as_str(), + numeric_value as u64, + )?; + } else if let Some(hex_value) = &attribute.hex_value { + js.set_string( + format!("{}_raw", attribute.attribute_type).as_str(), + &hex_value, + )?; + } + } + + return Ok(()); +} + +fn log_ike( + state: &IKEState, tx: &IKETransaction, flags: u32, jb: &mut JsonBuilder, +) -> Result<(), JsonError> { + jb.open_object("ike")?; + + jb.set_uint("version_major", tx.hdr.maj_ver as u64)?; + jb.set_uint("version_minor", tx.hdr.min_ver as u64)?; + jb.set_string("init_spi", &tx.hdr.spi_initiator)?; + jb.set_string("resp_spi", &tx.hdr.spi_responder)?; + jb.set_uint("message_id", tx.hdr.msg_id as u64)?; + + if tx.ike_version == 1 { + if let Some(exchange_type) = tx.hdr.ikev1_header.exchange_type { + jb.set_uint("exchange_type", exchange_type as u64)?; + if (flags & LOG_EXTENDED) == LOG_EXTENDED { + if let Some(etype) = ExchangeType::from_u8(exchange_type) { + jb.set_string("exchange_type_verbose", etype.to_string().as_str())?; + }; + } + } + } else if tx.ike_version == 2 { + jb.set_uint("exchange_type", tx.hdr.ikev2_header.exch_type.0 as u64)?; + } + + if tx.ike_version == 1 { + let mut index = 0; + for server_transform in &state.ikev1_container.server.transforms { + if index >= 1 { + debug_validate_bug_on!(true); + break; + } + add_attributes(server_transform, jb)?; + index += 1; + } + } else if tx.ike_version == 2 { + if tx.hdr.flags & IKEV2_FLAG_INITIATOR != 0 { + jb.set_string("role", &"initiator")?; + } else { + jb.set_string("role", &"responder")?; + jb.set_string("alg_enc", &format!("{:?}", state.ikev2_container.alg_enc))?; + jb.set_string("alg_auth", &format!("{:?}", state.ikev2_container.alg_auth))?; + jb.set_string("alg_prf", &format!("{:?}", state.ikev2_container.alg_prf))?; + jb.set_string("alg_dh", &format!("{:?}", state.ikev2_container.alg_dh))?; + jb.set_string("alg_esn", &format!("{:?}", state.ikev2_container.alg_esn))?; + } + } + + // payloads in packet + jb.open_array("payload")?; + if tx.ike_version == 1 { + if let Some(payload_types) = &tx.payload_types.ikev1_payload_types { + for pt in payload_types { + append_payload_type_extended(jb, pt)?; + } + } + } else if tx.ike_version == 2 { + for payload in tx.payload_types.ikev2_payload_types.iter() { + jb.append_string(&format!("{:?}", payload))?; + } + } + jb.close()?; + + if tx.ike_version == 1 { + log_ikev1(state, tx, jb)?; + } else if tx.ike_version == 2 { + log_ikev2(tx, jb)?; + } + jb.close()?; + return Ok(()); +} + +fn log_ikev1(state: &IKEState, tx: &IKETransaction, jb: &mut JsonBuilder) -> Result<(), JsonError> { + jb.open_object("ikev1")?; + + if let Some(doi) = state.ikev1_container.domain_of_interpretation { + jb.set_uint("doi", doi as u64)?; + } + jb.set_bool("encrypted_payloads", tx.hdr.ikev1_header.encrypted_payloads)?; + + if !tx.hdr.ikev1_header.encrypted_payloads { + // enable logging of collected state if not-encrypted payloads + + // client data + jb.open_object("client")?; + if state.ikev1_container.client.key_exchange.len() > 0 { + jb.set_string( + "key_exchange_payload", + &state.ikev1_container.client.key_exchange, + )?; + if let Ok(client_key_length) = + u64::try_from(state.ikev1_container.client.key_exchange.len()) + { + jb.set_uint("key_exchange_payload_length", client_key_length / 2)?; + } + } + if state.ikev1_container.client.nonce.len() > 0 { + jb.set_string("nonce_payload", &state.ikev1_container.client.nonce)?; + if let Ok(client_nonce_length) = u64::try_from(state.ikev1_container.client.nonce.len()) + { + jb.set_uint("nonce_payload_length", client_nonce_length / 2)?; + } + } + + jb.open_array("proposals")?; + for client_transform in &state.ikev1_container.client.transforms { + jb.start_object()?; + add_attributes(client_transform, jb)?; + jb.close()?; + } + jb.close()?; // proposals + jb.close()?; // client + + // server data + jb.open_object("server")?; + if state.ikev1_container.server.key_exchange.len() > 0 { + jb.set_string( + "key_exchange_payload", + &state.ikev1_container.server.key_exchange, + )?; + if let Ok(server_key_length) = + u64::try_from(state.ikev1_container.server.key_exchange.len()) + { + jb.set_uint("key_exchange_payload_length", server_key_length / 2)?; + } + } + if state.ikev1_container.server.nonce.len() > 0 { + jb.set_string("nonce_payload", &state.ikev1_container.server.nonce)?; + if let Ok(server_nonce_length) = u64::try_from(state.ikev1_container.server.nonce.len()) + { + jb.set_uint("nonce_payload_length", server_nonce_length / 2)?; + } + } + jb.close()?; // server + + jb.open_array("vendor_ids")?; + for vendor in state + .ikev1_container + .client + .vendor_ids + .union(&state.ikev1_container.server.vendor_ids) + { + jb.append_string(vendor)?; + } + jb.close()?; // vendor_ids + } + jb.close()?; + + return Ok(()); +} + +fn append_payload_type_extended(js: &mut JsonBuilder, pt: &u8) -> Result<(), JsonError> { + if let Some(v) = IsakmpPayloadType::from_u8(*pt) { + js.append_string(&format!("{:?}", v))?; + } + Ok(()) +} + +fn log_ikev2(tx: &IKETransaction, jb: &mut JsonBuilder) -> Result<(), JsonError> { + jb.open_object("ikev2")?; + + jb.set_uint("errors", tx.errors as u64)?; + jb.open_array("notify")?; + for notify in tx.notify_types.iter() { + jb.append_string(&format!("{:?}", notify))?; + } + jb.close()?; + jb.close()?; + Ok(()) +} + +#[no_mangle] +pub extern "C" fn rs_ike_logger_log( + state: &mut IKEState, tx: *mut std::os::raw::c_void, flags: u32, js: &mut JsonBuilder, +) -> bool { + let tx = cast_pointer!(tx, IKETransaction); + log_ike(state, tx, flags, js).is_ok() +} diff --git a/rust/src/ike/mod.rs b/rust/src/ike/mod.rs index 2601e27cbe..4f8114d2a0 100644 --- a/rust/src/ike/mod.rs +++ b/rust/src/ike/mod.rs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2018 Open Information Security Foundation +/* Copyright (C) 2017-2020 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 @@ -19,6 +19,9 @@ extern crate ipsec_parser; +mod detect; pub mod ike; -pub mod state; -pub mod log; +mod ikev1; +mod ikev2; +pub mod logger; +mod parser; diff --git a/rust/src/ike/parser.rs b/rust/src/ike/parser.rs new file mode 100644 index 0000000000..f3fe86fae3 --- /dev/null +++ b/rust/src/ike/parser.rs @@ -0,0 +1,739 @@ +/* Copyright (C) 2020 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. + */ + +use crate::common::to_hex; +use core::fmt; +use nom::number::streaming::{be_u16, be_u32, be_u64, be_u8}; +use nom::*; +use std::collections::HashSet; + +// Generic ISAKMP "Container" structs +#[repr(u8)] +#[derive(Copy, Clone, FromPrimitive)] +pub enum ExchangeType { + None = 0, + Base = 1, + IdentityProtection = 2, + AuthenticationOnly = 3, + Aggressive = 4, + Informational = 5, + Transaction = 6, + QuickMode = 32, + NewGroupMode = 33, +} + +impl fmt::Display for ExchangeType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ExchangeType::Base => write!(f, "Base"), + ExchangeType::IdentityProtection => write!(f, "Identity Protection"), + ExchangeType::AuthenticationOnly => write!(f, "Authentication Only"), + ExchangeType::Aggressive => write!(f, "Aggressive"), + ExchangeType::Informational => write!(f, "Informational"), + ExchangeType::Transaction => write!(f, "Transaction (Config Mode)"), + ExchangeType::QuickMode => write!(f, "Quick Mode"), + ExchangeType::NewGroupMode => write!(f, "New Group Mode"), + _ => write!(f, "Unknown Exchange Type"), + } + } +} + +pub struct IsakmpHeader { + pub init_spi: u64, + pub resp_spi: u64, + pub next_payload: u8, + pub maj_ver: u8, + pub min_ver: u8, + pub exch_type: u8, + pub flags: u8, + pub msg_id: u32, + pub length: u32, +} + +pub struct IsakmpPayloadHeader { + pub next_payload: u8, + pub reserved: u8, + pub payload_length: u16, +} + +pub struct IsakmpPayload<'a> { + pub payload_header: IsakmpPayloadHeader, + pub data: &'a [u8], +} + +// IKEV1 specific payloads + +// 1 -> Security Association +pub struct SecurityAssociationPayload<'a> { + pub domain_of_interpretation: u32, + pub situation: Option<&'a [u8]>, + pub data: Option<&'a [u8]>, +} + +// 2 -> Proposal +pub struct ProposalPayload<'a> { + pub proposal_number: u8, + pub proposal_type: u8, + pub spi_size: u8, + pub number_transforms: u8, + pub spi: &'a [u8], + pub data: &'a [u8], +} + +// 3 -> Transform +pub struct TransformPayload<'a> { + pub transform_number: u8, + pub transform_type: u8, + pub sa_attributes: &'a [u8], +} + +// 4 -> Key Exchange +pub struct KeyExchangePayload<'a> { + pub key_exchange_data: &'a [u8], +} + +// 5 -> Identification +// 6 -> Certificate +// 7 -> Certificate Request +// 8 -> Hash +// 9 -> Signature + +// 10 -> Nonce +pub struct NoncePayload<'a> { + pub nonce_data: &'a [u8], +} + +// 11 -> Notification +// 12 -> Delete + +// 13 -> Vendor ID +pub struct VendorPayload<'a> { + pub vendor_id: &'a [u8], +} + +// Attributes inside Transform +#[derive(Debug, Clone)] +pub enum AttributeType { + Unknown = 0, + EncryptionAlgorithm = 1, + HashAlgorithm = 2, + AuthenticationMethod = 3, + GroupDescription = 4, + GroupType = 5, + GroupPrime = 6, + GroupGeneratorOne = 7, + GroupGeneratorTwo = 8, + GroupCurveA = 9, + GroupCurveB = 10, + LifeType = 11, + LifeDuration = 12, + PRF = 13, + KeyLength = 14, + FieldSize = 15, + GroupOrder = 16, +} + +impl fmt::Display for AttributeType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AttributeType::EncryptionAlgorithm => write!(f, "alg_enc"), + AttributeType::HashAlgorithm => write!(f, "alg_hash"), + AttributeType::AuthenticationMethod => write!(f, "alg_auth"), + AttributeType::GroupDescription => write!(f, "alg_dh"), + AttributeType::GroupType => write!(f, "sa_group_type"), + AttributeType::GroupPrime => write!(f, "sa_group_prime"), + AttributeType::GroupGeneratorOne => write!(f, "sa_group_generator_one"), + AttributeType::GroupGeneratorTwo => write!(f, "sa_group_generator_two"), + AttributeType::GroupCurveA => write!(f, "sa_group_curve_a"), + AttributeType::GroupCurveB => write!(f, "sa_group_curve_b"), + AttributeType::LifeType => write!(f, "sa_life_type"), + AttributeType::LifeDuration => write!(f, "sa_life_duration"), + AttributeType::PRF => write!(f, "alg_prf"), + AttributeType::KeyLength => write!(f, "sa_key_length"), + AttributeType::FieldSize => write!(f, "sa_field_size"), + AttributeType::GroupOrder => write!(f, "sa_group_order"), + _ => write!(f, "unknown"), + } + } +} + +#[derive(Debug, Clone)] +pub enum AttributeValue { + // https://www.iana.org/assignments/ipsec-registry/ipsec-registry.xhtml + Unknown, + // Encryption Algorithm + EncDesCbc, + EncIdeaCbc, + EncBlowfishCbc, + EncRc5R16B64Cbc, + EncTripleDesCbc, + EncCastCbc, + EncAesCbc, + EncCamelliaCbc, + // Hash Algorithm + HashMd5, + HashSha, + HashTiger, + HashSha2_256, + HashSha2_384, + HashSha2_512, + // Authentication Method + AuthPreSharedKey, + AuthDssSignatures, + AuthRsaSignatures, + AuthEncryptionWithRsa, + AuthRevisedEncryptionWithRsa, + AuthReserved, + AuthEcdsaSha256, + AuthEcdsaSha384, + AuthEcdsaSha512, + // Group Description + GroupDefault768BitModp, + GroupAlternate1024BitModpGroup, + GroupEc2nOnGp2p155, + GroupEc2nOnGp2p185, + GroupModp1536Bit, + GroupEc2nOverGf2p163, + GroupEc2nOverGf2p283, + GroupEc2nOverGf2p409, + GroupEc2nOverGf2p571, + GroupModp2048Bit, + GroupModp3072Bit, + GroupModp4096Bit, + GroupModp6144Bit, + GroupModp8192Bit, + GroupRandomEcp256, + GroupRandomEcp384, + GroupRandomEcp521, + GroupModp1024With160BitPrime, + GroupModp2048With224BitPrime, + GroupModp2048With256BitPrime, + GroupRandomEcp192, + GroupRandomEcp224, + GroupBrainpoolEcp224, + GroupBrainpoolEcp256, + GroupBrainpoolEcp384, + GroupBrainpoolEcp512, + // Life Type + LifeTypeSeconds, + LifeTypeKilobytes, +} + +impl fmt::Display for AttributeValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Clone)] +pub struct SaAttribute { + pub attribute_format: u8, + pub attribute_type: AttributeType, + pub attribute_value: AttributeValue, + pub numeric_value: Option, + pub hex_value: Option, +} + +named! {pub parse_isakmp_header, + do_parse!( + init_spi: be_u64 >> + resp_spi: be_u64 >> + np: be_u8 >> + vers: bits!( + tuple!(take_bits!(4u8),take_bits!(4u8)) + ) >> + ex: be_u8 >> + flags: be_u8 >> + id: be_u32 >> + l: be_u32 >> + ( + IsakmpHeader { + init_spi, + resp_spi, + next_payload: np, + maj_ver: vers.0, + min_ver: vers.1, + exch_type: ex, + flags, + msg_id: id, + length: l, + } + ) + ) +} + +pub fn parse_security_association(i: &[u8]) -> IResult<&[u8], SecurityAssociationPayload> { + do_parse!( + i, + domain_of_interpretation: be_u32 + >> situation: cond!(domain_of_interpretation == 1, take!(4)) + >> data: cond!( + domain_of_interpretation == 1 && i.len() >= 8, + take!(i.len() - 8) + ) + >> (SecurityAssociationPayload { + domain_of_interpretation, + situation, + data + }) + ) +} + +pub fn parse_key_exchange(i: &[u8], length: u16) -> IResult<&[u8], KeyExchangePayload> { + do_parse!( + i, + key_exchange_data: take!(length) >> (KeyExchangePayload { key_exchange_data }) + ) +} + +pub fn parse_proposal(i: &[u8]) -> IResult<&[u8], ProposalPayload> { + do_parse!( + i, + proposal_number: be_u8 + >> proposal_type: be_u8 + >> spi_size: be_u8 + >> number_transforms: be_u8 + >> spi: take!(spi_size) + >> payload_data: + cond!( + (i.len() as i16 - 4) - spi_size as i16 >= 0, + take!((i.len() as u16 - 4) - spi_size as u16) + ) + >> (ProposalPayload { + proposal_number, + proposal_type, + spi_size, + number_transforms, + spi, + data: if let Some(_data) = payload_data { + _data + } else { + b"" + } + }) + ) +} + +pub fn parse_transform(i: &[u8], length: u16) -> IResult<&[u8], TransformPayload> { + do_parse!( + i, + transform_number: be_u8 + >> transform_type: be_u8 + >> be_u16 + >> payload_data: cond!(length >= 4, take!(length - 4)) + >> (TransformPayload { + transform_number, + transform_type, + sa_attributes: if let Some(_data) = payload_data { + _data + } else { + b"" + } + }) + ) +} + +pub fn parse_vendor_id(i: &[u8], length: u16) -> IResult<&[u8], VendorPayload> { + map!(i, take!(length), |v| VendorPayload { vendor_id: v }) +} + +fn get_attribute_type(v: u16) -> AttributeType { + match v { + 1 => AttributeType::EncryptionAlgorithm, + 2 => AttributeType::HashAlgorithm, + 3 => AttributeType::AuthenticationMethod, + 4 => AttributeType::GroupDescription, + 5 => AttributeType::GroupType, + 6 => AttributeType::GroupPrime, + 7 => AttributeType::GroupGeneratorOne, + 8 => AttributeType::GroupGeneratorTwo, + 9 => AttributeType::GroupCurveA, + 10 => AttributeType::GroupCurveB, + 11 => AttributeType::LifeType, + 12 => AttributeType::LifeDuration, + 13 => AttributeType::PRF, + 14 => AttributeType::KeyLength, + 15 => AttributeType::FieldSize, + 16 => AttributeType::GroupOrder, + _ => AttributeType::Unknown, + } +} + +fn get_encryption_algorithm(v: u16) -> AttributeValue { + match v { + 1 => AttributeValue::EncDesCbc, + 2 => AttributeValue::EncIdeaCbc, + 3 => AttributeValue::EncBlowfishCbc, + 4 => AttributeValue::EncRc5R16B64Cbc, + 5 => AttributeValue::EncTripleDesCbc, + 6 => AttributeValue::EncCastCbc, + 7 => AttributeValue::EncAesCbc, + 8 => AttributeValue::EncCamelliaCbc, + _ => AttributeValue::Unknown, + } +} + +fn get_hash_algorithm(v: u16) -> AttributeValue { + match v { + 1 => AttributeValue::HashMd5, + 2 => AttributeValue::HashSha, + 3 => AttributeValue::HashTiger, + 4 => AttributeValue::HashSha2_256, + 5 => AttributeValue::HashSha2_384, + 6 => AttributeValue::HashSha2_512, + _ => AttributeValue::Unknown, + } +} + +fn get_authentication_method(v: u16) -> AttributeValue { + match v { + 1 => AttributeValue::AuthPreSharedKey, + 2 => AttributeValue::AuthDssSignatures, + 3 => AttributeValue::AuthRsaSignatures, + 4 => AttributeValue::AuthEncryptionWithRsa, + 5 => AttributeValue::AuthRevisedEncryptionWithRsa, + 6 => AttributeValue::AuthReserved, + 7 => AttributeValue::AuthReserved, + 8 => AttributeValue::AuthReserved, + 9 => AttributeValue::AuthEcdsaSha256, + 10 => AttributeValue::AuthEcdsaSha384, + 11 => AttributeValue::AuthEcdsaSha512, + _ => AttributeValue::Unknown, + } +} + +fn get_group_description(v: u16) -> AttributeValue { + match v { + 1 => AttributeValue::GroupDefault768BitModp, + 2 => AttributeValue::GroupAlternate1024BitModpGroup, + 3 => AttributeValue::GroupEc2nOnGp2p155, + 4 => AttributeValue::GroupEc2nOnGp2p185, + 5 => AttributeValue::GroupModp1536Bit, + 6 => AttributeValue::GroupEc2nOverGf2p163, + 7 => AttributeValue::GroupEc2nOverGf2p163, + 8 => AttributeValue::GroupEc2nOverGf2p283, + 9 => AttributeValue::GroupEc2nOverGf2p283, + 10 => AttributeValue::GroupEc2nOverGf2p409, + 11 => AttributeValue::GroupEc2nOverGf2p409, + 12 => AttributeValue::GroupEc2nOverGf2p571, + 13 => AttributeValue::GroupEc2nOverGf2p571, + 14 => AttributeValue::GroupModp2048Bit, + 15 => AttributeValue::GroupModp3072Bit, + 16 => AttributeValue::GroupModp4096Bit, + 17 => AttributeValue::GroupModp6144Bit, + 18 => AttributeValue::GroupModp8192Bit, + 19 => AttributeValue::GroupRandomEcp256, + 20 => AttributeValue::GroupRandomEcp384, + 21 => AttributeValue::GroupRandomEcp521, + 22 => AttributeValue::GroupModp1024With160BitPrime, + 23 => AttributeValue::GroupModp2048With224BitPrime, + 24 => AttributeValue::GroupModp2048With256BitPrime, + 25 => AttributeValue::GroupRandomEcp192, + 26 => AttributeValue::GroupRandomEcp224, + 27 => AttributeValue::GroupBrainpoolEcp224, + 28 => AttributeValue::GroupBrainpoolEcp256, + 29 => AttributeValue::GroupBrainpoolEcp384, + 30 => AttributeValue::GroupBrainpoolEcp512, + _ => AttributeValue::Unknown, + } +} + +named! { pub parse_sa_attribute<&[u8], Vec>, + many0!( + complete!( + do_parse!( + format: bits!(tuple!(take_bits!(1u8),take_bits!(15u16))) >> + attribute_length_or_value: be_u16 >> // depends on format bit: 1 -> value | 0 -> number of following bytes + numeric_variable_value: cond!(format.0 == 0 && attribute_length_or_value == 4, be_u32) >> // interpret as number + variable_attribute_value: cond!(format.0 == 0 && attribute_length_or_value != 4, take!(attribute_length_or_value)) >> + ( + SaAttribute { + attribute_format: format.0, + attribute_type: get_attribute_type(format.1), + attribute_value : match format.1 { + 1 => get_encryption_algorithm(attribute_length_or_value), + 2 => get_hash_algorithm(attribute_length_or_value), + 3 => get_authentication_method(attribute_length_or_value), + 4 => get_group_description(attribute_length_or_value), + 11 => match attribute_length_or_value { + 1 => AttributeValue::LifeTypeSeconds, + 2 => AttributeValue::LifeTypeKilobytes, + _ => AttributeValue::Unknown + } + _ => AttributeValue::Unknown + }, + numeric_value: match format.0 { + 1 => Some(attribute_length_or_value as u32), + 0 => { + if let Some(_numeric_variable_value) = numeric_variable_value { + Some(_numeric_variable_value) + } + else { + None + } + }, + _ => None, + }, + hex_value: match format.0 { + 0 => { + if let Some(_variable_attribute_value) = variable_attribute_value { + Some(to_hex(_variable_attribute_value)) + } else { + None + } + } + _ => None, + } + } + ) + ) + ) + ) +} + +pub fn parse_nonce(i: &[u8], length: u16) -> IResult<&[u8], NoncePayload> { + map!(i, take!(length), |v| NoncePayload { nonce_data: v }) +} + +named! { pub parse_ikev1_payload_list<&[u8], Vec>, + many0!( + complete!( + do_parse!( + next_payload: be_u8 >> + reserved: be_u8 >> + payload_length: be_u16 >> + payload_data: cond!(payload_length >= 4, take!(payload_length - 4)) >> + ( + IsakmpPayload { + payload_header: IsakmpPayloadHeader { + next_payload, + reserved, + payload_length + }, + data: if let Some(_data) = payload_data { _data } else { b"" } + } + ) + ) + ) + ) +} + +#[derive(FromPrimitive, Debug)] +pub enum IsakmpPayloadType { + None = 0, + SecurityAssociation = 1, + Proposal = 2, + Transform = 3, + KeyExchange = 4, + Identification = 5, + Certificate = 6, + CertificateRequest = 7, + Hash = 8, + Signature = 9, + Nonce = 10, + Notification = 11, + Delete = 12, + VendorID = 13, + SaKekPayload = 15, + SaTekPayload = 16, + KeyDownload = 17, + SequenceNumber = 18, + ProofOfPossession = 19, + NatDiscovery = 20, + NatOriginalAddress = 21, + GroupAssociatedPolicy = 22, +} + +impl fmt::Display for IsakmpPayloadType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +pub fn parse_payload<'a>( + payload_type: u8, data: &'a [u8], data_length: u16, domain_of_interpretation: &mut Option, + key_exchange: &mut Vec, nonce: &mut Vec, transforms: &mut Vec>, + vendor_ids: &mut Vec, payload_types: &mut HashSet, +) -> Result<(), ()> { + payload_types.insert(payload_type); + + let element = num::FromPrimitive::from_u8(payload_type); + match element { + Some(IsakmpPayloadType::SecurityAssociation) => { + if let Err(_) = parse_security_association_payload( + data, + data_length, + domain_of_interpretation, + key_exchange, + nonce, + transforms, + vendor_ids, + payload_types, + ) { + SCLogDebug!("Error parsing SecurityAssociation"); + return Err(()); + } + Ok(()) + } + Some(IsakmpPayloadType::Proposal) => { + if let Err(_) = parse_proposal_payload( + data, + data_length, + domain_of_interpretation, + key_exchange, + nonce, + transforms, + vendor_ids, + payload_types, + ) { + SCLogDebug!("Error parsing Proposal"); + return Err(()); + } + Ok(()) + } + Some(IsakmpPayloadType::Transform) => { + if let Ok((_rem, payload)) = parse_transform(data, data_length) { + if let Ok((_, attribute_list)) = parse_sa_attribute(payload.sa_attributes) { + transforms.push(attribute_list); + } + } + Ok(()) + } + Some(IsakmpPayloadType::KeyExchange) => { + let res = parse_key_exchange(data, data_length); + if let Ok((_rem, payload)) = res { + *key_exchange = Vec::from(payload.key_exchange_data); + } + Ok(()) + } + Some(IsakmpPayloadType::Nonce) => { + let res = parse_nonce(data, data_length); + if let Ok((_rem, payload)) = res { + *nonce = Vec::from(payload.nonce_data); + } + Ok(()) + } + Some(IsakmpPayloadType::VendorID) => { + let res = parse_vendor_id(data, data_length); + if let Ok((_rem, payload)) = res { + vendor_ids.push(to_hex(payload.vendor_id)); + } + Ok(()) + } + _ => Ok(()), + } +} + +fn parse_proposal_payload<'a>( + data: &'a [u8], data_length: u16, domain_of_interpretation: &mut Option, + key_exchange: &mut Vec, nonce: &mut Vec, transforms: &mut Vec>, + vendor_ids: &mut Vec, payload_types: &mut HashSet, +) -> Result<(), ()> { + match parse_proposal(&data[0..data_length as usize]) { + Ok((_rem, payload)) => { + let mut cur_payload_type = IsakmpPayloadType::Transform as u8; + match parse_ikev1_payload_list(payload.data) { + Ok((_, payload_list)) => { + for isakmp_payload in payload_list { + if let Err(_) = parse_payload( + cur_payload_type, + isakmp_payload.data, + isakmp_payload.data.len() as u16, + domain_of_interpretation, + key_exchange, + nonce, + transforms, + vendor_ids, + payload_types, + ) { + SCLogDebug!("Error parsing transform payload"); + return Err(()); + } + cur_payload_type = isakmp_payload.payload_header.next_payload; + } + Ok(()) + } + Err(nom::Err::Incomplete(_)) => { + SCLogDebug!("Incomplete data parsing payload list"); + Err(()) + } + Err(_) => { + SCLogDebug!("Error parsing payload list"); + Err(()) + } + } + } + Err(nom::Err::Incomplete(_)) => { + SCLogDebug!("Incomplete data"); + Err(()) + } + Err(_) => Err(()), + } +} + +fn parse_security_association_payload<'a>( + data: &'a [u8], data_length: u16, domain_of_interpretation: &mut Option, + key_exchange: &mut Vec, nonce: &mut Vec, transforms: &mut Vec>, + vendor_ids: &mut Vec, payload_types: &mut HashSet, +) -> Result<(), ()> { + match parse_security_association(&data[0..data_length as usize]) { + Ok((_rem, payload)) => { + *domain_of_interpretation = Some(payload.domain_of_interpretation); + if payload.domain_of_interpretation == 1 { + // 1 is assigned to IPsec DOI + let mut cur_payload_type = IsakmpPayloadType::Proposal as u8; + if let Some(p_data) = payload.data { + match parse_ikev1_payload_list(p_data) { + Ok((_, payload_list)) => { + for isakmp_payload in payload_list { + if let Err(_) = parse_payload( + cur_payload_type, + isakmp_payload.data, + isakmp_payload.data.len() as u16, + domain_of_interpretation, + key_exchange, + nonce, + transforms, + vendor_ids, + payload_types, + ) { + SCLogDebug!("Error parsing proposal payload"); + return Err(()); + } + cur_payload_type = isakmp_payload.payload_header.next_payload; + } + } + Err(nom::Err::Incomplete(_)) => { + SCLogDebug!("Incomplete data parsing payload list"); + return Err(()); + } + Err(_) => { + SCLogDebug!("Error parsing payload list"); + return Err(()); + } + } + } + } + Ok(()) + } + Err(nom::Err::Incomplete(_)) => { + SCLogDebug!("Incomplete data"); + Err(()) + } + Err(_) => Err(()), + } +} diff --git a/rust/src/ike/state.rs b/rust/src/ike/state.rs deleted file mode 100644 index 282b7fed3a..0000000000 --- a/rust/src/ike/state.rs +++ /dev/null @@ -1,57 +0,0 @@ -/* 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 IKEConnectionState { - Init, - InitSASent, - InitKESent, - InitNonceSent, - RespSASent, - RespKESent, - RespNonceSent, - RespCertReqSent, - - ParsingDone, - - Invalid, -} - -impl IKEConnectionState { - pub fn advance(&self, payload: &IkeV2Payload) -> IKEConnectionState { - use self::IKEConnectionState::*; - 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/src/Makefile.am b/src/Makefile.am index a272d0a61e..abc6699264 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -213,6 +213,14 @@ noinst_HEADERS = \ detect-icmpv6-mtu.h \ detect-icode.h \ detect-id.h \ + detect-ike-exch-type.h \ + detect-ike-spi.h \ + detect-ike-vendor.h \ + detect-ike-chosen-sa.h \ + detect-ike-key-exchange-payload-length.h \ + detect-ike-nonce-payload-length.h \ + detect-ike-nonce-payload.h \ + detect-ike-key-exchange-payload.h \ detect-ipopts.h \ detect-ipproto.h \ detect-iprep.h \ @@ -781,6 +789,14 @@ libsuricata_c_a_SOURCES = \ detect-icmpv6-mtu.c \ detect-icode.c \ detect-id.c \ + detect-ike-exch-type.c \ + detect-ike-spi.c \ + detect-ike-vendor.c \ + detect-ike-chosen-sa.c \ + detect-ike-key-exchange-payload-length.c \ + detect-ike-nonce-payload-length.c \ + detect-ike-nonce-payload.c \ + detect-ike-key-exchange-payload.c \ detect-ipopts.c \ detect-ipproto.c \ detect-iprep.c \ diff --git a/src/app-layer-ike.c b/src/app-layer-ike.c index f6a5c19333..9f8877aee7 100644 --- a/src/app-layer-ike.c +++ b/src/app-layer-ike.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Open Information Security Foundation +/* Copyright (C) 2020 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 @@ -19,8 +19,10 @@ * \file * * \author Pierre Chifflier + * \author Frank Honza + * + * IKE application layer detector and parser. * - * Parser for IKE application layer running on UDP port 500. */ #include "suricata-common.h" @@ -37,5 +39,152 @@ void RegisterIKEParsers(void) { - rs_register_ike_parser(); + rs_ike_register_parser(); +#ifdef UNITTESTS + AppLayerParserRegisterProtocolUnittests(IPPROTO_UDP, ALPROTO_IKE, IKEParserRegisterTests); +#endif +} + +#ifdef UNITTESTS +#include "stream-tcp.h" +#include "util-unittest-helper.h" +#include "flow-util.h" + +static int IkeParserTest(void) +{ + uint64_t ret[4]; + + Flow f; + TcpSession ssn; + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + FLOW_INITIALIZE(&f); + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_UDP; + f.protomap = FlowGetProtoMapping(f.proto); + f.alproto = ALPROTO_IKE; + + StreamTcpInitConfig(TRUE); + + static const unsigned char initiator_sa[] = { 0xe4, 0x7a, 0x59, 0x1f, 0xd0, 0x57, 0x58, 0x7f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xa8, 0x0d, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x01, + 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x07, 0x80, 0x0e, 0x00, 0x80, 0x80, 0x02, 0x00, 0x02, + 0x80, 0x04, 0x00, 0x02, 0x80, 0x03, 0x00, 0x01, 0x80, 0x0b, 0x00, 0x01, 0x00, 0x0c, 0x00, + 0x04, 0x00, 0x01, 0x51, 0x80, 0x0d, 0x00, 0x00, 0x14, 0x4a, 0x13, 0x1c, 0x81, 0x07, 0x03, + 0x58, 0x45, 0x5c, 0x57, 0x28, 0xf2, 0x0e, 0x95, 0x45, 0x2f, 0x0d, 0x00, 0x00, 0x14, 0x43, + 0x9b, 0x59, 0xf8, 0xba, 0x67, 0x6c, 0x4c, 0x77, 0x37, 0xae, 0x22, 0xea, 0xb8, 0xf5, 0x82, + 0x0d, 0x00, 0x00, 0x14, 0x7d, 0x94, 0x19, 0xa6, 0x53, 0x10, 0xca, 0x6f, 0x2c, 0x17, 0x9d, + 0x92, 0x15, 0x52, 0x9d, 0x56, 0x00, 0x00, 0x00, 0x14, 0x90, 0xcb, 0x80, 0x91, 0x3e, 0xbb, + 0x69, 0x6e, 0x08, 0x63, 0x81, 0xb5, 0xec, 0x42, 0x7b, 0x1f }; + + // the initiator sending the security association with proposals + int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_IKE, STREAM_TOSERVER | STREAM_START, + (uint8_t *)initiator_sa, sizeof(initiator_sa)); + FAIL_IF_NOT(r == 0); + + static const unsigned char responder_sa[] = { 0xe4, 0x7a, 0x59, 0x1f, 0xd0, 0x57, 0x58, 0x7f, + 0xa0, 0x0b, 0x8e, 0xf0, 0x90, 0x2b, 0xb8, 0xec, 0x01, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x6c, 0x0d, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x01, + 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x07, 0x80, 0x0e, 0x00, 0x80, 0x80, 0x02, 0x00, 0x02, + 0x80, 0x04, 0x00, 0x02, 0x80, 0x03, 0x00, 0x01, 0x80, 0x0b, 0x00, 0x01, 0x00, 0x0c, 0x00, + 0x04, 0x00, 0x01, 0x51, 0x80, 0x00, 0x00, 0x00, 0x14, 0x4a, 0x13, 0x1c, 0x81, 0x07, 0x03, + 0x58, 0x45, 0x5c, 0x57, 0x28, 0xf2, 0x0e, 0x95, 0x45, 0x2f }; + + // responder answering with chosen proposal + r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_IKE, STREAM_TOCLIENT, + (uint8_t *)responder_sa, sizeof(responder_sa)); + FAIL_IF_NOT(r == 0); + + static const unsigned char initiator_key[] = { 0xe4, 0x7a, 0x59, 0x1f, 0xd0, 0x57, 0x58, 0x7f, + 0xa0, 0x0b, 0x8e, 0xf0, 0x90, 0x2b, 0xb8, 0xec, 0x04, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x1c, 0x0a, 0x00, 0x00, 0x84, 0x35, 0x04, 0xd3, 0xd2, 0xed, 0x14, + 0xe0, 0xca, 0x03, 0xb8, 0x51, 0xa5, 0x1a, 0x9d, 0xa2, 0xe5, 0xa4, 0xc1, 0x4c, 0x1d, 0x7e, + 0xc3, 0xe1, 0xfb, 0xe9, 0x50, 0x02, 0x54, 0x24, 0x51, 0x4b, 0x3c, 0x69, 0xed, 0x7f, 0xbb, + 0x44, 0xe0, 0x92, 0x25, 0xda, 0x52, 0xd2, 0xa9, 0x26, 0x04, 0xa9, 0x9b, 0xf6, 0x1b, 0x7b, + 0xee, 0xd7, 0xfb, 0xfa, 0x63, 0x5e, 0x82, 0xf0, 0x65, 0xf4, 0xfe, 0x78, 0x07, 0x51, 0x35, + 0x4d, 0xbe, 0x47, 0x4c, 0x3d, 0xe7, 0x20, 0x7d, 0xcf, 0x69, 0xfd, 0xbb, 0xed, 0x32, 0xc1, + 0x69, 0x1c, 0xc1, 0x49, 0xb3, 0x18, 0xee, 0xe0, 0x03, 0x70, 0xe6, 0x5f, 0xc3, 0x06, 0x9b, + 0xba, 0xcf, 0xb0, 0x13, 0x46, 0x71, 0x73, 0x96, 0x6e, 0x9d, 0x5f, 0x4b, 0xc4, 0xf3, 0x85, + 0x7e, 0x35, 0x9b, 0xba, 0x3a, 0xdb, 0xb6, 0xef, 0xee, 0xa5, 0x16, 0xf3, 0x89, 0x7d, 0x85, + 0x34, 0xf3, 0x0d, 0x00, 0x00, 0x18, 0x89, 0xd7, 0xc8, 0xfb, 0xf9, 0x4b, 0x51, 0x5b, 0x52, + 0x1d, 0x5d, 0x95, 0x89, 0xc2, 0x60, 0x20, 0x21, 0xe1, 0xa7, 0x09, 0x0d, 0x00, 0x00, 0x14, + 0xaf, 0xca, 0xd7, 0x13, 0x68, 0xa1, 0xf1, 0xc9, 0x6b, 0x86, 0x96, 0xfc, 0x77, 0x57, 0x01, + 0x00, 0x0d, 0x00, 0x00, 0x14, 0x11, 0xbd, 0xfe, 0x02, 0xd0, 0x56, 0x58, 0x7f, 0x2c, 0x18, + 0x12, 0x59, 0x72, 0xc3, 0x24, 0x01, 0x14, 0x00, 0x00, 0x0c, 0x09, 0x00, 0x26, 0x89, 0xdf, + 0xd6, 0xb7, 0x12, 0x14, 0x00, 0x00, 0x18, 0x15, 0x74, 0xd6, 0x4c, 0x01, 0x65, 0xba, 0xd1, + 0x6a, 0x02, 0x3f, 0x03, 0x8d, 0x45, 0xa0, 0x74, 0x98, 0xd8, 0xd0, 0x51, 0x00, 0x00, 0x00, + 0x18, 0xfe, 0xbf, 0x46, 0x2f, 0x1c, 0xd7, 0x58, 0x05, 0xa7, 0xba, 0xa2, 0x87, 0x47, 0xe7, + 0x69, 0xd6, 0x74, 0xf8, 0x56, 0x00 }; + + // the initiator sending key exchange + r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_IKE, STREAM_TOSERVER, + (uint8_t *)initiator_key, sizeof(initiator_key)); + FAIL_IF_NOT(r == 0); + + static const unsigned char responder_key[] = { 0xe4, 0x7a, 0x59, 0x1f, 0xd0, 0x57, 0x58, 0x7f, + 0xa0, 0x0b, 0x8e, 0xf0, 0x90, 0x2b, 0xb8, 0xec, 0x04, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x30, 0x0a, 0x00, 0x00, 0x84, 0x6d, 0x02, 0x6d, 0x56, 0x16, 0xc4, + 0x5b, 0xe0, 0x5e, 0x5b, 0x89, 0x84, 0x11, 0xe9, 0xf9, 0x5d, 0x19, 0x5c, 0xea, 0x00, 0x9a, + 0xd2, 0x2c, 0x62, 0xbe, 0xf0, 0x6c, 0x57, 0x1b, 0x7c, 0xfb, 0xc4, 0x79, 0x2f, 0x45, 0x56, + 0x4e, 0xc7, 0x10, 0xac, 0x58, 0x4a, 0xa1, 0x8d, 0x20, 0xcb, 0xc8, 0xf5, 0xf8, 0x91, 0x06, + 0x66, 0xb8, 0x9e, 0x4e, 0xe2, 0xf9, 0x5a, 0xbc, 0x02, 0x30, 0xe2, 0xcb, 0xa1, 0xb8, 0x8a, + 0xc4, 0xbb, 0xa7, 0xfc, 0xc8, 0x18, 0xa9, 0x86, 0xc0, 0x1a, 0x4c, 0xa8, 0x65, 0xa5, 0xeb, + 0x82, 0x88, 0x4d, 0xbe, 0xc8, 0x5b, 0xfd, 0x7d, 0x1a, 0x30, 0x3b, 0x09, 0x89, 0x4d, 0xcf, + 0x2e, 0x37, 0x85, 0xfd, 0x79, 0xdb, 0xa2, 0x25, 0x37, 0x7c, 0xf8, 0xcc, 0xa0, 0x09, 0xce, + 0xff, 0xbb, 0x6a, 0xa3, 0x8b, 0x64, 0x8c, 0x4b, 0x05, 0x40, 0x4f, 0x1c, 0xfa, 0xac, 0x36, + 0x1a, 0xff, 0x0d, 0x00, 0x00, 0x18, 0x15, 0xb6, 0x88, 0x42, 0x1e, 0xd5, 0xc3, 0xdd, 0x92, + 0xd3, 0xb8, 0x6e, 0x47, 0xa7, 0x6f, 0x0d, 0x39, 0xcc, 0x09, 0xe0, 0x0d, 0x00, 0x00, 0x14, + 0x12, 0xf5, 0xf2, 0x8c, 0x45, 0x71, 0x68, 0xa9, 0x70, 0x2d, 0x9f, 0xe2, 0x74, 0xcc, 0x01, + 0x00, 0x0d, 0x00, 0x00, 0x14, 0xaf, 0xca, 0xd7, 0x13, 0x68, 0xa1, 0xf1, 0xc9, 0x6b, 0x86, + 0x96, 0xfc, 0x77, 0x57, 0x01, 0x00, 0x0d, 0x00, 0x00, 0x14, 0x55, 0xcc, 0x29, 0xed, 0x90, + 0x2a, 0xb8, 0xec, 0x53, 0xb1, 0xdf, 0x86, 0x7c, 0x61, 0x09, 0x29, 0x14, 0x00, 0x00, 0x0c, + 0x09, 0x00, 0x26, 0x89, 0xdf, 0xd6, 0xb7, 0x12, 0x14, 0x00, 0x00, 0x18, 0xfe, 0xbf, 0x46, + 0x2f, 0x1c, 0xd7, 0x58, 0x05, 0xa7, 0xba, 0xa2, 0x87, 0x47, 0xe7, 0x69, 0xd6, 0x74, 0xf8, + 0x56, 0x00, 0x00, 0x00, 0x00, 0x18, 0x15, 0x74, 0xd6, 0x4c, 0x01, 0x65, 0xba, 0xd1, 0x6a, + 0x02, 0x3f, 0x03, 0x8d, 0x45, 0xa0, 0x74, 0x98, 0xd8, 0xd0, 0x51 }; + + // responder sending key exchange + r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_IKE, STREAM_TOCLIENT, + (uint8_t *)responder_key, sizeof(responder_key)); + FAIL_IF_NOT(r == 0); + + static const unsigned char encrypted[] = { 0xe4, 0x7a, 0x59, 0x1f, 0xd0, 0x57, 0x58, 0x7f, 0xa0, + 0x0b, 0x8e, 0xf0, 0x90, 0x2b, 0xb8, 0xec, 0x05, 0x10, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x6c, 0xa4, 0x85, 0xa5, 0xd5, 0x86, 0x8a, 0x3c, 0x92, 0x5d, 0xed, 0xf2, + 0xd1, 0x0d, 0x5e, 0x47, 0x11, 0x2b, 0xc2, 0x94, 0x60, 0x18, 0xc7, 0x61, 0x28, 0xed, 0x7b, + 0xb2, 0x9d, 0xb0, 0x61, 0xfd, 0xab, 0xf7, 0x9a, 0x18, 0xe7, 0x56, 0x89, 0x53, 0x6d, 0x27, + 0xcb, 0xe0, 0x92, 0x1d, 0x67, 0xf7, 0x02, 0xf3, 0x47, 0xae, 0x6e, 0x79, 0xde, 0xe1, 0x09, + 0x4d, 0xc8, 0x6a, 0x5a, 0x26, 0x44, 0x8a, 0xde, 0x72, 0x83, 0x06, 0x94, 0xe1, 0x5d, 0xca, + 0x2d, 0x96, 0x03, 0xeb, 0xc5, 0xf7, 0x90, 0x47, 0x3d }; + // the initiator sending encrypted data + r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_IKE, STREAM_TOSERVER, (uint8_t *)encrypted, + sizeof(encrypted)); + FAIL_IF_NOT(r == 0); + + AppLayerParserTransactionsCleanup(&f); + UTHAppLayerParserStateGetIds(f.alparser, &ret[0], &ret[1], &ret[2], &ret[3]); + FAIL_IF_NOT(ret[0] == 5); // inspect_id[0] + FAIL_IF_NOT(ret[1] == 5); // inspect_id[1] + FAIL_IF_NOT(ret[2] == 5); // log_id + FAIL_IF_NOT(ret[3] == 5); // min_id + + if (alp_tctx != NULL) { + AppLayerParserThreadCtxFree(alp_tctx); + } + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + PASS; +} +#endif + +void IKEParserRegisterTests(void) +{ +#ifdef UNITTESTS + UtRegisterTest("IkeParserTest", IkeParserTest); +#endif } diff --git a/src/app-layer-ike.h b/src/app-layer-ike.h index d8eff7a5c0..c6270cccc5 100644 --- a/src/app-layer-ike.h +++ b/src/app-layer-ike.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Open Information Security Foundation +/* Copyright (C) 2020 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 @@ -19,12 +19,14 @@ * \file * * \author Pierre Chifflier + * \author Frank Honza */ #ifndef __APP_LAYER_IKE_H__ #define __APP_LAYER_IKE_H__ void RegisterIKEParsers(void); +void IKEParserRegisterTests(void); /** Opaque Rust types. */ typedef struct IKEState_ IKEState; diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 1ee5ef91f6..d5ae96de74 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -256,6 +256,14 @@ #include "detect-modbus.h" #include "detect-cipservice.h" #include "detect-dnp3.h" +#include "detect-ike-exch-type.h" +#include "detect-ike-spi.h" +#include "detect-ike-vendor.h" +#include "detect-ike-chosen-sa.h" +#include "detect-ike-key-exchange-payload-length.h" +#include "detect-ike-nonce-payload-length.h" +#include "detect-ike-nonce-payload.h" +#include "detect-ike-key-exchange-payload.h" #include "action-globals.h" #include "tm-threads.h" @@ -486,6 +494,15 @@ void SigTableSetup(void) DetectEnipCommandRegister(); DetectDNP3Register(); + DetectIkeExchTypeRegister(); + DetectIkeSpiRegister(); + DetectIkeVendorRegister(); + DetectIkeChosenSaRegister(); + DetectIkeKeyExchangePayloadLengthRegister(); + DetectIkeNoncePayloadLengthRegister(); + DetectIkeNonceRegister(); + DetectIkeKeyExchangeRegister(); + DetectTlsSniRegister(); DetectTlsIssuerRegister(); DetectTlsSubjectRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index aea54e28f0..ab77074d44 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -299,6 +299,16 @@ enum DetectKeywordId { DETECT_TRANSFORM_PCREXFORM, DETECT_TRANSFORM_URL_DECODE, + DETECT_AL_IKE_EXCH_TYPE, + DETECT_AL_IKE_SPI_INITIATOR, + DETECT_AL_IKE_SPI_RESPONDER, + DETECT_AL_IKE_VENDOR, + DETECT_AL_IKE_CHOSEN_SA, + DETECT_AL_IKE_KEY_EXCHANGE_PAYLOAD_LENGTH, + DETECT_AL_IKE_NONCE_PAYLOAD_LENGTH, + DETECT_AL_IKE_NONCE, + DETECT_AL_IKE_KEY_EXCHANGE, + /* make sure this stays last */ DETECT_TBLSIZE, }; diff --git a/src/detect-ike-chosen-sa.c b/src/detect-ike-chosen-sa.c new file mode 100644 index 0000000000..d08785d148 --- /dev/null +++ b/src/detect-ike-chosen-sa.c @@ -0,0 +1,283 @@ +/* Copyright (C) 2020 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. + */ + +/** + * + * \author Frank Honza + */ + +#include "suricata-common.h" +#include "conf.h" +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-content-inspection.h" +#include "detect-ike-chosen-sa.h" +#include "app-layer-parser.h" +#include "util-byte.h" +#include "util-unittest.h" + +#include "rust-bindings.h" + +/** + * [ike.chosen_sa_attribute]:=; + */ + +// support the basic attributes, which are parsed as integer and life_duration, if variable length +// is 4 it is stored as integer too +#define PARSE_REGEX \ + "^\\s*(alg_enc|alg_hash|alg_auth|alg_dh|\ +sa_group_type|sa_life_type|sa_life_duration|alg_prf|sa_key_length|sa_field_size)\ +\\s*=\\s*([0-9]+)\\s*$" + +static DetectParseRegex parse_regex; + +typedef struct { + char *sa_type; + uint32_t sa_value; +} DetectIkeChosenSaData; + +static DetectIkeChosenSaData *DetectIkeChosenSaParse(const char *); +static int DetectIkeChosenSaSetup(DetectEngineCtx *, Signature *s, const char *str); +static void DetectIkeChosenSaFree(DetectEngineCtx *, void *); +static int g_ike_chosen_sa_buffer_id = 0; + +static int DetectEngineInspectIkeChosenSaGeneric(DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *det_ctx, const struct DetectEngineAppInspectionEngine_ *engine, + const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id); + +static int DetectIkeChosenSaMatch(DetectEngineThreadCtx *, Flow *, uint8_t, void *, void *, + const Signature *, const SigMatchCtx *); +void IKEChosenSaRegisterTests(void); + +/** + * \brief Registration function for ike.ChosenSa keyword. + */ +void DetectIkeChosenSaRegister(void) +{ + sigmatch_table[DETECT_AL_IKE_CHOSEN_SA].name = "ike.chosen_sa_attribute"; + sigmatch_table[DETECT_AL_IKE_CHOSEN_SA].desc = "match IKE chosen SA Attribute"; + sigmatch_table[DETECT_AL_IKE_CHOSEN_SA].url = + "/rules/ike-keywords.html#ike-chosen_sa_attribute"; + sigmatch_table[DETECT_AL_IKE_CHOSEN_SA].AppLayerTxMatch = DetectIkeChosenSaMatch; + sigmatch_table[DETECT_AL_IKE_CHOSEN_SA].Setup = DetectIkeChosenSaSetup; + sigmatch_table[DETECT_AL_IKE_CHOSEN_SA].Free = DetectIkeChosenSaFree; +#ifdef UNITTESTS + sigmatch_table[DETECT_AL_IKE_CHOSEN_SA].RegisterTests = IKEChosenSaRegisterTests; +#endif + DetectSetupParseRegexes(PARSE_REGEX, &parse_regex); + + DetectAppLayerInspectEngineRegister2("ike.chosen_sa_attribute", ALPROTO_IKE, SIG_FLAG_TOCLIENT, + 1, DetectEngineInspectIkeChosenSaGeneric, NULL); + + g_ike_chosen_sa_buffer_id = DetectBufferTypeGetByName("ike.chosen_sa_attribute"); +} + +static int DetectEngineInspectIkeChosenSaGeneric(DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *det_ctx, const struct DetectEngineAppInspectionEngine_ *engine, + const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id) +{ + return DetectEngineInspectGenericList( + de_ctx, det_ctx, s, engine->smd, f, flags, alstate, txv, tx_id); +} + +/** + * \internal + * \brief Function to match SA attributes of a IKE state + * + * \param det_ctx Pointer to the pattern matcher thread. + * \param f Pointer to the current flow. + * \param flags Flags. + * \param state App layer state. + * \param txv Pointer to the Ike Transaction. + * \param s Pointer to the Signature. + * \param ctx Pointer to the sigmatch that we will cast into DetectIkeChosenSaData. + * + * \retval 0 no match. + * \retval 1 match. + */ +static int DetectIkeChosenSaMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) +{ + SCEnter(); + + const DetectIkeChosenSaData *dd = (const DetectIkeChosenSaData *)ctx; + + uint32_t value; + if (!rs_ike_state_get_sa_attribute(txv, dd->sa_type, &value)) + SCReturnInt(0); + if (value == dd->sa_value) + SCReturnInt(1); + SCReturnInt(0); +} + +/** + * \internal + * \brief Function to parse options passed via ike.chosen_sa_attribute keywords. + * + * \param rawstr Pointer to the user provided options. + * + * \retval dd pointer to DetectIkeChosenSaData on success. + * \retval NULL on failure. + */ +static DetectIkeChosenSaData *DetectIkeChosenSaParse(const char *rawstr) +{ + /* + * idea: do not implement one c file per type, invent an own syntax: + * ike.chosen_sa_attribute:"encryption_algorithm=4" + * ike.chosen_sa_attribute:"hash_algorithm=8" + */ + DetectIkeChosenSaData *dd = NULL; + int ret = 0, res = 0; + int ov[MAX_SUBSTRINGS]; + char attribute[100]; + char value[100]; + + ret = DetectParsePcreExec(&parse_regex, rawstr, 0, 0, ov, MAX_SUBSTRINGS); + if (ret < 3 || ret > 5) { + SCLogError(SC_ERR_PCRE_MATCH, + "pcre match for ike.chosen_sa_attribute failed, should be: =, " + "but was: %s; error code %d", + rawstr, ret); + goto error; + } + + res = pcre_copy_substring((char *)rawstr, ov, MAX_SUBSTRINGS, 1, attribute, sizeof(attribute)); + if (res < 0) { + SCLogError(SC_ERR_PCRE_COPY_SUBSTRING, "pcre_copy_substring failed"); + goto error; + } + + res = pcre_copy_substring((char *)rawstr, ov, MAX_SUBSTRINGS, 2, value, sizeof(value)); + if (res < 0) { + SCLogError(SC_ERR_PCRE_COPY_SUBSTRING, "pcre_copy_substring failed"); + goto error; + } + + dd = SCCalloc(1, sizeof(DetectIkeChosenSaData)); + if (unlikely(dd == NULL)) + goto error; + + dd->sa_type = SCStrdup(attribute); + if (dd->sa_type == NULL) + goto error; + + if (ByteExtractStringUint32(&dd->sa_value, 10, strlen(value), value) <= 0) { + SCLogError(SC_ERR_INVALID_SIGNATURE, "invalid input as arg " + "to ike.chosen_sa_attribute keyword"); + goto error; + } + + return dd; + +error: + if (dd) { + if (dd->sa_type != NULL) + SCFree(dd->sa_type); + SCFree(dd); + } + return NULL; +} + +/** + * \brief Function to add the parsed IKE SA attribute query into the current signature. + * + * \param de_ctx Pointer to the Detection Engine Context. + * \param s Pointer to the Current Signature. + * \param rawstr Pointer to the user provided flags options. + * + * \retval 0 on Success. + * \retval -1 on Failure. + */ +static int DetectIkeChosenSaSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_IKE) != 0) + return -1; + + DetectIkeChosenSaData *dd = DetectIkeChosenSaParse(rawstr); + if (dd == NULL) { + SCLogError(SC_ERR_INVALID_ARGUMENT, "Parsing \'%s\' failed", rawstr); + goto error; + } + + /* okay so far so good, lets get this into a SigMatch + * and put it in the Signature. */ + SigMatch *sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_AL_IKE_CHOSEN_SA; + sm->ctx = (void *)dd; + + SigMatchAppendSMToList(s, sm, g_ike_chosen_sa_buffer_id); + return 0; + +error: + DetectIkeChosenSaFree(de_ctx, dd); + return -1; +} + +/** + * \internal + * \brief Function to free memory associated with DetectIkeChosenSaData. + * + * \param de_ptr Pointer to DetectIkeChosenSaData. + */ +static void DetectIkeChosenSaFree(DetectEngineCtx *de_ctx, void *ptr) +{ + DetectIkeChosenSaData *dd = (DetectIkeChosenSaData *)ptr; + if (dd == NULL) + return; + if (dd->sa_type != NULL) + SCFree(dd->sa_type); + + SCFree(ptr); +} + +/* + * ONLY TESTS BELOW THIS COMMENT + */ + +#ifdef UNITTESTS + +/** + * \test IKEChosenSaParserTest is a test for valid values + * + * \retval 1 on success + * \retval 0 on failure + */ +static int IKEChosenSaParserTest(void) +{ + DetectIkeChosenSaData *de = NULL; + de = DetectIkeChosenSaParse("alg_hash=2"); + + FAIL_IF_NULL(de); + FAIL_IF(de->sa_value != 2); + FAIL_IF(strcmp(de->sa_type, "alg_hash") != 0); + + DetectIkeChosenSaFree(NULL, de); + PASS; +} + +#endif /* UNITTESTS */ + +void IKEChosenSaRegisterTests(void) +{ +#ifdef UNITTESTS + UtRegisterTest("IKEChosenSaParserTest", IKEChosenSaParserTest); +#endif /* UNITTESTS */ +} diff --git a/src/detect-ike-chosen-sa.h b/src/detect-ike-chosen-sa.h new file mode 100644 index 0000000000..efbd5fe4c2 --- /dev/null +++ b/src/detect-ike-chosen-sa.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2020 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 Frank Honza + */ + +#ifndef __DETECT_IKE_CHOSEN_SA_H__ +#define __DETECT_IKE_CHOSEN_SA_H__ + +void DetectIkeChosenSaRegister(void); + +#endif /* __DETECT_IKE_CHOSEN_SA_H__ */ diff --git a/src/detect-ike-exch-type.c b/src/detect-ike-exch-type.c new file mode 100644 index 0000000000..8132e0a24f --- /dev/null +++ b/src/detect-ike-exch-type.c @@ -0,0 +1,156 @@ +/* Copyright (C) 2020 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. + */ + +/** + * + * \author Frank Honza + */ + +#include "suricata-common.h" +#include "conf.h" +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-content-inspection.h" +#include "detect-ike-exch-type.h" +#include "app-layer-parser.h" +#include "util-byte.h" +#include "detect-engine-uint.h" + +#include "rust-bindings.h" + +/** + * [ike.exchtype]:[<|>|<=|>=]; + */ + +static int DetectIkeExchTypeSetup(DetectEngineCtx *, Signature *s, const char *str); +static void DetectIkeExchTypeFree(DetectEngineCtx *, void *); +static int g_ike_exch_type_buffer_id = 0; + +static int DetectEngineInspectIkeExchTypeGeneric(DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *det_ctx, const struct DetectEngineAppInspectionEngine_ *engine, + const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id); + +static int DetectIkeExchTypeMatch(DetectEngineThreadCtx *, Flow *, uint8_t, void *, void *, + const Signature *, const SigMatchCtx *); + +/** + * \brief Registration function for ike.exchtype keyword. + */ +void DetectIkeExchTypeRegister(void) +{ + sigmatch_table[DETECT_AL_IKE_EXCH_TYPE].name = "ike.exchtype"; + sigmatch_table[DETECT_AL_IKE_EXCH_TYPE].desc = "match IKE exchange type"; + sigmatch_table[DETECT_AL_IKE_EXCH_TYPE].url = "/rules/ike-keywords.html#ike-exchtype"; + sigmatch_table[DETECT_AL_IKE_EXCH_TYPE].Match = NULL; + sigmatch_table[DETECT_AL_IKE_EXCH_TYPE].AppLayerTxMatch = DetectIkeExchTypeMatch; + sigmatch_table[DETECT_AL_IKE_EXCH_TYPE].Setup = DetectIkeExchTypeSetup; + sigmatch_table[DETECT_AL_IKE_EXCH_TYPE].Free = DetectIkeExchTypeFree; + + DetectAppLayerInspectEngineRegister2("ike.exchtype", ALPROTO_IKE, SIG_FLAG_TOSERVER, 1, + DetectEngineInspectIkeExchTypeGeneric, NULL); + + DetectAppLayerInspectEngineRegister2("ike.exchtype", ALPROTO_IKE, SIG_FLAG_TOCLIENT, 1, + DetectEngineInspectIkeExchTypeGeneric, NULL); + + g_ike_exch_type_buffer_id = DetectBufferTypeGetByName("ike.exchtype"); + + DetectUintRegister(); +} + +static int DetectEngineInspectIkeExchTypeGeneric(DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *det_ctx, const struct DetectEngineAppInspectionEngine_ *engine, + const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id) +{ + return DetectEngineInspectGenericList( + de_ctx, det_ctx, s, engine->smd, f, flags, alstate, txv, tx_id); +} + +/** + * \internal + * \brief Function to match exchange type of a IKE state + * + * \param det_ctx Pointer to the pattern matcher thread. + * \param f Pointer to the current flow. + * \param flags Flags. + * \param state App layer state. + * \param txv Pointer to the Ike Transaction. + * \param s Pointer to the Signature. + * \param ctx Pointer to the sigmatch that we will cast into DetectU8Data. + * + * \retval 0 no match. + * \retval 1 match. + */ +static int DetectIkeExchTypeMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) +{ + SCEnter(); + + uint8_t exch_type; + if (!rs_ike_state_get_exch_type(txv, &exch_type)) + SCReturnInt(0); + + const DetectU8Data *du8 = (const DetectU8Data *)ctx; + SCReturnInt(DetectU8Match(exch_type, du8)); +} + +/** + * \brief Function to add the parsed IKE exchange type field into the current signature. + * + * \param de_ctx Pointer to the Detection Engine Context. + * \param s Pointer to the Current Signature. + * \param rawstr Pointer to the user provided flags options. + * + * \retval 0 on Success. + * \retval -1 on Failure. + */ +static int DetectIkeExchTypeSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_IKE) != 0) + return -1; + + DetectU8Data *ike_exch_type = DetectU8Parse(rawstr); + if (ike_exch_type == NULL) + return -1; + + /* okay so far so good, lets get this into a SigMatch + * and put it in the Signature. */ + SigMatch *sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_AL_IKE_EXCH_TYPE; + sm->ctx = (SigMatchCtx *)ike_exch_type; + + SigMatchAppendSMToList(s, sm, g_ike_exch_type_buffer_id); + return 0; + +error: + DetectIkeExchTypeFree(de_ctx, ike_exch_type); + return -1; +} + +/** + * \internal + * \brief Function to free memory associated with DetectU8Data. + * + * \param de_ptr Pointer to DetectU8Data. + */ +static void DetectIkeExchTypeFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCFree(ptr); +} diff --git a/src/detect-ike-exch-type.h b/src/detect-ike-exch-type.h new file mode 100644 index 0000000000..f7360eb21c --- /dev/null +++ b/src/detect-ike-exch-type.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2020 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 Frank Honza + */ + +#ifndef __DETECT_IKE_EXCH_TYPE_H__ +#define __DETECT_IKE_EXCH_TYPE_H__ + +void DetectIkeExchTypeRegister(void); + +#endif /* __DETECT_IKE_EXCH_TYPE_H__ */ diff --git a/src/detect-ike-key-exchange-payload-length.c b/src/detect-ike-key-exchange-payload-length.c new file mode 100644 index 0000000000..65580e8393 --- /dev/null +++ b/src/detect-ike-key-exchange-payload-length.c @@ -0,0 +1,162 @@ +/* Copyright (C) 2020 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. + */ + +/** + * + * \author Frank Honza + */ + +#include "suricata-common.h" +#include "conf.h" +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-content-inspection.h" +#include "detect-ike-key-exchange-payload-length.h" +#include "app-layer-parser.h" +#include "util-byte.h" +#include "detect-engine-uint.h" + +#include "rust-bindings.h" + +/** + * [ike.key_exchange_payload_length]:[=|<|>|<=|>=]; + */ +static int DetectIkeKeyExchangePayloadLengthSetup(DetectEngineCtx *, Signature *s, const char *str); +static void DetectIkeKeyExchangePayloadLengthFree(DetectEngineCtx *, void *); +static int g_ike_key_exch_payload_length_buffer_id = 0; + +static int DetectEngineInspectIkeKeyExchangePayloadLengthGeneric(DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *det_ctx, const struct DetectEngineAppInspectionEngine_ *engine, + const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id); + +static int DetectIkeKeyExchangePayloadLengthMatch(DetectEngineThreadCtx *, Flow *, uint8_t, void *, + void *, const Signature *, const SigMatchCtx *); + +/** + * \brief Registration function for ike.key_exchange_payload_length keyword. + */ +void DetectIkeKeyExchangePayloadLengthRegister(void) +{ + sigmatch_table[DETECT_AL_IKE_KEY_EXCHANGE_PAYLOAD_LENGTH].name = + "ike.key_exchange_payload_length"; + sigmatch_table[DETECT_AL_IKE_KEY_EXCHANGE_PAYLOAD_LENGTH].desc = + "match IKE key exchange payload length"; + sigmatch_table[DETECT_AL_IKE_KEY_EXCHANGE_PAYLOAD_LENGTH].url = + "/rules/ike-keywords.html#ike-key-exchange-payload-length"; + sigmatch_table[DETECT_AL_IKE_KEY_EXCHANGE_PAYLOAD_LENGTH].AppLayerTxMatch = + DetectIkeKeyExchangePayloadLengthMatch; + sigmatch_table[DETECT_AL_IKE_KEY_EXCHANGE_PAYLOAD_LENGTH].Setup = + DetectIkeKeyExchangePayloadLengthSetup; + sigmatch_table[DETECT_AL_IKE_KEY_EXCHANGE_PAYLOAD_LENGTH].Free = + DetectIkeKeyExchangePayloadLengthFree; + + DetectAppLayerInspectEngineRegister2("ike.key_exchange_payload_length", ALPROTO_IKE, + SIG_FLAG_TOSERVER, 1, DetectEngineInspectIkeKeyExchangePayloadLengthGeneric, NULL); + + DetectAppLayerInspectEngineRegister2("ike.key_exchange_payload_length", ALPROTO_IKE, + SIG_FLAG_TOCLIENT, 1, DetectEngineInspectIkeKeyExchangePayloadLengthGeneric, NULL); + + g_ike_key_exch_payload_length_buffer_id = + DetectBufferTypeGetByName("ike.key_exchange_payload_length"); + + DetectUintRegister(); +} + +static int DetectEngineInspectIkeKeyExchangePayloadLengthGeneric(DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *det_ctx, const struct DetectEngineAppInspectionEngine_ *engine, + const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id) +{ + return DetectEngineInspectGenericList( + de_ctx, det_ctx, s, engine->smd, f, flags, alstate, txv, tx_id); +} + +/** + * \internal + * \brief Function to match key exchange payload length of a IKE state + * + * \param det_ctx Pointer to the pattern matcher thread. + * \param f Pointer to the current flow. + * \param flags Flags. + * \param state App layer state. + * \param txv Pointer to the Ike Transaction. + * \param s Pointer to the Signature. + * \param ctx Pointer to the sigmatch that we will cast into DetectU32Data. + * + * \retval 0 no match. + * \retval 1 match. + */ +static int DetectIkeKeyExchangePayloadLengthMatch(DetectEngineThreadCtx *det_ctx, Flow *f, + uint8_t flags, void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) +{ + SCEnter(); + + uint32_t length; + if (!rs_ike_state_get_key_exchange_payload_length(txv, &length)) + SCReturnInt(0); + const DetectU32Data *du32 = (const DetectU32Data *)ctx; + return DetectU32Match(length, du32); +} + +/** + * \brief Function to add the parsed IKE key exchange payload length query into the current + * signature. + * + * \param de_ctx Pointer to the Detection Engine Context. + * \param s Pointer to the Current Signature. + * \param rawstr Pointer to the user provided flags options. + * + * \retval 0 on Success. + * \retval -1 on Failure. + */ +static int DetectIkeKeyExchangePayloadLengthSetup( + DetectEngineCtx *de_ctx, Signature *s, const char *rawstr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_IKE) != 0) + return -1; + + DetectU32Data *key_exchange_payload_length = DetectU32Parse(rawstr); + if (key_exchange_payload_length == NULL) + return -1; + + /* okay so far so good, lets get this into a SigMatch + * and put it in the Signature. */ + SigMatch *sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_AL_IKE_KEY_EXCHANGE_PAYLOAD_LENGTH; + sm->ctx = (SigMatchCtx *)key_exchange_payload_length; + + SigMatchAppendSMToList(s, sm, g_ike_key_exch_payload_length_buffer_id); + return 0; + +error: + DetectIkeKeyExchangePayloadLengthFree(de_ctx, key_exchange_payload_length); + return -1; +} + +/** + * \internal + * \brief Function to free memory associated with DetectU32Data. + * + * \param de_ptr Pointer to DetectU32Data. + */ +static void DetectIkeKeyExchangePayloadLengthFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCFree(ptr); +} diff --git a/src/detect-ike-key-exchange-payload-length.h b/src/detect-ike-key-exchange-payload-length.h new file mode 100644 index 0000000000..7d76e558b9 --- /dev/null +++ b/src/detect-ike-key-exchange-payload-length.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2020 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. + */ + +/** + * + * \author Frank Honza + */ + +#ifndef __DETECT_IKE_KEY_EXCHANGE_PAYLOAD_LENGTH_H__ +#define __DETECT_IKE_KEY_EXCHANGE_PAYLOAD_LENGTH_H__ + +void DetectIkeKeyExchangePayloadLengthRegister(void); + +#endif /* __DETECT_IKE_KEY_EXCHANGE_PAYLOAD_LENGTH_H__ */ diff --git a/src/detect-ike-key-exchange-payload.c b/src/detect-ike-key-exchange-payload.c new file mode 100644 index 0000000000..a11227ecda --- /dev/null +++ b/src/detect-ike-key-exchange-payload.c @@ -0,0 +1,120 @@ +/* Copyright (C) 2020 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. + */ + +/** + * + * \author Frank Honza + */ + +#include "suricata-common.h" +#include "threads.h" +#include "debug.h" +#include "decode.h" +#include "detect.h" + +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-mpm.h" +#include "detect-engine-prefilter.h" +#include "detect-urilen.h" + +#include "flow.h" +#include "flow-var.h" +#include "flow-util.h" + +#include "util-debug.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" +#include "util-spm.h" + +#include "app-layer.h" +#include "app-layer-parser.h" + +#include "detect-ike-key-exchange-payload.h" +#include "stream-tcp.h" + +#include "rust.h" +#include "app-layer-ike.h" +#include "rust-bindings.h" + +#define KEYWORD_NAME_KEY_EXCHANGE "ike.key_exchange_payload" +#define KEYWORD_DOC_KEY_EXCHANGE "ike-keywords.html#ike-key_exchange_payload"; +#define BUFFER_NAME_KEY_EXCHANGE "ike.key_exchange_payload" +#define BUFFER_DESC_KEY_EXCHANGE "ike key_exchange payload" + +static int g_buffer_key_exchange_id = 0; + +static int DetectKeyExchangeSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + if (DetectBufferSetActiveList(s, g_buffer_key_exchange_id) < 0) + return -1; + + if (DetectSignatureSetAppProto(s, ALPROTO_IKE) < 0) + return -1; + + return 0; +} + +static InspectionBuffer *GetKeyExchangeData(DetectEngineThreadCtx *det_ctx, + const DetectEngineTransforms *transforms, Flow *_f, const uint8_t _flow_flags, void *txv, + const int list_id) +{ + InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id); + if (buffer->inspect == NULL) { + const uint8_t *b = NULL; + uint32_t b_len = 0; + + if (rs_ike_state_get_key_exchange(txv, &b, &b_len) != 1) + return NULL; + if (b == NULL || b_len == 0) + return NULL; + + InspectionBufferSetup(det_ctx, list_id, buffer, b, b_len); + InspectionBufferApplyTransforms(buffer, transforms); + } + + return buffer; +} + +void DetectIkeKeyExchangeRegister(void) +{ + // register key_exchange + sigmatch_table[DETECT_AL_IKE_KEY_EXCHANGE].name = KEYWORD_NAME_KEY_EXCHANGE; + sigmatch_table[DETECT_AL_IKE_KEY_EXCHANGE].url = + "/rules/" KEYWORD_DOC_KEY_EXCHANGE sigmatch_table[DETECT_AL_IKE_KEY_EXCHANGE].desc = + "sticky buffer to match on the IKE key_exchange_payload"; + sigmatch_table[DETECT_AL_IKE_KEY_EXCHANGE].Setup = DetectKeyExchangeSetup; + sigmatch_table[DETECT_AL_IKE_KEY_EXCHANGE].flags |= + SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; + + DetectAppLayerInspectEngineRegister2(BUFFER_NAME_KEY_EXCHANGE, ALPROTO_IKE, SIG_FLAG_TOSERVER, + 1, DetectEngineInspectBufferGeneric, GetKeyExchangeData); + + DetectAppLayerMpmRegister2(BUFFER_NAME_KEY_EXCHANGE, SIG_FLAG_TOSERVER, 1, + PrefilterGenericMpmRegister, GetKeyExchangeData, ALPROTO_IKE, 1); + + DetectAppLayerInspectEngineRegister2(BUFFER_NAME_KEY_EXCHANGE, ALPROTO_IKE, SIG_FLAG_TOCLIENT, + 1, DetectEngineInspectBufferGeneric, GetKeyExchangeData); + + DetectAppLayerMpmRegister2(BUFFER_NAME_KEY_EXCHANGE, SIG_FLAG_TOCLIENT, 1, + PrefilterGenericMpmRegister, GetKeyExchangeData, ALPROTO_IKE, 1); + + DetectBufferTypeSetDescriptionByName(BUFFER_NAME_KEY_EXCHANGE, BUFFER_DESC_KEY_EXCHANGE); + + g_buffer_key_exchange_id = DetectBufferTypeGetByName(BUFFER_NAME_KEY_EXCHANGE); + SCLogDebug("registering " BUFFER_NAME_KEY_EXCHANGE " rule option"); +} diff --git a/src/detect-ike-key-exchange-payload.h b/src/detect-ike-key-exchange-payload.h new file mode 100644 index 0000000000..55a914b5c3 --- /dev/null +++ b/src/detect-ike-key-exchange-payload.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2020 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. + */ + +/** + * + * \author Frank Honza + */ + +#ifndef __DETECT_IKE_KEY_EXCHANGE_PAYLOAD_H__ +#define __DETECT_IKE_KEY_EXCHANGE_PAYLOAD_H__ + +void DetectIkeKeyExchangeRegister(void); + +#endif /* __DETECT_IKE_KEY_EXCHANGE_PAYLOAD_H__ */ diff --git a/src/detect-ike-nonce-payload-length.c b/src/detect-ike-nonce-payload-length.c new file mode 100644 index 0000000000..d7091b2472 --- /dev/null +++ b/src/detect-ike-nonce-payload-length.c @@ -0,0 +1,156 @@ +/* Copyright (C) 2020 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. + */ + +/** + * + * \author Frank Honza + */ + +#include "suricata-common.h" +#include "conf.h" +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-content-inspection.h" +#include "detect-ike-nonce-payload-length.h" +#include "app-layer-parser.h" +#include "util-byte.h" +#include "detect-engine-uint.h" + +#include "rust-bindings.h" + +/** + * [ike.nonce_payload_length]:[=|<|>|<=|>=]; + */ +static int DetectIkeNoncePayloadLengthSetup(DetectEngineCtx *, Signature *s, const char *str); +static void DetectIkeNoncePayloadLengthFree(DetectEngineCtx *, void *); +static int g_ike_nonce_payload_length_buffer_id = 0; + +static int DetectEngineInspectIkeNoncePayloadLengthGeneric(DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *det_ctx, const struct DetectEngineAppInspectionEngine_ *engine, + const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id); + +static int DetectIkeNoncePayloadLengthMatch(DetectEngineThreadCtx *, Flow *, uint8_t, void *, + void *, const Signature *, const SigMatchCtx *); + +/** + * \brief Registration function for ike.nonce_payload_length keyword. + */ +void DetectIkeNoncePayloadLengthRegister(void) +{ + sigmatch_table[DETECT_AL_IKE_NONCE_PAYLOAD_LENGTH].name = "ike.nonce_payload_length"; + sigmatch_table[DETECT_AL_IKE_NONCE_PAYLOAD_LENGTH].desc = "match IKE nonce payload length"; + sigmatch_table[DETECT_AL_IKE_NONCE_PAYLOAD_LENGTH].url = + "/rules/ike-keywords.html#ike-nonce-payload-length"; + sigmatch_table[DETECT_AL_IKE_NONCE_PAYLOAD_LENGTH].AppLayerTxMatch = + DetectIkeNoncePayloadLengthMatch; + sigmatch_table[DETECT_AL_IKE_NONCE_PAYLOAD_LENGTH].Setup = DetectIkeNoncePayloadLengthSetup; + sigmatch_table[DETECT_AL_IKE_NONCE_PAYLOAD_LENGTH].Free = DetectIkeNoncePayloadLengthFree; + + DetectAppLayerInspectEngineRegister2("ike.nonce_payload_length", ALPROTO_IKE, SIG_FLAG_TOSERVER, + 1, DetectEngineInspectIkeNoncePayloadLengthGeneric, NULL); + + DetectAppLayerInspectEngineRegister2("ike.nonce_payload_length", ALPROTO_IKE, SIG_FLAG_TOCLIENT, + 1, DetectEngineInspectIkeNoncePayloadLengthGeneric, NULL); + + g_ike_nonce_payload_length_buffer_id = DetectBufferTypeGetByName("ike.nonce_payload_length"); + + DetectUintRegister(); +} + +static int DetectEngineInspectIkeNoncePayloadLengthGeneric(DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *det_ctx, const struct DetectEngineAppInspectionEngine_ *engine, + const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id) +{ + return DetectEngineInspectGenericList( + de_ctx, det_ctx, s, engine->smd, f, flags, alstate, txv, tx_id); +} + +/** + * \internal + * \brief Function to match nonce length of a IKE state + * + * \param det_ctx Pointer to the pattern matcher thread. + * \param f Pointer to the current flow. + * \param flags Flags. + * \param state App layer state. + * \param txv Pointer to the Ike Transaction. + * \param s Pointer to the Signature. + * \param ctx Pointer to the sigmatch that we will cast into DetectU32Data. + * + * \retval 0 no match. + * \retval 1 match. + */ +static int DetectIkeNoncePayloadLengthMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) +{ + SCEnter(); + + uint32_t length; + if (!rs_ike_state_get_nonce_payload_length(txv, &length)) + SCReturnInt(0); + const DetectU32Data *du32 = (const DetectU32Data *)ctx; + return DetectU32Match(length, du32); +} + +/** + * \brief Function to add the parsed IKE nonce length field into the current signature. + * + * \param de_ctx Pointer to the Detection Engine Context. + * \param s Pointer to the Current Signature. + * \param rawstr Pointer to the user provided flags options. + * + * \retval 0 on Success. + * \retval -1 on Failure. + */ +static int DetectIkeNoncePayloadLengthSetup( + DetectEngineCtx *de_ctx, Signature *s, const char *rawstr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_IKE) != 0) + return -1; + + DetectU32Data *nonce_payload_length = DetectU32Parse(rawstr); + if (nonce_payload_length == NULL) + return -1; + + /* okay so far so good, lets get this into a SigMatch + * and put it in the Signature. */ + SigMatch *sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_AL_IKE_NONCE_PAYLOAD_LENGTH; + sm->ctx = (SigMatchCtx *)nonce_payload_length; + + SigMatchAppendSMToList(s, sm, g_ike_nonce_payload_length_buffer_id); + return 0; + +error: + DetectIkeNoncePayloadLengthFree(de_ctx, nonce_payload_length); + return -1; +} + +/** + * \internal + * \brief Function to free memory associated with DetectU32Data. + * + * \param de_ptr Pointer to DetectU32Data. + */ +static void DetectIkeNoncePayloadLengthFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCFree(ptr); +} diff --git a/src/detect-ike-nonce-payload-length.h b/src/detect-ike-nonce-payload-length.h new file mode 100644 index 0000000000..8a17f5530a --- /dev/null +++ b/src/detect-ike-nonce-payload-length.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2020 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. + */ + +/** + * + * \author Frank Honza + */ + +#ifndef __DETECT_IKE_NONCE_PAYLOAD_LENGTH_H__ +#define __DETECT_IKE_NONCE_PAYLOAD_LENGTH_H__ + +void DetectIkeNoncePayloadLengthRegister(void); + +#endif /* __DETECT_IKE_NONCE_PAYLOAD_LENGTH_H__ */ diff --git a/src/detect-ike-nonce-payload.c b/src/detect-ike-nonce-payload.c new file mode 100644 index 0000000000..168613cba1 --- /dev/null +++ b/src/detect-ike-nonce-payload.c @@ -0,0 +1,119 @@ +/* Copyright (C) 2020 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. + */ + +/** + * + * \author Frank Honza + */ + +#include "suricata-common.h" +#include "threads.h" +#include "debug.h" +#include "decode.h" +#include "detect.h" + +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-mpm.h" +#include "detect-engine-prefilter.h" +#include "detect-urilen.h" + +#include "flow.h" +#include "flow-var.h" +#include "flow-util.h" + +#include "util-debug.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" +#include "util-spm.h" + +#include "app-layer.h" +#include "app-layer-parser.h" + +#include "detect-ike-nonce-payload.h" +#include "stream-tcp.h" + +#include "rust.h" +#include "app-layer-ike.h" +#include "rust-bindings.h" + +#define KEYWORD_NAME_NONCE "ike.nonce_payload" +#define KEYWORD_DOC_NONCE "ike-keywords.html#ike-nonce_payload"; +#define BUFFER_NAME_NONCE "ike.nonce_payload" +#define BUFFER_DESC_NONCE "ike nonce payload" + +static int g_buffer_nonce_id = 0; + +static int DetectNonceSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + if (DetectBufferSetActiveList(s, g_buffer_nonce_id) < 0) + return -1; + + if (DetectSignatureSetAppProto(s, ALPROTO_IKE) < 0) + return -1; + + return 0; +} + +static InspectionBuffer *GetNonceData(DetectEngineThreadCtx *det_ctx, + const DetectEngineTransforms *transforms, Flow *_f, const uint8_t _flow_flags, void *txv, + const int list_id) +{ + InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id); + if (buffer->inspect == NULL) { + const uint8_t *b = NULL; + uint32_t b_len = 0; + + if (rs_ike_state_get_nonce(txv, &b, &b_len) != 1) + return NULL; + if (b == NULL || b_len == 0) + return NULL; + + InspectionBufferSetup(det_ctx, list_id, buffer, b, b_len); + InspectionBufferApplyTransforms(buffer, transforms); + } + + return buffer; +} + +void DetectIkeNonceRegister(void) +{ + // register nonce + sigmatch_table[DETECT_AL_IKE_NONCE].name = KEYWORD_NAME_NONCE; + sigmatch_table[DETECT_AL_IKE_NONCE].url = + "/rules/" KEYWORD_DOC_NONCE sigmatch_table[DETECT_AL_IKE_NONCE].desc = + "sticky buffer to match on the IKE nonce_payload"; + sigmatch_table[DETECT_AL_IKE_NONCE].Setup = DetectNonceSetup; + sigmatch_table[DETECT_AL_IKE_NONCE].flags |= SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; + + DetectAppLayerInspectEngineRegister2(BUFFER_NAME_NONCE, ALPROTO_IKE, SIG_FLAG_TOSERVER, 1, + DetectEngineInspectBufferGeneric, GetNonceData); + + DetectAppLayerMpmRegister2(BUFFER_NAME_NONCE, SIG_FLAG_TOSERVER, 1, PrefilterGenericMpmRegister, + GetNonceData, ALPROTO_IKE, 1); + + DetectAppLayerInspectEngineRegister2(BUFFER_NAME_NONCE, ALPROTO_IKE, SIG_FLAG_TOCLIENT, 1, + DetectEngineInspectBufferGeneric, GetNonceData); + + DetectAppLayerMpmRegister2(BUFFER_NAME_NONCE, SIG_FLAG_TOCLIENT, 1, PrefilterGenericMpmRegister, + GetNonceData, ALPROTO_IKE, 1); + + DetectBufferTypeSetDescriptionByName(BUFFER_NAME_NONCE, BUFFER_DESC_NONCE); + + g_buffer_nonce_id = DetectBufferTypeGetByName(BUFFER_NAME_NONCE); + SCLogDebug("registering " BUFFER_NAME_NONCE " rule option"); +} diff --git a/src/detect-ike-nonce-payload.h b/src/detect-ike-nonce-payload.h new file mode 100644 index 0000000000..f0923d8776 --- /dev/null +++ b/src/detect-ike-nonce-payload.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2020 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. + */ + +/** + * + * \author Frank Honza + */ + +#ifndef __DETECT_IKE_NONCE_PAYLOAD_H__ +#define __DETECT_IKE_NONCE_PAYLOAD_H__ + +void DetectIkeNonceRegister(void); + +#endif /* __DETECT_IKE_NONCE_PAYLOAD_H__ */ diff --git a/src/detect-ike-spi.c b/src/detect-ike-spi.c new file mode 100644 index 0000000000..7699f9c67c --- /dev/null +++ b/src/detect-ike-spi.c @@ -0,0 +1,172 @@ +/* Copyright (C) 2020 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. + */ + +/** + * + * \author Frank Honza + */ + +#include "suricata-common.h" +#include "threads.h" +#include "debug.h" +#include "decode.h" +#include "detect.h" + +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-mpm.h" +#include "detect-engine-prefilter.h" +#include "detect-urilen.h" + +#include "flow.h" +#include "flow-var.h" +#include "flow-util.h" + +#include "util-debug.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" +#include "util-spm.h" + +#include "app-layer.h" +#include "app-layer-parser.h" + +#include "detect-ike-spi.h" +#include "stream-tcp.h" + +#include "rust.h" +#include "app-layer-ike.h" +#include "rust-bindings.h" + +#define KEYWORD_NAME_INITIATOR "ike.init_spi" +#define KEYWORD_DOC_INITIATOR "ike-keywords.html#ike-init_spi"; +#define BUFFER_NAME_INITIATOR "ike.init_spi" +#define BUFFER_DESC_INITIATOR "ike init spi" + +#define KEYWORD_NAME_RESPONDER "ike.resp_spi" +#define KEYWORD_DOC_RESPONDER "ike-keywords.html#ike-resp_spi"; +#define BUFFER_NAME_RESPONDER "ike.resp_spi" +#define BUFFER_DESC_RESPONDER "ike resp spi" + +static int g_buffer_initiator_id = 0; +static int g_buffer_responder_id = 0; + +static int DetectSpiInitiatorSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + if (DetectBufferSetActiveList(s, g_buffer_initiator_id) < 0) + return -1; + + if (DetectSignatureSetAppProto(s, ALPROTO_IKE) < 0) + return -1; + + return 0; +} + +static int DetectSpiResponderSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + if (DetectBufferSetActiveList(s, g_buffer_responder_id) < 0) + return -1; + + if (DetectSignatureSetAppProto(s, ALPROTO_IKE) < 0) + return -1; + + return 0; +} + +static InspectionBuffer *GetInitiatorData(DetectEngineThreadCtx *det_ctx, + const DetectEngineTransforms *transforms, Flow *_f, const uint8_t _flow_flags, void *txv, + const int list_id) +{ + InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id); + if (buffer->inspect == NULL) { + const uint8_t *b = NULL; + uint32_t b_len = 0; + + if (rs_ike_state_get_spi_initiator(txv, &b, &b_len) != 1) + return NULL; + if (b == NULL || b_len == 0) + return NULL; + + InspectionBufferSetup(det_ctx, list_id, buffer, b, b_len); + InspectionBufferApplyTransforms(buffer, transforms); + } + + return buffer; +} + +static InspectionBuffer *GetResponderData(DetectEngineThreadCtx *det_ctx, + const DetectEngineTransforms *transforms, Flow *_f, const uint8_t _flow_flags, void *txv, + const int list_id) +{ + InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id); + if (buffer->inspect == NULL) { + const uint8_t *b = NULL; + uint32_t b_len = 0; + + if (rs_ike_state_get_spi_responder(txv, &b, &b_len) != 1) + return NULL; + if (b == NULL || b_len == 0) + return NULL; + + InspectionBufferSetup(det_ctx, list_id, buffer, b, b_len); + InspectionBufferApplyTransforms(buffer, transforms); + } + + return buffer; +} + +void DetectIkeSpiRegister(void) +{ + // register initiator + sigmatch_table[DETECT_AL_IKE_SPI_INITIATOR].name = KEYWORD_NAME_INITIATOR; + sigmatch_table[DETECT_AL_IKE_SPI_INITIATOR].url = + "/rules/" KEYWORD_DOC_INITIATOR sigmatch_table[DETECT_AL_IKE_SPI_INITIATOR].desc = + "sticky buffer to match on the IKE spi initiator"; + sigmatch_table[DETECT_AL_IKE_SPI_INITIATOR].Setup = DetectSpiInitiatorSetup; + sigmatch_table[DETECT_AL_IKE_SPI_INITIATOR].flags |= + SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; + + DetectAppLayerInspectEngineRegister2(BUFFER_NAME_INITIATOR, ALPROTO_IKE, SIG_FLAG_TOSERVER, 1, + DetectEngineInspectBufferGeneric, GetInitiatorData); + + DetectAppLayerMpmRegister2(BUFFER_NAME_INITIATOR, SIG_FLAG_TOSERVER, 1, + PrefilterGenericMpmRegister, GetInitiatorData, ALPROTO_IKE, 1); + + DetectBufferTypeSetDescriptionByName(BUFFER_NAME_INITIATOR, BUFFER_DESC_INITIATOR); + + g_buffer_initiator_id = DetectBufferTypeGetByName(BUFFER_NAME_INITIATOR); + SCLogDebug("registering " BUFFER_NAME_INITIATOR " rule option"); + + // register responder + sigmatch_table[DETECT_AL_IKE_SPI_RESPONDER].name = KEYWORD_NAME_RESPONDER; + sigmatch_table[DETECT_AL_IKE_SPI_RESPONDER].url = + "/rules/" KEYWORD_DOC_RESPONDER sigmatch_table[DETECT_AL_IKE_SPI_RESPONDER].desc = + "sticky buffer to match on the IKE spi responder"; + sigmatch_table[DETECT_AL_IKE_SPI_RESPONDER].Setup = DetectSpiResponderSetup; + sigmatch_table[DETECT_AL_IKE_SPI_RESPONDER].flags |= + SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; + + DetectAppLayerInspectEngineRegister2(BUFFER_NAME_RESPONDER, ALPROTO_IKE, SIG_FLAG_TOCLIENT, 1, + DetectEngineInspectBufferGeneric, GetResponderData); + + DetectAppLayerMpmRegister2(BUFFER_NAME_RESPONDER, SIG_FLAG_TOCLIENT, 1, + PrefilterGenericMpmRegister, GetResponderData, ALPROTO_IKE, 1); + + DetectBufferTypeSetDescriptionByName(BUFFER_NAME_RESPONDER, BUFFER_DESC_RESPONDER); + + g_buffer_responder_id = DetectBufferTypeGetByName(BUFFER_NAME_RESPONDER); + SCLogDebug("registering " BUFFER_NAME_RESPONDER " rule option"); +} diff --git a/src/detect-ike-spi.h b/src/detect-ike-spi.h new file mode 100644 index 0000000000..e4ae75d542 --- /dev/null +++ b/src/detect-ike-spi.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2020 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. + */ + +/** + * + * \author Frank Honza + */ + +#ifndef __DETECT_IKE_SPI_H__ +#define __DETECT_IKE_SPI_H__ + +void DetectIkeSpiRegister(void); + +#endif /* __DETECT_IKE_SPI_H__ */ diff --git a/src/detect-ike-vendor.c b/src/detect-ike-vendor.c new file mode 100644 index 0000000000..800ab0d536 --- /dev/null +++ b/src/detect-ike-vendor.c @@ -0,0 +1,211 @@ +/* Copyright (C) 2020 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. + */ + +/** + * + * \author Frank Honza + */ + +#include "suricata-common.h" +#include "conf.h" +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-prefilter.h" +#include "detect-engine-content-inspection.h" +#include "detect-engine-mpm.h" +#include "detect-ike-vendor.h" +#include "app-layer-parser.h" +#include "util-byte.h" + +#include "rust-bindings.h" + +static int DetectIkeVendorSetup(DetectEngineCtx *, Signature *, const char *); + +typedef struct { + char *vendor; +} DetectIkeVendorData; + +struct IkeVendorGetDataArgs { + int local_id; + void *txv; +}; + +typedef struct PrefilterMpmIkeVendor { + int list_id; + const MpmCtx *mpm_ctx; + const DetectEngineTransforms *transforms; +} PrefilterMpmIkeVendor; + +static int g_ike_vendor_buffer_id = 0; + +static InspectionBuffer *IkeVendorGetData(DetectEngineThreadCtx *det_ctx, + const DetectEngineTransforms *transforms, Flow *f, struct IkeVendorGetDataArgs *cbdata, + int list_id, bool first) +{ + SCEnter(); + + InspectionBufferMultipleForList *fb = InspectionBufferGetMulti(det_ctx, list_id); + InspectionBuffer *buffer = InspectionBufferMultipleForListGet(fb, cbdata->local_id); + if (buffer == NULL) + return NULL; + if (!first && buffer->inspect != NULL) + return buffer; + + const uint8_t *data; + uint32_t data_len; + if (rs_ike_tx_get_vendor(cbdata->txv, (uint16_t)cbdata->local_id, &data, &data_len) == 0) { + return NULL; + } + + InspectionBufferSetup(det_ctx, list_id, buffer, data, data_len); + InspectionBufferApplyTransforms(buffer, transforms); + + SCReturnPtr(buffer, "InspectionBuffer"); +} + +/** \brief IkeVendor Mpm prefilter callback + * + * \param det_ctx detection engine thread ctx + * \param p packet to inspect + * \param f flow to inspect + * \param txv tx to inspect + * \param pectx inspection context + */ +static void PrefilterTxIkeVendor(DetectEngineThreadCtx *det_ctx, const void *pectx, Packet *p, + Flow *f, void *txv, const uint64_t idx, const uint8_t flags) +{ + SCEnter(); + + const PrefilterMpmIkeVendor *ctx = (const PrefilterMpmIkeVendor *)pectx; + const MpmCtx *mpm_ctx = ctx->mpm_ctx; + const int list_id = ctx->list_id; + + int local_id = 0; + while (1) { + struct IkeVendorGetDataArgs cbdata = { local_id, txv }; + InspectionBuffer *buffer = + IkeVendorGetData(det_ctx, ctx->transforms, f, &cbdata, list_id, true); + if (buffer == NULL) + break; + + if (buffer->inspect_len >= mpm_ctx->minlen) { + (void)mpm_table[mpm_ctx->mpm_type].Search( + mpm_ctx, &det_ctx->mtcu, &det_ctx->pmq, buffer->inspect, buffer->inspect_len); + } + local_id++; + } + + SCReturn; +} + +static void PrefilterMpmIkeVendorFree(void *ptr) +{ + if (ptr != NULL) + SCFree(ptr); +} + +static int PrefilterMpmIkeVendorRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, + MpmCtx *mpm_ctx, const DetectBufferMpmRegistery *mpm_reg, int list_id) +{ + PrefilterMpmIkeVendor *pectx = SCCalloc(1, sizeof(*pectx)); + if (pectx == NULL) + return -1; + pectx->list_id = list_id; + pectx->mpm_ctx = mpm_ctx; + pectx->transforms = &mpm_reg->transforms; + + return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxIkeVendor, mpm_reg->app_v2.alproto, + mpm_reg->app_v2.tx_min_progress, pectx, PrefilterMpmIkeVendorFree, mpm_reg->pname); +} + +static int DetectEngineInspectIkeVendor(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const DetectEngineAppInspectionEngine *engine, const Signature *s, Flow *f, uint8_t flags, + void *alstate, void *txv, uint64_t tx_id) +{ + int local_id = 0; + + const DetectEngineTransforms *transforms = NULL; + if (!engine->mpm) { + transforms = engine->v2.transforms; + } + + while (1) { + struct IkeVendorGetDataArgs cbdata = { + local_id, + txv, + }; + InspectionBuffer *buffer = + IkeVendorGetData(det_ctx, transforms, f, &cbdata, engine->sm_list, false); + if (buffer == NULL || buffer->inspect == NULL) + break; + + det_ctx->buffer_offset = 0; + det_ctx->discontinue_matching = 0; + det_ctx->inspection_recursion_counter = 0; + + const int match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd, NULL, f, + (uint8_t *)buffer->inspect, buffer->inspect_len, buffer->inspect_offset, + DETECT_CI_FLAGS_SINGLE, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE); + if (match == 1) { + return DETECT_ENGINE_INSPECT_SIG_MATCH; + } + local_id++; + } + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; +} + +/** + * \brief Registration function for ike.vendor keyword. + */ +void DetectIkeVendorRegister(void) +{ + sigmatch_table[DETECT_AL_IKE_VENDOR].name = "ike.vendor"; + sigmatch_table[DETECT_AL_IKE_VENDOR].desc = "match IKE Vendor"; + sigmatch_table[DETECT_AL_IKE_VENDOR].url = "/rules/ike-keywords.html#ike-vendor"; + sigmatch_table[DETECT_AL_IKE_VENDOR].Setup = DetectIkeVendorSetup; + sigmatch_table[DETECT_AL_IKE_VENDOR].flags |= SIGMATCH_NOOPT; + sigmatch_table[DETECT_AL_IKE_VENDOR].flags |= SIGMATCH_INFO_STICKY_BUFFER; + + DetectAppLayerMpmRegister2("ike.vendor", SIG_FLAG_TOSERVER, 1, PrefilterMpmIkeVendorRegister, + NULL, ALPROTO_IKE, 1); + + DetectAppLayerInspectEngineRegister2( + "ike.vendor", ALPROTO_IKE, SIG_FLAG_TOSERVER, 1, DetectEngineInspectIkeVendor, NULL); + + g_ike_vendor_buffer_id = DetectBufferTypeGetByName("ike.vendor"); +} + +/** + * \brief setup the sticky buffer keyword used in the rule + * + * \param de_ctx Pointer to the Detection Engine Context + * \param s Pointer to the Signature to which the current keyword belongs + * \param str Should hold an empty string always + * + * \retval 0 On success + * \retval -1 On failure + */ + +static int DetectIkeVendorSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + if (DetectBufferSetActiveList(s, g_ike_vendor_buffer_id) < 0) + return -1; + if (DetectSignatureSetAppProto(s, ALPROTO_IKE) < 0) + return -1; + return 0; +} diff --git a/src/detect-ike-vendor.h b/src/detect-ike-vendor.h new file mode 100644 index 0000000000..2570f91752 --- /dev/null +++ b/src/detect-ike-vendor.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2020 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 Frank Honza + */ + +#ifndef __DETECT_IKE_VENDOR_H__ +#define __DETECT_IKE_VENDOR_H__ + +void DetectIkeVendorRegister(void); + +#endif /* __DETECT_IKE_VENDOR_H__ */ diff --git a/src/output-json-ike.c b/src/output-json-ike.c index adba6d7a47..165ffa62a4 100644 --- a/src/output-json-ike.c +++ b/src/output-json-ike.c @@ -19,6 +19,7 @@ * \file * * \author Pierre Chifflier + * \author Frank Honza * * Implement JSON/eve logging app-layer IKE. */ @@ -49,9 +50,12 @@ #include "rust.h" +#define LOG_IKE_DEFAULT 0 +#define LOG_IKE_EXTENDED (1 << 0) + typedef struct LogIKEFileCtx_ { LogFileCtx *file_ctx; - OutputJsonCommonSettings cfg; + uint32_t flags; } LogIKEFileCtx; typedef struct LogIKELogThread_ { @@ -63,21 +67,16 @@ typedef struct LogIKELogThread_ { static int JsonIKELogger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, void *state, void *tx, uint64_t tx_id) { - IKETransaction *iketx = tx; LogIKELogThread *thread = thread_data; - JsonBuilder *jb = CreateEveHeader((Packet *)p, LOG_DIR_PACKET, "ike", NULL); if (unlikely(jb == NULL)) { return TM_ECODE_FAILED; } - EveAddCommonOptions(&thread->ikelog_ctx->cfg, p, f, jb); - - jb_open_object(jb, "ike"); - if (unlikely(!rs_ike_log_json_response(state, iketx, jb))) { + LogIKEFileCtx *ike_ctx = thread->ikelog_ctx; + if (!rs_ike_logger_log(state, tx, ike_ctx->flags, jb)) { goto error; } - jb_close(jb); MemBufferReset(thread->buffer); OutputJsonBuilderBuffer(jb, thread->file_ctx, &thread->buffer); @@ -107,18 +106,24 @@ static OutputInitResult OutputIKELogInitSub(ConfNode *conf, OutputCtx *parent_ct return result; } ikelog_ctx->file_ctx = ajt->file_ctx; - ikelog_ctx->cfg = ajt->cfg; OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx)); if (unlikely(output_ctx == NULL)) { SCFree(ikelog_ctx); return result; } + + ikelog_ctx->flags = LOG_IKE_DEFAULT; + const char *extended = ConfNodeLookupChildValue(conf, "extended"); + if (extended) { + if (ConfValIsTrue(extended)) { + ikelog_ctx->flags = LOG_IKE_EXTENDED; + } + } + output_ctx->data = ikelog_ctx; output_ctx->DeInit = OutputIKELogDeInitCtxSub; - SCLogDebug("IKE log sub-module initialized."); - AppLayerParserRegisterLogger(IPPROTO_UDP, ALPROTO_IKE); result.ctx = output_ctx; @@ -179,6 +184,4 @@ void JsonIKELogRegister(void) OutputRegisterTxSubModule(LOGGER_JSON_IKE, "eve-log", "JsonIKELog", "eve-log.ike", OutputIKELogInitSub, ALPROTO_IKE, JsonIKELogger, JsonIKELogThreadInit, JsonIKELogThreadDeinit, NULL); - - SCLogDebug("IKE JSON logger registered."); } -- 2.47.2