ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
tls_policy.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 tls_policy.h
6 * @brief TLS version negotiation + pinned cipher-suite policy (PROTOCORE_ENABLE_TLS_POLICY).
7 *
8 * The transport TLS layer already runs the record and handshake and floors the version at TLS 1.2,
9 * so both TLS 1.2 (RFC 5246) and TLS 1.3 (RFC 8446) are negotiated. What this adds on top is a
10 * policy: pin the negotiated version to an audited [min,max] range and make the chosen version
11 * observable, and pin the cipher suites to an audited allowlist selected by server preference
12 * (AEAD-only for a hardened profile).
13 *
14 * The pure policy core: @ref TlsPolicyNs::negotiate picks the version the way a server does (the
15 * highest it supports not above the client's), @ref TlsPolicyNs::name names it for a status
16 * endpoint, @ref TlsPolicyNs::select picks a suite by server preference from the offered set, and
17 * @ref TlsPolicyNs::is_aead classifies one. Host-testable; the app feeds the results to the TLS
18 * config. No heap, no stdlib.
19 *
20 * @author Douglas Quigg (dstroy0)
21 * @date 2026
22 */
23
24#ifndef PROTOCORE_TLS_POLICY_H
25#define PROTOCORE_TLS_POLICY_H
26
27#include "protocore_config.h" // the entry point: protocore_types.h for the widths
28
29#if PROTOCORE_ENABLE_TLS_POLICY
30
32
33/** @brief TLS protocol version wire words. */
34#define TLS_VERSION_1_2 0x0303
35#define TLS_VERSION_1_3 0x0304
36
37// This module holds nothing between calls, so it carves no borrow and states none. An entry takes
38// one all the same, and never reads it, so every namespace in the tree is invoked the same way.
39
40/** @brief The client's offer and the server's supported range. */
41typedef struct
42{
43 uint16_t client_max; ///< the client's highest offered version
44 uint16_t server_min; ///< the lowest version this server accepts
45 uint16_t server_max; ///< the highest it supports
46} TlsPolicyNegotiateArgs;
47
48/** @brief The version word a name is asked for. */
49typedef struct
50{
51 uint16_t version; ///< the wire word
52} TlsPolicyNameArgs;
53
54/** @brief The offered suites, and the pinned list that orders the choice. */
55typedef struct
56{
57 const uint16_t *client_offered; ///< what the client sent
58 size_t n_client; ///< how many
59 const uint16_t *server_pinned; ///< the audited allowlist, in preference order
60 size_t n_server; ///< how many
61} TlsPolicySelectArgs;
62
63/** @brief The suite a classification is asked about. */
64typedef struct
65{
66 uint16_t suite; ///< the wire id
67} TlsPolicyAeadArgs;
68
69/**
70 * @brief TLS version and cipher-suite policy.
71 *
72 * A caller sets the members a call takes, invokes it through ::TlsPolicy with the bytes it runs out
73 * of, and reads the outcome off the same handle.
74 *
75 * TlsPolicy.negotiate_args.client_max = TLS_VERSION_1_3;
76 * TlsPolicy.negotiate_args.server_min = TLS_VERSION_1_2;
77 * TlsPolicy.negotiate_args.server_max = TLS_VERSION_1_3;
78 * TlsPolicy.negotiate(work);
79 * // TlsPolicy.version is the chosen word, 0 when the ranges do not overlap
80 *
81 * @var TlsPolicyNs::negotiate_args the client's offer and the server's supported range
82 * @var TlsPolicyNs::name_args the version word a name is asked for
83 * @var TlsPolicyNs::select_args the offered suites, and the pinned list that orders the choice
84 * @var TlsPolicyNs::aead_args the suite a classification is asked about
85 * @var TlsPolicyNs::ok a call's true/false outcome
86 * @var TlsPolicyNs::version the negotiated version word, 0 when the ranges do not overlap
87 * @var TlsPolicyNs::suite the selected suite id, 0 when none of the pinned suites was offered
88 * @var TlsPolicyNs::text the version's human name: "TLS 1.2", "TLS 1.3", or "unknown"
89 * @var TlsPolicyNs::aead whether the suite is one of the modern AEAD suites
90 * @var TlsPolicyNs::negotiate the highest supported version not above the client's
91 * @var TlsPolicyNs::name name a version word for a status endpoint
92 * @var TlsPolicyNs::select the first pinned suite the client also offered (server preference)
93 * @var TlsPolicyNs::is_aead classify a suite as GCM / ChaCha20-Poly1305 or not
94 *
95 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing between
96 * calls, so there is no state to keep and nothing to wipe. The parameter is there so a caller drives
97 * every namespace the same way.
98 *
99 * No storage member and no context: a caller sets operands and reads @ref TlsPolicyNs::ok, and that
100 * is all the surface there is.
101 */
102typedef struct
103{
104 TlsPolicyNegotiateArgs negotiate_args;
105 TlsPolicyNameArgs name_args;
106 TlsPolicySelectArgs select_args;
107 TlsPolicyAeadArgs aead_args;
108 proto_bool ok;
109 uint16_t version;
110 uint16_t suite;
111 const char *text;
112 proto_bool aead;
113} TlsPolicyVars;
114
115/** @brief The operands and the outcome. */
116extern TlsPolicyVars TlsPolicyV;
117
118/** @brief The entries. */
119typedef struct
120{
121 void (*const negotiate)(uint8_t *work);
122 void (*const name)(uint8_t *work);
123 void (*const select)(uint8_t *work);
124 void (*const is_aead)(uint8_t *work);
125} TlsPolicyNs;
126
127// What the table binds, defined once in the .c and taking one parameter each: everything
128// else an entry needs is an operand in TlsPolicyV or a region of the borrow at a fixed offset.
129void protocore_tls_policy_negotiate(uint8_t *work);
130void protocore_tls_policy_name(uint8_t *work);
131void protocore_tls_policy_select(uint8_t *work);
132void protocore_tls_policy_is_aead(uint8_t *work);
133
134// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
135// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
136// `TlsPolicy.negotiate(work)` resolves to a named function and becomes a DIRECT call. An extern table
137// leaves the call indirect and the symbol live at every level, -O2 -flto included.
138static const TlsPolicyNs TlsPolicy __attribute__((unused)) = {
139 .negotiate = protocore_tls_policy_negotiate,
140 .name = protocore_tls_policy_name,
141 .select = protocore_tls_policy_select,
142 .is_aead = protocore_tls_policy_is_aead,
143};
144
146
147#endif // PROTOCORE_ENABLE_TLS_POLICY
148
149#endif // PROTOCORE_TLS_POLICY_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