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 v3 #7095

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
211 changes: 208 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,13 @@ use nom::sequence::pair;
use nom::IResult;
use num::FromPrimitive;
use std::fmt;
use std::fmt::Write;
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 +123,210 @@ 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>,
pub extv: Vec<QuicTlsExtension>,
//does not work, because of lifetime due to references to the slice used for parsing
Copy link
Member

Choose a reason for hiding this comment

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

can this comment move to extv just above and explain why it is like this instead of using Vec<TlsExtension>? Comments about why code that is in use are like they are, are more useful than comments explaining commented out things, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok

//and cannot manage to create a stucture with the Vector the slices refer to...
//pub exts: Vec<TlsExtension>,
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>>,
}

// get interesting stuff out of parsed tls extensions
fn quic_get_tls_extensions(
Copy link
Member

Choose a reason for hiding this comment

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

could this func be done in a more compact way?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

more compact with Jason advice

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 {
match write!(ja3, "-") {
_ => {}
}
} else {
dash = true;
}
match write!(ja3, "{}", u16::from(etype)) {
_ => {}
}
Comment on lines +198 to +200
Copy link
Member

Choose a reason for hiding this comment

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

The idiomatic way to avoid the result warning is:

let _ = write!(...

let mut values = Vec::new();
match e {
Copy link
Member

Choose a reason for hiding this comment

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

can this be broken out into a util func?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jasonish is there a better rustier way to build a string and write integer into it ?

Copy link
Member

Choose a reason for hiding this comment

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

You could do

let mut s = String::new();
s.push_str(&my_u16.to_string());

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you Jason, looks better indeed

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 {
Copy link
Member

Choose a reason for hiding this comment

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

perhaps move this into a client specific util func

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok

match write!(ja3, ",") {
_ => {}
}
dash = false;
for e in &exts {
match e {
TlsExtension::EllipticCurves(x) => {
for ec in x {
if dash {
match write!(ja3, "-") {
_ => {}
}
} else {
dash = true;
}
match write!(ja3, "{}", ec.0) {
_ => {}
}
}
}
_ => {}
}
}
match write!(ja3, ",") {
_ => {}
}
dash = false;
for e in &exts {
match e {
TlsExtension::EcPointFormats(x) => {
for ec in *x {
if dash {
match write!(ja3, "-") {
_ => {}
}
} else {
dash = true;
}
match write!(ja3, "{}", ec) {
_ => {}
}
}
}
_ => {}
}
}
}
}
}
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);
match write!(&mut ja3, "{},", u16::from(ch.version)) {
_ => {}
}
let mut dash = false;
for c in &ch.ciphers {
if dash {
match write!(&mut ja3, "-") {
Copy link
Member

Choose a reason for hiding this comment

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

these kinds of statements are taking 3 lines each for no good reason. Can these be made more compact?

Copy link
Member

Choose a reason for hiding this comment

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

let _ = write...

(noted above already)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

push_str seems nice

_ => {}
}
} else {
dash = true;
}
match write!(&mut ja3, "{}", u16::from(*c)) {
_ => {}
}
}
match write!(&mut ja3, ",") {
_ => {}
}
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) => {
let mut ja3 = String::with_capacity(256);
match write!(&mut ja3, "{},", u16::from(sh.version)) {
_ => {}
}
match write!(&mut ja3, "{}", u16::from(sh.cipher)) {
_ => {}
}
match write!(&mut ja3, ",") {
_ => {}
}
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 +369,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 +383,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 +424,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
40 changes: 34 additions & 6 deletions rust/src/quic/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

use super::quic::QuicTransaction;
use crate::jsonbuilder::{JsonBuilder, JsonError};
use std::fmt::Write;

fn log_template(tx: &QuicTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("quic")?;
Expand All @@ -30,14 +31,41 @@ fn log_template(tx: &QuicTransaction, js: &mut JsonBuilder) -> Result<(), JsonEr
js.set_string("ua", &String::from_utf8_lossy(&ua))?;
}
}
js.open_array("cyu")?;
for cyu in &tx.cyu {
js.start_object()?;
js.set_string("hash", &cyu.hash)?;
js.set_string("string", &cyu.string)?;
if tx.cyu.len() > 0 {
js.open_array("cyu")?;
for cyu in &tx.cyu {
js.start_object()?;
js.set_string("hash", &cyu.hash)?;
js.set_string("string", &cyu.string)?;
js.close()?;
}
js.close()?;
}

if let Some(ja3) = &tx.ja3 {
js.set_string("ja3", ja3)?;
}
if tx.extv.len() > 0 {
js.open_array("extensions")?;
for e in &tx.extv {
let mut etypes = String::with_capacity(32);
match write!(&mut etypes, "{}", e.etype) {
_ => {}
}
js.start_object()?;
js.set_string("type", &etypes)?;

if e.values.len() > 0 {
js.open_array("values")?;
for i in 0..e.values.len() {
js.append_string(&String::from_utf8_lossy(&e.values[i]))?;
}
js.close()?;
}
js.close()?;
}
js.close()?;
}
js.close()?;

js.close()?;
Ok(())
Expand Down
Loading