Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quic ietf 4967 v5 #7106

Closed
wants to merge 13 commits into from
Closed
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@
LUA_LIB_NAME="lua-5.1"
CFLAGS="${CFLAGS} -DOS_DARWIN"
CPPFLAGS="${CPPFLAGS} -I/opt/local/include"
LDFLAGS="${LDFLAGS} -L/opt/local/lib"
LDFLAGS="${LDFLAGS} -L/opt/local/lib -framework Security"
;;
*-*-linux*)
# Always compile with -fPIC on Linux for shared library support.
Expand Down
4 changes: 3 additions & 1 deletion rust/Cargo.toml.in
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ kerberos-parser = "~0.5.0"
ntp-parser = "~0.6.0"
ipsec-parser = "~0.7.0"
snmp-parser = "~0.6.0"
tls-parser = "~0.9.4"
tls-parser = "~0.11.0"
x509-parser = "~0.6.5"
libc = "~0.2.82"
sha2 = "~0.9.2"
Expand All @@ -53,6 +53,8 @@ md-5 = "~0.9.1"
regex = "~1.4.2"
lazy_static = "~1.4.0"
base64 = "~0.13.0"
# the feature quic is needed by rustls::quic::Keys to "decrypt" the first quic packets
rustls = { version="~0.20.3", default-features = false, features=["quic"] }

suricata-derive = { path = "./derive" }

Expand Down
17 changes: 16 additions & 1 deletion rust/src/quic/detect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* 02110-1301, USA.
*/

use crate::quic::quic::{QuicTransaction};
use crate::quic::quic::QuicTransaction;
use std::ptr;

#[no_mangle]
Expand Down Expand Up @@ -48,6 +48,21 @@ pub unsafe extern "C" fn rs_quic_tx_get_sni(
}
}

#[no_mangle]
pub unsafe extern "C" fn rs_quic_tx_get_ja3(
tx: &QuicTransaction, buffer: *mut *const u8, buffer_len: *mut u32,
) -> u8 {
if let Some(ja3) = &tx.ja3 {
*buffer = ja3.as_ptr();
*buffer_len = ja3.len() as u32;
1
} else {
*buffer = ptr::null();
*buffer_len = 0;
0
}
}

#[no_mangle]
pub unsafe extern "C" fn rs_quic_tx_get_version(
tx: &QuicTransaction, buffer: *mut *const u8, buffer_len: *mut u32,
Expand Down
2 changes: 2 additions & 0 deletions rust/src/quic/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub enum QuicError {
StreamTagNoMatch(u32),
InvalidPacket,
Incomplete,
NotSupported,
NomError(ErrorKind),
}

Expand All @@ -45,6 +46,7 @@ impl fmt::Display for QuicError {
}
QuicError::Incomplete => write!(f, "Incomplete data"),
QuicError::InvalidPacket => write!(f, "Invalid packet"),
QuicError::NotSupported => write!(f, "Not supported case yet"),
QuicError::NomError(e) => write!(f, "Internal parser error {:?}", e),
}
}
Expand Down
184 changes: 181 additions & 3 deletions rust/src/quic/frames.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

use super::error::QuicError;
use crate::quic::parser::quic_var_uint;
use nom::bytes::complete::take;
use nom::combinator::{all_consuming, complete};
use nom::multi::{count, many0};
Expand All @@ -24,6 +25,12 @@ use nom::sequence::pair;
use nom::IResult;
use num::FromPrimitive;
use std::fmt;
use tls_parser::TlsMessage::Handshake;
use tls_parser::TlsMessageHandshake::{ClientHello, ServerHello};
use tls_parser::{
parse_tls_extensions, parse_tls_message_handshake, TlsCipherSuiteID, TlsExtension,
TlsExtensionType,
};

/// Tuple of StreamTag and offset
type TagOffset = (StreamTag, u32);
Expand Down Expand Up @@ -115,13 +122,184 @@ impl fmt::Display for StreamTag {
}
}

#[derive(Debug, PartialEq)]
pub(crate) struct Ack {
pub largest_acknowledged: u64,
pub ack_delay: u64,
pub ack_range_count: u64,
pub first_ack_range: u64,
}

#[derive(Debug, PartialEq)]
pub(crate) struct Crypto {
pub ciphers: Vec<TlsCipherSuiteID>,
// We remap the Vec<TlsExtension> from tls_parser::parse_tls_extensions because of
// the lifetime of TlsExtension due to references to the slice used for parsing
pub extv: Vec<QuicTlsExtension>,
pub ja3: String,
}

#[derive(Debug, PartialEq)]
pub(crate) enum Frame {
Padding,
Ack(Ack),
Crypto(Crypto),
Stream(Stream),
Unknown(Vec<u8>),
}

fn parse_ack_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> {
let (rest, largest_acknowledged) = quic_var_uint(input)?;
let (rest, ack_delay) = quic_var_uint(rest)?;
let (rest, ack_range_count) = quic_var_uint(rest)?;
let (rest, first_ack_range) = quic_var_uint(rest)?;

if ack_range_count != 0 {
//TODO RFC9000 section 19.3.1. ACK Ranges
return Err(nom::Err::Error(QuicError::NotSupported));
}

Ok((
rest,
Frame::Ack(Ack {
largest_acknowledged,
ack_delay,
ack_range_count,
first_ack_range,
}),
))
}

#[derive(Clone, Debug, PartialEq)]
pub struct QuicTlsExtension {
pub etype: TlsExtensionType,
pub values: Vec<Vec<u8>>,
}

