ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
esp.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file esp.h
6 * @brief ESP (RFC 4303) packet transform with AES-256-GCM (RFC 4106) - the IPsec datapath's crypto core.
7 *
8 * Tier 3 of the IPsec roadmap item is the ESP datapath. Its two halves separate cleanly: this pure,
9 * host-testable PACKET transform (encapsulate a payload into an ESP packet / verify + decapsulate one),
10 * and the device-side network-layer integration (hooking lwIP's IP input/output + the SAD/SPD), which is
11 * a separate, later track. This file is only the transform, gated with the IKEv2 feature (its Child-SA
12 * keys - SK_ei / SK_er from pc_ike_child_keymat - drive it) and reusing the library's AES-256-GCM.
13 *
14 * Wire layout (RFC 4303 §2, AES-GCM per RFC 4106):
15 * SPI(4) | Sequence Number(4) | IV(8, explicit) | { AES-GCM: Payload | Padding | Pad Length | Next
16 * Header } | ICV(16).
17 * The AEAD authenticates SPI | Seq as additional data; the nonce is the 4-byte salt (from the ESP key)
18 * concatenated with the 8-byte explicit IV. Padding right-aligns Pad Length + Next Header to a 4-octet
19 * boundary and holds the RFC 4303 monotonic bytes 1, 2, 3 ...
20 *
21 * @author Douglas Quigg (dstroy0)
22 * @date 2026
23 */
24
25#ifndef PROTOCORE_ESP_H
26#define PROTOCORE_ESP_H
27
28#include "protocore_config.h"
29
30#if PC_ENABLE_IKEV2
31
32#include <stddef.h>
33#include <stdint.h>
34
35/** @brief ESP header size: SPI(4) + Sequence Number(4). */
36#define PC_ESP_HDR_LEN 8
37/** @brief Explicit IV length carried in the packet (AES-GCM, RFC 4106). */
38#define PC_ESP_IV_LEN 8
39/** @brief Implicit salt length (the tail of the ESP key, not on the wire). */
40#define PC_ESP_SALT_LEN 4
41/** @brief AES-GCM authentication tag / ICV length. */
42#define PC_ESP_ICV_LEN 16
43/** @brief AES-256 key length. */
44#define PC_ESP_KEY_LEN 32
45
46/**
47 * @brief Encapsulate @p payload in an RFC 4303 ESP packet with AES-256-GCM.
48 *
49 * Writes SPI | Seq | @p iv | AES-GCM(payload | Padding | Pad Length | @p next_header) | ICV into @p out.
50 * The AAD is SPI | Seq; the GCM nonce is @p salt | @p iv; the plaintext is padded (RFC 4303 §2.4) so that
51 * Pad Length + Next Header land on a 4-octet boundary.
52 * @param key 32-byte AES-256 key (SK_ei / SK_er without the salt).
53 * @param salt the 4-byte salt (the ESP key's tail).
54 * @param iv the 8-byte explicit IV (unique per packet under a key - e.g. the sequence number).
55 * @return the ESP packet length written, or 0 on a bad argument / overflow.
56 */
57size_t pc_esp_gcm_encapsulate(uint32_t spi, uint32_t seq, const uint8_t key[PC_ESP_KEY_LEN],
58 const uint8_t salt[PC_ESP_SALT_LEN], const uint8_t iv[PC_ESP_IV_LEN], uint8_t next_header,
59 const uint8_t *payload, size_t payload_len, uint8_t *out, size_t out_cap);
60
61/**
62 * @brief Verify + decapsulate an ESP packet in place (the ciphertext is decrypted within @p packet).
63 *
64 * Verifies the ICV over SPI | Seq in constant time, decrypts, and strips the RFC 4303 trailer, exposing
65 * the inner payload plus its Next Header and the SPI / Sequence Number.
66 * @param packet the ESP packet (mutated: decrypted in place). @param payload_out points into it on success.
67 * @return true iff the ICV verifies (a forged / truncated packet returns false and writes no payload).
68 */
69bool pc_esp_gcm_decapsulate(const uint8_t key[PC_ESP_KEY_LEN], const uint8_t salt[PC_ESP_SALT_LEN], uint8_t *packet,
70 size_t len, uint32_t *spi_out, uint32_t *seq_out, uint8_t *next_header_out,
71 const uint8_t **payload_out, size_t *payload_len_out);
72
73// ── ESP anti-replay window (RFC 4303 §3.4.3) ───────────────────────────────────────────────────
74//
75// One receiver-side sliding window per inbound SA rejects replayed or too-old sequence numbers. This is
76// the 32-bit-sequence (non-ESN) window; the bitmap is a single 64-bit word, so the window is 64 packets.
77
78/** @brief ESP anti-replay window size (fixed by the 64-bit bitmap). */
79#define PC_ESP_REPLAY_WINDOW 64
80
81/** @brief Anti-replay sliding-window state for one inbound SA (zero-heap). */
82struct EspReplay
83{
84 uint32_t highest; ///< highest accepted sequence number so far
85 uint64_t bitmap; ///< bit i set = (highest - i) already accepted (bit 0 = highest itself)
86 bool seen_any; ///< false until the first packet is accepted
87};
88
89/** @brief Reset an anti-replay window (no packets seen yet). */
90void pc_esp_replay_init(EspReplay *r);
91
92/**
93 * @brief Anti-replay check + record for a received sequence number @p seq (RFC 4303 §3.4.3).
94 *
95 * Accepts and records a fresh @p seq (advancing the window when it is the new highest); rejects a
96 * duplicate already inside the window (a replay) or one that falls left of the window (too old).
97 * Sequence number 0 is invalid (ESP counts from 1) and is rejected.
98 * @return true to accept the packet, false to drop it.
99 */
100bool pc_esp_replay_check(EspReplay *r, uint32_t seq);
101
102#endif // PC_ENABLE_IKEV2
103#endif // PROTOCORE_ESP_H
User-facing configuration for ProtoCore.