ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
nts.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file nts.h
6 * @brief Network Time Security (NTS, RFC 8915) wire codec (PROTOCORE_ENABLE_NTS).
7 *
8 * NTS secures NTP against spoofing. It has two wire formats, both codified here:
9 *
10 * - **NTS-KE** (Key Establishment, RFC 8915 sec 4), a short record exchange run over TLS 1.3 on port
11 * 4460: TLV records `[critical|type : u16][body-length : u16][body]`. The client offers a next
12 * protocol (NTPv4) + an AEAD algorithm (AES-SIV-CMAC-256); the server returns cookies + the
13 * negotiated AEAD (+ optional server/port). `protocore_nts_ke_record` / `_request` build the request and
14 * `protocore_nts_ke_parse` walks a response, surfacing each record via a callback.
15 *
16 * - **NTS-protected NTP** (RFC 8915 sec 5), NTPv4 with RFC 7822 extension fields: the Unique
17 * Identifier, the NTS Cookie, and the NTS Authenticator-and-Encrypted-Extension-Fields (AEAD nonce +
18 * ciphertext). `protocore_nts_ef` builds a padded extension field; `protocore_nts_ef_unique_id` /
19 * `_cookie` are the common ones.
20 *
21 * Pure framing, zero heap, no stdlib, host-testable. The AES-SIV-CMAC-256 AEAD (RFC 5297) that protects
22 * the authenticator, and the TLS-exporter key derivation (sec 5.1), are the crypto integration on top -
23 * the label constants for that derivation are exposed here.
24 */
25
26#ifndef PROTOCORE_NTS_H
27#define PROTOCORE_NTS_H
28
29#include "protocore_config.h" // the entry point: protocore_types.h for the widths
30
31#if PROTOCORE_ENABLE_NTS
32
34
35// This module holds nothing between calls, so it carves no borrow and states none. An entry
36// takes one all the same, and never reads it, so every namespace in the tree is invoked the
37// same way.
38
39/** @brief NTS-KE record types (RFC 8915 sec 4). The critical bit is 0x8000. */
40#define NTS_KE_CRITICAL 0x8000
41#define NTS_KE_END_OF_MESSAGE 0
42#define NTS_KE_NEXT_PROTOCOL 1
43#define NTS_KE_ERROR 2
44#define NTS_KE_WARNING 3
45#define NTS_KE_AEAD_ALGORITHM 4
46#define NTS_KE_COOKIE 5
47#define NTS_KE_NTPV4_SERVER 6
48#define NTS_KE_NTPV4_PORT 7
49#define NTS_NEXT_PROTO_NTPV4 0 ///< the only next-protocol defined.
50#define NTS_AEAD_AES_SIV_CMAC_256 15 ///< the mandatory-to-implement AEAD (RFC 5297 / IANA id 15).
51
52/** @brief NTS NTP extension-field types (RFC 8915 sec 5.3; RFC 7822 EF format). */
53#define NTS_EF_UNIQUE_IDENTIFIER 0x0104
54#define NTS_EF_COOKIE 0x0204
55#define NTS_EF_COOKIE_PLACEHOLDER 0x0304
56#define NTS_EF_AUTH_AND_ENCRYPTED 0x0404
57
58/** @brief One record surfaced by protocore_nts_ke_parse. */
59typedef void (*protocore_nts_ke_cb)(proto_bool critical, uint16_t type, const uint8_t *body, size_t body_len,
60 void *arg);
61
62/** @brief What ke_record takes: critical, type, body, body_len, out, ... */
63typedef struct
64{
65 proto_bool critical;
66 uint16_t type;
67 const uint8_t *body;
68 size_t body_len;
69 uint8_t *out;
70 size_t cap;
71} NtsKeRecordArgs;
72
73/** @brief What ke_request takes: out, cap. */
74typedef struct
75{
76 uint8_t *out;
77 size_t cap;
78} NtsKeRequestArgs;
79
80/** @brief What ke_parse takes: buf, len, cb, arg. */
81typedef struct
82{
83 const uint8_t *buf;
84 size_t len;
85 protocore_nts_ke_cb cb;
86 void *arg;
87} NtsKeParseArgs;
88
89/** @brief What ef takes: field_type, value, value_len, out, cap. */
90typedef struct
91{
92 uint16_t field_type; ///< the NTS_EF_* type
93 const uint8_t *value; ///< the field value (may be null when value_len == 0)
94 size_t value_len; ///< value length
95 uint8_t *out;
96 size_t cap;
97} NtsEfArgs;
98
99/** @brief What ef_unique_id takes: nonce, nonce_len, out, cap. */
100typedef struct
101{
102 const uint8_t *nonce;
103 size_t nonce_len;
104 uint8_t *out;
105 size_t cap;
106} NtsEfUniqueIdArgs;
107
108/** @brief What ef_cookie takes: cookie, cookie_len, out, cap. */
109typedef struct
110{
111 const uint8_t *cookie;
112 size_t cookie_len;
113 uint8_t *out;
114 size_t cap;
115} NtsEfCookieArgs;
116
117/** @brief RFC 8915 sec 5.1 TLS exporter label + per-direction context (C2S = 0x0000_0001_00, S2C = ..01). */
118extern const char NTS_EXPORTER_LABEL[]; ///< "EXPORTER-network-time-security".
119
120/**
121 * @brief Network Time Security (NTS, RFC 8915) wire codec (PROTOCORE_ENABLE_NTS).
122 *
123 * A caller sets the members a call takes, invokes it through ::Nts with the bytes it runs
124 * out of, and reads the outcome off the same handle.
125 *
126 * Nts.ke_record_args.critical = ...;
127 * Nts.ke_record_args.type = ...;
128 * Nts.ke_record_args.body = ...;
129 * Nts.ke_record_args.body_len = ...;
130 * Nts.ke_record_args.out = ...;
131 * Nts.ke_record_args.cap = ...;
132 * Nts.ke_record(work);
133 * // Nts.n is what the call reports
134 *
135 * @var NtsNs::ke_record_args what ke_record takes: critical, type, body, body_len, out,
136 * @var NtsNs::ke_request_args what ke_request takes: out, cap
137 * @var NtsNs::ke_parse_args what ke_parse takes: buf, len, cb, arg
138 * @var NtsNs::ef_args what ef takes: field_type, value, value_len, out, cap
139 * @var NtsNs::ef_unique_id_args what ef_unique_id takes: nonce, nonce_len, out, cap
140 * @var NtsNs::ef_cookie_args what ef_cookie takes: cookie, cookie_len, out, cap
141 * @var NtsNs::ok true if the stream is well-formed and ends with an End-of-Message ...
142 * @var NtsNs::n the total field length written (a multiple of 4), or 0 if it won't ...
143 * @var NtsNs::ke_record build one NTS-KE record `[critical|type][len][body]`. bytes ...
144 * @var NtsNs::ke_request build the standard NTS-KE client request: Next Protocol (NTPv4), ...
145 * @var NtsNs::ke_parse walk an NTS-KE record stream, invoking cb for each record
146 * @var NtsNs::ef build an RFC 7822 extension field ...
147 * @var NtsNs::ef_unique_id build a Unique Identifier EF (>= 32 bytes of the caller's random, ...
148 * @var NtsNs::ef_cookie build an NTS Cookie EF carrying cookie
149 *
150 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
151 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
152 * a caller drives every namespace the same way.
153 */
154typedef struct
155{
156 NtsKeRecordArgs ke_record_args;
157 NtsKeRequestArgs ke_request_args;
158 NtsKeParseArgs ke_parse_args;
159 NtsEfArgs ef_args;
160 NtsEfUniqueIdArgs ef_unique_id_args;
161 NtsEfCookieArgs ef_cookie_args;
162 proto_bool ok;
163 size_t n;
164} NtsVars;
165
166/** @brief The operands and the outcome. */
167extern NtsVars NtsV;
168
169/** @brief The entries. */
170typedef struct
171{
172 void (*const ke_record)(uint8_t *work);
173 void (*const ke_request)(uint8_t *work);
174 void (*const ke_parse)(uint8_t *work);
175 void (*const ef)(uint8_t *work);
176 void (*const ef_unique_id)(uint8_t *work);
177 void (*const ef_cookie)(uint8_t *work);
178} NtsNs;
179
180// What the table binds, defined once in the .c and taking one parameter each: everything
181// else an entry needs is an operand in NtsV or a region of the borrow at a fixed offset.
182void protocore_nts_ke_record(uint8_t *work);
183void protocore_nts_ke_request(uint8_t *work);
184void protocore_nts_ke_parse(uint8_t *work);
185void protocore_nts_ef(uint8_t *work);
186void protocore_nts_ef_unique_id(uint8_t *work);
187void protocore_nts_ef_cookie(uint8_t *work);
188
189// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
190// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
191// `Nts.ke_record(work)` resolves to a named function and becomes a DIRECT call. An extern table
192// leaves the call indirect and the symbol live at every level, -O2 -flto included.
193static const NtsNs Nts __attribute__((unused)) = {
194 .ke_record = protocore_nts_ke_record,
195 .ke_request = protocore_nts_ke_request,
196 .ke_parse = protocore_nts_ke_parse,
197 .ef = protocore_nts_ef,
198 .ef_unique_id = protocore_nts_ef_unique_id,
199 .ef_cookie = protocore_nts_ef_cookie,
200};
201
203
204#endif // PROTOCORE_ENABLE_NTS
205
206#endif // PROTOCORE_NTS_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97