quic: complete parsing of initial for non gquic

The format of initial packet for quic ietf, ie quic v1,
is described in rfc 9000, section 17.2.2

Parse more frames and logs interesting extensions from crypto frame

Do not try to parse encrypted data, ie after we have seen
a crypto frame in each direction.

Use sni from crypto frame with tls for detection already implemented

Ticket: #4967
pull/7678/head
Philippe Antoine 3 years ago committed by Philippe Antoine
parent 7044131c39
commit c6cf61a39b

@ -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.

@ -35,6 +35,9 @@ num-traits = "~0.2.14"
widestring = "~0.4.3"
flate2 = "~1.0.19"
brotli = "~3.3.0"
hkdf = "~0.12.3"
aes = "~0.6.0"
aes-gcm = "~0.8.0"
sawp-modbus = "~0.11.0"
sawp = "~0.11.0"

@ -0,0 +1,180 @@
/* Copyright (C) 2021 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 aes::cipher::generic_array::GenericArray;
use aes::Aes128;
use aes::BlockCipher;
use aes::NewBlockCipher;
use aes_gcm::AeadInPlace;
use aes_gcm::Aes128Gcm;
use aes_gcm::NewAead;
use hkdf::Hkdf;
use sha2::Sha256;
pub const AES128_KEY_LEN: usize = 16;
pub const AES128_TAG_LEN: usize = 16;
pub const AES128_IV_LEN: usize = 12;
pub struct HeaderProtectionKey(Aes128);
impl HeaderProtectionKey {
fn new(secret: &[u8]) -> Self {
let hk = Hkdf::<Sha256>::from_prk(secret).unwrap();
let mut secret = [0u8; AES128_KEY_LEN];
hkdf_expand_label(&hk, b"quic hp", &mut secret, AES128_KEY_LEN as u16);
return Self(Aes128::new(GenericArray::from_slice(&secret)));
}
pub fn decrypt_in_place(
&self, sample: &[u8], first: &mut u8, packet_number: &mut [u8],
) -> Result<(), ()> {
let mut mask = GenericArray::clone_from_slice(sample);
self.0.encrypt_block(&mut mask);
let (first_mask, pn_mask) = mask.split_first().unwrap();
let bits = if (*first & 0x80) != 0 {
0x0f // Long header: 4 bits masked
} else {
0x1f // Short header: 5 bits masked
};
*first ^= first_mask & bits;
let pn_len = (*first & 0x03) as usize + 1;
for (dst, m) in packet_number.iter_mut().zip(pn_mask).take(pn_len) {
*dst ^= m;
}
Ok(())
}
}
pub struct PacketKey {
key: Aes128Gcm,
iv: [u8; AES128_IV_LEN],
}
impl PacketKey {
fn new(secret: &[u8]) -> Self {
let hk = Hkdf::<Sha256>::from_prk(secret).unwrap();
let mut secret = [0u8; AES128_KEY_LEN];
hkdf_expand_label(&hk, b"quic key", &mut secret, AES128_KEY_LEN as u16);
let key = Aes128Gcm::new(GenericArray::from_slice(&secret));
let mut r = PacketKey {
key: key,
iv: [0u8; AES128_IV_LEN],
};
hkdf_expand_label(&hk, b"quic iv", &mut r.iv, AES128_IV_LEN as u16);
return r;
}
pub fn decrypt_in_place<'a>(
&self, packet_number: u64, header: &[u8], payload: &'a mut [u8],
) -> Result<&'a [u8], ()> {
if payload.len() < AES128_TAG_LEN {
return Err(());
}
let mut nonce = [0; AES128_IV_LEN];
nonce[4..].copy_from_slice(&packet_number.to_be_bytes());
for (nonce, inp) in nonce.iter_mut().zip(self.iv.iter()) {
*nonce ^= inp;
}
let tag_pos = payload.len() - AES128_TAG_LEN;
let (buffer, tag) = payload.split_at_mut(tag_pos);
let taga = GenericArray::from_slice(tag);
self.key
.decrypt_in_place_detached(GenericArray::from_slice(&nonce), header, buffer, &taga)
.map_err(|_| ())?;
Ok(&payload[..tag_pos])
}
}
pub struct DirectionalKeys {
pub header: HeaderProtectionKey,
pub packet: PacketKey,
}
impl DirectionalKeys {
fn new(secret: &[u8]) -> Self {
Self {
header: HeaderProtectionKey::new(secret),
packet: PacketKey::new(secret),
}
}
}
pub struct QuicKeys {
pub local: DirectionalKeys,
pub remote: DirectionalKeys,
}
fn hkdf_expand_label(hk: &Hkdf<Sha256>, label: &[u8], okm: &mut [u8], olen: u16) {
const LABEL_PREFIX: &[u8] = b"tls13 ";
let output_len = u16::to_be_bytes(olen);
let label_len = u8::to_be_bytes((LABEL_PREFIX.len() + label.len()) as u8);
let context_len = u8::to_be_bytes(0);
let info = &[
&output_len[..],
&label_len[..],
LABEL_PREFIX,
label,
&context_len[..],
];
hk.expand_multi_info(info, okm).unwrap();
}
pub fn quic_keys_initial(version: u32, client_dst_connection_id: &[u8]) -> Option<QuicKeys> {
let salt = match version {
0x51303530 => &[
0x50, 0x45, 0x74, 0xEF, 0xD0, 0x66, 0xFE, 0x2F, 0x9D, 0x94, 0x5C, 0xFC, 0xDB, 0xD3,
0xA7, 0xF0, 0xD3, 0xB5, 0x6B, 0x45,
],
0xff00_001d..=0xff00_0020 => &[
// https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#section-5.2
0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61,
0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99,
],
0xfaceb002 | 0xff00_0017..=0xff00_001c => &[
// https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-23#section-5.2
0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4,
0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02,
],
0x0000_0001 | 0xff00_0021..=0xff00_0022 => &[
// https://www.rfc-editor.org/rfc/rfc9001.html#name-initial-secrets
0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8,
0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a,
],
_ => {
return None;
}
};
let hk = Hkdf::<Sha256>::new(Some(salt), client_dst_connection_id);
let mut client_secret = [0u8; 32];
hkdf_expand_label(&hk, b"client in", &mut client_secret, 32);
let mut server_secret = [0u8; 32];
hkdf_expand_label(&hk, b"server in", &mut server_secret, 32);
return Some(QuicKeys {
local: DirectionalKeys::new(&server_secret),
remote: DirectionalKeys::new(&client_secret),
});
}

@ -24,6 +24,7 @@ pub enum QuicError {
StreamTagNoMatch(u32),
InvalidPacket,
Incomplete,
Unhandled,
NomError(ErrorKind),
}
@ -45,6 +46,7 @@ impl fmt::Display for QuicError {
}
QuicError::Incomplete => write!(f, "Incomplete data"),
QuicError::InvalidPacket => write!(f, "Invalid packet"),
QuicError::Unhandled => write!(f, "Unhandled packet"),
QuicError::NomError(e) => write!(f, "Internal parser error {:?}", e),
}
}

@ -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};
@ -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);
@ -115,13 +122,131 @@ 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>,
}
#[derive(Debug, PartialEq)]
pub(crate) enum Frame {
Padding,
Ping,
Ack(Ack),
Crypto(Crypto),
Stream(Stream),
Unknown(Vec<u8>),
}
fn parse_padding_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> {
// nom take_while: cannot infer type for type parameter `Error` declared on the function `take_while`
let mut offset = 0;
while offset < input.len() {
if input[offset] != 0 {
break;
}
offset = offset + 1;
}
return Ok((&input[offset..], Frame::Padding));
}
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 (mut rest, first_ack_range) = quic_var_uint(rest)?;
for _ in 0..ack_range_count {
//RFC9000 section 19.3.1. ACK Ranges
let (rest1, _gap) = quic_var_uint(rest)?;
let (rest1, _ack_range_length) = quic_var_uint(rest1)?;
rest = rest1;
}
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(input: Option<&[u8]>) -> Vec<QuicTlsExtension> {
let mut extv = Vec::new();
if let Some(extr) = input {
if let Ok((_, exts)) = parse_tls_extensions(extr) {
for e in &exts {
let etype = TlsExtensionType::from(e);
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 })
}
}
}
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, 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 ciphers = ch.ciphers;
let extv = quic_get_tls_extensions(ch.ext);
return Ok((rest, Frame::Crypto(Crypto { ciphers, extv })));
}
ServerHello(sh) => {
let ciphers = vec![sh.cipher];
let extv = quic_get_tls_extensions(sh.ext);
return Ok((rest, Frame::Crypto(Crypto { ciphers, extv })));
}
_ => {}
}
}
}
return Err(nom::Err::Error(QuicError::InvalidPacket));
}
fn parse_tag(input: &[u8]) -> IResult<&[u8], StreamTag, QuicError> {
let (rest, tag) = be_u32(input)?;
@ -164,8 +289,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;
@ -180,7 +303,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 {
@ -210,6 +333,31 @@ fn parse_stream_frame(input: &[u8], frame_ty: u8) -> IResult<&[u8], Frame, QuicE
))
}
fn parse_crypto_stream_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> {
let (rest, _offset) = quic_var_uint(input)?;
let (rest, data_length) = quic_var_uint(rest)?;
if data_length > u32::MAX as u64 {
return Err(nom::Err::Error(QuicError::Unhandled));
}
let (rest, stream_data) = take(data_length as u32)(rest)?;
let tags = if let Ok((_, tags)) = all_consuming(parse_crypto_stream)(stream_data) {
Some(tags)
} else {
None
};
Ok((
rest,
Frame::Stream(Stream {
fin: false,
stream_id: Vec::new(),
offset: Vec::new(),
tags,
}),
))
}
impl Frame {
fn decode_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> {
let (rest, frame_ty) = be_u8(input)?;
@ -220,7 +368,11 @@ impl Frame {
parse_stream_frame(rest, frame_ty)?
} else {
match frame_ty {
0x00 => (rest, Frame::Padding),
0x00 => parse_padding_frame(rest)?,
0x01 => (rest, Frame::Ping),
0x02 => parse_ack_frame(rest)?,
0x06 => parse_crypto_frame(rest)?,
0x08 => parse_crypto_stream_frame(rest)?,
_ => ([].as_ref(), Frame::Unknown(rest.to_vec())),
}
};

@ -15,12 +15,79 @@
* 02110-1301, USA.
*/
use super::parser::QuicType;
use super::quic::QuicTransaction;
use crate::jsonbuilder::{JsonBuilder, JsonError};
fn quic_tls_extension_name(e: u16) -> Option<String> {
match e {
0 => Some("server_name".to_string()),
1 => Some("max_fragment_length".to_string()),
2 => Some("client_certificate_url".to_string()),
3 => Some("trusted_ca_keys".to_string()),
4 => Some("truncated_hmac".to_string()),
5 => Some("status_request".to_string()),
6 => Some("user_mapping".to_string()),
7 => Some("client_authz".to_string()),
8 => Some("server_authz".to_string()),
9 => Some("cert_type".to_string()),
10 => Some("supported_groups".to_string()),
11 => Some("ec_point_formats".to_string()),
12 => Some("srp".to_string()),
13 => Some("signature_algorithms".to_string()),
14 => Some("use_srtp".to_string()),
15 => Some("heartbeat".to_string()),
16 => Some("alpn".to_string()),
17 => Some("status_request_v2".to_string()),
18 => Some("signed_certificate_timestamp".to_string()),
19 => Some("client_certificate_type".to_string()),
20 => Some("server_certificate_type".to_string()),
21 => Some("padding".to_string()),
22 => Some("encrypt_then_mac".to_string()),
23 => Some("extended_master_secret".to_string()),
24 => Some("token_binding".to_string()),
25 => Some("cached_info".to_string()),
26 => Some("tls_lts".to_string()),
27 => Some("compress_certificate".to_string()),
28 => Some("record_size_limit".to_string()),
29 => Some("pwd_protect".to_string()),
30 => Some("pwd_clear".to_string()),
31 => Some("password_salt".to_string()),
32 => Some("ticket_pinning".to_string()),
33 => Some("tls_cert_with_extern_psk".to_string()),
34 => Some("delegated_credentials".to_string()),
35 => Some("session_ticket".to_string()),
36 => Some("tlmsp".to_string()),
37 => Some("tlmsp_proxying".to_string()),
38 => Some("tlmsp_delegate".to_string()),
39 => Some("supported_ekt_ciphers".to_string()),
41 => Some("pre_shared_key".to_string()),
42 => Some("early_data".to_string()),
43 => Some("supported_versions".to_string()),
44 => Some("cookie".to_string()),
45 => Some("psk_key_exchange_modes".to_string()),
47 => Some("certificate_authorities".to_string()),
48 => Some("oid_filters".to_string()),
49 => Some("post_handshake_auth".to_string()),
50 => Some("signature_algorithms_cert".to_string()),
51 => Some("key_share".to_string()),
52 => Some("transparency_info".to_string()),
53 => Some("connection_id_deprecated".to_string()),
54 => Some("connection_id".to_string()),
55 => Some("external_id_hash".to_string()),
56 => Some("external_session_id".to_string()),
57 => Some("quic_transport_parameters".to_string()),
58 => Some("ticket_request".to_string()),
59 => Some("dnssec_chain".to_string()),
13172 => Some("next_protocol_negotiation".to_string()),
65281 => Some("renegotiation_info".to_string()),
_ => None,
}
}
fn log_template(tx: &QuicTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("quic")?;
if tx.header.flags.is_long {
if tx.header.ty != QuicType::Short {
js.set_string("version", String::from(tx.header.version).as_str())?;
if let Some(sni) = &tx.sni {
@ -41,6 +108,28 @@ fn log_template(tx: &QuicTransaction, js: &mut JsonBuilder) -> Result<(), JsonEr
js.close()?;
}
if tx.extv.len() > 0 {
js.open_array("extensions")?;
for e in &tx.extv {
js.start_object()?;
let etype = u16::from(e.etype);
if let Some(s) = quic_tls_extension_name(etype) {
js.set_string("name", &s)?;
}
js.set_uint("type", etype.into())?;
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()?;
Ok(())
}

@ -15,6 +15,7 @@
* 02110-1301, USA.
*/
mod crypto;
mod cyu;
pub mod detect;
mod error;

@ -18,8 +18,9 @@ use super::error::QuicError;
use super::frames::Frame;
use nom::bytes::complete::take;
use nom::combinator::{all_consuming, map};
use nom::number::complete::{be_u32, be_u8};
use nom::number::complete::{be_u24, be_u32, be_u8};
use nom::IResult;
use std::convert::TryFrom;
/*
gQUIC is the Google version of QUIC.
@ -98,6 +99,58 @@ impl PublicFlags {
}
}
pub fn quic_pkt_num(input: &[u8]) -> u64 {
// There must be a more idiomatic way sigh...
match input.len() {
1 => {
return input[0] as u64;
}
2 => {
return ((input[0] as u64) << 8) | (input[1] as u64);
}
3 => {
return ((input[0] as u64) << 16) | ((input[1] as u64) << 8) | (input[2] as u64);
}
4 => {
return ((input[0] as u64) << 24)
| ((input[1] as u64) << 16)
| ((input[2] as u64) << 8)
| (input[3] as u64);
}
_ => {
// should not be reachable
debug_validate_fail!("Unexpected length for quic pkt num");
return 0;
}
}
}
pub fn quic_var_uint(input: &[u8]) -> IResult<&[u8], u64, QuicError> {
let (rest, first) = be_u8(input)?;
let msb = first >> 6;
let lsb = (first & 0x3F) as u64;
match msb {
3 => {
// nom does not have be_u56
let (rest, second) = be_u24(rest)?;
let (rest, third) = be_u32(rest)?;
return Ok((rest, (lsb << 56) | ((second as u64) << 32) | (third as u64)));
}
2 => {
let (rest, second) = be_u24(rest)?;
return Ok((rest, (lsb << 24) | (second as u64)));
}
1 => {
let (rest, second) = be_u8(rest)?;
return Ok((rest, (lsb << 8) | (second as u64)));
}
_ => {
// only remaining case is msb==0
return Ok((rest, lsb));
}
}
}
/// A QUIC packet's header.
#[derive(Debug, PartialEq)]
pub struct QuicHeader {
@ -107,6 +160,7 @@ pub struct QuicHeader {
pub version_buf: Vec<u8>,
pub dcid: Vec<u8>,
pub scid: Vec<u8>,
pub length: u16,
}
#[derive(Debug, PartialEq)]
@ -126,6 +180,7 @@ impl QuicHeader {
version_buf: Vec::new(),
dcid,
scid,
length: 0,
}
}
@ -148,6 +203,7 @@ impl QuicHeader {
version_buf: Vec::new(),
dcid: dcid.to_vec(),
scid: Vec::new(),
length: 0,
},
));
} else {
@ -212,15 +268,40 @@ impl QuicHeader {
(rest, dcid.to_vec(), scid.to_vec())
};
let mut has_length = false;
let rest = match ty {
QuicType::Initial => {
let (rest, _pkt_num) = be_u32(rest)?;
let (rest, _msg_auth_hash) = take(12usize)(rest)?;
rest
if version.is_gquic() {
let (rest, _pkt_num) = be_u32(rest)?;
let (rest, _msg_auth_hash) = take(12usize)(rest)?;
rest
} else {
let (rest, token_length) = quic_var_uint(rest)?;
let (rest, _token) = take(token_length as usize)(rest)?;
has_length = true;
rest
}
}
_ => rest,
};
let (rest, length) = if has_length {
let (rest2, plength) = quic_var_uint(rest)?;
if plength > rest2.len() as u64 {
return Err(nom::Err::Error(QuicError::InvalidPacket));
}
if let Ok(length) = u16::try_from(plength) {
(rest2, length)
} else {
return Err(nom::Err::Error(QuicError::InvalidPacket));
}
} else {
if let Ok(length) = u16::try_from(rest.len()) {
(rest, length)
} else {
return Err(nom::Err::Error(QuicError::InvalidPacket));
}
};
Ok((
rest,
@ -231,6 +312,7 @@ impl QuicHeader {
version_buf: version_buf.to_vec(),
dcid,
scid,
length,
},
))
}
@ -275,7 +357,8 @@ mod tests {
.to_vec(),
scid: hex::decode("09b15e86dd0990aaf906c5de620c4538398ffa58")
.unwrap()
.to_vec()
.to_vec(),
length: 1154,
},
value
);
@ -295,6 +378,7 @@ mod tests {
version_buf: vec![0x51, 0x30, 0x34, 0x34],
dcid: hex::decode("05cad2cc06c4d0e4").unwrap().to_vec(),
scid: Vec::new(),
length: 1042,
},
header
);

@ -16,17 +16,20 @@
*/
use super::{
crypto::{quic_keys_initial, QuicKeys, AES128_KEY_LEN},
cyu::Cyu,
frames::{Frame, StreamTag},
parser::{QuicData, QuicHeader, QuicType},
frames::{Frame, QuicTlsExtension, StreamTag},
parser::{quic_pkt_num, QuicData, QuicHeader, QuicType},
};
use crate::applayer::{self, *};
use crate::core::{AppProto, Flow, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_UDP};
use std::ffi::CString;
use tls_parser::TlsExtensionType;
static mut ALPROTO_QUIC: AppProto = ALPROTO_UNKNOWN;
const DEFAULT_DCID_LEN: usize = 16;
const PKT_NUM_BUF_MAX_LEN: usize = 4;
#[derive(Debug)]
pub struct QuicTransaction {
@ -35,11 +38,15 @@ pub struct QuicTransaction {
pub cyu: Vec<Cyu>,
pub sni: Option<Vec<u8>>,
pub ua: Option<Vec<u8>>,
pub extv: Vec<QuicTlsExtension>,
tx_data: AppLayerTxData,
}
impl QuicTransaction {
fn new(header: QuicHeader, data: QuicData, sni: Option<Vec<u8>>, ua: Option<Vec<u8>>) -> Self {
fn new(
header: QuicHeader, data: QuicData, sni: Option<Vec<u8>>, ua: Option<Vec<u8>>,
extv: Vec<QuicTlsExtension>,
) -> Self {
let cyu = Cyu::generate(&header, &data.frames);
QuicTransaction {
tx_id: 0,
@ -47,6 +54,7 @@ impl QuicTransaction {
cyu,
sni,
ua,
extv,
tx_data: AppLayerTxData::new(),
}
}
@ -54,6 +62,9 @@ impl QuicTransaction {
pub struct QuicState {
max_tx_id: u64,
keys: Option<QuicKeys>,
hello_tc: bool,
hello_ts: bool,
transactions: Vec<QuicTransaction>,
}
@ -61,6 +72,9 @@ impl Default for QuicState {
fn default() -> Self {
Self {
max_tx_id: 0,
keys: None,
hello_tc: false,
hello_ts: false,
transactions: Vec::new(),
}
}
@ -88,11 +102,12 @@ impl QuicState {
fn new_tx(
&mut self, header: QuicHeader, data: QuicData, sni: Option<Vec<u8>>, ua: Option<Vec<u8>>,
) -> QuicTransaction {
let mut tx = QuicTransaction::new(header, data, sni, ua);
extb: Vec<QuicTlsExtension>,
) {
let mut tx = QuicTransaction::new(header, data, sni, ua, extb);
self.max_tx_id += 1;
tx.tx_id = self.max_tx_id;
return tx;
self.transactions.push(tx);
}
fn tx_iterator(
@ -114,44 +129,157 @@ impl QuicState {
return None;
}
fn parse(&mut self, input: &[u8]) -> bool {
match QuicHeader::from_bytes(input, DEFAULT_DCID_LEN) {
Ok((rest, header)) => match QuicData::from_bytes(rest) {
Ok(data) => {
// no tx for the short header (data) frames
if header.ty != QuicType::Short {
let mut sni: Option<Vec<u8>> = None;
let mut ua: Option<Vec<u8>> = None;
for frame in &data.frames {
if let Frame::Stream(s) = frame {
if let Some(tags) = &s.tags {
for (tag, value) in tags {
if tag == &StreamTag::Sni {
sni = Some(value.to_vec());
} else if tag == &StreamTag::Uaid {
ua = Some(value.to_vec());
}
if sni.is_some() && ua.is_some() {
break;
}
}
}
fn decrypt<'a>(
&mut self, to_server: bool, header: &QuicHeader, framebuf: &'a [u8], buf: &'a [u8],
hlen: usize, output: &'a mut Vec<u8>,
) -> Result<usize, ()> {
if let Some(keys) = &self.keys {
let hkey = if to_server {
&keys.remote.header
} else {
&keys.local.header
};
if framebuf.len() < PKT_NUM_BUF_MAX_LEN + AES128_KEY_LEN {
return Err(());
}
let h2len = hlen + usize::from(header.length);
let mut h2 = Vec::with_capacity(h2len);
h2.extend_from_slice(&buf[..h2len]);
let mut h20 = h2[0];
let mut pktnum_buf = Vec::with_capacity(PKT_NUM_BUF_MAX_LEN);
pktnum_buf.extend_from_slice(&h2[hlen..hlen + PKT_NUM_BUF_MAX_LEN]);
let r1 = hkey.decrypt_in_place(
&h2[hlen + PKT_NUM_BUF_MAX_LEN..hlen + PKT_NUM_BUF_MAX_LEN + AES128_KEY_LEN],
&mut h20,
&mut pktnum_buf,
);
if !r1.is_ok() {
return Err(());
}
// mutate one at a time
h2[0] = h20;
let _ = &h2[hlen..hlen + 1 + ((h20 & 3) as usize)]
.copy_from_slice(&pktnum_buf[..1 + ((h20 & 3) as usize)]);
let pkt_num = quic_pkt_num(&h2[hlen..hlen + 1 + ((h20 & 3) as usize)]);
if framebuf.len() < 1 + ((h20 & 3) as usize) {
return Err(());
}
output.extend_from_slice(&framebuf[1 + ((h20 & 3) as usize)..]);
let pkey = if to_server {
&keys.remote.packet
} else {
&keys.local.packet
};
let r = pkey.decrypt_in_place(pkt_num, &h2[..hlen + 1 + ((h20 & 3) as usize)], output);
if let Ok(r2) = r {
return Ok(r2.len());
}
}
return Err(());
}
fn handle_frames(&mut self, data: QuicData, header: QuicHeader, to_server: bool) {
let mut sni: Option<Vec<u8>> = None;
let mut ua: Option<Vec<u8>> = None;
let mut extv: Vec<QuicTlsExtension> = Vec::new();
for frame in &data.frames {
match frame {
Frame::Stream(s) => {
if let Some(tags) = &s.tags {
for (tag, value) in tags {
if tag == &StreamTag::Sni {
sni = Some(value.to_vec());
} else if tag == &StreamTag::Uaid {
ua = Some(value.to_vec());
}
if sni.is_some() && ua.is_some() {
break;
}
}
}
}
Frame::Crypto(c) => {
for e in &c.extv {
if e.etype == TlsExtensionType::ServerName && e.values.len() > 0 {
sni = Some(e.values[0].to_vec());
}
}
extv.extend_from_slice(&c.extv);
if to_server {
self.hello_ts = true
} else {
self.hello_tc = true
}
}
_ => {}
}
}
self.new_tx(header, data, sni, ua, extv);
}
fn parse(&mut self, input: &[u8], to_server: bool) -> bool {
// so as to loop over multiple quic headers in one packet
let mut buf = input;
while buf.len() > 0 {
match QuicHeader::from_bytes(buf, DEFAULT_DCID_LEN) {
Ok((rest, header)) => {
if (to_server && self.hello_ts) || (!to_server && self.hello_tc) {
// payload is encrypted, stop parsing here
return true;
}
if header.ty == QuicType::Short {
// nothing to get
return true;
}
// unprotect/decrypt packet
if self.keys.is_none() && header.ty == QuicType::Initial {
self.keys = quic_keys_initial(u32::from(header.version), &header.dcid);
}
// header.length was checked against rest.len() during parsing
let (mut framebuf, next_buf) = rest.split_at(header.length.into());
let hlen = buf.len() - rest.len();
let mut output;
if self.keys.is_some() {
output = Vec::with_capacity(framebuf.len() + 4);
if let Ok(dlen) =
self.decrypt(to_server, &header, framebuf, buf, hlen, &mut output)
{
output.resize(dlen, 0);
} else {
return false;
}
framebuf = &output;
}
buf = next_buf;
if header.ty != QuicType::Initial {
// only version is interesting, no frames
self.new_tx(
header,
QuicData { frames: Vec::new() },
None,
None,
Vec::new(),
);
continue;
}
let transaction = self.new_tx(header, data, sni, ua);
self.transactions.push(transaction);
match QuicData::from_bytes(framebuf) {
Ok(data) => {
self.handle_frames(data, header, to_server);
}
Err(_e) => {
return false;
}
}
return true;
}
Err(_e) => {
return false;
}
},
Err(_e) => {
return false;
}
}
return true;
}
}
@ -190,17 +318,33 @@ pub unsafe extern "C" fn rs_quic_probing_parser(
}
#[no_mangle]
pub unsafe extern "C" fn rs_quic_parse(
pub unsafe extern "C" fn rs_quic_parse_tc(
_flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void,
stream_slice: StreamSlice, _data: *const std::os::raw::c_void,
) -> AppLayerResult {
let state = cast_pointer!(state, QuicState);
let buf = stream_slice.as_slice();
if state.parse(buf, false) {
return AppLayerResult::ok();
} else {
return AppLayerResult::err();
}
}
#[no_mangle]
pub unsafe extern "C" fn rs_quic_parse_ts(
_flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void,
stream_slice: StreamSlice, _data: *const std::os::raw::c_void,
) -> AppLayerResult {
let state = cast_pointer!(state, QuicState);
let buf = stream_slice.as_slice();
if state.parse(buf) {
if state.parse(buf, true) {
return AppLayerResult::ok();
} else {
return AppLayerResult::err();
}
return AppLayerResult::err();
}
#[no_mangle]
@ -275,8 +419,8 @@ pub unsafe extern "C" fn rs_quic_register_parser() {
state_new: rs_quic_state_new,
state_free: rs_quic_state_free,
tx_free: rs_quic_state_tx_free,
parse_ts: rs_quic_parse,
parse_tc: rs_quic_parse,
parse_ts: rs_quic_parse_ts,
parse_tc: rs_quic_parse_tc,
get_tx_count: rs_quic_state_get_tx_count,
get_tx: rs_quic_state_get_tx,
tx_comp_st_ts: 1,

Loading…
Cancel
Save