|
|
|
|
@ -123,6 +123,9 @@ impl QuicTransaction {
|
|
|
|
|
pub struct QuicState {
|
|
|
|
|
state_data: AppLayerStateData,
|
|
|
|
|
max_tx_id: u64,
|
|
|
|
|
// This state tracks one QUIC handshake at a time. Supporting concurrent
|
|
|
|
|
// handshakes in the same UDP flow is follow-up work:
|
|
|
|
|
// https://redmine.openinfosecfoundation.org/issues/9228
|
|
|
|
|
keys: Option<QuicKeys>,
|
|
|
|
|
/// crypto fragment data already seen and reassembled to client
|
|
|
|
|
crypto_frag_tc: Vec<u8>,
|
|
|
|
|
@ -134,6 +137,8 @@ pub struct QuicState {
|
|
|
|
|
crypto_fraglen_ts: u32,
|
|
|
|
|
hello_tc: bool,
|
|
|
|
|
hello_ts: bool,
|
|
|
|
|
client_scid: Option<Vec<u8>>,
|
|
|
|
|
client_initial_dcid: Option<Vec<u8>>,
|
|
|
|
|
has_retried: bool,
|
|
|
|
|
transactions: VecDeque<QuicTransaction>,
|
|
|
|
|
}
|
|
|
|
|
@ -150,6 +155,8 @@ impl Default for QuicState {
|
|
|
|
|
crypto_fraglen_ts: 0,
|
|
|
|
|
hello_tc: false,
|
|
|
|
|
hello_ts: false,
|
|
|
|
|
client_scid: None,
|
|
|
|
|
client_initial_dcid: None,
|
|
|
|
|
has_retried: false,
|
|
|
|
|
transactions: VecDeque::new(),
|
|
|
|
|
}
|
|
|
|
|
@ -161,6 +168,29 @@ impl QuicState {
|
|
|
|
|
Self::default()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_new_client_initial(&self, header: &QuicHeader) -> bool {
|
|
|
|
|
match self.client_scid.as_deref() {
|
|
|
|
|
None => true,
|
|
|
|
|
Some(scid) if scid != header.scid => true,
|
|
|
|
|
Some([]) => self.client_initial_dcid.as_deref() != Some(header.dcid.as_slice()),
|
|
|
|
|
Some(_) => false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn reset_for_new_client_initial(&mut self, header: &QuicHeader) {
|
|
|
|
|
// Forget the previous connection state only after the new Initial has
|
|
|
|
|
// been successfully decrypted and parsed.
|
|
|
|
|
self.client_scid = Some(header.scid.clone());
|
|
|
|
|
self.client_initial_dcid = Some(header.dcid.clone());
|
|
|
|
|
self.crypto_frag_tc.clear();
|
|
|
|
|
self.crypto_frag_ts.clear();
|
|
|
|
|
self.crypto_fraglen_tc = 0;
|
|
|
|
|
self.crypto_fraglen_ts = 0;
|
|
|
|
|
self.hello_tc = false;
|
|
|
|
|
self.hello_ts = false;
|
|
|
|
|
self.has_retried = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Free a transaction by ID.
|
|
|
|
|
fn free_tx(&mut self, tx_id: u64) {
|
|
|
|
|
let tx = self
|
|
|
|
|
@ -343,7 +373,17 @@ impl QuicState {
|
|
|
|
|
while !buf.is_empty() {
|
|
|
|
|
match QuicHeader::from_bytes(buf, DEFAULT_DCID_LEN) {
|
|
|
|
|
Ok((rest, header)) => {
|
|
|
|
|
if (to_server && self.hello_ts) || (!to_server && self.hello_tc) {
|
|
|
|
|
let new_client_initial = to_server
|
|
|
|
|
&& header.ty == QuicType::Initial
|
|
|
|
|
&& self.is_new_client_initial(&header);
|
|
|
|
|
let candidate_keys = if new_client_initial {
|
|
|
|
|
quic_keys_initial(u32::from(header.version), &header.dcid)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
if ((to_server && self.hello_ts) || (!to_server && self.hello_tc))
|
|
|
|
|
&& candidate_keys.is_none()
|
|
|
|
|
{
|
|
|
|
|
// payload is encrypted, stop parsing here
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
@ -353,7 +393,10 @@ impl QuicState {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// unprotect/decrypt packet
|
|
|
|
|
if self.keys.is_none() && header.ty == QuicType::Initial {
|
|
|
|
|
if self.keys.is_none()
|
|
|
|
|
&& header.ty == QuicType::Initial
|
|
|
|
|
&& candidate_keys.is_none()
|
|
|
|
|
{
|
|
|
|
|
self.keys = quic_keys_initial(u32::from(header.version), &header.dcid);
|
|
|
|
|
} else if !to_server
|
|
|
|
|
&& self.keys.is_some()
|
|
|
|
|
@ -387,11 +430,25 @@ impl QuicState {
|
|
|
|
|
}
|
|
|
|
|
let hlen = buf.len() - rest.len();
|
|
|
|
|
let mut output;
|
|
|
|
|
if self.keys.is_some() && !framebuf.is_empty() {
|
|
|
|
|
let mut candidate_previous_keys = None;
|
|
|
|
|
if (self.keys.is_some() || candidate_keys.is_some()) && !framebuf.is_empty() {
|
|
|
|
|
output = Vec::with_capacity(framebuf.len() + 4);
|
|
|
|
|
if let Ok(dlen) =
|
|
|
|
|
let decrypt_result = if let Some(keys) = candidate_keys {
|
|
|
|
|
let previous_keys = self.keys.replace(keys);
|
|
|
|
|
let result =
|
|
|
|
|
self.decrypt(to_server, &header, framebuf, buf, hlen, &mut output);
|
|
|
|
|
if result.is_ok() {
|
|
|
|
|
candidate_previous_keys = Some(previous_keys);
|
|
|
|
|
result
|
|
|
|
|
} else {
|
|
|
|
|
self.keys = previous_keys;
|
|
|
|
|
output.clear();
|
|
|
|
|
self.decrypt(to_server, &header, framebuf, buf, hlen, &mut output)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.decrypt(to_server, &header, framebuf, buf, hlen, &mut output)
|
|
|
|
|
{
|
|
|
|
|
};
|
|
|
|
|
if let Ok(dlen) = decrypt_result {
|
|
|
|
|
output.resize(dlen, 0);
|
|
|
|
|
} else {
|
|
|
|
|
self.set_event_notx(QuicEvent::FailedDecrypt, header, to_server);
|
|
|
|
|
@ -403,28 +460,46 @@ impl QuicState {
|
|
|
|
|
|
|
|
|
|
let mut frag = Vec::new();
|
|
|
|
|
// take the current fragment and reset it in the state
|
|
|
|
|
let past_frag = if to_server {
|
|
|
|
|
let past_frag = if candidate_previous_keys.is_some() {
|
|
|
|
|
&frag
|
|
|
|
|
} else if to_server {
|
|
|
|
|
std::mem::swap(&mut self.crypto_frag_ts, &mut frag);
|
|
|
|
|
&frag
|
|
|
|
|
} else {
|
|
|
|
|
std::mem::swap(&mut self.crypto_frag_tc, &mut frag);
|
|
|
|
|
&frag
|
|
|
|
|
};
|
|
|
|
|
let past_fraglen = if to_server {
|
|
|
|
|
let past_fraglen = if candidate_previous_keys.is_some() {
|
|
|
|
|
0
|
|
|
|
|
} else if to_server {
|
|
|
|
|
self.crypto_fraglen_ts
|
|
|
|
|
} else {
|
|
|
|
|
self.crypto_fraglen_tc
|
|
|
|
|
};
|
|
|
|
|
if to_server {
|
|
|
|
|
self.crypto_fraglen_ts = 0
|
|
|
|
|
} else {
|
|
|
|
|
self.crypto_fraglen_tc = 0
|
|
|
|
|
if candidate_previous_keys.is_none() {
|
|
|
|
|
if to_server {
|
|
|
|
|
self.crypto_fraglen_ts = 0
|
|
|
|
|
} else {
|
|
|
|
|
self.crypto_fraglen_tc = 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
match QuicData::from_bytes(framebuf, past_frag, past_fraglen) {
|
|
|
|
|
Ok(data) => {
|
|
|
|
|
if candidate_previous_keys.is_some() {
|
|
|
|
|
self.reset_for_new_client_initial(&header);
|
|
|
|
|
} else if new_client_initial {
|
|
|
|
|
// The candidate keys did not decrypt the packet, but the current
|
|
|
|
|
// keys did. Keep the connection state and remember its authenticated
|
|
|
|
|
// connection IDs so it is not retried as a candidate.
|
|
|
|
|
self.client_scid = Some(header.scid.clone());
|
|
|
|
|
self.client_initial_dcid = Some(header.dcid.clone());
|
|
|
|
|
}
|
|
|
|
|
self.handle_frames(data, header, to_server, pstate);
|
|
|
|
|
}
|
|
|
|
|
Err(_e) => {
|
|
|
|
|
if let Some(previous_keys) = candidate_previous_keys {
|
|
|
|
|
self.keys = previous_keys;
|
|
|
|
|
}
|
|
|
|
|
self.set_event_notx(QuicEvent::ErrorOnData, header, to_server);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|