summaryrefslogtreecommitdiffstats
path: root/rust/src/asn1
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:39:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:39:49 +0000
commita0aa2307322cd47bbf416810ac0292925e03be87 (patch)
tree37076262a026c4b48c8a0e84f44ff9187556ca35 /rust/src/asn1
parentInitial commit. (diff)
downloadsuricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz
suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'rust/src/asn1')
-rw-r--r--rust/src/asn1/mod.rs393
-rw-r--r--rust/src/asn1/parse_rules.rs326
2 files changed, 719 insertions, 0 deletions
diff --git a/rust/src/asn1/mod.rs b/rust/src/asn1/mod.rs
new file mode 100644
index 0000000..4b77b0c
--- /dev/null
+++ b/rust/src/asn1/mod.rs
@@ -0,0 +1,393 @@
+/* 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.
+ */
+
+//! ASN.1 parser module.
+
+use der_parser::ber::{parse_ber_recursive, BerObject, BerObjectContent, Tag};
+use nom7::Err;
+use std::convert::TryFrom;
+
+mod parse_rules;
+use parse_rules::DetectAsn1Data;
+
+/// Container for parsed Asn1 objects
+#[derive(Debug)]
+pub struct Asn1<'a>(Vec<BerObject<'a>>);
+
+/// Errors possible during decoding of Asn1
+#[derive(Debug)]
+enum Asn1DecodeError {
+ InvalidKeywordParameter,
+ MaxFrames,
+ BerError(Err<der_parser::error::BerError>),
+}
+
+/// Enumeration of Asn1 checks
+#[derive(Debug, PartialEq)]
+enum Asn1Check {
+ OversizeLength,
+ BitstringOverflow,
+ DoubleOverflow,
+ MaxDepth,
+}
+
+impl<'a> Asn1<'a> {
+ /// Checks each BerObject contained in self with the provided detection
+ /// data, returns the first successful match if one occurs
+ fn check(&self, ad: &DetectAsn1Data) -> Option<Asn1Check> {
+ for obj in &self.0 {
+ let res = Asn1::check_object_recursive(obj, ad, ad.max_frames as usize);
+ if res.is_some() {
+ return res;
+ }
+ }
+
+ None
+ }
+
+ fn check_object_recursive(
+ obj: &BerObject, ad: &DetectAsn1Data, max_depth: usize,
+ ) -> Option<Asn1Check> {
+ // Check stack depth
+ if max_depth == 0 {
+ return Some(Asn1Check::MaxDepth);
+ }
+
+ // Check current object
+ let res = Asn1::check_object(obj, ad);
+ if res.is_some() {
+ return 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 res;
+ }
+ }
+
+ None
+ }
+
+ /// Checks a BerObject and subnodes against the Asn1 checks
+ fn check_object(obj: &BerObject, ad: &DetectAsn1Data) -> Option<Asn1Check> {
+ // get length
+ // Note that if length is indefinite (BER), this will return None
+ let len = obj.header.length().definite().ok()?;
+ // 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 len > oversize_length as usize
+ || 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() == Tag::BitString
+ && obj.header.is_primitive())
+ {
+ if let BerObjectContent::BitString(bits, _v) = &obj.content {
+ if len > 0
+ && *bits as usize > len.saturating_mul(8)
+ {
+ 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() == Tag::RealType
+ && obj.header.is_primitive())
+ {
+ if let Ok(data) = obj.content.as_slice() {
+ if len > 0
+ && !data.is_empty()
+ && data[0] & 0xC0 == 0
+ && (len > 256 || data.len() > 256)
+ {
+ return Some(Asn1Check::DoubleOverflow);
+ }
+ }
+ }
+
+ None
+ }
+
+ fn from_slice(input: &'a [u8], ad: &DetectAsn1Data) -> Result<Asn1<'a>, 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<'a>(
+ buffer: &'a [u8], buffer_offset: u32, ad: &DetectAsn1Data,
+) -> Result<Asn1<'a>, Asn1DecodeError> {
+ // Get offset
+ let offset = if let Some(absolute_offset) = ad.absolute_offset {
+ absolute_offset
+ } else if let Some(relative_offset) = ad.relative_offset {
+ // relative offset in regards to the last content match
+
+ // buffer_offset (u32) + relative_offset (i32) => offset (u16)
+ u16::try_from({
+ if relative_offset > 0 {
+ buffer_offset
+ .checked_add(u32::try_from(relative_offset)?)
+ .ok_or(Asn1DecodeError::InvalidKeywordParameter)?
+ } else {
+ buffer_offset
+ .checked_sub(u32::try_from(-relative_offset)?)
+ .ok_or(Asn1DecodeError::InvalidKeywordParameter)?
+ }
+ })
+ .or(Err(Asn1DecodeError::InvalidKeywordParameter))?
+ } else {
+ 0
+ };
+
+ // Make sure we won't read past the end or front of the buffer
+ if offset as usize >= buffer.len() {
+ return Err(Asn1DecodeError::InvalidKeywordParameter);
+ }
+
+ // Get slice from buffer at offset
+ let slice = &buffer[offset as usize..];
+
+ 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 unsafe extern "C" fn rs_asn1_decode(
+ input: *const u8, input_len: u16, buffer_offset: u32, ad_ptr: *const DetectAsn1Data,
+) -> *mut Asn1<'static> {
+ if input.is_null() || input_len == 0 || ad_ptr.is_null() {
+ return std::ptr::null_mut();
+ }
+
+ let slice = build_slice!(input, input_len as usize);
+
+ let ad = &*ad_ptr ;
+
+ let res = asn1_decode(slice, buffer_offset, 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 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;
+
+ match asn1.check(ad) {
+ Some(_check) => 1,
+ None => 0,
+ }
+}
+
+impl From<std::num::TryFromIntError> for Asn1DecodeError {
+ fn from(_e: std::num::TryFromIntError) -> Asn1DecodeError {
+ Asn1DecodeError::InvalidKeywordParameter
+ }
+}
+
+impl From<Err<der_parser::error::BerError>> for Asn1DecodeError {
+ fn from(e: Err<der_parser::error::BerError>) -> Asn1DecodeError {
+ Asn1DecodeError::BerError(e)
+ }
+}
+
+#[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);
+ 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
index 0000000..540734c
--- /dev/null
+++ b/rust/src/asn1/parse_rules.rs
@@ -0,0 +1,326 @@
+/* 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 nom7::branch::alt;
+use nom7::bytes::complete::tag;
+use nom7::character::complete::{digit1, multispace0, multispace1};
+use nom7::combinator::{map_res, opt, verify};
+use nom7::error::{make_error, ErrorKind};
+use nom7::sequence::{separated_pair, tuple};
+use nom7::{Err, 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 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 {
+ SCLogError!("Could not parse asn1-max-frames: {}", max_frames);
+ return std::ptr::null_mut();
+ };
+ }
+
+ Box::into_raw(Box::new(data))
+ }
+ Err(e) => {
+ SCLogError!("Malformed asn1 argument: {}", e.to_string());
+ 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 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, Eq)]
+pub struct DetectAsn1Data {
+ pub bitstring_overflow: bool,
+ pub double_overflow: bool,
+ pub oversize_length: Option<u32>,
+ pub absolute_offset: Option<u16>,
+ 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_u16_number(input: &str) -> IResult<&str, u16> {
+ map_res(digit1, |digits: &str| digits.parse::<u16>())(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(Err::Error(make_error(
+ input,
+ 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, u16)> {
+ separated_pair(tag("absolute_offset"), multispace1, parse_u16_number)(i)
+ }
+
+ fn relative_offset(i: &str) -> IResult<&str, (&str, i32)> {
+ separated_pair(
+ tag("relative_offset"),
+ multispace1,
+ verify(parse_i32_number, |v| {
+ *v >= -i32::from(std::u16::MAX) && *v <= i32::from(std::u16::MAX)
+ }),
+ )(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(Err::Error(make_error(
+ rest,
+ 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 0",
+ DetectAsn1Data { oversize_length: Some(0), ..Default::default()};
+ "check lower bound on oversize_length")]
+ #[test_case("oversize_length -1",
+ DetectAsn1Data::default() => panics r#"Error { input: "oversize_length -1", code: Verify }"#;
+ "check under lower bound on oversize_length")]
+ #[test_case("oversize_length 4294967295",
+ DetectAsn1Data { oversize_length: Some(4294967295), ..Default::default()};
+ "check upper bound on oversize_length")]
+ #[test_case("oversize_length 4294967296",
+ DetectAsn1Data::default() => panics r#"Error { input: "oversize_length 4294967296", code: Verify }"#;
+ "check over upper bound on oversize_length")]
+ #[test_case("oversize_length",
+ DetectAsn1Data::default() => panics r#"Error { input: "oversize_length", code: 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 0",
+ DetectAsn1Data { absolute_offset: Some(0), ..Default::default()};
+ "check lower bound on absolute_offset")]
+ #[test_case("absolute_offset -1",
+ DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset -1", code: Verify }"#;
+ "check under lower bound on absolute_offset")]
+ #[test_case("absolute_offset 65535",
+ DetectAsn1Data { absolute_offset: Some(65535), ..Default::default()};
+ "check upper bound on absolute_offset")]
+ #[test_case("absolute_offset 65536",
+ DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset 65536", code: Verify }"#;
+ "check over upper bound on absolute_offset")]
+ #[test_case("absolute_offset",
+ DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset", code: 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 -65535",
+ DetectAsn1Data { relative_offset: Some(-65535), ..Default::default()};
+ "check lower bound on relative_offset")]
+ #[test_case("relative_offset -65536",
+ DetectAsn1Data::default() => panics r#"Error { input: "relative_offset -65536", code: Verify }"#;
+ "check under lower bound on relative_offset")]
+ #[test_case("relative_offset 65535",
+ DetectAsn1Data { relative_offset: Some(65535), ..Default::default()};
+ "check upper bound on relative_offset")]
+ #[test_case("relative_offset 65536",
+ DetectAsn1Data::default() => panics r#"Error { input: "relative_offset 65536", code: Verify }"#;
+ "check over upper bound on relative_offset")]
+ #[test_case("relative_offset",
+ DetectAsn1Data::default() => panics r#"Error { input: "relative_offset", code: 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 r#"Error { input: "", code: Eof }"#;
+ "test that we break with a empty string")]
+ // Test invalid rules
+ #[test_case("oversize_length 1024, some_other_param 360",
+ DetectAsn1Data::default() => panics r#"Error { input: " some_other_param 360", code: Verify }"#;
+ "test that we break on invalid options")]
+ #[test_case("oversize_length 1024,,",
+ DetectAsn1Data::default() => panics r#"Error { input: ",", code: Verify }"#;
+ "test that we break on invalid format (missing option)")]
+ #[test_case("bitstring_overflowabsolute_offset",
+ DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset", code: Verify }"#;
+ "test that we break on invalid format (missing separator)")]
+ 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);
+ }
+}