use std::net::{Ipv4Addr, Ipv6Addr};
use crafter::prelude::*;
const DOC_SRC: Ipv4Addr = Ipv4Addr::new(192, 0, 2, 10);
const DOC_DST: Ipv4Addr = Ipv4Addr::new(198, 51, 100, 20);
fn aes_key() -> Vec<u8> {
vec![0x11u8; 16]
}
fn hmac_key() -> Vec<u8> {
vec![0x33u8; 32]
}
fn doc_security_association() -> SecurityAssociation {
SecurityAssociation::new(0x0000_2000)
.encryption(EncryptionAlgorithm::AesCbc, aes_key())
.integrity(IntegrityAlgorithm::HmacSha2_256_128, hmac_key())
.transport()
.extended_sequence(false)
}
fn esp_packet() -> Packet {
let sa = doc_security_association();
assert!(sa.validate().is_ok(), "documentation SA validates");
Ipv4::new().src(DOC_SRC).dst(DOC_DST).protocol(IPPROTO_ESP)
/ Esp::secured(sa).spi(0x0000_2000).sequence(1)
/ Tcp::new().sport(40000).dport(443)
/ Raw::from("esp-public-api")
}
#[test]
fn prelude_exposes_ipsec_surface() {
let _mode_transport = IpsecMode::Transport;
let _mode_tunnel = IpsecMode::Tunnel;
let _enc = EncryptionAlgorithm::AesGcm16;
let _integ = IntegrityAlgorithm::HmacSha2_256_128;
let _sa: SecurityAssociation = SecurityAssociation::new(0x10);
let _esp: Esp = Esp::new();
assert_eq!(ESP_HEADER_LEN, 8);
assert_eq!(ESP_HIGH_SEQUENCE_LEN, 4);
assert_eq!(ESP_PAD_LENGTH_FIELD_LEN, 1);
assert_eq!(ESP_NEXT_HEADER_FIELD_LEN, 1);
assert_eq!(ESP_MAX_PAD_LEN, 255);
}
#[test]
fn prelude_only_esp_build_and_compile() -> Result<()> {
let packet = esp_packet();
let compiled = packet.compile()?;
let bytes = compiled.as_bytes();
assert_eq!(bytes[9], IPPROTO_ESP);
assert_eq!(&bytes[20..24], &0x0000_2000u32.to_be_bytes());
assert_eq!(&bytes[24..28], &1u32.to_be_bytes());
let summary = packet.summary();
assert!(summary.contains("Esp"), "summary names the ESP layer");
Ok(())
}
#[test]
fn prelude_only_esp_decode_round_trips_opaque() -> Result<()> {
let packet = esp_packet();
let bytes = packet.compile()?;
let wire = bytes.as_bytes().to_vec();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, &wire)?;
let esp = decoded.layer::<Esp>().expect("decoded ESP layer present");
assert_eq!(esp.spi_value(), Some(0x0000_2000));
assert_eq!(esp.sequence_value(), Some(1));
assert!(
esp.opaque_body().is_some(),
"no-SA decode keeps the encrypted body opaque"
);
let recompiled = decoded.compile()?;
assert_eq!(recompiled.as_bytes(), wire.as_slice());
Ok(())
}
fn ah_security_association(tunnel: bool) -> SecurityAssociation {
let sa = SecurityAssociation::new(0x0000_3000)
.integrity(IntegrityAlgorithm::HmacSha2_256_128, hmac_key())
.extended_sequence(false);
if tunnel {
sa.tunnel()
} else {
sa.transport()
}
}
#[test]
fn prelude_exposes_ah_surface() {
let _ah: Ah = Ah::new();
assert_eq!(AH_FIXED_LEN, 12);
assert_eq!(AH_NEXT_HEADER_LEN, 1);
assert_eq!(AH_PAYLOAD_LEN_FIELD_LEN, 1);
assert_eq!(AH_RESERVED_LEN, 2);
assert_eq!(AH_SPI_LEN, 4);
assert_eq!(AH_SEQUENCE_LEN, 4);
assert_eq!(AH_HIGH_SEQUENCE_LEN, 4);
assert_eq!(AH_LENGTH_UNIT, 4);
assert_eq!(AH_PAYLOAD_LEN_OFFSET, 2);
}
#[test]
fn prelude_only_ah_transport_build_and_decode() -> Result<()> {
let sa = ah_security_association(false);
assert!(sa.validate().is_ok(), "documentation AH SA validates");
assert_eq!(sa.mode, IpsecMode::Transport);
let packet: Packet = Ipv4::new().src(DOC_SRC).dst(DOC_DST).protocol(IPPROTO_AH)
/ Ah::secured(sa).spi(0x0000_3000).sequence(1)
/ Tcp::new().sport(40000).dport(443)
/ Raw::from("ah-public-api");
let compiled = packet.compile()?;
let wire = compiled.as_bytes().to_vec();
assert_eq!(wire[9], IPPROTO_AH);
assert_eq!(wire[20], IPPROTO_TCP);
assert_eq!(&wire[24..28], &0x0000_3000u32.to_be_bytes());
assert_eq!(&wire[28..32], &1u32.to_be_bytes());
let summary = packet.summary();
assert!(summary.contains("Ah"), "summary names the AH layer");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, &wire)?;
let ah = decoded.layer::<Ah>().expect("decoded AH layer present");
assert_eq!(ah.spi_value(), Some(0x0000_3000));
assert_eq!(ah.sequence_value(), Some(1));
assert_eq!(ah.next_header_value(), Some(IPPROTO_TCP));
assert_eq!(ah.icv_value().map(<[u8]>::len), Some(16));
assert!(decoded.layer::<Tcp>().is_some(), "inner TCP decodes");
let recompiled = decoded.compile()?;
assert_eq!(recompiled.as_bytes(), wire.as_slice());
Ok(())
}
#[test]
fn prelude_only_ah_tunnel_build_and_decode() -> Result<()> {
let sa = ah_security_association(true);
assert!(
sa.validate().is_ok(),
"documentation AH tunnel SA validates"
);
assert_eq!(sa.mode, IpsecMode::Tunnel);
let inner_src = Ipv4Addr::new(192, 0, 2, 30);
let inner_dst = Ipv4Addr::new(198, 51, 100, 40);
let packet: Packet = Ipv4::new().src(DOC_SRC).dst(DOC_DST).protocol(IPPROTO_AH)
/ Ah::secured(sa).spi(0x0000_3000).sequence(1)
/ Ipv4::new()
.src(inner_src)
.dst(inner_dst)
.protocol(IPPROTO_TCP)
/ Tcp::new().sport(50000).dport(443)
/ Raw::from("ah-tunnel-public-api");
let compiled = packet.compile()?;
let wire = compiled.as_bytes().to_vec();
assert_eq!(wire[9], IPPROTO_AH);
assert_eq!(
wire[20], 4,
"AH Next Header is IPv4-in-IPv4 for tunnel mode"
);
assert_eq!(&wire[24..28], &0x0000_3000u32.to_be_bytes());
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, &wire)?;
let ah = decoded.layer::<Ah>().expect("decoded AH layer present");
assert_eq!(ah.spi_value(), Some(0x0000_3000));
assert_eq!(ah.next_header_value(), Some(4));
assert_eq!(ah.icv_value().map(<[u8]>::len), Some(16));
let ipv4_layers = decoded.layers::<Ipv4>().count();
assert_eq!(ipv4_layers, 2, "outer and inner IPv4 both decode");
assert!(decoded.layer::<Tcp>().is_some(), "inner TCP decodes");
let recompiled = decoded.compile()?;
assert_eq!(recompiled.as_bytes(), wire.as_slice());
Ok(())
}
const IKE_UDP_PORT: u16 = 500;
#[test]
fn prelude_exposes_ikev2_surface() {
let _header: IkeHeader = IkeHeader::new().exchange(IKE_SA_INIT).initiator();
let _sa: IkeSaPayload = IkeSaPayload::new().with_proposal(
Proposal::new(1, PROTOCOL_ID_IKE).with_transform(
Transform::new(TRANSFORM_TYPE_ENCR, 20)
.with_attribute(TransformAttribute::key_length(128)),
),
);
let _ke: IkeKePayload = IkeKePayload::new(DH_GROUP_MODP_2048, vec![0u8; 32]);
let _ni: IkeNoncePayload = IkeNoncePayload::new(vec![0u8; 16]);
let _notify: IkeNotifyPayload =
IkeNotifyPayload::new(NOTIFY_PROTOCOL_NONE, NotifyType::Cookie, Vec::<u8>::new());
let _delete: IkeDeletePayload = IkeDeletePayload::new(DELETE_PROTOCOL_ESP);
let _id: IkeIdPayload = IkeIdPayload::initiator_ipv4(Ipv4Addr::new(192, 0, 2, 10));
let _id_role = IdRole::Initiator;
let _auth: IkeAuthPayload = IkeAuthPayload::new(AuthMethod::SharedKeyMic, vec![0u8; 8]);
let _vendor: IkeVendorIdPayload = IkeVendorIdPayload::new(vec![0u8; 4]);
let _eap: IkeEapPayload = IkeEapPayload::new(vec![0u8; 4]);
let _pt: PayloadType = PayloadType::SecurityAssociation;
let _ts_role = TsRole::Initiator;
let _id_type = IdType::Ipv4Addr;
let _cfg = CfgType::Request;
let _cert = CertEncoding::X509Signature;
assert_eq!(IKE_HEADER_LEN, 28);
assert_eq!(IKE_VERSION_2, 0x20);
assert_eq!(IKE_SA_INIT, 34);
assert_eq!(GENERIC_PAYLOAD_HEADER_LEN, 4);
assert_eq!(PAYLOAD_SA, 33);
assert_eq!(PAYLOAD_KE, 34);
assert_eq!(PAYLOAD_NONCE, 40);
assert_eq!(PAYLOAD_TYPE_NONE, 0);
}
fn ike_sa_init_packet() -> Packet {
let proposal = Proposal::new(1, PROTOCOL_ID_IKE)
.with_transform(
Transform::new(TRANSFORM_TYPE_ENCR, 20)
.with_attribute(TransformAttribute::key_length(128)),
)
.with_transform(Transform::new(TRANSFORM_TYPE_DH, DH_GROUP_MODP_2048));
let sa = IkeSaPayload::new().with_proposal(proposal);
let ke = IkeKePayload::new(DH_GROUP_MODP_2048, vec![0xAB; 32]);
let ni = IkeNoncePayload::new(vec![0x5A; 16]);
let header = IkeHeader::new()
.initiator_spi(0x0102_0304_0506_0708)
.exchange(IKE_SA_INIT)
.initiator();
Ipv4::new().src(DOC_SRC).dst(DOC_DST).protocol(IPPROTO_UDP)
/ Udp::new().sport(IKE_UDP_PORT).dport(IKE_UDP_PORT)
/ header
/ sa
/ ke
/ ni
}
#[test]
fn prelude_only_ike_sa_init_build_decode_round_trips() -> Result<()> {
let packet = ike_sa_init_packet();
let compiled = packet.compile()?;
let wire = compiled.as_bytes().to_vec();
assert_eq!(wire[9], IPPROTO_UDP);
assert_eq!(&wire[20..22], &IKE_UDP_PORT.to_be_bytes());
assert_eq!(&wire[22..24], &IKE_UDP_PORT.to_be_bytes());
assert_eq!(&wire[28..36], &0x0102_0304_0506_0708u64.to_be_bytes());
let summary = packet.summary();
assert!(
summary.contains("IkeHeader"),
"summary names the IKE header"
);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, &wire)?;
let header = decoded
.layer::<IkeHeader>()
.expect("decoded IKE header present");
assert_eq!(header.exchange_type_value(), Some(IKE_SA_INIT));
assert_eq!(header.initiator_spi_value(), Some(0x0102_0304_0506_0708));
assert_eq!(header.next_payload_value(), Some(PAYLOAD_SA));
assert!(
decoded.layer::<IkeSaPayload>().is_some(),
"SA payload decodes"
);
assert!(
decoded.layer::<IkeKePayload>().is_some(),
"KE payload decodes"
);
assert!(
decoded.layer::<IkeNoncePayload>().is_some(),
"Nonce payload decodes"
);
let ke = decoded.layer::<IkeKePayload>().unwrap();
assert_eq!(ke.dh_group_num(), DH_GROUP_MODP_2048);
let recompiled = decoded.compile()?;
assert_eq!(recompiled.as_bytes(), wire.as_slice());
Ok(())
}
fn gcm_key() -> Vec<u8> {
vec![0x24u8; 16]
}
fn gcm_salt() -> Vec<u8> {
vec![0xA1, 0xB2, 0xC3, 0xD4]
}
fn gcm_iv() -> Vec<u8> {
vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
}
const DOC6_SRC: Ipv6Addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
const DOC6_DST: Ipv6Addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2);
fn gcm_transport_sa(spi: u32) -> SecurityAssociation {
SecurityAssociation::new(spi)
.encryption(EncryptionAlgorithm::AesGcm16, gcm_key())
.salt(gcm_salt())
.transport()
.extended_sequence(false)
}
fn assert_no_key_material(rendered: &str) {
let lowered = rendered.to_lowercase();
for needle in ["24242424", "33333333", "11111111", "a1b2c3d4"] {
assert!(
!lowered.contains(needle),
"rendered output leaked key/salt material ({needle}): {rendered}"
);
}
}
#[test]
fn esp_transport_aead_ipv4_decode_with_sa_recovers_inner() -> Result<()> {
let spi = 0x0000_2100;
let sa = gcm_transport_sa(spi);
assert!(sa.validate().is_ok());
let packet: Packet = Ipv4::new().src(DOC_SRC).dst(DOC_DST).protocol(IPPROTO_ESP)
/ Esp::secured(sa.clone()).spi(spi).sequence(1).iv(gcm_iv())
/ Tcp::new().sport(40001).dport(443)
/ Raw::from("esp-gcm-v4");
let wire = packet.compile()?.as_bytes().to_vec();
assert_eq!(wire[9], IPPROTO_ESP);
let opaque = Packet::decode_from_l3(NetworkLayer::Ipv4, &wire)?;
assert!(opaque.layer::<Esp>().unwrap().opaque_body().is_some());
assert!(
opaque.layer::<Tcp>().is_none(),
"no SA: TCP stays encrypted"
);
let registry = ProtocolRegistry::new().with_security_association(sa);
let decoded = Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, &wire)?;
let esp = decoded.layer::<Esp>().expect("typed ESP recovered");
assert_eq!(esp.spi_value(), Some(spi));
let tcp = decoded.layer::<Tcp>().expect("inner TCP decrypted");
assert_eq!(tcp.source_port_value(), 40001);
assert_eq!(tcp.destination_port_value(), 443);
assert_eq!(
decoded
.layer::<Raw>()
.expect("inner Raw decrypted")
.as_bytes(),
b"esp-gcm-v4"
);
assert_no_key_material(&decoded.summary());
assert_no_key_material(&decoded.show());
Ok(())
}
#[test]
fn esp_transport_aead_ipv6_decode_with_sa_recovers_inner() -> Result<()> {
let spi = 0x0000_2600;
let sa = gcm_transport_sa(spi);
let packet: Packet = Ipv6::new()
.src(DOC6_SRC)
.dst(DOC6_DST)
.nh(IPPROTO_IPV6_ESP)
.hop_limit(64)
/ Esp::secured(sa.clone()).spi(spi).sequence(1).iv(gcm_iv())
/ Tcp::new().sport(50001).dport(8443)
/ Raw::from("esp-gcm-v6");
let wire = packet.compile()?.as_bytes().to_vec();
let registry = ProtocolRegistry::new().with_security_association(sa);
let decoded = Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv6, &wire)?;
assert_eq!(decoded.layer::<Esp>().unwrap().spi_value(), Some(spi));
let tcp = decoded
.layer::<Tcp>()
.expect("inner TCP decrypted over IPv6");
assert_eq!(tcp.source_port_value(), 50001);
assert_eq!(
decoded
.layer::<Raw>()
.expect("inner Raw decrypted")
.as_bytes(),
b"esp-gcm-v6"
);
assert_no_key_material(&decoded.show());
Ok(())
}
#[test]
fn esp_tunnel_aead_decode_with_sa_recovers_inner_ip() -> Result<()> {
let spi = 0x0000_2700;
let sa = SecurityAssociation::new(spi)
.encryption(EncryptionAlgorithm::AesGcm16, gcm_key())
.salt(gcm_salt())
.tunnel();
let inner_src = Ipv4Addr::new(192, 0, 2, 71);
let inner_dst = Ipv4Addr::new(198, 51, 100, 72);
let packet: Packet = Ipv4::new().src(DOC_SRC).dst(DOC_DST).protocol(IPPROTO_ESP)
/ Esp::secured(sa.clone()).spi(spi).sequence(1).iv(gcm_iv())
/ Ipv4::new()
.src(inner_src)
.dst(inner_dst)
.protocol(IPPROTO_TCP)
/ Tcp::new().sport(33000).dport(22)
/ Raw::from("esp-tunnel");
let wire = packet.compile()?.as_bytes().to_vec();
let registry = ProtocolRegistry::new().with_security_association(sa);
let decoded = Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, &wire)?;
assert_eq!(decoded.layers::<Ipv4>().count(), 2, "outer + inner IPv4");
let inner_ip = decoded
.layers::<Ipv4>()
.nth(1)
.expect("inner IPv4 recovered");
assert_eq!(inner_ip.source(), inner_src);
assert_eq!(inner_ip.destination(), inner_dst);
let tcp = decoded.layer::<Tcp>().expect("inner TCP decrypted");
assert_eq!(tcp.destination_port_value(), 22);
assert_no_key_material(&decoded.show());
Ok(())
}
#[test]
fn esp_cbc_hmac_transport_decode_with_sa_recovers_inner() -> Result<()> {
let spi = 0x0000_2800;
let cbc_key = vec![0x11u8; 16];
let int_key = vec![0x33u8; 32];
let sa = SecurityAssociation::new(spi)
.encryption(EncryptionAlgorithm::AesCbc, cbc_key)
.integrity(IntegrityAlgorithm::HmacSha2_256_128, int_key)
.transport();
assert!(sa.validate().is_ok());
let cbc_iv: Vec<u8> = (0u8..16).collect();
let packet: Packet = Ipv4::new().src(DOC_SRC).dst(DOC_DST).protocol(IPPROTO_ESP)
/ Esp::secured(sa.clone()).spi(spi).sequence(1).iv(cbc_iv)
/ Tcp::new().sport(41000).dport(443)
/ Raw::from("esp-cbc-hmac");
let wire = packet.compile()?.as_bytes().to_vec();
let registry = ProtocolRegistry::new().with_security_association(sa);
let decoded = Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, &wire)?;
let tcp = decoded.layer::<Tcp>().expect("inner TCP decrypted (CBC)");
assert_eq!(tcp.source_port_value(), 41000);
assert_eq!(
decoded
.layer::<Raw>()
.expect("inner Raw decrypted")
.as_bytes(),
b"esp-cbc-hmac"
);
assert_no_key_material(&decoded.show());
Ok(())
}
#[test]
fn esp_opaque_no_sa_round_trips() -> Result<()> {
let spi = 0x0000_2900;
let sa = gcm_transport_sa(spi);
let packet: Packet = Ipv4::new().src(DOC_SRC).dst(DOC_DST).protocol(IPPROTO_ESP)
/ Esp::secured(sa).spi(spi).sequence(1).iv(gcm_iv())
/ Tcp::new().sport(42000).dport(443)
/ Raw::from("esp-opaque");
let wire = packet.compile()?.as_bytes().to_vec();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, &wire)?;
let esp = decoded.layer::<Esp>().expect("typed ESP header");
assert_eq!(esp.spi_value(), Some(spi));
assert!(esp.opaque_body().is_some(), "no-SA body stays opaque");
assert!(decoded.layer::<Tcp>().is_none(), "TCP stays sealed");
assert_eq!(decoded.compile()?.as_bytes(), wire.as_slice());
Ok(())
}
fn ah_hmac_sa(spi: u32, tunnel: bool) -> SecurityAssociation {
let sa = SecurityAssociation::new(spi)
.integrity(IntegrityAlgorithm::HmacSha2_256_128, hmac_key())
.extended_sequence(false);
if tunnel {
sa.tunnel()
} else {
sa.transport()
}
}
#[test]
fn ah_transport_ipv4_decode_with_sa_verifies() -> Result<()> {
let spi = 0x0000_3100;
let sa = ah_hmac_sa(spi, false);
let packet: Packet = Ipv4::new()
.src(DOC_SRC)
.dst(DOC_DST)
.protocol(IPPROTO_AH)
.ttl(64)
/ Ah::secured(sa.clone()).spi(spi).sequence(1)
/ Tcp::new().sport(43000).dport(443)
/ Raw::from("ah-v4");
let wire = packet.compile()?.as_bytes().to_vec();
assert_eq!(wire[9], IPPROTO_AH);
let opaque = Packet::decode_from_l3(NetworkLayer::Ipv4, &wire)?;
assert_eq!(opaque.layer::<Ah>().unwrap().verification_status(), None);
let registry = ProtocolRegistry::new().with_security_association(sa);
let decoded = Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, &wire)?;
let ah = decoded.layer::<Ah>().expect("typed AH recovered");
assert_eq!(ah.spi_value(), Some(spi));
assert_eq!(
ah.verification_status(),
Some(true),
"matching SA verifies the AH ICV"
);
assert!(decoded.layer::<Tcp>().is_some(), "inner TCP in the clear");
assert_no_key_material(&decoded.show());
Ok(())
}
#[test]
fn ah_tunnel_ipv4_decode_with_sa_verifies() -> Result<()> {
let spi = 0x0000_3200;
let sa = ah_hmac_sa(spi, true);
let inner_src = Ipv4Addr::new(192, 0, 2, 81);
let inner_dst = Ipv4Addr::new(198, 51, 100, 82);
let packet: Packet = Ipv4::new()
.src(DOC_SRC)
.dst(DOC_DST)
.protocol(IPPROTO_AH)
.ttl(64)
/ Ah::secured(sa.clone()).spi(spi).sequence(1)
/ Ipv4::new()
.src(inner_src)
.dst(inner_dst)
.protocol(IPPROTO_TCP)
/ Tcp::new().sport(44000).dport(22)
/ Raw::from("ah-tunnel-v4");
let wire = packet.compile()?.as_bytes().to_vec();
let registry = ProtocolRegistry::new().with_security_association(sa);
let decoded = Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, &wire)?;
let ah = decoded.layer::<Ah>().expect("typed AH recovered");
assert_eq!(ah.next_header_value(), Some(4), "AH NH is IPv4-in-IPv4");
assert_eq!(ah.verification_status(), Some(true));
assert_eq!(decoded.layers::<Ipv4>().count(), 2, "outer + inner IPv4");
assert!(decoded.layer::<Tcp>().is_some());
Ok(())
}
#[test]
fn ah_transport_ipv6_decode_with_sa_verifies() -> Result<()> {
let spi = 0x0000_3300;
let sa = ah_hmac_sa(spi, false);
let packet: Packet = Ipv6::new()
.src(DOC6_SRC)
.dst(DOC6_DST)
.nh(IPPROTO_IPV6_AH)
.hop_limit(64)
/ Ah::secured(sa.clone()).spi(spi).sequence(1)
/ Tcp::new().sport(45000).dport(443)
/ Raw::from("ah-v6");
let wire = packet.compile()?.as_bytes().to_vec();
let registry = ProtocolRegistry::new().with_security_association(sa);
let decoded = Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv6, &wire)?;
let ah = decoded.layer::<Ah>().expect("typed AH recovered over IPv6");
assert_eq!(ah.spi_value(), Some(spi));
assert_eq!(ah.verification_status(), Some(true));
assert!(decoded.layer::<Tcp>().is_some());
assert_no_key_material(&decoded.show());
Ok(())
}
#[test]
fn ah_tunnel_ipv6_decode_with_sa_verifies() -> Result<()> {
let spi = 0x0000_3400;
let sa = ah_hmac_sa(spi, true);
let inner_src = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0x11);
let inner_dst = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0x12);
let packet: Packet = Ipv6::new()
.src(DOC6_SRC)
.dst(DOC6_DST)
.nh(IPPROTO_IPV6_AH)
.hop_limit(64)
/ Ah::secured(sa.clone()).spi(spi).sequence(1)
/ Ipv6::new()
.src(inner_src)
.dst(inner_dst)
.nh(IPPROTO_TCP)
.hop_limit(64)
/ Tcp::new().sport(46000).dport(22)
/ Raw::from("ah-tunnel-v6");
let wire = packet.compile()?.as_bytes().to_vec();
let registry = ProtocolRegistry::new().with_security_association(sa);
let decoded = Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv6, &wire)?;
let ah = decoded.layer::<Ah>().expect("typed AH recovered");
assert_eq!(ah.next_header_value(), Some(IPPROTO_IPV6), "AH NH is IPv6");
assert_eq!(ah.verification_status(), Some(true));
assert_eq!(decoded.layers::<Ipv6>().count(), 2, "outer + inner IPv6");
assert!(decoded.layer::<Tcp>().is_some());
Ok(())
}
#[test]
fn ah_decode_with_sa_detects_tampered_payload() -> Result<()> {
let spi = 0x0000_3500;
let sa = ah_hmac_sa(spi, false);
let packet: Packet = Ipv4::new()
.src(DOC_SRC)
.dst(DOC_DST)
.protocol(IPPROTO_AH)
.ttl(64)
/ Ah::secured(sa.clone()).spi(spi).sequence(1)
/ Tcp::new().sport(47000).dport(443)
/ Raw::from("ah-tamper");
let mut wire = packet.compile()?.as_bytes().to_vec();
*wire.last_mut().unwrap() ^= 0x01;
let registry = ProtocolRegistry::new().with_security_association(sa);
let err = Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, &wire)
.expect_err("a tampered payload byte must fail AH verification");
assert!(
matches!(err, CrafterError::InvalidFieldValue { field, .. } if field == "ipsec.ah.icv"),
"expected an ICV mismatch error, got {err:?}"
);
Ok(())
}
#[test]
fn ike_auth_with_sk_payload_round_trips() -> Result<()> {
let sk_sa = SecurityAssociation::new(0x0000_4400)
.encryption(EncryptionAlgorithm::AesGcm16, gcm_key())
.salt(gcm_salt());
let id = IkeIdPayload::initiator_ipv4(Ipv4Addr::new(192, 0, 2, 10));
let auth = IkeAuthPayload::new(AuthMethod::SharedKeyMic, vec![0x9Au8; 8]);
let sk = IkeEncryptedPayload::new(sk_sa)
.iv(gcm_iv())
.payload(id)
.payload(auth);
let header = IkeHeader::new()
.initiator_spi(0x1122_3344_5566_7788)
.responder_spi(0x99AA_BBCC_DDEE_FF00)
.exchange(IKE_AUTH)
.initiator();
let packet: Packet = Ipv4::new().src(DOC_SRC).dst(DOC_DST).protocol(IPPROTO_UDP)
/ Udp::new().sport(IKE_UDP_PORT).dport(IKE_UDP_PORT)
/ header
/ sk;
let wire = packet.compile()?.as_bytes().to_vec();
assert_eq!(wire[9], IPPROTO_UDP);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, &wire)?;
let hdr = decoded.layer::<IkeHeader>().expect("IKE header decoded");
assert_eq!(hdr.exchange_type_value(), Some(IKE_AUTH));
assert_eq!(hdr.next_payload_value(), Some(PAYLOAD_SK));
assert_eq!(decoded.compile()?.as_bytes(), wire.as_slice());
assert_no_key_material(&packet.summary());
assert_no_key_material(&packet.show());
Ok(())
}
#[test]
fn natt_udp_4500_esp_decode_with_sa_recovers_inner() -> Result<()> {
let spi = 0x0000_4500;
let sa = gcm_transport_sa(spi);
let packet: Packet = Ipv4::new().src(DOC_SRC).dst(DOC_DST).protocol(IPPROTO_UDP)
/ Udp::new().sport(4500).dport(4500)
/ Esp::secured(sa.clone()).spi(spi).sequence(1).iv(gcm_iv())
/ Tcp::new().sport(48000).dport(443)
/ Raw::from("natt-esp");
let wire = packet.compile()?.as_bytes().to_vec();
let registry = ProtocolRegistry::new().with_security_association(sa);
let decoded = Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, &wire)?;
assert!(decoded.layer::<Udp>().is_some(), "outer UDP/4500");
assert_eq!(decoded.layer::<Esp>().unwrap().spi_value(), Some(spi));
let tcp = decoded
.layer::<Tcp>()
.expect("inner TCP decrypted under NAT-T");
assert_eq!(tcp.source_port_value(), 48000);
assert_eq!(
decoded.layer::<Raw>().expect("inner Raw").as_bytes(),
b"natt-esp"
);
Ok(())
}
#[test]
fn natt_udp_4500_ike_with_marker_round_trips() -> Result<()> {
let proposal = Proposal::new(1, PROTOCOL_ID_IKE)
.with_transform(Transform::new(TRANSFORM_TYPE_ENCR, 20))
.with_transform(Transform::new(TRANSFORM_TYPE_DH, DH_GROUP_MODP_2048));
let ike_sa = IkeSaPayload::new().with_proposal(proposal);
let ke = IkeKePayload::new(DH_GROUP_MODP_2048, vec![0xAB; 32]);
let ni = IkeNoncePayload::new(vec![0x5A; 16]);
let header = IkeHeader::new()
.initiator_spi(0x0102_0304_0506_0708)
.exchange(IKE_SA_INIT)
.initiator();
let packet: Packet = Ipv4::new().src(DOC_SRC).dst(DOC_DST).protocol(IPPROTO_UDP)
/ Udp::new().sport(4500).dport(4500)
/ non_esp_marker()
/ header
/ ike_sa
/ ke
/ ni;
let wire = packet.compile()?.as_bytes().to_vec();
assert_eq!(&wire[28..32], &[0, 0, 0, 0], "non-ESP marker present");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, &wire)?;
assert!(
decoded.layer::<NatTraversal>().is_some(),
"marker decodes as a typed NatTraversal layer"
);
let hdr = decoded
.layer::<IkeHeader>()
.expect("IKE header after marker");
assert_eq!(hdr.exchange_type_value(), Some(IKE_SA_INIT));
assert!(decoded.layer::<IkeSaPayload>().is_some());
assert!(decoded.layer::<IkeKePayload>().is_some());
assert!(decoded.layer::<IkeNoncePayload>().is_some());
assert_eq!(decoded.compile()?.as_bytes(), wire.as_slice());
Ok(())
}