ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
csrf.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 csrf.h
6 * @brief Stateless HMAC-signed CSRF token (PROTOCORE_ENABLE_CSRF).
7 *
8 * A token is `<nonce_hex>.<sig_hex>` where sig is the first CSRF_SIG_BYTES of
9 * HMAC-SHA256(secret, nonce). The secret is seeded once from the platform's randomness; the nonce is
10 * a per-issue counter and need not be secret, because the security is the HMAC. A verify recomputes
11 * the HMAC over the embedded nonce and compares the signature in constant time, so no server-side
12 * session state is kept.
13 *
14 * The token is sized to fit a single MAX_VAL_LEN header value and a `csrf=` cookie. Nothing here
15 * touches a platform, so it runs on the host with PROTOCORE_ENABLE_CSRF set and a fixed secret.
16 *
17 * @author Douglas Quigg (dstroy0)
18 * @date 2026
19 */
20
21#ifndef PROTOCORE_CSRF_H
22#define PROTOCORE_CSRF_H
23
24#include "protocore_config.h" // the entry point: protocore_types.h for the widths
25
26#if PROTOCORE_ENABLE_CSRF
27
29
30/** @brief Nonce length in bytes (hex-encoded in the token). */
31#define CSRF_NONCE_BYTES 6
32/** @brief Signature length in bytes (truncated HMAC, hex-encoded in the token). */
33#define CSRF_SIG_BYTES 14
34/** @brief Buffer size for a token string: 2*nonce + '.' + 2*sig + NUL = 42, rounded up. */
35#define CSRF_TOKEN_BUF 48
36
37// PROTOCORE_CSRF_BORROW - the bytes the issuer runs out of - is stated in protocore_config.h, which
38// sums it into the secure arena. The secret lives in those bytes, so a caller takes them once for
39// the life of the program and every call runs out of the same span. How they are carved is this
40// module's and is never named here.
41
42/** @brief The secret an issuer signs with. */
43typedef struct
44{
45 const uint8_t *secret; ///< key bytes; NULL clears the secret
46 size_t len; ///< how many; past 32 the key is truncated
47} CsrfSecretArgs;
48
49/** @brief Where a fresh token lands. */
50typedef struct
51{
52 char *out; ///< destination, at least CSRF_TOKEN_BUF
53 size_t cap; ///< its size
54} CsrfIssueArgs;
55
56/** @brief The token a verify checks. */
57typedef struct
58{
59 const char *token; ///< the `<nonce_hex>.<sig_hex>` string
60} CsrfVerifyArgs;
61
62/**
63 * @brief Stateless HMAC-signed CSRF tokens.
64 *
65 * A caller sets the members a call takes, invokes it through ::Csrf with the bytes it runs out of,
66 * and reads the outcome off the same handle.
67 *
68 * Csrf.secret_args.secret = seed;
69 * Csrf.secret_args.len = sizeof(seed);
70 * Csrf.set_secret(work);
71 * Csrf.issue_args.out = buf;
72 * Csrf.issue_args.cap = sizeof(buf);
73 * Csrf.issue(work);
74 * // Csrf.n is the token length, 0 when no secret is set or the buffer is short
75 *
76 * @var CsrfNs::secret_args the secret an issuer signs with
77 * @var CsrfNs::issue_args where a fresh token lands
78 * @var CsrfNs::verify_args the token a verify checks
79 * @var CsrfNs::ok a call's true/false outcome
80 * @var CsrfNs::n the issued token's length in characters
81 * @var CsrfNs::valid whether the last @ref CsrfNs::verify accepted the token
82 * @var CsrfNs::set_secret seed the HMAC secret; call once with platform randomness
83 * @var CsrfNs::issue write a fresh signed token out
84 * @var CsrfNs::verify recompute the HMAC over the embedded nonce and compare in constant time
85 * @var CsrfNs::reset clear the secret and the nonce counter
86 *
87 * @c work is PROTOCORE_CSRF_BORROW secure bytes the CALLER took, at an address it knows. It is not held past the call,
88 * so nothing here aliases it. The secret is in those bytes rather than in this module, so a caller takes them once for
89 * the life of the program and every call runs out of the same span. The caller releases it, and the pool wipes on
90 * release; this module neither takes it, holds it, nor releases it.
91 *
92 * No storage member and no context: a caller sets operands and reads @ref CsrfNs::ok, and that is
93 * all the surface there is.
94 */
95typedef struct
96{
97 CsrfSecretArgs secret_args;
98 CsrfIssueArgs issue_args;
99 CsrfVerifyArgs verify_args;
100 proto_bool ok;
101 int n;
102 proto_bool valid;
103} CsrfVars;
104
105/** @brief The operands and the outcome. */
106extern CsrfVars CsrfV;
107
108/** @brief The entries. */
109typedef struct
110{
111 void (*const set_secret)(uint8_t *work);
112 void (*const issue)(uint8_t *work);
113 void (*const verify)(uint8_t *work);
114 void (*const reset)(uint8_t *work);
115} CsrfNs;
116
117// What the table binds, defined once in the .c and taking one parameter each: everything
118// else an entry needs is an operand in CsrfV or a region of the borrow at a fixed offset.
119void protocore_csrf_set_secret(uint8_t *work);
120void protocore_csrf_issue(uint8_t *work);
121void protocore_csrf_verify(uint8_t *work);
122void protocore_csrf_reset(uint8_t *work);
123
124// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
125// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
126// `Csrf.set_secret(work)` resolves to a named function and becomes a DIRECT call. An extern table
127// leaves the call indirect and the symbol live at every level, -O2 -flto included.
128static const CsrfNs Csrf __attribute__((unused)) = {
129 .set_secret = protocore_csrf_set_secret,
130 .issue = protocore_csrf_issue,
131 .verify = protocore_csrf_verify,
132 .reset = protocore_csrf_reset,
133};
134
135/**
136 * @brief The PROTOCORE_CSRF_BORROW bytes the program's issuer runs out of.
137 *
138 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where that
139 * borrow comes from. Taken once from the end of the secure pool, which no mark and no release walks,
140 * so the secret and the nonce counter last the life of the program.
141 *
142 * @return the span.
143 */
144uint8_t *protocore_csrf_span(void);
145
147
148#endif // PROTOCORE_ENABLE_CSRF
149
150#endif // PROTOCORE_CSRF_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