cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
seq_auth.c
Go to the documentation of this file.
3
5#if !defined(CWIST_PLATFORM_HOSTED)
6/* WASM links no OpenSSL: HMAC-SHA256 comes from the bundled header-only
7 * implementation and entropy from the host (crypto.getRandomValues under
8 * Emscripten, getentropy/__wasi_random_get under WASI). */
10#else
11#include <openssl/hmac.h>
12#include <openssl/mem.h>
13#include <openssl/rand.h>
14#endif
15#include <limits.h>
16#include <pthread.h>
17#include <string.h>
18#if defined(__EMSCRIPTEN__)
19#include <emscripten.h>
20#endif
21#if defined(__wasi__)
22#include <unistd.h> /* getentropy */
23#endif
24#if defined(CWIST_BAREMETAL)
25#include <cwist/sys/port.h>
26#endif
27
28#if !defined(CWIST_PLATFORM_HOSTED)
29#define SEQ_AUTH_WASM_CRYPTO 1
30#define SEQ_AUTH_TAG_CAP CWIST_SHA256_DIGEST_LEN
31/* Constant-time compare + secure cleanse without OpenSSL. */
44static int seq_auth_memcmp(const uint8_t *a, const uint8_t *b, size_t len) {
45 uint8_t diff = 0;
46 for (size_t i = 0; i < len; i++) diff |= (uint8_t)(a[i] ^ b[i]);
47 return (int)diff;
48}
49#define SEQ_AUTH_MEMCMP(a, b, len) seq_auth_memcmp((a), (b), (len))
50#define SEQ_AUTH_CLEANSE(ptr, len) memset((ptr), 0, (len))
51#else
52#define SEQ_AUTH_TAG_CAP EVP_MAX_MD_SIZE
53#define SEQ_AUTH_MEMCMP(a, b, len) CRYPTO_memcmp((a), (b), (len))
54#define SEQ_AUTH_CLEANSE(ptr, len) OPENSSL_cleanse((ptr), (len))
55#endif
56
57#if defined(__EMSCRIPTEN__)
58EM_JS(int, seq_auth_random_js, (uint8_t *buf, int len), {
59 // clang-format off - JS body, not C: keep make format away from it
60 if (typeof crypto == = 'undefined' || !crypto.getRandomValues) return 0;
61 crypto.getRandomValues(new Uint8Array(Module.HEAPU8.buffer, buf, len));
62 return 1;
63 // clang-format on
64});
65#endif
66
67struct cwist_seq_auth_context {
70 uint8_t *nonces;
71 uint8_t *completed_ids;
72 size_t capacity;
73 size_t nonce_count;
74 size_t completed_count;
75 size_t nonce_next;
76 size_t completed_next;
77 pthread_mutex_t lock;
78};
79
86static uint16_t read_u16(const uint8_t *p) {
87 return (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
88}
89
105static bool seq_auth_chunk_valid(const uint8_t *data, size_t len, cwist_seq_chunk_t *out) {
106 if (!data || len < CWIST_SEQ_HEADER_SIZE || !out) return false;
107 out->seq = read_u16(data);
108 out->total = read_u16(data + 2);
109 out->payload_len = read_u16(data + 4);
110 out->chunk_size = read_u16(data + 6);
111 if (!out->seq || !out->total || out->total > CWIST_SEQ_MAX_CHUNKS || out->seq > out->total ||
112 !out->payload_len || !out->chunk_size || out->payload_len > out->chunk_size ||
113 (out->seq != out->total && out->payload_len != out->chunk_size) ||
114 len < (size_t)CWIST_SEQ_AUTH_HEADER_SIZE + out->payload_len)
115 return false;
117 return true;
118}
119
138static bool auth_tag(const cwist_seq_auth_context_t *ctx, const uint8_t *header_without_tag,
139 size_t header_len, const uint8_t *payload, size_t payload_len,
141#ifdef SEQ_AUTH_WASM_CRYPTO
142 /* Streaming HMAC-SHA256 (RFC 2104) over session_id || header || payload.
143 * The 32-byte key never exceeds the 64-byte block size, so no key
144 * pre-hashing is needed. */
145 uint8_t k[64] = {0};
146 memcpy(k, ctx->key, sizeof(ctx->key));
147 uint8_t ipad[64], opad[64];
148 for (size_t i = 0; i < 64; i++) {
149 ipad[i] = (uint8_t)(k[i] ^ 0x36);
150 opad[i] = (uint8_t)(k[i] ^ 0x5c);
151 }
154 cwist_sha256_init(&c);
155 cwist_sha256_update(&c, ipad, sizeof(ipad));
156 cwist_sha256_update(&c, ctx->session_id, sizeof(ctx->session_id));
157 cwist_sha256_update(&c, header_without_tag, header_len);
158 cwist_sha256_update(&c, payload, payload_len);
159 cwist_sha256_final(&c, inner);
160 cwist_sha256_init(&c);
161 cwist_sha256_update(&c, opad, sizeof(opad));
162 cwist_sha256_update(&c, inner, sizeof(inner));
163 cwist_sha256_final(&c, tag);
164 SEQ_AUTH_CLEANSE(ipad, sizeof(ipad));
165 SEQ_AUTH_CLEANSE(opad, sizeof(opad));
166 SEQ_AUTH_CLEANSE(inner, sizeof(inner));
167 return true;
168#else
169 HMAC_CTX *h = HMAC_CTX_new();
170 unsigned int tag_len = 0;
171 bool ok = h && HMAC_Init_ex(h, ctx->key, sizeof(ctx->key), EVP_sha256(), NULL) == 1 &&
172 HMAC_Update(h, ctx->session_id, sizeof(ctx->session_id)) == 1 &&
173 HMAC_Update(h, header_without_tag, header_len) == 1 &&
174 HMAC_Update(h, payload, payload_len) == 1 && HMAC_Final(h, tag, &tag_len) == 1 &&
176 HMAC_CTX_free(h);
177 return ok;
178#endif
179}
180
194static bool cache_contains(const uint8_t *cache, size_t count, size_t width, const uint8_t *value) {
195 for (size_t i = 0; i < count; ++i)
196 if (SEQ_AUTH_MEMCMP(cache + i * width, value, width) == 0) return true;
197 return false;
198}
199
213static void cache_add(uint8_t *cache, size_t *count, size_t *next, size_t capacity, size_t width,
214 const uint8_t *value) {
215 memcpy(cache + *next * width, value, width);
216 *next = (*next + 1) % capacity;
217 if (*count < capacity) (*count)++;
218}
219
238 const uint8_t session_id[CWIST_SEQ_AUTH_SESSION_ID_SIZE],
239 size_t replay_window) {
240 if (!key || !session_id || replay_window == 0 || replay_window > SIZE_MAX / 28) return NULL;
241 cwist_seq_auth_context_t *ctx = cwist_alloc(sizeof(*ctx));
242 if (!ctx) return NULL;
243 memset(ctx, 0, sizeof(*ctx));
244 if (pthread_mutex_init(&ctx->lock, NULL) != 0) {
245 cwist_free(ctx);
246 return NULL;
247 }
250 if (!ctx->nonces || !ctx->completed_ids) {
252 return NULL;
253 }
254 memcpy(ctx->key, key, sizeof(ctx->key));
255 memcpy(ctx->session_id, session_id, sizeof(ctx->session_id));
256 ctx->capacity = replay_window;
257 return ctx;
258}
259
269 if (!ctx) return;
270 SEQ_AUTH_CLEANSE(ctx->key, sizeof(ctx->key));
271 SEQ_AUTH_CLEANSE(ctx->session_id, sizeof(ctx->session_id));
272 pthread_mutex_destroy(&ctx->lock);
273 cwist_free(ctx->nonces);
274 cwist_free(ctx->completed_ids);
275 cwist_free(ctx);
276}
277
290bool cwist_seq_auth_random(uint8_t *out, size_t len) {
291 if (!out || len == 0 || len > INT_MAX) return false;
292#if defined(__EMSCRIPTEN__)
293 return seq_auth_random_js(out, (int)len) == 1;
294#elif defined(__wasi__)
295 return getentropy(out, len) == 0;
296#elif defined(CWIST_BAREMETAL)
297 return cwist_port_entropy(out, len) == 0;
298#else
299 return RAND_bytes(out, (int)len) == 1;
300#endif
301}
302
324 const uint8_t message_id[CWIST_SEQ_AUTH_MESSAGE_ID_SIZE],
325 const uint8_t nonce[CWIST_SEQ_AUTH_NONCE_SIZE], const uint8_t *chunk,
326 size_t chunk_len, uint8_t **out, size_t *out_len) {
328 if (!ctx || !message_id || !nonce || !out || !out_len ||
330 chunk_len != (size_t)CWIST_SEQ_HEADER_SIZE + parsed.payload_len)
331 return false;
333 if (!wire) return false;
338 memcpy(wire + CWIST_SEQ_AUTH_HEADER_SIZE, parsed.payload, parsed.payload_len);
340 if (!auth_tag(ctx, wire,
343 parsed.payload, parsed.payload_len, tag)) {
344 cwist_free(wire);
345 return false;
346 }
350 *out = wire;
351 *out_len = CWIST_SEQ_AUTH_HEADER_SIZE + parsed.payload_len;
352 return true;
353}
354
374bool cwist_seq_auth_unwrap(cwist_seq_auth_context_t *ctx, const uint8_t *data, size_t len,
377 if (!ctx || !metadata || !chunk || !seq_auth_chunk_valid(data, len, &parsed) ||
378 len != (size_t)CWIST_SEQ_AUTH_HEADER_SIZE + parsed.payload_len)
379 return false;
380 const uint8_t *message_id = data + CWIST_SEQ_HEADER_SIZE;
381 const uint8_t *nonce = message_id + CWIST_SEQ_AUTH_MESSAGE_ID_SIZE;
382 const uint8_t *tag = nonce + CWIST_SEQ_AUTH_NONCE_SIZE;
384 if (!auth_tag(ctx, data,
387 parsed.payload, parsed.payload_len, expected) ||
389 return false;
390 pthread_mutex_lock(&ctx->lock);
391 bool replayed =
392 cache_contains(ctx->nonces, ctx->nonce_count, CWIST_SEQ_AUTH_NONCE_SIZE, nonce) ||
393 cache_contains(ctx->completed_ids, ctx->completed_count, CWIST_SEQ_AUTH_MESSAGE_ID_SIZE,
394 message_id);
395 if (replayed) {
396 pthread_mutex_unlock(&ctx->lock);
397 return false;
398 }
399 cache_add(ctx->nonces, &ctx->nonce_count, &ctx->nonce_next, ctx->capacity,
401 pthread_mutex_unlock(&ctx->lock);
402 memcpy(metadata->message_id, message_id, CWIST_SEQ_AUTH_MESSAGE_ID_SIZE);
404 *chunk = parsed;
405 return true;
406}
407
419 const uint8_t message_id[CWIST_SEQ_AUTH_MESSAGE_ID_SIZE]) {
420 if (!ctx || !message_id) return;
421 pthread_mutex_lock(&ctx->lock);
422 if (cache_contains(ctx->completed_ids, ctx->completed_count, CWIST_SEQ_AUTH_MESSAGE_ID_SIZE,
423 message_id)) {
424 pthread_mutex_unlock(&ctx->lock);
425 return;
426 }
427 cache_add(ctx->completed_ids, &ctx->completed_count, &ctx->completed_next, ctx->capacity,
429 pthread_mutex_unlock(&ctx->lock);
430}
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
void * cwist_alloc_array(size_t count, size_t elem_size)
Allocate zeroed memory for count elements.
Definition alloc.c:21
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
#define EVP_sha256
Definition evp.h:25
mbedTLS TLS backend: HMAC subset (HMAC-SHA256, one-shot + streaming).
#define HMAC_Init_ex
Definition hmac.h:24
struct cwist_mtls_hmac_ctx HMAC_CTX
Definition hmac.h:19
#define HMAC_Update
Definition hmac.h:25
#define HMAC_CTX_new
Definition hmac.h:22
#define HMAC_Final
Definition hmac.h:26
#define HMAC_CTX_free
Definition hmac.h:23
mbedTLS TLS backend: OPENSSL_cleanse (BoringSSL puts it here).
size_t count
Definition otel.c:58
size_t cap
Definition otel.c:56
Target capability macros shared by all CWIST sources.
Hooks a bare-metal firmware supplies to CWIST (issue #447).
int cwist_port_entropy(void *buf, size_t len)
Fill a buffer with cryptographically secure random bytes.
mbedTLS TLS backend: RAND_bytes over mbedTLS CTR-DRBG.
#define RAND_bytes
Definition rand.h:17
#define CWIST_SEQ_MAX_CHUNKS
Definition seq.h:36
bool cwist_seq_chunk_parse(const uint8_t *data, size_t len, cwist_seq_chunk_t *out)
Parse a sequence chunk from raw bytes.
Definition seq.c:63
#define CWIST_SEQ_HEADER_SIZE
Definition seq.h:29
cwist_seq_auth_context_t * cwist_seq_auth_context_create(const uint8_t key[CWIST_SEQ_AUTH_KEY_SIZE], const uint8_t session_id[CWIST_SEQ_AUTH_SESSION_ID_SIZE], size_t replay_window)
Create an authenticated-sequencing context.
Definition seq_auth.c:237
bool cwist_seq_auth_wrap(const cwist_seq_auth_context_t *ctx, const uint8_t message_id[CWIST_SEQ_AUTH_MESSAGE_ID_SIZE], const uint8_t nonce[CWIST_SEQ_AUTH_NONCE_SIZE], const uint8_t *chunk, size_t chunk_len, uint8_t **out, size_t *out_len)
Attach message id, nonce, and authentication tag to a chunk.
Definition seq_auth.c:323
#define SEQ_AUTH_CLEANSE(ptr, len)
Definition seq_auth.c:50
bool cwist_seq_auth_random(uint8_t *out, size_t len)
Fill a buffer with cryptographically secure random bytes.
Definition seq_auth.c:290
void cwist_seq_auth_context_destroy(cwist_seq_auth_context_t *ctx)
Destroy an authenticated-sequencing context.
Definition seq_auth.c:268
#define SEQ_AUTH_TAG_CAP
Definition seq_auth.c:30
#define SEQ_AUTH_MEMCMP(a, b, len)
Definition seq_auth.c:49
bool cwist_seq_auth_unwrap(cwist_seq_auth_context_t *ctx, const uint8_t *data, size_t len, cwist_seq_auth_metadata_t *metadata, cwist_seq_chunk_t *chunk)
Verify, authenticate, and split an incoming authenticated chunk.
Definition seq_auth.c:374
void cwist_seq_auth_mark_complete(cwist_seq_auth_context_t *ctx, const uint8_t message_id[CWIST_SEQ_AUTH_MESSAGE_ID_SIZE])
Record a message id as fully received.
Definition seq_auth.c:418
Authenticated v2 envelope for CWIST sequenced payloads.
#define CWIST_SEQ_AUTH_NONCE_SIZE
Definition seq_auth.h:21
#define CWIST_SEQ_AUTH_SESSION_ID_SIZE
Definition seq_auth.h:19
#define CWIST_SEQ_AUTH_TAG_SIZE
Definition seq_auth.h:22
#define CWIST_SEQ_AUTH_MESSAGE_ID_SIZE
Definition seq_auth.h:20
#define CWIST_SEQ_AUTH_HEADER_SIZE
Definition seq_auth.h:23
struct cwist_seq_auth_context cwist_seq_auth_context_t
Definition seq_auth.h:27
#define CWIST_SEQ_AUTH_KEY_SIZE
Definition seq_auth.h:18
Minimal self-contained SHA-256 and HMAC-SHA256 (RFC 4634 style).
#define CWIST_SHA256_DIGEST_LEN
Definition sha256.h:21
Parsed view of one sequenced chunk.
Definition seq.h:45
uint16_t payload_len
Payload length in this chunk.
Definition seq.h:48
uint16_t total
Total chunks in the message.
Definition seq.h:47
uint16_t chunk_size
Payload length of a full chunk.
Definition seq.h:49
const uint8_t * payload
Pointer to payload bytes.
Definition seq.h:50
uint16_t seq
1-based sequence number.
Definition seq.h:46