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.
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"
--- /dev/null
+/* 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),
+ });
+}
StreamTagNoMatch(u32),
InvalidPacket,
Incomplete,
+ Unhandled,
NomError(ErrorKind),
}
}
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),
}
}
*/
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};
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);
}
}
+#[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)?;
}
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;
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 {
))
}
+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)?;
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())),
}
};
* 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 {
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(())
}
* 02110-1301, USA.
*/
+mod crypto;
mod cyu;
pub mod detect;
mod error;
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.
}
}
+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 {
pub version_buf: Vec<u8>,
pub dcid: Vec<u8>,
pub scid: Vec<u8>,
+ pub length: u16,
}
#[derive(Debug, PartialEq)]
version_buf: Vec::new(),
dcid,
scid,
+ length: 0,
}
}
version_buf: Vec::new(),
dcid: dcid.to_vec(),
scid: Vec::new(),
+ length: 0,
},
));
} else {
(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,
version_buf: version_buf.to_vec(),
dcid,
scid,
+ length,
},
))
}
.to_vec(),
scid: hex::decode("09b15e86dd0990aaf906c5de620c4538398ffa58")
.unwrap()
- .to_vec()
+ .to_vec(),
+ length: 1154,
},
value
);
version_buf: vec![0x51, 0x30, 0x34, 0x34],
dcid: hex::decode("05cad2cc06c4d0e4").unwrap().to_vec(),
scid: Vec::new(),
+ length: 1042,
},
header
);
*/
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 {
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,
cyu,
sni,
ua,
+ extv,
tx_data: AppLayerTxData::new(),
}
}
pub struct QuicState {
max_tx_id: u64,
+ keys: Option<QuicKeys>,
+ hello_tc: bool,
+ hello_ts: bool,
transactions: Vec<QuicTransaction>,
}
fn default() -> Self {
Self {
max_tx_id: 0,
+ keys: None,
+ hello_tc: false,
+ hello_ts: false,
transactions: Vec::new(),
}
}
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(
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;
}
}
}
#[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]
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,