ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ikev2.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 ikev2.h
6 * @brief IKEv2 (RFC 7296) message + payload codec (PC_ENABLE_IKEV2) - a zero-heap builder / parser for
7 * the Internet Key Exchange v2 wire format that negotiates IPsec security associations over UDP
8 * 500 / 4500 (NAT-T). This is the "secure machine bridge over untrusted networks" southbound: the
9 * framing tier of an IKEv2/IPsec stack.
10 *
11 * Scope (tier 1 of the IPsec roadmap item): the pure wire codec only - build / parse into caller
12 * buffers, no sockets and no crypto. It frames the 28-octet IKE header and the generic payload chain
13 * (SA -> proposals -> transforms, KE, Ni/Nr, IDi/IDr, CERT/CERTREQ, AUTH, N notify, D delete, TSi/TSr,
14 * and the SK encrypted-payload envelope). Tier 2's crypto lives here too: the SKEYSEED / SK_* key
15 * derivation (RFC 7296 §2.13-2.14, prf+ over HMAC-SHA2-256), the SK-payload AEAD (RFC 5282
16 * AES-256-GCM-16), the Diffie-Hellman shared secret (RFC 7296 §2.7, group 31 = X25519), PSK + ECDSA-P256
17 * authentication (§2.15 / RFC 7427), the IKE_SA_INIT + IKE_AUTH message assembly (§1.2 / §3.14), and the
18 * SA / Child-SA key schedules (§2.14 / §2.17). On top of those sits a full stateful handshake DRIVER -
19 * both the initiator and responder complete IKE_SA_INIT + IKE_AUTH to IKE_ST_ESTABLISHED with mutual PSK
20 * auth - plus the post-auth INFORMATIONAL (DPD / Delete / Notify) and CREATE_CHILD_SA exchanges over the
21 * established SA. Remaining for tier 2: rekey/retransmit orchestration and RSA-signature auth. Tier 3 (the
22 * ESP datapath, a network-layer transform that hooks lwIP) is a separate, later track. All the crypto
23 * reuses primitives the library already ships.
24 *
25 * Wire framing (byte-exact, network byte order): the IKE header is 8-byte Initiator SPI, 8-byte
26 * Responder SPI, 1-byte Next Payload, 1-byte Version (0x20 = MjVer 2 / MnVer 0), 1-byte Exchange Type,
27 * 1-byte Flags, 4-byte Message ID, 4-byte Length (whole message). Every payload starts with the same
28 * 4-byte generic header: Next Payload (the type of the FOLLOWING payload, so the chain is walked
29 * forward from the header's Next Payload), a Critical bit + 7 reserved bits, and a 2-byte Payload Length
30 * (this payload incl. the generic header). Multi-byte fields are big-endian throughout.
31 *
32 * Reference: RFC 7296 (IKEv2) + the IANA "Internet Key Exchange Version 2 (IKEv2) Parameters" registry.
33 * Every structure was cross-checked byte-for-byte against scapy's IKEv2 contrib (the header, KE, Nonce,
34 * Notify, Delete, and the SA -> proposal -> transform tree, including the key-length transform attribute
35 * verified against scapy's decoder).
36 *
37 * @author Douglas Quigg (dstroy0)
38 * @date 2026
39 */
40
41#ifndef PROTOCORE_IKEV2_H
42#define PROTOCORE_IKEV2_H
43
44#include "protocore_config.h"
45
46#if PC_ENABLE_IKEV2
47
48#include <stddef.h>
49#include <stdint.h>
50
51/** @brief IKEv2 UDP port (IKE_SA_INIT / IKE_AUTH before NAT is detected). */
52#define PC_IKEV2_PORT 500
53/** @brief IKEv2 UDP port after NAT traversal is negotiated (NAT-T, RFC 3948). */
54#define PC_IKEV2_NAT_PORT 4500
55/** @brief Fixed IKE header size. */
56#define PC_IKE_HDR_LEN 28
57/** @brief IKE SPI size (Initiator / Responder). */
58#define PC_IKE_SPI_LEN 8
59/** @brief Generic payload header size (next-payload + critical/reserved + 2-byte length). */
60#define PC_IKE_PAYLOAD_HDR_LEN 4
61/** @brief IKE version byte value: major 2, minor 0. */
62#define PC_IKE_VERSION 0x20
63/** @brief Critical bit in a payload's second header byte. */
64#define PC_IKE_CRITICAL 0x80
65
66/** @brief IKE header flag bits (RFC 7296 3.1). */
67#define PC_IKE_FLAG_INITIATOR 0x08 ///< set in messages from the original initiator
68#define PC_IKE_FLAG_VERSION 0x10 ///< set if a higher minor version is supported
69#define PC_IKE_FLAG_RESPONSE 0x20 ///< set in a response (clear in a request)
70
71/** @brief Exchange types (RFC 7296 3.1). */
72enum class IkeExchange : uint8_t
73{
74 IKE_SA_INIT = 34,
75 IKE_AUTH = 35,
76 IKE_CREATE_CHILD_SA = 36,
77 IKE_INFORMATIONAL = 37,
78};
79
80/** @brief Payload types / Next Payload values (RFC 7296 3.2); 0 terminates a chain. */
81enum class IkePayloadType : uint8_t
82{
83 IKE_PL_NONE = 0,
84 IKE_PL_SA = 33, ///< Security Association (proposals / transforms)
85 IKE_PL_KE = 34, ///< Key Exchange
86 IKE_PL_IDI = 35, ///< Identification - Initiator
87 IKE_PL_IDR = 36, ///< Identification - Responder
88 IKE_PL_CERT = 37, ///< Certificate
89 IKE_PL_CERTREQ = 38, ///< Certificate Request
90 IKE_PL_AUTH = 39, ///< Authentication
91 IKE_PL_NONCE = 40, ///< Nonce (Ni / Nr)
92 IKE_PL_NOTIFY = 41, ///< Notify
93 IKE_PL_DELETE = 42, ///< Delete
94 IKE_PL_VENDOR = 43, ///< Vendor ID
95 IKE_PL_TSI = 44, ///< Traffic Selector - Initiator
96 IKE_PL_TSR = 45, ///< Traffic Selector - Responder
97 IKE_PL_SK = 46, ///< Encrypted and Authenticated
98 IKE_PL_CP = 47, ///< Configuration
99 IKE_PL_EAP = 48, ///< Extensible Authentication
100 IKE_PL_SKF = 53, ///< Encrypted and Authenticated Fragment (RFC 7383)
101};
102
103/** @brief Transform types (RFC 7296 3.3.2). */
104enum class IkeTransformType : uint8_t
105{
106 IKE_TRANSFORM_ENCR = 1, ///< Encryption Algorithm
107 IKE_TRANSFORM_PRF = 2, ///< Pseudo-random Function
108 IKE_TRANSFORM_INTEG = 3, ///< Integrity Algorithm
109 IKE_TRANSFORM_DH = 4, ///< Diffie-Hellman / Key Exchange Method
110 IKE_TRANSFORM_ESN = 5, ///< Extended Sequence Numbers
111};
112
113/** @brief A few common Transform IDs (IANA), for convenience; any 16-bit id is accepted on the wire. */
114#define IKE_ENCR_AES_CBC 12
115#define IKE_ENCR_AES_GCM_16 20
116#define IKE_ENCR_CHACHA20_POLY1305 28
117#define IKE_PRF_HMAC_SHA2_256 5
118#define IKE_INTEG_HMAC_SHA2_256_128 12
119#define IKE_DH_MODP2048 14
120#define IKE_DH_ECP256 19
121#define IKE_DH_CURVE25519 31
122
123/** @brief Transform attribute type: Key Length (RFC 7296 3.3.5), encoded TV (AF bit set). */
124#define IKE_ATTR_KEY_LENGTH 14
125
126/** @brief Protocol IDs (RFC 7296 3.3.1 / 3.10 / 3.11). */
127enum class IkeProtocol : uint8_t
128{
129 IKE_PROTO_NONE = 0, ///< notify/delete not concerning an existing SA (RFC 7296 3.10)
130 IKE_PROTO_IKE = 1,
131 IKE_PROTO_AH = 2,
132 IKE_PROTO_ESP = 3,
133};
134
135/** @brief Identification payload ID types (RFC 7296 3.5). */
136enum class IkeIdType : uint8_t
137{
138 IKE_ID_RESERVED = 0, ///< IANA-reserved; the value an out-param holds before a parse succeeds
139 IKE_ID_IPV4_ADDR = 1,
140 IKE_ID_FQDN = 2,
141 IKE_ID_RFC822_ADDR = 3,
142 IKE_ID_IPV6_ADDR = 5,
143 IKE_ID_KEY_ID = 11,
144};
145
146/** @brief Authentication methods (RFC 7296 3.8 / IANA). */
147enum class IkeAuthMethod : uint8_t
148{
149 IKE_AUTH_RESERVED = 0, ///< IANA-reserved; the value an out-param holds before a parse succeeds
150 IKE_AUTH_RSA_SIG = 1, ///< RSA Digital Signature
151 IKE_AUTH_PSK = 2, ///< Shared Key Message Integrity Code (pre-shared key)
152 IKE_AUTH_DSS_SIG = 3, ///< DSS Digital Signature
153 IKE_AUTH_DIGITAL_SIG = 14 ///< Generic Digital Signature (RFC 7427)
154};
155
156/** @brief Traffic selector types (RFC 7296 3.13.1). */
157enum class IkeTsType : uint8_t
158{
159 IKE_TS_IPV4_ADDR_RANGE = 7,
160 IKE_TS_IPV6_ADDR_RANGE = 8,
161};
162
163/** @brief A decoded / to-be-encoded IKE header. */
164struct IkeHeader
165{
166 uint8_t init_spi[PC_IKE_SPI_LEN];
167 uint8_t resp_spi[PC_IKE_SPI_LEN];
168 IkePayloadType next_payload; ///< type of the first payload in the message
169 uint8_t version; ///< 0x20 for IKEv2
170 IkeExchange exchange; ///< @ref IkeExchange
171 uint8_t flags; ///< OR of PC_IKE_FLAG_*
172 uint32_t message_id;
173 uint32_t length; ///< whole-message length (built value; on parse, the value read off the wire)
174};
175
176/** @brief A parsed generic payload: its type (from the chain), the following type, and the body slice
177 * (the bytes after the 4-byte generic header), pointing INTO the caller's buffer. */
178struct IkePayload
179{
180 IkePayloadType type; ///< this payload's type
181 IkePayloadType next_payload; ///< type of the next payload (IKE_PL_NONE = last)
182 bool critical; ///< the Critical bit
183 const uint8_t *body;
184 size_t body_len;
185};
186
187/** @brief Forward-walks the payload chain of a message. */
188struct IkePayloadIter
189{
190 const uint8_t *area; ///< payload area (message + PC_IKE_HDR_LEN)
191 size_t len; ///< bytes remaining in the area
192 size_t off; ///< current offset into @ref area
193 IkePayloadType next_type; ///< type of the payload at @ref off (IKE_PL_NONE = done)
194};
195
196/** @brief One transform to encode inside a proposal (@ref pc_ike_sa_build). */
197struct IkeTransform
198{
199 IkeTransformType type; ///< which transform slot this fills
200 uint16_t id; ///< transform id (algorithm)
201 int32_t key_length; ///< key-length attribute in bits, or < 0 for none
202};
203
204/** @brief A parsed transform (from @ref pc_ike_transform_next). */
205struct IkeTransformRef
206{
207 IkeTransformType type;
208 uint16_t id;
209 int32_t key_length; ///< decoded key-length attribute, or < 0 if absent
210 bool last; ///< true if this is the last transform in the proposal
211};
212
213/** @brief A parsed proposal (from @ref pc_ike_sa_first_proposal). */
214struct IkeProposalRef
215{
216 uint8_t proposal_num;
217 IkeProtocol protocol_id; ///< which SA this proposal is for
218 uint8_t spi_size;
219 uint8_t num_transforms;
220 const uint8_t *spi; ///< SPI bytes (spi_size long), or nullptr
221 const uint8_t *transforms;
222 size_t transforms_len;
223 bool last; ///< true if this is the only / last proposal
224};
225
226/** @brief Iterates the transforms within a proposal's transform area. */
227struct IkeTransformIter
228{
229 const uint8_t *area;
230 size_t len;
231 size_t off;
232};
233
234/** @brief One traffic selector (RFC 7296 3.13.1). */
235struct IkeTrafficSelector
236{
237 IkeTsType ts_type; ///< selector address family
238 uint8_t ip_protocol; ///< 0 = any
239 uint16_t start_port;
240 uint16_t end_port;
241 const uint8_t *start_addr; ///< 4 bytes (IPv4) or 16 bytes (IPv6)
242 const uint8_t *end_addr;
243 size_t addr_len; ///< 4 or 16
244};
245
246/** @brief Configuration payload CFG Type (RFC 7296 3.15.1) - the exchange role of a CP payload. */
247enum class IkeCfgType : uint8_t
248{
249 IKE_CFG_REQUEST = 1, ///< a request for configuration (e.g. an internal address)
250 IKE_CFG_REPLY = 2, ///< the reply granting it
251 IKE_CFG_SET = 3, ///< an unsolicited push of configuration
252 IKE_CFG_ACK = 4, ///< acknowledgement of a SET
253};
254
255// Common Configuration Attribute types (RFC 7296 3.15.1).
256#define PC_IKE_CFG_INTERNAL_IP4_ADDRESS 1
257#define PC_IKE_CFG_INTERNAL_IP4_NETMASK 2
258#define PC_IKE_CFG_INTERNAL_IP4_DNS 3
259#define PC_IKE_CFG_INTERNAL_IP4_NBNS 4
260#define PC_IKE_CFG_INTERNAL_IP4_DHCP 6
261#define PC_IKE_CFG_APPLICATION_VERSION 7
262#define PC_IKE_CFG_INTERNAL_IP6_ADDRESS 8
263#define PC_IKE_CFG_INTERNAL_IP6_DNS 10
264#define PC_IKE_CFG_INTERNAL_IP6_DHCP 12
265#define PC_IKE_CFG_INTERNAL_IP4_SUBNET 13
266#define PC_IKE_CFG_INTERNAL_IP6_SUBNET 15
267
268/** @brief One Configuration Attribute (RFC 7296 3.15.1): a 15-bit type and its value. */
269struct IkeCfgAttr
270{
271 uint16_t type; ///< attribute type (the reserved high bit is masked off)
272 const uint8_t *value; ///< value bytes, or nullptr when @ref value_len is 0 (e.g. an empty request)
273 uint16_t value_len;
274};
275
276/** @brief Iterates the attributes within a Configuration payload's attribute area. */
277struct IkeCfgAttrIter
278{
279 const uint8_t *area;
280 size_t len;
281 size_t off;
282};
283
284// ── IKE header ──────────────────────────────────────────────────────────────────────────────────
285
286/**
287 * @brief Build the 28-byte IKE header from @p h (writing @p h->length verbatim - use
288 * @ref pc_ike_set_length to patch it once the payloads are appended).
289 * @return PC_IKE_HDR_LEN (28) on success, or 0 on overflow / bad input.
290 */
291size_t pc_ike_hdr_build(uint8_t *buf, size_t cap, const IkeHeader *h);
292
293/**
294 * @brief Parse the 28-byte IKE header into @p out.
295 * @return true if at least 28 bytes are present; false otherwise. (The version byte is surfaced in
296 * @p out->version but not rejected, so a caller can decide how to handle a mismatch.)
297 */
298bool pc_ike_hdr_parse(const uint8_t *buf, size_t len, IkeHeader *out);
299
300/**
301 * @brief Patch the 4-byte Length field of an already-built header (bytes 24..27) to @p total_len.
302 * @return true if @p buf holds at least a header.
303 */
304bool pc_ike_set_length(uint8_t *buf, size_t buf_cap, uint32_t total_len);
305
306// ── generic payload chain ─────────────────────────────────────────────────────────────────────
307
308/**
309 * @brief Start walking the payload chain: @p first_type is the header's Next Payload and @p area /
310 * @p area_len is the message body after the header (message + PC_IKE_HDR_LEN).
311 */
312void pc_ike_payload_iter_init(IkePayloadIter *it, IkePayloadType first_type, const uint8_t *area, size_t area_len);
313
314/**
315 * @brief Read the next payload's generic header + body slice into @p out and advance.
316 * @return true if a well-formed payload was produced; false at end of chain or on a malformed length
317 * (payload length < 4 or running past the area).
318 */
319bool pc_ike_payload_next(IkePayloadIter *it, IkePayload *out);
320
321/**
322 * @brief Build a raw payload: the 4-byte generic header (@p next_payload + optional @p critical +
323 * length) followed by @p body. Most callers use the typed builders below instead.
324 * @return total bytes written (4 + body_len), or 0 on overflow.
325 */
326size_t pc_ike_payload_build(uint8_t *buf, size_t cap, IkePayloadType next_payload, bool critical, const uint8_t *body,
327 size_t body_len);
328
329// ── typed payload builders (each writes a full payload incl. the generic header) ──────────────
330
331/** @brief Build an SA payload carrying ONE proposal with @p num_transforms transforms. */
332size_t pc_ike_sa_build(uint8_t *buf, size_t cap, IkePayloadType next_payload, uint8_t proposal_num,
333 IkeProtocol protocol_id, const uint8_t *spi, uint8_t spi_size, const IkeTransform *transforms,
334 uint8_t num_transforms);
335
336/** @brief Build a KE payload: DH group + key-exchange data. */
337size_t pc_ike_ke_build(uint8_t *buf, size_t cap, IkePayloadType next_payload, uint16_t dh_group, const uint8_t *data,
338 size_t data_len);
339
340/** @brief Build a Nonce payload (Ni / Nr): the raw nonce bytes. */
341size_t pc_ike_nonce_build(uint8_t *buf, size_t cap, IkePayloadType next_payload, const uint8_t *nonce,
342 size_t nonce_len);
343
344/** @brief Build an Identification payload (IDi or IDr - the type is set by the previous Next Payload). */
345size_t pc_ike_id_build(uint8_t *buf, size_t cap, IkePayloadType next_payload, IkeIdType id_type, const uint8_t *data,
346 size_t data_len);
347
348/** @brief Build an AUTH payload: auth method + authentication data. */
349size_t pc_ike_auth_build(uint8_t *buf, size_t cap, IkePayloadType next_payload, IkeAuthMethod auth_method,
350 const uint8_t *data, size_t data_len);
351
352/** @brief Build a CERT or CERTREQ payload (same layout): cert encoding + data. The payload type is set
353 * by the previous Next Payload; this only writes the body. */
354size_t pc_ike_cert_build(uint8_t *buf, size_t cap, IkePayloadType next_payload, uint8_t cert_encoding,
355 const uint8_t *data, size_t data_len);
356
357/** @brief Build a Notify payload: protocol + optional SPI + 16-bit type + notification data. */
358size_t pc_ike_notify_build(uint8_t *buf, size_t cap, IkePayloadType next_payload, IkeProtocol protocol_id,
359 const uint8_t *spi, uint8_t spi_size, uint16_t notify_type, const uint8_t *data,
360 size_t data_len);
361
362/** @brief Build a Delete payload: protocol + a list of @p num_spis SPIs each @p spi_size bytes. */
363size_t pc_ike_delete_build(uint8_t *buf, size_t cap, IkePayloadType next_payload, IkeProtocol protocol_id,
364 uint8_t spi_size, const uint8_t *spis, uint16_t num_spis);
365
366/** @brief Build a Traffic Selector payload (TSi or TSr) from @p num selectors. */
367size_t pc_ike_ts_build(uint8_t *buf, size_t cap, IkePayloadType next_payload, const IkeTrafficSelector *sels,
368 uint8_t num);
369
370/**
371 * @brief Build a Configuration payload (CP): a CFG Type then @p num_attrs attributes (RFC 7296 3.15).
372 *
373 * Each attribute is written as its 15-bit type (the reserved high bit clear), a 2-byte value length, and
374 * the value; a zero-length attribute (an empty request, e.g. INTERNAL_IP4_ADDRESS in a CFG_REQUEST) is
375 * fine. @return the payload length written, or 0 on overflow / a bad argument.
376 */
377size_t pc_ike_cp_build(uint8_t *buf, size_t cap, IkePayloadType next_payload, IkeCfgType cfg_type,
378 const IkeCfgAttr *attrs, uint8_t num_attrs);
379
380/**
381 * @brief Frame an SK (encrypted) payload envelope: the generic header then @p iv, @p ciphertext, and
382 * @p icv laid end to end. The AEAD that produces @p ciphertext + @p icv is the caller's (a later
383 * tier) - this only lays out the bytes.
384 * @return total bytes written, or 0 on overflow.
385 */
386size_t pc_ike_sk_build(uint8_t *buf, size_t cap, IkePayloadType next_payload, const uint8_t *iv, size_t iv_len,
387 const uint8_t *ciphertext, size_t ct_len, const uint8_t *icv, size_t icv_len);
388
389// ── message fragmentation (RFC 7383) ──────────────────────────────────────────────────────────
390//
391// A large encrypted message is split into Encrypted Fragment (SKF) payloads. Support is negotiated in
392// IKE_SA_INIT with the IKEV2_FRAGMENTATION_SUPPORTED notify; each fragment is its own AEAD (IV +
393// ciphertext + ICV) carrying a slice of the original inner-payload plaintext, numbered 1..Total.
394
395/** @brief IKEV2_FRAGMENTATION_SUPPORTED notify message type (RFC 7383 §2), zero-length data. */
396#define PC_IKE_N_FRAGMENTATION_SUPPORTED 16430
397/** @brief Largest Total Fragments this reassembler tracks. */
398#define PC_IKE_FRAG_MAX 32
399
400/**
401 * @brief Frame an SKF (Encrypted Fragment) payload envelope (RFC 7383 §2.5): the generic header, the
402 * Fragment Number and Total Fragments (each 16-bit), then @p iv, @p ciphertext, and @p icv.
403 *
404 * Per §2.5 the generic header's Next Payload is the first inner payload type on fragment 1 and 0 on the
405 * rest; the caller passes it as @p next_payload. The AEAD is the caller's - this only lays out the bytes.
406 * @return total bytes written, or 0 on overflow / a bad argument (including @p frag_num 0 or > @p total).
407 */
408size_t pc_ike_skf_build(uint8_t *buf, size_t cap, IkePayloadType next_payload, uint16_t frag_num, uint16_t total,
409 const uint8_t *iv, size_t iv_len, const uint8_t *ciphertext, size_t ct_len, const uint8_t *icv,
410 size_t icv_len);
411
412/**
413 * @brief Split an SKF payload body into its fragment counters and the IV / ciphertext / ICV slices.
414 * @param iv_len / @p icv_len the negotiated transform's sizes, needed to carve the fixed IV and ICV out.
415 * @return false on a truncated body or a nonsensical fragment number (0, or > Total).
416 */
417bool pc_ike_skf_parse(const uint8_t *body, size_t body_len, uint16_t *frag_num, uint16_t *total, size_t iv_len,
418 size_t icv_len, const uint8_t **iv, const uint8_t **ct, size_t *ct_len, const uint8_t **icv);
419
420/**
421 * @brief Reassembly state for one fragmented message (RFC 7383 §2.5.3). Decrypted plaintext chunks are
422 * staged, in arrival order, into a caller-provided @c pool and concatenated 1..Total on completion.
423 */
424struct IkeFragReasm
425{
426 uint16_t total; ///< Total Fragments (0 until the first fragment sets it)
427 uint16_t count; ///< distinct fragments stored so far
428 bool present[PC_IKE_FRAG_MAX]; ///< present[i] = fragment (i+1) stored
429 size_t off[PC_IKE_FRAG_MAX]; ///< pool offset of fragment (i+1)'s chunk
430 size_t len[PC_IKE_FRAG_MAX]; ///< fragment (i+1)'s chunk length
431 uint8_t *pool; ///< caller-provided staging buffer
432 size_t pool_cap;
433 size_t pool_used;
434};
435
436/** @brief Begin reassembly with a caller-owned staging buffer (must outlive the reassembler). */
437void pc_ike_frag_reasm_init(IkeFragReasm *r, uint8_t *pool, size_t pool_cap);
438
439/**
440 * @brief Stage one decrypted fragment plaintext chunk (RFC 7383 §2.5.3).
441 * @return false on a bad fragment number (0 / > Total / > @ref PC_IKE_FRAG_MAX), a Total that disagrees
442 * with an earlier fragment, a duplicate, or a pool overflow. A duplicate is rejected, not merged.
443 */
444bool pc_ike_frag_reasm_add(IkeFragReasm *r, uint16_t frag_num, uint16_t total, const uint8_t *chunk, size_t len);
445
446/** @brief True once every one of Total fragments has been staged. */
447bool pc_ike_frag_reasm_complete(const IkeFragReasm *r);
448
449/**
450 * @brief Concatenate the staged fragments in order into @p out (the reassembled inner-payload plaintext).
451 * @return the assembled length, or 0 if reassembly is incomplete or @p out_cap is too small.
452 */
453size_t pc_ike_frag_reasm_assemble(const IkeFragReasm *r, uint8_t *out, size_t out_cap);
454
455// ── anti-DoS COOKIE (RFC 7296 §2.6) ───────────────────────────────────────────────────────────
456//
457// Under an IKE_SA_INIT flood a responder can stay stateless: it replies with only a COOKIE notify and
458// creates no SA. A legitimate initiator retries with the cookie as its first payload, proving it can
459// receive at its claimed address before the responder spends any state or crypto. The cookie is a keyed
460// hash of the initiator's first-message material, so the responder recomputes and checks it with no
461// stored per-initiator state - only a small secret (versioned, rotated periodically).
462
463/** @brief COOKIE notify message type (RFC 7296 §3.10.1). */
464#define PC_IKE_N_COOKIE 16390
465/** @brief COOKIE length: a 1-byte secret-version tag + a SHA-256 digest. */
466#define PC_IKE_COOKIE_LEN 33
467
468/**
469 * @brief Compute an anti-DoS cookie: @p version | SHA-256( @p ni | @p ipi | @p spii | @p secret ).
470 *
471 * This is the RFC 7296 §2.6 stateless-cookie construction. @p ni is the initiator's nonce data, @p ipi
472 * its source IP octets, @p spii its 8-byte SPI, and @p secret the responder's current rotating secret;
473 * @p version tags which secret was used so a rotated secret can still verify in-flight cookies.
474 * @return @ref PC_IKE_COOKIE_LEN on success, or 0 on a null argument / @p out_cap too small.
475 */
476size_t pc_ike_cookie_compute(uint8_t version, const uint8_t *secret, size_t secret_len, const uint8_t *ni,
477 size_t ni_len, const uint8_t *ipi, size_t ipi_len, const uint8_t spii[PC_IKE_SPI_LEN],
478 uint8_t *out, size_t out_cap);
479
480/**
481 * @brief Verify a received cookie against a recomputation (RFC 7296 §2.6), in constant time.
482 *
483 * The version tag is taken from @p cookie itself, so the caller supplies the @p secret matching that
484 * version. @return true iff @p cookie is exactly @ref PC_IKE_COOKIE_LEN bytes and matches.
485 */
486bool pc_ike_cookie_verify(const uint8_t *cookie, size_t cookie_len, const uint8_t *secret, size_t secret_len,
487 const uint8_t *ni, size_t ni_len, const uint8_t *ipi, size_t ipi_len,
488 const uint8_t spii[PC_IKE_SPI_LEN]);
489
490/** @brief Build a COOKIE Notify payload carrying @p cookie (RFC 7296 §2.6). */
491size_t pc_ike_cookie_notify_build(uint8_t *buf, size_t cap, IkePayloadType next_payload, const uint8_t *cookie,
492 size_t cookie_len);
493
494// ── typed payload parsers (each takes a payload BODY from pc_ike_payload_next) ────────────────
495
496/** @brief Decode a KE body into DH group + key data. */
497bool pc_ike_ke_parse(const uint8_t *body, size_t body_len, uint16_t *dh_group, const uint8_t **data, size_t *data_len);
498
499/** @brief Decode an Identification body into id type + data. */
500bool pc_ike_id_parse(const uint8_t *body, size_t body_len, IkeIdType *id_type, const uint8_t **data, size_t *data_len);
501
502/** @brief Decode an AUTH body into method + data. */
503bool pc_ike_auth_parse(const uint8_t *body, size_t body_len, IkeAuthMethod *auth_method, const uint8_t **data,
504 size_t *data_len);
505
506/** @brief Decode a Notify body into protocol, type, optional SPI, and notification data. */
507bool pc_ike_notify_parse(const uint8_t *body, size_t body_len, IkeProtocol *protocol_id, uint16_t *notify_type,
508 const uint8_t **spi, uint8_t *spi_size, const uint8_t **data, size_t *data_len);
509
510/** @brief Decode a Delete body into protocol, SPI size, count, and the SPI list. */
511bool pc_ike_delete_parse(const uint8_t *body, size_t body_len, IkeProtocol *protocol_id, uint8_t *spi_size,
512 uint16_t *num_spis, const uint8_t **spis);
513
514/**
515 * @brief Slice an SK body into IV / ciphertext / ICV given the negotiated @p iv_len and @p icv_len.
516 * @return true if the body is at least @p iv_len + @p icv_len bytes.
517 */
518bool pc_ike_sk_parse(const uint8_t *body, size_t body_len, size_t iv_len, size_t icv_len, const uint8_t **iv,
519 const uint8_t **ciphertext, size_t *ct_len, const uint8_t **icv);
520
521// ── SA / proposal / transform parsing ─────────────────────────────────────────────────────────
522
523/** @brief Read the first proposal out of an SA payload body. */
524bool pc_ike_sa_first_proposal(const uint8_t *body, size_t body_len, IkeProposalRef *out);
525
526/** @brief Start iterating the transforms of a parsed proposal. */
527void pc_ike_transform_iter_init(IkeTransformIter *it, const IkeProposalRef *p);
528
529/** @brief Read the next transform (decoding the key-length attribute if present). */
530bool pc_ike_transform_next(IkeTransformIter *it, IkeTransformRef *out);
531
532// ── traffic selector parsing ──────────────────────────────────────────────────────────────────
533
534/** @brief The number of traffic selectors in a TS payload body (0 on malformed). */
535uint8_t pc_ike_ts_count(const uint8_t *body, size_t body_len);
536
537/** @brief Decode selector @p index (0-based) from a TS payload body. */
538bool pc_ike_ts_get(const uint8_t *body, size_t body_len, uint8_t index, IkeTrafficSelector *out);
539
540/**
541 * @brief Decode a Configuration payload body into its CFG Type and its attribute area (RFC 7296 3.15).
542 * @param attrs / @p attrs_len receive a pointer into @p body and the length of the attribute region;
543 * walk them with @ref pc_ike_cp_attr_iter_init + @ref pc_ike_cp_attr_next.
544 * @return false on a truncated body.
545 */
546bool pc_ike_cp_parse(const uint8_t *body, size_t body_len, IkeCfgType *cfg_type, const uint8_t **attrs,
547 size_t *attrs_len);
548
549/** @brief Begin iterating the attributes returned by @ref pc_ike_cp_parse. */
550void pc_ike_cp_attr_iter_init(IkeCfgAttrIter *it, const uint8_t *attrs, size_t attrs_len);
551
552/** @brief Decode the next Configuration Attribute; false at the end or on a truncated attribute. */
553bool pc_ike_cp_attr_next(IkeCfgAttrIter *it, IkeCfgAttr *out);
554
555// ── tier 2: SKEYSEED / SK_* key derivation (RFC 7296 §2.13-2.14) ───────────────────────────────
556//
557// The PRF is HMAC-SHA2-256 (transform id IKE_PRF_HMAC_SHA2_256), whose output/key length is 32 bytes.
558// prf+ (§2.13) expands (key, seed) into arbitrary keying material; pc_ike_derive_keys runs the §2.14
559// SKEYSEED + SK_* chain for an initial IKE SA. The AEAD that USES these keys is a later tier.
560
561/** @brief PRF (HMAC-SHA2-256) output / key length, in bytes. */
562#define PC_IKE_PRF_LEN 32
563/** @brief Largest single SK_* key this build stores (an AES-256 key + a 4-byte AEAD salt, with margin). */
564#define PC_IKE_SK_MAX 40
565/** @brief Largest IKEv2 nonce (RFC 7296 §2.10: 16..256 octets). */
566#define PC_IKE_NONCE_MAX 256
567
568/** @brief Per-key lengths for the SK_* chain (algorithm-dependent; the caller sets them from the
569 * negotiated transforms). Each is in bytes. */
570struct IkeKeyLengths
571{
572 size_t sk_d; ///< SK_d length = the PRF key length (32 for HMAC-SHA2-256).
573 size_t sk_a; ///< SK_ai / SK_ar length = the integrity key length (32 for HMAC-SHA2-256-128).
574 size_t sk_e; ///< SK_ei / SK_er length = the encryption key (+ any AEAD salt: 32, or 36 for AES-GCM).
575 size_t sk_p; ///< SK_pi / SK_pr length = the PRF key length (32).
576};
577
578/** @brief The seven derived keys (RFC 7296 §2.14). Each key's valid length is the matching field below. */
579struct IkeKeyMaterial
580{
581 uint8_t sk_d[PC_IKE_SK_MAX];
582 uint8_t sk_ai[PC_IKE_SK_MAX];
583 uint8_t sk_ar[PC_IKE_SK_MAX];
584 uint8_t sk_ei[PC_IKE_SK_MAX];
585 uint8_t sk_er[PC_IKE_SK_MAX];
586 uint8_t sk_pi[PC_IKE_SK_MAX];
587 uint8_t sk_pr[PC_IKE_SK_MAX];
588 size_t sk_d_len; ///< valid bytes in sk_d
589 size_t sk_a_len; ///< valid bytes in sk_ai / sk_ar
590 size_t sk_e_len; ///< valid bytes in sk_ei / sk_er
591 size_t sk_p_len; ///< valid bytes in sk_pi / sk_pr
592};
593
594/**
595 * @brief prf+ (RFC 7296 §2.13) with HMAC-SHA2-256 as the PRF: expand (@p key, @p seed) into @p out_len
596 * bytes as T1 | T2 | ... where Ti = prf(key, Ti-1 | seed | i), i a 1-byte counter from 0x01, and
597 * T0 is empty.
598 * @return false on a null argument or if @p out_len would need more than 255 blocks (the §2.13 cap).
599 */
600bool pc_ike_prf_plus(const uint8_t *key, size_t key_len, const uint8_t *seed, size_t seed_len, uint8_t *out,
601 size_t out_len);
602
603/**
604 * @brief Derive SKEYSEED and the seven SK_* keys (RFC 7296 §2.14) for an initial IKE SA:
605 * SKEYSEED = prf(Ni | Nr, g^ir)
606 * {SK_d | SK_ai | SK_ar | SK_ei | SK_er | SK_pi | SK_pr} = prf+(SKEYSEED, Ni | Nr | SPIi | SPIr)
607 * The PRF is HMAC-SHA2-256; @p dh_secret is the raw shared Diffie-Hellman secret g^ir.
608 * @param spi_i / spi_r the 8-byte initiator / responder IKE SPIs.
609 * @param lens per-key lengths (each 1..PC_IKE_SK_MAX; each nonce 1..PC_IKE_NONCE_MAX).
610 * @return false on a null argument or an out-of-range length.
611 */
612bool pc_ike_derive_keys(const uint8_t *dh_secret, size_t dh_len, const uint8_t *ni, size_t ni_len, const uint8_t *nr,
613 size_t nr_len, const uint8_t *spi_i, const uint8_t *spi_r, const IkeKeyLengths *lens,
614 IkeKeyMaterial *out);
615
616// ── tier 2: SK-payload AEAD (AES-256-GCM-16, RFC 5282 in RFC 7296 §3.14) ────────────────────────
617//
618// The SK payload body is `IV | ciphertext | ICV`. For AES-GCM (RFC 5282) the 12-byte GCM nonce is a
619// 4-byte salt (the tail of SK_ei / SK_er, kept out of the 32-byte AES key) concatenated with the 8-byte
620// explicit IV the sender writes into the body; the AAD is the message from the IKE header through the SK
621// payload's 4-byte generic header (everything not encrypted). The plaintext framing (inner payloads +
622// padding + pad-length) is the caller's; these functions are the crypto core.
623
624/** @brief AES-256 key length for AES-GCM-16 (SK_ei / SK_er, salt excluded). */
625#define PC_IKE_AEAD_KEY_LEN 32
626/** @brief AEAD salt length: the 4-byte tail of SK_ei / SK_er (RFC 5282). */
627#define PC_IKE_GCM_SALT_LEN 4
628/** @brief Explicit IV length carried in the SK body for AES-GCM (RFC 5282). */
629#define PC_IKE_GCM_IV_LEN 8
630/** @brief AEAD tag / ICV length (AES-GCM-16). */
631#define PC_IKE_AEAD_ICV_LEN 16
632
633/**
634 * @brief Seal an SK payload with AES-256-GCM: authenticate @p aad and encrypt @p pt, writing @p pt_len
635 * ciphertext bytes then the 16-byte ICV into @p out (which must hold @p pt_len + PC_IKE_AEAD_ICV_LEN;
636 * @p out may alias @p pt). The GCM nonce is @p salt || @p iv (RFC 5282).
637 * @param key 32-byte AES-256 key = SK_ei (initiator->responder) or SK_er (responder->initiator).
638 * @return false only on a null argument.
639 */
640bool pc_ike_sk_aead_seal(const uint8_t key[PC_IKE_AEAD_KEY_LEN], const uint8_t salt[PC_IKE_GCM_SALT_LEN],
641 const uint8_t iv[PC_IKE_GCM_IV_LEN], const uint8_t *aad, size_t aad_len, const uint8_t *pt,
642 size_t pt_len, uint8_t *out);
643
644/**
645 * @brief Open an AES-256-GCM SK payload: verify @p tag over @p aad || @p ct in constant time and, only on
646 * success, decrypt @p ct into @p out (@p out may alias @p ct). The nonce is @p salt || @p iv.
647 * @return true iff the tag is valid (a forged / tampered message returns false and writes no plaintext).
648 */
649bool pc_ike_sk_aead_open(const uint8_t key[PC_IKE_AEAD_KEY_LEN], const uint8_t salt[PC_IKE_GCM_SALT_LEN],
650 const uint8_t iv[PC_IKE_GCM_IV_LEN], const uint8_t *aad, size_t aad_len, const uint8_t *ct,
651 size_t ct_len, const uint8_t tag[PC_IKE_AEAD_ICV_LEN], uint8_t *out);
652
653// ── tier 2: Diffie-Hellman shared secret (the KE payload's g^ir, RFC 7296 §2.7) ─────────────────
654//
655// Group 31 (curve25519, RFC 7748 X25519) is supported today; groups 19 (NIST P-256) and 14 (MODP-2048)
656// are a later increment. The ephemeral private key is the caller's (32 random bytes for X25519); these
657// derive our KE public value and the shared secret that feeds pc_ike_derive_keys.
658
659/** @brief X25519 private / public / shared-secret length (bytes). */
660#define PC_IKE_X25519_LEN 32
661
662/**
663 * @brief Compute our KE public value for a negotiated D-H @p group (group 31: X25519(@p our_priv, base)).
664 * @return the public-value length written to @p out, or 0 on an unsupported group / bad length / small cap.
665 */
666size_t pc_ike_dh_public(uint16_t group, const uint8_t *our_priv, size_t priv_len, uint8_t *out, size_t out_cap);
667
668/**
669 * @brief Compute the D-H shared secret g^ir for a negotiated @p group (group 31: X25519(@p our_priv,
670 * @p peer_pub)). The result feeds SKEYSEED in pc_ike_derive_keys.
671 * @return the shared-secret length written to @p out, or 0 on an unsupported group / bad length / small cap.
672 */
673size_t pc_ike_dh_compute(uint16_t group, const uint8_t *our_priv, size_t priv_len, const uint8_t *peer_pub,
674 size_t pub_len, uint8_t *out, size_t out_cap);
675
676// ── tier 2: IKE_AUTH pre-shared-key authentication (RFC 7296 §2.15) ─────────────────────────────
677//
678// With a shared key the AUTH payload data is
679// AUTH = prf( prf(PSK, "Key Pad for IKEv2"), <SignedOctets> )
680// where <SignedOctets> = RealMessage | Nonce(peer) | prf(SK_p, RestOfIDPayload):
681// * RealMessage = the octets of this side's first message (the whole IKE_SA_INIT it sent, for the
682// initiator; the IKE_SA_INIT it sent, for the responder),
683// * Nonce(peer) = the *other* side's nonce data,
684// * SK_p = SK_pi when the initiator signs, SK_pr when the responder signs,
685// * RestOfIDPayload = the ID payload body (the 4 bytes after its generic header: ID type + 3 reserved
686// + the identification data), i.e. an IkePayload::body for an IDi/IDr payload.
687// The PRF is HMAC-SHA2-256. This computes the 32-byte AUTH data; verifying a peer is the same value
688// compared in constant time against the received AUTH payload.
689
690/** @brief AUTH payload data length for prf HMAC-SHA2-256 (bytes). */
691#define PC_IKE_AUTH_LEN 32
692/** @brief The fixed RFC 7296 §2.15 pre-shared-key pad string (17 octets, no NUL). */
693#define PC_IKE_PSK_PAD "Key Pad for IKEv2"
694
695/**
696 * @brief Compute the RFC 7296 §2.15 pre-shared-key AUTH payload data into @p out (PC_IKE_AUTH_LEN bytes).
697 * @param psk / psk_len the shared key.
698 * @param real_msg / real_len this side's first message octets (RealMessage).
699 * @param peer_nonce / nonce_len the peer's nonce data.
700 * @param sk_p / sk_p_len SK_pi (initiator) or SK_pr (responder).
701 * @param id_body / id_body_len the signing side's ID payload body (RestOfIDPayload).
702 * @return false on a null argument; true on success (@p out filled).
703 */
704bool pc_ike_auth_psk(const uint8_t *psk, size_t psk_len, const uint8_t *real_msg, size_t real_len,
705 const uint8_t *peer_nonce, size_t nonce_len, const uint8_t *sk_p, size_t sk_p_len,
706 const uint8_t *id_body, size_t id_body_len, uint8_t out[PC_IKE_AUTH_LEN]);
707
708// ── tier 2: IKE_SA_INIT message assembly (RFC 7296 §1.2) ───────────────────────────────────────
709//
710// The IKE_SA_INIT exchange's message is HDR | SA | KE | Nonce. These compose the tier-1 payload codec
711// into the whole message (correct Next Payload chain + header Length) and read it back, so the state
712// machine works in messages, not loose payloads. One proposal per SA (the common client shape).
713
714/** @brief The salient parsed contents of an IKE_SA_INIT message; slices point into the message buffer. */
715struct IkeSaInitMsg
716{
717 uint8_t init_spi[PC_IKE_SPI_LEN];
718 uint8_t resp_spi[PC_IKE_SPI_LEN];
719 bool is_response; ///< true if the RESPONSE flag was set
720 IkeProposalRef proposal; ///< the first proposal of SAi1 / SAr1
721 uint16_t dh_group; ///< the KE payload's D-H group
722 const uint8_t *ke_data; ///< KE key-exchange data
723 size_t ke_len; ///< KE data length
724 const uint8_t *nonce; ///< Ni / Nr data
725 size_t nonce_len; ///< nonce length
726};
727
728/**
729 * @brief Build a complete IKE_SA_INIT message: HDR | SA(one proposal) | KE | Nonce, with the payload
730 * chain's Next Payload fields and the header Length all set correctly.
731 * @param is_response false for a request (sets the INITIATOR flag), true for a response.
732 * @param proposal_num the SA proposal number (usually 1).
733 * @param transforms the proposal's transforms (ENCR / PRF / INTEG / DH ...).
734 * @param dh_group the KE payload's D-H group id.
735 * @param ke_data the KE key-exchange data (our public value).
736 * @param nonce the Ni / Nr nonce data.
737 * @return total message length written, or 0 on overflow / a bad argument.
738 */
739size_t pc_ike_sa_init_build(uint8_t *buf, size_t cap, const uint8_t init_spi[PC_IKE_SPI_LEN],
740 const uint8_t resp_spi[PC_IKE_SPI_LEN], uint32_t msg_id, bool is_response,
741 uint8_t proposal_num, const IkeTransform *transforms, uint8_t num_transforms,
742 uint16_t dh_group, const uint8_t *ke_data, size_t ke_len, const uint8_t *nonce,
743 size_t nonce_len);
744
745/**
746 * @brief Parse an IKE_SA_INIT message into @p out (SPIs, first proposal, KE group + data, nonce).
747 * @return true iff the header is IKE_SA_INIT and the SA, KE, and Nonce payloads are all present + valid.
748 */
749bool pc_ike_sa_init_parse(const uint8_t *msg, size_t len, IkeSaInitMsg *out);
750
751// ── tier 2: IKE_AUTH encrypted-message assembly (RFC 7296 §3.14, RFC 5282) ─────────────────────
752//
753// The IKE_AUTH (and any post-IKE_SA_INIT) message is HDR | SK{ inner payloads }. The SK payload body is
754// IV | ciphertext | ICV; the plaintext is the inner payload chain followed by RFC 7296 §3.14 padding +
755// a 1-byte Pad Length (this build uses zero padding, so a single 0x00). The AEAD (AES-256-GCM-16) is
756// keyed by SK_ei / SK_er with the RFC 5282 salt||IV nonce, and authenticates the AAD = the IKE header
757// through the SK payload's 4-byte generic header (everything before the IV). The inner chain is the
758// caller's (build IDi | AUTH | SAi2 | TSi | TSr with the tier-1 payload builders, correctly chained).
759
760/** @brief Fixed overhead the SK envelope adds around @p inner_len bytes: SK generic hdr + IV + pad-len + ICV. */
761#define PC_IKE_SK_OVERHEAD (PC_IKE_PAYLOAD_HDR_LEN + PC_IKE_GCM_IV_LEN + 1 + PC_IKE_AEAD_ICV_LEN)
762
763/**
764 * @brief Build a complete SK-encrypted message (HDR | SK{ @p inner }) with AES-256-GCM.
765 * @param first_inner_type the type of the first inner payload (the SK payload's Next Payload), e.g. IKE_PL_IDI.
766 * @param inner / inner_len the pre-built, chained inner payloads (their own Next Payload fields set).
767 * @param key / salt / iv SK_ei/SK_er (32 B), the 4-byte salt, and the 8-byte explicit IV (written into the body).
768 * @return total message length, or 0 on overflow / a bad argument.
769 */
770size_t pc_ike_auth_msg_build(uint8_t *buf, size_t cap, const uint8_t init_spi[PC_IKE_SPI_LEN],
771 const uint8_t resp_spi[PC_IKE_SPI_LEN], uint32_t msg_id, bool is_response,
772 IkePayloadType first_inner_type, const uint8_t *inner, size_t inner_len,
773 const uint8_t key[PC_IKE_AEAD_KEY_LEN], const uint8_t salt[PC_IKE_GCM_SALT_LEN],
774 const uint8_t iv[PC_IKE_GCM_IV_LEN]);
775
776/**
777 * @brief Verify + decrypt an SK-encrypted message in place, exposing the inner payload chain.
778 *
779 * Parses HDR | SK, verifies the ICV over the AAD in constant time, decrypts the ciphertext in place, and
780 * strips the RFC 7296 §3.14 padding + Pad Length. On success @p inner_out points at the decrypted inner
781 * chain inside @p msg and @p first_inner_type is its first payload's type.
782 * @return true iff the header is SK-framed and the tag verifies (a forged message returns false).
783 */
784bool pc_ike_auth_msg_open(uint8_t *msg, size_t len, const uint8_t key[PC_IKE_AEAD_KEY_LEN],
785 const uint8_t salt[PC_IKE_GCM_SALT_LEN], IkePayloadType *first_inner_type,
786 const uint8_t **inner_out, size_t *inner_len_out);
787
788// ── tier 2: IKE_AUTH ECDSA-P256 (certificate) authentication (RFC 7296 §2.15, RFC 7427) ─────────
789//
790// A digital-signature AUTH signs the SAME octets the PSK MAC covers - RealMessage | Nonce |
791// prf(SK_p, RestOfIDPayload) - with the identity's private key (here NIST P-256 / ECDSA-SHA256), and a
792// peer is authenticated by verifying that signature against the public key from its CERT. The octets are
793// assembled into a caller-provided scratch buffer (zero-heap) since the signer hashes them whole.
794
795/** @brief P-256 uncompressed public point length (0x04 || X || Y). */
796#define PC_IKE_ECDSA_P256_PUB_LEN 65
797/** @brief P-256 private scalar length. */
798#define PC_IKE_ECDSA_P256_PRIV_LEN 32
799/** @brief Raw ECDSA-P256 signature length (r || s). */
800#define PC_IKE_ECDSA_P256_SIG_LEN 64
801
802/**
803 * @brief Assemble the RFC 7296 §2.15 signed octets = RealMessage | Nonce | prf(SK_p, id_body) into
804 * @p scratch (needs @p real_len + @p nonce_len + 32 bytes).
805 * @return the octet length written, or 0 on overflow / a null argument.
806 */
807size_t pc_ike_signed_octets(uint8_t *scratch, size_t cap, const uint8_t *real, size_t real_len, const uint8_t *nonce,
808 size_t nonce_len, const uint8_t *sk_p, size_t sk_p_len, const uint8_t *id_body,
809 size_t id_body_len);
810
811/**
812 * @brief Produce an ECDSA-P256 (SHA-256) AUTH signature over the signed octets. @p scratch holds the
813 * assembled octets (see pc_ike_signed_octets). @return true on success (@p sig filled).
814 */
815bool pc_ike_auth_sign_ecdsa_p256(uint8_t sig[PC_IKE_ECDSA_P256_SIG_LEN], const uint8_t priv[PC_IKE_ECDSA_P256_PRIV_LEN],
816 uint8_t *scratch, size_t scratch_cap, const uint8_t *real, size_t real_len,
817 const uint8_t *nonce, size_t nonce_len, const uint8_t *sk_p, size_t sk_p_len,
818 const uint8_t *id_body, size_t id_body_len);
819
820/**
821 * @brief Verify a peer's ECDSA-P256 (SHA-256) AUTH signature over the signed octets against its public
822 * point @p pub. @return true iff the signature is valid (a forged AUTH / wrong key returns false).
823 */
824bool pc_ike_auth_verify_ecdsa_p256(const uint8_t pub[PC_IKE_ECDSA_P256_PUB_LEN],
825 const uint8_t sig[PC_IKE_ECDSA_P256_SIG_LEN], uint8_t *scratch, size_t scratch_cap,
826 const uint8_t *real, size_t real_len, const uint8_t *nonce, size_t nonce_len,
827 const uint8_t *sk_p, size_t sk_p_len, const uint8_t *id_body, size_t id_body_len);
828
829// ── tier 2: IKE SA context + key material from a completed IKE_SA_INIT (RFC 7296 §2.14, §2.17) ──
830//
831// After IKE_SA_INIT both peers know the SPIs, the negotiated cipher suite, both nonces, and (via their
832// own D-H private + the peer's KE) the shared secret - everything needed to derive the SK_* keys. These
833// tie the tier-2 crypto (D-H + prf+ key schedule) together against a parsed exchange, so the state
834// machine holds one `IkeSa` per session.
835
836/** @brief The negotiated IKE cipher suite (the transforms chosen in the IKE_SA_INIT SA payload). */
837struct IkeSuite
838{
839 uint16_t encr; ///< encryption transform id (e.g. IKE_ENCR_AES_GCM_16).
840 int32_t encr_keylen; ///< encryption key length in BITS (e.g. 256), or < 0 for a fixed-size cipher.
841 uint16_t prf; ///< PRF transform id (IKE_PRF_HMAC_SHA2_256).
842 uint16_t integ; ///< integrity transform id, or 0 for an AEAD cipher (no separate integrity key).
843 uint16_t dh; ///< D-H group id (IKE_DH_CURVE25519).
844};
845
846/** @brief One IKE SA's session state after IKE_SA_INIT: identity, negotiated suite, and derived keys. */
847struct IkeSa
848{
849 uint8_t init_spi[PC_IKE_SPI_LEN];
850 uint8_t resp_spi[PC_IKE_SPI_LEN];
851 bool is_initiator; ///< our role in this SA
852 IkeSuite suite; ///< the negotiated cipher suite
853 IkeKeyMaterial keys; ///< the derived SK_d / ai / ar / ei / er / pi / pr (filled by keys_from_init)
854};
855
856/**
857 * @brief Map a negotiated @p suite to the SK_* per-key lengths (RFC 7296 §2.14 + the cipher's key size).
858 *
859 * Supports the suites the library implements: PRF/INTEG HMAC-SHA2-256 (32-byte keys, sk_a = 0 for an
860 * AEAD cipher), and AES-GCM-16 (encr key + a 4-byte salt) or a plain block cipher (encr key only).
861 * @return false on an unsupported suite (a PRF other than HMAC-SHA2-256, or a bad key length).
862 */
863bool pc_ike_suite_keylengths(const IkeSuite *suite, IkeKeyLengths *out);
864
865/**
866 * @brief Derive @p sa->keys from a completed IKE_SA_INIT: compute g^ir = D-H(@p our_dh_priv, @p peer_ke)
867 * for the suite's group, then run the §2.14 SKEYSEED + SK_* schedule with @p sa's SPIs and the
868 * suite's key lengths. @p sa->init_spi / resp_spi / suite must already be set.
869 * @return false on an unsupported suite / group or a bad length; the two peers derive identical keys.
870 */
871bool pc_ike_sa_keys_from_init(IkeSa *sa, const uint8_t *our_dh_priv, size_t our_dh_priv_len, const uint8_t *peer_ke,
872 size_t peer_ke_len, const uint8_t *ni, size_t ni_len, const uint8_t *nr, size_t nr_len);
873
874/**
875 * @brief Derive the NEW IKE SA keys when rekeying an IKE SA (RFC 7296 §2.18):
876 * SKEYSEED = prf(SK_d(old), g^ir(new) | Ni | Nr)
877 * {SK_*} = prf+(SKEYSEED, Ni | Nr | SPIi | SPIr) [the new SPIs]
878 * Distinct from the initial schedule: the OLD SA's SK_d is the PRF key and g^ir prepends the seed.
879 * @param sk_d_old the current IKE SA's SK_d. @param dh_secret the new g^ir (X25519). @param spi_i / spi_r
880 * the new SA's SPIs. @return false on a null arg or an out-of-range length.
881 */
882bool pc_ike_rekey_derive_keys(const uint8_t *sk_d_old, size_t sk_d_old_len, const uint8_t *dh_secret, size_t dh_len,
883 const uint8_t *ni, size_t ni_len, const uint8_t *nr, size_t nr_len, const uint8_t *spi_i,
884 const uint8_t *spi_r, const IkeKeyLengths *lens, IkeKeyMaterial *out);
885
886// ── tier 2: initiator IKE_SA_INIT handshake driver (RFC 7296 §1.2) ─────────────────────────────
887//
888// A small deterministic state machine for the initiator's first exchange: emit the IKE_SA_INIT request,
889// then consume the responder's IKE_SA_INIT and derive the SA keys. The ephemeral material (SPI, the D-H
890// key pair, the nonce) is the CALLER's (supplied from the platform RNG), so the core stays pure and
891// host-testable; the context carries only what the next step needs. The IKE_AUTH exchange is the next
892// increment.
893
894/** @brief Handshake progress for the initiator's IKE_SA_INIT exchange. */
895/** @brief Largest IKE_SA_INIT message the handshake stores as its RealMessage (for the AUTH octets). */
896#define PC_IKE_MSG_MAX 640
897
898enum class IkeState : uint8_t
899{
900 IKE_ST_INIT = 0, ///< nothing sent yet
901 IKE_ST_SA_INIT_SENT, ///< IKE_SA_INIT request emitted, awaiting the response
902 IKE_ST_SA_INIT_DONE, ///< response consumed, SA keys derived (ready for IKE_AUTH)
903 IKE_ST_AUTH_SENT, ///< IKE_AUTH request emitted, awaiting the response
904 IKE_ST_ESTABLISHED, ///< responder's IKE_AUTH verified; the IKE SA is up
905 IKE_ST_FAILED, ///< a received message was rejected
906};
907
908/** @brief Initiator handshake context: the SA under construction plus what the next step needs. */
909struct IkeHandshake
910{
911 IkeSa sa; ///< the SA being established (keys filled after SA_INIT)
912 IkeState state; ///< @ref IkeState
913 uint8_t our_dh_priv[PC_IKE_X25519_LEN]; ///< our ephemeral D-H private (to compute g^ir on the response)
914 uint8_t our_nonce[PC_IKE_NONCE_MAX]; ///< Ni (needed for key derivation + later the AUTH octets)
915 uint16_t our_nonce_len;
916 uint8_t peer_nonce[PC_IKE_NONCE_MAX]; ///< Nr, captured from the response (the AUTH octets sign over it)
917 uint16_t peer_nonce_len;
918 uint8_t init_msg[PC_IKE_MSG_MAX]; ///< our IKE_SA_INIT bytes = RealMessage1 (signed by the AUTH)
919 uint16_t init_msg_len;
920 uint8_t resp_msg[PC_IKE_MSG_MAX]; ///< the responder's IKE_SA_INIT = RealMessage2 (verifies its AUTH)
921 uint16_t resp_msg_len;
922};
923
924/**
925 * @brief Begin the initiator handshake: fill @p hs and emit the IKE_SA_INIT request into @p out.
926 *
927 * The request is HDR | SA(one proposal from @p transforms) | KE(@p our_dh_pub) | Nonce(@p our_nonce),
928 * with the responder SPI 0 and message id 0. @p our_dh_priv / @p our_dh_pub are the caller's ephemeral
929 * X25519 key pair (group @p suite->dh, which must be curve25519 today); @p our_spi is a fresh 8-byte SPI.
930 * @return the request length written to @p out, or 0 on a bad argument / overflow.
931 */
932size_t pc_ike_initiator_start(IkeHandshake *hs, const uint8_t our_spi[PC_IKE_SPI_LEN],
933 const uint8_t our_dh_priv[PC_IKE_X25519_LEN], const uint8_t our_dh_pub[PC_IKE_X25519_LEN],
934 const uint8_t *our_nonce, size_t nonce_len, const IkeSuite *suite,
935 const IkeTransform *transforms, uint8_t num_transforms, uint8_t *out, size_t out_cap);
936
937/**
938 * @brief Consume the responder's IKE_SA_INIT: validate it, capture the responder SPI + KE + nonce, and
939 * derive the SA keys (@p hs->sa.keys). Advances @p hs->state to IKE_ST_SA_INIT_DONE, or
940 * IKE_ST_FAILED on a mismatch.
941 * @return true on success (keys derived), false if the message is malformed, echoes the wrong initiator
942 * SPI, or the key derivation fails.
943 */
944bool pc_ike_initiator_on_sa_init(IkeHandshake *hs, const uint8_t *resp, size_t resp_len);
945
946/**
947 * @brief Emit the initiator's IKE_AUTH request (PSK auth) into @p out: SK{ IDi | AUTH }.
948 *
949 * Requires @p hs in IKE_ST_SA_INIT_DONE. Builds the IDi payload from @p idi_type / @p idi_data, computes
950 * AUTH = prf(prf(PSK, "Key Pad for IKEv2"), RealMessage1 | Nr | prf(SK_pi, IDi')) (RFC 7296 §2.15) over
951 * the stored IKE_SA_INIT + the responder nonce, and wraps IDi | AUTH in the SK envelope keyed by SK_ei
952 * (the salt is SK_ei's 4-byte tail) with the caller's 8-byte @p iv. Advances @p hs to IKE_ST_AUTH_SENT.
953 * @return the message length, or 0 on a bad state / argument / overflow.
954 */
955size_t pc_ike_initiator_build_auth_psk(IkeHandshake *hs, IkeIdType idi_type, const uint8_t *idi_data, size_t idi_len,
956 const uint8_t *psk, size_t psk_len, const uint8_t iv[PC_IKE_GCM_IV_LEN],
957 uint8_t *out, size_t out_cap);
958
959/**
960 * @brief Consume the responder's IKE_AUTH (PSK): decrypt SK{ IDr | AUTH } with SK_er, then verify the
961 * responder's AUTH over ResponderSignedOctets = RealMessage2 | Ni | prf(SK_pr, IDr') in constant
962 * time. Requires @p hs in IKE_ST_AUTH_SENT; advances to IKE_ST_ESTABLISHED, or IKE_ST_FAILED on a
963 * decrypt / parse / verify failure.
964 * @return true iff the responder authenticated (the IKE SA is now up).
965 */
966bool pc_ike_initiator_on_auth_psk(IkeHandshake *hs, const uint8_t *resp, size_t resp_len, const uint8_t *psk,
967 size_t psk_len);
968
969// ── tier 2: responder IKE_SA_INIT handshake driver (RFC 7296 §1.2) ─────────────────────────────
970//
971// The responder mirror of pc_ike_initiator_start / _on_sa_init: consume the initiator's IKE_SA_INIT,
972// emit the IKE_SA_INIT response, and derive the SA keys - after which both peers hold identical SK_*.
973// The IkeHandshake fields keep their role-neutral meaning: init_msg = RealMessage1 (the request),
974// resp_msg = RealMessage2 (our response), our_nonce = our nonce, peer_nonce = the peer's.
975
976/**
977 * @brief Consume an initiator's IKE_SA_INIT and emit the response into @p out, deriving the SA keys.
978 *
979 * @p our_spi is the responder's fresh SPI; @p our_dh_priv / @p our_dh_pub the ephemeral X25519 key pair;
980 * @p our_nonce the responder nonce (Nr); @p suite the accepted cipher suite (its group must match the
981 * request's KE); @p transforms the echoed accepted proposal. Sets @p hs (role = responder) to
982 * IKE_ST_SA_INIT_DONE.
983 * @return the response message length written to @p out, or 0 on a malformed / mismatched request or overflow.
984 */
985size_t pc_ike_responder_on_sa_init(IkeHandshake *hs, const uint8_t *req, size_t req_len,
986 const uint8_t our_spi[PC_IKE_SPI_LEN], const uint8_t our_dh_priv[PC_IKE_X25519_LEN],
987 const uint8_t our_dh_pub[PC_IKE_X25519_LEN], const uint8_t *our_nonce,
988 size_t nonce_len, const IkeSuite *suite, const IkeTransform *transforms,
989 uint8_t num_transforms, uint8_t *out, size_t out_cap);
990
991/**
992 * @brief Consume the initiator's IKE_AUTH (PSK) and emit the responder's, reaching ESTABLISHED.
993 *
994 * Requires @p hs in IKE_ST_SA_INIT_DONE (responder role). Decrypts SK{ IDi | AUTH } with SK_ei, verifies
995 * the initiator's AUTH over RealMessage1 | Nr | prf(SK_pi, IDi') in constant time, then builds this side's
996 * IDr (from @p idr_type / @p idr_data) + AUTH over RealMessage2 | Ni | prf(SK_pr, IDr') and wraps IDr |
997 * AUTH in SK{} keyed by SK_er with @p iv. Advances to IKE_ST_ESTABLISHED, or IKE_ST_FAILED on any miss.
998 * @return the response message length, or 0 on a decrypt / verify / build failure.
999 */
1000size_t pc_ike_responder_on_auth_psk(IkeHandshake *hs, const uint8_t *req, size_t req_len, const uint8_t *psk,
1001 size_t psk_len, IkeIdType idr_type, const uint8_t *idr_data, size_t idr_len,
1002 const uint8_t iv[PC_IKE_GCM_IV_LEN], uint8_t *out, size_t out_cap);
1003
1004// ── tier 2: INFORMATIONAL exchange (RFC 7296 §1.4) over an established SA ───────────────────────
1005//
1006// Once an SA is IKE_ST_ESTABLISHED, either peer may run an INFORMATIONAL exchange: Dead-Peer Detection
1007// (an empty request, @p first_inner_type = IKE_PL_NONE, @p inner_len = 0), a Delete, or a Notify. The
1008// message is SK-encrypted keyed by our egress direction (SK_ei when we are the original initiator, else
1009// SK_er) with the flags carrying INITIATOR/RESPONSE independently.
1010
1011/**
1012 * @brief Build an SK-encrypted INFORMATIONAL message we are SENDING over @p sa (an empty inner is DPD).
1013 * @return the message length, or 0 on a bad argument / overflow.
1014 */
1015size_t pc_ike_informational_build(const IkeSa *sa, bool is_response, uint32_t msg_id, IkePayloadType first_inner_type,
1016 const uint8_t *inner, size_t inner_len, const uint8_t iv[PC_IKE_GCM_IV_LEN],
1017 uint8_t *out, size_t out_cap);
1018
1019/**
1020 * @brief Verify + decrypt a received INFORMATIONAL @p msg in place (keyed by the peer's egress direction),
1021 * exposing the inner payload chain. @return true iff the ICV verifies.
1022 */
1023bool pc_ike_informational_open(const IkeSa *sa, uint8_t *msg, size_t len, IkePayloadType *first_inner_type,
1024 const uint8_t **inner_out, size_t *inner_len_out);
1025
1026// ── tier 2: CREATE_CHILD_SA exchange (RFC 7296 §1.3, §2.17) ─────────────────────────────────────
1027//
1028// Create a new Child SA (or rekey) over an established IKE SA. The message is SK-encrypted like an
1029// INFORMATIONAL; the inner SA | Ni | Nr | [KEi/KEr] | TSi | TSr chain is the caller's (tier-1 builders).
1030// The Child SA's ESP keys come from the §2.17 key schedule below.
1031
1032/** @brief Build an SK-encrypted CREATE_CHILD_SA message we are SENDING (open it with pc_ike_informational_open). */
1033size_t pc_ike_create_child_sa_build(const IkeSa *sa, bool is_response, uint32_t msg_id, IkePayloadType first_inner_type,
1034 const uint8_t *inner, size_t inner_len, const uint8_t iv[PC_IKE_GCM_IV_LEN],
1035 uint8_t *out, size_t out_cap);
1036
1037/**
1038 * @brief Derive Child SA keying material: KEYMAT = prf+(SK_d, [g^ir |] Ni | Nr) (RFC 7296 §2.17), written
1039 * end to end into @p out (SK_ei | SK_ai | SK_er | SK_ar; the caller slices by the ESP transforms).
1040 * @param dh_secret NULL for no-PFS; the new g^ir (X25519) for a PFS rekey. @param out_len the total ESP
1041 * key length needed.
1042 * @return false on a null arg, an out-of-range length, or more than 255 prf+ blocks.
1043 */
1044bool pc_ike_child_keymat(const uint8_t *sk_d, size_t sk_d_len, const uint8_t *dh_secret, size_t dh_len,
1045 const uint8_t *ni, size_t ni_len, const uint8_t *nr, size_t nr_len, uint8_t *out,
1046 size_t out_len);
1047
1048/**
1049 * @brief Verify a peer's RSA-2048 (PKCS#1 v1.5, SHA-256) AUTH signature over the signed octets against
1050 * its public key (@p n_be = 256-byte modulus, @p e_be4 = 4-byte exponent, from its CERT).
1051 *
1052 * The device signs with its own ECDSA-P256 key (pc_ike_auth_sign_ecdsa_p256); this covers the common
1053 * case of authenticating a PEER whose certificate is RSA. @p scratch holds the assembled signed octets.
1054 * @return true iff the signature is valid.
1055 */
1056bool pc_ike_auth_verify_rsa_sha256(const uint8_t *n_be, const uint8_t *e_be4, const uint8_t *sig, size_t sig_len,
1057 uint8_t *scratch, size_t scratch_cap, const uint8_t *real, size_t real_len,
1058 const uint8_t *nonce, size_t nonce_len, const uint8_t *sk_p, size_t sk_p_len,
1059 const uint8_t *id_body, size_t id_body_len);
1060
1061#endif // PC_ENABLE_IKEV2
1062
1063#endif // PROTOCORE_IKEV2_H
User-facing configuration for ProtoCore.