]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
rust/asn1: Introduce ASN1 rust module
authorEmmanuel Thompson <eet6646@gmail.com>
Mon, 27 Apr 2020 20:27:22 +0000 (16:27 -0400)
committerVictor Julien <victor@inliniac.net>
Wed, 8 Jul 2020 14:50:38 +0000 (16:50 +0200)
This module uses the `der-parser` crate to parse ASN1 objects in order to replace src/util-decode-asn1.c
It also handles the parsing of the asn1 keyword rules and detection checks performed in src/detect-asn1.c

rust/Cargo.toml.in
rust/src/asn1/mod.rs [new file with mode: 0644]
rust/src/asn1/parse_rules.rs [new file with mode: 0644]
rust/src/lib.rs

index d4c3b271b6a5cd6f5ff1d9bde7cc8da12aa9af56..9a0f8a0150a07ebb8ac644b43fb22d2a408d3ed6 100644 (file)
@@ -39,3 +39,6 @@ snmp-parser = "0.6"
 tls-parser = "0.9"
 x509-parser = "0.6.5"
 libc = "0.2.67"
+
+[dev-dependencies]
+test-case = "1.0"
diff --git a/rust/src/asn1/mod.rs b/rust/src/asn1/mod.rs
new file mode 100644 (file)
index 0000000..c88bcdf
--- /dev/null
@@ -0,0 +1,428 @@
+/* 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 der_parser::ber::{parse_ber_recursive, BerObject, BerObjectContent, BerTag};
+use der_parser::error::BerError;
+use std::convert::TryFrom;
+
+mod parse_rules;
+use parse_rules::DetectAsn1Data;
+
+/// Container for parsed Asn1 objects
+#[derive(Debug)]
+pub struct Asn1(Vec<BerObject<'static>>);
+
+/// Errors possible during decoding of Asn1
+#[derive(Debug)]
+#[repr(u32)]
+pub enum Asn1DecodeError {
+    Success = 0,
+    InvalidKeywordParameter,
+    MaxFrames,
+    InvalidStructure,
+    BerTypeError,
+    BerValueError,
+    InvalidTag,
+    InvalidLength,
+    InvalidClass,
+    ConstructExpected,
+    ConstructUnexpected,
+    IntegerTooLarge,
+    BerMaxDepth,
+    ObjectTooShort,
+    DerConstraintFailed,
+    UnknownTag,
+    Unsupported,
+}
+
+/// Enumeration of Asn1 checks
+#[derive(Debug, PartialEq)]
+enum Asn1Check {
+    OversizeLength,
+    BitstringOverflow,
+    DoubleOverflow,
+}
+
+/// Errors possible during Asn1 checks
+#[derive(Debug)]
+#[repr(u32)]
+pub enum Asn1CheckError {
+    Success = 0,
+    MaxDepth,
+}
+
+impl Asn1 {
+    /// Checks each BerObject contained in self with the provided detection
+    /// data, returns the first successful match if one occurs
+    fn check(&self, ad: &DetectAsn1Data) -> Result<Option<Asn1Check>, Asn1CheckError> {
+        for obj in &self.0 {
+            let res = Asn1::check_object_recursive(obj, ad, ad.max_frames as usize)?;
+            if res.is_some() {
+                return Ok(res);
+            }
+        }
+
+        Ok(None)
+    }
+
+    fn check_object_recursive(
+        obj: &BerObject,
+        ad: &DetectAsn1Data,
+        max_depth: usize,
+    ) -> Result<Option<Asn1Check>, Asn1CheckError> {
+        // Check stack depth
+        if max_depth == 0 {
+            return Err(Asn1CheckError::MaxDepth);
+        }
+
+        // Check current object
+        let res = Asn1::check_object(obj, ad);
+        if res.is_some() {
+            return Ok(res);
+        }
+
+        // Check sub-nodes
+        for node in obj.ref_iter() {
+            let res = Asn1::check_object_recursive(node, ad, max_depth - 1)?;
+            if res.is_some() {
+                return Ok(res);
+            }
+        }
+
+        Ok(None)
+    }
+
+    /// Checks a BerObject and subnodes against the Asn1 checks
+    fn check_object(obj: &BerObject, ad: &DetectAsn1Data) -> Option<Asn1Check> {
+        // oversize_length will check if a node has a length greater than
+        // the user supplied length
+        if let Some(oversize_length) = ad.oversize_length {
+            if obj.header.len > oversize_length as u64
+                || obj.content.as_slice().unwrap_or(&[]).len() > oversize_length as usize
+            {
+                return Some(Asn1Check::OversizeLength);
+            }
+        }
+
+        // bitstring_overflow check a malformed option where the number of bits
+        // to ignore is greater than the length decoded (in bits)
+        if ad.bitstring_overflow
+            && (obj.header.is_universal()
+                && obj.header.tag == BerTag::BitString
+                && obj.header.is_primitive())
+        {
+            if let BerObjectContent::BitString(bits, _v) = &obj.content {
+                if obj.header.len > 0
+                    && *bits as u64 > (obj.header.len.checked_mul(8).unwrap_or(std::u64::MAX))
+                {
+                    return Some(Asn1Check::BitstringOverflow);
+                }
+            }
+        }
+
+        // double_overflow checks a known issue that affects the MSASN1 library
+        // when decoding double/real types. If the encoding is ASCII,
+        // and the buffer is greater than 256, the array is overflown
+        if ad.double_overflow
+            && (obj.header.is_universal()
+                && obj.header.tag == BerTag::RealType
+                && obj.header.is_primitive())
+        {
+            if let Ok(data) = obj.content.as_slice() {
+                if obj.header.len > 0
+                    && !data.is_empty()
+                    && data[0] & 0xC0 == 0
+                    && (obj.header.len > 256 || data.len() > 256)
+                {
+                    return Some(Asn1Check::DoubleOverflow);
+                }
+            }
+        }
+
+        None
+    }
+
+    fn from_slice(input: &'static [u8], ad: &DetectAsn1Data) -> Result<Asn1, Asn1DecodeError> {
+        let mut results = Vec::new();
+        let mut rest = input;
+
+        // while there's data to process
+        while !rest.is_empty() {
+            let max_depth = ad.max_frames as usize;
+
+            if results.len() >= max_depth {
+                return Err(Asn1DecodeError::MaxFrames);
+            }
+
+            let res = parse_ber_recursive(rest, max_depth);
+
+            match res {
+                Ok((new_rest, obj)) => {
+                    results.push(obj);
+
+                    rest = new_rest;
+                }
+                // If there's an error, bail
+                Err(_) => {
+                    // silent error as this could fail
+                    // on non-asn1 or fragmented packets
+                    break;
+                }
+            }
+        }
+
+        Ok(Asn1(results))
+    }
+}
+
+/// Decodes Asn1 objects from an input + length while applying the offset
+/// defined in the asn1 keyword options
+fn asn1_decode(
+    input: *const u8,
+    input_len: u32,
+    ad: &DetectAsn1Data,
+) -> Result<Asn1, Asn1DecodeError> {
+    // Get offset
+    let offset = if let Some(absolute_offset) = ad.absolute_offset {
+        absolute_offset as isize
+    } else if let Some(relative_offset) = ad.relative_offset {
+        relative_offset as isize
+    } else {
+        0
+    };
+
+    // Make sure we won't read past the end of the buffer
+    if offset >= input_len as isize {
+        return Err(Asn1DecodeError::InvalidKeywordParameter);
+    }
+
+    // Apply offset to input pointer
+    let input = unsafe { input.offset(offset) };
+
+    // Adjust the length
+    let input_len = (input_len as isize)
+        .checked_sub(offset)
+        .ok_or(Asn1DecodeError::InvalidKeywordParameter)?;
+    let input_len =
+        usize::try_from(input_len).map_err(|_| Asn1DecodeError::InvalidKeywordParameter)?;
+
+    // Get the slice from memory
+    let slice = build_slice!(input, input_len);
+
+    Asn1::from_slice(slice, ad)
+}
+
+/// Attempt to parse a Asn1 object from input, and return a pointer
+/// to the parsed object if successful, null on failure
+///
+/// # Safety
+///
+/// input must be a valid buffer of at least input_len bytes
+/// pointer must be freed using `rs_asn1_free`
+#[no_mangle]
+pub(crate) unsafe extern "C" fn rs_asn1_decode(
+    input: *const u8,
+    input_len: u32,
+    ad_ptr: *const DetectAsn1Data,
+) -> *mut Asn1 {
+    if input.is_null() || input_len == 0 || ad_ptr.is_null() {
+        return std::ptr::null_mut();
+    }
+
+    let ad = &*ad_ptr;
+
+    let res = asn1_decode(input, input_len, ad);
+
+    match res {
+        Ok(asn1) => Box::into_raw(Box::new(asn1)),
+        Err(_e) => std::ptr::null_mut(),
+    }
+}
+
+/// Free a Asn1 object allocated by Rust
+///
+/// # Safety
+///
+/// ptr must be a valid object obtained using `rs_asn1_decode`
+#[no_mangle]
+pub unsafe extern "C" fn rs_asn1_free(ptr: *mut Asn1) {
+    if ptr.is_null() {
+        return;
+    }
+    drop(Box::from_raw(ptr));
+}
+
+/// This function implements the detection of the following options:
+///   - oversize_length
+///   - bitstring_overflow
+///   - double_overflow
+///
+/// # Safety
+///
+/// ptr must be a valid object obtained using `rs_asn1_decode`
+/// ad_ptr must be a valid object obtained using `rs_detect_asn1_parse`
+///
+/// Returns 1 if any of the options match, 0 if not
+#[no_mangle]
+pub(crate) unsafe extern "C" fn rs_asn1_checks(
+    ptr: *const Asn1,
+    ad_ptr: *const DetectAsn1Data,
+) -> u8 {
+    if ptr.is_null() || ad_ptr.is_null() {
+        return 0;
+    }
+
+    let asn1 = &*ptr;
+    let ad = &*ad_ptr;
+
+    if let Ok(Some(_)) = asn1.check(ad) {
+        return 1;
+    }
+
+    0
+}
+
+impl From<nom::Err<der_parser::error::BerError>> for Asn1DecodeError {
+    fn from(e: nom::Err<der_parser::error::BerError>) -> Asn1DecodeError {
+        match e {
+            nom::Err::Incomplete(_) => Asn1DecodeError::InvalidLength,
+            nom::Err::Error(e) | nom::Err::Failure(e) => match e {
+                BerError::BerTypeError => Asn1DecodeError::BerTypeError,
+                BerError::BerValueError => Asn1DecodeError::BerValueError,
+                BerError::InvalidTag => Asn1DecodeError::InvalidTag,
+                BerError::InvalidClass => Asn1DecodeError::InvalidClass,
+                BerError::InvalidLength => Asn1DecodeError::InvalidLength,
+                BerError::ConstructExpected => Asn1DecodeError::ConstructExpected,
+                BerError::ConstructUnexpected => Asn1DecodeError::ConstructUnexpected,
+                BerError::IntegerTooLarge => Asn1DecodeError::IntegerTooLarge,
+                BerError::BerMaxDepth => Asn1DecodeError::BerMaxDepth,
+                BerError::ObjectTooShort => Asn1DecodeError::ObjectTooShort,
+                BerError::DerConstraintFailed => Asn1DecodeError::DerConstraintFailed,
+                BerError::UnknownTag => Asn1DecodeError::UnknownTag,
+                BerError::Unsupported => Asn1DecodeError::Unsupported,
+                _ => Asn1DecodeError::InvalidStructure,
+            },
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use test_case::test_case;
+
+    // Example from the specification X.690-0207 Appendix A.3
+    static ASN1_A3: &[u8] = b"\x60\x81\x85\x61\x10\x1A\x04John\x1A\x01 \
+                    P\x1A\x05Smith\xA0\x0A\x1A\x08Director \
+                    \x42\x01\x33\xA1\x0A\x43\x0819710917 \
+                    \xA2\x12\x61\x10\x1A\x04Mary\x1A\x01T\x1A\x05 \
+                    Smith\xA3\x42\x31\x1F\x61\x11\x1A\x05Ralph\x1A\x01 \
+                    T\x1A\x05Smith\xA0\x0A\x43\x0819571111 \
+                    \x31\x1F\x61\x11\x1A\x05Susan\x1A\x01B\x1A\x05 \
+                    Jones\xA0\x0A\x43\x0819590717";
+
+    /// Ensure that the checks work when they should
+    #[test_case("oversize_length 132 absolute_offset 0", ASN1_A3, DetectAsn1Data {
+            oversize_length: Some(132),
+            absolute_offset: Some(0),
+            ..Default::default()
+        }, Some(Asn1Check::OversizeLength); "Test oversize_length rule (match)" )]
+    #[test_case("oversize_length 133 absolute_offset 0", ASN1_A3, DetectAsn1Data {
+            oversize_length: Some(133),
+            absolute_offset: Some(0),
+            ..Default::default()
+        }, None; "Test oversize_length rule (non-match)" )]
+    #[test_case("bitstring_overflow, absolute_offset 0",
+        /* tagnum bitstring, primitive, and as universal tag,
+           length = 1 octet, but the next octet specify to ignore the last 256 bits */
+        b"\x03\x01\xFF",
+        DetectAsn1Data {
+            bitstring_overflow: true,
+            absolute_offset: Some(0),
+            ..Default::default()
+        }, Some(Asn1Check::BitstringOverflow); "Test bitstring_overflow rule (match)" )]
+    #[test_case("bitstring_overflow, absolute_offset 0",
+        /* tagnum bitstring, primitive, and as universal tag,
+           length = 1 octet, but the next octet specify to ignore the last 7 bits */
+        b"\x03\x01\x07",
+        DetectAsn1Data {
+            bitstring_overflow: true,
+            absolute_offset: Some(0),
+            ..Default::default()
+        }, None; "Test bitstring_overflow rule (non-match)" )]
+    #[test_case("double_overflow, absolute_offset 0",
+        {
+            static TEST_BUF: [u8; 261] = {
+                let mut b = [0x05; 261];
+                /* universal class, primitive type, tag_num = 9 (Data type Real) */
+                b[0] = 0x09;
+                /* length, definite form, 2 octets */
+                b[1] = 0x82;
+                /* length is the sum of the following octets (257): */
+                b[2] = 0x01;
+                b[3] = 0x01;
+
+                b
+            };
+
+            &TEST_BUF
+        },
+        DetectAsn1Data {
+            double_overflow: true,
+            absolute_offset: Some(0),
+            ..Default::default()
+        }, Some(Asn1Check::DoubleOverflow); "Test double_overflow rule (match)" )]
+    #[test_case("double_overflow, absolute_offset 0",
+        {
+            static TEST_BUF: [u8; 261] = {
+                let mut b = [0x05; 261];
+                /* universal class, primitive type, tag_num = 9 (Data type Real) */
+                b[0] = 0x09;
+                /* length, definite form, 2 octets */
+                b[1] = 0x82;
+                /* length is the sum of the following octets (256): */
+                b[2] = 0x01;
+                b[3] = 0x00;
+
+                b
+            };
+
+            &TEST_BUF
+        },
+        DetectAsn1Data {
+            double_overflow: true,
+            absolute_offset: Some(0),
+            ..Default::default()
+        }, None; "Test double_overflow rule (non-match)" )]
+    fn test_checks(
+        rule: &str,
+        asn1_buf: &'static [u8],
+        expected_data: DetectAsn1Data,
+        expected_check: Option<Asn1Check>,
+    ) {
+        // Parse rule
+        let (_rest, ad) = parse_rules::asn1_parse_rule(rule).unwrap();
+        assert_eq!(expected_data, ad);
+
+        // Decode
+        let asn1 = Asn1::from_slice(asn1_buf, &ad).unwrap();
+
+        // Run checks
+        let result = asn1.check(&ad).unwrap();
+        assert_eq!(expected_check, result);
+    }
+}
diff --git a/rust/src/asn1/parse_rules.rs b/rust/src/asn1/parse_rules.rs
new file mode 100644 (file)
index 0000000..c1cfae6
--- /dev/null
@@ -0,0 +1,275 @@
+/* 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::log::*;
+use nom::branch::alt;
+use nom::bytes::complete::tag;
+use nom::character::complete::{digit1, multispace0, multispace1};
+use nom::combinator::{map_res, opt};
+use nom::sequence::{separated_pair, tuple};
+use nom::IResult;
+use std::ffi::CStr;
+use std::os::raw::c_char;
+
+const ASN1_DEFAULT_MAX_FRAMES: u16 = 30;
+
+/// Parse the asn1 keyword and return a pointer to a `DetectAsn1Data`
+/// containing the parsed options, returns null on failure
+///
+/// # Safety
+///
+/// pointer must be free'd using `rs_detect_asn1_free`
+#[no_mangle]
+pub(crate) unsafe extern "C" fn rs_detect_asn1_parse(input: *const c_char) -> *mut DetectAsn1Data {
+    if input.is_null() {
+        return std::ptr::null_mut();
+    }
+
+    let arg = match CStr::from_ptr(input).to_str() {
+        Ok(arg) => arg,
+        _ => {
+            return std::ptr::null_mut();
+        }
+    };
+
+    match asn1_parse_rule(&arg) {
+        Ok((_rest, data)) => {
+            let mut data = data;
+
+            // Get configuration value
+            if let Some(max_frames) = crate::conf::conf_get("asn1-max-frames") {
+                if let Ok(v) = max_frames.parse::<u16>() {
+                    data.max_frames = v;
+                } else {
+                    SCLogDebug!("Could not parse asn1-max-frames: {}", max_frames);
+                };
+            }
+
+            Box::into_raw(Box::new(data))
+        }
+        Err(_) => std::ptr::null_mut(),
+    }
+}
+
+/// Free a `DetectAsn1Data` object allocated by Rust
+///
+/// # Safety
+///
+/// ptr must be a valid object obtained using `rs_detect_asn1_parse`
+#[no_mangle]
+pub(crate) unsafe extern "C" fn rs_detect_asn1_free(ptr: *mut DetectAsn1Data) {
+    if ptr.is_null() {
+        return;
+    }
+    drop(Box::from_raw(ptr));
+}
+
+/// Struct to hold parsed asn1 keyword options
+#[derive(Debug, PartialEq)]
+pub(crate) struct DetectAsn1Data {
+    pub bitstring_overflow: bool,
+    pub double_overflow: bool,
+    pub oversize_length: Option<u32>,
+    pub absolute_offset: Option<u32>,
+    pub relative_offset: Option<i32>,
+    pub max_frames: u16,
+}
+
+impl Default for DetectAsn1Data {
+    fn default() -> DetectAsn1Data {
+        DetectAsn1Data {
+            bitstring_overflow: false,
+            double_overflow: false,
+            oversize_length: None,
+            absolute_offset: None,
+            relative_offset: None,
+            max_frames: ASN1_DEFAULT_MAX_FRAMES,
+        }
+    }
+}
+
+fn parse_u32_number(input: &str) -> IResult<&str, u32> {
+    map_res(digit1, |digits: &str| digits.parse::<u32>())(input)
+}
+fn parse_i32_number(input: &str) -> IResult<&str, i32> {
+    let (rest, negate) = opt(tag("-"))(input)?;
+    let (rest, d) = map_res(digit1, |s: &str| s.parse::<i32>())(rest)?;
+    let n = if negate.is_some() { -1 } else { 1 };
+    Ok((rest, d * n))
+}
+
+/// Parse asn1 keyword options
+pub(super) fn asn1_parse_rule(input: &str) -> IResult<&str, DetectAsn1Data> {
+    // If nothing to parse, return
+    if input.is_empty() {
+        return Err(nom::Err::Error(nom::error::make_error(
+            input,
+            nom::error::ErrorKind::Eof,
+        )));
+    }
+
+    // Rule parsing functions
+    fn bitstring_overflow(i: &str) -> IResult<&str, &str> {
+        tag("bitstring_overflow")(i)
+    }
+
+    fn double_overflow(i: &str) -> IResult<&str, &str> {
+        tag("double_overflow")(i)
+    }
+
+    fn oversize_length(i: &str) -> IResult<&str, (&str, u32)> {
+        separated_pair(tag("oversize_length"), multispace1, parse_u32_number)(i)
+    }
+
+    fn absolute_offset(i: &str) -> IResult<&str, (&str, u32)> {
+        separated_pair(tag("absolute_offset"), multispace1, parse_u32_number)(i)
+    }
+
+    fn relative_offset(i: &str) -> IResult<&str, (&str, i32)> {
+        separated_pair(tag("relative_offset"), multispace1, parse_i32_number)(i)
+    }
+
+    let mut data = DetectAsn1Data::default();
+
+    let mut rest = input;
+
+    // Parse the input and set data
+    while !rest.is_empty() {
+        let (
+            new_rest,
+            (
+                _,
+                bitstring_overflow,
+                double_overflow,
+                oversize_length,
+                absolute_offset,
+                relative_offset,
+                _,
+            ),
+        ) = tuple((
+            opt(multispace0),
+            opt(bitstring_overflow),
+            opt(double_overflow),
+            opt(oversize_length),
+            opt(absolute_offset),
+            opt(relative_offset),
+            opt(alt((multispace1, tag(",")))),
+        ))(rest)?;
+
+        if bitstring_overflow.is_some() {
+            data.bitstring_overflow = true;
+        } else if double_overflow.is_some() {
+            data.double_overflow = true;
+        } else if let Some((_, v)) = oversize_length {
+            data.oversize_length = Some(v);
+        } else if let Some((_, v)) = absolute_offset {
+            data.absolute_offset = Some(v);
+        } else if let Some((_, v)) = relative_offset {
+            data.relative_offset = Some(v);
+        } else {
+            return Err(nom::Err::Error(nom::error::make_error(
+                rest,
+                nom::error::ErrorKind::Verify,
+            )));
+        }
+
+        rest = new_rest;
+    }
+
+    Ok((rest, data))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use test_case::test_case;
+
+    // Test oversize_length
+    #[test_case("oversize_length 1024",
+        DetectAsn1Data { oversize_length: Some(1024), ..Default::default()};
+        "check that we parse oversize_length correctly")]
+    #[test_case("oversize_length",
+        DetectAsn1Data::default() => panics "Error((\"oversize_length\", Verify))";
+        "check that we fail if the needed arg oversize_length is not given")]
+    // Test absolute_offset
+    #[test_case("absolute_offset 1024",
+        DetectAsn1Data { absolute_offset: Some(1024), ..Default::default()};
+        "check that we parse absolute_offset correctly")]
+    #[test_case("absolute_offset",
+        DetectAsn1Data::default() => panics "Error((\"absolute_offset\", Verify))";
+        "check that we fail if the needed arg absolute_offset is not given")]
+    // Test relative_offset
+    #[test_case("relative_offset 1024",
+        DetectAsn1Data { relative_offset: Some(1024), ..Default::default()};
+        "check that we parse relative_offset correctly")]
+    #[test_case("relative_offset",
+        DetectAsn1Data::default() => panics "Error((\"relative_offset\", Verify))";
+        "check that we fail if the needed arg relative_offset is not given")]
+    // Test bitstring_overflow
+    #[test_case("bitstring_overflow",
+        DetectAsn1Data { bitstring_overflow: true, ..Default::default()};
+        "check that we parse bitstring_overflow correctly")]
+    // Test double_overflow
+    #[test_case("double_overflow",
+        DetectAsn1Data { double_overflow: true, ..Default::default()};
+        "check that we parse double_overflow correctly")]
+    // Test combination of params
+    #[test_case("oversize_length 1024, relative_offset 10",
+        DetectAsn1Data { oversize_length: Some(1024), relative_offset: Some(10),
+            ..Default::default()};
+        "check for combinations of keywords (comma seperated)")]
+    #[test_case("oversize_length 1024 absolute_offset 10",
+        DetectAsn1Data { oversize_length: Some(1024), absolute_offset: Some(10),
+            ..Default::default()};
+        "check for combinations of keywords (space seperated)")]
+    #[test_case("oversize_length 1024 absolute_offset 10, bitstring_overflow",
+        DetectAsn1Data { bitstring_overflow: true, oversize_length: Some(1024),
+            absolute_offset: Some(10), ..Default::default()};
+        "check for combinations of keywords (space/comma seperated)")]
+    #[test_case(
+        "double_overflow, oversize_length 1024 absolute_offset 10,\n bitstring_overflow",
+        DetectAsn1Data { double_overflow: true, bitstring_overflow: true,
+            oversize_length: Some(1024), absolute_offset: Some(10),
+            ..Default::default()};
+        "1. check for combinations of keywords (space/comma/newline seperated)")]
+    #[test_case(
+        "\n\t double_overflow, oversize_length 1024 relative_offset 10,\n bitstring_overflow",
+        DetectAsn1Data { double_overflow: true, bitstring_overflow: true,
+            oversize_length: Some(1024), relative_offset: Some(10),
+            ..Default::default()};
+        "2. check for combinations of keywords (space/comma/newline seperated)")]
+    // Test empty
+    #[test_case("",
+        DetectAsn1Data::default() => panics "Error((\"\", Eof))";
+        "test that we break with a empty string")]
+    // Test invalid rules
+    #[test_case("oversize_length 1024, some_other_param 360",
+        DetectAsn1Data::default() => panics "Error((\" some_other_param 360\", Verify))";
+        "test that we break on invalid options")]
+    #[test_case("oversize_length 1024,,",
+        DetectAsn1Data::default() => panics "Error((\",\", Verify))";
+        "test that we break on invalid format (missing option)")]
+    #[test_case("bitstring_overflowabsolute_offset",
+        DetectAsn1Data::default() => panics "Error((\"absolute_offset\", Verify))";
+        "test that we break on invalid format (missing seperator)")]
+    fn test_asn1_parse_rule(input: &str, expected: DetectAsn1Data) {
+        let (rest, res) = asn1_parse_rule(input).unwrap();
+
+        assert_eq!(0, rest.len());
+        assert_eq!(expected, res);
+    }
+}
index 637c3d1a86b423dd24fa93c53dcd4750d8251e84..7e742b5d398b2bfda1f69d72a0890ff04d1aff0b 100644 (file)
@@ -79,4 +79,5 @@ pub mod rfb;
 pub mod applayertemplate;
 pub mod rdp;
 pub mod x509;
+pub mod asn1;
 pub mod ssh;