fn quic_tls_ja3_client_extends(ja3: &mut String, exts: Vec<TlsExtension>) {
ja3.push_str(",");
let mut dash = false;
for e in &exts {
match e {
TlsExtension::EllipticCurves(x) => {
for ec in x {
if dash {
ja3.push_str("-");
} else {
dash = true;
}
ja3.push_str(&ec.0.to_string());
}
}
_ => {}
}
}
ja3.push_str(",");
dash = false;
for e in &exts {
match e {
TlsExtension::EcPointFormats(x) => {
for ec in *x {
if dash {
ja3.push_str("-");
} else {
dash = true;
}
ja3.push_str(&ec.to_string());
}
}
_ => {}
}
}
}

// get interesting stuff out of parsed tls extensions
fn quic_get_tls_extensions(
input: Option<&[u8]>, ja3: &mut String, client: bool,
) -> Vec<QuicTlsExtension> {
let mut extv = Vec::new();
if let Some(extr) = input {
if let Ok((_, exts)) = parse_tls_extensions(extr) {
let mut dash = false;
for e in &exts {
let etype = TlsExtensionType::from(e);
if dash {
ja3.push_str("-");
} else {
dash = true;
}
ja3.push_str(&u16::from(etype).to_string());
let mut values = Vec::new();
match e {
TlsExtension::SNI(x) => {
for sni in x {
let mut value = Vec::new();
value.extend_from_slice(sni.1);
values.push(value);
}
}
TlsExtension::ALPN(x) => {
for alpn in x {
let mut value = Vec::new();
value.extend_from_slice(alpn);
values.push(value);
}
}
_ => {}
}
extv.push(QuicTlsExtension { etype, values })
}
if client {
quic_tls_ja3_client_extends(ja3, exts);
}
}
}
return extv;
}

fn parse_crypto_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> {
let (rest, offset) = quic_var_uint(input)?;
let (rest, length) = quic_var_uint(rest)?;
let (rest, _) = take(offset as usize)(rest)?;
let (_, data) = take(length as usize)(rest)?;

if let Ok((rest, msg)) = parse_tls_message_handshake(data) {
if let Handshake(hs) = msg {
match hs {
ClientHello(ch) => {
let mut ja3 = String::with_capacity(256);
ja3.push_str(&u16::from(ch.version).to_string());
let mut dash = false;
for c in &ch.ciphers {
if dash {
ja3.push_str("-");
} else {
dash = true;
}
ja3.push_str(&u16::from(*c).to_string());
}
ja3.push_str(",");
let ciphers = ch.ciphers;
let extv = quic_get_tls_extensions(ch.ext, &mut ja3, true);
return Ok((rest, Frame::Crypto(Crypto { ciphers, extv, ja3 })));
}
ServerHello(sh) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In TLS/tcp we only have ja3 for client hello iirc, for the server side there is ja3s. How is that here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used only one ja3.
I do not understand why there should be a different naming.

The client ja3 has EllipticCurve and EllipticCurvePointFormat data while the server does not have them (as the server is not supposed to send such extensions) cf quic_tls_ja3_client_extends

Does that answer your question ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at least for tls ja3 is for clients, ja3s for servers

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not our invention, just part of the official "standard" https://github.com/salesforce/ja3#ja3s

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, you want two signature keywords ja3 and ja3s instead of using toclient or toserver ? Or ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to it match how we do it for tls, unless there is a good reason not to do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #7130

I find it a bit strange as a code duplication, but I do not think this reason is good enough

let mut ja3 = String::with_capacity(256);
ja3.push_str(&u16::from(sh.version).to_string());
ja3.push_str(",");
ja3.push_str(&u16::from(sh.cipher).to_string());
ja3.push_str(",");
let ciphers = vec![sh.cipher];
let extv = quic_get_tls_extensions(sh.ext, &mut ja3, false);
return Ok((rest, Frame::Crypto(Crypto { ciphers, extv, ja3 })));
}
_ => {}
}
}
}
return Err(nom::Err::Error(QuicError::InvalidPacket));
}

fn parse_tag(input: &[u8]) -> IResult<&[u8], StreamTag, QuicError> {
let (rest, tag) = be_u32(input)?;

Expand Down Expand Up @@ -164,8 +342,6 @@ fn parse_crypto_stream(input: &[u8]) -> IResult<&[u8], Vec<TagValue>, QuicError>
}

fn parse_stream_frame(input: &[u8], frame_ty: u8) -> IResult<&[u8], Frame, QuicError> {
let rest = input;

// 0b1_f_d_ooo_ss
let fin = frame_ty & 0x40 == 0x40;
let has_data_length = frame_ty & 0x20 == 0x20;
Expand All @@ -180,7 +356,7 @@ fn parse_stream_frame(input: &[u8], frame_ty: u8) -> IResult<&[u8], Frame, QuicE

let stream_id_hdr_length = usize::from((frame_ty & 0x03) + 1);

let (rest, stream_id) = take(stream_id_hdr_length)(rest)?;
let (rest, stream_id) = take(stream_id_hdr_length)(input)?;
let (rest, offset) = take(offset_hdr_length)(rest)?;

let (rest, data_length) = if has_data_length {
Expand Down Expand Up @@ -221,6 +397,8 @@ impl Frame {
} else {
match frame_ty {
0x00 => (rest, Frame::Padding),
0x02 => parse_ack_frame(rest)?,
0x06 => parse_crypto_frame(rest)?,
_ => ([].as_ref(), Frame::Unknown(rest.to_vec())),
}
};
Expand Down
Loading