ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ipsec_db.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 ipsec_db.h
6 * @brief IPsec Security Policy Database (SPD) + Security Association Database (SAD) - RFC 4301.
7 *
8 * The ESP datapath (esp.h) is the crypto transform; this file is the two databases that decide, for a
9 * given packet, WHETHER and WITH WHICH SA to apply it. Both are pure, host-testable data structures with
10 * no heap and no lwIP dependency - the remaining device-side piece is only the IP input/output hook that
11 * feeds packets through these lookups.
12 *
13 * - SPD (RFC 4301 §4.4.1): an ordered list of policies matched against a packet's selectors (source /
14 * destination address ranges, protocol, port ranges); the FIRST matching policy wins and names an
15 * action - PROTECT (apply ESP with a bound SA), BYPASS (send in the clear), or DISCARD (drop).
16 * - SAD (RFC 4301 §4.4.2): the active Security Associations keyed by SPI. An inbound ESP packet is
17 * demuxed to its SA by SPI; an outbound PROTECT policy names the SA to encapsulate with. Each SA
18 * carries its key / salt, its outbound sequence counter, and its inbound anti-replay window.
19 *
20 * Selectors are value types (addresses stored inline, big-endian) so the databases persist independently
21 * of any wire buffer. @ref pc_ipsec_selector_from_ts bridges an IKEv2-negotiated TSi/TSr pair (the
22 * traffic selectors carried in ikev2.h) into an SPD selector, per RFC 4301 §4.4.1.
23 *
24 * @author Douglas Quigg (dstroy0)
25 * @date 2026
26 */
27
28#ifndef PROTOCORE_IPSEC_DB_H
29#define PROTOCORE_IPSEC_DB_H
30
31#include "protocore_config.h"
32
33#if PC_ENABLE_IKEV2
34
37#include <stddef.h>
38#include <stdint.h>
39
40/** @brief Longest selector address (IPv6). IPv4 uses the low 4 bytes. */
41#define PC_IPSEC_ADDR_MAX 16
42/** @brief Maximum policies in one SPD. */
43#define PC_IPSEC_SPD_MAX 8
44/** @brief Maximum Security Associations in one SAD. */
45#define PC_IPSEC_SAD_MAX 8
46
47/** @brief SPD policy action (RFC 4301 §4.4.1). */
48enum class IpsecAction : uint8_t
49{
50 DISCARD = 0, ///< drop the packet
51 BYPASS = 1, ///< forward without IPsec
52 PROTECT = 2, ///< apply ESP with the bound SA
53};
54
55/**
56 * @brief A traffic selector as an SPD range (value type, addresses inline big-endian).
57 *
58 * A packet matches when its family and protocol agree and its source / destination addresses and ports
59 * each fall within the inclusive [lo, hi] ranges. A protocol of 0 or a port range of [0, 65535] is "any".
60 */
61struct IpsecSelector
62{
63 uint8_t addr_len; ///< 4 (IPv4) or 16 (IPv6); also selects the family
64 uint8_t ip_protocol; ///< 0 = any
65 uint8_t src_lo[PC_IPSEC_ADDR_MAX];
66 uint8_t src_hi[PC_IPSEC_ADDR_MAX];
67 uint8_t dst_lo[PC_IPSEC_ADDR_MAX];
68 uint8_t dst_hi[PC_IPSEC_ADDR_MAX];
69 uint16_t src_port_lo;
70 uint16_t src_port_hi;
71 uint16_t dst_port_lo;
72 uint16_t dst_port_hi;
73};
74
75/** @brief One SPD policy: a selector, its action, and (for PROTECT) the outbound SA's SPI. */
76struct IpsecPolicy
77{
78 IpsecSelector sel;
79 IpsecAction action;
80 uint32_t sa_spi; ///< PROTECT: the SAD entry to encapsulate with (0 = not yet bound)
81};
82
83/** @brief An ordered Security Policy Database (first match wins). */
84struct IpsecSpd
85{
86 IpsecPolicy entries[PC_IPSEC_SPD_MAX];
87 size_t count;
88};
89
90/** @brief A concrete packet's 5-tuple, looked up against the SPD. Addresses point at big-endian octets. */
91struct IpsecFlow
92{
93 uint8_t addr_len; ///< 4 or 16 (must match the selector family)
94 uint8_t ip_protocol;
95 const uint8_t *src; ///< @ref addr_len octets
96 const uint8_t *dst; ///< @ref addr_len octets
97 uint16_t src_port;
98 uint16_t dst_port;
99};
100
101/** @brief One Security Association (RFC 4301 §4.4.2). */
102struct IpsecSaEntry
103{
104 uint32_t spi; ///< the SA's SPI (its SAD key)
105 uint8_t dst[PC_IPSEC_ADDR_MAX]; ///< SA destination address
106 uint8_t addr_len; ///< 4 or 16
107 uint8_t key[PC_ESP_KEY_LEN]; ///< AES-256 key (SK_ei / SK_er without salt)
108 uint8_t salt[PC_ESP_SALT_LEN]; ///< AES-GCM salt (the key's tail)
109 uint32_t seq; ///< outbound: last sequence number issued (0 = none yet)
110 EspReplay replay; ///< inbound: anti-replay window
111 bool inbound; ///< true = receive SA, false = send SA
112 bool valid; ///< false = free slot
113};
114
115/** @brief The active Security Association Database, keyed by SPI. */
116struct IpsecSad
117{
118 IpsecSaEntry entries[PC_IPSEC_SAD_MAX];
119 size_t count;
120};
121
122// ── SPD ─────────────────────────────────────────────────────────────────────────────────────────
123
124/** @brief Empty an SPD (no policies). */
125void pc_ipsec_spd_init(IpsecSpd *spd);
126
127/**
128 * @brief Append a policy to the SPD (order is significant - first match wins on lookup).
129 * @param sa_spi for a PROTECT action, the SAD SPI to bind (ignored otherwise).
130 * @return true on success, false if @p spd is full or an argument is null.
131 */
132bool pc_ipsec_spd_add(IpsecSpd *spd, const IpsecSelector *sel, IpsecAction action, uint32_t sa_spi);
133
134/**
135 * @brief Find the first SPD policy whose selector matches @p flow (RFC 4301 §4.4.1 ordered match).
136 * @return the matching policy, or nullptr if none matches (the caller drops, per the default-deny rule).
137 */
138const IpsecPolicy *pc_ipsec_spd_lookup(const IpsecSpd *spd, const IpsecFlow *flow);
139
140/** @brief True iff @p flow falls inside @p sel (family, protocol, address ranges, and port ranges). */
141bool pc_ipsec_selector_match(const IpsecSelector *sel, const IpsecFlow *flow);
142
143/**
144 * @brief Fill @p out from an IKEv2-negotiated TSi / TSr pair (RFC 4301 §4.4.1 SPD-from-TS).
145 *
146 * @p ts_src is the local (initiator) traffic selector, @p ts_dst the peer (responder) one; the protocol
147 * is taken from the pair (they must agree, or 0/any is honored). Both selectors must share an address
148 * family.
149 * @return true on success, false on a null argument or a family / length mismatch.
150 */
151bool pc_ipsec_selector_from_ts(IpsecSelector *out, const IkeTrafficSelector *ts_src, const IkeTrafficSelector *ts_dst);
152
153// ── SAD ─────────────────────────────────────────────────────────────────────────────────────────
154
155/** @brief Empty a SAD (no SAs). */
156void pc_ipsec_sad_init(IpsecSad *sad);
157
158/**
159 * @brief Install a Security Association keyed by @p spi.
160 *
161 * An inbound SA's anti-replay window is initialized; an outbound SA's sequence counter starts at 0. A
162 * duplicate SPI is rejected (SPIs must be unique within the SAD).
163 * @return the installed entry, or nullptr if the SAD is full, an argument is null, or @p spi already exists.
164 */
165IpsecSaEntry *pc_ipsec_sad_add(IpsecSad *sad, uint32_t spi, const uint8_t *dst, uint8_t addr_len,
166 const uint8_t key[PC_ESP_KEY_LEN], const uint8_t salt[PC_ESP_SALT_LEN], bool inbound);
167
168/** @brief Look up a valid SA by SPI (inbound ESP demux, RFC 4301 §4.1). nullptr if absent. */
169IpsecSaEntry *pc_ipsec_sad_find(IpsecSad *sad, uint32_t spi);
170
171/** @brief Remove the SA with @p spi (e.g. on an IKE DELETE). @return true if one was removed. */
172bool pc_ipsec_sad_remove(IpsecSad *sad, uint32_t spi);
173
174/**
175 * @brief Allocate the next outbound sequence number for @p sa (RFC 4303 §3.3.3, pre-increment from 1).
176 * @param seq_out receives the sequence number to place in the packet.
177 * @return true on success; false (and @p sa left unchanged) if the 32-bit counter would wrap - the SA
178 * must be rekeyed before any further packets, since a repeated sequence number breaks GCM.
179 */
180bool pc_ipsec_sad_next_seq(IpsecSaEntry *sa, uint32_t *seq_out);
181
182#endif // PC_ENABLE_IKEV2
183#endif // PROTOCORE_IPSEC_DB_H
ESP (RFC 4303) packet transform with AES-256-GCM (RFC 4106) - the IPsec datapath's crypto core.
IKEv2 (RFC 7296) message + payload codec (PC_ENABLE_IKEV2) - a zero-heap builder / parser for the Int...
User-facing configuration for ProtoCore.