You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
suricata/rust/src/ike/parser.rs

713 lines
23 KiB
Rust

/* 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)?;
3 years ago
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)?;
3 years ago
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)?;
3 years ago
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)?;
3 years ago
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)?;
3 years ago
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
3 years ago
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),
3 years ago
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,
3 years ago
_ => AttributeValue::Unknown,
},
_ => AttributeValue::Unknown,
},
numeric_value: match format.0 {
1 => Some(attribute_length_or_value as u32),
3 years ago
0 => numeric_variable_value,
_ => None,
},
hex_value: match format.0 {
3 years ago
0 => variable_attribute_value
.map(to_hex),
_ => None,
3 years ago
},
};
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)?;
3 years ago
Ok((
i,
IsakmpPayload {
payload_header: IsakmpPayloadHeader {
next_payload,
reserved,
payload_length,
},
data: payload_data.unwrap_or_default(),
},
3 years ago
))
}
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<'a>(
payload_type: u8, data: &'a [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 let Err(_) = parse_security_association_payload(
data,
data_length,
domain_of_interpretation,
key_exchange,
nonce,
transforms,
vendor_ids,
payload_types,
) {
SCLogDebug!("Error parsing SecurityAssociation");
return Err(());
}
Ok(())
}
Some(IsakmpPayloadType::Proposal) => {
if let Err(_) = parse_proposal_payload(
data,
data_length,
domain_of_interpretation,
key_exchange,
nonce,
transforms,
vendor_ids,
payload_types,
) {
SCLogDebug!("Error parsing Proposal");
return Err(());
}
Ok(())
}
Some(IsakmpPayloadType::Transform) => {
if let Ok((_rem, payload)) = parse_transform(data, data_length) {
if let Ok((_, attribute_list)) = parse_sa_attribute(payload.sa_attributes) {
transforms.push(attribute_list);
}
}
Ok(())
}
Some(IsakmpPayloadType::KeyExchange) => {
let res = parse_key_exchange(data, data_length);
if let Ok((_rem, payload)) = res {
*key_exchange = Vec::from(payload.key_exchange_data);
}
Ok(())
}
Some(IsakmpPayloadType::Nonce) => {
let res = parse_nonce(data, data_length);
if let Ok((_rem, payload)) = res {
*nonce = Vec::from(payload.nonce_data);
}
Ok(())
}
Some(IsakmpPayloadType::VendorID) => {
let res = parse_vendor_id(data, data_length);
if let Ok((_rem, payload)) = res {
vendor_ids.push(to_hex(payload.vendor_id));
}
Ok(())
}
_ => Ok(()),
}
}
fn parse_proposal_payload<'a>(
data: &'a [u8], data_length: u16, domain_of_interpretation: &mut Option<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 let Err(_) = parse_payload(
cur_payload_type,
isakmp_payload.data,
isakmp_payload.data.len() as u16,
domain_of_interpretation,
key_exchange,
nonce,
transforms,
vendor_ids,
payload_types,
) {
SCLogDebug!("Error parsing transform payload");
return Err(());
}
cur_payload_type = isakmp_payload.payload_header.next_payload;
}
Ok(())
}
Err(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<'a>(
data: &'a [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 let Err(_) = parse_payload(
cur_payload_type,
isakmp_payload.data,
isakmp_payload.data.len() as u16,
domain_of_interpretation,
key_exchange,
nonce,
transforms,
vendor_ids,
payload_types,
) {
SCLogDebug!("Error parsing proposal payload");
return Err(());
}
cur_payload_type = isakmp_payload.payload_header.next_payload;
}
}
Err(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(()),
}
}