use crate::endian::read_u32_be;
use crate::error::{CrafterError, Result};
use crate::packet::Packet;
use crate::protocols::ip::shared::protocol_numbers::IPPROTO_IPV6;
use crate::protocols::ipsec::sa::{open, SecurityAssociation};
use crate::registry::ProtocolRegistry;
use super::header::{ESP_HEADER_LEN, ESP_NEXT_HEADER_FIELD_LEN, ESP_PAD_LENGTH_FIELD_LEN};
use super::Esp;
const IPPROTO_IPV4: u8 = 4;
pub(crate) fn decode_esp_opaque(bytes: &[u8]) -> Result<Esp> {
if bytes.len() < ESP_HEADER_LEN {
return Err(CrafterError::buffer_too_short(
"esp header",
ESP_HEADER_LEN,
bytes.len(),
));
}
let spi = read_u32_be(&bytes[0..4])?;
let sequence = read_u32_be(&bytes[4..8])?;
Ok(Esp::new()
.spi(spi)
.sequence(sequence)
.opaque(bytes[ESP_HEADER_LEN..].to_vec()))
}
#[derive(Debug, Clone)]
pub struct DecodedEsp {
pub esp: Esp,
pub plaintext: Vec<u8>,
pub next_header: u8,
#[allow(dead_code)]
pub pad_length: u8,
}
fn sa_icv_len(sa: &SecurityAssociation) -> usize {
if sa.enc.is_aead() {
sa.enc.icv_len().unwrap_or(0)
} else {
sa.integ.icv_len().unwrap_or(0)
}
}
pub(crate) fn decode_esp_with_sa(
bytes: &[u8],
sa: &SecurityAssociation,
high_sequence: u32,
) -> Result<DecodedEsp> {
let iv_len = sa.enc.iv_len();
let icv_len = sa_icv_len(sa);
let trailer_fixed = ESP_PAD_LENGTH_FIELD_LEN + ESP_NEXT_HEADER_FIELD_LEN;
let minimum = ESP_HEADER_LEN + iv_len + icv_len + trailer_fixed;
if bytes.len() < minimum {
return Err(CrafterError::buffer_too_short(
"esp datagram",
minimum,
bytes.len(),
));
}
let spi = read_u32_be(&bytes[0..4])?;
let sequence = read_u32_be(&bytes[4..8])?;
let mut aad = bytes[0..ESP_HEADER_LEN].to_vec();
if sa.esn {
aad.extend_from_slice(&high_sequence.to_be_bytes());
}
let iv = &bytes[ESP_HEADER_LEN..ESP_HEADER_LEN + iv_len];
let icv_start = bytes.len() - icv_len;
let ciphertext = &bytes[ESP_HEADER_LEN + iv_len..icv_start];
let icv = &bytes[icv_start..];
let plaintext = open(sa, iv, &aad, ciphertext, icv)?;
if plaintext.len() < trailer_fixed {
return Err(CrafterError::invalid_field_value(
"esp.pad_length",
"decrypted ESP trailer is shorter than the pad-length + next-header octets",
));
}
let next_header = plaintext[plaintext.len() - 1];
let pad_length = plaintext[plaintext.len() - 2];
let trailer_start = plaintext.len() - trailer_fixed;
let pad_len = usize::from(pad_length);
if pad_len > trailer_start {
return Err(CrafterError::invalid_field_value(
"esp.pad_length",
"pad length exceeds the available padding in the decrypted ESP trailer",
));
}
let pad = &plaintext[trailer_start - pad_len..trailer_start];
if sa.enc.block_size() > 1 {
for (offset, &octet) in pad.iter().enumerate() {
let expected = u8::try_from(offset + 1).unwrap_or(0);
if octet != expected {
return Err(CrafterError::invalid_field_value(
"esp.pad_length",
"ESP pad bytes do not follow the RFC 4303 monotonic 1,2,3,... pattern",
));
}
}
}
let inner = plaintext[..trailer_start - pad_len].to_vec();
let mut esp = Esp::new()
.spi(spi)
.sequence(sequence)
.next_header(next_header);
if sa.esn {
esp = esp.high_sequence(high_sequence);
}
Ok(DecodedEsp {
esp,
plaintext: inner,
next_header,
pad_length,
})
}
fn dispatch_esp_inner(
registry: &ProtocolRegistry,
packet: Packet,
next_header: u8,
plaintext: &[u8],
) -> Result<Packet> {
match next_header {
IPPROTO_IPV4 => {
let inner = registry.decode_ipv4(plaintext)?;
Ok(packet.concat(inner))
}
IPPROTO_IPV6 => {
let inner = registry.decode_ipv6(plaintext)?;
Ok(packet.concat(inner))
}
protocol => registry.decode_ipv4_protocol(packet, protocol, plaintext),
}
}
pub(crate) fn append_esp_packet_with_registry(
_registry: &ProtocolRegistry,
packet: Packet,
bytes: &[u8],
) -> Result<Packet> {
let esp = decode_esp_opaque(bytes)?;
Ok(packet.push(esp))
}
pub(crate) fn append_esp_packet_with_registry_sa(
registry: &ProtocolRegistry,
packet: Packet,
bytes: &[u8],
sa: Option<&SecurityAssociation>,
) -> Result<Packet> {
let Some(sa) = sa else {
return append_esp_packet_with_registry(registry, packet, bytes);
};
let DecodedEsp {
esp,
plaintext,
next_header,
..
} = decode_esp_with_sa(bytes, sa, 0)?;
let packet = packet.push(esp);
dispatch_esp_inner(registry, packet, next_header, &plaintext)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::{LayerContext, Packet};
fn fixed_esp_bytes() -> Vec<u8> {
vec![
0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x01, 0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02, 0x03, 0x04, 0xAA, 0xBB, 0xCC, 0xDD,
]
}
fn compile_esp(esp: Esp) -> Vec<u8> {
let packet = Packet::from_layer(esp);
let mut out = Vec::new();
let ctx = LayerContext::new(&packet, 0);
packet.get(0).unwrap().compile(&ctx, &mut out).unwrap();
out
}
#[test]
fn decode_opaque_exposes_spi_and_sequence() {
let bytes = fixed_esp_bytes();
let esp = decode_esp_opaque(&bytes).expect("decode opaque ESP");
assert_eq!(esp.spi_value(), Some(0x0000_2000));
assert_eq!(esp.sequence_value(), Some(0x0000_0001));
assert_eq!(esp.opaque_body(), Some(&bytes[ESP_HEADER_LEN..]));
assert!(esp.attached_security_association().is_none());
}
#[test]
fn decode_opaque_round_trips_to_original_bytes() {
let bytes = fixed_esp_bytes();
let esp = decode_esp_opaque(&bytes).expect("decode opaque ESP");
let recompiled = compile_esp(esp);
assert_eq!(recompiled, bytes);
}
#[test]
fn truncated_buffer_is_structured_error_not_panic() {
let truncated = vec![0x00, 0x00, 0x20, 0x00];
let err = decode_esp_opaque(&truncated).expect_err("must reject truncated ESP");
match err {
CrafterError::BufferTooShort {
context,
required,
available,
} => {
assert_eq!(context, "esp header");
assert_eq!(required, ESP_HEADER_LEN);
assert_eq!(available, truncated.len());
}
other => panic!("expected buffer_too_short, got {other:?}"),
}
}
#[test]
fn append_with_registry_pushes_the_opaque_esp() {
let bytes = fixed_esp_bytes();
let registry = ProtocolRegistry::with_builtin_bindings();
let packet = append_esp_packet_with_registry(®istry, Packet::new(), &bytes)
.expect("append opaque ESP");
assert_eq!(packet.len(), 1);
let esp = packet
.get(0)
.unwrap()
.as_any()
.downcast_ref::<Esp>()
.expect("pushed layer is Esp");
assert_eq!(esp.spi_value(), Some(0x0000_2000));
assert_eq!(esp.sequence_value(), Some(0x0000_0001));
assert_eq!(esp.opaque_body(), Some(&bytes[ESP_HEADER_LEN..]));
}
use crate::packet::Raw;
use crate::protocols::ipsec::sa::{
EncryptionAlgorithm, IntegrityAlgorithm, SecurityAssociation,
};
use crate::protocols::ipv4::{Ipv4, IPPROTO_ESP};
use crate::protocols::transport::common::payload_bytes_after;
use crate::protocols::Tcp;
fn compile_esp_packet(sa: SecurityAssociation, iv: Vec<u8>) -> (Vec<u8>, Vec<u8>) {
let ipv4 = Ipv4::new()
.protocol(IPPROTO_ESP)
.src("192.0.2.1".parse().unwrap())
.dst("192.0.2.2".parse().unwrap());
let spi = sa.spi;
let esp = Esp::secured(sa).spi(spi).iv(iv);
let tcp = Tcp::new().sport(1234).dport(443);
let raw = Raw::from_bytes(vec![0xDE, 0xAD, 0xBE, 0xEF, 0x10, 0x20, 0x30, 0x40]);
let packet: Packet = Packet::from_layer(ipv4) / esp / tcp / raw;
let esp_ctx = LayerContext::new(&packet, 1);
let inner_plaintext = payload_bytes_after(esp_ctx).expect("inner plaintext");
let compiled = packet.compile().expect("compile packet").into_bytes();
let ip_header_len = usize::from(compiled[0] & 0x0f) * 4;
let esp_bytes = compiled[ip_header_len..].to_vec();
assert_eq!(compiled[9], IPPROTO_ESP);
(esp_bytes, inner_plaintext)
}
fn aes_key() -> Vec<u8> {
vec![0x11u8; 16]
}
fn hmac_key() -> Vec<u8> {
vec![0x33u8; 32]
}
fn round_trip_and_tamper(sa: SecurityAssociation, iv: Vec<u8>, icv_len: usize) {
let (esp_bytes, inner) = compile_esp_packet(sa.clone(), iv);
let decoded = decode_esp_with_sa(&esp_bytes, &sa, 0).expect("decode ESP with SA");
assert_eq!(
decoded.plaintext, inner,
"recovered inner must match cleartext"
);
assert_eq!(decoded.esp.spi_value(), Some(sa.spi));
assert_eq!(decoded.next_header, 6);
assert_eq!(decoded.esp.next_header_value(), Some(6));
let iv_len = sa.enc.iv_len();
let mut bad_ct = esp_bytes.clone();
let ct_index = ESP_HEADER_LEN + iv_len;
bad_ct[ct_index] ^= 0x01;
assert!(
decode_esp_with_sa(&bad_ct, &sa, 0).is_err(),
"a tampered ciphertext bit must make decode fail closed"
);
let mut bad_icv = esp_bytes.clone();
let last = bad_icv.len() - 1;
bad_icv[last] ^= 0x01;
assert!(
decode_esp_with_sa(&bad_icv, &sa, 0).is_err(),
"a tampered ICV bit must make decode fail closed"
);
assert!(icv_len > 0 && esp_bytes.len() > iv_len + icv_len + ESP_HEADER_LEN);
}
#[test]
fn decode_with_sa_round_trips_aes_gcm() {
let sa = SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::AesGcm16, aes_key())
.salt(vec![0xAA, 0xBB, 0xCC, 0xDD]);
assert!(sa.validate().is_ok());
let iv = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
round_trip_and_tamper(sa, iv, 16);
}
#[test]
fn decode_with_sa_round_trips_aes_cbc_hmac_sha256() {
let sa = SecurityAssociation::new(0x0000_3000)
.encryption(EncryptionAlgorithm::AesCbc, aes_key())
.integrity(IntegrityAlgorithm::HmacSha2_256_128, hmac_key());
assert!(sa.validate().is_ok());
let iv: Vec<u8> = (0u8..16).collect();
round_trip_and_tamper(sa, iv, 16);
}
#[test]
fn decode_with_sa_rejects_bad_cbc_pad_pattern() {
use crate::protocols::ipsec::sa::seal;
let sa = SecurityAssociation::new(0x40)
.encryption(EncryptionAlgorithm::AesCbc, aes_key())
.integrity(IntegrityAlgorithm::HmacSha2_256_128, hmac_key());
let iv: Vec<u8> = (0u8..16).collect();
let mut plaintext = vec![0xAA, 0xBB];
plaintext.extend_from_slice(&[0xFFu8; 12]); plaintext.push(12); plaintext.push(6); assert_eq!(plaintext.len() % 16, 0);
let mut aad = Vec::new();
aad.extend_from_slice(&0x40u32.to_be_bytes());
aad.extend_from_slice(&1u32.to_be_bytes());
let sealed = seal(&sa, &iv, &aad, &plaintext).expect("seal malformed-pad plaintext");
let mut esp_bytes = Vec::new();
esp_bytes.extend_from_slice(&aad);
esp_bytes.extend_from_slice(&iv);
esp_bytes.extend_from_slice(&sealed.ciphertext);
esp_bytes.extend_from_slice(&sealed.icv);
let err = decode_esp_with_sa(&esp_bytes, &sa, 0).expect_err("bad CBC pad must error");
match err {
CrafterError::InvalidFieldValue { field, .. } => {
assert_eq!(field, "esp.pad_length");
}
other => panic!("expected esp.pad_length error, got {other:?}"),
}
}
#[test]
fn decode_with_sa_truncated_buffer_is_structured_error() {
let sa = SecurityAssociation::new(0x50)
.encryption(EncryptionAlgorithm::AesGcm16, aes_key())
.salt(vec![0xAA, 0xBB, 0xCC, 0xDD]);
let truncated = vec![0u8; ESP_HEADER_LEN + 4];
let err = decode_esp_with_sa(&truncated, &sa, 0).expect_err("must reject truncated ESP");
assert!(matches!(err, CrafterError::BufferTooShort { .. }));
}
use crate::protocols::ipv4::IPPROTO_TCP;
fn compile_esp_datagram_bytes(sa: SecurityAssociation, iv: Vec<u8>, inner: Packet) -> Vec<u8> {
let ipv4 = Ipv4::new()
.protocol(IPPROTO_ESP)
.src("192.0.2.1".parse().unwrap())
.dst("192.0.2.2".parse().unwrap());
let spi = sa.spi;
let esp = Esp::secured(sa).spi(spi).iv(iv);
let packet: Packet = (Packet::from_layer(ipv4) / esp).concat(inner);
let compiled = packet.compile().expect("compile packet").into_bytes();
let ip_header_len = usize::from(compiled[0] & 0x0f) * 4;
compiled[ip_header_len..].to_vec()
}
#[test]
fn transport_mode_dispatches_inner_tcp() {
let sa = SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::AesGcm16, aes_key())
.salt(vec![0xAA, 0xBB, 0xCC, 0xDD]);
let iv = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
let inner = Packet::from_layer(Tcp::new().sport(1234).dport(443))
/ Raw::from_bytes(vec![0xDE, 0xAD, 0xBE, 0xEF, 0x10, 0x20, 0x30, 0x40]);
let esp_bytes = compile_esp_datagram_bytes(sa.clone(), iv, inner);
let registry = ProtocolRegistry::with_builtin_bindings();
let packet =
append_esp_packet_with_registry_sa(®istry, Packet::new(), &esp_bytes, Some(&sa))
.expect("decode transport-mode ESP with SA");
let esp = packet
.get(0)
.unwrap()
.as_any()
.downcast_ref::<Esp>()
.expect("first layer is Esp");
assert_eq!(esp.spi_value(), Some(sa.spi));
assert_eq!(esp.next_header_value(), Some(IPPROTO_TCP));
let tcp = packet.layer::<Tcp>().expect("inner Tcp decoded");
assert_eq!(tcp.source_port_value(), 1234);
assert_eq!(tcp.destination_port_value(), 443);
assert_eq!(
packet.layer::<Raw>().expect("inner Raw decoded").as_bytes(),
&[0xDE, 0xAD, 0xBE, 0xEF, 0x10, 0x20, 0x30, 0x40]
);
}
#[test]
fn tunnel_mode_dispatches_inner_ipv4_tcp() {
let sa = SecurityAssociation::new(0x0000_3000)
.encryption(EncryptionAlgorithm::AesGcm16, aes_key())
.salt(vec![0xAA, 0xBB, 0xCC, 0xDD])
.tunnel();
let iv = vec![0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18];
let inner_ipv4 = Ipv4::new()
.protocol(IPPROTO_TCP)
.src("198.51.100.1".parse().unwrap())
.dst("198.51.100.2".parse().unwrap());
let inner = Packet::from_layer(inner_ipv4) / Tcp::new().sport(2222).dport(8080);
let esp_bytes = compile_esp_datagram_bytes(sa.clone(), iv, inner);
let registry = ProtocolRegistry::with_builtin_bindings();
let packet =
append_esp_packet_with_registry_sa(®istry, Packet::new(), &esp_bytes, Some(&sa))
.expect("decode tunnel-mode ESP with SA");
let esp = packet
.get(0)
.unwrap()
.as_any()
.downcast_ref::<Esp>()
.expect("first layer is Esp");
assert_eq!(esp.spi_value(), Some(sa.spi));
assert_eq!(esp.next_header_value(), Some(IPPROTO_IPV4));
let inner_ip = packet.layer::<Ipv4>().expect("inner Ipv4 decoded");
assert_eq!(inner_ip.source().to_string(), "198.51.100.1");
assert_eq!(inner_ip.destination().to_string(), "198.51.100.2");
let inner_tcp = packet.layer::<Tcp>().expect("inner Tcp decoded");
assert_eq!(inner_tcp.source_port_value(), 2222);
assert_eq!(inner_tcp.destination_port_value(), 8080);
}
#[test]
fn no_sa_uses_the_opaque_path() {
let bytes = fixed_esp_bytes();
let registry = ProtocolRegistry::with_builtin_bindings();
let packet = append_esp_packet_with_registry_sa(®istry, Packet::new(), &bytes, None)
.expect("opaque fallback");
assert_eq!(packet.len(), 1);
let esp = packet
.get(0)
.unwrap()
.as_any()
.downcast_ref::<Esp>()
.expect("pushed layer is Esp");
assert_eq!(esp.opaque_body(), Some(&bytes[ESP_HEADER_LEN..]));
}
fn compile_esn_esp_bytes(sa: SecurityAssociation, iv: Vec<u8>, high: u32) -> Vec<u8> {
let ipv4 = Ipv4::new()
.protocol(IPPROTO_ESP)
.src("192.0.2.1".parse().unwrap())
.dst("192.0.2.2".parse().unwrap());
let spi = sa.spi;
let esp = Esp::secured(sa)
.spi(spi)
.sequence(1)
.iv(iv)
.high_sequence(high);
let tcp = Tcp::new().sport(1234).dport(443);
let raw = Raw::from_bytes(vec![0xDE, 0xAD, 0xBE, 0xEF, 0x10, 0x20, 0x30, 0x40]);
let packet: Packet = Packet::from_layer(ipv4) / esp / tcp / raw;
let compiled = packet.compile().expect("compile ESN packet").into_bytes();
let ip_header_len = usize::from(compiled[0] & 0x0f) * 4;
compiled[ip_header_len..].to_vec()
}
fn esn_gcm_sa(esn: bool) -> SecurityAssociation {
SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::AesGcm16, aes_key())
.salt(vec![0xAA, 0xBB, 0xCC, 0xDD])
.extended_sequence(esn)
}
#[test]
fn esn_changes_the_icv_and_round_trips_aead() {
let iv = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
let high = 7u32;
let off = compile_esn_esp_bytes(esn_gcm_sa(false), iv.clone(), high);
let on = compile_esn_esp_bytes(esn_gcm_sa(true), iv.clone(), high);
assert_eq!(off.len(), on.len(), "the ESN high word is not on the wire");
let icv_len = 16;
assert_ne!(
&off[off.len() - icv_len..],
&on[on.len() - icv_len..],
"enabling ESN must change the ICV"
);
let sa_on = esn_gcm_sa(true);
let decoded = decode_esp_with_sa(&on, &sa_on, high).expect("ESN decode round-trips");
assert_eq!(decoded.next_header, 6); assert_eq!(decoded.esp.high_sequence_value(), Some(high));
assert_eq!(
&decoded.plaintext[decoded.plaintext.len() - 8..],
&[0xDE, 0xAD, 0xBE, 0xEF, 0x10, 0x20, 0x30, 0x40]
);
}
#[test]
fn esn_wrong_high_word_fails_integrity_aead() {
let iv = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
let on = compile_esn_esp_bytes(esn_gcm_sa(true), iv, 7);
let sa_on = esn_gcm_sa(true);
assert!(
decode_esp_with_sa(&on, &sa_on, 7).is_ok(),
"the correct high word must verify"
);
let err = decode_esp_with_sa(&on, &sa_on, 8)
.expect_err("a wrong ESN high word must fail integrity");
assert!(
matches!(err, CrafterError::InvalidFieldValue { .. }),
"wrong high word is a structured integrity error, got {err:?}"
);
}
fn esn_cbc_sa(esn: bool) -> SecurityAssociation {
SecurityAssociation::new(0x0000_3000)
.encryption(EncryptionAlgorithm::AesCbc, aes_key())
.integrity(IntegrityAlgorithm::HmacSha2_256_128, hmac_key())
.extended_sequence(esn)
}
#[test]
fn esn_changes_the_icv_and_round_trips_cbc_hmac() {
let iv: Vec<u8> = (0u8..16).collect();
let high = 9u32;
let off = compile_esn_esp_bytes(esn_cbc_sa(false), iv.clone(), high);
let on = compile_esn_esp_bytes(esn_cbc_sa(true), iv.clone(), high);
let icv_len = 16; assert_eq!(off.len(), on.len(), "the ESN high word is not on the wire");
let body = |b: &[u8]| b[ESP_HEADER_LEN + 16..b.len() - icv_len].to_vec();
assert_eq!(body(&off), body(&on), "ESN must not change the ciphertext");
assert_ne!(
&off[off.len() - icv_len..],
&on[on.len() - icv_len..],
"enabling ESN must change the ICV"
);
let sa_on = esn_cbc_sa(true);
let decoded = decode_esp_with_sa(&on, &sa_on, high).expect("ESN CBC decode round-trips");
assert_eq!(decoded.next_header, 6); assert!(
decode_esp_with_sa(&on, &sa_on, high.wrapping_add(1)).is_err(),
"a wrong ESN high word must fail the separate-integrity ICV"
);
}
}