cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
csrf.c
Go to the documentation of this file.
1
2#define _POSIX_C_SOURCE 200809L
6#include <errno.h>
7#include <fcntl.h>
8#include <string.h>
9#include <unistd.h>
10
11#define CWIST_CSRF_COOKIE_NAME "csrf_token"
12#define CWIST_CSRF_TOKEN_BYTES 32
13#define CWIST_CSRF_TOKEN_CHARS (CWIST_CSRF_TOKEN_BYTES * 2)
14
20static bool csrf_safe_method(cwist_http_method_t method) {
21 return method == CWIST_HTTP_GET || method == CWIST_HTTP_HEAD || method == CWIST_HTTP_OPTIONS;
22}
23
32static char *csrf_generate_token(void) {
33 unsigned char bytes[CWIST_CSRF_TOKEN_BYTES];
34 int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
35 if (fd < 0) return NULL;
36 size_t got = 0;
37 while (got < sizeof(bytes)) {
38 ssize_t n = read(fd, bytes + got, sizeof(bytes) - got);
39 if (n > 0) {
40 got += (size_t)n;
41 continue;
42 }
43 if (n < 0 && errno == EINTR) continue;
44 close(fd);
45 return NULL;
46 }
47 close(fd);
48 static const char hex[] = "0123456789abcdef";
50 if (!token) return NULL;
51 for (size_t i = 0; i < sizeof(bytes); ++i) {
52 token[2 * i] = hex[bytes[i] >> 4];
53 token[2 * i + 1] = hex[bytes[i] & 15U];
54 }
56 return token;
57}
58
66static bool csrf_equal(const char *a, const char *b) {
67 if (!a || !b) return false;
68 size_t alen = strlen(a), blen = strlen(b);
69 size_t diff = alen ^ blen;
70 size_t limit = alen > blen ? alen : blen;
71 for (size_t i = 0; i < limit; ++i) {
72 unsigned char ac = i < alen ? (unsigned char)a[i] : 0;
73 unsigned char bc = i < blen ? (unsigned char)b[i] : 0;
74 diff |= (size_t)(ac ^ bc);
75 }
76 return diff == 0;
77}
78
84static int hex_value(unsigned char c) {
85 if (c >= '0' && c <= '9') return c - '0';
86 if (c >= 'a' && c <= 'f') return c - 'a' + 10;
87 if (c >= 'A' && c <= 'F') return c - 'A' + 10;
88 return -1;
89}
90
102static char *csrf_form_value(const cwist_http_request *req) {
103 if (!req || !req->body || !req->body->data) return NULL;
104 const char *body = req->body->data;
105 size_t len = req->body->size;
106 size_t pos = 0;
107 while (pos < len) {
108 size_t field_start = pos;
109 while (pos < len && body[pos] != '&') ++pos;
110 size_t field_end = pos++;
111 const char *eq = memchr(body + field_start, '=', field_end - field_start);
112 size_t key_len = eq ? (size_t)(eq - (body + field_start)) : field_end - field_start;
113 if (!((key_len == 5 && memcmp(body + field_start, "_csrf", 5) == 0) ||
114 (key_len == 10 && memcmp(body + field_start, "csrf_token", 10) == 0)))
115 continue;
116 const char *value = eq ? eq + 1 : "";
117 size_t value_len = eq ? field_end - (size_t)(value - body) : 0;
118 char *decoded = cwist_alloc(value_len + 1);
119 if (!decoded) return NULL;
120 size_t out = 0;
121 for (size_t i = 0; i < value_len; ++i) {
122 unsigned char c = (unsigned char)value[i];
123 if (c == '+')
124 decoded[out++] = ' ';
125 else if (c == '%' && i + 2 < value_len) {
126 int hi = hex_value((unsigned char)value[i + 1]);
127 int lo = hex_value((unsigned char)value[i + 2]);
128 if (hi < 0 || lo < 0) {
130 return NULL;
131 }
132 decoded[out++] = (char)((hi << 4) | lo);
133 i += 2;
134 } else
135 decoded[out++] = (char)c;
136 }
137 decoded[out] = '\0';
138 return decoded;
139 }
140 return NULL;
141}
142
152static void csrf_issue_cookie(cwist_http_request *req, cwist_http_response *res,
153 const char *token) {
154 cwist_cookie_options options = {0};
155 options.path = "/";
156 options.max_age_seconds = 30 * 24 * 60 * 60;
157 options.http_only = false;
158 options.secure = req && req->app && req->app->use_ssl;
159 options.same_site = "Strict";
161}
162
175static void csrf_handler(cwist_http_request *req, cwist_http_response *res,
176 cwist_handler_func next) {
177 const char *cookie_header = cwist_http_header_get(req->headers, "Cookie");
179 const char *cookie = cookies ? cwist_cookie_get(cookies, CWIST_CSRF_COOKIE_NAME) : NULL;
180 if (cookies) {
183 }
184 if (req->csrf_token) {
186 req->csrf_token = NULL;
187 }
188 if (!cookie) {
189 char *token = csrf_generate_token();
190 if (!token) {
191 if (cookies) cwist_query_map_destroy(cookies);
193 return;
194 }
195 req->csrf_token = token;
196 csrf_issue_cookie(req, res, token);
197 if (cookies) cwist_query_map_destroy(cookies);
198 if (csrf_safe_method(req->method))
199 next(req, res);
200 else {
202 cwist_sstring_assign(res->body, "CSRF token missing");
203 }
204 return;
205 }
207 if (csrf_safe_method(req->method)) {
209 next(req, res);
210 return;
211 }
212 const char *header = cwist_http_header_get(req->headers, "X-CSRF-Token");
213 char *form = header ? NULL : csrf_form_value(req);
214 bool valid = csrf_equal(header ? header : form, cookie);
215 if (form) cwist_free(form);
217 if (!valid) {
219 cwist_sstring_assign(res->body, "CSRF token invalid");
220 return;
221 }
222 next(req, res);
223}
224
231 (void)app;
232 return csrf_handler;
233}
241 return req ? req->csrf_token : NULL;
242}
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
char * cwist_strdup(const char *src)
Duplicate a string using libttak-backed storage.
Definition alloc.c:32
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
HTTP cookie parsing and construction helpers.
const char * cwist_cookie_get(cwist_query_map *map, const char *name)
Get a cookie value by name from a parsed map.
Definition cookie.c:114
int cwist_cookie_set(cwist_http_response *res, const char *name, const char *value, const cwist_cookie_options *opts)
Add a Set-Cookie header to the response.
Definition cookie.c:119
void cwist_cookie_parse(cwist_query_map *map, const char *header)
Parse a Cookie header value into a query-style map.
Definition cookie.c:80
cwist_error_t cwist_sstring_assign(cwist_sstring *str, const char *data)
Assign a C string.
Definition sstring.c:504
#define CWIST_CSRF_TOKEN_BYTES
Definition csrf.c:12
cwist_middleware_func cwist_mw_csrf(cwist_app *app)
Create the CSRF protection middleware.
Definition csrf.c:230
#define CWIST_CSRF_COOKIE_NAME
Definition csrf.c:11
#define CWIST_CSRF_TOKEN_CHARS
Definition csrf.c:13
const char * cwist_csrf_token(cwist_http_request *req)
Get the CSRF token associated with a request.
Definition csrf.c:240
CSRF protection via double-submit cookie pattern.
char * cwist_http_header_get(cwist_http_header_node *head, const char *key)
Find a header value by key.
Definition http.c:1880
@ CWIST_HTTP_FORBIDDEN
Definition http.h:33
@ CWIST_HTTP_INTERNAL_ERROR
Definition http.h:35
cwist_http_method_t
Definition http.h:16
@ CWIST_HTTP_OPTIONS
Definition http.h:23
@ CWIST_HTTP_HEAD
Definition http.h:22
@ CWIST_HTTP_GET
Definition http.h:17
cwist_query_map * cwist_query_map_create(void)
Create a new query map.
Definition query.c:54
void cwist_query_map_destroy(cwist_query_map *map)
Destroy a query map.
Definition query.c:81
size_t cap
Definition otel.c:56
Main Application Context.
Definition app.h:155
bool use_ssl
Definition app.h:157
char * csrf_token
CSRF token populated by csrf middleware.
Definition http.h:186
struct cwist_app * app
Owning app context (if any).
Definition http.h:176
cwist_sstring * body
Definition http.h:73
cwist_http_header_node * headers
Definition http.h:72
cwist_http_method_t method
Definition http.h:66
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
Definition http.h:82
cwist_sstring * body
Definition http.h:87
cwist_http_status_t status_code
Definition http.h:84
size_t size
Definition sstring.h:20
char * data
Access directly only when raw handling is necessary.
Definition sstring.h:15
void(* cwist_handler_func)(cwist_http_request *req, cwist_http_response *res)
Function pointer type for HTTP route handlers.
Definition app.h:32
void(* cwist_middleware_func)(cwist_http_request *req, cwist_http_response *res, cwist_handler_func next)
Middleware type that receives req/res pair and the next stage in the chain.
Definition app.h:115