Skip to content

Commit

Permalink
Ensure that retransmissions check original request.
Browse files Browse the repository at this point in the history
Addressed a few cases where a previous request could be retransmitted
when this shouldn't be done (e.g. garbage or unencrypted requests).

Fixed an issue where the fragments count used the last response instead
of the last request.
  • Loading branch information
zlogic committed Sep 25, 2024
1 parent f83a33f commit 9194d07
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 28 deletions.
11 changes: 9 additions & 2 deletions src/ikev2/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1046,13 +1046,20 @@ impl Encryption for EncryptionAesGcm256 {
}
}

pub fn hash_sha1(data: &[u8]) -> [u8; 20] {
let mut result = [0u8; 20];
pub fn hash_sha1(data: &[u8]) -> [u8; digest::SHA1_OUTPUT_LEN] {
let mut result = [0u8; digest::SHA1_OUTPUT_LEN];
let hash = digest::digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, data);
result.copy_from_slice(hash.as_ref());
result
}

pub fn hash_sha256(data: &[u8]) -> [u8; digest::SHA256_OUTPUT_LEN] {
let mut result = [0u8; digest::SHA256_OUTPUT_LEN];
let hash = digest::digest(&digest::SHA256, data);
result.copy_from_slice(hash.as_ref());
result
}

pub struct UnsupportedTransform {}

impl fmt::Display for UnsupportedTransform {
Expand Down
81 changes: 55 additions & 26 deletions src/ikev2/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,9 @@ pub struct IKEv2Session {
remote_message_id: u32,
local_message_id: u32,
fragment_reassembly: Option<FragmentReassembly>,
last_request_hash: Option<[u8; 32]>,
last_response: Vec<Vec<u8>>,
last_request: Vec<Vec<u8>>,
last_sent_request: Vec<Vec<u8>>,
sent_request: Option<RequestContext>,
request_retransmit: usize,
pending_actions: Vec<IKEv2PendingAction>,
Expand Down Expand Up @@ -170,8 +171,9 @@ impl IKEv2Session {
remote_message_id: 0,
local_message_id: 0,
fragment_reassembly: None,
last_request_hash: None,
last_response: vec![],
last_request: vec![],
last_sent_request: vec![],
sent_request: None,
request_retransmit: 0,
pending_actions: vec![],
Expand Down Expand Up @@ -228,8 +230,26 @@ impl IKEv2Session {
}
Ordering::Equal => {
// Retransmit last response if available.
if !self.last_response.is_empty() {
return Ok(Self::should_retransmit(request));
if let Some(last_request_hash) = self.last_request_hash.as_ref() {
if !Self::should_retransmit(request) {
return Ok(false);
}
let request_hash = crypto::hash_sha256(request.raw_data());
if &request_hash == last_request_hash {
// Update remote address if client changed IP or switched to another NAT port.
self.remote_addr = remote_addr;
self.local_addr = local_addr;
return Ok(true);
} else {
// Retransmitted request is modified - could be an attacker
// attempting to request a copy of the response.
return Err(
"Retransmitted request hash mismatch, not sending response".into()
);
}
} else if self.remote_message_id > 0 {
// Last response expired, and this is not a "start from 0" session.
return Ok(false);
}
}
Ordering::Greater => {
Expand All @@ -250,10 +270,13 @@ impl IKEv2Session {
)?;

let decrypted_data = if exchange_type == message::ExchangeType::IKE_SA_INIT {
self.last_request_hash = Some(crypto::hash_sha256(request.raw_data()));
None
} else {
// TODO: return INVALID_SYNTAX notification?
let decrypted_data = self.decrypt_request(request)?;
if Self::should_retransmit(request) {
self.last_request_hash = Some(crypto::hash_sha256(request.raw_data()));
}
if decrypted_data.is_none() {
// Not all fragments have been received.
return Ok(false);
Expand Down Expand Up @@ -402,21 +425,26 @@ impl IKEv2Session {
}

fn should_retransmit(request: &message::InputMessage) -> bool {
if let Ok(exchange_type) = request.read_exchange_type() {
if exchange_type == message::ExchangeType::IKE_SA_INIT {
return request.read_message_id() == 0;
}
} else {
return false;
};
// RFC 7383 2.6.1 states that only message 1 should trigger a retransmission.
// Otherwise, the first payload should be an encrypted message
// (avoid any retransmissions if request is not encrypted).
match request.iter_payloads().next() {
Some(Ok(payload)) => {
if payload.payload_type()
== message::PayloadType::ENCRYPTED_AND_AUTHENTICATED_FRAGMENT
{
payload
.encrypted_data()
.map_or(true, |payload| payload.fragment_number() == 1)
} else {
true
}
}
Some(Err(_)) => true,
None => true,
Some(Ok(payload)) => match payload.payload_type() {
message::PayloadType::ENCRYPTED_AND_AUTHENTICATED_FRAGMENT => payload
.encrypted_data()
.map_or(true, |payload| payload.fragment_number() == 1),
message::PayloadType::ENCRYPTED_AND_AUTHENTICATED => true,
_ => false,
},
Some(Err(_)) => false,
None => false,
}
}

Expand Down Expand Up @@ -1545,8 +1573,9 @@ impl IKEv2Session {
remote_message_id: 0,
local_message_id: 0,
fragment_reassembly: None,
last_request_hash: None,
last_response: vec![],
last_request: vec![],
last_sent_request: vec![],
sent_request: None,
request_retransmit: 0,
pending_actions: vec![],
Expand Down Expand Up @@ -1586,7 +1615,7 @@ impl IKEv2Session {
exchange_type: message::ExchangeType,
command_generator: impl FnOnce(&mut message::MessageWriter) -> Result<(), SessionError>,
) -> Result<u32, SessionError> {
if self.sent_request.is_some() || !self.last_request.is_empty() {
if self.sent_request.is_some() || !self.last_sent_request.is_empty() {
return Err("Already processing another command".into());
}
let mut request_bytes = [0u8; MAX_DATAGRAM_SIZE];
Expand All @@ -1598,10 +1627,10 @@ impl IKEv2Session {
true,
self.local_message_id,
)?;
self.last_request = vec![];
self.last_sent_request = vec![];
command_generator(&mut ikev2_request)?;

self.last_request = self.encrypt_message(&mut ikev2_request)?;
self.last_sent_request = self.encrypt_message(&mut ikev2_request)?;

self.request_retransmit = 0;
Ok(self.local_message_id)
Expand Down Expand Up @@ -1718,11 +1747,11 @@ impl IKEv2Session {
if message_id != self.local_message_id {
return Ok(());
}
if !self.last_request.is_empty() {
if !self.last_sent_request.is_empty() {
self.request_retransmit += 1;
}
let total_fragments = self.last_response.len();
for (i, fragment) in self.last_request.iter().enumerate() {
let total_fragments = self.last_sent_request.len();
for (i, fragment) in self.last_sent_request.iter().enumerate() {
Self::log_fragment_contents(
"request",
self.remote_message_id,
Expand Down Expand Up @@ -1774,7 +1803,7 @@ impl IKEv2Session {

// Remove last request to stop retransmissions.
self.local_message_id += 1;
self.last_request = vec![];
self.last_sent_request = vec![];
self.sent_request = None;
self.request_retransmit = 0;

Expand Down

0 comments on commit 9194d07

Please sign in to comment.