mirror of https://github.com/OISF/suricata
rust/asn1: Introduce ASN1 rust module
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.cpull/5149/head
parent
6b8517dc12
commit
63704fdf13
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue