pub mod header;
pub(crate) mod decode;
use crate::field::Field;
use crate::packet::{Layer, LayerContext};
use crate::protocols::icmp::{Icmpv4, Icmpv6};
use crate::protocols::ip::shared::protocol_numbers::{
IPPROTO_ICMP, IPPROTO_ICMPV6, IPPROTO_IPV6, IPPROTO_NO_NEXT, IPPROTO_TCP, IPPROTO_UDP,
};
use crate::protocols::ipsec::sa::{
seal, EncryptionAlgorithm, IntegrityAlgorithm, IpsecMode, SecurityAssociation,
};
use crate::protocols::ipv4::Ipv4;
use crate::protocols::ipv6::Ipv6;
use crate::protocols::transport::common::{
hex_bytes, impl_layer_div, impl_layer_object, payload_bytes_after,
};
use crate::protocols::{Tcp, Udp};
use crate::{CrafterError, Result};
use self::header::{ESP_HEADER_LEN, ESP_HIGH_SEQUENCE_LEN};
const IPPROTO_IPV4: u8 = 4;
#[derive(Debug, Clone)]
pub struct Esp {
spi: Field<u32>,
sequence: Field<u32>,
next_header: Field<u8>,
pad: Field<Vec<u8>>,
iv: Field<Vec<u8>>,
icv: Field<Vec<u8>>,
sa: Option<SecurityAssociation>,
opaque: Option<Vec<u8>>,
high_sequence: Field<u32>,
}
const DEFAULT_ESP_SPI: u32 = 0x0000_0001;
const DEFAULT_ESP_SEQUENCE: u32 = 1;
const DEFAULT_ESP_HIGH_SEQUENCE: u32 = 0;
impl Esp {
pub fn new() -> Self {
Self {
spi: Field::defaulted(DEFAULT_ESP_SPI),
sequence: Field::defaulted(DEFAULT_ESP_SEQUENCE),
next_header: Field::unset(),
pad: Field::unset(),
iv: Field::unset(),
icv: Field::unset(),
sa: None,
opaque: None,
high_sequence: Field::defaulted(DEFAULT_ESP_HIGH_SEQUENCE),
}
}
pub fn secured(sa: SecurityAssociation) -> Self {
Self::new().security_association(sa)
}
pub fn spi(mut self, spi: u32) -> Self {
self.spi.set_user(spi);
self
}
pub fn sequence(mut self, sequence: u32) -> Self {
self.sequence.set_user(sequence);
self
}
pub fn seq(self, sequence: u32) -> Self {
self.sequence(sequence)
}
pub fn high_sequence(mut self, high_sequence: u32) -> Self {
self.high_sequence.set_user(high_sequence);
self
}
pub fn security_association(mut self, sa: SecurityAssociation) -> Self {
self.sa = Some(sa);
self
}
pub fn next_header(mut self, next_header: u8) -> Self {
self.next_header.set_user(next_header);
self
}
pub fn pad(mut self, pad: impl Into<Vec<u8>>) -> Self {
self.pad.set_user(pad.into());
self
}
pub fn iv(mut self, iv: impl Into<Vec<u8>>) -> Self {
self.iv.set_user(iv.into());
self
}
pub fn icv(mut self, icv: impl Into<Vec<u8>>) -> Self {
self.icv.set_user(icv.into());
self
}
pub fn opaque(mut self, opaque: impl Into<Vec<u8>>) -> Self {
self.opaque = Some(opaque.into());
self
}
pub fn spi_value(&self) -> Option<u32> {
self.spi.value().copied()
}
pub fn sequence_value(&self) -> Option<u32> {
self.sequence.value().copied()
}
pub fn high_sequence_value(&self) -> Option<u32> {
self.high_sequence.value().copied()
}
pub fn next_header_value(&self) -> Option<u8> {
self.next_header.value().copied()
}
pub fn pad_value(&self) -> Option<&[u8]> {
self.pad.value().map(Vec::as_slice)
}
pub fn iv_value(&self) -> Option<&[u8]> {
self.iv.value().map(Vec::as_slice)
}
pub fn icv_value(&self) -> Option<&[u8]> {
self.icv.value().map(Vec::as_slice)
}
pub fn attached_security_association(&self) -> Option<&SecurityAssociation> {
self.sa.as_ref()
}
pub fn opaque_body(&self) -> Option<&[u8]> {
self.opaque.as_deref()
}
}
fn default_iv(len: usize) -> Vec<u8> {
vec![0u8; len]
}
fn layer_esp_next_header(layer: &dyn Layer, _mode: IpsecMode) -> Option<u8> {
let any = layer.as_any();
if any.is::<Ipv4>() {
return Some(IPPROTO_IPV4);
}
if any.is::<Ipv6>() {
return Some(IPPROTO_IPV6);
}
if any.is::<Tcp>() {
return Some(IPPROTO_TCP);
}
if any.is::<Udp>() {
return Some(IPPROTO_UDP);
}
if any.is::<Icmpv4>() {
return Some(IPPROTO_ICMP);
}
if any.is::<Icmpv6>() {
return Some(IPPROTO_ICMPV6);
}
None
}
fn esp_encryption_label(alg: EncryptionAlgorithm) -> String {
match alg {
EncryptionAlgorithm::Null => "NULL".to_string(),
EncryptionAlgorithm::AesCbc => "AES_CBC".to_string(),
EncryptionAlgorithm::AesCtr => "AES_CTR".to_string(),
EncryptionAlgorithm::AesCcm8 => "AES_CCM_8".to_string(),
EncryptionAlgorithm::AesGcm16 => "AES_GCM_16".to_string(),
EncryptionAlgorithm::ChaCha20Poly1305 => "CHACHA20_POLY1305".to_string(),
EncryptionAlgorithm::Unknown(id) => format!("UNKNOWN({id})"),
}
}
fn esp_integrity_label(alg: IntegrityAlgorithm) -> String {
match alg {
IntegrityAlgorithm::None => "NONE".to_string(),
IntegrityAlgorithm::HmacSha1_96 => "HMAC_SHA1_96".to_string(),
IntegrityAlgorithm::AesXcbc96 => "AES_XCBC_96".to_string(),
IntegrityAlgorithm::AesGmac => "AES_128_GMAC".to_string(),
IntegrityAlgorithm::HmacSha2_256_128 => "HMAC_SHA2_256_128".to_string(),
IntegrityAlgorithm::HmacSha2_384_192 => "HMAC_SHA2_384_192".to_string(),
IntegrityAlgorithm::HmacSha2_512_256 => "HMAC_SHA2_512_256".to_string(),
IntegrityAlgorithm::Unknown(id) => format!("UNKNOWN({id})"),
}
}
struct EspSealInputs {
plaintext: Vec<u8>,
aad: Vec<u8>,
iv: Vec<u8>,
}
impl Esp {
fn effective_next_header(&self, ctx: &LayerContext<'_>, mode: IpsecMode) -> u8 {
if let Some(next_header) = self.next_header.value().copied() {
return next_header;
}
ctx.next()
.and_then(|layer| layer_esp_next_header(layer, mode))
.unwrap_or(IPPROTO_NO_NEXT)
}
fn effective_pad(&self, payload_len: usize, block_size: usize) -> Vec<u8> {
if let Some(pad) = self.pad.value() {
return pad.clone();
}
let alignment = block_size.max(4);
let trailer_fixed = header::ESP_PAD_LENGTH_FIELD_LEN + header::ESP_NEXT_HEADER_FIELD_LEN;
let unaligned = payload_len + trailer_fixed;
let remainder = unaligned % alignment;
let pad_len = if remainder == 0 {
0
} else {
alignment - remainder
};
(1..=pad_len as u8).collect()
}
fn esp_seal_inputs(
&self,
ctx: &LayerContext<'_>,
sa: &SecurityAssociation,
) -> Result<EspSealInputs> {
let spi = self.spi.value().copied().unwrap_or(DEFAULT_ESP_SPI);
let sequence = self
.sequence
.value()
.copied()
.unwrap_or(DEFAULT_ESP_SEQUENCE);
let payload = payload_bytes_after(*ctx)?;
let block_size = sa.enc.block_size();
let pad = self.effective_pad(payload.len(), block_size);
let pad_len = u8::try_from(pad.len()).map_err(|_| {
CrafterError::invalid_field_value("esp.pad", "ESP pad exceeds 255 octets")
})?;
let next_header = self.effective_next_header(ctx, sa.mode);
let mut plaintext = Vec::with_capacity(
payload.len()
+ pad.len()
+ header::ESP_PAD_LENGTH_FIELD_LEN
+ header::ESP_NEXT_HEADER_FIELD_LEN,
);
plaintext.extend_from_slice(&payload);
plaintext.extend_from_slice(&pad);
plaintext.push(pad_len);
plaintext.push(next_header);
let mut aad = Vec::with_capacity(ESP_HEADER_LEN + ESP_HIGH_SEQUENCE_LEN);
aad.extend_from_slice(&spi.to_be_bytes());
aad.extend_from_slice(&sequence.to_be_bytes());
if sa.esn {
let high = self
.high_sequence
.value()
.copied()
.unwrap_or(DEFAULT_ESP_HIGH_SEQUENCE);
aad.extend_from_slice(&high.to_be_bytes());
}
let iv = match self.iv.value() {
Some(iv) => iv.clone(),
None => default_iv(sa.enc.iv_len()),
};
Ok(EspSealInputs { plaintext, aad, iv })
}
fn compile_with_cipher(&self, ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
let sa = self.sa.as_ref().ok_or_else(|| {
CrafterError::invalid_field_value(
"esp.compile",
"ESP compile_with_cipher requires a SecurityAssociation",
)
})?;
let EspSealInputs { plaintext, aad, iv } = self.esp_seal_inputs(ctx, sa)?;
let sealed = seal(sa, &iv, &aad, &plaintext)?;
let icv = match self.icv.value() {
Some(icv) => icv.clone(),
None => sealed.icv,
};
out.reserve(ESP_HEADER_LEN + iv.len() + sealed.ciphertext.len() + icv.len());
out.extend_from_slice(&aad[..ESP_HEADER_LEN]);
out.extend_from_slice(&iv);
out.extend_from_slice(&sealed.ciphertext);
out.extend_from_slice(&icv);
Ok(())
}
fn compile_with_aead(&self, ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
let sa = self.sa.as_ref().ok_or_else(|| {
CrafterError::invalid_field_value(
"esp.compile",
"ESP compile_with_aead requires a SecurityAssociation",
)
})?;
let EspSealInputs { plaintext, aad, iv } = self.esp_seal_inputs(ctx, sa)?;
let sealed = seal(sa, &iv, &aad, &plaintext)?;
let icv = match self.icv.value() {
Some(icv) => icv.clone(),
None => sealed.icv,
};
out.reserve(ESP_HEADER_LEN + iv.len() + sealed.ciphertext.len() + icv.len());
out.extend_from_slice(&aad[..ESP_HEADER_LEN]);
out.extend_from_slice(&iv);
out.extend_from_slice(&sealed.ciphertext);
out.extend_from_slice(&icv);
Ok(())
}
fn compile_opaque(&self, out: &mut Vec<u8>) -> Result<()> {
let spi = self.spi.value().copied().unwrap_or(DEFAULT_ESP_SPI);
let sequence = self
.sequence
.value()
.copied()
.unwrap_or(DEFAULT_ESP_SEQUENCE);
let opaque = self.opaque.as_deref().unwrap_or_default();
out.reserve(ESP_HEADER_LEN + opaque.len());
out.extend_from_slice(&spi.to_be_bytes());
out.extend_from_slice(&sequence.to_be_bytes());
out.extend_from_slice(opaque);
Ok(())
}
fn compile_keyless(&self, ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
let spi = self.spi.value().copied().unwrap_or(DEFAULT_ESP_SPI);
let sequence = self
.sequence
.value()
.copied()
.unwrap_or(DEFAULT_ESP_SEQUENCE);
let body = payload_bytes_after(*ctx)?;
out.reserve(ESP_HEADER_LEN + body.len());
out.extend_from_slice(&spi.to_be_bytes());
out.extend_from_slice(&sequence.to_be_bytes());
out.extend_from_slice(&body);
Ok(())
}
fn display_spi(&self) -> u32 {
self.spi.value().copied().unwrap_or(DEFAULT_ESP_SPI)
}
fn display_sequence(&self) -> u32 {
self.sequence
.value()
.copied()
.unwrap_or(DEFAULT_ESP_SEQUENCE)
}
fn display_enc_label(&self) -> String {
match self.sa.as_ref() {
Some(sa) => esp_encryption_label(sa.enc),
None => "opaque".to_string(),
}
}
fn display_icv_len(&self) -> Option<usize> {
if let Some(icv) = self.icv.value() {
return Some(icv.len());
}
let sa = self.sa.as_ref()?;
if sa.enc.is_aead() {
sa.enc.icv_len()
} else {
sa.integ.icv_len()
}
}
}
impl Layer for Esp {
fn name(&self) -> &'static str {
"Esp"
}
fn summary(&self) -> String {
let mode = self
.sa
.as_ref()
.map(|sa| sa.mode.label())
.unwrap_or("transport");
format!(
"Esp(spi=0x{:08x}, seq={}, enc={}, mode={})",
self.display_spi(),
self.display_sequence(),
self.display_enc_label(),
mode,
)
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
let mut fields = vec![
("spi", format!("0x{:08x}", self.display_spi())),
("sequence", self.display_sequence().to_string()),
(
"next_header",
self.next_header
.value()
.map(|nh| nh.to_string())
.unwrap_or_else(|| "auto".to_string()),
),
("encryption", self.display_enc_label()),
(
"integrity",
self.sa
.as_ref()
.map(|sa| esp_integrity_label(sa.integ))
.unwrap_or_else(|| "none".to_string()),
),
(
"icv_len",
self.display_icv_len()
.map(|len| len.to_string())
.unwrap_or_else(|| "auto".to_string()),
),
];
if let Some(opaque) = self.opaque.as_deref() {
fields.push(("body_len", opaque.len().to_string()));
fields.push(("opaque", hex_bytes(opaque)));
}
fields
}
fn encoded_len(&self) -> usize {
ESP_HEADER_LEN + self.opaque.as_deref().map_or(0, <[u8]>::len)
}
fn encoded_len_with_context(&self, ctx: &LayerContext<'_>) -> usize {
if self.opaque.is_some() {
return self.encoded_len();
}
let mut out = Vec::new();
match self.compile(ctx, &mut out) {
Ok(()) => out.len(),
Err(_) => self.encoded_len(),
}
}
fn consumes_following(&self) -> bool {
true
}
fn compile(&self, ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
match self.sa.as_ref() {
None if self.opaque.is_some() => self.compile_opaque(out),
Some(sa) if sa.enc.is_aead() => self.compile_with_aead(ctx, out),
Some(_) => self.compile_with_cipher(ctx, out),
None => self.compile_keyless(ctx, out),
}
}
impl_layer_object!(Esp);
}
impl_layer_div!(Esp);
impl Default for Esp {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field::FieldState;
use crate::protocols::ipsec::sa::{EncryptionAlgorithm, SecurityAssociation};
#[test]
fn new_defaults_spi_and_sequence() {
let esp = Esp::new();
assert_eq!(esp.spi.state(), FieldState::Defaulted);
assert_eq!(esp.spi.value().copied(), Some(DEFAULT_ESP_SPI));
assert_eq!(esp.sequence.state(), FieldState::Defaulted);
assert_eq!(esp.sequence.value().copied(), Some(DEFAULT_ESP_SEQUENCE));
assert_eq!(esp.high_sequence.state(), FieldState::Defaulted);
assert_eq!(
esp.high_sequence.value().copied(),
Some(DEFAULT_ESP_HIGH_SEQUENCE)
);
assert_eq!(esp.next_header.state(), FieldState::Unset);
assert_eq!(esp.pad.state(), FieldState::Unset);
assert_eq!(esp.iv.state(), FieldState::Unset);
assert_eq!(esp.icv.state(), FieldState::Unset);
assert!(esp.sa.is_none());
assert!(esp.opaque.is_none());
}
#[test]
fn default_matches_new() {
let from_default = Esp::default();
assert_eq!(from_default.spi.state(), FieldState::Defaulted);
assert_eq!(from_default.spi_value(), Some(DEFAULT_ESP_SPI));
assert_eq!(from_default.sequence_value(), Some(DEFAULT_ESP_SEQUENCE));
assert!(from_default.attached_security_association().is_none());
}
#[test]
fn setters_mark_fields_as_user() {
let esp = Esp::new()
.spi(0x0000_2000)
.sequence(42)
.high_sequence(0x0000_0001)
.next_header(6)
.pad(vec![0x01, 0x02])
.iv(vec![0xAAu8; 8])
.icv(vec![0xBBu8; 16]);
assert_eq!(esp.spi.state(), FieldState::User);
assert_eq!(esp.spi_value(), Some(0x0000_2000));
assert_eq!(esp.sequence.state(), FieldState::User);
assert_eq!(esp.sequence_value(), Some(42));
assert_eq!(esp.high_sequence.state(), FieldState::User);
assert_eq!(esp.high_sequence_value(), Some(0x0000_0001));
assert_eq!(esp.next_header.state(), FieldState::User);
assert_eq!(esp.next_header_value(), Some(6));
assert_eq!(esp.pad.state(), FieldState::User);
assert_eq!(esp.pad_value(), Some(&[0x01, 0x02][..]));
assert_eq!(esp.iv.state(), FieldState::User);
assert_eq!(esp.iv_value(), Some(&[0xAAu8; 8][..]));
assert_eq!(esp.icv.state(), FieldState::User);
assert_eq!(esp.icv_value(), Some(&[0xBBu8; 16][..]));
}
#[test]
fn seq_alias_matches_sequence() {
let esp = Esp::new().seq(7);
assert_eq!(esp.sequence.state(), FieldState::User);
assert_eq!(esp.sequence_value(), Some(7));
}
#[test]
fn secured_attaches_the_sa() {
let sa = SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::AesGcm16, vec![0u8; 16])
.salt(vec![0u8; 4]);
let esp = Esp::secured(sa.clone());
let attached = esp.attached_security_association().expect("SA attached");
assert_eq!(attached, &sa);
assert_eq!(esp.spi.state(), FieldState::Defaulted);
assert_eq!(esp.sequence.state(), FieldState::Defaulted);
}
#[test]
fn security_association_setter_attaches_the_sa() {
let sa = SecurityAssociation::new(0x10);
let esp = Esp::new().security_association(sa.clone());
assert_eq!(esp.attached_security_association(), Some(&sa));
}
#[test]
fn opaque_carries_body_bytes() {
let esp = Esp::new().opaque(vec![0xDE, 0xAD, 0xBE, 0xEF]);
assert_eq!(esp.opaque_body(), Some(&[0xDE, 0xAD, 0xBE, 0xEF][..]));
}
use crate::packet::Packet;
use crate::packet::Raw;
use crate::protocols::ipsec::sa::{seal, IntegrityAlgorithm};
use crate::protocols::ipv4::Ipv4;
fn aes_key() -> Vec<u8> {
vec![0x11u8; 16]
}
fn hmac_key() -> Vec<u8> {
vec![0x33u8; 32]
}
fn payload() -> Vec<u8> {
vec![0xDE, 0xAD, 0xBE, 0xEF]
}
fn expected_esp_bytes(
sa: &SecurityAssociation,
spi: u32,
sequence: u32,
iv: &[u8],
next_header: u8,
upper: &[u8],
) -> Vec<u8> {
let block_size = sa.enc.block_size();
let alignment = block_size.max(4);
let unaligned =
upper.len() + header::ESP_PAD_LENGTH_FIELD_LEN + header::ESP_NEXT_HEADER_FIELD_LEN;
let remainder = unaligned % alignment;
let pad_len = if remainder == 0 {
0
} else {
alignment - remainder
};
let pad: Vec<u8> = (1..=pad_len as u8).collect();
let mut plaintext = Vec::new();
plaintext.extend_from_slice(upper);
plaintext.extend_from_slice(&pad);
plaintext.push(pad_len as u8);
plaintext.push(next_header);
let mut aad = Vec::new();
aad.extend_from_slice(&spi.to_be_bytes());
aad.extend_from_slice(&sequence.to_be_bytes());
let sealed = seal(sa, iv, &aad, &plaintext).unwrap();
let mut out = Vec::new();
out.extend_from_slice(&aad);
out.extend_from_slice(iv);
out.extend_from_slice(&sealed.ciphertext);
out.extend_from_slice(&sealed.icv);
out
}
fn compile_esp_over_ipv4_inner(esp: Esp, inner: Packet) -> (Vec<u8>, u8) {
let ipv4 = Ipv4::new()
.protocol(crate::protocols::ipv4::IPPROTO_ESP)
.src("192.0.2.1".parse().unwrap())
.dst("192.0.2.2".parse().unwrap());
let packet: Packet = (Packet::from_layer(ipv4) / esp).concat(inner);
let ip_bytes = {
let mut out = Vec::new();
let ctx = LayerContext::new(&packet, 0);
packet.get(0).unwrap().compile(&ctx, &mut out).unwrap();
out
};
let ip_protocol = ip_bytes[9];
let mut esp_bytes = Vec::new();
let ctx = LayerContext::new(&packet, 1);
packet
.get(1)
.unwrap()
.compile(&ctx, &mut esp_bytes)
.unwrap();
(esp_bytes, ip_protocol)
}
fn compile_esp_over_ipv4(esp: Esp, upper: &[u8]) -> (Vec<u8>, u8) {
compile_esp_over_ipv4_inner(esp, Packet::from_layer(Raw::from_bytes(upper)))
}
#[test]
fn compile_aes_cbc_hmac_sha256_golden() {
let sa = SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::AesCbc, aes_key())
.integrity(IntegrityAlgorithm::HmacSha2_256_128, hmac_key());
assert!(sa.validate().is_ok());
let iv: Vec<u8> = (0u8..16).collect();
let esp = Esp::secured(sa.clone())
.spi(0x0000_2000)
.sequence(1)
.next_header(IPPROTO_TCP)
.iv(iv.clone());
let (esp_bytes, ip_protocol) = compile_esp_over_ipv4(esp, &payload());
assert_eq!(ip_protocol, crate::protocols::ipv4::IPPROTO_ESP);
let expected = expected_esp_bytes(&sa, 0x0000_2000, 1, &iv, IPPROTO_TCP, &payload());
assert_eq!(esp_bytes, expected);
assert_eq!(&esp_bytes[0..4], &0x0000_2000u32.to_be_bytes());
assert_eq!(&esp_bytes[4..8], &1u32.to_be_bytes());
assert_eq!(&esp_bytes[8..24], &iv[..]);
assert_eq!(esp_bytes.len(), ESP_HEADER_LEN + 16 + 16 + 16);
}
#[test]
fn compile_aes_ctr_hmac_sha256_golden() {
let sa = SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::AesCtr, aes_key())
.salt(vec![0x00, 0x00, 0x00, 0x30]) .integrity(IntegrityAlgorithm::HmacSha2_256_128, hmac_key());
assert!(sa.validate().is_ok());
let iv: Vec<u8> = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
let esp = Esp::secured(sa.clone())
.spi(0x0000_2000)
.sequence(1)
.next_header(IPPROTO_TCP)
.iv(iv.clone());
let (esp_bytes, ip_protocol) = compile_esp_over_ipv4(esp, &payload());
assert_eq!(ip_protocol, crate::protocols::ipv4::IPPROTO_ESP);
let expected = expected_esp_bytes(&sa, 0x0000_2000, 1, &iv, IPPROTO_TCP, &payload());
assert_eq!(esp_bytes, expected);
assert_eq!(&esp_bytes[0..4], &0x0000_2000u32.to_be_bytes());
assert_eq!(&esp_bytes[4..8], &1u32.to_be_bytes());
assert_eq!(&esp_bytes[8..16], &iv[..]);
assert_eq!(esp_bytes.len(), ESP_HEADER_LEN + 8 + 8 + 16);
}
#[test]
fn compile_round_trips_through_seal_open() {
let sa = SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::AesCbc, aes_key())
.integrity(IntegrityAlgorithm::HmacSha2_256_128, hmac_key());
let iv: Vec<u8> = (0u8..16).collect();
let esp = Esp::secured(sa.clone())
.spi(0x0000_2000)
.sequence(1)
.next_header(IPPROTO_TCP)
.iv(iv.clone());
let (esp_bytes, _) = compile_esp_over_ipv4(esp, &payload());
let mut aad = Vec::new();
aad.extend_from_slice(&0x0000_2000u32.to_be_bytes());
aad.extend_from_slice(&1u32.to_be_bytes());
let ct = &esp_bytes[ESP_HEADER_LEN + 16..esp_bytes.len() - 16];
let icv = &esp_bytes[esp_bytes.len() - 16..];
let opened = crate::protocols::ipsec::sa::open(&sa, &iv, &aad, ct, icv).unwrap();
assert_eq!(&opened[..4], &payload()[..]);
assert_eq!(*opened.last().unwrap(), IPPROTO_TCP);
}
#[test]
fn compile_pad_override_is_emitted_verbatim() {
let sa = SecurityAssociation::new(0x10)
.encryption(EncryptionAlgorithm::AesCtr, aes_key())
.salt(vec![0x00, 0x00, 0x00, 0x30])
.integrity(IntegrityAlgorithm::HmacSha2_256_128, hmac_key());
let iv: Vec<u8> = vec![0u8; 8];
let esp = Esp::secured(sa.clone())
.spi(0x10)
.sequence(1)
.next_header(IPPROTO_TCP)
.iv(iv.clone())
.pad(vec![0xAA, 0xBB, 0xCC]); let (esp_bytes, _) = compile_esp_over_ipv4(esp, &payload());
assert_eq!(esp_bytes.len(), ESP_HEADER_LEN + 8 + 9 + 16);
let mut aad = Vec::new();
aad.extend_from_slice(&0x10u32.to_be_bytes());
aad.extend_from_slice(&1u32.to_be_bytes());
let ct = &esp_bytes[ESP_HEADER_LEN + 8..esp_bytes.len() - 16];
let icv = &esp_bytes[esp_bytes.len() - 16..];
let opened = crate::protocols::ipsec::sa::open(&sa, &iv, &aad, ct, icv).unwrap();
assert_eq!(
opened,
vec![0xDE, 0xAD, 0xBE, 0xEF, 0xAA, 0xBB, 0xCC, 3, IPPROTO_TCP]
);
}
#[test]
fn compile_default_iv_is_zero_filled() {
let sa = SecurityAssociation::new(0x10)
.encryption(EncryptionAlgorithm::AesCbc, aes_key())
.integrity(IntegrityAlgorithm::HmacSha2_256_128, hmac_key());
let esp = Esp::secured(sa)
.spi(0x10)
.sequence(1)
.next_header(IPPROTO_TCP);
let (esp_bytes, _) = compile_esp_over_ipv4(esp, &payload());
assert_eq!(&esp_bytes[ESP_HEADER_LEN..ESP_HEADER_LEN + 16], &[0u8; 16]);
}
#[test]
fn compile_derives_next_header_from_inner_udp() {
let sa = SecurityAssociation::new(0x10)
.encryption(EncryptionAlgorithm::AesCbc, aes_key())
.integrity(IntegrityAlgorithm::HmacSha2_256_128, hmac_key());
let iv: Vec<u8> = (0u8..16).collect();
let esp = Esp::secured(sa.clone())
.spi(0x10)
.sequence(1)
.iv(iv.clone());
let inner: Packet =
Packet::from_layer(crate::protocols::Udp::new()) / Raw::from_bytes([0u8; 0]);
let (esp_bytes, _) = compile_esp_over_ipv4_inner(esp, inner);
let mut aad = Vec::new();
aad.extend_from_slice(&0x10u32.to_be_bytes());
aad.extend_from_slice(&1u32.to_be_bytes());
let ct = &esp_bytes[ESP_HEADER_LEN + 16..esp_bytes.len() - 16];
let icv = &esp_bytes[esp_bytes.len() - 16..];
let opened = crate::protocols::ipsec::sa::open(&sa, &iv, &aad, ct, icv).unwrap();
assert_eq!(*opened.last().unwrap(), IPPROTO_UDP);
}
fn chacha_key() -> Vec<u8> {
vec![0x22u8; 32]
}
fn aead_iv() -> Vec<u8> {
vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
}
#[test]
fn compile_aes_gcm_16_golden() {
let sa = SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::AesGcm16, aes_key())
.salt(vec![0xAA, 0xBB, 0xCC, 0xDD]);
assert!(sa.validate().is_ok());
let iv = aead_iv();
let esp = Esp::secured(sa.clone())
.spi(0x0000_2000)
.sequence(1)
.next_header(IPPROTO_TCP)
.iv(iv.clone());
let (esp_bytes, ip_protocol) = compile_esp_over_ipv4(esp, &payload());
assert_eq!(ip_protocol, crate::protocols::ipv4::IPPROTO_ESP);
let expected = expected_esp_bytes(&sa, 0x0000_2000, 1, &iv, IPPROTO_TCP, &payload());
assert_eq!(esp_bytes, expected);
assert_eq!(&esp_bytes[0..4], &0x0000_2000u32.to_be_bytes());
assert_eq!(&esp_bytes[4..8], &1u32.to_be_bytes());
assert_eq!(&esp_bytes[ESP_HEADER_LEN..ESP_HEADER_LEN + 8], &iv[..]);
assert_eq!(esp_bytes.len(), ESP_HEADER_LEN + 8 + 8 + 16);
}
#[test]
fn compile_chacha20_poly1305_golden() {
let sa = SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::ChaCha20Poly1305, chacha_key())
.salt(vec![0xA0, 0xA1, 0xA2, 0xA3]);
assert!(sa.validate().is_ok());
let iv = aead_iv();
let esp = Esp::secured(sa.clone())
.spi(0x0000_2000)
.sequence(1)
.next_header(IPPROTO_TCP)
.iv(iv.clone());
let (esp_bytes, ip_protocol) = compile_esp_over_ipv4(esp, &payload());
assert_eq!(ip_protocol, crate::protocols::ipv4::IPPROTO_ESP);
let expected = expected_esp_bytes(&sa, 0x0000_2000, 1, &iv, IPPROTO_TCP, &payload());
assert_eq!(esp_bytes, expected);
assert_eq!(&esp_bytes[0..4], &0x0000_2000u32.to_be_bytes());
assert_eq!(&esp_bytes[4..8], &1u32.to_be_bytes());
assert_eq!(&esp_bytes[ESP_HEADER_LEN..ESP_HEADER_LEN + 8], &iv[..]);
assert_eq!(esp_bytes.len(), ESP_HEADER_LEN + 8 + 8 + 16);
}
#[test]
fn compile_aes_ccm_8_golden() {
let sa = SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::AesCcm8, aes_key())
.salt(vec![0xA0, 0xA1, 0xA2]);
assert!(sa.validate().is_ok());
let iv = aead_iv();
let esp = Esp::secured(sa.clone())
.spi(0x0000_2000)
.sequence(1)
.next_header(IPPROTO_TCP)
.iv(iv.clone());
let (esp_bytes, ip_protocol) = compile_esp_over_ipv4(esp, &payload());
assert_eq!(ip_protocol, crate::protocols::ipv4::IPPROTO_ESP);
let expected = expected_esp_bytes(&sa, 0x0000_2000, 1, &iv, IPPROTO_TCP, &payload());
assert_eq!(esp_bytes, expected);
assert_eq!(&esp_bytes[0..4], &0x0000_2000u32.to_be_bytes());
assert_eq!(&esp_bytes[4..8], &1u32.to_be_bytes());
assert_eq!(&esp_bytes[ESP_HEADER_LEN..ESP_HEADER_LEN + 8], &iv[..]);
assert_eq!(esp_bytes.len(), ESP_HEADER_LEN + 8 + 8 + 8);
}
#[test]
fn compile_aead_round_trips_through_seal_open() {
let sa = SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::AesGcm16, aes_key())
.salt(vec![0xAA, 0xBB, 0xCC, 0xDD]);
let iv = aead_iv();
let esp = Esp::secured(sa.clone())
.spi(0x0000_2000)
.sequence(1)
.next_header(IPPROTO_TCP)
.iv(iv.clone());
let (esp_bytes, _) = compile_esp_over_ipv4(esp, &payload());
let mut aad = Vec::new();
aad.extend_from_slice(&0x0000_2000u32.to_be_bytes());
aad.extend_from_slice(&1u32.to_be_bytes());
let ct = &esp_bytes[ESP_HEADER_LEN + 8..esp_bytes.len() - 16];
let icv = &esp_bytes[esp_bytes.len() - 16..];
let opened = crate::protocols::ipsec::sa::open(&sa, &iv, &aad, ct, icv).unwrap();
assert_eq!(&opened[..4], &payload()[..]);
assert_eq!(*opened.last().unwrap(), IPPROTO_TCP);
}
#[test]
fn compile_aead_default_iv_is_zero_filled() {
let sa = SecurityAssociation::new(0x10)
.encryption(EncryptionAlgorithm::AesGcm16, aes_key())
.salt(vec![0u8; 4]);
let esp = Esp::secured(sa)
.spi(0x10)
.sequence(1)
.next_header(IPPROTO_TCP);
let (esp_bytes, _) = compile_esp_over_ipv4(esp, &payload());
assert_eq!(&esp_bytes[ESP_HEADER_LEN..ESP_HEADER_LEN + 8], &[0u8; 8]);
}
#[test]
fn compile_aead_icv_override_is_emitted_verbatim() {
let sa = SecurityAssociation::new(0x10)
.encryption(EncryptionAlgorithm::AesGcm16, aes_key())
.salt(vec![0u8; 4]);
let bad_icv = vec![0xFFu8; 16];
let esp = Esp::secured(sa)
.spi(0x10)
.sequence(1)
.next_header(IPPROTO_TCP)
.iv(aead_iv())
.icv(bad_icv.clone());
let (esp_bytes, _) = compile_esp_over_ipv4(esp, &payload());
assert_eq!(&esp_bytes[esp_bytes.len() - 16..], &bad_icv[..]);
}
#[test]
fn compile_opaque_round_trips_bytes_verbatim() {
let opaque: Vec<u8> = (0x10u8..0x30).collect();
let esp = Esp::new()
.spi(0xCAFE_F00D)
.sequence(0x0000_0007)
.iv(vec![0xFFu8; 8])
.icv(vec![0xEEu8; 16])
.opaque(opaque.clone());
let (esp_bytes, ip_protocol) = compile_esp_over_ipv4(esp, &payload());
assert_eq!(ip_protocol, crate::protocols::ipv4::IPPROTO_ESP);
let mut expected = Vec::new();
expected.extend_from_slice(&0xCAFE_F00Du32.to_be_bytes());
expected.extend_from_slice(&0x0000_0007u32.to_be_bytes());
expected.extend_from_slice(&opaque);
assert_eq!(esp_bytes, expected);
assert_eq!(esp_bytes.len(), ESP_HEADER_LEN + opaque.len());
}
#[test]
fn compile_null_encryption_builds_real_trailer_and_icv() {
let sa = SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::Null, Vec::new())
.integrity(IntegrityAlgorithm::HmacSha2_256_128, hmac_key());
assert!(sa.validate().is_ok());
let esp = Esp::secured(sa.clone())
.spi(0x0000_2000)
.sequence(1)
.next_header(IPPROTO_TCP);
let (esp_bytes, ip_protocol) = compile_esp_over_ipv4(esp, &payload());
assert_eq!(ip_protocol, crate::protocols::ipv4::IPPROTO_ESP);
let expected = expected_esp_bytes(&sa, 0x0000_2000, 1, &[], IPPROTO_TCP, &payload());
assert_eq!(esp_bytes, expected);
assert_eq!(esp_bytes.len(), ESP_HEADER_LEN + 8 + 16);
assert_eq!(&esp_bytes[0..4], &0x0000_2000u32.to_be_bytes());
assert_eq!(&esp_bytes[4..8], &1u32.to_be_bytes());
let body = &esp_bytes[ESP_HEADER_LEN..esp_bytes.len() - 16];
assert_eq!(&body[..4], &payload()[..]);
assert_eq!(body, &[0xDE, 0xAD, 0xBE, 0xEF, 1, 2, 2, IPPROTO_TCP]);
let mut aad = Vec::new();
aad.extend_from_slice(&0x0000_2000u32.to_be_bytes());
aad.extend_from_slice(&1u32.to_be_bytes());
let icv = &esp_bytes[esp_bytes.len() - 16..];
let opened = crate::protocols::ipsec::sa::open(&sa, &[], &aad, body, icv).unwrap();
assert_eq!(&opened[..4], &payload()[..]);
assert_eq!(*opened.last().unwrap(), IPPROTO_TCP);
}
#[test]
fn compile_null_none_emits_trailer_without_iv_or_icv() {
let sa = SecurityAssociation::new(0x10).encryption(EncryptionAlgorithm::Null, Vec::new());
assert!(sa.validate().is_ok());
let esp = Esp::secured(sa)
.spi(0x10)
.sequence(1)
.next_header(IPPROTO_TCP);
let (esp_bytes, _) = compile_esp_over_ipv4(esp, &payload());
assert_eq!(esp_bytes.len(), ESP_HEADER_LEN + 8);
let body = &esp_bytes[ESP_HEADER_LEN..];
assert_eq!(body, &[0xDE, 0xAD, 0xBE, 0xEF, 1, 2, 2, IPPROTO_TCP]);
}
#[test]
fn compile_keyless_emits_header_plus_unencrypted_body() {
let esp = Esp::new().spi(0x0000_0001).sequence(1);
let (esp_bytes, ip_protocol) = compile_esp_over_ipv4(esp, &payload());
assert_eq!(ip_protocol, crate::protocols::ipv4::IPPROTO_ESP);
let mut expected = Vec::new();
expected.extend_from_slice(&0x0000_0001u32.to_be_bytes());
expected.extend_from_slice(&1u32.to_be_bytes());
expected.extend_from_slice(&payload()); assert_eq!(esp_bytes, expected);
assert_eq!(esp_bytes.len(), ESP_HEADER_LEN + payload().len());
}
#[test]
fn compile_keyless_defaults_spi_and_sequence() {
let esp = Esp::new();
let (esp_bytes, _) = compile_esp_over_ipv4(esp, &payload());
assert_eq!(&esp_bytes[0..4], &DEFAULT_ESP_SPI.to_be_bytes());
assert_eq!(&esp_bytes[4..8], &DEFAULT_ESP_SEQUENCE.to_be_bytes());
assert_eq!(&esp_bytes[ESP_HEADER_LEN..], &payload()[..]);
}
fn whole_packet_sa() -> SecurityAssociation {
SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::AesGcm16, aes_key())
.salt(vec![0xAA, 0xBB, 0xCC, 0xDD])
}
#[test]
fn whole_packet_compile_consumes_following_layers() {
let sa = whole_packet_sa();
let iv = aead_iv();
let esp = Esp::secured(sa.clone())
.spi(0x0000_2000)
.sequence(1)
.iv(iv.clone());
let ipv4 = Ipv4::new()
.protocol(crate::protocols::ipv4::IPPROTO_ESP)
.src("192.0.2.1".parse().unwrap())
.dst("192.0.2.2".parse().unwrap());
let tcp = crate::protocols::Tcp::new();
let packet: Packet = Packet::from_layer(ipv4) / esp / tcp / Raw::from_bytes([0xDEu8; 4]);
assert!(packet.get(1).unwrap().consumes_following());
let whole = packet.compile().unwrap();
let ip_bytes = {
let mut out = Vec::new();
let ctx = LayerContext::new(&packet, 0);
packet.get(0).unwrap().compile(&ctx, &mut out).unwrap();
out
};
let esp_bytes = {
let mut out = Vec::new();
let ctx = LayerContext::new(&packet, 1);
packet.get(1).unwrap().compile(&ctx, &mut out).unwrap();
out
};
assert_eq!(whole.len(), ip_bytes.len() + esp_bytes.len());
assert_eq!(&whole.as_bytes()[..ip_bytes.len()], &ip_bytes[..]);
assert_eq!(&whole.as_bytes()[ip_bytes.len()..], &esp_bytes[..]);
assert_eq!(ip_bytes[9], crate::protocols::ipv4::IPPROTO_ESP);
}
#[test]
fn whole_packet_encoded_len_excludes_consumed_tail() {
let sa = whole_packet_sa();
let esp = Esp::secured(sa).spi(0x0000_2000).sequence(1).iv(aead_iv());
let ipv4 = Ipv4::new()
.protocol(crate::protocols::ipv4::IPPROTO_ESP)
.src("192.0.2.1".parse().unwrap())
.dst("192.0.2.2".parse().unwrap());
let tcp = crate::protocols::Tcp::new();
let packet: Packet = Packet::from_layer(ipv4) / esp / tcp / Raw::from_bytes([0xDEu8; 4]);
assert_eq!(packet.encoded_len(), packet.compile().unwrap().len());
}
#[test]
fn summary_reports_spi_and_algorithm_without_key_bytes() {
let sa = SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::AesGcm16, vec![0xDEu8; 16])
.salt(vec![0xBEu8; 4]);
let esp = Esp::secured(sa).spi(0x0000_2000).sequence(7);
let summary = esp.summary();
assert_eq!(
summary,
"Esp(spi=0x00002000, seq=7, enc=AES_GCM_16, mode=transport)"
);
assert!(!summary.to_lowercase().contains("dede"));
assert!(!summary.to_lowercase().contains("bebe"));
}
#[test]
fn summary_reports_opaque_when_no_sa() {
let esp = Esp::new().spi(0x10).sequence(3);
assert_eq!(
esp.summary(),
"Esp(spi=0x00000010, seq=3, enc=opaque, mode=transport)"
);
}
#[test]
fn show_contains_spi_and_algorithm_without_key_bytes() {
let sa = SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::AesCbc, vec![0xDEu8; 16])
.integrity(IntegrityAlgorithm::HmacSha2_256_128, vec![0xBEu8; 32]);
let iv = (0u8..16).collect::<Vec<u8>>();
let esp = Esp::secured(sa)
.spi(0x0000_2000)
.sequence(1)
.next_header(IPPROTO_TCP)
.iv(iv);
let ipv4 = Ipv4::new()
.protocol(crate::protocols::ipv4::IPPROTO_ESP)
.src("192.0.2.1".parse().unwrap())
.dst("192.0.2.2".parse().unwrap());
let packet: Packet = Packet::from_layer(ipv4) / esp / Raw::from_bytes(b"hello!!!");
let show = packet.show();
assert!(show.contains("spi: 0x00002000"));
assert!(show.contains("AES_CBC"));
assert!(show.contains("HMAC_SHA2_256_128"));
assert!(!show.to_lowercase().contains("dede"));
assert!(!show.to_lowercase().contains("bebe"));
}
}