summaryrefslogtreecommitdiffstats
path: root/rust/src/ike
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rust/src/ike/detect.rs237
-rw-r--r--rust/src/ike/ike.rs449
-rw-r--r--rust/src/ike/ikev1.rs169
-rw-r--r--rust/src/ike/ikev2.rs316
-rw-r--r--rust/src/ike/logger.rs234
-rw-r--r--rust/src/ike/mod.rs29
-rw-r--r--rust/src/ike/parser.rs712
7 files changed, 2146 insertions, 0 deletions
diff --git a/rust/src/ike/detect.rs b/rust/src/ike/detect.rs
new file mode 100644
index 0000000..64d27a4
--- /dev/null
+++ b/rust/src/ike/detect.rs
@@ -0,0 +1,237 @@
+/* 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 <frank.honza@dcso.de>
+
+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.is_null());
+
+ 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.is_null() || buffer_len.is_null());
+
+ 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.is_null() || buffer_len.is_null());
+
+ 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.is_null() || buffer_len.is_null());
+
+ 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.is_null() || buffer_len.is_null());
+
+ 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: u32, buf: *mut *const u8, len: *mut u32,
+) -> u8 {
+ if tx.ike_version == 1 && i < tx.hdr.ikev1_header.vendor_ids.len() as u32 {
+ 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.is_null());
+ 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 {
+ if !tx.hdr.ikev1_transforms.is_empty() {
+ // there should be only one chosen server_transform, check event
+ if let Some(server_transform) = tx.hdr.ikev1_transforms.first() {
+ 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;
+ }
+ }
+ }
+ }
+ }
+ } else if tx.ike_version == 2 {
+ for attr in tx.hdr.ikev2_transforms.iter() {
+ match attr {
+ IkeV2Transform::Encryption(e) => {
+ if sa == "alg_enc" {
+ ret_val = e.0 as u32;
+ ret_code = 1;
+ break;
+ }
+ }
+ IkeV2Transform::Auth(e) => {
+ if sa == "alg_auth" {
+ ret_val = e.0 as u32;
+ ret_code = 1;
+ break;
+ }
+ }
+ IkeV2Transform::PRF(ref e) => {
+ if sa == "alg_prf" {
+ ret_val = e.0 as u32;
+ ret_code = 1;
+ break;
+ }
+ }
+ IkeV2Transform::DH(ref e) => {
+ if sa == "alg_dh" {
+ ret_val = e.0 as u32;
+ ret_code = 1;
+ break;
+ }
+ }
+ _ => (),
+ }
+ }
+ }
+ }
+
+ 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.is_null());
+
+ 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.is_null());
+
+ 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
new file mode 100644
index 0000000..2a5448a
--- /dev/null
+++ b/rust/src/ike/ike.rs
@@ -0,0 +1,449 @@
+/* Copyright (C) 2020-2021 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 <frank.honza@dcso.de>
+
+extern crate ipsec_parser;
+use self::ipsec_parser::*;
+
+use crate::applayer;
+use crate::applayer::*;
+use crate::core::{self, *};
+use crate::ike::ikev1::{handle_ikev1, IkeV1Header, Ikev1Container};
+use crate::ike::ikev2::{handle_ikev2, Ikev2Container};
+use crate::ike::parser::*;
+use nom7::Err;
+use std;
+use std::collections::HashSet;
+use std::ffi::CString;
+
+#[derive(AppLayerEvent)]
+pub enum IkeEvent {
+ MalformedData,
+ NoEncryption,
+ WeakCryptoEnc,
+ WeakCryptoPrf,
+ WeakCryptoDh,
+ WeakCryptoAuth,
+ WeakCryptoNoDh,
+ WeakCryptoNoAuth,
+ InvalidProposal,
+ UnknownProposal,
+ PayloadExtraData,
+ MultipleServerProposal,
+}
+
+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<Vec<SaAttribute>>,
+ pub ikev2_transforms: Vec<IkeV2Transform>,
+ pub ikev1_header: IkeV1Header,
+ pub ikev2_header: IkeV2Header,
+}
+
+impl Default for IkeHeaderWrapper {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl IkeHeaderWrapper {
+ pub fn new() -> Self {
+ Self {
+ 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<HashSet<u8>>,
+ pub ikev2_payload_types: Vec<IkePayloadType>,
+}
+
+#[derive(Default)]
+pub struct IKETransaction {
+ tx_id: u64,
+
+ pub ike_version: u8,
+ pub direction: Direction,
+ pub hdr: IkeHeaderWrapper,
+ pub payload_types: IkePayloadWrapper,
+ pub notify_types: Vec<NotifyType>,
+
+ /// errors seen during exchange
+ pub errors: u32,
+
+ logged: LoggerFlags,
+ pub tx_data: applayer::AppLayerTxData,
+}
+
+impl Transaction for IKETransaction {
+ fn id(&self) -> u64 {
+ self.tx_id
+ }
+}
+
+impl IKETransaction {
+ pub fn new(direction: Direction) -> Self {
+ Self {
+ direction,
+ tx_data: applayer::AppLayerTxData::for_direction(direction),
+ ..Default::default()
+ }
+ }
+
+ /// Set an event.
+ pub fn set_event(&mut self, event: IkeEvent) {
+ self.tx_data.set_event(event as u8);
+ }
+}
+
+#[derive(Default)]
+pub struct IKEState {
+ state_data: AppLayerStateData,
+ tx_id: u64,
+ pub transactions: Vec<IKETransaction>,
+
+ pub ikev1_container: Ikev1Container,
+ pub ikev2_container: Ikev2Container,
+}
+
+impl State<IKETransaction> for IKEState {
+ fn get_transaction_count(&self) -> usize {
+ self.transactions.len()
+ }
+
+ fn get_transaction_by_index(&self, index: usize) -> Option<&IKETransaction> {
+ self.transactions.get(index)
+ }
+}
+
+impl IKEState {
+ // Free a transaction by ID.
+ fn free_tx(&mut self, tx_id: u64) {
+ let tx = self
+ .transactions
+ .iter()
+ .position(|tx| tx.tx_id == tx_id + 1);
+ debug_assert!(tx.is_some());
+ if let Some(idx) = tx {
+ let _ = self.transactions.remove(idx);
+ }
+ }
+
+ pub fn get_tx(&mut self, tx_id: u64) -> Option<&mut IKETransaction> {
+ self.transactions.iter_mut().find(|tx| tx.tx_id == tx_id + 1)
+ }
+
+ pub fn new_tx(&mut self, direction: Direction) -> IKETransaction {
+ let mut tx = IKETransaction::new(direction);
+ self.tx_id += 1;
+ tx.tx_id = self.tx_id;
+ return tx;
+ }
+
+ /// Set an event. The event is set on the most recent transaction.
+ pub fn set_event(&mut self, event: IkeEvent) {
+ if let Some(tx) = self.transactions.last_mut() {
+ tx.set_event(event);
+ } else {
+ SCLogDebug!(
+ "IKE: trying to set event {} on non-existing transaction",
+ event as u32
+ );
+ }
+ }
+
+ fn handle_input(&mut self, input: &[u8], direction: Direction) -> AppLayerResult {
+ // We're not interested in empty requests.
+ if input.is_empty() {
+ 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();
+ }
+
+ 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();
+ }
+ return AppLayerResult::ok(); // todo either remove outer loop or check header length-field if we have completely read everything
+ }
+ Err(Err::Incomplete(_)) => {
+ SCLogDebug!("Insufficient data while parsing IKE");
+ return AppLayerResult::err();
+ }
+ Err(_) => {
+ SCLogDebug!("Error while parsing IKE packet");
+ return AppLayerResult::err();
+ }
+ }
+ }
+}
+
+/// Probe to see if this input looks like a request or response.
+fn probe(input: &[u8], direction: Direction, 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 != Direction::ToServer {
+ unsafe {
+ *rdir = Direction::ToServer.into();
+ }
+ }
+ 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 != Direction::ToServer {
+ unsafe {
+ *rdir = Direction::ToServer.into();
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+ Err(_) => return false,
+ }
+}
+
+// C exports.
+
+/// C entry point for a probing parser.
+#[no_mangle]
+pub unsafe 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 ALPROTO_FAILED;
+ }
+
+ if !input.is_null() {
+ let slice = build_slice!(input, input_len as usize);
+ if probe(slice, direction.into(), rdir) {
+ return ALPROTO_IKE;
+ }
+ }
+ return ALPROTO_FAILED;
+}
+
+#[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::default();
+ let boxed = Box::new(state);
+ return Box::into_raw(boxed) as *mut _;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_ike_state_free(state: *mut std::os::raw::c_void) {
+ // Just unbox...
+ std::mem::drop(Box::from_raw(state as *mut IKEState));
+}
+
+#[no_mangle]
+pub unsafe 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 unsafe extern "C" fn rs_ike_parse_request(
+ _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void,
+ stream_slice: StreamSlice, _data: *const std::os::raw::c_void,
+) -> AppLayerResult {
+ let state = cast_pointer!(state, IKEState);
+ return state.handle_input(stream_slice.as_slice(), Direction::ToServer);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_ike_parse_response(
+ _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void,
+ stream_slice: StreamSlice, _data: *const std::os::raw::c_void,
+) -> AppLayerResult {
+ let state = cast_pointer!(state, IKEState);
+ return state.handle_input(stream_slice.as_slice(), Direction::ToClient);
+}
+
+#[no_mangle]
+pub unsafe 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 tx as *const _ as *mut _;
+ }
+ None => {
+ return std::ptr::null_mut();
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe 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 {
+ // 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 {
+ return 1;
+}
+
+#[no_mangle]
+pub unsafe 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 unsafe 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);
+}
+
+static mut ALPROTO_IKE: AppProto = ALPROTO_UNKNOWN;
+
+// Parser name as a C style string.
+const PARSER_NAME: &[u8] = b"ike\0";
+const PARSER_ALIAS: &[u8] = b"ikev2\0";
+
+export_tx_data_get!(rs_ike_get_tx_data, IKETransaction);
+export_state_data_get!(rs_ike_get_state_data, IKEState);
+
+#[no_mangle]
+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,
+ default_port: default_port.as_ptr(),
+ ipproto: core::IPPROTO_UDP,
+ probe_ts: Some(rs_ike_probing_parser),
+ probe_tc: Some(rs_ike_probing_parser),
+ min_depth: 0,
+ max_depth: 16,
+ state_new: rs_ike_state_new,
+ state_free: rs_ike_state_free,
+ tx_free: rs_ike_state_tx_free,
+ parse_ts: rs_ike_parse_request,
+ parse_tc: rs_ike_parse_response,
+ get_tx_count: rs_ike_state_get_tx_count,
+ get_tx: rs_ike_state_get_tx,
+ tx_comp_st_ts: 1,
+ tx_comp_st_tc: 1,
+ tx_get_progress: rs_ike_tx_get_alstate_progress,
+ get_eventinfo: Some(IkeEvent::get_event_info),
+ get_eventinfo_byid: Some(IkeEvent::get_event_info_by_id),
+ localstorage_new: None,
+ localstorage_free: None,
+ get_tx_files: None,
+ get_tx_iterator: Some(applayer::state_get_tx_iterator::<IKEState, IKETransaction>),
+ get_tx_data: rs_ike_get_tx_data,
+ get_state_data: rs_ike_get_state_data,
+ apply_tx_config: None,
+ flags: 0,
+ truncate: None,
+ get_frame_id_by_name: None,
+ get_frame_name_by_id: None,
+ };
+
+ let ip_proto_str = CString::new("udp").unwrap();
+
+ if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
+ let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
+ 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.");
+ }
+}
diff --git a/rust/src/ike/ikev1.rs b/rust/src/ike/ikev1.rs
new file mode 100644
index 0000000..1e79c29
--- /dev/null
+++ b/rust/src/ike/ikev1.rs
@@ -0,0 +1,169 @@
+/* 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 <frank.honza@dcso.de>
+
+use crate::applayer::*;
+use crate::common::to_hex;
+use crate::core::Direction;
+use crate::ike::ike::{IKEState, IkeEvent};
+use crate::ike::parser::*;
+use nom7::Err;
+use std;
+use std::collections::HashSet;
+
+#[derive(Default)]
+pub struct IkeV1Header {
+ pub exchange_type: Option<u8>,
+ pub encrypted_payloads: bool,
+
+ pub key_exchange: Vec<u8>,
+ pub nonce: Vec<u8>,
+ pub vendor_ids: Vec<String>,
+}
+
+#[derive(Default)]
+pub struct Ikev1ParticipantData {
+ pub key_exchange: String,
+ pub nonce: String,
+ pub nb_transforms: u64,
+ pub transform: Vec<SaAttribute>,
+}
+
+impl Ikev1ParticipantData {
+ pub fn reset(&mut self) {
+ self.key_exchange.clear();
+ self.nonce.clear();
+ self.nb_transforms = 0;
+ self.transform.clear();
+ }
+
+ pub fn update(
+ &mut self, key_exchange: &str, nonce: &str, transforms: &Vec<Vec<SaAttribute>>,
+ ) {
+ self.key_exchange = key_exchange.to_string();
+ self.nonce = nonce.to_string();
+ if self.nb_transforms == 0 && !transforms.is_empty() {
+ self.transform.extend(transforms[0].iter().cloned());
+ }
+ self.nb_transforms += transforms.len() as u64;
+ }
+}
+
+#[derive(Default)]
+pub struct Ikev1Container {
+ pub domain_of_interpretation: Option<u32>,
+ pub client: Ikev1ParticipantData,
+ pub server: Ikev1ParticipantData,
+}
+
+pub fn handle_ikev1(
+ state: &mut IKEState, current: &[u8], isakmp_header: IsakmpHeader, direction: Direction,
+) -> AppLayerResult {
+ let mut tx = state.new_tx(direction);
+
+ 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<u8> = 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 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,
+ ).is_err() {
+ 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 == Direction::ToServer {
+ state.ikev1_container.client.reset();
+ } else {
+ state.ikev1_container.server.reset();
+ }
+ }
+
+ // add transaction values to state values
+ if direction == Direction::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_transforms,
+ );
+ } else {
+ if state.ikev1_container.server.nb_transforms <= 1
+ && state.ikev1_container.server.nb_transforms
+ + tx.hdr.ikev1_transforms.len() as u64
+ > 1
+ {
+ SCLogDebug!("More than one chosen server proposal");
+ state.set_event(IkeEvent::MultipleServerProposal);
+ }
+
+ 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_transforms,
+ );
+ }
+
+ if !rem.is_empty() {
+ // more data left unread than should be
+ SCLogDebug!("Unread Payload Data");
+ state.set_event(IkeEvent::PayloadExtraData);
+ }
+ }
+ Err(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 0000000..a1be25f
--- /dev/null
+++ b/rust/src/ike/ikev2.rs
@@ -0,0 +1,316 @@
+/* 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 <chifflier@wzdftpd.net>
+
+use crate::applayer::*;
+use crate::core::Direction;
+use crate::ike::ipsec_parser::*;
+
+use super::ipsec_parser::IkeV2Transform;
+use crate::ike::ike::{IKEState, IKETransaction, IkeEvent};
+use crate::ike::parser::IsakmpHeader;
+use ipsec_parser::{IkeExchangeType, IkePayloadType, IkeV2Header};
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+#[repr(u8)]
+pub enum IKEV2ConnectionState {
+ Init,
+ InitSASent,
+ InitKESent,
+ InitNonceSent,
+ RespSASent,
+ RespKESent,
+
+ 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, // should go to RespNonceSent,
+ (&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<Vec<IkeV2Transform>>,
+
+ /// The encryption algorithm selected by the responder
+ pub alg_enc: IkeTransformEncType,
+ /// The authentication algorithm selected by the responder
+ pub alg_auth: IkeTransformAuthType,
+ /// The PRF algorithm selected by the responder
+ pub alg_prf: IkeTransformPRFType,
+ /// The Diffie-Hellman algorithm selected by the responder
+ pub alg_dh: IkeTransformDHType,
+ /// The extended sequence numbers parameter selected by the responder
+ pub alg_esn: IkeTransformESNType,
+ /// The Diffie-Hellman group from the server KE message, if present.
+ pub dh_group: IkeTransformDHType,
+}
+
+impl Default for Ikev2Container {
+ fn default() -> Ikev2Container {
+ Ikev2Container {
+ connection_state: IKEV2ConnectionState::Init,
+ dh_group: IkeTransformDHType::None,
+ client_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(
+ state: &mut IKEState, current: &[u8], isakmp_header: IsakmpHeader, direction: Direction,
+) -> 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(direction);
+ 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;
+ 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, &mut tx, prop, direction);
+ // }
+ }
+ IkeV2PayloadContent::KE(ref kex) => {
+ SCLogDebug!("KEX {:?}", kex.dh_group);
+ if direction == Direction::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);
+ tx.payload_types
+ .ikev2_payload_types
+ .append(&mut payload_types);
+ tx.errors = errors;
+ tx.notify_types.append(&mut notify_types);
+ }
+ }
+ _e => {
+ SCLogDebug!("parse_ikev2_payload_with_type: {:?}", _e);
+ }
+ }
+ state.transactions.push(tx);
+ return AppLayerResult::ok();
+}
+
+fn add_proposals(
+ state: &mut IKEState, tx: &mut IKETransaction, prop: &Vec<IkeV2Proposal>, direction: Direction,
+) {
+ for p in prop {
+ let transforms: Vec<IkeV2Transform> = p.transforms.iter().map(|x| x.into()).collect();
+ // Rule 1: warn on weak or unknown transforms
+ for xform in &transforms {
+ match *xform {
+ IkeV2Transform::Encryption(ref enc) => {
+ match *enc {
+ IkeTransformEncType::ENCR_DES_IV64
+ | IkeTransformEncType::ENCR_DES
+ | IkeTransformEncType::ENCR_3DES
+ | IkeTransformEncType::ENCR_RC5
+ | IkeTransformEncType::ENCR_IDEA
+ | IkeTransformEncType::ENCR_CAST
+ | IkeTransformEncType::ENCR_BLOWFISH
+ | IkeTransformEncType::ENCR_3IDEA
+ | IkeTransformEncType::ENCR_DES_IV32
+ | IkeTransformEncType::ENCR_NULL => {
+ SCLogDebug!("Weak Encryption: {:?}", enc);
+ // XXX send event only if direction == Direction::ToClient ?
+ tx.set_event(IkeEvent::WeakCryptoEnc);
+ }
+ _ => (),
+ }
+ }
+ IkeV2Transform::PRF(ref prf) => match *prf {
+ IkeTransformPRFType::PRF_NULL => {
+ SCLogDebug!("'Null' PRF transform proposed");
+ tx.set_event(IkeEvent::InvalidProposal);
+ }
+ IkeTransformPRFType::PRF_HMAC_MD5 | IkeTransformPRFType::PRF_HMAC_SHA1 => {
+ SCLogDebug!("Weak PRF: {:?}", prf);
+ tx.set_event(IkeEvent::WeakCryptoPrf);
+ }
+ _ => (),
+ },
+ IkeV2Transform::Auth(ref auth) => {
+ match *auth {
+ IkeTransformAuthType::NONE => {
+ // Note: this could be expected with an AEAD encryption 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);
+ tx.set_event(IkeEvent::WeakCryptoAuth);
+ }
+ _ => (),
+ }
+ }
+ IkeV2Transform::DH(ref dh) => match *dh {
+ IkeTransformDHType::None => {
+ SCLogDebug!("'None' DH transform proposed");
+ tx.set_event(IkeEvent::InvalidProposal);
+ }
+ IkeTransformDHType::Modp768
+ | IkeTransformDHType::Modp1024
+ | IkeTransformDHType::Modp1024s160
+ | IkeTransformDHType::Modp1536 => {
+ SCLogDebug!("Weak DH: {:?}", dh);
+ tx.set_event(IkeEvent::WeakCryptoDh);
+ }
+ _ => (),
+ },
+ IkeV2Transform::Unknown(_tx_type, _tx_id) => {
+ SCLogDebug!("Unknown proposal: type={:?}, id={}", _tx_type, _tx_id);
+ tx.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");
+ tx.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");
+ tx.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,
+ }) && !transforms.iter().any(|x| match *x {
+ IkeV2Transform::Encryption(ref enc) => enc.is_aead(),
+ _ => false,
+ }) {
+ SCLogDebug!("No integrity transform found");
+ tx.set_event(IkeEvent::WeakCryptoNoAuth);
+ }
+ // Finally
+ if direction == Direction::ToClient {
+ transforms.iter().for_each(|t| match *t {
+ IkeV2Transform::Encryption(ref e) => {
+ state.ikev2_container.alg_enc = *e;
+ tx.hdr.ikev2_transforms.push(IkeV2Transform::Encryption(*e));
+ }
+ IkeV2Transform::Auth(ref a) => {
+ state.ikev2_container.alg_auth = *a;
+ tx.hdr.ikev2_transforms.push(IkeV2Transform::Auth(*a));
+ }
+ IkeV2Transform::PRF(ref p) => {
+ state.ikev2_container.alg_prf = *p;
+ tx.hdr.ikev2_transforms.push(IkeV2Transform::PRF(*p));
+ }
+ IkeV2Transform::DH(ref dh) => {
+ state.ikev2_container.alg_dh = *dh;
+ tx.hdr.ikev2_transforms.push(IkeV2Transform::DH(*dh));
+ }
+ IkeV2Transform::ESN(ref e) => {
+ state.ikev2_container.alg_esn = *e;
+ tx.hdr.ikev2_transforms.push(IkeV2Transform::ESN(*e));
+ }
+ _ => {}
+ });
+ SCLogDebug!("Selected transforms: {:?}", transforms);
+ } else {
+ SCLogDebug!("Proposed transforms: {:?}", transforms);
+ state.ikev2_container.client_transforms.push(transforms);
+ }
+ }
+}
diff --git a/rust/src/ike/logger.rs b/rust/src/ike/logger.rs
new file mode 100644
index 0000000..cc5705f
--- /dev/null
+++ b/rust/src/ike/logger.rs
@@ -0,0 +1,234 @@
+/* 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::core::Direction;
+use crate::ike::parser::{ExchangeType, IsakmpPayloadType, SaAttribute};
+use crate::jsonbuilder::{JsonBuilder, JsonError};
+use num_traits::FromPrimitive;
+use std;
+use std::convert::TryFrom;
+
+const LOG_EXTENDED: u32 = 0x01;
+
+fn add_attributes(transform: &Vec<SaAttribute>, js: &mut JsonBuilder) -> Result<(), JsonError> {
+ for attribute in transform {
+ js.set_string(
+ attribute.attribute_type.to_string().as_str(),
+ 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 {
+ if state.ikev1_container.server.nb_transforms > 0 {
+ // log the first transform as the chosen one
+ add_attributes(&state.ikev1_container.server.transform, jb)?;
+ }
+ if tx.direction == Direction::ToClient && tx.hdr.ikev1_transforms.len() > 1 {
+ // in case we have multiple server transforms log them in a list
+ jb.open_array("server_proposals")?;
+ for server_transform in &tx.hdr.ikev1_transforms {
+ jb.start_object()?;
+ add_attributes(server_transform, jb)?;
+ jb.close()?;
+ }
+ jb.close()?;
+ }
+ } 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.is_empty() {
+ 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.is_empty() {
+ 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)?;
+ }
+ }
+
+ if tx.direction == Direction::ToServer && !tx.hdr.ikev1_transforms.is_empty() {
+ jb.open_array("proposals")?;
+ for client_transform in &tx.hdr.ikev1_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.is_empty() {
+ 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.is_empty() {
+ 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
+
+ if !tx.hdr.ikev1_header.vendor_ids.is_empty() {
+ jb.open_array("vendor_ids")?;
+ for vendor in &tx.hdr.ikev1_header.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)?;
+ if !tx.notify_types.is_empty() {
+ jb.open_array("notify")?;
+ for notify in tx.notify_types.iter() {
+ jb.append_string(&format!("{:?}", notify))?;
+ }
+ jb.close()?;
+ }
+ jb.close()?;
+ Ok(())
+}
+
+#[no_mangle]
+pub unsafe 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
new file mode 100644
index 0000000..366688e
--- /dev/null
+++ b/rust/src/ike/mod.rs
@@ -0,0 +1,29 @@
+/* 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.
+ */
+
+//! IKE parser, detection, logger and application layer module.
+
+// written by Pierre Chifflier <chifflier@wzdftpd.net>
+
+extern crate ipsec_parser;
+
+mod detect;
+pub mod ike;
+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 0000000..dcc5745
--- /dev/null
+++ b/rust/src/ike/parser.rs
@@ -0,0 +1,712 @@
+/* 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 nom7::bytes::streaming::take;
+use nom7::combinator::{complete, cond, map};
+use nom7::multi::many0;
+use nom7::number::streaming::{be_u16, be_u32, be_u64, be_u8};
+use nom7::{Err, IResult};
+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<u32>,
+ pub hex_value: Option<String>,
+}
+
+pub fn parse_isakmp_header(i: &[u8]) -> IResult<&[u8], IsakmpHeader> {
+ let (i, init_spi) = be_u64(i)?;
+ let (i, resp_spi) = be_u64(i)?;
+ let (i, next_payload) = be_u8(i)?;
+ let (i, vers_byte) = be_u8(i)?;
+ let vers = (vers_byte >> 4, vers_byte & 0b1111);
+ let (i, exch_type) = be_u8(i)?;
+ let (i, flags) = be_u8(i)?;
+ let (i, msg_id) = be_u32(i)?;
+ let (i, length) = be_u32(i)?;
+ let hdr = IsakmpHeader {
+ init_spi,
+ resp_spi,
+ next_payload,
+ maj_ver: vers.0,
+ min_ver: vers.1,
+ exch_type,
+ flags,
+ msg_id,
+ length,
+ };
+ Ok((i, hdr))
+}
+
+pub fn parse_security_association(i: &[u8]) -> IResult<&[u8], SecurityAssociationPayload> {
+ let start_i = i;
+ let (i, domain_of_interpretation) = be_u32(i)?;
+ let (i, situation) = cond(domain_of_interpretation == 1, take(4_usize))(i)?;
+ let (i, data) = cond(domain_of_interpretation == 1 && start_i.len() >= 8, |b| {
+ take(start_i.len() - 8)(b)
+ })(i)?;
+ Ok((
+ i,
+ SecurityAssociationPayload {
+ domain_of_interpretation,
+ situation,
+ data,
+ },
+ ))
+}
+
+pub fn parse_key_exchange(i: &[u8], length: u16) -> IResult<&[u8], KeyExchangePayload> {
+ let (i, key_exchange_data) = take(length as usize)(i)?;
+ Ok((i, KeyExchangePayload { key_exchange_data }))
+}
+
+pub fn parse_proposal(i: &[u8]) -> IResult<&[u8], ProposalPayload> {
+ let start_i = i;
+ let (i, proposal_number) = be_u8(i)?;
+ let (i, proposal_type) = be_u8(i)?;
+ let (i, spi_size) = be_u8(i)?;
+ let (i, number_transforms) = be_u8(i)?;
+ let (i, spi) = take(spi_size as usize)(i)?;
+ let (i, payload_data) = cond((start_i.len() - 4) >= spi_size.into(), |b| {
+ take((start_i.len() - 4) - spi_size as usize)(b)
+ })(i)?;
+ let payload = ProposalPayload {
+ proposal_number,
+ proposal_type,
+ spi_size,
+ number_transforms,
+ spi,
+ data: payload_data.unwrap_or_default(),
+ };
+ Ok((i, payload))
+}
+
+pub fn parse_transform(i: &[u8], length: u16) -> IResult<&[u8], TransformPayload> {
+ let (i, transform_number) = be_u8(i)?;
+ let (i, transform_type) = be_u8(i)?;
+ let (i, _) = be_u16(i)?;
+ let (i, payload_data) = cond(length >= 4, |b| take(length - 4)(b))(i)?;
+ Ok((
+ i,
+ TransformPayload {
+ transform_number,
+ transform_type,
+ sa_attributes: payload_data.unwrap_or_default(),
+ },
+ ))
+}
+
+pub fn parse_vendor_id(i: &[u8], length: u16) -> IResult<&[u8], VendorPayload> {
+ map(take(length), |v| VendorPayload { vendor_id: v })(i)
+}
+
+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,
+ }
+}
+
+pub fn parse_sa_attribute(i: &[u8]) -> IResult<&[u8], Vec<SaAttribute>> {
+ fn parse_attribute(i: &[u8]) -> IResult<&[u8], SaAttribute> {
+ let (i, b) = be_u16(i)?;
+ let format = ((b >> 15) as u8, b & 0x7f_ff);
+ let (i, attribute_length_or_value) = be_u16(i)?; // depends on format bit) = 1 -> value | 0 -> number of following bytes
+ let (i, numeric_variable_value) =
+ cond(format.0 == 0 && attribute_length_or_value == 4, be_u32)(i)?; // interpret as number
+ let (i, variable_attribute_value) = cond(
+ format.0 == 0 && attribute_length_or_value != 4,
+ take(attribute_length_or_value),
+ )(i)?;
+ let attr = 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 => numeric_variable_value,
+ _ => None,
+ },
+ hex_value: match format.0 {
+ 0 => variable_attribute_value
+ .map(to_hex),
+ _ => None,
+ },
+ };
+ Ok((i, attr))
+ }
+ many0(complete(parse_attribute))(i)
+}
+
+pub fn parse_nonce(i: &[u8], length: u16) -> IResult<&[u8], NoncePayload> {
+ map(take(length), |v| NoncePayload { nonce_data: v })(i)
+}
+
+pub fn parse_ikev1_payload_list(i: &[u8]) -> IResult<&[u8], Vec<IsakmpPayload>> {
+ fn parse_payload(i: &[u8]) -> IResult<&[u8], IsakmpPayload> {
+ let (i, next_payload) = be_u8(i)?;
+ let (i, reserved) = be_u8(i)?;
+ let (i, payload_length) = be_u16(i)?;
+ let (i, payload_data) = cond(payload_length >= 4, |b| take(payload_length - 4)(b))(i)?;
+ Ok((
+ i,
+ IsakmpPayload {
+ payload_header: IsakmpPayloadHeader {
+ next_payload,
+ reserved,
+ payload_length,
+ },
+ data: payload_data.unwrap_or_default(),
+ },
+ ))
+ }
+ many0(complete(parse_payload))(i)
+}
+
+#[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(
+ payload_type: u8, data: &[u8], data_length: u16, domain_of_interpretation: &mut Option<u32>,
+ key_exchange: &mut Vec<u8>, nonce: &mut Vec<u8>, transforms: &mut Vec<Vec<SaAttribute>>,
+ vendor_ids: &mut Vec<String>, payload_types: &mut HashSet<u8>,
+) -> Result<(), ()> {
+ payload_types.insert(payload_type);
+
+ let element = num::FromPrimitive::from_u8(payload_type);
+ match element {
+ Some(IsakmpPayloadType::SecurityAssociation) => {
+ if parse_security_association_payload(
+ data,
+ data_length,
+ domain_of_interpretation,
+ key_exchange,
+ nonce,
+ transforms,
+ vendor_ids,
+ payload_types,
+ ).is_err() {
+ SCLogDebug!("Error parsing SecurityAssociation");
+ return Err(());
+ }
+ Ok(())
+ }
+ Some(IsakmpPayloadType::Proposal) => {
+ if parse_proposal_payload(
+ data,
+ data_length,
+ domain_of_interpretation,
+ key_exchange,
+ nonce,
+ transforms,
+ vendor_ids,
+ payload_types,
+ ).is_err() {
+ 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(
+ data: &[u8], data_length: u16, domain_of_interpretation: &mut Option<u32>,
+ key_exchange: &mut Vec<u8>, nonce: &mut Vec<u8>, transforms: &mut Vec<Vec<SaAttribute>>,
+ vendor_ids: &mut Vec<String>, payload_types: &mut HashSet<u8>,
+) -> 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 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,
+ ).is_err() {
+ SCLogDebug!("Error parsing transform payload");
+ return Err(());
+ }
+ cur_payload_type = isakmp_payload.payload_header.next_payload;
+ }
+ Ok(())
+ }
+ Err(Err::Incomplete(_)) => {
+ SCLogDebug!("Incomplete data parsing payload list");
+ Err(())
+ }
+ Err(_) => {
+ SCLogDebug!("Error parsing payload list");
+ Err(())
+ }
+ }
+ }
+ Err(Err::Incomplete(_)) => {
+ SCLogDebug!("Incomplete data");
+ Err(())
+ }
+ Err(_) => Err(()),
+ }
+}
+
+fn parse_security_association_payload(
+ data: &[u8], data_length: u16, domain_of_interpretation: &mut Option<u32>,
+ key_exchange: &mut Vec<u8>, nonce: &mut Vec<u8>, transforms: &mut Vec<Vec<SaAttribute>>,
+ vendor_ids: &mut Vec<String>, payload_types: &mut HashSet<u8>,
+) -> 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 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,
+ ).is_err() {
+ SCLogDebug!("Error parsing proposal payload");
+ return Err(());
+ }
+ cur_payload_type = isakmp_payload.payload_header.next_payload;
+ }
+ }
+ Err(Err::Incomplete(_)) => {
+ SCLogDebug!("Incomplete data parsing payload list");
+ return Err(());
+ }
+ Err(_) => {
+ SCLogDebug!("Error parsing payload list");
+ return Err(());
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+ Err(Err::Incomplete(_)) => {
+ SCLogDebug!("Incomplete data");
+ Err(())
+ }
+ Err(_) => Err(()),
+ }
+}