1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//! Build and inspect an ESP-protected datagram entirely offline.
//!
//! This example crafts `Ipv4 / Esp::secured(sa) / Tcp / Raw` in transport mode
//! with an AES-GCM (AEAD) security association, compiles it to wire bytes, and
//! inspects it through `summary()` / `show()` / `hexdump()`. It then decodes the
//! datagram twice: once with the default SA-less registry (the encrypted body is
//! preserved opaquely) and once with a registry carrying the matching SA (the
//! AEAD tag is verified, the body is decrypted, and the inner TCP / Raw layers
//! are recovered as typed layers).
//!
//! Everything is offline: no interface is opened and no packet is transmitted.
//! All addresses are documentation address space (RFC 5737 / RFC 3849) and the
//! keys are fixed repeated bytes — never real key material.
//!
//! Run it with:
//!
//! ```text
//! cargo run --example ipsec_esp
//! ```
use std::net::Ipv4Addr;
use crafter::prelude::*;
/// Documentation-safe source / destination pair (RFC 5737).
const DOC_SRC: Ipv4Addr = Ipv4Addr::new(192, 0, 2, 10);
const DOC_DST: Ipv4Addr = Ipv4Addr::new(198, 51, 100, 20);
/// The documentation SPI carried by both the SA and the ESP header.
const ESP_SPI: u32 = 0x0000_2100;
/// A fixed 16-octet AES-128-GCM key (documentation-only, never a real key).
fn gcm_key() -> Vec<u8> {
vec![0x24u8; 16]
}
/// The fixed 4-octet AES-GCM salt (the implicit nonce prefix, RFC 4106).
fn gcm_salt() -> Vec<u8> {
vec![0xA1, 0xB2, 0xC3, 0xD4]
}
/// A deterministic 8-octet explicit IV so the sealed wire bytes are stable.
fn gcm_iv() -> Vec<u8> {
vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
}
/// Build the fixed AES-GCM-16 transport-mode security association.
fn esp_security_association() -> SecurityAssociation {
SecurityAssociation::new(ESP_SPI)
.encryption(EncryptionAlgorithm::AesGcm16, gcm_key())
.salt(gcm_salt())
.transport()
.extended_sequence(false)
}
fn main() -> Result<()> {
let sa = esp_security_association();
assert!(sa.validate().is_ok(), "documentation ESP SA validates");
// Build a transport-mode ESP datagram. The enclosing IPv4 datagram advertises
// ESP (protocol 50); the TCP / Raw upper layers are sealed inside ESP.
let packet: Packet = Ipv4::new().src(DOC_SRC).dst(DOC_DST).protocol(IPPROTO_ESP)
/ Esp::secured(sa.clone())
.spi(ESP_SPI)
.sequence(1)
.iv(gcm_iv())
/ Tcp::new().sport(40001).dport(443)
/ Raw::from("esp-example");
let compiled = packet.compile()?;
println!("example: ipsec_esp");
println!("mode: offline");
println!("documentation address pair: {DOC_SRC} -> {DOC_DST}");
println!("suite: AES-GCM-16 (AEAD), transport mode");
println!("summary: {}", packet.summary());
println!("show:\n{}", packet.show());
println!("compiled bytes: {}", compiled.len());
println!("hexdump:\n{}", compiled.hexdump());
let wire = compiled.as_bytes().to_vec();
// The IPv4 protocol field (offset 9) advertises ESP; the sealed body follows
// the 20-octet IPv4 header, so there is no cleartext TCP tail on the wire.
assert_eq!(wire[9], IPPROTO_ESP, "outer IPv4 advertises ESP");
// Decode with the default registry: no SA, so the encrypted body is opaque.
let opaque = Packet::decode_from_l3(NetworkLayer::Ipv4, &wire)?;
let opaque_esp = opaque.layer::<Esp>().expect("typed ESP header recovered");
println!();
println!("decode (no SA): {}", opaque.summary());
println!(
" spi: {:#010x}",
opaque_esp.spi_value().expect("SPI is exposed")
);
println!(
" sequence: {}",
opaque_esp.sequence_value().expect("sequence is exposed")
);
println!(
" opaque body bytes: {}",
opaque_esp
.opaque_body()
.map(<[u8]>::len)
.expect("no-SA decode keeps the body opaque")
);
assert!(
opaque.layer::<Tcp>().is_none(),
"without an SA the inner TCP stays encrypted"
);
// Decode with a registry carrying the SA: the AEAD tag verifies, the body
// decrypts, the trailer is stripped, and the inner layers decode as typed.
let registry = ProtocolRegistry::new().with_security_association(sa);
let decoded = Packet::decode_from_l3_with_registry(®istry, NetworkLayer::Ipv4, &wire)?;
println!();
println!("decode (with SA): {}", decoded.summary());
let esp = decoded.layer::<Esp>().expect("typed ESP recovered with SA");
println!(" spi: {:#010x}", esp.spi_value().expect("SPI is exposed"));
let tcp = decoded.layer::<Tcp>().expect("inner TCP decrypted");
println!(
" inner tcp: {} -> {}",
tcp.source_port_value(),
tcp.destination_port_value()
);
let inner = decoded.layer::<Raw>().expect("inner Raw decrypted");
println!(" inner raw: {:?}", inner.raw_string_lossy());
Ok(())
}